CompletionService 调用链
CompletionService 是 LLM 调用的核心门面(Facade),负责将前端的补全请求编排为完整的 API 调用管线。本文档详解其六步管线、流式处理、思考预算计算、重试机制和工具调用循环。
文件位置
| 文件 | 路径 |
|---|---|
| CompletionService | packages/desktop/app/main/services/capabilities/llm/completion/CompletionService.ts |
| DirectApiHandler | packages/desktop/app/main/services/capabilities/llm/completion/DirectApiHandler.ts |
| StreamHandler | packages/desktop/app/main/services/capabilities/llm/completion/StreamHandler.ts |
| ToolHandler | packages/desktop/app/main/services/capabilities/llm/completion/ToolHandler.ts |
| TransformerHandler | packages/desktop/app/main/services/capabilities/llm/completion/TransformerHandler.ts |
| ThinkingResolver | packages/desktop/app/main/services/capabilities/llm/completion/ThinkingResolver.ts |
| URL Builder | packages/desktop/app/main/services/capabilities/llm/completion/url-builder.ts |
| Header Builder | packages/desktop/app/main/services/capabilities/llm/completion/header-builder.ts |
| Message Converter | packages/desktop/app/main/services/capabilities/llm/completion/message-converter.ts |
| Types | packages/desktop/app/main/services/capabilities/llm/completion/types.ts |
| NativeSearchInjector | packages/desktop/app/main/services/capabilities/llm/completion/NativeSearchInjector.ts |
| ProviderSearchInjector | packages/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_VAR → process.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 |
|---|---|---|
openai | callOpenAICompletion | streamOpenAICompletion |
openai-response | callOpenAIResponseCompletion | streamOpenAIResponseCompletion |
anthropic | callAnthropicCompletion | streamAnthropicCompletion |
google | callGeminiCompletion | streamGeminiCompletion |
azure-openai | callOpenAICompletion | streamOpenAICompletion |
步骤 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,因此需要:
- 计算思考预算
thinkingBudget - 从
max_tokens中减去思考预算得到adjustedMaxTokens - 生成
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_ITERATIONS | globalParams.toolMaxTurns ?? 5 | 最大迭代次数 |
| 工具格式 | 自动根据 apiFormat | OpenAI/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:complete | R → M | CompletionRouter | 非流式补全 |
completion:getModels | R → M | CompletionRouter | 获取可用模型 |
completion:testModel | R → M | CompletionRouter | 测试模型连接 |
| 流式补全 | R → M | ChatStreamHandler | SSE 流通过 IPC 消息传递 |
| 重新生成 | R → M | RegenerateHandler | 重新生成回复 |
扩展点
添加新的 API 格式
- 在
types.ts的ApiFormat类型中添加新值 - 在
url-builder.ts中添加 URL 构建函数 - 在
header-builder.ts中添加头部构建逻辑 - 在
message-converter.ts中添加消息格式转换 - 在
DirectApiHandler.ts中添加callXxxCompletion函数 - 在
StreamHandler.ts中添加streamXxxCompletion函数 - 在
CompletionService.callDirectHandler()和callStreamHandler()的 switch 中添加 case
自定义重试策略
当前只重试一次。如需更复杂的重试(如多次重试、不同等待时间),修改 complete() 和 completeStream() 中的重试逻辑。
添加新的搜索注入
- SDK 原生搜索(如 Anthropic):通过
NativeSearchInjector的applyAugmentation()注入 - 提供商特定搜索(如 model-param / builtin-tool):通过
ProviderSearchInjector注入
关联文件表
| 文件 | 关联方式 |
|---|---|
capabilities/llm/config-service/LLMConfigService.ts | 提供 Provider 查询和路由解析 |
capabilities/llm/completion/ApiKeyPoolService.ts | 密钥选择和负载均衡 |
infra/utils/sse-parser.ts | SSE 流解析工具函数 |
shared/completion-types.ts | SimpleChatMessage 等共享类型 |
shared/thinking-config.ts | 思考预算计算和推理模型判断 |
shared/llm-config.ts | LLMProvider 类型定义 |
capabilities/tools/mcp-users/McpService.ts | 工具执行(ToolHandler 调用) |
capabilities/llm/api-converter/openai-to-anthropic.ts | OpenAI → Anthropic 格式转换 |
routers/CompletionRouter.ts | IPC 入口 |