🐸 青蛙编程网
首页 / AI / 脚手架 Harness / 3. 工具系统与执行编排

3. 工具系统与执行编排

这是什么:工具系统 = Agent 能调用的那些动作;执行编排 = 决定这些动作按什么顺序、什么方式发生。

为什么用:模型再强,接口设计得差也白搭——同一个模型换个编辑接口,评测得分能从 7% 变成 68%。工具就是 Agent 的手。

如何用:本章先讲工具描述和结果怎么写,再讲编排层怎么把多步骤任务串起来,最后看混合状态机和钩子这两种工程手法。

3.1 工具接口决定了任务完成率

回到那个最说明问题的实验:Can.ac 的编码评测里,模型一个字没改,只替换了文件编辑接口,得分从 6.7% 跳到 68.3%。

这说明工具接口的设计要满足几条:

要求 说明 反例
粒度合适 一次调用能完成一个完整意图 让 Agent 手写 diff 头
参数可推断 模型不看文档也知道该填什么 参数叫 p1/p2
结果可读 输出能直接指导下一步 只回 Error: failed
错误可行动 失败时给出「怎么改」 抛原始异常栈

粒度这一条最容易被忽略。给 Agent 一个 edit_file(path, old_string, new_string),它得自己在脑子里拼 diff;给一个 apply_patch(patch),它至少得自己写 diff。粒度越接近「人类意图」,模型越不容易错。

参数可推断也一样重要。工具描述是模型唯一的说明书:

TOOL_SCHEMAS = [{
    "type": "function",
    "function": {
        "name": "run_tests",
        "description": "运行项目的测试并返回失败明细。用户要求验证、跑测试、检查改动是否正确时使用。",
        "parameters": {
            "type": "object",
            "properties": {
                "target": {
                    "type": "string",
                    "description": "要测试的路径或包名,例如 'server/tests/test_users.py'。留空表示跑全部。",
                },
                "timeout_seconds": {
                    "type": "integer",
                    "description": "超时秒数,默认 120",
                },
            },
            "required": [],
        },
    },
}]

注意 description 里带了触发场景(「用户要求验证、跑测试时使用」),target 里给了具体例子——这跟 技能 Skills 里 description 的写法是同一个道理。

3.2 工具结果要「可读」

模型看到的是工具返回的字符串。这段字符串的质量,直接决定下一步的成败。

对比两种错误返回:

# ❌ 差:模型只知道失败了,不知道怎么办
return f"Error: exit code {code}"

# ✅ 好:失败原因 + 下一步建议
return (
    f"测试失败:3 个用例未通过(退出码 {code})\n"
    f"失败清单:\n{failed_lines}\n"
    f"建议:先修这 3 个用例;若为环境问题(如缺少依赖),"
    f"先跑 `pip install -r requirements.txt` 再重试。"
)

第二条多出来的几个字,是让 Agent「别在同一处反复试错」的关键。

日志格式也一样重要。 Carlini 那个用 16 个 Agent 写 C 编译器的项目里有个细节:日志写到文件而不是控制台,并且采用 grep 友好的单行格式,例如 ERROR: [reason]。需要诊断时再检索文件——这样无关日志不会持续占用上下文。

🤝 记忆口诀:结果要能指导下一步,错误要能指导下一步。

3.3 执行编排:让模型按轨道走

工具是「手」,编排层是「让它按什么顺序动手」。

一个稳妥的推进轨道是:

理解目标 → 判断还缺什么信息 → 分析 → 生成 → 检查

对应到实现上,通常是三类控制手段:

手段 作用 典型配置
系统提示约束 规定「先想清楚再动手」 「动手前先列出要改的文件」
工具选择策略 控制什么时候允许调工具 规划阶段禁用写文件
停止条件 什么时候算收敛 连续 3 轮无新信息就停

第 2 条值得展开:把「规划」和「执行」分成两个阶段,是 Harness 里性价比很高的一招。规划阶段只给只读工具(读文件、搜索、列目录),产出计划;执行阶段再放开写工具。这样做的好处:

  • 模型不会「边想边改」,把仓库改得面目全非
  • 计划本身可以被人类看一眼再放行
  • 出问题时更容易定位是「想错了」还是「做错了」

3.4 混合状态机:该死板的死板,该灵活的灵活

