Skip to content

本地搭 Agent:骨架很薄,护栏才是正文 ​

最近一直在做 Agent 评估相关的工作,接下来一段时间也还会继续。做这件事需要一个能在本地反复折腾的被测对象:要有真实的多步工具调用,能随时换模型,每一步都能看得见。之所以自己搭,主要是为了能在上面装可观测的探针:代码在自己手里,才能看清探针到底采到了什么、漏了什么。

用的是 Hugging Face 开源的 smolagents,做了个「大厨助手」:问它冰箱里有什么、几个人吃,它查菜谱、按人数算用量、排好几道菜的先后顺序。做饭这个题材有可核对的答案,也有足够多的越界方式。

框架很快就跑起来了,但「能对话」和「能放心用」之间,隔着的全是框架管不到的东西。回头看,代码里最长的不是 Agent,而是一圈护栏:

约束写在代码里,不写在提示词里。 用量换算、按人数缩放、排时间线,全做成确定性的工具,不让模型心算。提示词里明确说了「不要编造」,实测它联网失败后照样凭记忆报克数,最后靠 final_answer_checks 做校验:答案里有具体用量、却既不是菜谱库里的菜也没标来源,就打回去重写。步数用尽时 smolagents 会绕过这道校验直接作答,出口处还得再查一次。

越界问题别进循环。 问医疗、法律、理财的,按关键词直接拦;其他明显无关的,先用一次轻量的模型调用判断,就地婉拒。不做成「拒答工具」,是因为等模型决定要不要调用它的时候,它已经在处理这个问题了。这几层都是在回答之前看输入。有些对话产品是另一种做法:一边流式输出,一边由独立的审核系统检查已经生成的内容,所以会出现先写了一半、再整段换成「暂不回答」的情况。看输入成本低、不会撤回,但只能凭问题判断;看输出能拦住模型真正写出来的东西,代价是用户会看到撤回。

工具失败要给出路,不要抛异常。 搜索一报错,模型就换个关键词重试,能连着烧掉四五步。改成返回一句「搜索不可用,别再试了,改用你自己掌握的做法并标注未核实」,一步就过去了。

这些护栏放到评估里看,正好就是测试点:越界的问题有没有拦住,该调工具的时候有没有自己心算,没依据的用量有没有标注来源。而要判断这些,只看最终答案不够。一个看起来很像回事的克数,可能是查了菜谱算出来的,也可能是编的,只有翻调用链才分得清。所以每次运行都用 OpenTelemetry 上报成一棵调用链:最外层是 Agent,下面挂着每次模型调用和工具调用,带着输入输出和 token 数;再配上随时切换模型,同一个问题换几个模型跑一遍,差异一目了然。

另外两个坑也记一下。一是接了个聚合多家模型的网关,有的模型不认 stop,有的不让传 temperature,最后改成哪个参数被拒就记下来、去掉重试。二是 smolagents 自带的 Gradio 界面,launch() 默认会开一条公网分享隧道,对一个能在你电脑上执行 Python 的 Agent 来说,等于把终端递给了陌生人,必须显式关掉。

框架解决的是「怎么让模型调用工具」,这部分已经很成熟了;没人替你解决的是「模型会在哪里不靠谱」。这些只能一个个试出来,再一处处用代码兜住。它们既是 Agent 真正的正文,也是评估该测的清单。

最后更新:

「本地搭 Agent:骨架很薄,护栏才是正文」
lo-fi · 实时生成