Claude Code源码解析-02-QueryEngine 推理引擎:Claude Code 的"大脑"到底怎么工作?
QueryEngine 推理引擎:Claude Code 的“大脑”到底怎么工作?
QueryEngine。 这篇文章带你看清它如何在真实场景中完成“思考 -> 调工具 -> 再思考 -> 交付结果”的完整闭环。概述:为什么 QueryEngine 值得关注?
核心文件:先看地图,再看细节
|
|
|
|---|---|
src/QueryEngine.ts |
|
src/query.ts |
|
src/query/deps.ts |
|
src/utils/queryContext.ts |
|
src/utils/queryHelpers.ts |
|
架构分层:谁负责“对外”,谁负责“思考”?
typescript
┌───────────────────────────────────────────┐ │ 调用方 (REPL / SDK / AgentTool) │ │ for await (event of query({...})) │ ├───────────────────────────────────────────┤ │ │ │ query() ─ 入口函数 │ │ │ │ │ ▼ │ │ queryLoop() ─ 核心 while(true) 循环 │ │ │ │ │ ├─ 准备消息上下文 │ │ ├─ 自动压缩 (autocompact) │ │ ├─ Token 限制检查 │ │ ├─ deps.callModel() ─ 流式 API 调用 │ │ ├─ 处理流式响应 │ │ ├─ 执行工具调用 │ │ ├─ 收集工具结果 │ │ └─ 继续循环 / 返回终止 │ │ │ ├───────────────────────────────────────────┤ │ deps (依赖注入) │ │ ├─ callModel → queryModelWithStreaming │ │ ├─ microcompact → microcompactMessages │ │ ├─ autocompact → autoCompactIfNeeded │ │ └─ uuid → randomUUID │ └───────────────────────────────────────────┘
queryLoop 详细流程:一次任务是怎么跑完的?
typescript
queryLoop() — while(true) │ ├─ 1. 发射 stream_request_start 事件 │ ├─ 2. 查询链追踪 (queryTracking) │ 记录工具使用链和上下文 │ ├─ 3. 获取压缩边界后的消息 │ getMessagesAfterCompactBoundary() │ ├─ 4. 工具结果预算控制 │ applyToolResultBudget() — 截断过大的工具输出 │ ├─ 5. 历史裁剪 (HISTORY_SNIP) │ 可选的消息历史截断 │ ├─ 6. 微压缩 (microcompact) │ 轻量级消息压缩 │ ├─ 7. 上下文折叠 (CONTEXT_COLLAPSE) │ 激进的上下文缩减策略 │ ├─ 8. 组装完整系统提示词 │ appendSystemContext(systemPrompt, systemContext) │ ├─ 9. 自动压缩 (autocompact) │ │ 当 token 接近上下文窗口限制时: │ │ - 调用压缩服务生成摘要 │ │ - 替换历史消息为压缩版本 │ │ - 更新 taskBudget 剩余量 │ │ - 发射压缩相关事件 │ ▼ │ ├─ 10. Token 阻塞检查 │ 如果超出限制且无法压缩 → 返回错误 │ ├─ 11. ══════ 调用模型 ══════ │ deps.callModel(messages, tools, thinkingConfig, ...) │ │ │ ├─ 流式接收 assistant 消息 │ ├─ 流式接收 thinking 块 │ ├─ 检测 tool_use 块 │ ├─ StreamingToolExecutor 并行预执行 │ └─ 错误恢复 (413/media/max_output) │ ├─ 12. 后采样钩子 (post-sampling hooks) │ ├─ 13. ═══ 分支判断 ═══ │ │ │ ├─ 无工具调用 (needsFollowUp = false) │ │ ├─ 反应式压缩恢复 │ │ ├─ max_output_tokens 恢复 │ │ ├─ 运行 stop hooks │ │ ├─ Token Budget 继续检查 │ │ └─ return { reason: 'completed' } │ │ │ └─ 有工具调用 (needsFollowUp = true) │ ├─ runTools() 或 StreamingToolExecutor │ ├─ 收集工具结果消息 │ ├─ 获取附件消息 (attachments) │ ├─ 内存/技能预取 │ ├─ maxTurns 检查 │ ├─ 更新 state.messages │ └─ continue ──► 回到循环顶部 │ └─ 循环结束 → return Terminal
QueryEngine 与 query 的关系:两个名字,一套协同
typescript
┌─────────────────────────────────────────────┐ │ QueryEngine (SDK 层) │ │ │ │ submitMessage(prompt) │ │ │ │ │ ├─ processUserInput() — 解析输入 │ │ ├─ fetchSystemPromptParts() — 系统提示 │ │ ├─ buildSystemInitMessage() — 初始消息 │ │ │ │ │ ├─ for await (msg of query({...})) │ │ │ │ │ │ │ └─ switch(msg.type) │ │ │ ├─ 'assistant' → SDKMessage │ │ │ ├─ 'user' → SDKMessage │ │ │ ├─ 'progress' → SDKStatus │ │ │ ├─ 'stream_event' → 流式事件 │ │ │ ├─ 'attachment' → 附件处理 │ │ │ └─ 'tool_use_summary' → 摘要 │ │ │ │ │ └─ yield SDKMessage / result │ │ │ │ 管理: mutableMessages, transcript, usage │ └─────────────────────────────────────────────┘ │ ▼ (共享核心) ┌─────────────────────────────────────────────┐ │ query() / queryLoop() (推理层) │ │ │ │ - 模型调用 (callModel) │ │ - 工具执行 (runTools) │ │ - 压缩管理 (autocompact/microcompact) │ │ - Token 预算 (tokenBudget) │ │ - 终止条件判断 │ │ │ │ REPL 和 SDK 共享同一个 query() 函数 │ └─────────────────────────────────────────────┘
依赖注入设计:为什么这一步很工程化?
query.ts 通过 deps 对象实现依赖注入,便于测试和不同运行环境。这种设计直接决定了后续的可维护性和可扩展性:typescript
// src/query/deps.ts export function productionDeps(): QueryDeps { return { callModel: queryModelWithStreaming, // Anthropic API 流式调用 microcompact: microcompactMessages, // 轻量压缩 autocompact: autoCompactIfNeeded, // 自动压缩 uuid: randomUUID, // UUID 生成 } }
终止条件:什么时候这台“引擎”会停?
|
|
|
|---|---|
completed |
|
abort |
|
maxTurns |
|
tokenLimit |
|
error |
|
stopHook |
|
关键设计特点:为什么它能从 Demo 走向生产?
query() 是一个 AsyncGenerator,通过 yield 流式返回事件,调用方用 for await 消费 StreamingToolExecutor 允许在模型生成过程中并行执行已完成的工具调用 结语
QueryEngine 的核心意义,不在于“又封装了一层模型调用”,而在于它定义了一套可持续执行的智能体范式:
夜雨聆风