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

claw-code 源码分析:结构化输出与重试——`structured_output` 一类开关如何改变「可解析性」与失败语义?

涉及源码src/query_engine.pysrc/runtime.pysrc/main.py;Rustrust/crates/tools/src/lib.rsStructuredOutput工具);对照rust/crates/claw-cli/src/app.rsOutputFormat,与 Python 开关不同名同源问题)。


1. 术语:仓库里至少有三条「结构化」线

机制位置structured_output的关系
QueryEngineConfig.structured_outputPythonquery_engine本文主角:决定 port 的TurnResult.output多行文本还是整段缩进 JSON 字符串
StructuredOutput工具Rusttools无关:模型通过工具调用提交任意 JSON 对象,执行器原样包进结果 JSON
OutputFormat::Json/NdjsonRustclaw-cliapp.rs无关:REPL/CLI 对人类可读回复再包一层 JSON 行

下面分述主角与旁支,避免读源码时混为一谈。


2. Python:structured_output如何改变可解析性

2.1 配置项

# 15:21:src/query_engine.py@dataclass(frozen=True)classQueryEngineConfig:max_turns:int=8max_budget_tokens:int=2000compact_after_turns:int=12structured_output:bool=Falsestructured_retry_limit:int=2
  • structured_output=False(默认)submit_messagesummary_lines'\n'.join(...)拼成多行纯文本。对下游而言是非 JSON:整段 stdout 不能json.loads一次吃干净(除非自行截取)。
  • structured_output=True:同一组summary_lines被包进对象{"summary": [...], "session_id": "..."},再json.dumps(..., indent=2)单文档合法 JSON 字符串(在序列化成功的前提下)。
# 152:159:src/query_engine.pydef_format_output(self,summary_lines:list[str])->str:ifself.config.structured_output:payload={'summary':summary_lines,'session_id':self.session_id,}returnself._render_structured_output(payload)return'\n'.join(summary_lines)

2.2 流式事件中的位置

stream_submit_messagemessage_delta里输出的text就是submit_message算出的result.output,因此开关同时改变 delta 载荷的可解析性

# 120:121:src/query_engine.pyresult=self.submit_message(prompt,matched_commands,matched_tools,denied_tools)yield{'type':'message_delta','text':result.output}

message_stop里的usage/stop_reason仍是结构化 dict(Python 字面量),与structured_output无关。

2.3 CLI 打印形态

turn-loop子命令把result.output原样 print,前面只加 markdown 风格标题;不会再包一层 JSON:

# 153:158:src/main.pyifargs.command=='turn-loop':results=PortRuntime().run_turn_loop(args.prompt,limit=args.limit,max_turns=args.max_turns,structured_output=args.structured_output)foridx,resultinenumerate(results,start=1):print(f'## Turn{idx}')print(result.output)print(f'stop_reason={result.stop_reason}')

因此:

  • 开启structured_output时,单轮 body是一段可json.loads的字符串,但整段 stdout仍含## Turn 1stop_reason=...——整体不是单一 JSON。要做管道解析需按节切片或只解析print(result.output)那一段。
  • 关闭时,body 为自然语言行,明确非 JSON

3. 失败语义与重试:structured_retry_limit

3.1 重试只针对json.dumps

# 161:169:src/query_engine.pydef_render_structured_output(self,payload:dict[str,object])->str:last_error:Exception|None=Nonefor_inrange(self.config.structured_retry_limit):try:returnjson.dumps(payload,indent=2)except(TypeError,ValueError)asexc:# pragma: no cover - defensive branchlast_error=exc payload={'summary':['structured output retry'],'session_id':self.session_id}raiseRuntimeError('structured output rendering failed')fromlast_error

语义要点:

  1. 触发条件:当前 payload 无法被json.dumps(例如summary_lines里混入了不可 JSON 化的对象——在正常路径里summary_lines全是str,该分支标注为pragma: no cover,属防御性)。
  2. 重试行为重试同一 payload;而是替换为极小安全 payload(固定一句'structured output retry'+session_id),再试,最多structured_retry_limit次。
  3. 最终失败:抛出RuntimeError('structured output rendering failed'),链上保留最后一次TypeError/ValueError。这是对调用方的硬失败;submit_message没有try/except 包住_format_output,故异常会冒泡TurnResult不会生成。
  4. 与业务stop_reason无关max_turns/max_budget_reached等仍走正常TurnResult序列化失败异常路径,不是stop_reason

3.2 早退路径与可解析性不一致

max_turns已达时,output是直接格式化的英文句子_format_output,因此即使structured_output=True,该轮也不会是 JSON

# 68:77:src/query_engine.pyiflen(self.mutable_messages)>=self.config.max_turns:output=f'Max turns reached before processing prompt:{prompt}'returnTurnResult(prompt=prompt,output=output,...stop_reason='max_turns_reached',)

结论structured_output只保证「正常摘要路径」下的 body 形态;错误/早退字符串仍可能破坏「每轮皆可json.loads」的假设——企业若要做严格 JSON 流水线,需在下游stop_reason分支统一包装 envelope


4. Rust:StructuredOutput工具(与配置开关无关)

工具定义允许任意额外字段additionalProperties: true),执行器把输入 map echo 到structured_output字段:

