从零构建一个能干活的 Agent
跑通 demo 一小时,连跑三十次不翻车是另一回事。
Agent 拆开看特别朴素:一个 LLM,加一组工具,再套一个循环。让它调用个天气 API、查个数据库,demo 一小时就能跑通。但「跑通 demo」和「能日常干活」之间,隔着好几个具体的坑。这篇记下我从 demo 到能用的过程中,踩过的四个。
坑一:工具给太多,模型挑花眼
第一版我豪情万丈地注册了二十来个工具,结果模型频繁选错工具、参数填错。砍到 5 个职责清晰的工具之后,选错率肉眼可见地下降。好工具的标准就三条:名字说人话、参数有 schema、职责单一。工具列表不是能力展示墙,是给模型看的菜单——菜单越长,点错菜的概率越高。
坑二:工具结果一股脑全塞回去
查一次数据库,把整个结果集的 JSON 塞回上下文,十几 KB。模型的注意力被无关字段稀释,接下来几轮开始犯低级错误,比如张冠李戴地引用字段。改成「摘要 + 需要细节时再查」之后稳定了很多。
工具的返回值是给模型看的界面,要按「读者」优化:先给结论,再给结构化的关键信息,细节按需获取。
坑三:错误被程序默默吞掉
最开始我把工具执行包在 try/except 里,失败就返回一个 "error"。模型看到这个只能瞎猜。改成把错误结构化地返回——发生了什么、哪个参数不对、可行的补救动作——它自己就会调整策略重试,大部分错误根本轮不到我处理。
坑四:没有兜底
上线前补了四道保险:
- 最大循环次数(防死循环烧钱);
- 单步超时(防工具挂死);
- 总预算上限(防任务失控);
- 连续失败 N 次转人工(防它一条道走到黑)。
这几条上线后,最坏情况从「半夜收到天价账单」变成「收到一条转人工的通知」。
代码
把上面几条放进一个最小可用的循环,大致是这样:
MAX_STEPS = 24
for step in range(MAX_STEPS):
resp = llm.chat(messages, tools=TOOLS)
if resp.stop_reason == "done":
return resp.answer # 任务完成
for call in resp.tool_calls:
try:
result = run_tool(call) # 校验参数、限流、超时
except ToolError as e:
result = {"error": str(e), "hint": e.hint}
messages.append(tool_result(call.id, result))
# 失败也作为结果返回,模型会自己调整策略
handoff_to_human(messages) # 兜底:循环用尽,转人工
Agent 的可靠性,本质是工具设计的可靠性。模型负责推理,但你得先把「环境」修好。
· 完 ·