Stripe 的 Minions 是跑在大型既有代码库上的无人值守系统,每周三周合并 1300+ 个完全由 Agent 生产、人类没写一行代码的 PR。拆开看,它靠的不是「更强的模型」,而是一套成熟的编排环境:

组件 关键设计
Devbox AWS EC2 预装源码和服务,预热池分配,启动约 10 秒
编排状态机 混合节点:lint、push 这类走确定性流程;实现功能、修 CI 错误这类走 Agent
Toolshed MCP 集中式 MCP 服务,近 500 个工具,每个 Minion 拿到筛选后的子集
反馈回路 pre-push hook 秒级修 lint;推送后最多 2 轮 CI,覆盖 300 万+ 测试

其中两条设计值得直接抄:

一、混合状态机。 跑 lint、推送代码这种步骤,不需要模型判断,就该走确定性流程;实现功能、修 CI 错误这种需要判断的,才交给 Agent。该死板的地方死板,该灵活的地方灵活——这正是 Agent 栈 里工作流与 Agent 的老问题,Stripe 给出了工程答案。

def orchestrate(task):
    plan = agent_plan(task)                 # Agent:需要判断

    lint_result = run_lint(plan.files)      # 确定性:不需要判断
    if not lint_result.ok:
        lint_result = agent_fix_lint(plan)  # Agent:需要判断

    tests = run_tests(plan)                # 确定性
    if not tests.ok:
        tests = agent_fix_tests(plan, tests)  # Agent:需要判断

    push(plan)                              # 确定性:绝不让模型自己推
    return wait_ci(plan, max_rounds=2)      # 确定性:最多两轮,不无限重试

二、工具子集。 近 500 个工具全塞给一个 Agent 是灾难——工具太多,模型选错的概率直线上升。给每个角色筛一个子集,工具数量回到十几个。

3.5 钩子:在关键点插一道程序

钩子(hook) = 在 Agent 生命周期的固定位置,由 Harness 执行的程序化动作。

常见钩子位置:

钩子 时机 用途
PreToolUse 工具执行前 拦截危险命令、注入额外上下文
PostToolUse 工具执行后 自动格式化、自动 lint 修复
PreCommit 提交前 跑测试、跑检查、拦下不合格提交
SessionStart 会话开始 注入任务状态、加载交接文档

钩子是 Harness 里最像「代码」的部分——它不依赖模型的判断,因此可靠。Stripe 的 pre-push hook 能在秒级自动修掉 lint 问题,OpenAI 则把自定义 Linter 做成硬约束,让违规在机械层面就被拦下,而不是指望模型记得别犯。

OpenAI 有句话很直白:

If it cannot be enforced mechanically, agents will deviate. (不能被机械执行的约束,Agent 迟早会偏离。)

这条是 L2/L3 层最实用的结论:凡是能用程序检查的规则,就不要只写在提示词里。

3.6 小结:L2 + L3 的检查表

  • 每个工具的粒度接近「一个完整意图」
  • 每个工具的参数名和描述里有可照抄的例子
  • 每个工具的失败返回都包含下一步该做什么
  • 规划阶段与执行阶段的工具权限是分开的
  • 能用程序判定的步骤(lint、测试、push)走确定性流程
  • 每个角色拿到的工具是筛过的子集,不是全集
  • 关键节点(提交前、工具执行前后)挂了钩子

动手改

  1. 挑你项目里 Agent 最常用的三个工具,把它们的描述重写一遍:加上触发场景、给参数加具体例子,然后对比改前改后模型选工具的准确率
  2. 把某个工具的失败返回从 Error: xxx 改成「失败原因 + 建议下一步」的形式
  3. 在你的工作流里找一段「其实不需要模型判断」的步骤(比如格式化、跑固定测试),把它从 Agent 的活儿里拿出来,改成确定性脚本

小结

  • 工具接口设计直接决定完成率:粒度、参数可推断、结果可读、错误可行动
  • 工具描述里要写触发场景,参数描述里要给具体例子
  • 工具返回值要能指导下一步;日志写成 grep 友好的单行格式并落到文件
  • 编排层:规划与执行分阶段,确定性步骤走确定性流程
  • 工具太多会出事——给每个角色筛子集(Stripe:500 个工具分发出子集)
  • 钩子是 Harness 里最可靠的部分;能机械执行的约束就别只写在提示词里

下一章:记忆与状态,长任务的中途状态到底该放哪 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。