AI图鉴

AI图鉴首页 › AI 资讯 › AI Agent 学习总结:从模型到 Harness

AI Agent 学习总结:从模型到 Harness

来源:掘金 · 发布时间:11-02 · 由 AI图鉴每小时自动聚合

本文结合实战实验,从工具调用、ReAct循环、上下文管理、缓存优化、运行时护栏及Skills机制出发,探讨Model与Harness如何协作,构建Agent系统

前言

最近我在系统学习 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 在上下文中的位置、上下文压缩、隔离、记忆系统和工具发现。

这条路才刚刚开始!:)

本文由 AI图鉴自动抓取并翻译,版权归原作者所有;如需查看原文请点下方链接。

相关 AI 工具

相关免费羊毛

同源资讯

阅读原文(juejin.cn)

标题、摘要与正文均来自该信源的公开页面,版权归原作者所有;本站自动聚合与翻译,仅作出处跳转。

← 返回 AI 资讯列表AI工具图鉴首页

返回 AI图鉴首页 · 提交工具 · 商务合作 · 关于本站 · 607 款 AI 工具分类导航 · 每日 AI 热门项目与动态