- DeepSeek/MiMo/Agnes 移除 supportsThinking 元信息硬门控:思考参数完全遵循用户配置 (事故复盘中 vision-exp 元信息标注不支持思考、实际产生了 8189 token 推理内容, 元信息不可靠;预算耗尽由引擎降级重试兜底,元信息不符仅告警不拦截) - Ollama 保留 /api/show 能力探测门控(服务端硬协议约束:向不支持思考的模型发 think 每次请求 400,属协议正确性而非意图覆盖),探测失败 fail-open - LLM 设置提示文案修订:元信息不符仍按用户配置发送,降级重试自动兜底 - 测试契约反向钉住:vision-exp + 用户开启→照发 enabled+reasoning_effort; 关闭/未配置→显式 disabled;Ollama 探测 false→不发 think / null→fail-open - 补录 docs/v0.8.0-迭代实施清单.md(含逐项验证记录与本次修订记录; 首次提交时该文件因故未入库,本次补齐) - 验证:typecheck 0 错误 / lint 0 问题 / 系统 Node 2146 通过 / thinking 矩阵 101 用例全绿
130 lines
5.1 KiB
TypeScript
130 lines
5.1 KiB
TypeScript
/**
|
||
* Agnes AI Provider Adapter
|
||
*
|
||
* OpenAI 兼容 API。支持 Tool Calling、Thinking 模式、多模态(图片 — URL + Base64)。
|
||
*
|
||
* v0.6.4 P3-1: 继承 OpenAICompatibleAdapter —— 传输/组装/回退链收敛到共享基类,
|
||
* 本文件只保留 Agnes 差异点:chat_template_kwargs 思考开关(v0.6.4 对称性修复)、
|
||
* 无条件 includeImages、非流式默认超时 300s。
|
||
* 注:Agnes API 未提供 /models 端点,listModels 使用基类默认实现。
|
||
*
|
||
* @see apis/agnes-ai-api-docs-20260625.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 AgnesAdapter extends OpenAICompatibleAdapter {
|
||
override readonly providerId: string = 'agnes';
|
||
readonly supportedModels = ['agnes-2.0-flash'];
|
||
readonly supportsToolCalling = true;
|
||
readonly supportsThinking = true;
|
||
|
||
// H-2 修复: Agnes 模型元信息(1M 上下文,65.5K 最大输出)
|
||
private static readonly MODEL_INFO: Record<string, MetonaModelInfo> = {
|
||
'agnes-2.0-flash': {
|
||
id: 'agnes-2.0-flash',
|
||
name: 'Agnes 2.0 Flash',
|
||
contextWindow: 1_000_000,
|
||
maxOutputTokens: 65_536,
|
||
supportsToolCalling: true,
|
||
supportsThinking: true,
|
||
description: 'Agnes AI 快速版,1M 上下文,支持多模态图片(URL + Base64)与思考模式',
|
||
},
|
||
};
|
||
|
||
// ===== 共享基类差异声明 =====
|
||
|
||
protected override chatCompletionsUrl(): string {
|
||
return `${this.config.baseURL}/chat/completions`;
|
||
}
|
||
|
||
protected override sendTimeoutMs(): number {
|
||
return 300_000;
|
||
}
|
||
|
||
protected override modelInfoTable(): Record<string, MetonaModelInfo> {
|
||
return AgnesAdapter.MODEL_INFO;
|
||
}
|
||
|
||
protected override providerLabel(): string {
|
||
return 'Agnes AI';
|
||
}
|
||
|
||
// ========== 协议参数映射(Agnes 差异点) ==========
|
||
|
||
protected override toNativeRequest(
|
||
request: MetonaRequest,
|
||
stream: boolean,
|
||
): Record<string, unknown> {
|
||
// v0.6.2: images 处理收敛至共享层(原索引对齐循环在孤立 tool 过滤后会错位)
|
||
const messages = buildOpenAICompatibleMessages(request, true);
|
||
const tools = buildOpenAICompatibleTools(request.tools);
|
||
|
||
const imageCount = request.messages.reduce((n, m) => n + (m.images?.length ?? 0), 0);
|
||
if (imageCount > 0) {
|
||
const firstUrl = request.messages.find((m) => m.images?.length)?.images?.[0]?.url ?? '';
|
||
log.info(
|
||
`[Agnes] Processing ${imageCount} image(s), first URL prefix: ${firstUrl.slice(0, 50)}`,
|
||
);
|
||
}
|
||
|
||
// v0.5.3: max_tokens 按模型上限钳制(agnes-2.0-flash 上限 65536)
|
||
const modelInfo = AgnesAdapter.MODEL_INFO[this.config.defaultModel];
|
||
const maxOutput = modelInfo?.maxOutputTokens ?? 65_536;
|
||
const maxTokens = Math.min(request.params.maxTokens ?? maxOutput, maxOutput);
|
||
|
||
const body: Record<string, unknown> = {
|
||
model: this.config.defaultModel,
|
||
messages,
|
||
temperature: request.params.temperature,
|
||
max_tokens: maxTokens,
|
||
stream,
|
||
};
|
||
|
||
if (stream) {
|
||
body.stream_options = { include_usage: true };
|
||
}
|
||
|
||
if (tools) {
|
||
body.tools = tools;
|
||
}
|
||
|
||
// C-3 修复 + v0.6.4 对称性修复: Thinking 模式 — Agnes 使用 chat_template_kwargs
|
||
// Agnes API 仅支持 enable_thinking: true/false,不支持 effort 级别
|
||
// thinkingEffort === 'low' 时映射为 false;thinkingEnabled 为 false 或未配置时
|
||
// 显式发送 enable_thinking:false —— 原实现只在 thinkingEnabled===true 时写该字段,
|
||
// 若服务端默认开启思考,客户端没有任何路径把它关掉(DeepSeek/MiMo 均显式发送
|
||
// disabled 保持对称,唯独此处漏了)。
|
||
// v0.8.0 修订(用户意图优先): enable_thinking 完全遵循用户配置,不再按
|
||
// supportsThinking 元信息硬门控 —— 预算耗尽风险由引擎空响应守卫的降级
|
||
// 重试链路兜底;元信息与配置不符时仅告警不拦截。
|
||
{
|
||
const effort = request.params.thinkingEffort ?? 'high';
|
||
// 未配置 thinkingEnabled 一律显式关闭 —— 与 DeepSeek/MiMo 的"服务端默认开启,
|
||
// 必须显式发送 disabled"口径对齐,让行为确定性不依赖服务端隐式默认。
|
||
const wantThinking = request.params.thinkingEnabled === true && effort !== 'low';
|
||
body.chat_template_kwargs = { enable_thinking: wantThinking };
|
||
if (
|
||
request.params.thinkingEnabled === true &&
|
||
wantThinking &&
|
||
AgnesAdapter.MODEL_INFO[this.config.defaultModel]?.supportsThinking === false
|
||
) {
|
||
log.warn(
|
||
`[Agnes] model "${this.config.defaultModel}" metadata says thinking unsupported — sending enable_thinking per user config (degraded retry handles budget exhaustion)`,
|
||
);
|
||
}
|
||
}
|
||
|
||
// 停止序列
|
||
if (request.params.stopSequences?.length) {
|
||
body.stop = request.params.stopSequences;
|
||
}
|
||
|
||
return body;
|
||
}
|
||
}
|