很多 AI Agent 教程都有一个相似的结尾:安装框架,写一段 Prompt,注册两个工具,调用 run(),终端里打印出正确答案。到这里,作者会告诉你:“恭喜,你已经完成了第一个 Agent。”
你跟着敲一遍,确实也跑通了。可视频一关,真实需求来了,事情突然变得很奇怪。
比如现在有一个任务:
仓库里的
refund_should_return_full_amount测试失败了,请定位并修复;不允许修改测试。
第一行代码该写什么?先建向量数据库,把整个仓库放进去?先接 MCP?先做 RAG?还是直接上 Claude Agent SDK?如果框架已经替你写好了 Agent Loop,那你自己的工程价值又放在哪里?
很多人卡住,并不是因为没记住 API,而是因为教程通常只演示了“模型如何选择动作”,却跳过了三次跨越:程序如何执行动作,Harness 如何控制过程,评测如何证明系统有效。
这篇文章不再横向罗列名词。我们从最里面的一次模型调用开始,一层层把 Agent 装出来。主线成立以后,上下文、记忆、RAG、向量数据库、Tool 和 Skill,都会自然地找到位置。
一、先别急着谈 Agent,模型到底交付了什么
先把模型的能力边界说清楚。
对 GPT 这一类自回归语言模型来说,预训练和生成的基础机制,是根据已有上下文逐步预测后续 token。《深入理解 AI Agent》的模型原理部分也从这里展开。token 可以粗略理解成模型处理文字和代码时使用的片段;模型计算下一个 token 的概率分布,按解码策略选出一个,接到序列后继续生成。
这不等于说模型只会低级的“文字接龙”。在足够大的数据和计算规模下,预测后续 token 这个训练目标可以学出编程、推理、规划和工具选择等复杂能力。我们这里只需要守住一个工程边界:模型生成的是输出,它不会因为写出了“执行测试”,就真的在你的电脑上启动一个进程。
假设我们把退款测试交给一个没有工具的模型。它可以根据测试名猜测:也许代码把七天内全额退款算成了按比例退款。这个猜测可能很聪明,但它看不到仓库,不知道实现在哪个文件,也无法运行测试验证。
有了 Tool Calling 以后,模型可以返回一段结构化请求:
{
"name": "search_code",
"arguments": {
"query": "refund_should_return_full_amount"
}
}
它仍然只是输出。可以把 Tool Call 理解成申请单:模型写下“我想搜索这段代码,参数是这些”,谁来执行、能访问哪些目录,仍由模型外部的运行时决定。Anthropic 的 Tool Use 文档也明确区分了模型产生请求与应用代码或供应商服务器执行操作:Tool Call 是模型的决策,不是模型亲自执行工具。
所以,“LLM API 只能输出文本”也不够准确。接口可以返回文本、结构化数据、Tool Call,以及部分产品提供的推理摘要或推理块;供应商还可能在服务端执行搜索和沙箱。但无论怎样包装,模型本体都没有凭空长出文件系统、数据库连接和支付权限。
既然模型只会提出下一步,而不会完成外部动作,我们自然需要一段模型之外的程序来接住这张申请单。
二、从一张 Tool Call,推导出 Agent Loop
现在给程序注册四个工具:
search_code:搜索代码;read_file:读取文件;edit_file:修改文件;run_test:执行测试并返回日志。
第一轮,模型调用 search_code。运行时真正搜索仓库,把命中的文件和行号作为 Tool Result 返回。第二轮,模型读到搜索结果,再申请 read_file。第三轮,它申请修改实现并运行测试。测试仍然失败,失败日志再次进入上下文,模型据此调整判断。
这个过程的骨架并不复杂:
messages = [system_prompt, user_request]
for step in range(MAX_STEPS):
response = llm(messages=messages, tools=tools)
messages.append(response)
if not response.tool_calls:
return response.text # 只表示循环结束,不表示任务通过验收
for call in response.tool_calls:
result = execute_tool(call)
messages.append(as_tool_result(call, result))
raise MaxStepsExceeded()
这里的 return 只表示模型不再申请工具,并不证明退款 Bug 已修复。生产系统退出前还要做独立验收。除此之外,这十几行代码已经有了 Agent 最小的四个部分:
messages保存模型当前能看到的状态和历史轨迹;tool_calls是模型对下一步动作的选择;execute_tool()让程序真正读取或改变外部世界;tool_result把行动结果变成下一轮的新观察。
模型决策,运行时行动,环境返回观察,然后模型继续决策。这个“决策—行动—观察”的循环,与 ReAct 论文提出的 Reasoning + Acting 范式一脉相承。现代 Agent Loop 未必采用论文里完全相同的显式推理格式,但核心闭环相似:模型不是一次性猜完答案,而是在外部反馈中逐步推进任务。
ReAct 也不等于把模型的完整思维链展示给用户。工程上需要记录的是可观察轨迹:申请了什么工具、执行了什么、环境返回了什么、随后又做了什么。以常见的 Messages 或 Chat API 基础形态为例,运行时还要维护模型回复、Tool Call 和 Tool Result;messages 就是这个最小 Agent 的工作状态。
所以 Agent 最里面没有神秘的“自主意识”,只是模型根据环境反馈反复选择下一步。
但是,这段代码离一个真正能用的 Coding Agent 还差得很远。

