/** * MiMo (Xiaomi) Provider Adapter * * 基于 OpenAI 兼容 API。支持 Tool Calling、Thinking 模式、流式输出。 * 模型: mimo-v2.5-pro(1M 上下文 / 131072 max_tokens)/ mimo-v2.5(1M 上下文 / 32768 max_tokens) * * v0.6.4 P3-1: 继承 OpenAICompatibleAdapter —— 传输/组装/回退链收敛到共享基类, * 本文件只保留 MiMo 差异点:max_completion_tokens 字段名、tool_choice 强制 "auto"、 * 思考模式与 temperature/top_p 互斥、无 /models 端点(本地元数据列表)。 * * @see apis/mimo-api-docs-20260715.html */ import log from 'electron-log'; import type { MetonaRequest } from '../types'; import type { MetonaModelInfo } from '../types/metona-adapter'; import { buildOpenAICompatibleMessages, buildOpenAICompatibleTools } from './shared/openai-format'; import { OpenAICompatibleAdapter } from './shared/openai-compatible-base'; export class MimoAdapter extends OpenAICompatibleAdapter { override readonly providerId: string = 'mimo'; readonly supportedModels = ['mimo-v2.5-pro', 'mimo-v2.5']; readonly supportsToolCalling = true; readonly supportsThinking = true; // MiMo 模型元信息 // v0.8.1: 仅承载展示与能力声明 —— 窗口/输出上限数值已按硬性契约删除, // 唯一合法来源是设置面板 llm.contextWindow / llm.maxTokens private static readonly MODEL_INFO: Record = { 'mimo-v2.5-pro': { id: 'mimo-v2.5-pro', name: 'MiMo V2.5 Pro', supportsToolCalling: true, supportsThinking: true, description: '小米 MiMo 旗舰模型,支持深度思考与工具调用', }, 'mimo-v2.5': { id: 'mimo-v2.5', name: 'MiMo V2.5', supportsToolCalling: true, supportsThinking: true, description: '小米 MiMo 标准模型,低延迟推理', }, }; // ===== 共享基类差异声明 ===== protected override chatCompletionsUrl(): string { return `${this.config.baseURL}/chat/completions`; } protected override sendTimeoutMs(): number { return 120_000; } protected override modelInfoTable(): Record { return MimoAdapter.MODEL_INFO; } protected override providerLabel(): string { return 'MiMo'; } /** * MiMo 官方未提供 /models 端点,直接返回本地元数据。 */ override async listModels(): Promise { return this.supportedModels.map((id) => MimoAdapter.MODEL_INFO[id] ?? { id }); } // ========== 协议参数映射(MiMo 差异点) ========== protected override toNativeRequest( request: MetonaRequest, stream: boolean, ): Record { // v0.6.2: images 处理收敛至共享层(原索引对齐循环在孤立 tool 过滤后会错位) const messages = buildOpenAICompatibleMessages(request, true); const tools = buildOpenAICompatibleTools(request.tools); // MiMo 使用 max_completion_tokens(非 max_tokens) // v0.8.1 硬性契约: 原样透传设置面板「最大输出上限」(llm.maxTokens),删除了 // 旧的"#41 thinking 兜底 32768"与"按模型上限钳制"逻辑 —— 代码中不存在任何 // 写死的输出上限;未配置时该字段不下发,由服务端默认值决定。 const body: Record = { model: this.config.defaultModel, messages, max_completion_tokens: request.params.maxTokens, stream, }; if (stream) { body.stream_options = { include_usage: true }; } if (tools) { body.tools = tools; // MiMo 仅支持 tool_choice: "auto" body.tool_choice = 'auto'; } // v0.6.4 P4-3: MiMo 服务端内置工具透出 —— config.providerOptions.enableWebSearch // 开启后附加 {type:'web_search'} 服务端搜索工具(annotations 引用随响应返回, // 由上层归并为文本内容展示)。与客户端 tools 定义互不影响。 const providerOptions = this.config.providerOptions as Record | undefined; if (providerOptions?.['enableWebSearch'] === true) { const serverTools = body.tools ? [...(body.tools as Array>), { type: 'web_search' }] : [{ type: 'web_search' }]; body.tools = serverTools; if (!body.tool_choice) body.tool_choice = 'auto'; } // v0.6.4 P4-3: strict JSON 响应格式开关(response_format: json_object)—— // 供结构化抽取类任务使用;与流式模式兼容性由服务端保证(文档标注支持子集) if (providerOptions?.['responseFormatJson'] === true) { body.response_format = { type: 'json_object' }; } // Thinking 模式(与 DeepSeek 参数结构一致) // MiMo API 默认 thinking.type = "enabled",必须显式发送 disabled 才能关闭 // v0.8.0 修订(用户意图优先): 思考参数完全遵循用户配置,不再按 // supportsThinking 元信息硬门控 —— 预算耗尽风险由引擎空响应守卫的 // 降级重试链路兜底;元信息与配置不符时仅告警不拦截。 const wantThinking = request.params.thinkingEnabled !== false; if (!wantThinking) { // 显式禁用思考:传 disabled + temperature/top_p(非思考模式下这两个参数有效) body.thinking = { type: 'disabled' }; body.temperature = request.params.temperature; body.top_p = request.params.topP; } else { // 启用思考(包括 undefined,因为 MiMo 默认 enabled) // 思考模式下 temperature/top_p 被 API 强制覆盖为 1.0/0.95,不传 body.thinking = { type: 'enabled' }; // 元信息标注不支持思考但用户开启 —— 告知降级兜底路径(不拦截) if (MimoAdapter.MODEL_INFO[this.config.defaultModel]?.supportsThinking === false) { log.warn( `[MiMo] model "${this.config.defaultModel}" metadata says thinking unsupported — sending thinking params per user config (degraded retry handles budget exhaustion)`, ); } // v0.8.0 P0-3: 思考占用输出预算 —— 用户配置的输出预算过小时显式告警 const effectiveMax = body.max_completion_tokens as number | undefined; if (typeof effectiveMax === 'number' && effectiveMax < 8192) { log.warn( `[MiMo] thinking enabled with small output budget (${effectiveMax} tokens per user config) — reasoning may consume the entire budget and truncate the answer`, ); } } // 停止序列 if (request.params.stopSequences?.length) { body.stop = request.params.stopSequences; } return body; } }