学习如何通过 6 个步骤从头构建一个 AI Agent
为什么要自己构建 Agent?
AI 应用的演进
AI 应用经历了三个重叠的发展阶段:
- 聊天机器人 (Chatbots):简单的问答,被动响应(早期实现,随着 2022 年底 ChatGPT 的发布加速普及)
- RAG (检索增强生成):外部知识检索,单轮调用(自 2020 年开始研究,2023 年起广泛采用)
- Agents (智能体):自主规划,使用工具,迭代执行(ReAct 论文于 2022 年发表,2023 年起迅速普及)
Agent 的独特之处在哪里? 它们不仅仅是回答问题------它们可以:
- 将复杂任务拆解为多个步骤
- 自主调用工具来收集信息或采取行动
- 在遇到错误时进行迭代和自我修正
- 端到端地完成整个工作流
为什么不使用框架?
像 LangChain 和 AutoGPT 这样的框架非常方便,但从头构建能够提供:
- 深度理解:透彻了解 Agent 的底层运作机制
- 完全控制:针对生产环境需求定制每一个细节
- 可扩展性:在不与框架抽象层"搏斗"的情况下添加功能
本指南 将通过 6 个循序渐进的步骤,带你构建一个类似 Claude Code 的 Agent,每一步都建立在前一步的基础上。
第一步:对话基础
提交 (Commit) :
be88f03- 带有流式输出的基本对话功能
做什么 & 为什么
在 Agent 能够行动之前,它必须能够记忆。这一步构建:
- 一个用于用户交互的 CLI (命令行) 界面
- 连接到 LLM (使用 OpenAI 协议)
- 流式输出 (Token 随生成实时显示)
- 对话历史管理 (万物之基)
为什么从这里开始? 每一个 Agent 功能都建立在对话管理之上。没有它,Agent 就没有记忆。
注意:我们在这一步构建的是一个聊天机器人,还不是 Agent。Agent 需要采取行动的能力------这将在第二步实现。
核心组件
对话管理器 (Conversation Manager):按顺序存储消息 (system → user → assistant → user → ...)。在每一轮对话中将完整历史提供给 LLM。
LLM 客户端 (LLM Client) :封装支持流式的 OpenAI API。使用 AsyncGenerator 在 Token 到达时逐个产出。
CLI 循环 (CLI Loop) :读取用户输入,调用 LLM,显示响应,重复。while (true) 循环维持着交互会话。
你将得到
一个有记忆的聊天机器人。虽然简单,但它是每一个后续功能构建的骨架。
第二步:工具调用 - 给 Agent 装上"双手"
提交 (Commit) :
dae56bc- 带有 6 个工具的函数调用
做什么 & 为什么
纯语言模型只能"说话"------它们不能行动。函数调用 (Function Calling,又称 Tool Use) 让 LLM 能够:
- 执行 Shell 命令 (
bash) - 读写文件 (
read,write,edit) - 搜索代码 (
glob,grep)
突破点 :Agent 不再只是回答"你应该运行 ls",而是真正运行 ls 并看到输出结果。
这就是你的聊天机器人变身为 Agent 的时刻。
ReAct 模式
每个 Agent 的核心都是一个 ReAct 循环 (Reasoning + Acting,推理 + 行动)。这个模式由 ReAct 论文 引入,它在以下步骤间交替:
- 推理 (Reasoning):LLM 思考下一步做什么
- 行动 (Acting):执行工具以收集信息或做出更改
- 观察 (Observing):查看工具结果并再次推理
伪代码:
bash
function agent_loop(user_message):
conversation.add(user_message)
while true:
# 推理:LLM 决定做什么
response = llm.generate(conversation, available_tools)
# 检查 LLM 是否想要使用工具
if response.has_tool_calls():
for tool_call in response.tool_calls:
# 行动:执行工具
result = execute_tool(tool_call)
# 观察:将结果添加到对话中
conversation.add(tool_result(result))
# 继续循环 - LLM 根据新的观察结果进行推理
else:
# 没有工具调用 = 任务完成
return response.text
关键洞察 :这是一个 while 循环,而不是单次调用。每一次观察都会为下一步的推理提供信息,使 Agent 能够:
- 将复杂任务拆解为步骤
- 根据工具结果进行调整
- 处理错误并尝试不同的方法
工作原理
工具注册表 (Tool Registry):可用工具的目录。每个工具都有:
- 定义 (Definition) (名称、描述、参数):告诉 LLM 这个工具是做什么的
- 执行 (Execute) 函数:执行实际操作
Agent 循环 (Agent Loop):实现上面的 ReAct 伪代码。循环持续进行,直到 LLM 仅回复文本 (无工具调用),标志着任务完成。
你将得到
你的 Agent 现在可以自主执行命令和操作文件。它从一个被动的聊天机器人变成了一个主动的自动化助手。
第三步:MCP - 连接生态系统
提交 (Commit) :
d238b9d- 模型上下文协议 (Model Context Protocol) 支持
做什么 & 为什么
内置工具是有限的。MCP (Model Context Protocol) 是 Anthropic 推出的用于连接外部工具服务器的标准:
- GitHub 操作
- 数据库查询
- 网络搜索
- ...任何社区构建的工具
愿景:你的 Agent 成为一个中心枢纽,可以通过标准协议连接无限的能力。
工作原理
MCP 客户端 :使用 stdio transport (进程管道) 通过标准输入/输出管道连接到外部服务器。这是本地工具服务器最常用的方法,也是我们在这一步中实现的。
客户端从连接的 MCP 服务器获取工具定义,并将它们转换为与内置工具相同的 Tool 接口。
统一工具注册表:LLM 将所有工具 (内置和 MCP) 视为一个扁平列表。它不知道 (也不在乎) 它们来自哪里。
自动加载 :在启动时,读取 mcp-servers.json 并自动连接到所有配置的服务器。
你将得到
无需修改代码即可扩展功能。需要 GitHub 集成?在 mcp-servers.json 中添加一行即可。你的 Agent 瞬间获得新能力。
第四步:TODO 管理 - 让进度可视化
提交 (Commit) :
6e25680- 任务列表追踪
做什么 & 为什么
复杂的任务需要许多步骤,如果只在模型的大脑中,随着上下文变长,很容易搞不清初始的计划。所以需要一个Todo List来不断标记执行到哪一步,以及下一步该执行什么。
TODO 管理 让 Agent 能够:
- 在开始时列出计划 ("我将做 X,然后 Y,最后 Z")
- 实时更新状态 (pending 等待中 → in_progress 进行中 → completed 已完成)
- 展示当前正在处理的内容
关键洞察:这不是给用户的待办事项列表------这是 Agent 自己的工作计划,并将其可视化。
工作原理
TODO 管理器:追踪带有状态的任务列表。检测变化 (新任务、状态变更、完成),并仅渲染增量部分。
TODO 工具 :LLM 调用 todo_write 来更新其计划。系统提示词 (System Prompt) 指示它在多步骤任务中使用此工具。
智能渲染:仅显示变更以避免终端刷屏。已完成的任务会获得绿色复选框。当前任务显示加载微调器 (spinner)。
使用示例
yaml
用户: "重构 auth.ts 以提取验证器"
Agent 调用 todo_write:
📋 Tasks:
⏳ 读取 auth.ts
⏸ 提取验证函数
⏸ 创建 validators.ts
⏸ 更新导入
[Agent 工作中...]
✓ Completed: 读取 auth.ts
⏳ 提取验证函数
...
你将得到
用户可以实时看到 Agent 的计划和进度。不再是黑盒操作------他们确切地知道每一步发生了什么。
第五步:Subagents
提交 (Commit) :
0f25524- SubAgent
做什么 & 为什么
一个 Agent 做所有事情效率很低。子 Agent 实现了专业化:
- 主 Agent:负责规划和协调
- 子 Agent:执行特定任务 (搜索、研究、规划)
类比:CEO 不写代码------他们委派给工程师。
工作原理
子 Agent 配置:每个子 Agent 都有:
- 专门的系统提示词 (例如,"你是一个代码搜索专家")
- 受限的工具访问权限 (例如,explorer 只能使用 glob/grep/read)
- 独立的对话历史
工作者 Agent (Worker Agent):运行子 Agent 任务的微型 Agent。它有自己的工具循环和最大迭代限制 (防止无限循环)。
委派工具 (Delegation Tool) :主 Agent 调用 delegate_task(agent_name="explorer", task="查找所有 API 端点")。子 Agent 执行任务,返回结果,其对话历史随后被丢弃。
内置子 Agent
| 名称 | 用途 | 工具 |
|---|---|---|
| explorer (探索者) | 搜索代码库结构 | glob, grep, read |
| researcher (研究员) | 阅读并理解代码 | read, glob, grep |
| planner (规划者) | 创建实施计划 | glob, grep, read |
为什么要委派?
- 专注:专门的提示词能针对特定任务产生更好的结果
- 隔离:子 Agent 的对话不会污染主 Agent 的上下文窗口
可选优化:你可以为处理简单任务的子 Agent 使用不同 (可能更便宜) 的模型,虽然这是一种设计选择,并非硬性要求。
你将得到
一个模块化系统,每个 Agent 各司其职。主 Agent 充当编排者,将专业任务委派给专家子 Agent。
第六步:技能 (Skills) - 注入领域专家知识
提交 (Commit) :
aa15e81- 按需知识加载
做什么 & 为什么
工具 = Agent 能做什么 (bash, read, write) 技能 = Agent 知道怎么做 (代码审查, PDF 处理, MCP 开发)
问题:如何在其不进行微调 (fine-tuning) 的情况下教给 Agent 专业知识?
解决方案:技能------包含专家指令的 Markdown 文件,按需加载。
技能系统设计
| 方面 | 实现 |
|---|---|
| 存储 | skills/*/SKILL.md 文件,带有 YAML 头信息 |
| 启动 | 仅加载元数据 (~100 tokens/技能) |
| 激活 | LLM 在需要时调用 load_skill("code-review") |
| 注入 | 完整内容 (~2000 tokens) 通过 tool_result 返回 |
为什么按需加载?
将所有技能放入系统提示词存在问题:
- 成本:每个请求都要为 10,000+ tokens 付费
- 噪音:不相关的技能会干扰模型
技能系统解决了这个问题:
- 元数据始终加载:便宜 (每项技能约 100 tokens)
- 完整内容按需加载:只在实际使用时付费
- 系统提示词稳定性:添加/修改技能不会改变系统提示词的前缀
关于缓存的说明 :Anthropic 的提示词缓存 (Prompt Caching) 是基于前缀的。由于技能是通过 tool_result (出现在对话中,而不是系统提示词中) 注入的,系统提示词前缀保持可缓存状态。但是,整体缓存效率取决于你的对话模式。
SKILL.md 格式
markdown
---
name: code-review
description: 包含安全检查的全面代码审查
---
# Code Review Skill
你现在是一位代码审查专家。请遵循此清单:
## Security
- [ ] SQL 注入漏洞
- [ ] XSS 漏洞
...
## Performance
- [ ] N+1 查询问题
...
[详细说明, 示例, 输出格式...]
渐进式披露
- 启动 :扫描
skills/目录,加载所有元数据 - 系统提示词:列出可用技能 ("Available skills: code-review, pdf-processing...")
- 使用时 :LLM 调用
load_skill工具 - 注入:完整的 SKILL.md 内容通过工具结果注入
- 应用:LLM 将加载的专业知识应用于当前任务
你将得到
通过编写 Markdown 文件赋予 Agent 领域知识。想要代码审查技能?写一个 SKILL.md。想要 PDF 处理?再写一个 SKILL.md。无需模型再训练,无需代码部署------只有知识文件。
融会贯通
6 步回顾
| 步骤 | 能力 | 关键技术 | 提交 (Commit) |
|---|---|---|---|
| 1. 对话基础 | 记忆 | 历史管理 + 流式输出 | be88f03 |
| 2. 工具调用 | 行动 + ReAct | 函数调用 + 工具循环 | dae56bc |
| 3. MCP | 生态系统 | 协议适配器 + 统一注册表 | d238b9d |
| 4. TODO | 透明度 | 状态追踪 + 增量渲染 | 6e25680 |
| 5. 子 Agent | 专业化 | 委派 + 隔离上下文 | 0f25524 |
| 6. 技能 | 专业知识 | 元数据索引 + 按需加载 | aa15e81 |
各部分如何组装
每一步都向核心 ReAct 循环添加了一项能力:
css
[步骤 1] 对话历史维持上下文
↓
[步骤 2] 工具注册表 + ReAct 循环 = Agent 核心
↓
[步骤 3] MCP 扩展可用工具
↓
[步骤 4-6] 增强决策能力:
├─ 更新 TODO (进度可见性)
├─ 委派给子 Agent (专业化)
└─ 加载技能 (领域知识)
↓
所有这些都反馈到 ReAct 循环中:
推理 → 行动 → 观察 → (重复直到完成)
每个功能都是模块化增量。你可以一步步构建,每一个添加都在不破坏现有功能的情况下增强 Agent 的能力。
代码仓库
完整代码:mini-agent
提交历史:
be88f03: 第一步 - 基本对话dae56bc: 第二步 - 工具调用 + ReAct 模式d238b9d: 第三步 - MCP 支持6e25680: 第四步 - TODO 管理0f25524: 第五步 - 子 Agentaa15e81: 第六步 - 技能系统
每个提交对应一步。使用 git checkout <commit> 来查看演进过程。