Build Your Own Agent: A Practical Guide
目标:通过 6 个循序渐进的步骤,学习如何从零开始构建一个 AI Agent。
为什么要自己构建 Agent?
你可能已经体验过 Claude Code、Qwen Code 或 GitHub Copilot 等 AI 编程助手,被它们自动生成代码、解释复杂逻辑的能力所震撼。但你是否想过,这些智能体(Agent)为何能像一位经验丰富的开发者一样思考与行动?仅仅会使用它们是不够的,理解其背后的工作原理才能真正释放其潜力。本文通过从零开始构建一个属于你自己的 Agent,让你更深入理解Agent如何进行规划执行,理解MCP,Skills,Subagent这些概念,更高效、更精准地使用这些工具。
为什么不直接使用框架?
像 LangChain 这样的框架虽然方便,但从头构建能带来以下优势:
- 深度理解:深入理解Agent如何进行规划执行,理解MCP,Skills,Subagent这些概念如何工作。
- 高扩展性:能针对你的场景自由定制,无需受限于框架的抽象封装。
本文将通过 6 个渐进式的步骤,带你构建一个类似 Claude Code 的 AI Agent。
步骤 1:基础对话
Commit:
be88f03- 带有流式输出的基础对话
目的
每一个 Agent 功能都建立在对话管理之上。没有它Agent 就没有记忆。这一步我们构建:
- 用于用户交互的 CLI (命令行) 界面
- 与 LLM 的连接 (使用 OpenAI 协议)
- 流式输出 (Token 生成即显示)
- 对话历史管理 (这是所有功能的基础)
注意:这一步我们构建的本质上是一个聊天机器人还不是 Agent。Agent 需要具备采取行动的能力——这将在步骤 2 中实现。
核心组件
会话管理器 (Conversation Manager):按顺序存储消息 (System → User → Assistant → User ...)。在每一轮对话中将完整的历史记录提供给 LLM。
LLM 客户端:封装 OpenAI API 并支持流式传输。使用 AsyncGenerator 在 Token 到达时逐个产出。
CLI 循环:读取用户输入 -> 调用 LLM -> 显示响应 -> 重复。while (true) 循环维持了交互式会话。
步骤 2:工具调用——赋予 Agent “双手”
Commit:
dae56bc- 包含 6 个工具的函数调用
目的
纯语言模型只能“说话”,不能“做事”。函数调用 (Function Calling,又称工具使用) 让 LLM 能够:
- 执行 Shell 命令 (
bash) - 读写文件 (
read,write,edit) - 搜索代码 (
glob,grep)
突破点:Agent 不再只是回答“你应该运行 ls”,而是真正去运行 ls 并看到输出结果。
这是聊天机器人变为 Agent 的时刻。
核心逻辑
每个 Agent 的核心都是一个 ReAct 循环 (Reasoning + Acting,推理+执行)。该模式由 ReAct 论文 提出,包含以下交替过程:
- 推理 (Reasoning):LLM 思考下一步该做什么。
- 执行 (Acting) :执行工具以收集信息或做出更改。
- 观察 (Observing) :查看工具结果并再次推理。
伪代码逻辑:
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 能够:
- 将复杂任务拆解为多个步骤
- 根据工具结果调整策略
- 处理错误并尝试不同的方法
当模型不再需要使用工具时,表示已经获取到足够的信息可以得出结论,本轮循环结束。
步骤 3:MCP——连接生态系统
Commit:
d238b9d- 支持模型上下文协议 (Model Context Protocol)
目的
内置工具的能力是有限的。MCP (Model Context Protocol) 是 Anthropic 推出的标准,用于连接外部工具服务器:
- GitHub 操作
- 数据库查询
- 网络搜索
- ...以及任何社区构建的工具
你的 Agent将变成一个枢纽,可以通过标准协议连接不同的MCP Server,实现能力扩展。
核心组件
MCP 客户端:使用 stdio transport(进程管道)链接本地MCP Server,如果要读取远程的MCP Server,可以通过http stream或者sse协议。
自动加载:启动时,读取 mcp-servers.json 并自动连接所有配置的服务器。客户端从连接的 MCP 服务器获取工具定义,并将其转换为与步骤 2 中相同的 Tool 定义格式。
统一工具注册表:LLM 看到的是一个合并后的扁平化工具列表,包含内置工具和从MCP Server获取的工具定义。它不知道(也不在乎)工具来自哪里。
步骤 4:TODO 管理——对抗上下文遗忘
Commit:
6e25680- 任务追踪
目的
当面对多步骤提示时,比如“重构 auth 模块,添加测试,并更新文档”,标准的 Agent 经常遭受 “上下文遗忘 (Context Fade)” 的困扰。在这种情况下计划只存在于模型的短期“脑海”中。经过几次工具调用后,最初的目标被掩埋,导致 Agent 随机跳转任务或完全忘记某些步骤。
TODO 管理 通过将不可见的思维过程转化为可见的、外置的状态来解决这个问题:
- 对抗上下文遗忘:通过将计划从模型的权重中提取出来并放入持久化列表中,为 Agent 提供了“外部存储器”。下一步做什么只需检查列表。
- 显式轨迹:Agent 被要求在开始时列出计划(“我将先做 X,然后 Y,再做 Z”),并实时更新状态(
pending→in_progress→completed)。 - 聚焦执行:看到“可视化计划”迫使模型保持线性的逻辑流,防止它在一系列漫长的操作中迷失方向。
这不仅仅是给用户看的 UI 功能,它确保即使对话变长,Agent 依然锚定在最初的使命上。
核心组件
TODO 工具:TODO本质上是第二步中的一个内置工具,系统提示词 (System Prompt) 指示它在处理多步骤任务时使用此工具,LLM 调用 todo_write 来更新其计划。
TODO 管理器:跟踪带有状态的任务列表。检测变化(新任务、状态更改、完成)并仅渲染增量部分。
示例
User: "重构 auth.ts 以提取验证器"
Agent 调用 todo_write:
任务列表:
⏳ 读取 auth.ts
⏸ 提取验证函数
⏸ 创建 validators.ts
⏸ 更新导入引用
[Agent 工作中...]
✓ 已完成: 读取 auth.ts
⏳ 提取验证函数
...
步骤 5:SubAgent——上下文隔离
Commit:
0f25524- 专用子智能体
目的
- 单个 Agent 试图处理复杂上下文的的任务时容易遭遇“上下文污染 (Context Pollution)”。当一个 Agent 读取数十个文件后,其上下文窗口会被原始数据填满,留给后续推理或实际执行任务的空间所剩无几。
- 解决方案:通过衍生子智能体实现上下文隔离。主 Agent 保持高层推理规划,而SubAgent进行探索和处理“嘈杂”的工作。
核心组件
委派工具:实现上同样添加内置工具delegate_task,主 Agent根据提示词调用delegate_task工具,比如 delegate_task(agent_name="explorer", task="Find all API endpoints")。随后触发SubAgent执行并返回结果,但SubAgent对话context不会返回。
Worker Agent:运行子智能体任务的模块,将子智能体视为主 Agent 循环的独立实例。每个子智能体都有自己的:
- 专用系统提示词:例如,“你是一名代码研究专家。你的唯一工作是寻找相关的代码模式。”
- 独立历史记录:子智能体内部的试错过程(搜索、失败、重试)停留在其自己的上下文中,永远不会被主 Agent 看到。
- 有限的工具权限:子智能体可能只有
read_file和list_dir权限,防止它意外修改代码库。
已内置SubAgent
| 名称 | 用途 | 工具权限 |
|---|---|---|
| explorer (探索者) | 搜索代码库结构 | glob, grep, read |
| researcher (研究员) | 阅读并理解代码 | read, glob, grep |
| planner (规划者) | 创建实施计划 | glob, grep, read |
步骤 6:技能 (Skills)——按需加载知识技能
Commit:
aa15e81- 按需加载知识技能
目的
Agent并不知道特定领域的知识技能,你可以将这些知识技能通过系统提示词的方式让模型知道,但如果将所有知识技能都塞进系统提示词会有问题:
- 成本:每次请求都要为大量的Token付费。
- 噪音:不相关的技能会分散模型的注意力。
Skills解决了这个问题:
- 元数据常驻:仅加载知识技能的简短描述作为元数据,大幅降低token数量。
- 内容按需加载:仅在实际使用时加载Skill的详细内容。
- 系统提示词稳定性:添加/修改技能不会改变基础系统提示词。
与工具的区别
- 工具 (Tools) = Agent 能做什么 (bash, read, write)
- 技能 (Skills) = Agent 知道怎么做 (代码审查规范, PDF 处理流程, MCP 开发指南)
按需加载流程
- 启动:扫描
skills/目录,加载所有元数据。 - 系统提示词:列出可用技能("Available skills: code-review, pdf-processing...")。
- 使用时:LLM 决定需要该技能,调用
load_skill工具。 - 注入:完整的
SKILL.md内容作为工具结果注入上下文。 - 应用:LLM 将加载的专业知识应用于当前任务。
SKILL.md 格式
---
name: code-review
description: Comprehensive code review with security checks
---
# Code Review Skill
You are now an expert code reviewer. Follow this checklist:
## Security
- [ ] SQL injection vulnerabilities
- [ ] XSS vulnerabilities
...
## Performance
- [ ] N+1 query problems
...
[Detailed instructions, examples, output format...]
回顾
6 步回顾
| 步骤 | 能力 | 关键技术 | Commit |
|---|---|---|---|
| 1. 对话基础 | 记忆 | 历史管理 + 流式传输 | be88f03 |
| 2. 工具调用 | 行动 + ReAct | 函数调用 + 工具循环 | dae56bc |
| 3. MCP | 生态系统 | 协议适配器 + 统一注册表 | d238b9d |
| 4. TODO | 规划+透明度 | 状态追踪 | 6e25680 |
| 5. 子智能体 | 专业化 | 任务委派 + 上下文隔离 | 0f25524 |
| 6. 技能 | 领域专长 | 元数据索引 + 按需加载 | aa15e81 |
这些模块如何协作
[步骤 1] 对话历史维持上下文
↓
[步骤 2] 工具注册表 + ReAct 循环 = Agent 核心
↓
[步骤 3] MCP 扩展可用工具库
↓
[步骤 4-6] 添加内置工具,增强决策能力:
├─ 更新 TODO (进度可视化)
├─ 委派给子智能体 (分工)
└─ 加载技能 (注入领域知识)
↓
所有这些都反馈进 ReAct 循环:
推理 (Reasoning) → 行动 (Acting) → 观察 (Observing) → (重复直到完成)
每个特性都是模块化的增量,每一步都在不破坏现有功能的情况下增强了 Agent 的能力。
完整代码
代码仓库:mini-agent
be88f03: 步骤 1 - 基础对话功能dae56bc: 步骤 2 - 工具调用 + ReAct 模式d238b9d: 步骤 3 - 支持 MCP (模型上下文协议)6e25680: 步骤 4 - TODO 任务管理0f25524: 步骤 5 - 子智能体 (Subagents)aa15e81: 步骤 6 - 技能系统 (Skill System)
每一个 Commit 对应一个步骤。你可以使用 git checkout <commit> 来查看代码的演变过程。