模型选择动作,运行时执行动作;没有新的 Tool Call 只代表循环停止,是否成功要由独立验收判断。
三、为什么十几行 Loop 不是 Claude Code
先别急着往 Loop 里加更多功能,看看它会怎么坏。
模型可能反复搜索同一个关键词,没读完规则就修改核心逻辑,为了过关直接删测试,或者跑完局部用例就宣布完成。任务再长一点,重要错误会被膨胀的上下文淹没;进程一断,进度也丢了。
这些问题都不能靠“再调用一次模型”自动解决。我们必须在最小 Loop 外面继续加工程结构:
| 最小 Loop 暴露的问题 | 需要补上的工程能力 |
|---|---|
| 反复调用、无法结束 | 最大轮数、时间和费用预算、重复检测、熔断 |
| 修改了不该碰的文件 | 权限策略、目录边界、沙箱、人工审批 |
| 工具超时或返回脏数据 | 超时、重试、错误分类、参数校验、幂等性 |
| 上下文越来越长 | 选择、压缩、分层加载、会话状态和 Checkpoint |
| 模型说“修好了” | 单元测试、静态检查、状态核验、验收规则 |
| 改坏以后无法恢复 | Git 基线、回滚、可恢复的中间状态 |
这些围在模型外面、负责提供信息和工具,同时约束、验证、纠正模型行为的系统,就是 Harness。
《深入理解 AI Agent》给出了一套很实用的分解:
Agent = Model + Harness
Harness = 上下文 + 工具 + 约束 + 验证 + 纠正
这不是业界唯一的术语标准,也不一定对应五个独立模块,但很适合查工程缺口:上下文和工具让模型能做事,约束阻止危险动作,验证发现偏差,纠正负责重试、回滚、重新规划或交给人。

