一次“做 Excel 失败”触发的工具补全:REPL、数据导出和依赖管理
系列博客第五篇 · 2026-07-02
今天的故事从一个失败的对话开始。
一、今天的故事从一个失败开始
今天早上我在 REPL 里跟 Agent 聊了这样一段话:
我:你可以建一个 Excel 然后把工作区文件名都写入到里面吗?
Agent 说“好的”,然后开始调用工具。我满怀期待地等着,结果十几秒后它告诉我:
Agent:达到最大工具循环次数 8,任务未能明确完成。
失败了。
这不是模型的问题,也不是 API 的问题——而是我的 工具集不完整。Agent 手头没有“创建 Excel”的工具,所以它只能硬着头皮用 run_shell + pip install openpyxl + python -c 写一段临时 Python 脚本去绕路。Windows 上引号、多行命令、路径空格……任何一个环节出问题就 silent failure。
这次失败让我意识到两件事:
- Agent 的工具箱里缺了数据导出类的工具(CSV、Excel),它需要“直给”的能力,而不是绕路。
- 我已经在 REPL 里测试了,如果没有 REPL,我根本不会这么快发现这个问题——因为每次
npm run dev是单次执行,失败一次就结束了,很难在对话中发现工具缺口。
所以今天我一口气补了四个工具,同时把 REPL 和 Eval 也正式打磨了一遍。这篇文章从头到尾记录这个过程。
二、先看最终效果:两次对话的对比
2.1 失败的那次(修复前)
Session 文件里记录了完整的对话历史:
| 轮次 | 用户 | Agent |
|---|---|---|
| 1 | “我叫小明,正在学 Agent Harness” | “你好,小明!欢迎学习 Agent Harness…” |
| 2 | “我叫什么” | “你叫小明。” |
| 3 | “我现在这个工作区你可以看见吗” | “可以看见。当前工作区是 `xxxxxxxxx” |
| 4 | “你可以建一个 Excel 然后把工作区文件名都写入到里面吗” | “达到最大工具循环次数 8,任务未能明确完成。” |
| 5 | (重试)同上 | “达到最大工具循环次数 8,任务未能明确完成。” |
两次都失败了,因为 Agent 在循环里尝试用 run_shell + Python 绕路,但每次都卡在了 Windows 的转义问题上,直到 maxTurns = 8 耗尽。
2.2 成功的那次(修复后)
我补完工具后再问同样的问题:
我:你可以建一个Excel然后把工作区文件名都写入到里面吗?
Agent 直接调用了 create_excel 工具:
create_excel(path="exports/workspace_files.xlsx", from_workspace_files=true)
几秒钟后:
Agent:Excel 已生成!文件在
exports/workspace_files.xlsx,共 122 个文件(包含子目录下的所有文件),按路径排序,带序号和文件路径两列。
从“绕路 8 轮失败”到“一行工具调用 5 秒成功”,这就是补齐工具的价值。
三、具体新增了哪些工具?
工具从 7 个扩展到 11 个,新增四个:
| 工具 | 作用 |
|---|---|
write_csv | 写入 CSV 文件,比用 run_shell 拼接 Python 命令可靠得多 |
create_excel | 创建 .xlsx 文件;支持 from_workspace_files=true 一键导出工作区文件列表 |
run_script | 运行工作区内 .ts/.js/.py 脚本(自动识别用 tsx/node/python) |
install_deps | 在白名单内安装 pip/npm 依赖,可选 verify(验证是否安装成功) |
3.1 create_excel —— 今天的主角
这个工具最核心的参数是 from_workspace_files:
{
path: string; // 输出路径,如 exports/workspace_files.xlsx
from_workspace_files?: boolean; // 是否自动扫描工作区文件
data?: any[][]; // 或者手动传入二维数组
}
from_workspace_files=true 时,工具会自动:
- 递归扫描
workspaceRoot下所有文件(排除node_modules、.git等)。 - 生成带序号和路径的表格。
- 写入
.xlsx文件。
不再需要模型去理解 os.walk() 怎么写、openpyxl 怎么用。
3.2 write_csv——轻量版导出
如果用户只是想快速导出结构化数据,CSV 格式更轻量。write_csv 接受二维数组或对象数组,自动转成 CSV 格式写入。
3.3 run_script——编码闭环的关键
现在 Agent 的编码工作流变成了:
write_file → 写代码
edit_file → 改代码(局部)
run_script → 运行测试
install_deps → 缺依赖时补装
以前 write_file 写完代码后,Agent 只能用 run_shell 去执行,但 run_shell 不知道文件类型,路径拼接也容易出错。现在 run_script 会根据文件扩展名自动选择执行器:
.ts→tsx.js→node.py→python
3.4 install_deps——白名单 + verify
这个工具最核心的是 白名单机制:
// npm 白名单
const NPM_ALLOWLIST = ['xlsx', 'csv-parse', 'csv-stringify', 'dotenv', 'tsx', 'typescript'];
// pip 白名单
const PIP_ALLOWLIST = ['openpyxl', 'pandas', 'numpy', 'xlsxwriter', 'python-dotenv', 'requests', 'openai'];
不在白名单里的包,工具会拒绝安装。同时支持 verify 参数——安装后尝试 import 验证是否真的可用。
这个设计的原因是:我 不想让 Agent 随便装任何包,万一它要装一个恶意包或者不兼容的版本,整个项目就崩溃了。白名单是安全底线。
四、这一切发生在 REPL 里——交互式 REPL 正式上线
4.1 之前的问题:每次都是“一次性对话”
之前的 CLI 命令是:
npm run dev -- "问题"
每次运行都是一次性的:启动 → 执行 → 退出。如果你想连续问三个问题,就得运行三次命令,而且每次都是“失忆”状态(除非你手动加上 --continue)。
4.2 现在:真正的交互式对话
npm run repl
进入 REPL 后:
Agent REPL 已启动
输入 /help 查看命令
>
然后你可以像聊天一样连续输入,Agent 会保持上下文:
> 我叫小明,正在学 agent harness
你好,小明!欢迎学习 agent harness...
> 我叫什么
你叫小明。
> 我现在这个工作区你可以看见吗
可以看见。当前工作区是xxxxx
> 你可以建一个Excel然后把工作区文件名都写入到里面吗
Excel 已生成!文件在 exports/workspace_files.xlsx,共 122 个文件...
这其实就是在同一个 Node.js 进程内,每一轮自动维护会话。第一轮默认开新会话,之后的每一轮自动 continue。
4.3 REPL 内命令
| 命令 | 作用 |
|---|---|
/help | 显示帮助 |
/exit | 退出 |
/new | 下一轮开启新 session(遗忘之前的内容) |
/forget | 清除上次 session |
/tool | 强制 tool 模式 |
/direct | 强制 direct 模式 |
/mode | 恢复自动判断(默认) |
举个例子,如果你想暂时让 Agent 只做纯聊天,可以:
> /direct
> 你好,随便聊两句
想切回工具模式:
> /tool
> 帮我看一下这个目录
4.4 Session 文件长什么样?
以一次成功的对话为例,Session 文件记录如下(sess_20260702100806_922cik.json):
{
"sessionId": "sess_20260702100806_922cik",
"createdAt": "2026-07-02T10:08:06.148Z",
"workspaceRoot": "xxxxxx",
"messages": [
{ "role": "user", "content": "我叫小明,正在学 agent harness", "runId": "..." },
{ "role": "assistant", "content": "你好,小明!欢迎学习 agent harness...", "runId": "..." },
{ "role": "user", "content": "我叫什么", "runId": "..." },
{ "role": "assistant", "content": "你叫小明。", "runId": "..." },
{ "role": "user", "content": "我现在这个工作区你可以看见吗", "runId": "..." },
{ "role": "assistant", "content": "可以看见。当前工作区是...", "runId": "..." },
{ "role": "user", "content": "你可以建一个Excel然后把工作区文件名都写入到里面吗", "runId": "..." },
{ "role": "assistant", "content": "Excel 已生成!...", "runId": "..." }
]
}
只存文本对话,不存工具调用过程。每次 Run 的完整 Trace 依然在 runs/ 目录下,两者互不干扰。Session 负责记录“说了什么”,Trace 负责记录“执行过程”。
五、顺便把 Eval 强化了一下:Tool Call Checker
之前 Eval 只能检查最终答案是否包含关键词,但没法验证 agent 有没有正确调用工具。
比如一个任务要求“用 list_files 列出根目录”,模型可能直接回答了——没调工具,但回答看起来像那么回事。这就没法在 Eval 里发现。
现在我在 src/eval/toolCallChecker.ts 里增加了一层校验,任务可以声明:
expectToolCalls: {
minToolCalls?: number; // 至少调 N 次工具
maxToolCalls?: number; // 最多调 N 次
mustIncludeTools?: string[]; // 必须包含某些工具
mustNotIncludeTools?: string[]; // 不能包含某些工具
}
Eval 输出会区分 textCheck(文本匹配)和 toolCheck(工具调用校验),并写入 runs/eval_*.json 报告。
这样一来,我就可以批量跑测试,自动发现“任务完成了但工具没用对”这种 subtle bug。
六、总结
6.1 工具缺了就是缺了,模型绕不过去
第一次做 Excel 失败的时候,我一度在想“是不是模型理解能力不行?”后来看了 trace 才发现,模型在 8 轮里每次都调用了 run_shell,每次都卡在 Windows 引号转义上。它不是不想做,而是工具集里没有合适的工具。
这让我想起一个道理:Agent 的智能上限 = 模型能力 × 工具完备度。模型再强,工具缺了就是缺了,绕道只会增加失败概率。
6.2 REPL 是发现工具缺口的最佳方式之一
如果我只用 npm run dev -- "做Excel",跑一次,失败就结束了,我可能只会想“这次任务没成功”,而不会深入去排查原因。
但在 REPL 里,我可以连续追问、重试、观察 Agent 的行为模式,快速定位瓶颈。REPL 把“测试”变成了“对话”,这是开发 Agent 时非常重要的调试方式。
6.3 数据导出类工具应该是“直给”的
create_excel 和 write_csv 的逻辑很简单,但它们解决了 Agent 的一大痛点:把结构化数据从工作区搬到用户手里。之前 Agent 只能回答“文件列表是什么”,现在它可以“把文件列表生成 Excel 发给你”。前者是“说”,后者是“交付”。
6.4 白名单是安全的底线
install_deps 的白名单看起来限制很大,但我认为这是必要的。Agent 理论上可以执行任意 shell 命令,如果它某天决定 npm install some-malicious-package,后果不堪设想。白名单 + run_shell 黑名单 + pathSafety,三层安全机制虽然麻烦,但值得。
366

被折叠的 条评论
为什么被折叠?



