---
title: 本地搭 Agent：骨架很薄，护栏才是正文
author: 休言
date: 2026-10-10
url: https://www.fechuck.com/notes/2026-10-10-local-agent.html
---

# 本地搭 Agent：骨架很薄，护栏才是正文

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

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

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

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

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

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

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

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

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