跳转到内容

工具结果为什么必须回到下一轮

用户要求:“读取 task.md,告诉我里面写了什么。”

模型发出 read 调用,handler 读到“把 Loop 跑通”。程序把结果打印到日志,随后继续请求模型。第二次请求没有带上这段结果,模型就会再次读取,或者直接猜答案。

工具没有坏,断掉的是 handler 之后的消息链。

第一轮模型调用产生动作请求:

user
读取 task.md 后回答
assistant
toolCall(id="read-1", name="read", path="task.md")

Harness 执行工具后,要追加一条与 read-1 对应的结果:

tool
toolResult(id="read-1", content="把 Loop 跑通。")

第二轮模型看到的顺序必须是:

user → assistant(toolCall) → tool(toolResult)

模型这时才有材料生成最终答案,或继续提出下一项动作。

调用 ID 很关键。同一条 assistant message 可以同时要求读取两个文件,也可能出现一个成功、一个失败。如果只按工具名称匹配结果,第二轮很容易把 a.txt 的内容接到 b.txt 上。

Loop 的主流程会收集 assistant message 中的工具调用,执行完成后把 tool result 放进上下文,再决定是否开启下一轮。查看 runAgentLoop() 的循环段

一条调用还要经历更细的步骤:

查找工具
prepareArguments
JSON Schema 校验
beforeToolCall
execute
afterToolCall
ToolResultMessage

这段实现集中在 agent-loop.ts 的工具执行部分

失败结果也必须回填。假如 read 返回“文件不存在”,模型需要看到这个观察,才能换路径或说明失败。如果异常只写进服务端日志,模型会继续沿着一次并未发生的成功执行推理。

模型可能在输出 JSON 参数时达到长度上限。此时工具名看起来完整,最后一个字符串或括号却可能缺失。Pi 会拒绝执行这一批可能不完整的 tool call,而不是替模型补齐参数。查看长度截断保护

对只读工具而言,猜错一次也许只是报错;对写文件、发消息或运行命令而言,猜参数会变成真实副作用。

运行实验:

Terminal window
cd agent-harness-lab
npm run lab -- 03

输出中有三项关键观察:

{
"terminal": "completed",
"turns": 2,
"toolCalls": 1,
"secondModelInputRoles": ["user", "assistant", "tool"]
}

turns 为 2,说明工具请求与最终回答分属两次模型调用;toolCalls 为 1,说明文件只读取了一次;第二次输入中出现 tool,直接证明结果已经回填。

如果第二个数组缺少 tool,即使模型偶尔猜对答案,也不能算 Loop 已经接通。

现在这条链路能读文件,但所有动作都被直接放行。下一章换成一条 schema 合法的写入请求,再把参数校验、权限判断和人工批准拆开。