メインコンテンツまでスキップ

CompletionService 调用链

CompletionService 是 LLM 调用的核心门面(Facade),负责将前端的补全请求编排为完整的 API 调用管线。本文档详解其六步管线、流式处理、思考预算计算、重试机制和工具调用循环。


文件位置

文件路径
CompletionServicepackages/desktop/app/main/services/capabilities/llm/completion/CompletionService.ts
DirectApiHandlerpackages/desktop/app/main/services/capabilities/llm/completion/DirectApiHandler.ts
StreamHandlerpackages/desktop/app/main/services/capabilities/llm/completion/StreamHandler.ts
ToolHandlerpackages/desktop/app/main/services/capabilities/llm/completion/ToolHandler.ts
TransformerHandlerpackages/desktop/app/main/services/capabilities/llm/completion/TransformerHandler.ts
ThinkingResolverpackages/desktop/app/main/services/capabilities/llm/completion/ThinkingResolver.ts
URL Builderpackages/desktop/app/main/services/capabilities/llm/completion/url-builder.ts
Header Builderpackages/desktop/app/main/services/capabilities/llm/completion/header-builder.ts
Message Converterpackages/desktop/app/main/services/capabilities/llm/completion/message-converter.ts
Typespackages/desktop/app/main/services/capabilities/llm/completion/types.ts
NativeSearchInjectorpackages/desktop/app/main/services/capabilities/llm/completion/NativeSearchInjector.ts
ProviderSearchInjectorpackages/desktop/app/main/services/capabilities/llm/completion/ProviderSearchInjector.ts

架构上下文

graph TB
subgraph CompletionService ["CompletionService (门面)"]
direction TB
Complete[complete]
Stream[completeStream]
WithTransformers[completeWithTransformers]
StreamTransformers[completeStreamWithTransformers]
WithTools[streamWithTools]
TestModel[testModel]
end

subgraph 管线步骤
direction TB
S1["(1) 路由解析<br/>resolveRoutedModel"]
S2["(2) Provider 查找<br/>getProvider + enabled 检查"]
S3["(3) API Key 解析<br/>codingPlan → pool → legacy"]
S4["(4) API 格式解析<br/>resolveApiFormat"]
S5["(5) Handler 分发<br/>callDirectHandler / callStreamHandler"]
S6["(6) 重试 + 成功报告"]
end

subgraph Handlers
DAH[DirectApiHandler<br/>非流式]
SH[StreamHandler<br/>SSE 流式]
TH[ToolHandler<br/>工具循环]
THR[TransformerHandler<br/>转换链]
end

subgraph 辅助服务
TR2[ThinkingResolver]
NSI[NativeSearchInjector]
PSI[ProviderSearchInjector]
MC[Message Converter]
UB[URL Builder]
HB[Header Builder]
end

Complete --> S1 --> S2 --> S3 --> S4 --> S5 --> S6
S5 --> DAH
S5 --> SH

WithTools --> TH
WithTransformers --> THR
StreamTransformers --> THR

DAH --> UB
DAH --> HB
DAH --> MC
SH --> UB
SH --> HB
SH --> MC
SH --> TR2
TH --> UB
TH --> HB

数据结构

请求与响应类型

// 补全请求选项
interface CompletionOptions {
providerId: string; // 提供商 ID
model: string; // 模型 ID(v89+ 为裸 SDK id,无 `<backend>:` 前缀和 `[1m]` 后缀)
messages: SimpleChatMessage[]; // 对话消息
maxTokens?: number; // 最大生成 Token
temperature?: number; // 温度
stream?: boolean; // 是否流式
thinkLevel?: ThinkLevel; // 思考级别: 'none' | 'low' | 'medium' | 'high'
nativeSearchAugmentation?: NativeSearchAugmentation; // SDK 原生搜索增强
sessionId?: string; // 会话 ID(API Key Pool 亲和性)
/**
* 1M-context 开关(v89+)。当 true 且 model 在 1M-capable 白名单
* (`claude-opus-4-7` / `claude-opus-4-6` / `claude-sonnet-4-6`)内时,
* `TransformerHandler` 在 transformer chain 出口调用 `injectExtendedContextBeta()`,
* 把 `'context-1m-2025-08-07'` 合并到出站请求的 `anthropic-beta` HTTP 头
* (**不是** body 字段;`/v1/messages` 拒绝未知 body 字段)。
*/
useExtendedContext?: boolean;
}