Model 负责预测下一步,Harness 负责准备条件、控制边界、检查结果,并在失败后纠正。
现在再看退款测试任务。一个成熟 Coding Agent 不只是会写代码。它能搜索仓库、维护当前 diff、限制修改范围、在沙箱里运行命令、保留 Git 基线、读取测试反馈,并在通过目标测试后继续执行相关回归测试。它之所以显得比十几行 Loop 强很多,差距主要就在这些外围工程里。
软件开发又恰好有编译器、类型系统、Linter、单元测试、Git diff 和 CI,这些都是便宜而明确的反馈。只要系统能指出错误并保证可恢复,模型就能继续迭代。Anthropic 在 Building effective agents 中也把自动验证和根据测试反馈迭代列为 Coding Agent 的关键优势。
所以,Coding Agent 并不是另一种神秘架构。它仍然是那个 Loop,只是代码世界已经替它准备了相对完整的眼睛、手脚、护栏和反馈系统。
理解了 Harness,很多流行名词也就不再是平行堆在架构图上的插件。它们分别在修补这个 Loop 的不同缺陷。
四、上下文、记忆、RAG、Tool、Skill,到底在解决什么
我们继续沿用退款测试,不做名词解释,直接让它出问题。
1. 模型忘了刚才发生了什么:当前轨迹
第一次测试失败后,如果日志没有回到 messages,模型下一轮就看不到反馈。Tool Result、之前的动作和当前进度构成本次轨迹,回答“这个任务刚才发生了什么”,而不是“这个用户过去是谁”。
2. 什么都塞进去,模型反而找不到重点:上下文工程
把整个仓库、全部日志、历史对话和所有工具说明一次性塞给模型,会增加成本和延迟,也让无关信息争夺注意力。上下文工程要决定的是:在这个决策点,模型需要看到什么,以什么顺序和结构看到。 对退款任务来说,失败日志、相关实现、规则、当前 diff 和项目约束重要,三个月前另一个模块的构建日志大概率没用。
上下文不是一个仓库。它是某一轮真正送进模型的 token。持久化信息只有经过选择、检索或加载,进入这次请求以后,才会影响模型当前的决定。
3. 新会话不知道用户习惯:用户记忆
“先解释根因,再修改代码”这类跨会话偏好,可以进入用户记忆。但“当前改了 refund_service.py,测试还没通过”属于任务状态或 Checkpoint。混在一起,就会把一次性进度永久写进用户档案。
4. 模型不知道团队规则:知识库
退款政策、系统架构、接口约定和历史故障属于团队共享知识,更适合进入带来源、版本和权限的知识库。它可以和用户记忆使用同一种数据库,区别在信息归属和生命周期:一个记录“张三喜欢先看结论”,另一个记录“签收七天内允许全额退款”。
5. 知识太多,不能全塞进上下文:RAG
知识库一旦变大,每轮完整加载显然不现实。于是需要先根据当前问题检索相关内容,把选中的片段注入上下文,再让模型继续生成。这就是 RAG,也就是检索增强生成。
RAG 是一条运行过程:
当前问题 → 检索候选资料 → 排序和过滤 → 注入上下文 → 模型决策
它并不等于“接一个向量数据库”。精确接口名和错误码用 BM25、全文搜索甚至 grep 可能更可靠;同义表达的召回则可能受益于 Embedding。实际系统常用混合检索,再用 Reranker 重排。
Embedding 是一种便于计算相似度的表示,向量数据库则是一种保存这些表示并做近邻搜索的基础设施。它们不能保证资料正确、最新或真的适用于当前任务。向量数据库不是记忆本身,也不是 RAG 本身。
6. 模型知道该做什么,却做不了:Tool
模型读完规则,知道要搜索代码、执行测试和编辑文件,仍然不能产生任何副作用。Tool 才是运行时暴露给模型的可执行能力边界。
工具设计也不是把内部 API 原样丢给模型。名称、参数、返回值、错误语义、权限和幂等性都会影响使用效果。生产写操作应优先提供范围清楚、可审计的专用工具,而不是无限权限的通用 SQL 执行器。
7. 工具都有了,模型每次还在重新摸索:Skill
退款问题有固定的排查经验:先复现测试,再核对政策版本,然后检查金额计算,最后运行回归测试;禁止先改测试,涉及订单状态迁移必须人工确认。
这类“遇到某种任务应该怎么做”的操作知识,可以写成 Skill。Skill 通常以按需加载的说明、脚本、模板和参考资料存在。它告诉模型怎样组合 Tool,但其中的脚本仍要由运行时或 Tool 执行。
Tool 和 Skill 的区别可以粗略记成两句话:
- Tool 回答“我能实际做什么”;
- Skill 回答“这类任务通常应该怎么做”。
《深入理解 AI Agent》第八章给过一个很实用的选择:事实和规则放进知识库,高频且要求确定执行的流程固化成代码工具,容易变化并且需要判断的操作经验写成 Skill。它们不是互相竞争的三个产品,而是三种不同性质的知识和能力载体。
到这里,我们已经不需要背名词了。每增加一个组件,都应该能回答:没有它,当前 Loop 会怎样失败;它改变的是输入、状态、动作还是验证;它在什么时候进入当前上下文;它又不负责什么。
五、自己做一个 Agent,常见的三条路
知道各层职责以后,工程选型就不该再从“哪个框架最火”开始,而应该先问:这个任务真的需要 Agent 吗?
如果一次模型调用加上合适的上下文就能解决,没有必要引入 Loop。如果步骤固定、分支清晰、合规要求高,普通代码工作流通常更稳定。只有当下一步无法预先写死,必须让模型根据开放环境动态判断时,自主 Agent 才开始有价值。
Anthropic 区分得很直接:Workflow 的路径由代码预先规定,Agent 的过程和工具选择由模型动态决定;他们同时建议从能解决问题的最简单方案开始,因为 Agent 往往用更多延迟和成本换取灵活性。
确认确实需要 Agent 以后,常见有三种实现路径。区别不在谁更“正宗”,而在你把成熟运行时嵌到系统哪一层。
路径一:自己实现 Loop 和 Harness
从 LLM API 和前面那十几行 Loop 起步,适合教学、工具较少的窄领域 Agent,以及对供应商替换、数据边界和状态机控制要求很高的系统。
代价也很明确。消息协议、工具调度、停止条件、上下文压缩、会话持久化、权限、重试、Checkpoint、Trace 和评测基础设施,都要由团队自己建设。真正的工作量从来不是写出 while,而是让这个 while 在异常、长任务和真实权限下仍然可控。
路径二:把成熟 Harness 作为 SDK 嵌入应用
Claude Agent SDK 的官方定位是 “Claude Code as a library”。它通过 Python 和 TypeScript 接口提供 Claude Code 的工具、Loop、上下文、权限、Hooks 和 Session 等能力。对代码理解、文件编辑和终端任务,这相当于把成熟 Coding Harness 直接嵌进自己的进程。
不过,SDK 不是完整的业务 Agent 产品。退款业务里的租户权限、订单幂等、领域审批、业务状态、最终验收、灰度策略和评测集,仍然属于你的应用。SDK 提供机制,你的产品负责政策。
路径三:把成熟 Agent 当成黑盒执行器
如果目标是尽快验证价值,可以通过 CLI、API 或子进程把成熟 Agent 当成黑盒执行内核,只在外层增加领域 Prompt、Skill、权限和验收。比如 claude -p 的程序化接口支持 JSON、会话恢复和工具权限,适合脚本、内部工具和 CI。
claude -p 当前也属于 Agent SDK 的程序化形态,与库接口共享 Claude Code 运行时;这里把它单列,是因为工程边界不同:第二条把 Harness 嵌进应用,第三条只消费一个进程或服务。黑盒封装上线最快,但并发、取消、Session 映射、审批和 Trace 复杂起来后,通常要迁到库接口或自有 AgentRuntime 边界。
真实工程通常不是纯粹的 Build 或 Buy。更常见的做法是:复用通用执行 Harness,自己掌握领域工具、数据、权限政策、业务状态、验证器和评测集。成熟运行时解决通用问题,产品价值留在你最了解的业务边界里。
选了哪条路,只决定这些能力由谁实现,并不证明 Agent 已经有效。三条路最终都会走到同一个问题:你怎么知道它真的做对了?
六、Agent 不是做出来的,是评测迭代出来的
回到最初的退款任务。其实用户在一句话里已经给了两个验收条件:目标测试必须通过,而且不允许修改测试。
生产环境还会继续补充:相关回归测试不能失败;退款规则不能被绕过;修改范围要合理;不能产生未经授权的外部副作用;轮数、延迟和成本需要落在预算内。
这些条件组合起来,才是一个最小评测任务。只写“修复退款 Bug”,然后看模型最后说了什么,无法判断任务有没有完成。
评测环境至少要明确任务集、初始状态、可用工具和验收规则,并保存 Trace:模型看到了什么、申请了什么、工具返回了什么、哪一步报错或触发权限检查。
评测对象也不是裸模型,而是 Model + Harness 的组合。同一个模型换一套上下文组织、工具接口和验证机制,结果可能完全不同。看到失败时,可以先做两类实验:
- 固定 Harness,只换强弱不同的模型,观察瓶颈是否随模型能力变化;
- 固定模型,关闭或替换 Harness 中的某个组件,观察这个组件到底有没有贡献。
前者是模型替换实验,帮助区分“模型不够强”还是“Harness 没把条件准备好”;后者是消融实验,用来定位 Harness 内部的问题。
接下来不要盯着一个总成功率,要看失败分布。退款 Agent 的失败可能来自不同层:
| 失败类型 | 典型现象 | 优先修改的位置 |
|---|---|---|
| 模型能力 | 已获得正确证据,仍做出错误判断 | 模型选择、推理策略、任务拆分 |
| 上下文 | 关键政策或测试日志根本没进入请求 | 检索、选择、排序、压缩 |
| 工具 | 参数容易传错、结果含糊、错误不可恢复 | Schema、返回结构、错误语义、幂等性 |
| 状态与控制 | 重复调用、中断后丢进度、无限循环 | Checkpoint、预算、熔断、恢复 |
| 验证器 | 修改测试也被判成功,或正确结果被误判 | Rubric、确定性检查、评测环境 |
能用单元测试、数据库最终状态和静态规则确定性验证时,优先用这些验证器。LLM-as-a-Judge 更适合评价报告质量、沟通完整性等开放维度,它不是万能裁判,也会受提示词、长度和模型偏差影响。
稳定性还要靠多次运行衡量。如果粗略假设五次运行相互独立、单次成功率都是 60%,那么“至少成功一次”接近 99%,“五次全部成功”却只有约 7.8%。真实 Agent 的失败往往相关,因此这只是说明两个指标回答不同问题:前者看能力上限,后者更接近稳定交付。
《深入理解 AI Agent》第六章用 AndroidWorld 构造了一个示教案例。这里必须说明:书中的具体百分比是假设数据,不是真实实验报告。这个案例的价值在于诊断顺序——面对复杂 UI 任务失败,先比较“换更强模型”和“给模型补充 UI 元素树”两种假设;如果更完整的环境信息带来更高性价比,就说明瓶颈首先在模型看见了什么,而不是模型有多聪明。
一次完整的优化闭环应该长这样:
定义任务和可执行验收标准
→ 运行并保存完整 Trace
→ 先检查评测器与环境是否可信
→ 区分模型瓶颈还是 Harness 瓶颈
→ 对失败聚类并提出可证伪假设
→ 做模型替换、消融或 A/B 对照
→ 重跑同一批评测任务
→ 比较成功率、稳定性、成本、延迟和护栏指标
→ 通过特性开关灰度发布
→ 将线上失败脱敏后沉淀为回归用例

