feat: 升级至 v0.3.4 — 接入 Xiaomi MiMo Provider + 文档全量校准

新增 MiMo (小米) LLM Provider 适配器,支持 mimo-v2.5-pro 和 mimo-v2.5 两个文本模型,复用 OpenAI 兼容 SSE 流式解析,支持 Thinking 模式和 Function Calling。同步校准全量 docs 文档与 README 使其与实际代码一致。

主要变更:
- 新增 mimo.adapter.ts 适配器(SSE + thinking.type + max_completion_tokens)
- 修复 thinking 逻辑 bug:禁用思考时未传 temperature/top_p
- 补全 sse-stream.ts 的 MiMo 缓存字段映射(prompt_tokens_details.cached_tokens)
- 补全 sse-stream.ts 的 finish_reason 映射(repetition_truncation)
- 注册 MiMo 适配器到 adapters/index.ts、main.ts 工厂
- handlers.ts 添加 mimo.contextWindow 热重载触发
- database.service.ts seed 添加 mimo 默认配置
- SettingsModal/OnboardingWizard/Header 添加 MiMo Provider UI
- constants.ts PROVIDER_LABELS 添加 mimo
- .env.example 添加 MIMO_API_KEY/MIMO_BASE_URL
- 反向修改 4 个 docs HTML 设计文档(工具数量/版本日期/适配器列表/数据库表)
- 反向修改 Agent网络工具通用设计-v2.md 附录 B 文件索引
- 完全重写 README.md(v0.3.4、27 工具、4 适配器、9 表)
This commit is contained in:
thzxx
2026-07-15 22:28:09 +08:00
parent 4554177db0
commit 656c6b7af1
22 changed files with 1677 additions and 115 deletions
+3 -1
View File
@@ -1,9 +1,10 @@
/**
* Provider Adapter 导出
*
* 种 Provider 各自独立继承 BaseAdapter,无耦合关系:
* 种 Provider 各自独立继承 BaseAdapter,无耦合关系:
* - DeepSeekAdapter — OpenAI 兼容 + DeepSeek 特有参数
* - AgnesAdapter — OpenAI 兼容 + Agnes 特有参数
* - MimoAdapter — OpenAI 兼容 + MiMo 特有参数
* - OllamaAdapter — Ollama 原生 API
*
* 共享工具(仅供 OpenAI 兼容 Adapter 使用):
@@ -14,4 +15,5 @@
export { BaseAdapter } from './base-adapter';
export { DeepSeekAdapter } from './deepseek.adapter';
export { AgnesAdapter } from './agnes-ai.adapter';
export { MimoAdapter } from './mimo.adapter';
export { OllamaAdapter } from './ollama.adapter';
+204
View File
@@ -0,0 +1,204 @@
/**
* MiMo (Xiaomi) Provider Adapter
*
* 基于 OpenAI 兼容 API。支持 Tool Calling、Thinking 模式、流式输出。
* 模型: mimo-v2.5-pro131072 max_tokens/ mimo-v2.532768 max_tokens
*
* 独立继承 BaseAdapter,通过 shared/openai-format 和 shared/sse-stream 复用
* OpenAI 兼容格式构建和 SSE 流式解析逻辑。不与其他 Provider Adapter 耦合。
*
* 与 DeepSeek 适配器的关键差异:
* - 使用 max_completion_tokens(非 max_tokens
* - thinking 参数结构与 DeepSeek 一致(thinking.type: "enabled"/"disabled"
* - 不提供 /models 端点(listModels 回退到本地元数据)
* - 不提供 /user/balance 端点
* - tool_choice 仅支持 "auto"
*
* @see apis/mimo-api-docs-20260715.html
*/
import { BaseAdapter } from './base-adapter';
import type { MetonaRequest, MetonaResponse, MetonaStreamEvent } from '../types';
import { MetonaFinishReason } from '../types';
import type { MetonaModelInfo } from '../types/metona-adapter';
import { buildOpenAICompatibleMessages, buildOpenAICompatibleTools } from './shared/openai-format';
import { parseSSEStream, parseOpenAICompatibleResponse } from './shared/sse-stream';
export class MimoAdapter extends BaseAdapter {
override readonly providerId: string = 'mimo';
readonly supportedModels = ['mimo-v2.5-pro', 'mimo-v2.5'];
readonly supportsToolCalling = true;
readonly supportsThinking = true;
// MiMo 模型元信息
// mimo-v2.5-pro: 131072 max_completion_tokensmimo-v2.5: 32768
// 官方未公布上下文窗口大小,保守设为 131072(与 pro 的 max_output 一致)
private static readonly MODEL_INFO: Record<string, MetonaModelInfo> = {
'mimo-v2.5-pro': {
id: 'mimo-v2.5-pro',
name: 'MiMo V2.5 Pro',
contextWindow: 131_072,
maxOutputTokens: 131_072,
supportsToolCalling: true,
supportsThinking: true,
description: '小米 MiMo 旗舰模型,支持深度思考与工具调用',
},
'mimo-v2.5': {
id: 'mimo-v2.5',
name: 'MiMo V2.5',
contextWindow: 131_072,
maxOutputTokens: 32_768,
supportsToolCalling: true,
supportsThinking: true,
description: '小米 MiMo 标准模型,低延迟推理',
},
};
// ===== POST /chat/completions (非流式) =====
async send(request: MetonaRequest): Promise<MetonaResponse> {
const body = this.toNativeRequest(request, false);
const response = await fetch(`${this.config.baseURL}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.config.apiKey}`,
...this.config.headers,
},
body: JSON.stringify(body),
signal: this.getFetchSignal(this.config.timeoutMs ?? 120_000),
});
if (!response.ok) {
const errorBody = await response.text().catch(() => '');
throw new Error(`MiMo API error: ${response.status} ${response.statusText} - ${errorBody}`);
}
const data = await response.json() as Record<string, unknown>;
const parsed = parseOpenAICompatibleResponse(data, request.meta.requestId, this.providerId, this.config.defaultModel);
return {
meta: {
requestId: request.meta.requestId,
provider: this.providerId,
model: (data.model as string) ?? this.config.defaultModel,
latencyMs: 0,
timestamp: Date.now(),
},
content: parsed.content,
reasoningContent: parsed.reasoningContent,
toolCalls: parsed.toolCalls,
usage: parsed.usage,
finishReason: parsed.finishReason as MetonaFinishReason,
};
}
// ===== POST /chat/completions (流式) =====
async *sendStream(request: MetonaRequest): AsyncIterable<MetonaStreamEvent> {
const body = this.toNativeRequest(request, true);
const response = await fetch(`${this.config.baseURL}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.config.apiKey}`,
...this.config.headers,
},
body: JSON.stringify(body),
signal: this.getFetchSignal(this.config.timeoutMs ?? 300_000),
});
if (!response.ok || !response.body) {
throw new Error(`MiMo stream error: ${response.status}`);
}
yield* parseSSEStream(
response.body,
request.meta.requestId,
request.meta.sessionId,
request.meta.iteration,
);
}
// ===== 模型列表 =====
/**
* MiMo 官方未提供 /models 端点,直接返回本地元数据。
*/
override async listModels(): Promise<MetonaModelInfo[]> {
return this.supportedModels.map((id) => MimoAdapter.MODEL_INFO[id] ?? { id });
}
/**
* 获取上下文窗口大小
*
* v0.3.1: 优先使用配置注入的 contextWindow,回退到 MODEL_INFO 默认值。
* MiMo OpenAI 兼容 API 不支持 context_window 参数,此值仅用于
* Engine 压缩判断和前端 UI 显示。
*/
override getContextWindow(): number {
if (typeof this.config.contextWindow === 'number' && this.config.contextWindow > 0) {
return this.config.contextWindow;
}
const modelInfo = MimoAdapter.MODEL_INFO[this.config.defaultModel];
return modelInfo?.contextWindow ?? 131_072;
}
// ========== 私有方法 ==========
/**
* 构建 MiMo 原生请求体
*
* MiMo 特有参数:
* - thinking: { type: "enabled" / "disabled" } — 与 DeepSeek 一致
* - max_completion_tokens — 非 max_tokensMiMo 使用新字段名)
* - stream_options: { include_usage: true } — 流式返回 usage
* - tool_choice: "auto" — MiMo 仅支持 auto
*
* 思考模式下 temperature/top_p 会被 API 强制覆盖,因此不传这两个参数。
*/
private toNativeRequest(request: MetonaRequest, stream: boolean): Record<string, unknown> {
const messages = buildOpenAICompatibleMessages(request);
const tools = buildOpenAICompatibleTools(request.tools);
const body: Record<string, unknown> = {
model: this.config.defaultModel,
messages,
// MiMo 使用 max_completion_tokens(非 max_tokens
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';
}
// Thinking 模式(与 DeepSeek 参数结构一致)
// MiMo API 默认 thinking.type = "enabled",必须显式发送 disabled 才能关闭
if (request.params.thinkingEnabled === false) {
// 显式禁用思考:传 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 (request.params.stopSequences?.length) {
body.stop = request.params.stopSequences;
}
return body;
}
}
@@ -2,11 +2,12 @@
* OpenAI 兼容 API 格式构建工具
*
* 将 MetonaRequest 转换为 OpenAI /chat/completions 兼容的原生请求格式。
* DeepSeekAgnes AI 共享此工具,各自 Adapter 只需处理 Provider 特有的差异参数。
* DeepSeekAgnes AI 和 MiMo 共享此工具,各自 Adapter 只需处理 Provider 特有的差异参数。
*
* @see electron/harness/types/metona-request.ts — MetonaRequest 定义
* @see apis/deepseek-api-docs-20260518.html
* @see apis/agnes-ai-api-docs-20260625.html
* @see apis/mimo-api-docs-20260715.html
*/
import type { MetonaRequest, MetonaToolDef } from '../../types';
+10 -3
View File
@@ -2,7 +2,7 @@
* SSE 流式解析工具
*
* 解析 OpenAI 兼容的 Server-Sent Events (SSE) 流式响应,
* 产出 MetonaStreamEvent。DeepSeekAgnes AI 共享此工具。
* 产出 MetonaStreamEvent。DeepSeekAgnes AI 和 MiMo 共享此工具。
*
* SSE 格式:data: {json}\n\n
* 结束标记:data: [DONE]
@@ -174,7 +174,10 @@ export async function* parseSSEStream(
outputTokens: chunk.usage.completion_tokens ?? 0,
totalTokens: chunk.usage.total_tokens ?? 0,
reasoningTokens: chunk.usage.completion_tokens_details?.reasoning_tokens,
cacheHitTokens: chunk.usage.prompt_cache_hit_tokens,
// DeepSeek: prompt_cache_hit_tokens / prompt_cache_miss_tokens
// MiMo: prompt_tokens_details.cached_tokens
cacheHitTokens: chunk.usage.prompt_cache_hit_tokens
?? chunk.usage.prompt_tokens_details?.cached_tokens,
cacheMissTokens: chunk.usage.prompt_cache_miss_tokens,
};
@@ -252,7 +255,9 @@ export function parseOpenAICompatibleResponse(
outputTokens: (usage?.completion_tokens as number) ?? 0,
totalTokens: (usage?.total_tokens as number) ?? 0,
reasoningTokens: (usage?.completion_tokens_details as Record<string, unknown>)?.reasoning_tokens as number | undefined,
cacheHitTokens: usage?.prompt_cache_hit_tokens as number | undefined,
// DeepSeek: prompt_cache_hit_tokens / MiMo: prompt_tokens_details.cached_tokens
cacheHitTokens: (usage?.prompt_cache_hit_tokens as number | undefined)
?? (usage?.prompt_tokens_details as Record<string, unknown> | undefined)?.cached_tokens as number | undefined,
cacheMissTokens: usage?.prompt_cache_miss_tokens as number | undefined,
},
};
@@ -264,6 +269,8 @@ function mapOpenAIFinishReason(reason: string): string {
case 'length': return 'length';
case 'tool_calls': return 'tool_calls';
case 'content_filter': return 'content_filter';
// MiMo 特有:检测到复读截断
case 'repetition_truncation': return 'stop';
default: return 'stop';
}
}
+1 -1
View File
@@ -38,7 +38,7 @@ export interface MetonaModelInfo {
* Provider 适配器配置
*/
export interface AdapterConfig {
/** Provider 标识(如 'deepseek'、'agnes'、'ollama' */
/** Provider 标识(如 'deepseek'、'agnes'、'mimo'、'ollama' */
provider: string;
/** API 基础 URL */
baseURL: string;
+4 -4
View File
@@ -667,9 +667,9 @@ export function registerAllIPCHandlers(
});
// LLM 相关配置变更时热重载 Adapter
// v0.3.1: 加入 deepseek.contextWindow / agnes.contextWindow,使上下文窗口配置变化也触发热重载
// v0.3.1: 加入 deepseek.contextWindow / agnes.contextWindow / mimo.contextWindow,使上下文窗口配置变化也触发热重载
if (['llm.provider', 'llm.model', 'llm.apiKey', 'llm.baseURL', 'ollama.numCtx',
'deepseek.contextWindow', 'agnes.contextWindow'].includes(key)) {
'deepseek.contextWindow', 'agnes.contextWindow', 'mimo.contextWindow'].includes(key)) {
// C-1 修复: reloadAdapter 返回 false 时表示加载失败,需要通知前端
const reloadSuccess = reloadAdapter();
if (!reloadSuccess) {
@@ -700,8 +700,8 @@ export function registerAllIPCHandlers(
agentLoop.updateConfig({ contextLength: (value as number) || undefined });
orchestrator.updateDefaultConfig({ contextLength: (value as number) || undefined });
log.info(`[CONFIG] Agent contextLength updated to ${value}`);
} else if (key === 'deepseek.contextWindow' || key === 'agnes.contextWindow') {
// v0.3.1: DeepSeek/Agnes contextWindow 变更,同步到 Engine 和 Orchestrator
} else if (key === 'deepseek.contextWindow' || key === 'agnes.contextWindow' || key === 'mimo.contextWindow') {
// v0.3.1: DeepSeek/Agnes/MiMo contextWindow 变更,同步到 Engine 和 Orchestrator
// reloadAdapter 已重建 adapter,此处确保 Engine contextWindow 同步(兜底)
const ctxWindow = (value as number) || undefined;
agentLoop.updateConfig({ contextWindow: ctxWindow });
+2
View File
@@ -36,6 +36,7 @@ import { AgentLoopEngine } from './harness/agent-loop';
import { ToolRegistry } from './harness/tools/registry';
import { DeepSeekAdapter } from './harness/adapters/deepseek.adapter';
import { AgnesAdapter } from './harness/adapters/agnes-ai.adapter';
import { MimoAdapter } from './harness/adapters/mimo.adapter';
import { OllamaAdapter } from './harness/adapters/ollama.adapter';
import {
ReadFileTool, WriteFileTool, ListDirectoryTool, SearchFilesTool,
@@ -161,6 +162,7 @@ async function initialize(): Promise<void> {
const adapterConfig = { provider, baseURL, apiKey, defaultModel: model, contextWindow };
switch (provider) {
case 'agnes': return new AgnesAdapter(adapterConfig);
case 'mimo': return new MimoAdapter(adapterConfig);
case 'ollama': return new OllamaAdapter(adapterConfig);
default: return new DeepSeekAdapter(adapterConfig);
}
+2
View File
@@ -378,6 +378,8 @@ export class DatabaseService {
// v0.3.1: DeepSeek/Agnes 改为可配置,不再写死 1M
{ key: 'deepseek.contextWindow', value: '1000000', category: 'deepseek' },
{ key: 'agnes.contextWindow', value: '1000000', category: 'agnes' },
// v0.3.4: MiMo 上下文窗口(官方未公布,保守设为 131072)
{ key: 'mimo.contextWindow', value: '131072', category: 'mimo' },
// Onboarding
{ key: 'onboarding.completed', value: 'false', category: 'general' },