// 补全结果
interface CompletionResult {
success: boolean;
message?: SimpleChatMessage; // 生成的消息
error?: string; // 错误信息
usage?: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
finishReason?: string; // 'stop' | 'tool_use' | 'max_tokens' 等
}

// 流式回调
interface StreamCallbacks {
onStart?: (messageId: string) => void;
onDelta?: (content: string) => void;
onReasoning?: (reasoning: string) => void;
onAudio?: (audio: SimpleChatAudio) => void;
onVideo?: (video: SimpleChatVideo) => void;
onBlock?: (block: MessageBlock) => void; // 内容块: thinking/text/tool_use/tool_result
onDone?: (message, usage?, metrics?) => void;
onError?: (error: string) => void;
}

// API 格式
type ApiFormat = 'openai' | 'anthropic' | 'google' | 'azure-openai' | 'openai-response';

算法/逻辑说明

六步请求管线

步骤 1:路由解析

routedInfo = llmConfig.resolveRoutedModel(providerId, model)
actualProviderId = routedInfo?.actualProviderId || providerId
actualModel = routedInfo?.actualModelId || model

路由解析处理 Chat → Code 和 Code → Chat 的模型路由。如果 model 匹配路由规则,则替换为实际的提供商和模型。

步骤 2:Provider 查找

provider = getProvider(actualProviderId)
if (!provider) → 返回错误 "Provider not found"
if (!provider.enabled) → 返回错误 "Provider is disabled"

查找通过 LLMConfigService 的 Provider Index 进行 O(1) 查找。

步骤 3:API Key 解析

resolveApiKeyForRequest(provider, providerId, sessionId):
// 优先级 1: Coding Plan 覆盖
if provider.codingPlan?.enabled && provider.codingPlan.apiKey:
return resolveApiKey(codingPlan.apiKey)

// 优先级 2: API Key Pool(会话亲和性加权轮询)
if apiKeyPool 可用:
poolKey = sessionId
? apiKeyPool.getKeyForSession(providerId, sessionId)
: apiKeyPool.getKey(providerId)
if poolKey: return poolKey

// 优先级 3: 遗留单密钥
return resolveApiKey(provider.api_key)

resolveApiKey() 处理环境变量展开($ENV_VARprocess.env.ENV_VAR)。

步骤 4:API 格式解析

resolveApiFormat(provider):
// 优先级 1: apiFormat 字段(v3 首选)
if provider.apiFormat: return provider.apiFormat

// 优先级 2: chatApiFormat 字段(遗留 v3)
if provider.chatApiFormat: return provider.chatApiFormat

// 优先级 3: apiType 隐式转换
if provider.apiType === 'claudecode' || 'anthropic': return 'anthropic'
if provider.apiType === 'google': return 'google'

// 默认: OpenAI 格式
return 'openai'

步骤 5:Handler 分发

根据 apiFormat 选择对应的处理函数:

apiFormat非流式 Handler流式 Handler
openaicallOpenAICompletionstreamOpenAICompletion
openai-responsecallOpenAIResponseCompletionstreamOpenAIResponseCompletion
anthropiccallAnthropicCompletionstreamAnthropicCompletion
googlecallGeminiCompletionstreamGeminiCompletion
azure-openaicallOpenAICompletionstreamOpenAICompletion

步骤 6:重试 + 成功报告

result = callHandler(...)
if result 失败 && apiKeyPool 可用 && sessionId 存在:
status = extractHttpStatus(result.error)
if status in [429, 529, 401, 403]:
newKey = apiKeyPool.reportError(providerId, sessionId, status)
if newKey:
result = callHandler(..., newKey) // 用新密钥重试一次

if result 成功 && apiKeyPool 可用:
apiKeyPool.reportSuccess(sessionId) // 重置冷却计数

流式处理

SSE 流解析

所有流式 Handler 都使用 streamSSEResponse() 工具函数,基于标准 SSE(Server-Sent Events)协议:

sequenceDiagram
participant CS as CompletionService
participant SH as StreamHandler
participant API as Provider API

CS->>SH: callStreamHandler(format, provider, key, options)
SH->>API: POST request (stream: true)
API-->>SH: SSE stream