评测不是给最终回答打一次分,而是让失败能够被定位、验证、修复并沉淀为下一轮回归用例。
看到分数下降先别急着改 Agent。测试环境可能坏了,评分器可能有 Bug,初始状态也可能漂移。应先审查失败 Trace,确认错误真的来自被测系统。
另一个误区是“优化 Agent 等于优化 Prompt”。有些问题确实能通过更清楚的指令解决;更多时候,真正有效的修改可能是缩小工具权限、让返回值更结构化、补一条确定性验证、调整检索内容,或者干脆把开放决策改回固定工作流。
Agent 工程的日常循环不是追逐新框架,而是:真实任务失败,沿 Trace 找到失败层,修改对应系统组件,再用固定评测集证明它确实变好了。
七、最后:Agent 天空里的两朵乌云
到这里,我们讨论的仍然是一个相对理想的 Loop:模型发出动作,环境返回结果,模型再进入下一轮。可真实世界并不会在模型思考时暂停。
用户会打断,页面会变化,后台任务会不断输出,新的事件可能在当前动作完成前到来。怎样让 Agent 流式、实时地与持续变化的环境交互,而不是永远停留在整齐的请求—响应回合里,是李博杰在“AI Agent 的两朵云”中提出的第一个问题。
第二个问题是学习。我们可以保存 Trace,把失败加入回归集,由工程师修改 Prompt、Tool 和 Harness。但这只是“系统被工程师改进了”,不等于 Agent 已经会从经验中安全地学习。
一次成功轨迹怎样提炼成可复用经验?新的 Skill 如何验证不会把偶然做法当成规律?错误记忆如何清理?能力更新以后,旧经验是否仍然有效?真正持续学习,需要经验提取、验证、准入、版本和回滚机制共同工作。把历史全部存进向量数据库,离“越用越聪明”还差得很远。
这两个问题不是行业公认的唯一分类,而是作者对当前 Agent 能力缺口的一种概括。但它们恰好揭示了同步 Agent Loop 的两条边界:环境不会停下来等待下一轮推理,保存轨迹也不等于学会经验。
回头看最开始那个问题:做一个 Agent,第一行代码究竟该写什么?
通常不是先安装框架,也不是先创建向量数据库。先写清任务是什么,允许系统做哪些动作,什么状态才算成功。然后用最小 Loop 跑起来,观察真实失败,再一层层补上必要的 Harness。
看懂教程,是认识了 Agent 的零件。真正会做,是知道每个零件在解决哪一种失败,并且能用评测证明,它确实把系统变得更可靠了。
参考资料
- 李博杰:《深入理解 AI Agent:设计原理与工程实践》
- 李博杰:语言模型的 token 预测机制
- 李博杰:Agent 基础知识与 Harness
- 李博杰:上下文工程与最小 Agent Loop
- 李博杰:用户记忆、知识库与 RAG
- 李博杰:Agent 评测与优化闭环
- Shunyu Yao 等:ReAct: Synergizing Reasoning and Acting in Language Models
- Anthropic:Building effective agents
- Anthropic:How tool use works
- Anthropic:Claude Agent SDK overview
- Anthropic:Run Claude Code programmatically
- 李博杰:AI Agent 的两朵云
