Skip to content

Query 查询函数

概述

query() 是 Claude Code 的核心对话循环函数。它负责将用户消息发送到 Claude API、处理流式响应、执行工具调用,并在工具执行后继续对话——直到 Claude 给出最终回复或达到终止条件。

Source: src/query.ts (1732 行)

函数签名

typescript
// Source: src/query.ts:219-239
export async function* query(
  params: QueryParams,
): AsyncGenerator<
  | StreamEvent
  | RequestStartEvent
  | Message
  | TombstoneMessage
  | ToolUseSummaryMessage,
  Terminal
>

关键点:

  • Generator 函数 — 使用 async function* 语法,允许调用者逐步接收流式事件
  • 返回 Terminal — Generator 的最终返回值,包含终止原因

QueryParams 类型

typescript
// Source: src/query.ts:181-199
type QueryParams = {
  messages: Message[]              // 对话历史
  systemPrompt: SystemPrompt       // 系统提示词
  userContext: { [k: string]: string }  // 用户上下文 (CLAUDE.md, 日期等)
  systemContext: { [k: string]: string } // 系统上下文 (git status 等)
  canUseTool: CanUseToolFn         // 权限检查回调
  toolUseContext: ToolUseContext    // 工具执行上下文
  fallbackModel?: string           // 降级模型
  querySource: QuerySource         // 查询来源标识
  maxTurns?: number                // 最大对话轮数
  taskBudget?: { total: number }   // Token 预算
  deps?: QueryDeps                 // 依赖注入(用于测试)
}

核心循环:queryLoop()

Source: src/query.ts:241-1731

整个查询过程是一个 while (true) 循环,每次迭代代表一个对话轮次

状态机

typescript
// Source: src/query.ts:268-279
type State = {
  messages: Message[]
  toolUseContext: ToolUseContext
  autoCompactTracking: AutoCompactTrackingState | undefined
  maxOutputTokensRecoveryCount: number
  hasAttemptedReactiveCompact: boolean
  turnCount: number
  pendingToolUseSummary: Promise<...> | undefined
  transition: Continue | undefined  // 记录为什么继续循环
}

每次循环末尾通过 state = next; continue 跳回循环顶部。transition.reason 记录了继续循环的原因:

transition.reason含义
next_turn工具执行完成,继续下一轮对话
max_output_tokens_escalate提高 token 上限后重试
max_output_tokens_recovery多轮恢复尝试
reactive_compact_retry响应式压缩成功后重试
collapse_drain_retry上下文折叠排空成功后重试
stop_hook_blockingStop hook 注入了阻塞错误
token_budget_continuationToken 预算允许继续

循环流程图

步骤详解

1. 消息压缩

Source: src/query.ts:365-447

四级压缩策略,按顺序执行:

每级可能减少 token 数量;后续级别检查是否仍超限。

2. 上下文组装

typescript
// Source: src/query.ts:449-451
const fullSystemPrompt = appendSystemContext(systemPrompt, systemContext);

appendSystemContext() 将 git status、日期等信息追加到系统提示词。

prependUserContext() 在消息列表前插入用户上下文(CLAUDE.md 内容等)作为 <system-reminder> 标签。

3. API 调用

Source: src/query.ts:659-708

typescript
deps.callModel({
  messages: prependUserContext(messagesForQuery, userContext),
  systemPrompt: fullSystemPrompt,
  thinkingConfig,
  tools,
  signal: toolUseContext.abortController.signal,
  options: {
    model: currentModel,
    fastMode: appState.fastMode,
    fallbackModel,
    querySource,
    agents: toolUseContext.options.agentDefinitions.activeAgents,
    mcpTools: appState.mcp.tools,
    // ... 更多选项
  }
})

4. 流式处理

Source: src/query.ts:708-866

typescript
for await (const message of deps.callModel(...)) {
  // 处理降级重试
  // 回填 tool_use 输入(SDK 兼容性)
  // 扣留可恢复错误(413、max_tokens、media 错误)
  // yield 正常消息给调用者
  // 提取 tool_use 块
  // 喂给流式工具执行器
}

5. 错误恢复

Prompt Too Long (413):

  1. 在流式处理中扣留错误(行 813)
  2. 尝试上下文折叠排空(行 1097-1120)
  3. 尝试响应式压缩(行 1122-1169)
  4. 如果都失败,暴露错误并退出

Max Output Tokens:

  1. 提升上限:8k → 64k(行 1192-1223)
  2. 多轮恢复:最多 3 次助推消息(行 1226-1255)
  3. 耗尽后暴露错误

6-7. Hooks 和预算检查

Stop hooks 可以注入阻塞错误,导致循环继续。Token 预算系统检查是否允许更多轮次。

8. 工具执行

Source: src/query.ts:1366-1412

两种执行路径:

方式特点
StreamingToolExecutor工具在流式解析时就开始执行,减少总延迟
runTools()流式结束后批量执行所有工具
typescript
const toolUpdates = streamingToolExecutor
  ? streamingToolExecutor.getRemainingResults()
  : runTools(toolUseBlocks, assistantMessages, canUseTool, toolUseContext);

for await (const update of toolUpdates) {
  if (update.message) {
    yield update.message;
    toolResults.push(...normalizeMessagesForAPI([update.message]));
  }
}

9. 循环继续

typescript
// Source: src/query.ts:1717-1731
const next: State = {
  messages: [...messagesForQuery, ...assistantMessages, ...toolResults],
  toolUseContext: updatedToolUseContext,
  turnCount: turnCount + 1,
  transition: { reason: 'next_turn' },
  // ... 重置恢复计数器
};
state = next;
continue;  // 跳回 while(true) 顶部

依赖注入

Source: src/query/deps.ts

typescript
export type QueryDeps = {
  callModel: typeof queryModelWithStreaming
  microcompact: typeof microcompactMessages
  autocompact: typeof autoCompactIfNeeded
  uuid: () => string
}

export function productionDeps(): QueryDeps {
  return {
    callModel: queryModelWithStreaming,
    microcompact: microcompactMessages,
    autocompact: autoCompactIfNeeded,
    uuid: randomUUID,
  }
}

测试时可以替换 callModel 为假 generator,避免实际 API 调用。

终止条件

Terminal.reason含义
completed正常完成(无工具调用,无错误)
aborted_streaming用户在流式传输时中断
aborted_tools用户在工具执行时中断
prompt_too_long恢复失败
max_tokens达到最大 token 限制
hook_stoppedHook 阻止继续
max_turns达到显式轮次限制
model_error未捕获异常

下一步