// 496:504:rust/crates/tools/src/lib.rsToolSpec{name:"StructuredOutput",description:"Return structured output in the requested format.",input_schema:json!({"type":"object","additionalProperties":true}),required_permission:PermissionMode::ReadOnly,},
fn execute_structured_output(input: StructuredOutputInput) -> StructuredOutputResult { StructuredOutputResult { data: String::from("Structured output provided successfully"), structured_output: input.0, } }

序列化走to_pretty_jsonserde_json失败则Result::Err(String)没有Python 那种「降级 payload + 多次重试」。失败语义是工具调用失败,由上层 runtime/对话环处理。

这与QueryEngineConfig.structured_output完全独立:前者是agent 工具链的契约,后者是Python port 的演示/测试输出格式


5. 对照:CLIOutputFormat(Rust)

claw-cli在写出 turn 结果时按OutputFormat选择文本或 JSON 行,例如:

// 270:288:rust/crates/claw-cli/src/app.rsmatchself.config.output_format{OutputFormat::Text=>{writeln!(out,"\nToken usage: {} input / {} output",self.state.last_usage.input_tokens,self.state.last_usage.output_tokens)?;}OutputFormat::Json=>{writeln!(out,"{}",serde_json::json!({"message":summary.assistant_text,"usage":{"input_tokens":self.state.last_usage.input_tokens,"output_tokens":self.state.last_usage.output_tokens,}}))?;}

这里JSON 由代码构造,不依赖对模型输出做json.dumps;失败主要来自 IO,而非「内容不可序列化」。与 Python port 的structured_output+ 重试是不同问题域。


6. 小结表

维度structured_output=Falsestructured_output=True
可解析性多行文本;无 JSON 保证正常路径下 body 为单文档 JSON 字符串summary+session_id
流式message_delta文本行同上整段 JSON 字符串
整页 stdout(turn-loop)非纯 JSON仍含标题与stop_reason=非单文件 JSON
序列化失败不适用降级 payload 重试structured_retry_limit次,再失败RuntimeError
max_turns 早退纯文本句子仍为纯文本,与开关不一致
budgetstop_reason正常 JSON/文本 body 后仍带max_budget_reached同左(body 形态仍由_format_output决定)

设计启示:若企业需要稳定、可机器校验的每轮 envelope,仅靠当前structured_output不够(早退、CLI 装饰行、混用测试)。更稳妥的是在边界层(HTTP/SSE、子进程协议)定义固定顶层 JSON,把人类可读段落放进字段,而不是依赖「整段 print 即 JSON」。


http://www.jsqmd.com/news/619880/

相关文章:

  • Windows 11终极清理指南:如何用Win11Debloat让你的系统重获新生
  • 从视频孪生到空间计算:镜像视界以AI重构三维感知新范式
  • 新手必看:用PHP伪协议轻松拿下SWPUCTF新生赛的include题(附完整payload)
  • OpenClaw Control UI 剪贴板 HTTP 兼容性问题解决方案 - Clipboard API 降级实践
  • 别再死记硬背了!用Python+Arduino动手搭建一个简易的计算机控制系统(从传感器到执行器)
  • 从“利旧”到“新建”:一个5G室外宏站共建项目的设备连接与布线实战图解(含AAU/BBU/SPN)
  • 2025届学术党必备的十大降重复率平台实际效果
  • [obsidian | claude code ]
  • 前端转行AI Agent:收藏这份干货,让你的技能值翻倍!
  • 紧急预警:2026Q2起,未集成AI原生能力的低代码平台将无法通过等保3.0+AI专项审计!SITS2026演示全合规路径曝光
  • 如何为表名加上图标前缀_根据表前缀自动匹配图标
  • Python实战:用gensim玩转LDA主题模型评估(困惑度+一致性可视化)
  • 南京生发机构哪家好?2026年权威甄选指南 - 小艾信息发布
  • zq—算法基础:时空复杂度()推
  • 开关电源纹波与噪声的实战抑制技巧:从理论到PCB设计优化
  • claw-code 源码分析:OmX `$team` / `$ralph`——把 AI 辅助开发从偶发灵感变成可重复流水线
  • 初学者如何避免HTML工具卡顿_低门槛高效开发配置建议【技巧】
  • 基于eNSP的校园网高可用与无线覆盖综合实验
  • FigmaCN中文插件:设计师的终极中文界面解决方案
  • Switch第三方控制器完美支持方案:sys-con系统模块深度解析
  • Xinference-v1.17.1在嵌入式开发中的应用:基于Keil5的AI模型部署
  • Multi-Agent在智慧城市管理中的应用模拟:交通、能源与公共服务的协同
  • C++STL小记
  • SITS2026权威解码:为什么92%的AI边缘项目在部署第48小时失败?
  • 如何构建个人数字图书馆:知识星球内容永久保存完整方案
  • 花49元年费能省120小时转录工时?2026年音频转文字推荐实测算完账至少省80冤枉钱
  • Backtrader-PyQt-UI:量化回测可视化工具的技术实现与应用实践
  • 微软发布的《生成式人工智能初学者.NET 第二版》课程卦
  • 从零到一:基于STM32与微信小程序的智慧农业系统全栈开发指南
  • CMIP6实战指南:AI驱动的降尺度技术与区域气候影响深度解析