Skip to content

Commit aa5f3be

Browse files
committed
Add complete MiniMax provider support
1 parent c83ecbd commit aa5f3be

8 files changed

Lines changed: 861 additions & 3 deletions

File tree

src/LLMProviders/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import LangchainChatGoogleGenerativeAIProvider from "./langchain/googleGenerativ
1414
import LangchainOpenAIAgentProvider from "./langchain/openaiAgent";
1515
import LangchainPerplexityChatProvider from "./langchain/perplexityChat";
1616
import LangchainTogetherChatProvider from "./langchain/togetherChat";
17+
import LangchainMiniMaxOpenAIProvider, {
18+
LangchainMiniMaxAnthropicProvider,
19+
} from "./langchain/minimaxChat";
1720
// import LangchainReplicaProvider from "./langchain/replica"
1821

1922
// import { LOCClone1, LOCClone2 } from "./langchain/clones";
@@ -46,6 +49,10 @@ export const defaultProviders = [
4649
// together ai
4750
LangchainTogetherChatProvider,
4851

52+
// minimax
53+
LangchainMiniMaxOpenAIProvider,
54+
LangchainMiniMaxAnthropicProvider,
55+
4956
// azure
5057
LangchainAzureOpenAIChatProvider,
5158
LangchainAzureOpenAIInstructProvider,

src/LLMProviders/interface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,10 @@ export interface LLMConfig {
8787
reasoningEffort?: "low" | "medium" | "high";
8888
/** Manual override: treat this model as a thinking/reasoning model regardless of auto-detection */
8989
isThinkingModel?: boolean;
90+
/** Thinking mode for models that support adaptive and disabled modes */
91+
thinkingMode?: "adaptive" | "disabled";
92+
/** Request admission tier for providers that support tier selection */
93+
serviceTier?: "standard" | "priority";
94+
/** Official API region selected in provider settings */
95+
apiRegion?: "Global" | "China";
9096
}
Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
import React from "react";
2+
import type { BaseLanguageModelParams } from "@langchain/core/language_models/base";
3+
import type { AnthropicInput } from "@langchain/anthropic";
4+
import type { OpenAIChatInput } from "@langchain/openai";
5+
6+
import { IconExternalLink } from "#/ui/icons";
7+
import LangchainBase from "./base";
8+
import LLMProviderInterface, { LLMConfig } from "../interface";
9+
import { HeaderEditor, ModelsHandler } from "../utils";
10+
import { Dropdown, Input, Message, SettingItem, useGlobal } from "../refs";
11+
import {
12+
calculateMiniMaxPrice,
13+
getMiniMaxEndpoint,
14+
getMiniMaxModelKwargs,
15+
MINIMAX_DOCS,
16+
MINIMAX_MODELS,
17+
MiniMaxProtocol,
18+
MiniMaxRegion,
19+
MiniMaxServiceTier,
20+
MiniMaxThinkingMode,
21+
normalizeMiniMaxMessages,
22+
wrapMiniMaxAnthropicFetch,
23+
} from "./minimaxConfig";
24+
25+
const commonDefaultValues = {
26+
apiRegion: "Global" as MiniMaxRegion,
27+
model: MINIMAX_MODELS.m3,
28+
thinkingMode: "adaptive" as const,
29+
serviceTier: "standard" as const,
30+
};
31+
32+
const openAIDefaultValues = {
33+
...commonDefaultValues,
34+
basePath: getMiniMaxEndpoint("Global", "openai"),
35+
};
36+
37+
const anthropicDefaultValues = {
38+
...commonDefaultValues,
39+
basePath: getMiniMaxEndpoint("Global", "anthropic"),
40+
};
41+
42+
type MiniMaxDefaultValues = {
43+
apiRegion: MiniMaxRegion;
44+
model: string;
45+
thinkingMode: MiniMaxThinkingMode;
46+
serviceTier: MiniMaxServiceTier;
47+
basePath: string;
48+
};
49+
50+
function MiniMaxSettings({
51+
props,
52+
protocol,
53+
defaultValues,
54+
}: {
55+
props: Parameters<LLMProviderInterface["RenderSettings"]>[0];
56+
protocol: MiniMaxProtocol;
57+
defaultValues: MiniMaxDefaultValues;
58+
}) {
59+
const global = useGlobal();
60+
const id = props.self.id;
61+
const config = (global.plugin.settings.LLMProviderOptions[id] ??= {
62+
...defaultValues,
63+
});
64+
const isM3 = (config.model || defaultValues.model) === MINIMAX_MODELS.m3;
65+
66+
return (
67+
<>
68+
<SettingItem
69+
name="API Key"
70+
register={props.register}
71+
sectionId={props.sectionId}
72+
>
73+
<Input
74+
type="password"
75+
value={config.api_key || ""}
76+
setValue={async (value) => {
77+
config.api_key = value;
78+
global.triggerReload();
79+
global.plugin.encryptAllKeys();
80+
await global.plugin.saveSettings();
81+
}}
82+
/>
83+
</SettingItem>
84+
85+
<SettingItem
86+
name="API Region"
87+
register={props.register}
88+
sectionId={props.sectionId}
89+
>
90+
<Dropdown
91+
value={config.apiRegion || defaultValues.apiRegion}
92+
values={["Global", "China"]}
93+
setValue={async (value) => {
94+
const region = value as MiniMaxRegion;
95+
config.apiRegion = region;
96+
config.basePath = getMiniMaxEndpoint(region, protocol);
97+
global.triggerReload();
98+
await global.plugin.saveSettings();
99+
}}
100+
/>
101+
</SettingItem>
102+
103+
<SettingItem
104+
name="Base Path"
105+
description={`${
106+
protocol === "openai" ? "OpenAI" : "Anthropic"
107+
}-compatible API endpoint`}
108+
register={props.register}
109+
sectionId={props.sectionId}
110+
>
111+
<Input
112+
value={config.basePath || defaultValues.basePath}
113+
placeholder="Enter your API Base Path"
114+
setValue={async (value) => {
115+
config.basePath = value || defaultValues.basePath;
116+
global.triggerReload();
117+
await global.plugin.saveSettings();
118+
}}
119+
/>
120+
</SettingItem>
121+
122+
<ModelsHandler
123+
register={props.register}
124+
sectionId={props.sectionId}
125+
llmProviderId={props.self.originalId || id}
126+
default_values={defaultValues}
127+
config={config}
128+
/>
129+
130+
{isM3 && (
131+
<>
132+
<SettingItem
133+
name="Thinking Mode"
134+
register={props.register}
135+
sectionId={props.sectionId}
136+
>
137+
<Dropdown
138+
value={config.thinkingMode || defaultValues.thinkingMode}
139+
values={["adaptive", "disabled"]}
140+
setValue={async (value) => {
141+
config.thinkingMode = value;
142+
global.triggerReload();
143+
await global.plugin.saveSettings();
144+
}}
145+
/>
146+
</SettingItem>
147+
148+
<SettingItem
149+
name="Service Tier"
150+
register={props.register}
151+
sectionId={props.sectionId}
152+
>
153+
<Dropdown
154+
value={config.serviceTier || defaultValues.serviceTier}
155+
values={["standard", "priority"]}
156+
setValue={async (value) => {
157+
config.serviceTier = value;
158+
global.triggerReload();
159+
await global.plugin.saveSettings();
160+
}}
161+
/>
162+
</SettingItem>
163+
</>
164+
)}
165+
166+
<HeaderEditor
167+
enabled={!!config.headers}
168+
setEnabled={async (value) => {
169+
config.headers = value ? "{}" : undefined;
170+
global.triggerReload();
171+
await global.plugin.saveSettings();
172+
}}
173+
headers={config.headers}
174+
setHeaders={async (value) => {
175+
config.headers = value;
176+
global.triggerReload();
177+
await global.plugin.saveSettings();
178+
}}
179+
/>
180+
181+
<div className="plug-tg-flex plug-tg-flex-col plug-tg-gap-2">
182+
<div className="plug-tg-text-lg plug-tg-opacity-70">Useful links</div>
183+
{(["Global", "China"] as MiniMaxRegion[]).map((region) => (
184+
<a key={region} href={MINIMAX_DOCS[region][protocol]}>
185+
<SettingItem
186+
name={`${region} API documentation`}
187+
className="plug-tg-text-xs plug-tg-opacity-50 hover:plug-tg-opacity-100"
188+
register={props.register}
189+
sectionId={props.sectionId}
190+
>
191+
<IconExternalLink />
192+
</SettingItem>
193+
</a>
194+
))}
195+
</div>
196+
</>
197+
);
198+
}
199+
200+
abstract class LangchainMiniMaxBaseProvider extends LangchainBase {
201+
abstract protocol: MiniMaxProtocol;
202+
abstract default_values: MiniMaxDefaultValues;
203+
abstract getConfig(options: LLMConfig): any;
204+
205+
RenderSettings(props: Parameters<LLMProviderInterface["RenderSettings"]>[0]) {
206+
return (
207+
<MiniMaxSettings
208+
props={props}
209+
protocol={this.protocol}
210+
defaultValues={this.default_values}
211+
/>
212+
);
213+
}
214+
215+
generate(
216+
messages: Message[],
217+
reqParams: Partial<Omit<LLMConfig, "n">>,
218+
onToken?: (
219+
token: string,
220+
first: boolean
221+
) => Promise<string | void | null | undefined>,
222+
customConfig?: any
223+
) {
224+
return super.generate(
225+
normalizeMiniMaxMessages(messages, this.protocol),
226+
reqParams,
227+
onToken,
228+
customConfig
229+
);
230+
}
231+
232+
generateMultiple(messages: Message[], reqParams: Partial<LLMConfig>) {
233+
return super.generateMultiple(
234+
normalizeMiniMaxMessages(messages, this.protocol),
235+
reqParams
236+
);
237+
}
238+
239+
async calcPrice(tokens: number, reqParams: Partial<LLMConfig>) {
240+
return calculateMiniMaxPrice(
241+
tokens,
242+
reqParams.max_tokens || 100,
243+
reqParams.model || MINIMAX_MODELS.m3,
244+
reqParams.serviceTier || "standard"
245+
);
246+
}
247+
}
248+
249+
export default class LangchainMiniMaxOpenAIProvider
250+
extends LangchainMiniMaxBaseProvider
251+
implements LLMProviderInterface
252+
{
253+
static provider = "Langchain" as const;
254+
static id = "MiniMax OpenAI (Langchain)" as const;
255+
static slug = "minimaxOpenAI" as const;
256+
static displayName = "MiniMax OpenAI";
257+
258+
protocol = "openai" as const;
259+
streamable = true;
260+
id = LangchainMiniMaxOpenAIProvider.id;
261+
provider = LangchainMiniMaxOpenAIProvider.provider;
262+
originalId = LangchainMiniMaxOpenAIProvider.id;
263+
default_values = openAIDefaultValues;
264+
265+
async load() {
266+
const { ChatOpenAI } = await import("@langchain/openai");
267+
this.llmClass = ChatOpenAI;
268+
}
269+
270+
getConfig(options: LLMConfig): Partial<OpenAIChatInput> {
271+
return this.cleanConfig({
272+
apiKey: options.api_key,
273+
openAIApiKey: options.api_key,
274+
modelKwargs: getMiniMaxModelKwargs(
275+
options.model,
276+
options.thinkingMode,
277+
options.serviceTier,
278+
options.modelKwargs
279+
),
280+
modelName: options.model,
281+
maxTokens: +options.max_tokens,
282+
temperature: +options.temperature,
283+
frequencyPenalty: +options.frequency_penalty || 0,
284+
presencePenalty: +options.presence_penalty || 0,
285+
n: options.n || 1,
286+
stop: options.stop || undefined,
287+
streaming: options.stream || false,
288+
maxRetries: 3,
289+
headers: options.headers || undefined,
290+
} as Partial<OpenAIChatInput>);
291+
}
292+
}
293+
294+
export class LangchainMiniMaxAnthropicProvider
295+
extends LangchainMiniMaxBaseProvider
296+
implements LLMProviderInterface
297+
{
298+
static provider = "Langchain" as const;
299+
static id = "MiniMax Anthropic (Langchain)" as const;
300+
static slug = "minimaxAnthropic" as const;
301+
static displayName = "MiniMax Anthropic";
302+
303+
protocol = "anthropic" as const;
304+
streamable = true;
305+
corsBypass = true;
306+
id = LangchainMiniMaxAnthropicProvider.id;
307+
provider = LangchainMiniMaxAnthropicProvider.provider;
308+
originalId = LangchainMiniMaxAnthropicProvider.id;
309+
default_values = anthropicDefaultValues;
310+
311+
async load() {
312+
const { ChatAnthropic } = await import("@langchain/anthropic");
313+
this.llmClass = class MiniMaxChatAnthropic extends ChatAnthropic {
314+
constructor(fields: any) {
315+
const { configuration = {}, ...rest } = fields;
316+
const fetchImpl = configuration.fetch || globalThis.fetch;
317+
super({
318+
...rest,
319+
clientOptions: {
320+
...rest.clientOptions,
321+
...configuration,
322+
fetch: wrapMiniMaxAnthropicFetch(fetchImpl),
323+
},
324+
});
325+
}
326+
};
327+
}
328+
329+
getConfig(
330+
options: LLMConfig
331+
): Partial<AnthropicInput & BaseLanguageModelParams> {
332+
const modelKwargs = getMiniMaxModelKwargs(
333+
options.model,
334+
options.thinkingMode,
335+
options.serviceTier,
336+
options.modelKwargs
337+
);
338+
const { thinking, service_tier, ...invocationKwargs } = modelKwargs as any;
339+
340+
return this.cleanConfig({
341+
anthropicApiKey: options.api_key,
342+
anthropicApiUrl: options.basePath,
343+
modelName: options.model,
344+
maxTokens: options.max_tokens,
345+
temperature:
346+
thinking?.type === "adaptive" ? undefined : options.temperature,
347+
stopSequences: options.stop,
348+
streaming: options.stream,
349+
maxRetries: 3,
350+
thinking,
351+
invocationKwargs: {
352+
...invocationKwargs,
353+
...(service_tier ? { service_tier } : {}),
354+
},
355+
} as any);
356+
}
357+
}

0 commit comments

Comments
 (0)