Qwen Code Tool-Use Summary 深度解析:用快模型为并行工具批次生成 Git 提交式摘要
2026/9/13 11:00:19 网站建设 项目流程

Qwen Code Tool-Use Summary 深度解析:用快模型为并行工具批次生成 Git 提交式摘要

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

本文基于仓库设计文档 docs/design/tool-use-summary/tool-use-summary-design.md 与用户手册 docs/users/features/tool-use-summaries.md,并结合packages/corepackages/cli的真实实现展开。当模型一次并行发起多个ReadGrepBash调用时,Qwen Code 会在批次结束后异步调用配置的快模型,返回一条 git-commit-subject 风格的短标签(如Read 4 text filesFixed NPE in UserService),用于替代默认的Tool × N头部。读完本文,你将掌握该功能的触发链路、三种开关语义、输出清洗规则、<Static>append-only 约束下的渲染设计,以及成本与隐私边界。

1. 功能概览:一条标签解决"并行工具看不懂"问题

Qwen Code 是一个运行在终端里的开源 AI 编程 Agent。当主模型在一个回合内扇出多个并行工具调用(例如同时read_file四个文件、再跑一轮grep)时,用户面对折叠后的Tool × N分组,往往需要逐个展开才能知道这批调用到底做了什么。

Tool-Use Summary 正是为此设计的 UX 增强:每个工具批次完成后,Qwen Code 向配置的快模型(fastModel)发起一次极短的调用,返回一条 git-commit-subject 风格的过去式标签,例如:

  • Searched in auth/
  • Fixed NPE in UserService
  • Created signup endpoint
  • Read config.json
  • Ran failing tests

该调用是 fire-and-forget(即发即忘)的,与下一轮主模型的 API 流式输出并行执行,约 1 秒的延迟被主模型 5–30 秒的流式响应完全掩盖,用户感知不到任何额外等待

设计文档在 Executive Summary 中用一张对比表系统梳理了与 Claude Code 同名能力的差异,下表摘录了最关键的几行:

维度Claude CodeQwen Code
触发点query.ts— 工具批次最终化之后use-llm-stream.tshandleCompletedTools(同一生命周期点)
生成模型Haiku(queryHaiku配置的fastModel(经runSideQuery
子代理行为!toolUseContext.agentId— 仅主会话隐式排除 — 子代理走agents/runtime/
输出形态SDK 流中的ToolUseSummaryMessageUI 历史中的HistoryItemToolUseSummary+ 导出工厂函数供未来 SDK 使用
开关CLAUDE_CODE_EMIT_TOOL_USE_SUMMARIES,默认offexperimental.emitToolUseSummaries(默认on)+ 环境变量覆盖
标签后处理模型原文cleanSummary(剥离 Markdown、引号、错误前缀;100 字符上限)
会话持久化仅流式,每次会话重新生成仅 UI 历史;ChatRecordingService不持久化tool_use_summary

该功能的实现并非简单照搬:Qwen Code 将默认值设为on、增加了settings.json持久化开关、加入了专门的cleanSummary清洗层,并针对 CLI 的渲染模型做了双路径展示设计。以下各节逐一深入。

2. Claude Code 参考实现:流的边界产物

设计文档首先剖析了 Claude Code 的做法,因为 Qwen Code 的实现是"行为对齐的移植"(源码注释明确写有Ported from Claude Code (services/toolUseSummary/toolUseSummaryGenerator.ts))。

2.1 参考流程

tool_batch_complete → fork queryHaiku (fire-and-forget) ↓ next_turn_stream_starts ↓ ← summary Promise resolves during streaming → ↓ await pendingToolUseSummary → yield ToolUseSummaryMessage ↓ continue with next turn

关键设计决策包括:

  1. 无论紧凑/详情模式都生成。摘要属于流级产物,由 UI 决定是否渲染;
  2. 作为一等消息类型发射tool_use_summaryuserassistanttool_result并列,通过precedingToolUseIds字段让消费方关联到具体批次;
  3. 排除子代理。子代理的输出在上游聚合,单独的批次标签只会产生主界面永远不展示的噪声;
  4. 默认关闭。仅环境变量门控,保证下游 SDK 未显式选择时零成本;
  5. 每字段 300 字符截断。覆盖最大的成本风险——单个大工具结果撑爆 prompt——同时保留足够信号。

3. Qwen Code 实现:从服务到双路径渲染

3.1 端到端数据流

Qwen Code 挂在相同的生命周期点,但渲染覆盖了ui.compactMode的两侧,让纯 CLI 用户无需任何 SDK 管道即可受益:

tool_batch_complete (handleCompletedTools) ↓ config.getEmitToolUseSummaries()? ↓ fork generateToolUseSummary (fire-and-forget) ↓ submitQuery() for next turn (streaming starts) ↓ ← summary Promise resolves during streaming → ↓ addItem({type:'tool_use_summary', summary, precedingToolUseIds}) ↓ HistoryItemDisplay renders: compactMode=false → ● <label> 独立行 compactMode=true → 隐藏;MainContent 查找注入 CompactToolGroupDisplay 头部

3.2 核心源码地图

组件文件关键逻辑
服务packages/core/src/services/toolUseSummary.tsgenerateToolUseSummarytruncateJsoncleanSummary、消息工厂
配置开关packages/core/src/config/config.ts#L8384-L8389getEmitToolUseSummaries:环境变量 → 设置 → 默认 true
触发packages/cli/src/ui/hooks/use-llm-stream.ts#L5637-L5683handleCompletedTools内 fire-and-forget,resolve 后addItem
全量模式渲染packages/cli/src/ui/components/HistoryItemDisplay.tsx#L551-L558!compactMode时渲染● <label>
紧凑模式查找packages/cli/src/ui/components/MainContent.tsxsummaryByCallId映射 → 每个 tool_group 的compactLabel
紧凑头部packages/cli/src/ui/components/messages/CompactToolGroupDisplay.tsx有标签时用<Summary> · N tools替换默认Tool × N
合并处理packages/cli/src/ui/utils/mergeCompactToolGroups.tstool_use_summary视为紧凑模式下隐藏项以保持相邻性
UI 类型packages/cli/src/ui/types.tsHistoryItemToolUseSummary{ type: 'tool_use_summary', summary, precedingToolUseIds }

说明:设计文档中记录的旧路径useGeminiStream.ts在当前仓库中已演化为 use-llm-stream.ts,触发逻辑位于该文件的handleCompletedTools回调内。阅读源码时以当前路径为准。

3.3<Static>append-only 约束:为什么标签是独立历史项

这是该 PR 的核心架构决策——为什么全量模式下标签是独立的 history item,而不是 tool_group 上的装饰

Qwen Code 通过 Ink 的<Static>渲染转录。<Static>append-only的:一旦某个条目提交到终端缓冲区,Ink 不会重绘该区域,除非调用refreshStatic()清空并整体重渲染整个转录。这正是 CLI 依赖的性能模型——静态条目不会在每次按键时重绘。

现在考虑快模型调用的时序:

T0 工具批次完成,tool_group 推入历史 T0+ε tool_group 经 <Static> 渲染并提交到缓冲区 T0+1s 快模型调用返回标签

在 T0+1s 时,我们无法把标签"追溯"塞进已经提交的 tool_group。两条路:

  1. 更新 tool_group 的 props + 调用refreshStatic()。可行,但每个批次都会触发整个转录重绘——这是应用中最昂贵的 UI 操作之一,且肉眼可见闪烁。为一个装饰性标签不可接受。
  2. 把摘要渲染成自己的新历史项,追加在 tool_group 之后<Static>原生支持新条目干净地追加,无需重绘。

该 PR 在全量模式下选择方案 2:tool_use_summary是真实的历史条目,由HistoryItemDisplay渲染为一行暗色● <label>

紧凑模式则不同:mergeCompactToolGroups合并连续 tool_group 时,MainContent本就会调用refreshStatic()——这是既有代码路径,重渲染合并组时可以从历史中查到标签。因此紧凑模式以头部替换的方式获得标签。为避免同一标签渲染两次(一次作为紧凑头部、一次作为尾部● <label>行),HistoryItemDisplaycompactMode为 true 时隐藏独立行:

Full mode Compact mode (with merge) ─────────── ───────────────────────── [tool_group] [merged tool_group — header replaced via lookup] ● <label> (● <label> line is hidden)

3.4 开关语义:三层优先级

三个层级,按优先级解析(见 config.ts#L8384-L8389 的getEmitToolUseSummaries):

  1. QWEN_CODE_EMIT_TOOL_USE_SUMMARIES=0|1|true|false—— 环境变量覆盖,最高优先级;
  2. experimental.emitToolUseSummariessettings.json)—— 默认true
  3. 隐式跳过—— 若config.getFastModel()返回undefined,无论开关如何都跳过生成。无报错,无可见变化。

此外在触发端还有一层运行时保护(use-llm-stream.ts#L5643-L5667):resolve 时会做"陈旧摘要检查"——只有当前批次对应的 tool_group 仍是历史中最新一个时才addItem。若快模型调用在途期间对话已经推进到更新的批次,摘要会被丢弃,避免● <label>行落在后续内容之后(全量模式)或归属到错误的组(紧凑模式)。

3.5 输出清洗:cleanSummary的六道工序

cleanSummary对每次模型响应在写入历史前执行清洗(完整实现见 toolUseSummary.ts#L264-L302):

  1. 只取第一行——丢弃模型推理前导段落;
  2. 剥离项目符号前缀-*)——模型有时会把标签当列表项返回;
  3. 剥离首尾引号/反引号——通过有界{1,10}正则(CodeQL 安全;真实标签的包裹引号不会超过几个)。字符类覆盖 ASCII 及常见 Unicode 引号对:"'\‘’“”「」『』`,兼容中文指令模型的弯引号与日式角括号输出;
  4. 剥离前缀标签Label:Summary:Result:Output:)——部分模型会预置这类词;
  5. 拒绝错误消息形态——API error: ...Error: ...I cannot ...I can't ...Unable to ...等英文,以及我无法我不能抱歉无法等中文拒绝语,命中即返回空串,不产生历史条目;
  6. 硬性 100 字符上限——移动端 UI 约在 30 字符处截断;余量用于覆盖 CJK 短语。

3.6 遥测与成本归属

生成调用设置promptId: 'tool_use_summary_generation'(在runSideQuery中以purpose: 'tool-use-summary'传递),其 token 用量在/stats中单独核算。用户可以精确看到该功能带来的增量成本,而不会与 prompt suggestions 或主会话的用量混淆。

4. 与 Claude Code 的偏差及原因

偏差原因
在环境变量之外增加设置层Qwen Code 在 CLI 中渲染标签,用户需要持久化开关,而非每次 shell 导出环境变量
默认on而非 off两种显示模式下标签都立即可见;配置了fastModel的用户本就在使用快模型特性
专门的cleanSummary后处理Qwen Code 支持的 provider 比 CC 更多样,部分模型会预置Label:或加引号;在边界处归一化以保持 UI 一致
存储HistoryItemToolUseSummary而非发射流消息CLI 优先的实现;SDK 流路径是后续 PR,ToolUseSummaryMessage工厂已导出备用
尚未接入 prompt caching未单独配置快模型的用户,快模型常与主模型相同;共享缓存需要经forkedAgent.ts路由,作为跟进项
双渲染路径(全量内联 + 紧凑头部)Qwen Code 默认ui.compactMode: false;没有内联全量渲染,该功能对大多数用户不可见

5. 用户侧配置与使用

5.1 配置快模型

标签由快模型生成——与 prompt suggestions、投机执行共用同一个fastModel。两种配置方式:

通过命令

/model --fast qwen3-coder-flash

通过settings.json

{ "fastModel": "qwen3-coder-flash" }

未配置fastModel时,摘要生成整体跳过——该功能在你配置之前不起任何作用(设计文档明确:回退到主模型是被刻意禁止的,以保证成本曲线有界)。

5.2 开关设置

设置类型默认值说明
experimental.emitToolUseSummariesbooleantrue摘要生成总开关。关闭以禁用额外的快模型调用
fastModelstring""用于摘要生成的快模型(与 prompt suggestions 共享)。必填;为空则无效果

环境变量覆盖QWEN_CODE_EMIT_TOOL_USE_SUMMARIES对当前会话覆盖上述设置:

  • QWEN_CODE_EMIT_TOOL_USE_SUMMARIES=0=false—— 强制关闭;
  • QWEN_CODE_EMIT_TOOL_USE_SUMMARIES=1=true—— 强制开启;
  • 未设置 —— 使用experimental.emitToolUseSummaries设置值。

完整示例

{ "fastModel": "qwen3-coder-flash", "experimental": { "emitToolUseSummaries": true } }

5.3 出现与不出现的条件

摘要生成需要全部满足以下条件:

  • experimental.emitToolUseSummariestrue(默认);
  • 已配置fastModel(settings 或/model --fast);
  • 批次中至少有一个工具完成;
  • 工具完成前回合未被中止;
  • 快模型返回了非空、非错误响应。

以下情况静默跳过(无报错、无 UI 变化):

  • 未配置快模型;
  • 快模型调用失败、超时或返回空;
  • 模型返回了明显错误形态字符串(如Error: ...I cannot ...)——由客户端过滤,避免展示误导性标签;
  • 回合在模型完成前被中止(Ctrl+C)。

所有跳过场景下,工具组都按原有方式渲染。

5.4 子代理边界

触发点位于主会话的回合循环(use-llm-stream.ts),因此:

  • ✅ Shell、MCP、文件操作,以及Task/子代理工具调用本身(以主批次成员出现时)会被摘要;
  • ❌ 子代理内部的工具批次(经packages/core/src/agents/runtime/运行)不会摘要。

包含Task工具的外层批次仍会获得标签,但快模型只看到子代理工具调用及其聚合输出——看不到子代理内部的单个工具调用。预期标签形态是Ran research-agentDelegated file search,而不是Searched 14 files。这是有意为之:摘要子代理内部会成倍增加快模型成本,并制造主界面永不展示的噪声。

5.5 生命周期要点

三个容易误读的细节:

  1. 每批次只生成一次,两种显示模式共享。快模型调用在工具批次最终化时(handleCompletedTools)恰好发生一次。之后切换Ctrl+O展开详情不会触发新调用——折叠与展开渲染都读取首次捕获的同一tool_use_summary历史条目;
  2. 切换或恢复会话时不回填。在功能启用(或你打开开关)之前完成的 tool_group,以及在恢复的会话中(ChatRecordingService不持久化摘要条目)不会获得标签。没有"扫描既有历史"的通道。会话中途开启该设置,只有之后的批次会显示标签;
  3. 仅主 Agent 批次。触发点在主会话的回合循环中。

5.6 显示行为

主视图已把完成的可折叠批次折叠为单行(✓ Read 4 text files)——摘要承担了旧版逐工具列表的工作。要看完整逐工具详情,按Ctrl+O切换展开详情模式,每个工具单独渲染,摘要以尾部● <label>行出现在组下方:

╭──────────────────────────────────────────────╮ │ ✓ ReadFile a.txt │ │ ✓ ReadFile b.txt │ │ ✓ ReadFile c.txt │ │ ✓ ReadFile d.txt │ ╰──────────────────────────────────────────────╯ ● Read 4 text files

对于小规模同类型批次(如Read × 3),展开态的● <label>行可能与可见工具行语义重复;若这正是你的常规工作流,可经experimental.emitToolUseSummaries: false整体关闭。

6. 数据流与隐私边界

摘要调用向快模型发送每个成功工具的名称、截断后的args与截断后的结果(每字段上限 300 字符),外加助手最近文本的前200 字符作为意图前缀。

  • 若快模型与主会话模型配置在同一 provider/auth 下,数据沿主会话已使用的同一边界流动,信任范围不变;
  • 若快模型来自不同 provider,工具输入与输出(可能包含read_file读到的文件内容、shell 命令输出、MCP 工具暴露的值)会随摘要 prompt 发送到该 provider——这是严格大于主会话的数据共享范围。

两个干净的应对选项:

  • fastModel配置为与主会话同 provider 的模型,使摘要调用不跨越新的认证/数据边界;
  • experimental.emitToolUseSummaries: false(或QWEN_CODE_EMIT_TOOL_USE_SUMMARIES=0)整体关闭。

300 字符/字段上限限制了暴露面但并未消除——截断窗口内工具输出中发现的密钥仍可能被发送。请以对待主模型数据边界的方式对待快模型。

7. 成本模型

每个达标的工具批次产生一次快模型调用。输入为小型固定系统 prompt 加上截断的工具输入/输出(每字段 300 字符上限);输出为单行短标签(100 字符上限,通常不超过 20 个 token)。generateToolUseSummaryrunSideQuery的采样参数为maxOutputTokens: 60temperature: 0.3,且maxAttempts: 1——标签是尽力而为的装饰品,每回合一次,瞬时故障时 7 次重试只会徒增流量而毫无收益(toolUseSummary.ts#L138-L151)。

当前仓库已知限制包括:

  • 无会话持久化tool_use_summary不写入聊天记录 JSONL。恢复会话会丢失标签,工具组回退到通用头部渲染。优先级低:继续会话时标签会自然重新生成;
  • 尚无 SDK 流发射。消息工厂已导出,但 CLI 尚未将tool_use_summary接入 SDK bridge;
  • 无 prompt caching。每个批次都产生一次全新输入 token 成本。绝对值可忽略(约 300 token),但每回合跑几十个批次时是可测的;
  • 合并紧凑组的摘要取首个批次的标签。连续 10 个不相似批次(紧循环,非典型场景)时,合并头部只显示领头批次的意图。这是接受的权衡:合并视图中展开每批次标签比只取首个更嘈杂;
  • 必须有快模型。未配置fastModel则跳过生成。

8. 未来工作

设计文档列出了四项后续规划:

  1. ToolUseSummaryMessage接入 SDK bridge,让已导出的工厂函数在下游真正被使用;
  2. forkedAgent.ts路由生成并启用enablePromptCaching,让重复的工具名前缀命中 provider 缓存;
  3. (可选)将tool_use_summary条目持久化到ChatRecordingService,并在会话恢复时重放;
  4. (可选)按工具名的标签快捷路径(例如单个read_file调用固定为Read <filename>),作为 LLM 前的快速通道。

9. 结语与验证入口

Tool-Use Summary 是"用一次便宜的调用换取整体可读性"的典型工程实践:fire-and-forget 的调度让 ~1s 延迟隐身于主模型流式输出之后,<Static>append-only 约束催生了"独立历史项 + 双路径渲染"的优雅解,而cleanSummary在异构 provider 环境下保证了 UI 的一致性。

希望深入验证的读者可以关注以下测试与实现入口:

  • 渲染测试:packages/cli/src/ui/components/HistoryItemDisplay.test.tsx(renders tool_use_summary as a dim badge line in full mode用例断言字符);
  • 合并场景测试:packages/cli/src/ui/components/MainContent.test.tsx(验证静态模式下tool_use_summary作为独立行保留、合并时不被丢弃);
  • 触发与陈旧检查:packages/cli/src/ui/hooks/use-llm-stream.ts#L5628-L5683;
  • 开关解析:packages/core/src/config/config.ts#L8384-L8389;
  • 清洗与截断:packages/core/src/services/toolUseSummary.ts。

相关功能联动可参考用户手册中的 Followup Suggestions(共享fastModel的另一快模型 UX 增强)以及 Expanded detail mode(Ctrl+O展开详情)。

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询