loop 每个 SSE event
SH->>SH: 解析 event data
alt content delta
SH->>CS: callbacks.onDelta(content)
else reasoning delta
SH->>CS: callbacks.onReasoning(reasoning)
else block event
SH->>CS: callbacks.onBlock(block)
else [DONE]
SH->>CS: callbacks.onDone(message, usage, metrics)
else error
SH->>CS: callbacks.onError(error)
end
end

流式重试(池模式)

flowchart TD
Start[completeStream] --> HasPool{apiKeyPool 可用?}
HasPool -->|否| DirectCall[直接调用 callStreamHandler]
HasPool -->|是| InterceptCall[带拦截回调调用]

InterceptCall --> StreamDone{流成功完成?}
StreamDone -->|是| ReportSuccess[reportSuccess]
StreamDone -->|否| CheckStatus{是 429/529/401/403?}

CheckStatus -->|是| GetNewKey[reportError → 获取新密钥]
CheckStatus -->|否| PropagatError[传播错误给前端]

GetNewKey --> HasNewKey{有新密钥?}
HasNewKey -->|是| RetryStream[用新密钥重试流]
HasNewKey -->|否| PropagatError

流式重试的关键设计:

  • 通过包装 callbacks.onError 来拦截 429/529 错误
  • 使用 retryState 对象引用跟踪闭包内的错误状态
  • 重试时不再触发 onStart(已经触发过一次)

Anthropic 思考预算计算

flowchart TD
Start[resolveThinkingBudget] --> CheckLevel{thinkLevel === 'none'?}
CheckLevel -->|是| NoThinking[返回原始 maxTokens<br/>无思考配置]
CheckLevel -->|否| CheckModel{isReasoningModel?}
CheckModel -->|否| NoThinking
CheckModel -->|是| CalcBudget[calculateThinkingBudget<br/>model, thinkLevel, maxTokens]

CalcBudget --> CheckFormat{API 格式是 Anthropic?}
CheckFormat -->|是| AdjustTokens[adjustedMaxTokens = getClaudeMaxTokens<br/>maxTokens - thinkingBudget]
CheckFormat -->|否| KeepTokens[adjustedMaxTokens = maxTokens]

AdjustTokens --> BuildConfig[buildAnthropicThinking<br/>生成 thinking 配置对象]
BuildConfig --> Return[返回 adjustedMaxTokens + thinkingConfig]
KeepTokens --> Return

Anthropic 特殊处理:Claude 模型的 max_tokens 包含思考 Token,因此需要:

  1. 计算思考预算 thinkingBudget
  2. max_tokens 中减去思考预算得到 adjustedMaxTokens
  3. 生成 thinking 配置对象传入请求体

OpenAI 推理模型

对 OpenAI 的 o-series 模型(o1、o3 等),使用 reasoning_effort 参数而非调整 Token 预算:

if thinkLevel !== 'none':
effort = getOpenAIReasoningEffort(thinkLevel)
// 'low' | 'medium' | 'high'
request.reasoning_effort = effort

max_tokens 解析优先级

resolveEffectiveMaxTokens(providerId, modelId, sessionMaxTokens):
// 1. 会话级设置(最高优先级,用户手动设置,不加上限)
if sessionMaxTokens > 0: return sessionMaxTokens

// 2. 全局模型参数(管理员设置,不加上限)
globalParams = llmConfig.getGlobalModelParameters()
if globalParams.maxTokens.enabled && value > 0: return value

// -------- 以下自动解析值受 MAX_TOKENS_CAP=65536 上限 --------

// 3. 模型配置中的 maxTokens
modelConfig = provider.modelConfigs.find(id === modelId)
if modelConfig.maxTokens > 0: return min(value, 65536)

// 4. 模型分组中的 maxTokens
modelGroup = provider.modelGroups.find(models.id === modelId)
if model.maxTokens > 0: return min(value, 65536)

// 5. 模型发现缓存
discovered = llmConfig.getDiscoveredModelMaxTokens(providerId, modelId)
if discovered > 0: return min(value, 65536)

// 6. undefined(让 API 使用默认值)
return undefined

// Anthropic 等要求必须有 max_tokens 的提供商使用:
getRequiredMaxTokens():
resolved = resolveEffectiveMaxTokens(...)
return resolved ?? DEFAULT_MAX_TOKENS // 通常 4096

工具调用循环(ToolHandler)

sequenceDiagram
participant TH as ToolHandler
participant LLM as LLM API
participant MCP as MCP Service

