当前位置: 首页 > news >正文

`ConversationRuntime::run_turn` 函数解析

ConversationRuntime::run_turn 函数解析

位置: rust/crates/runtime/src/conversation.rs:318-524

概述

ConversationRuntime::run_turn 是整个 claw-code CLI 的核心 LLM 对话引擎。它接收用户输入,以"请求 → 解析 → 工具执行 → 再请求"的循环与 LLM API 交互,直到模型不再调用工具为止。它是前文 run_turn.md 中描述的"核心循环层"的完整实现。


函数签名

pub fn run_turn(&mut self,user_input: impl Into<String>,mut prompter: Option<&mut dyn PermissionPrompter>,
) -> Result<TurnSummary, RuntimeError>
参数 含义
user_input 用户的文本输入(支持隐式转换 Into<String>
prompter 可选的终端权限提示器,为 None 时完全由策略自动决策
返回值 含义
Ok(TurnSummary) 完整的对话回合摘要(含所有助手消息、工具结果、用量等)
Err(RuntimeError) 运行时错误(API 失败、超限、健康检查失败等)

核心数据结构

TurnSummary(对话回合摘要)

pub struct TurnSummary {pub assistant_messages: Vec<ConversationMessage>,  // 本轮所有 LLM 回复pub tool_results: Vec<ConversationMessage>,        // 本轮所有工具执行结果pub prompt_cache_events: Vec<PromptCacheEvent>,    // prompt cache 事件pub iterations: usize,                             // 循环迭代次数pub usage: TokenUsage,                             // 累积 token 用量pub auto_compaction: Option<AutoCompactionEvent>,  // 自动压缩事件(如有)
}

AssistantEvent(LLM 流式事件)

pub enum AssistantEvent {Thinking { thinking: String, signature: Option<String> },TextDelta(String),ToolUse { id: String, name: String, input: String },Usage(TokenUsage),PromptCache(PromptCacheEvent),MessageStop,
}

完整执行流程

┌─ run_turn(user_input, prompter)
│
│  ╔═══════════════════════════════════════════════╗
│  ║  阶段 1: 会话健康检查                         ║
│  ╚═══════════════════════════════════════════════╝
│
│  if self.session.compaction.is_some() {
│      └── run_session_health_probe()
│            ├── 空 session + 有 compaction → Ok (刚压缩完的正常状态)
│            └── 发送 glob_search 探针 (无害的不可达模式)
│                  ├── 成功 → Ok
│                  └── 失败 → Err("Session health probe failed ...")
│                            提示用户 /session new 新建会话
│  }
│
│  ╔═══════════════════════════════════════════════╗
│  ║  阶段 2: 记录用户输入                          ║
│  ╚═══════════════════════════════════════════════╝
│
│  ├── record_turn_started(&user_input)     ← session_tracer 记录
│  └── session.push_user_text(user_input)   ← 用户消息写入消息历史
│
│  ╔═══════════════════════════════════════════════╗
│  ║  阶段 3: 主循环 (LLM 请求-工具执行循环)       ║
│  ╚═══════════════════════════════════════════════╝
│
│  初始化:
│    assistant_messages = []
│    tool_results = []
│    prompt_cache_events = []
│    iterations = 0
│    auto_compaction = None
│
│  ┌── loop ──────────────────────────────────────────────────────────┐
│  │                                                                    │
│  │  iterations += 1                                                   │
│  │                                                                    │
│  │  3.0 迭代次数检查                                                  │
│  │  if iterations > max_iterations:                                   │
│  │      record_turn_failed(iterations, error)                         │
│  │      return Err("conversation loop exceeded max iterations")       │
│  │                                                                    │
│  │  ╔═══════════════════════════════════════════════════════════╗     │
│  │  ║  子阶段 A: 发送 API 请求并构建助手回复                   ║     │
│  │  ╚═══════════════════════════════════════════════════════════╝     │
│  │                                                                    │
│  │  3.1 构建 API 请求                                                 │
│  │  request = ApiRequest {                                            │
│  │      system_prompt: self.system_prompt.clone(),                    │
│  │      messages: self.session.messages.clone(),   ← 全部消息历史     │
│  │  }                                                                 │
│  │                                                                    │
│  │  3.2 流式调用 LLM API                                              │
│  │  events = match self.api_client.stream(request) {                  │
│  │      Ok(events) → events,                   ← AssistantEvent 流    │
│  │      Err(error) → record_turn_failed + return Err                 │
│  │  }                                                                 │
│  │                                                                    │
│  │  3.3 构建助手消息                                                   │
│  │  (assistant_message, usage, turn_prompt_cache_events)              │
│  │      = build_assistant_message(events)                             │
│  │                                                                    │
│  │  遍历 AssistantEvent 流:                                           │
│  │    ├── Thinking{thinking, signature}                                │
│  │    │     → flush_text_block (将累积的 text 变成 Text block)        │
│  │    │     → blocks.push(Thinking)                                   │
│  │    ├── TextDelta(delta) → text.push_str(delta) (累积文本)          │
│  │    ├── ToolUse{id, name, input}                                    │
│  │    │     → flush_text_block                                        │
│  │    │     → blocks.push(ToolUse)                                    │
│  │    ├── Usage(value) → usage = Some(value)                          │
│  │    ├── PromptCache(event) → prompt_cache_events.push(event)        │
│  │    └── MessageStop → finished = true                               │
│  │                                                                    │
│  │  flush_text_block (将剩余文本 push 为 Text block)                  │
│  │                                                                    │
│  │  验证:                                                              │
│  │    ├── 必须有 MessageStop → 否则 Err                               │
│  │    └── blocks 非空 → 否则 Err                                      │
│  │                                                                    │
│  │  → return (ConversationMessage, TokenUsage, PromptCacheEvents)     │
│  │                                                                    │
│  │  3.4 记录用量                                                      │
│  │  if let Some(usage) = usage {                                      │
│  │      self.usage_tracker.record(usage);                             │
│  │  }                                                                 │
│  │  prompt_cache_events.extend(turn_prompt_cache_events);             │
│  │                                                                    │
│  │  3.5 提取 tool_use                                                 │
│  │  pending_tool_uses = assistant_message.blocks                      │
│  │      .filter_map(ContentBlock::ToolUse)                            │
│  │      .map(|(id, name, input)|)                                     │
│  │      .collect::<Vec<_>>()                                          │
│  │                                                                    │
│  │  3.6 记录助手迭代到 session                                        │
│  │  record_assistant_iteration(iterations, message, tool_use_count)   │
│  │  session.push_message(assistant_message)                           │
│  │  assistant_messages.push(assistant_message)                        │
│  │                                                                    │
│  │  3.7 自动压缩检查 (防止 session 无限增长)                          │
│  │  if let Some(compaction) = self.maybe_auto_compact() {             │
│  │      auto_compaction = Some(compaction);                           │
│  │  }                                                                 │
│  │                                                                    │
│  │  3.8 判断是否需要继续                                              │
│  │  if pending_tool_uses.is_empty() {                                 │
│  │      break;  ← 没有 tool_use,对话回合结束                          │
│  │  }                                                                 │
│  │                                                                    │
│  │  ╔═══════════════════════════════════════════════════════════╗     │
│  │  ║  子阶段 B: 逐个执行工具                                  ║     │
│  │  ╚═══════════════════════════════════════════════════════════╝     │
│  │                                                                    │
│  │  for (tool_use_id, tool_name, input) in pending_tool_uses {       │
│  │      │                                                             │
│  │      3.9 执行 PreToolUse Hook                                      │
│  │      pre_hook_result = self.run_pre_tool_use_hook(&tool_name)      │
│  │      effective_input = pre_hook_result.updated_input()             │
│  │          .map_or(input.clone(), ToOwned::to_owned)                  │
│  │      permission_context = PermissionContext::new(                  │
│  │          pre_hook_result.permission_override(),                    │
│  │          pre_hook_result.permission_reason(),                      │
│  │      )                                                             │
│  │                                                                    │
│  │      3.10 权限决策 (4 级链路)                                      │
│  │      permission_outcome =                                          │
│  │        ├── hook cancelled  → PermissionOutcome::Deny              │
│  │        ├── hook failed     → PermissionOutcome::Deny              │
│  │        ├── hook denied     → PermissionOutcome::Deny              │
│  │        ├── 有 prompter     → permission_policy.authorize_with_    │
│  │        │                       context(tool, input, context,      │
│  │        │                       Some(prompter))                    │
│  │        │                     └── 策略评估 + 终端用户交互           │
│  │        └── 无 prompter     → permission_policy.authorize_with_    │
│  │                              context(tool, input, context, None)  │
│  │                              └── 纯策略自动决策                    │
│  │                                                                    │
│  │      3.11 执行工具或拒绝                                           │
│  │      result_message = match permission_outcome {                   │
│  │          Allow → {                                                  │
│  │              record_tool_started(iterations, tool_name)             │
│  │                                                                    │
│  │              // 实际执行                                           │
│  │              (output, is_error) = match tool_executor.execute() {  │
│  │                  Ok(output)  → (output, false)                     │
│  │                  Err(error)  → (error.to_string(), true)           │
│  │              }                                                     │
│  │              output = merge_hook_feedback(pre_hook.messages,       │
│  │                                           output, false)           │
│  │                                                                    │
│  │              // PostToolUse Hook                                   │
│  │              post_hook_result = if is_error {                      │
│  │                  self.run_post_tool_use_failure_hook(...)          │
│  │              } else {                                              │
│  │                  self.run_post_tool_use_hook(...)                  │
│  │              }                                                     │
│  │              if post_hook_result.denied/failed/cancelled {         │
│  │                  is_error = true                                   │
│  │              }                                                     │
│  │              output = merge_hook_feedback(                         │
│  │                  post_hook.messages, output, is_error              │
│  │              )                                                     │
│  │                                                                    │
│  │              ConversationMessage::tool_result(id, name,            │
│  │                                                output, is_error)   │
│  │          }                                                         │
│  │          Deny{reason} → {                                          │
│  │              // hook 拒绝或用户拒绝                                 │
│  │              ConversationMessage::tool_result(                     │
│  │                  id, name,                                         │
│  │                  merge_hook_feedback(pre_hook.messages,            │
│  │                                       reason, true),               │
│  │                  true  ← is_error                                  │
│  │              )                                                     │
│  │          }                                                         │
│  │      }                                                             │
│  │                                                                    │
│  │      3.12 记录工具结果到 session                                   │
│  │      session.push_message(result_message)                          │
│  │      record_tool_finished(iterations, &result_message)             │
│  │      tool_results.push(result_message)                             │
│  │  }                                                                 │
│  │                                                                    │
│  │  ──→ 回到 loop 开头,继续下一轮迭代 ──→                           │
│  └────────────────────────────────────────────────────────────────────┘
│
│  ╔═══════════════════════════════════════════════╗
│  ║  阶段 4: 构建回合摘要                          ║
│  ╚═══════════════════════════════════════════════╝
│
│  summary = TurnSummary {
│      assistant_messages,    // 所有迭代的 LLM 回复
│      tool_results,          // 所有工具执行结果
│      prompt_cache_events,   // 所有 prompt cache 事件
│      iterations,            // 总迭代次数
│      usage: self.usage_tracker.cumulative_usage(),
│      auto_compaction,       // 自动压缩事件(如有)
│  }
│
│  record_turn_completed(&summary)  ← session_tracer 记录
│  return Ok(summary)
│
└────────────────────────────────────────────────────

关键辅助函数详解

build_assistant_message(conversation.rs:715)

AssistantEvent 流转换成一个 ConversationMessage。处理逻辑:

事件流: TextDelta("我来") → TextDelta("帮你查") → ToolUse(read_file) → MessageStop步骤:1. text = "", blocks = []2. TextDelta("我来")    → text += "我来"3. TextDelta("帮你查")  → text += "帮你查"4. ToolUse(read_file)  → flush_text_block()→ blocks.push(Text { "我来帮你查" })→ text = ""→ blocks.push(ToolUse { read_file })5. MessageStop         → finished = true6. flush_text_block()  → text 为空,不操作7. 验证 finished && blocks 非空8. return ConversationMessage { blocks: [Text, ToolUse] }

flush_text_block(conversation.rs:774):

fn flush_text_block(text: &mut String, blocks: &mut Vec<ContentBlock>) {if !text.is_empty() {blocks.push(ContentBlock::Text {text: std::mem::take(text),  // 清空 text 并转移所有权});}
}

std::mem::taketext 替换为空字符串并返回原值。这是 Rust 中高效转移所有权的惯用模式,避免了克隆。

验证:

  • 必须收到 MessageStop → 否则返回 Err("assistant stream ended without a message stop event")
  • blocks 不能为空 → 否则返回 Err("assistant stream produced no content")

maybe_auto_compact(conversation.rs:564)

每次迭代后检查累积 token 是否超过阈值,是则自动压缩 session:

fn maybe_auto_compact(&mut self) -> Option<AutoCompactionEvent> {if self.usage_tracker.cumulative_usage().input_tokens< self.auto_compaction_input_tokens_threshold{return None;  // 未超阈值,跳过}let result = compact_session(&self.session, CompactionConfig{...});if result.removed_message_count == 0 {return None;  // 没有可删除的消息}self.session = result.compacted_session;Some(AutoCompactionEvent { removed_message_count })
}

默认阈值: 100,000 tokens。可通过环境变量 CLAUDE_CODE_AUTO_COMPACT_INPUT_TOKENS 覆盖。

run_session_health_probe(conversation.rs:301)

ROADMAP #38 特性。当 session 被 compact 过(self.session.compaction.is_some()),在下一次 run_turn 时发送无害探针验证 tool executor 是否正常工作:

fn run_session_health_probe(&mut self) -> Result<(), String> {if self.session.messages.is_empty() && self.session.compaction.is_some() {return Ok(());  // 刚压缩完且无消息,正常}let probe_input = r#"{"pattern": "*.health-check-probe-"}"#;match self.tool_executor.execute("glob_search", probe_input) {Ok(_) => Ok(()),Err(e) => Err(format!("Tool executor probe failed: {e}")),}
}

使用 glob_search 查询一个不可能存在的文件模式。如果探针失败,说明压缩后产生了不一致状态,提示用户 /session new

merge_hook_feedback(conversation.rs:790)

将 hook 反馈消息合并到工具输出中:

fn merge_hook_feedback(messages: &[String], output: String, is_error: bool) -> String {if messages.is_empty() { return output; }let mut sections = Vec::new();if !output.trim().is_empty() { sections.push(output); }let label = if is_error { "Hook feedback (error)" } else { "Hook feedback" };sections.push(format!("{label}:\n{}", messages.join("\n")));sections.join("\n\n")
}

合并后的格式示例:

工具执行结果...Hook feedback:
hook 产生的附加信息

权限系统

4 级决策链路

权限决策严格按照优先级从高到低评估:

第 1 级: PreToolUse Hook 取消 → Deny("hook cancelled")
第 2 级: PreToolUse Hook 失败 → Deny("hook failed")
第 3 级: PreToolUse Hook 拒绝 → Deny("hook denied")
第 4 级: 策略决策├── 有 prompter (交互模式)│     → permission_policy.authorize_with_context()│        ├── deny_rules 匹配 → Deny│        ├── allow_rules 匹配 → Allow│        ├── ask_rules 匹配 → 交 prompter 决策│        ├── 权限模式自动决策│        └── 都不满足 → 交 prompter│└── 无 prompter (非交互模式)→ permission_policy.authorize_with_context(..., None)└── 纯规则决策,不询问用户

PermissionPolicy 数据结构

struct PermissionPolicy {active_mode: PermissionMode,           // 活动权限模式tool_requirements: BTreeMap<String, PermissionMode>,  // 工具→所需模式映射allow_rules: Vec<PermissionRule>,      // 自动允许规则deny_rules: Vec<PermissionRule>,       // 自动拒绝规则ask_rules: Vec<PermissionRule>,        // 需询问规则denied_tools: Vec<String>,             // 无条件拒绝的工具列表(#159)
}

一次完整对话的轮次示例

以"帮我修改 main.rs 的 run_turn 函数"为例:

第 1 次迭代:API: TextDelta("让我先看看文件的内容...")API: ToolUse(read_file, {path: "main.rs"})API: MessageStop→ pending_tool_uses = [ToolUse(read_file)]→ hook 检查 → 权限 Allow → tool_executor.execute("read_file", ...)→ 结果入 session → 继续循环第 2 次迭代:API: TextDelta("现在我看到了,让我做修改...")API: ToolUse(edit, {path: "main.rs", old_string: "...", new_string: "..."})API: MessageStop→ pending_tool_uses = [ToolUse(edit)]→ hook 检查 → 权限 Allow (或提 prompter 询问用户)→ tool_executor.execute("edit", ...)→ 结果入 session → 继续循环第 3 次迭代:API: TextDelta("已经完成了修改,主要变化是...")API: MessageStop→ pending_tool_uses = [] → break→ TurnSummary { iterations: 3, assistant_messages: [...], tool_results: [...] }

错误处理

错误场景 触发位置 处理方式
会话健康探针失败 阶段 1 返回 RuntimeError,提示 /session new
超过最大迭代次数 阶段 3.0 返回 RuntimeError,记录 turn_failed
API 流式调用失败 阶段 3.2 返回 RuntimeError,记录 turn_failed
助手消息构建失败 阶段 3.3 返回 RuntimeError,记录 turn_failed
工具执行失败 阶段 3.11 将错误字符串作为工具结果,标记 is_error: true,不中断循环
Hook 拒绝/失败 阶段 3.9/3.11 拒绝工具调用或标记结果为 error,不中断循环

调用栈全景

LiveCli::run_turn(input)                                         ← main.rs│├── prepare_turn_runtime(true)                                 ← main.rs:5287│     └── build_runtime() → ConversationRuntime::new_with_features()│├── runtime.run_turn(input, prompter)                          ← main.rs:5325│     ││     └── ConversationRuntime::run_turn(input, prompter)       ← conversation.rs:318│           ││           ├── [健康检查] run_session_health_probe()          ← conversation.rs:301│           ││           ├── [记录输入] record_turn_started()               ← conversation.rs:589│           │     session.push_user_text()                     ← session.rs│           ││           └── loop (迭代 N 次)│                 ││                 ├── api_client.stream(request)                ← AnthropicRuntimeClient│                 │     └── consume_stream()                    ← main.rs:9493│                 │           └── client.stream_message()       ← api 层│                 ││                 ├── build_assistant_message(events)           ← conversation.rs:715│                 ││                 ├── usage_tracker.record(usage)│                 ││                 ├── maybe_auto_compact()                      ← conversation.rs:564│                 │     └── compact_session()                   ← compact.rs│                 ││                 └── for 每个 tool_use:│                       ││                       ├── run_pre_tool_use_hook()             ← conversation.rs:228│                       │     └── hook_runner.run_pre_tool_use_with_context()│                       ││                       ├── permission_policy.authorize_with_context()  ← permissions.rs│                       │     ├── 规则匹配│                       │     └── CliPermissionPrompter::decide()       ← main.rs:9301│                       ││                       ├── tool_executor.execute()             ← CliToolExecutor│                       │     ├── GlobalToolRegistry::execute()  ← 内置/插件工具│                       │     └── execute_runtime_tool()         ← MCP 工具│                       ││                       └── run_post_tool_use_hook()            ← conversation.rs:246│                             └── hook_runner.run_post_tool_use_with_context()│└── [成功] replace_runtime(runtime)                            ← main.rs:5309persist_session()

与 LiveCli::run_turn 的边界

关注点 ConversationRuntime::run_turn LiveCli::run_turn
LLM 请求循环 ✅ 核心实现
工具执行 ✅ 核心实现
权限决策 ✅ 核心实现
Hooks 执行 ✅ 核心实现
自动压缩 ✅ 核心实现
用量追踪 ✅ 核心实现
Spinner 动画
终端流式渲染 ✅ (委托给 AnthropicRuntimeClient)
结果打印 ✅ final_assistant_text
自动压缩重试 ✅ (context_window 错误恢复)
Session 持久化 ✅ persist_session
权限交互提示 ✅ CliPermissionPrompter

线程安全与状态管理

  • ConversationRuntime 不是线程安全的——它直接修改 self.sessionself.usage_tracker 等字段
  • 每次 run_turn 调用前由 LiveCli::prepare_turn_runtime 基于 session.clone() 创建独立的 runtime 实例
  • 成功返回后通过 replace_runtime() 将新状态合并回 LiveCli
  • 失败时丢弃 runtime,LiveCli 的原始状态不受影响

总结

ConversationRuntime::run_turn 实现了 claw-code 的核心对话模式——请求 → 响应 → 工具 → 再请求 的迭代循环

  1. 健康检查: 压缩后的 session 先探针确认一致性
  2. API 调用: 发送完整消息历史给 LLM,流式读取事件
  3. 事件解析: 将流式事件组装为结构化的 ConversationMessage
  4. 工具执行: 对每个 tool_use 执行 hook → 权限 → 执行 → hook 的完整链路
  5. 自动压缩: 超过 token 阈值时自动压缩 session 防止溢出
  6. 循环: 如果有 tool_use 则继续请求 LLM,直到 LLM 返回纯文本回复
http://www.jsqmd.com/news/904925/

相关文章:

  • 别再只盯着Delaunay了!Townscaper网格生成的‘松弛’(Relax)与‘整形’(Reshape)才是灵魂,附Unity可视化调试技巧
  • 为什么你的DeepSeek集群总在凌晨降级?揭秘GPU节点亲和性错配、NVLink带宽瓶颈与Prometheus指标盲区(附Grafana看板JSON)
  • 跨越天际:从智能汽车到 eVTOL 的适航与系统级开发7——飞行器级功能危害评估(FHA)与系统安全性评估(SSA)
  • 2026年5月淮安黄金回收哪家好?5家实测+避坑全攻略 - 生活测评君
  • 淮安外贸建站哪家专业?WaiMaoYa 外贸鸭一次建站投入,长期持续收益,赋能品牌出海 - 外贸营销驿站
  • 急疯!WPS兼容腾讯元宝公式的最佳方法?AI导出鸭实测后我扔掉了Pandoc
  • 告别繁琐账务,金蝶AI星辰助力中小企业轻松实现业财税一体化
  • 【数据分析】python-pandas速查文档(2)
  • 教育科技项目利用Taotoken为学生提供稳定的AI答疑接口
  • Web 红包题第二弹
  • Dism++终极指南:免费开源的Windows系统优化神器
  • 油压机PLC数据采集到MES系统,实现生产状态实时管控
  • 大语言模型上下文污染:成因、诊断与四层防御策略
  • 如何快速清理百度文库页面:三步免费获取纯净文档的完整指南
  • D3keyHelper终极指南:5分钟掌握暗黑破坏神3自动化技能宏
  • 【信息科学与工程学】【金融工程】【财务领域】【会计领域】第四十七篇 产品定价_非寿险定价01
  • AMD Ryzen终极调试工具SMUDebugTool:免费解锁硬件性能的完整指南
  • 学Simulink——风光储一体化并网逆变器的能量管理策略仿真
  • NVIDIA Profile Inspector新手入门:解锁显卡隐藏性能的终极指南
  • 零基础学 Python合集--1:list列表-持续更新
  • 【AVRCP】规范精讲[18]: 从字节到交互,全流程拆解AVRCP命令与响应实战
  • 2026最新【四六级历年真题2017-2025.12】分享
  • 解密浏览器Cookie本地导出:Get cookies.txt LOCALLY实战指南
  • 终极指南:Dell G15散热控制中心的开源替代方案完全解析
  • Python进阶 面向对象基础
  • 在线去本地视频水印的工具推荐:一篇实测横评看完
  • AI浪潮来袭:小白程序员如何把握机遇,成为超级个体并收藏这份成长指南?
  • 告别卡顿!实测对比:Parallels Desktop vs. VMware Fusion vs. UTM,谁才是Mac上跑Win10的最佳选择?
  • Path of Building PoE2终极指南:流放之路2最强构建规划工具完全教程
  • 2026农用薄膜十大品牌排行榜-农用薄膜哪个牌子好-大家比精选排行榜单 - GrowthUME