Natural Language Tool State

2026-05-22#agent-runtime#tool-use#bash#self-prompting#tool-output

背景

这条观点来自一张关于 Claude bash 命令风格的截图。核心说法是:

  • Claude 生成 bash 命令时常会先写一行注释,再写实际命令。
  • 它也常用 echo&& echo "success" || echo "failed" 这类方式,把 exit code 翻译成自然语言。
  • 这些看似多余的文本,其实是在利用自然语言作为模型更容易消费的状态表达。
  • 设计 agent tool use 时,可以让 tool input 先承载 description / intent,让 tool output 返回自然语言状态,而不是只返回裸数据。

考证结论

这个观点的方向是对的,但需要拆成三层来看。

第一层:bash 注释确实会被 shell 忽略,但会留在模型已经生成的上下文里。GNU Bash 手册说明,非交互 shell 里以 # 开头的词会让该词及本行后续字符被忽略。因此,如果模型先生成一行注释,再生成命令,注释不会改变 shell 执行,却会在模型自己的生成轨迹里先固定意图。

第二层:自然语言中间态对模型行动有帮助,这有论文和工程文档支持。Chain-of-Thought 说明中间推理步骤能提升复杂推理;ReAct 进一步把 reasoning trace 和 action 交错起来,让推理轨迹帮助模型跟踪、更新计划并处理异常。这能解释为什么“先用一句自然语言说清楚要做什么”常常能让后面的 tool call 更稳。

第三层:tool description 和 tool output 的自然语言质量确实影响模型表现。Anthropic 文档明确说,工具 description 要解释工具做什么、何时使用、参数含义和限制,并称详细 description 是影响 tool performance 的重要因素。OpenAI function calling 文档也说明工具定义会注入到模型上下文里,function output 通常以字符串返回,格式可以是 JSON、error code 或 plain text,模型会按需要解释。

所以,更准确的说法不是“自然语言一定比结构化数据更高级”,而是:

对 LLM 来说,结构化状态负责可执行性,自然语言状态负责可解释性和下一步行动先验。

需要校正的点

截图里提到 OpenCode bash tool 有 description 参数。这个例子需要校正。

我查了 opencode-ai/opencode 当前源码(2026-05-22,commit 73ee493)和 README:内置 bash tool 的参数是 commandtimeout,没有独立的 per-call description 字段。它确实有一个很长的工具级 description,并且 command 参数本身也有 description;系统 prompt 还要求模型在运行非平凡 bash 命令前说明命令做什么、为什么运行。

这说明 OpenCode 的真实设计更接近:

  • 在工具 schema / tool description 里提前塞足语义。
  • 在 assistant 正文里让模型说明非平凡命令的目的。
  • bash tool input 本身仍保持 command 为主。

如果我们自己设计 custom bash tool,仍然可以加入 description / intent 字段,但不能把这说成当前 OpenCode 内置 bash tool 的既有事实。

设计建议

Tool input

对高风险、长耗时、可变更系统状态的工具,建议在 tool input 里显式加入 intentdescription 字段:

{
  "intent": "检查 dev server 是否被 3000 端口占用,并找出占用进程",
  "command": "lsof -i :3000"
}

这个字段的作用不是给 shell 看,而是让模型在生成 command 前先把动作意图压成一句稳定的自然语言。它相当于 tool-call 层面的 mini ReAct thought,但更适合被 runtime 记录、审计、审批。

Tool output

不要只返回:

{"exit_code": 1}

更好的返回是结构化状态加自然语言摘要:

{
  "status": "failed",
  "exit_code": 1,
  "summary": "部署失败:3000 端口已被 nginx(pid 8432) 占用。",
  "suggested_next_steps": ["改用其他端口", "确认后停止占用进程"]
}

这样 runtime 仍能依赖 status / exit_code 做确定性控制,模型也能从 summarysuggested_next_steps 里直接得到下一步行动的高概率路径。

Bash echo 模式的坑

cmd && echo "success" || echo "failed" 对模型友好,但对 runtime 不一定友好。因为最后执行的是 echo,整个 shell 命令的最终 exit code 可能变成 0,从而掩盖原命令失败。

更稳的做法是由 tool wrapper 捕获原始 exit code,然后同时返回:

  • 原始 stdout / stderr
  • 原始 exit_code
  • 归一化 status
  • 面向模型的自然语言 summary
  • 可选 suggested_next_steps

也就是说,不要用 echo 去替代状态机;要让 wrapper 把机器状态翻译成人类/模型都能读的状态。

可发展的文章角度

  • 标题方向:为什么 Agent Tool Output 不该只返回 exit_code
  • 主线问题:工具结果到底是机器状态,还是给模型继续生成的上下文?
  • 关键对照:裸状态码、自然语言摘要、结构化 diagnosis 三者各自服务谁
  • 可接到已有方向:tool call role design、Trace Log、multi-agent veto boundary
  • 结尾落点:好的 tool result 应该同时能被程序判断、被人审计、被模型继续行动

参考来源