前言
最近我在系统学习 AI Agent。
一开始,我对 Agent 的理解比较简单:
大模型能够调用工具,就算是 Agent。
但真正跑完前 16 节实验以后,我发现事情并没有这么简单。
模型能够生成一个 tool_call,不代表工具真的执行了;工具在本地执行成功,不代表结果已经传给模型;模型最终回答正确,也不代表中间的数据链路是正确的。
有时候,一个看起来完全正确的答案,背后的执行过程可能早就跑偏了。
这段时间,我围绕下面这个公式做了大量实验:
Agent = LLM + 上下文 + 工具
进入工程环境以后,这个公式还可以进一步改写为:
Agent = Model + Harness
其中 Harness 包含:
上下文管理 + 工具接口 + 约束 + 验证 + 纠正
这篇文章,我就结合自己实际跑过的实验,聊聊我对 AI Agent 的最新理解。
话不多说!直接开始!
1、Agent 不只是一个会调用工具的大模型
我们先从最基础的公式开始:
Agent = LLM + 上下文 + 工具
这里的三个部分分别负责什么?
- LLM:根据当前输入决定下一步做什么
- 上下文:保存任务、历史消息、工具结果等信息
- 工具:真正读取文件、查询数据或修改外部环境
如果用 Android 开发来类比:
- LLM 像负责业务判断的决策模块
- 上下文像当前页面状态与任务数据
- 工具调用像 Binder
transact() tool_call_id则类似一次事务的 token
模型只负责生成调用请求。
真正把参数传给函数、执行函数、拿回结果并组装下一轮请求的,是外部程序,也就是 Harness。
1.1 一个答案正确,但链路不一定正确的实验
我运行的第一个任务是:
1000 美元等于多少欧元?再换算成人民币是多少?
本地模型最终给出的答案是:
1000 USD → 920 EUR
920 EUR → 7240 CNY
只看最终结果,好像完全没有问题。
但打开真实轨迹后,我发现模型在第一轮响应中,一次性生成了两个工具调用:
convert_currency(1000, USD, EUR)
convert_currency(920, EUR, CNY)
注意,此时第一个工具还没有执行。
也就是说,第二个调用中的 920 并不是模型读取第一个工具结果以后得到的,而是模型提前计算或猜出来的。
这就出现了一个很有意思的问题:
最终答案正确,但它不能证明第二步真的依赖了第一步工具结果。
如果汇率更复杂,模型提前猜错了,整个链路就会出现问题。
所以,判断 Agent 是否正确,不能只看最后一句话。
至少还要分别检查:
- 模型请求了什么工具
- Harness 实际执行了什么
- 工具返回了什么
- 结果有没有进入下一轮请求
- 模型最终使用了哪份数据
2、ReAct 的本质,是一个可以验证的反馈循环
很多文章会把 ReAct 解释为:
Reasoning → Acting → Observation
这个解释没有错,但如果只停留在这里,很容易把 ReAct 理解成“模型自己思考,然后自己完成任务”。
实际上,一次完整循环更接近下面这样:
用户任务
↓
Model 决定下一步
↓
Harness 执行工具
↓
Environment 返回结果
↓
Harness 把结果放入下一轮上下文
↓
Model 再次决策
如果类比 Android,它有点像:
Looper.loop()
↓
Handler.handleMessage()
↓
产生新的 Message
↓
继续进入消息循环
只不过 Android 消息分发通常是确定性的,而 LLM 即使看到相同上下文,也可能做出不同决策。
2.1 完整轨迹下,任务如何逐步推进
我运行过一个读取 PDF 并进行货币换算的任务。
基线实验一共进行了三轮模型请求:
第 1 轮:请求 parse_pdf
第 2 轮:读取 PDF 结果,请求 convert_currency
第 3 轮:读取换算结果,生成最终回答
真实轨迹中可以看到:
PDF 内容:Japan Office: ¥380,000,000 JPY
下一轮模型调用的工具参数为:
amount = 380000000
from_currency = JPY
to_currency = USD
工具最终返回:
converted_amount = 2541806.02
这条链路中,PDF 里的金额进入了下一轮工具参数,换算结果又进入了最后一轮请求。
因此,我们才能说:
本次任务确实沿着“读取文件—提取金额—执行换算—生成答案”的路径推进了。
2.2 删除历史以后,会发生什么?
接下来我只修改了一个条件:
context_mode: full → no_history
模型、任务和工具都没有变化。
结果模型连续十轮请求 parse_pdf,每一次 Harness 都成功读取了 PDF,但始终没有进入货币换算。
原因也很直接。
在 no_history 模式下,每轮请求只保留:
system + user
上一轮的 assistant.tool_calls 和 role=tool 结果没有进入下一轮上下文。
于是对于模型来说,每一轮都像第一次收到任务。
这个实验让我真正理解了一件事:
工具在本地执行成功,不等于模型看到了工具结果。
Harness 如果没有把 Observation 正确放回上下文,ReAct 循环就断了。
3、工具调用不是一个动作,而是一条证据链
以前看到日志里出现 tool_call,我会下意识认为工具已经执行了。
现在来看,这个判断非常危险。
一次工具调用至少包含下面几个阶段:
模型请求工具
↓
Harness 校验参数
↓
Harness 执行工具
↓
工具返回结果
↓
Harness 构造 role=tool 消息
↓
结果进入下一轮模型请求
↓
模型使用结果生成答案
这些阶段必须分别核对。
3.1 tool_call_id 配对能证明什么?
在工具调用协议中,通常会看到:
assistant.tool_calls[0].id = call_123
tool.tool_call_id = call_123
这只能证明:
这条工具结果在消息结构上对应某个工具调用请求。
它不能单独证明:
- 工具真的执行过
- 执行参数没有被 Harness 修改
- 工具产生了真实副作用
- 模型正确理解了工具结果
就像 Binder 中事务 token 对上了,只能证明请求与回复完成了配对。
至于服务端内部到底做了什么,还需要检查真正的执行记录和权威状态。
3.2 executed=true 也不一定代表副作用完成
在提示注入实验中,我遇到了一个特别典型的情况。
程序已经记录:
executed_tool_calls = 1
但目标文件并不存在。
追到源码才发现,executed_tool_calls 是在真正写文件之前记录的。
如果后续路径检查或写入过程失败,就可能出现:
executed = 1
file_exists = False
所以,“执行记录”这个名字看起来很强,但它实际能证明什么,仍然要以记录位置和源码为准。
如果要确认写文件成功,最权威的证据还是:
文件是否真的存在
文件内容是否正确
工作区事件是否记录
4、上下文不是聊天记录,而是每次请求的输入快照
学习 Agent 之前,我习惯把上下文理解成“聊天历史”。
但实际工程中的上下文更准确地说是:
模型在某一次 API 请求中真正收到的全部输入。
它可能包含:
- system 消息
- user 消息
- assistant 历史
- tool 结果
- 工具定义
- Skill 目录
- 缓存前缀
- Harness 动态注入的规则
Harness 内部保存了某条数据,并不代表模型一定收到了。
反过来,最终历史中出现某条消息,也不代表它真的进入过当时的 API 请求。
因此,排查问题时应该优先查看真实的:
request.messages
request.tools
response.tool_calls
而不是只看程序最后拼出来的完整历史。
4.1 消息角色错了,模型也可能答对
我还做过一个消息角色实验。
基线中,工具结果使用:
role = tool
对照实验只把它改成:
role = user
其他文本内容保持不变。
本地模型仍然读取了其中的金额,并给出了正确答案。
但这不能证明两种角色是等价的。
它只能说明:
本次模型接受了这段被错误标记为 user 的内容。
换一个模型、Provider 或更加复杂的任务,结果可能完全不同。
一次容错成功,不能用来证明协议正确。
5、缓存优化的重点,是保持前缀稳定
上下文不仅影响模型行为,还会影响推理成本。
这里需要区分两个概念:
- KV Cache:同一次生成过程中,对已有 token 的 K/V 张量进行复用
- Prompt Cache:跨 API 请求匹配稳定前缀,复用之前已经计算过的内容
两者都依赖一个重要条件:
前缀尽量保持稳定。
这有点像 Jetpack Compose 的稳定性优化。
如果组件输入稳定,就可以跳过不必要的重组;如果每次都修改前面的结构,即使后面内容相同,也很难复用之前的计算。
5.1 缓存比例与缓存价格不是一回事
在成本实验中,我只把缓存 token 的价格比例从:
0.1 → 0.5
实验中的 Prompt Token、Cached Token 和 Cache% 都没有变化:
Prompt Tokens = 7567
Cached Tokens = 768
Cache% = 10.1%
但估算的计费 token 从:
6876 → 7183
节省比例从:
9.1% → 5.1%
这说明缓存命中情况和缓存价格是两件不同的事。
价格变贵不会让已记录的缓存 token 自动减少,只会改变成本估算。
而且这仍然只是 Harness 根据输入比例计算出的估算值。要判断真实费用,还需要继续核对服务商的计价规则与账单。
6、Prompt 很重要,但不能替代运行时约束
提示工程当然有用。
但是学习到后面,我越来越清楚:
Prompt 主要影响模型如何决定,Harness 才能约束系统最终允许什么发生。
6.1 删除工具描述,不一定立即选错工具
在工具定义实验中,我给模型提供了两个工具:
find_files
search_text
任务要求搜索包含 runtime_guard 的 Python 代码。
完整工具描述下,模型生成:
search_text(
query = "runtime_guard",
path_glob = "**/*.py"
)
删除五处描述后,模型仍然选择了 search_text,但参数变成:
path_glob = "*.py"
这次实验只能说明:
- 本次模型没有因为删除描述而改选其他工具
- 但参数范围发生了变化
- 工具名、参数名和 Schema 本身仍然提供了语义线索
而且两次探针都没有真正执行搜索。
因此不能根据这一次结果,就宣布“工具描述没有用”或者“删除描述会提高效果”。
6.2 运行时护栏才是真正的最后防线
在提示注入实验中,我让同一个未授权写文件请求分别经过两种配置:
D3:只有提示词防御
D4:增加 runtime guard
修正路径干扰以后,结果为:
D3:
requested = 1
executed = 1
workspace_events = 1
file_exists = True
D4:
requested = 1
executed = 0
workspace_events = 0
file_exists = False
D4 并不是期待模型永远不发出危险请求。
它的作用是:
即使危险请求已经到达 Harness,也要在真实副作用发生之前拒绝。
这和 Android runtime permission 很像。
应用可以请求访问相机,但最终能不能调用成功,不能只依赖开发者在 Prompt 或注释里写一句“请谨慎使用相机”。
真正的授权必须由运行时系统执行。
7、Agent Skills 的核心,是渐进式披露
学习到 Agent Skills 时,我一开始把它理解成“更长、更专业的 Prompt”。
但 Skills 真正有价值的地方,不只是保存提示词,而是把知识分层加载。
它通常可以分成三层:
第一层:Skill 名称和描述
第二层:完整 SKILL.md
第三层:参考资料、脚本和资源文件
如果用 Android 类比,它很像:
Manifest 声明能力
↓
命中场景后加载动态模块
↓
需要时再读取模块内部资源
启动时只向模型暴露轻量目录,等任务命中某个 Skill 后,再加载完整说明和子资源。
这样可以减少无关内容对上下文的占用。
7.1 有产物,不代表 Agent 自主选择了 Skill
在离线实验中,程序读取了 SKILL.md 和参考资料,并最终生成了一份可以正常打开的 PPTX。
但这个结果不能证明:
- 模型自主发现了 Skill
- 模型决定激活 Skill
- 模型亲自打开并检查了 PPTX
- 模型根据检查结果修改过产物
因为这次离线实验的调用顺序是程序预先安排的。
日志只能证明 Harness 确实经过了对应的加载通道。
如果要证明模型自主使用了 Skill,还需要继续补齐:
模型实际收到的 Skill 目录
↓
模型发出的 Skill 激活请求
↓
Harness 返回的 Skill 正文
↓
正文进入后续模型请求
↓
模型基于正文继续决策
这也是我学习过程中反复遇到的一条原则:
产物正确,不等于过程已经被证明。
8、我最大的收获:开始用证据定位 Agent 问题
学完这些内容以后,我解决 Agent 问题的方式发生了变化。
以前遇到错误,我的第一反应通常是:
是不是模型不够强?
现在我会沿着下面这条链路逐层排查:
1. 模型实际收到了什么?
2. 模型请求了什么工具和参数?
3. Harness 是否按原参数执行?
4. 工具真正返回了什么?
5. 结果是否进入下一轮请求?
6. 模型是否使用了这份结果?
7. 最终答案是否与权威状态一致?
只有前面的证据都成立,才应该继续怀疑模型对结果的理解或决策能力。
否则,直接更换更大的模型,很可能只是暂时掩盖 Harness 中的问题。
9、总结:Agent 工程的重点,是建立可靠的闭环
学完前 16 节以后,如果让我重新解释 AI Agent,我不会再只说:
Agent 就是一个能够调用工具的大模型。
我会把它解释为:
Agent 是一个由 Model 做决策、由 Harness 管理上下文和工具、通过环境反馈不断推进,并且能够被约束、验证和纠正的系统。
模型决定它想做什么。
工具决定某个动作怎样执行。
而 Harness 决定:
- 模型能看到什么
- 模型能够调用什么
- 哪些动作允许执行
- 结果怎样回到下一轮
- 错误如何被发现
- 系统什么时候才能宣布任务完成
所以,真正进入工程以后:
Agent = Model + Harness
模型当然重要。
但一个没有约束、验证和纠正机制的系统,即使模型很强,也可能在看见错误后继续提交错误答案。
这 16 节学习带给我的最大变化,不是又记住了多少 Agent 术语。
而是当一个 Agent 给出结果时,我开始本能地追问:
这个结论,有哪一层证据可以证明?
我觉得,这才是从“会使用 AI”走向“会构建 AI Agent”的真正分水岭。
接下来,我还会继续学习 Skills 在上下文中的位置、上下文压缩、隔离、记忆系统和工具发现。
这条路才刚刚开始!:)