TH->>TH: MAX_ITERATIONS = globalParams.toolMaxTurns ?? 5
TH->>TH: iteration = 0

loop iteration < MAX_ITERATIONS
TH->>TH: iteration++
TH->>TH: buildToolRequest(format, messages, model, options)
TH->>LLM: POST request with tools
LLM-->>TH: SSE stream response

TH->>TH: extractToolCalls(response)

alt 没有工具调用
TH->>TH: break(LLM 完成回答)
else 有工具调用
loop 每个 tool call
TH->>TH: callbacks.onToolCall(toolCall)
TH->>MCP: executeToolCalls(toolCalls, mcpService)
MCP-->>TH: tool results
TH->>TH: callbacks.onToolResult(id, result)
end
TH->>TH: 将工具调用和结果追加到 messages
TH->>TH: buildIterationBlocks(工具调用块)
end
end

TH->>TH: callbacks.onDone(finalContent, usage)

关键行为

参数默认值说明
MAX_ITERATIONSglobalParams.toolMaxTurns ?? 5最大迭代次数
工具格式自动根据 apiFormatOpenAI/Anthropic/Gemini 格式的 tool 定义
终止条件无工具调用 或 达到上限LLM 不再请求工具时自然结束

工具调用支持三种格式,由 logToolFormat() 自动检测:

  • OpenAI 格式{ type: 'function', function: { name, parameters } }
  • Anthropic 格式{ name, input_schema }
  • Gemini 格式{ functionDeclarations: [...] }

Vision 回退

当消息包含图片但模型不支持视觉时,自动使用辅助 Vision 模型:

applyVisionFallback(options):
if 消息中没有图片: return
if 模型支持 vision: return

visionModel = llmConfig.resolveEffectiveModels().vision
if 没有 vision 模型:
// 剥离图片
for msg in messages:
msg.images = undefined
return

// 使用 VisionDescriptionService 描述图片
for msg in messages with images:
description = visionService.describeImages(images, msg.content, visionModel)
msg.content += "\n\n[Image Description]\n" + description
msg.images = undefined

错误拦截模式

extractHttpStatus() 从错误消息字符串中提取 HTTP 状态码:

extractHttpStatus(error: string):
match = error.match(/\((\d{3})\):/)
return match ? parseInt(match[1]) : null

// 示例: "API error (429): Rate limit exceeded" → 429

IPC 集成表

IPC 通道方向Router说明
completion:completeR → MCompletionRouter非流式补全
completion:getModelsR → MCompletionRouter获取可用模型
completion:testModelR → MCompletionRouter测试模型连接
流式补全R → MChatStreamHandlerSSE 流通过 IPC 消息传递
重新生成R → MRegenerateHandler重新生成回复

扩展点

添加新的 API 格式

  1. types.tsApiFormat 类型中添加新值
  2. url-builder.ts 中添加 URL 构建函数
  3. header-builder.ts 中添加头部构建逻辑
  4. message-converter.ts 中添加消息格式转换
  5. DirectApiHandler.ts 中添加 callXxxCompletion 函数
  6. StreamHandler.ts 中添加 streamXxxCompletion 函数
  7. CompletionService.callDirectHandler()callStreamHandler() 的 switch 中添加 case

自定义重试策略

当前只重试一次。如需更复杂的重试(如多次重试、不同等待时间),修改 complete()completeStream() 中的重试逻辑。

添加新的搜索注入

  • SDK 原生搜索(如 Anthropic):通过 NativeSearchInjectorapplyAugmentation() 注入
  • 提供商特定搜索(如 model-param / builtin-tool):通过 ProviderSearchInjector 注入

关联文件表

文件关联方式
capabilities/llm/config-service/LLMConfigService.ts提供 Provider 查询和路由解析
capabilities/llm/completion/ApiKeyPoolService.ts密钥选择和负载均衡
infra/utils/sse-parser.tsSSE 流解析工具函数
shared/completion-types.tsSimpleChatMessage 等共享类型
shared/thinking-config.ts思考预算计算和推理模型判断
shared/llm-config.tsLLMProvider 类型定义
capabilities/tools/mcp-users/McpService.ts工具执行(ToolHandler 调用)
capabilities/llm/api-converter/openai-to-anthropic.tsOpenAI → Anthropic 格式转换
routers/CompletionRouter.tsIPC 入口