从零打造 AI Agent:核心闭环篇(S01-S06)
只要任务还没做完,系统就继续重复同一套步骤。就像工厂流水线,产品没完成,传送带就一直转。核心闭环 = Agent Loop + 工具路由 + 待办写入 + 子代理 + 技能系统 + 上下文压缩Agent Loop = 发消息 → 看回复 → 执行工具 → 写回结果 → 下一轮工具路由 = dispatch map = {tool_name: handler}待办写入 = pending → in_
🚀 从零打造 AI Agent:核心闭环篇(S01-S06)
没有循环,就没有 Agent。这不是一句鸡汤,这是 AI 从"会说话"变成"会干活"的唯一途径。
🎬 开场:为什么你的 AI 总是"纸上谈兵"?
你有没有遇到过这种情况?
你让 AI 帮你写代码,它洋洋洒洒写了一千字,分析了需求,设计了架构,甚至画了流程图…
然后呢?
然后就没有然后了。代码只存在于它的"想象"里,你的文件夹空空如也。
问题出在哪?
问题出在:AI 只能"说话",不能"做事"。
它可以生成文本,但它不会自己去打开文件、运行命令、观察报错,把结果再喂回给模型继续推理。
那么,怎么让 AI 从"会说话"变成"会干活"呢?
答案就是这一系列文章的主题——打造真正的 AI Agent。
📚 这个系列要讲什么
Learn Claude Code 将构建 Agent 系统分成了 19 个章节,我们将其整合成 4 个大部分:
一、核心闭环(S01-S06)
基础能力建设,让 AI 从"说话"变"干活"
二、系统加固(S07-S11)
安全与稳定,让 AI 更可靠
三、任务运行时(S12-S14)
任务管理,让 AI 能协作
四、多 Agent 平台(S15-S19)
团队协作,让 AI 团队作战
今天,我们先从核心闭环开始。
🍳 先用做菜打个比方
想象你要做一道宫保鸡丁:
没有闭环的方式
- 你告诉厨师:“我要宫保鸡丁”
- 厨师说:“好的,需要去买鸡丁”
- 你说:“那去买吧”
- 厨师去买回来
- 厨师说:“鸡丁买回来了,还要花生”
- 你说:“那去买花生”
- …(无限循环,累死你)
有闭环的方式
- 你跟厨师说:“给我做一道宫保鸡丁,具体你自己看着办”
- 厨师自动去买鸡丁
- 回来告诉你"鸡丁买好了,要炒了"
- 你说"炒"
- 炒完说"该放花生了"
- 你说"放"
- 最后说"做好了"
Agent Loop 就是这个道理:任务 → 执行 → 反馈 → 下一步 → … → 完成。
🎯 一句话记住
Agent Loop 的本质,是把"模型的动作意图"变成"真实执行结果",再把结果送回模型继续推理。
� S01:Agent Loop - 核心闭环
什么是 Loop?
不是程序死循环,而是:只要任务还没做完,系统就继续重复同一套步骤。
就像工厂流水线,产品没完成,传送带就一直转。
最小心智模型
用户消息
↓
模型
↓
+-- 普通回答 → 结束
|
+-- 调用工具 → 执行
|
↓
工具结果
↓
写回消息历史
↓
下一轮继续
关键点只有一句:
工具结果必须重新进入消息历史,成为下一轮推理的输入。
少了这一步,模型就无法基于真实观察继续工作。
5个必须懂的名词
- Loop(循环):只要任务没完成,就继续重复同一套步骤
- Turn(一轮):发消息给模型 → 读回复 → 执行工具 → 把结果写回消息历史
- tool_result(工具结果):工具执行完返回的东西,必须写回消息历史
- State(状态):主循环继续往前走时,需要一直带着走的那份数据
- Messages(消息历史):不是聊天记录,而是模型下一轮要读的工作上下文
最小实现代码
# 第1步:准备初始消息
messages = [{"role": "user", "content": query}]
# 第2步:调用模型
response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=messages,
tools=TOOLS,
)
# 第3步:追加 assistant 回复(最容易被忘!)
messages.append({"role": "assistant", "content": response.content})
# 第4步:如果模型调用了工具,就执行
results = []
for block in response.content:
if block.type == "tool_use":
output = run_bash(block.input["command"])
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
})
# 第5步:把工具结果写回 messages
messages.append({"role": "user", "content": results})
新手最容易踩的5个坑
❌ 坑1:工具结果打印了,但不写回 messages
# 错误做法
result = run_bash("ls")
print(result) # 打印了,但没写回去!
# 正确做法
messages.append({"role": "user", "content": [tool_result]})
❌ 坑2:只保存用户消息,不保存 assistant 消息
# 错误做法
response = client.messages.create(messages=messages)
# response 往哪放?没放!
# 正确做法
messages.append({"role": "assistant", "content": response.content})
❌ 坑3:不绑定 tool_use_id
# 错误做法
results.append({
"type": "tool_result",
"content": "文件内容..." # 缺了 tool_use_id!
})
# 正确做法
results.append({
"type": "tool_result",
"tool_use_id": block.id, # 必须绑定!
"content": "文件内容..."
})
❌ 坑4:一上来就把流式、并发、恢复、压缩全塞进来
第一关只做一件事——把最小回路跑通。其他的,慢慢加。
❌ 坑5:以为 messages 只是聊天展示
Messages 是模型的输入原料,告诉模型"现在处于什么状态,下一步该做什么"。
🛠️ S02:工具使用 - 把意图路由成动作
核心问题
只有 bash 时,所有操作都走 shell,容易出问题:
cat截断不可预测sed遇到特殊字符就崩- 每次 bash 调用都是不受约束的安全面
解决方案
加工具不需要改循环。只要定义好 handler 和 schema,注册到 dispatch map,主循环自然知道怎么调用。
三张图理解工具系统
1. Tool Schema(给模型看的说明书)
{
"name": "read_file",
"description": "读取文件内容",
"input_schema": {
"properties": {
"path": {"type": "string", "description": "文件路径"}
},
"required": ["path"]
}
}
2. Handler Function(真正的执行者)
def run_read(path: str) -> str:
text = safe_path(path).read_text()
return text
3. Dispatch Map(路由表)
TOOL_HANDLERS = {
"bash": lambda **kw: run_bash(kw["command"]),
"read_file": lambda **kw: run_read(kw["path"]),
"write_file": lambda **kw: run_write(kw["path"], kw["content"]),
}
🍳 外卖点餐的比喻
- 你(模型)想点宫保鸡丁
- App(dispatch map)查表找到"川菜厨师"
- 川菜厨师(handler)真正去做菜
- 结果返回给你
关键是:你不需要告诉 App “去找哪个厨师”,它自己查表匹配。
核心代码
# 路径安全检查
def safe_path(p: str) -> Path:
path = (WORKDIR / p).resolve()
if not path.is_relative_to(WORKDIR):
raise ValueError(f"Path escapes workspace: {p}")
return path
# 工具执行
for block in response.content:
if block.type == "tool_use":
handler = TOOL_HANDLERS.get(block.name)
output = handler(**block.input) if handler else f"Unknown tool"
新手最容易踩的3个坑
- 每个工具都写 if/elif → 用 dispatch map,一行解决
- 不加路径安全检查 → 用户可能读取任意文件
- handler 参数不一致 → schema 和 handler 参数名要保持一致
📝 S03:待办写入 - 会话级计划
核心问题
多步任务容易走一步忘一步,明明做过的检查会重复再做。
解决方案
让 Agent 把"当前计划"显式写出来:
[ ] 凉拌黄瓜 ← 刚做完
[x] 红烧肉 ← 正在做
[ ] 番茄蛋汤 ← 待做
🍳 做饭记事本的比喻
没有记事本: 做完一道,你问助手"接下来做什么?" 助手一脸懵。
有记事本: 你做完一道,就在记事本上打个勾,下一道要做什么,一目了然。
核心数据结构
# 待办条目
PlanItem = {
"content": "Read the failing test",
"status": "pending", # pending / in_progress / completed
"activeForm": "Reading the failing test",
}
# 强制规则:同一时间最多一个 in_progress
def update(self, items: list) -> str:
in_progress_count = sum(1 for i in items if i.status == "in_progress")
if in_progress_count > 1:
raise ValueError("Only one item can be in_progress")
新手最容易踩的5个坑
- 计划写得过长 → 一次最多列 3-5 项
- 同时多个 in_progress → 焦点散掉
- 只在开始写一次,后面从不更新 → 计划变成摆设
- 把会话计划当成长期任务系统 → S03 只是当前会话,不负责跨阶段持久化
- 以为 reminder 是可有可无的装饰 → 主循环不仅要执行动作,还要维护动作过程中的结构化状态
👥 S04:子代理 - 子任务使用全新上下文
核心问题
大任务的所有中间过程都堆在主上下文里,真正的结论反而没地方放。
解决方案
把局部任务放进独立上下文里做,做完只把必要结果带回来。
工作流程
Parent agent
|
| 1. 决定把局部任务外包出去
v
Subagent(全新上下文)
|
| 2. 在自己的上下文里工作
v
Summary(摘要返回)
|
| 3. 只把结果带回父 Agent
v
Parent agent continues
🍳 老板与助理的比喻
没有子代理: 助理读了 50 个文件,全记在脑子里,真正的结论装不下了。
有子代理: 助理派一个审计员去读文件,审计员只把结论带回来。
核心代码
def run_subagent(prompt: str) -> str:
# 关键:从一份新的消息列表开始,不是共享父 Agent 的 messages
sub_messages = [{"role": "user", "content": prompt}]
# 只返回摘要,不返回全量历史
return {"type": "tool_result", "content": summary_text}
新手最容易踩的4个坑
- 把子代理当成"炫技的并发" → 首先是为了解决上下文问题
- 把父历史全部原样灌回去 → 只带摘要,不带全量历史
- 一上来就做复杂的角色系统 → 先把"干净上下文的子任务执行器"做对
- 忘记给子代理设置停止条件 → 可能无限转
fork 是什么
普通子代理: 从空白上下文开始
sub_messages = [{"role": "user", "content": prompt}]
fork: 先复制父 Agent 的上下文,再追加子任务
sub_messages = list(parent_messages) # 继承上下文
sub_messages.append({"role": "user", "content": prompt})
📚 S05:技能系统 - 先轻发现,再深加载
核心问题
不同任务需要不同领域知识,全部塞进 system prompt 会臃肿。
解决方案
平时只展示目录(发现),需要时才加载正文(按需)
两层结构
第1层:轻量目录(system prompt 里)
- skill 名称
- skill 描述
- 让模型知道"有哪些可用"
第2层:按需正文(真正需要时才加载)
- 只有模型真正需要时才加载
- 通过 tool_result 注入当前上下文
🍳 图书馆目录的比喻
- 大厅目录牌写着"医学区在左转、烹饪区在右转…"
- 你想找做菜的书,系统把《烹饪全书》拿出来
- 用完又放回去,不占用大厅空间
四类 Memory 的区别
| 概念 | 定义 | 判断方法 |
|---|---|---|
| skill | 可选知识包 | 某类任务才需要的做法?→ skill |
| memory | 跨会话记住的信息 | 长期事实需要记住?→ memory |
| CLAUDE.md | 稳定规则说明 | 更全局的规则?→ CLAUDE.md |
新手最容易踩的5个坑
- 把所有 skill 正文永远塞进 system prompt → 会臃肿
- skill 目录信息写得太弱 → 没有描述,模型不知道什么时候该加载
- 把 skill 当成"绝对规则" → skill 更像"可选工作手册"
- 把 skill 和 memory 混成一类 → skill 解决"怎么做",memory 解决"记住什么"
- 一上来就讲太多多源加载细节 → 重点是"轻发现 + 按需加载"
📦 S06:上下文压缩 - 保持活跃上下文小而稳
核心问题
上下文越来越长,模型注意力被淹没,最终撞上上限。
三层压缩策略
第1层:大结果不直接塞进上下文
→ 写到磁盘,只留预览
第2层:旧结果不一直原样保留
→ 替换成简短占位
第3层:整体历史太长时
→ 生成一份连续性摘要
🍳 手机内存的比喻
没有压缩: 桌面上堆满上周、上上月、半年前的文件,要找东西得翻遍整个桌子
有压缩: 最近用的文件放桌上,旧文件放抽屉,再旧的放档案柜
压缩后必须保住的4类信息
| 必须保住 | 解释 |
|---|---|
| 当前任务目标 | 模型要知道"我在做什么" |
| 已完成的关键动作 | 模型要知道"做过什么" |
| 关键决定与约束 | 模型要知道"有什么限制" |
| 下一步应该做什么 | 模型要知道"接下来该干嘛" |
新手最容易踩的5个坑
- 以为压缩等于删除 → 是换一种表示,不是删掉
- 只在撞到上限后才临时乱补 → 从一开始就有三层思路
- 摘要只写成一句空话 → 必须保住关键信息
- 把压缩和 memory 混成一类 → 压缩解决"当前会话太长",memory 解决"跨会话保留"
- 一上来就给初学者讲过多产品化层级 → 先讲清最小正确模型
🎯 核心公式总结
核心闭环 = Agent Loop + 工具路由 + 待办写入 + 子代理 + 技能系统 + 上下文压缩
Agent Loop = 发消息 → 看回复 → 执行工具 → 写回结果 → 下一轮
工具路由 = dispatch map = {tool_name: handler}
待办写入 = pending → in_progress → completed
子代理 = 新上下文 + 摘要返回
技能系统 = 轻量目录 + 按需加载
上下文压缩 = 大结果落盘 + 旧结果缩短 + 整体摘要
🎯 一句话带走
记住这句话就够了:Agent 的核心不是"模型很聪明",而是"系统持续把现实结果喂回模型"。工具结果必须写回消息历史,成为下一轮的输入,没有这条回路,AI 就只是"会说话",不会"会干活"。
▶️ 下一步
下一篇文章我们将进入系统加固部分,学习如何给 Agent 加上安全闸门、扩展能力、长期记忆,让它更稳定,更安全。
相关文章:
关注我,一起探索 AI Agent 的世界!
更多推荐




所有评论(0)