import { BaseChatModel, } from "@langchain/core/language_models/chat_models"; import { AIMessage, ChatMessage, HumanMessage, } from "@langchain/core/messages"; import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { convertToOpenAIFunction } from "@langchain/core/utils/function_calling"; /** * Function that extracts the custom sender_type of a generic chat message. * @param message Chat message from which to extract the custom sender_type. * @returns The custom sender_type of the chat message. */ function extractGenericMessageCustomRole(message) { if (message.role !== "ai" && message.role !== "user") { console.warn(`Unknown message role: ${message.role}`); } if (message.role === "ai") { return "BOT"; } if (message.role === "user") { return "USER"; } return message.role; } /** * Function that converts a base message to a Minimax message sender_type. * @param message Base message to convert. * @returns The Minimax message sender_type. */ function messageToMinimaxRole(message) { const type = message._getType(); switch (type) { case "ai": return "BOT"; case "human": return "USER"; case "system": throw new Error("System messages not supported"); case "function": return "FUNCTION"; case "generic": { if (!ChatMessage.isInstance(message)) throw new Error("Invalid generic chat message"); return extractGenericMessageCustomRole(message); } default: throw new Error(`Unknown message type: ${type}`); } } /** * Wrapper around Minimax large language models that use the Chat endpoint. * * To use you should have the `MINIMAX_GROUP_ID` and `MINIMAX_API_KEY` * environment variable set. * @example * ```typescript * // Define a chat prompt with a system message setting the context for translation * const chatPrompt = ChatPromptTemplate.fromMessages([ * SystemMessagePromptTemplate.fromTemplate( * "You are a helpful assistant that translates {input_language} to {output_language}.", * ), * HumanMessagePromptTemplate.fromTemplate("{text}"), * ]); * * // Create a new LLMChain with the chat model and the defined prompt * const chainB = new LLMChain({ * prompt: chatPrompt, * llm: new ChatMinimax({ temperature: 0.01 }), * }); * * // Call the chain with the input language, output language, and the text to translate * const resB = await chainB.call({ * input_language: "English", * output_language: "Chinese", * text: "I love programming.", * }); * * // Log the result * console.log({ resB }); * * ``` */ export class ChatMinimax extends BaseChatModel { static lc_name() { return "ChatMinimax"; } get callKeys() { return [ ...super.callKeys, "functions", "tools", "defaultBotName", "defaultUserName", "plugins", "replyConstraints", "botSetting", "sampleMessages", ]; } get lc_secrets() { return { minimaxApiKey: "MINIMAX_API_KEY", apiKey: "MINIMAX_API_KEY", minimaxGroupId: "MINIMAX_GROUP_ID", }; } constructor(fields) { super(fields ?? {}); Object.defineProperty(this, "lc_serializable", { enumerable: true, configurable: true, writable: true, value: true }); Object.defineProperty(this, "minimaxGroupId", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "minimaxApiKey", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "apiKey", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "streaming", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "prompt", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "modelName", { enumerable: true, configurable: true, writable: true, value: "abab5.5-chat" }); Object.defineProperty(this, "model", { enumerable: true, configurable: true, writable: true, value: "abab5.5-chat" }); Object.defineProperty(this, "defaultBotName", { enumerable: true, configurable: true, writable: true, value: "Assistant" }); Object.defineProperty(this, "defaultUserName", { enumerable: true, configurable: true, writable: true, value: "I" }); Object.defineProperty(this, "prefixMessages", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "apiUrl", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "basePath", { enumerable: true, configurable: true, writable: true, value: "https://api.minimax.chat/v1" }); Object.defineProperty(this, "headers", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "temperature", { enumerable: true, configurable: true, writable: true, value: 0.9 }); Object.defineProperty(this, "topP", { enumerable: true, configurable: true, writable: true, value: 0.8 }); Object.defineProperty(this, "tokensToGenerate", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "skipInfoMask", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "proVersion", { enumerable: true, configurable: true, writable: true, value: true }); Object.defineProperty(this, "beamWidth", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "botSetting", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "continueLastMessage", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "maskSensitiveInfo", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "roleMeta", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "useStandardSse", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "replyConstraints", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.minimaxGroupId = fields?.minimaxGroupId ?? getEnvironmentVariable("MINIMAX_GROUP_ID"); if (!this.minimaxGroupId) { throw new Error("Minimax GroupID not found"); } this.minimaxApiKey = fields?.apiKey ?? fields?.minimaxApiKey ?? getEnvironmentVariable("MINIMAX_API_KEY"); if (!this.minimaxApiKey) { throw new Error("Minimax ApiKey not found"); } this.apiKey = this.minimaxApiKey; this.streaming = fields?.streaming ?? this.streaming; this.prompt = fields?.prompt ?? this.prompt; this.temperature = fields?.temperature ?? this.temperature; this.topP = fields?.topP ?? this.topP; this.skipInfoMask = fields?.skipInfoMask ?? this.skipInfoMask; this.prefixMessages = fields?.prefixMessages ?? this.prefixMessages; this.maskSensitiveInfo = fields?.maskSensitiveInfo ?? this.maskSensitiveInfo; this.beamWidth = fields?.beamWidth ?? this.beamWidth; this.continueLastMessage = fields?.continueLastMessage ?? this.continueLastMessage; this.tokensToGenerate = fields?.tokensToGenerate ?? this.tokensToGenerate; this.roleMeta = fields?.roleMeta ?? this.roleMeta; this.botSetting = fields?.botSetting ?? this.botSetting; this.useStandardSse = fields?.useStandardSse ?? this.useStandardSse; this.replyConstraints = fields?.replyConstraints ?? this.replyConstraints; this.defaultBotName = fields?.defaultBotName ?? this.defaultBotName; this.modelName = fields?.model ?? fields?.modelName ?? this.model; this.model = this.modelName; this.basePath = fields?.configuration?.basePath ?? this.basePath; this.headers = fields?.configuration?.headers ?? this.headers; this.proVersion = fields?.proVersion ?? this.proVersion; const modelCompletion = this.proVersion ? "chatcompletion_pro" : "chatcompletion"; this.apiUrl = `${this.basePath}/text/${modelCompletion}`; } fallbackBotName(options) { let botName = options?.defaultBotName ?? this.defaultBotName ?? "Assistant"; if (this.botSetting) { botName = this.botSetting[0].bot_name; } return botName; } defaultReplyConstraints(options) { const constraints = options?.replyConstraints ?? this.replyConstraints; if (!constraints) { let botName = options?.defaultBotName ?? this.defaultBotName ?? "Assistant"; if (this.botSetting) { botName = this.botSetting[0].bot_name; } return { sender_type: "BOT", sender_name: botName, }; } return constraints; } /** * Get the parameters used to invoke the model */ invocationParams(options) { return { model: this.model, stream: this.streaming, prompt: this.prompt, temperature: this.temperature, top_p: this.topP, tokens_to_generate: this.tokensToGenerate, skip_info_mask: this.skipInfoMask, mask_sensitive_info: this.maskSensitiveInfo, beam_width: this.beamWidth, use_standard_sse: this.useStandardSse, role_meta: this.roleMeta, bot_setting: options?.botSetting ?? this.botSetting, reply_constraints: this.defaultReplyConstraints(options), sample_messages: this.messageToMinimaxMessage(options?.sampleMessages, options), functions: options?.functions ?? (options?.tools ? options?.tools.map(convertToOpenAIFunction) : undefined), plugins: options?.plugins, }; } /** * Get the identifying parameters for the model */ identifyingParams() { return { ...this.invocationParams(), }; } /** * Convert a list of messages to the format expected by the model. * @param messages * @param options */ messageToMinimaxMessage(messages, options) { return messages ?.filter((message) => { if (ChatMessage.isInstance(message)) { return message.role !== "system"; } return message._getType() !== "system"; }) ?.map((message) => { const sender_type = messageToMinimaxRole(message); if (typeof message.content !== "string") { throw new Error("ChatMinimax does not support non-string message content."); } return { sender_type, text: message.content, sender_name: message.name ?? (sender_type === "BOT" ? this.fallbackBotName() : options?.defaultUserName ?? this.defaultUserName), }; }); } /** @ignore */ async _generate(messages, options, runManager) { const tokenUsage = { totalTokens: 0 }; this.botSettingFallback(options, messages); const params = this.invocationParams(options); const messagesMapped = [ ...(this.messageToMinimaxMessage(messages, options) ?? []), ...(this.prefixMessages ?? []), ]; const data = params.stream ? await new Promise((resolve, reject) => { let response; let rejected = false; let resolved = false; this.completionWithRetry({ ...params, messages: messagesMapped, }, true, options?.signal, (event) => { const data = JSON.parse(event.data); if (data?.error_code) { if (rejected) { return; } rejected = true; reject(data); return; } const message = data; // on the first message set the response properties if (!message.choices[0].finish_reason) { // the last stream message let streamText; if (this.proVersion) { const messages = message.choices[0].messages ?? []; streamText = messages[0].text; } else { streamText = message.choices[0].delta; } // TODO this should pass part.index to the callback // when that's supported there // eslint-disable-next-line no-void void runManager?.handleLLMNewToken(streamText ?? ""); return; } response = message; if (!this.proVersion) { response.choices[0].text = message.reply; } if (resolved || rejected) { return; } resolved = true; resolve(response); }).catch((error) => { if (!rejected) { rejected = true; reject(error); } }); }) : await this.completionWithRetry({ ...params, messages: messagesMapped, }, false, options?.signal); const { total_tokens: totalTokens } = data.usage ?? {}; if (totalTokens) { tokenUsage.totalTokens = totalTokens; } if (data.base_resp?.status_code !== 0) { throw new Error(`Minimax API error: ${data.base_resp?.status_msg}`); } const generations = []; if (this.proVersion) { for (const choice of data.choices) { const messages = choice.messages ?? []; // 取最后一条消息 if (messages) { const message = messages[messages.length - 1]; const text = message?.text ?? ""; generations.push({ text, message: minimaxResponseToChatMessage(message), }); } } } else { for (const choice of data.choices) { const text = choice?.text ?? ""; generations.push({ text, message: minimaxResponseToChatMessage({ sender_type: "BOT", sender_name: options?.defaultBotName ?? this.defaultBotName ?? "Assistant", text, }), }); } } return { generations, llmOutput: { tokenUsage }, }; } /** @ignore */ async completionWithRetry(request, stream, signal, onmessage) { // The first run will get the accessToken const makeCompletionRequest = async () => { const url = `${this.apiUrl}?GroupId=${this.minimaxGroupId}`; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.apiKey}`, ...this.headers, }, body: JSON.stringify(request), signal, }); if (!stream) { const json = await response.json(); return json; } else { if (response.body) { const reader = response.body.getReader(); const decoder = new TextDecoder("utf-8"); let data = ""; let continueReading = true; while (continueReading) { const { done, value } = await reader.read(); if (done) { continueReading = false; break; } data += decoder.decode(value); let continueProcessing = true; while (continueProcessing) { const newlineIndex = data.indexOf("\n"); if (newlineIndex === -1) { continueProcessing = false; break; } const line = data.slice(0, newlineIndex); data = data.slice(newlineIndex + 1); if (line.startsWith("data:")) { const event = new MessageEvent("message", { data: line.slice("data:".length).trim(), }); onmessage?.(event); } } } return {}; } return {}; } }; return this.caller.call(makeCompletionRequest); } _llmType() { return "minimax"; } /** @ignore */ _combineLLMOutput() { return []; } botSettingFallback(options, messages) { const botSettings = options?.botSetting ?? this.botSetting; if (!botSettings) { const systemMessages = messages?.filter((message) => { if (ChatMessage.isInstance(message)) { return message.role === "system"; } return message._getType() === "system"; }); // get the last system message if (!systemMessages?.length) { return; } const lastSystemMessage = systemMessages[systemMessages.length - 1]; if (typeof lastSystemMessage.content !== "string") { throw new Error("ChatMinimax does not support non-string message content."); } // setting the default botSetting. this.botSetting = [ { content: lastSystemMessage.content, bot_name: options?.defaultBotName ?? this.defaultBotName ?? "Assistant", }, ]; } } } function minimaxResponseToChatMessage(message) { switch (message.sender_type) { case "USER": return new HumanMessage(message.text || ""); case "BOT": return new AIMessage(message.text || "", { function_call: message.function_call, }); case "FUNCTION": return new AIMessage(message.text || ""); default: return new ChatMessage(message.text || "", message.sender_type ?? "unknown"); } }