Posted on ::

目标:通过 6 个循序渐进的步骤,从零实现一个可用的 AI Agent,并真正理解它背后的工作机制。

完整代码


为什么要自己构建 Agent?

这两年,大家已经很熟悉各种 AI 编程助手了。

Claude Code、Qwen Code、GitHub Copilot……它们能读代码、写代码、解释复杂逻辑,甚至还能自己调用工具,一步一步把任务做完。第一次用的时候,很多人都会有同一种感觉:

它已经不像一个"聊天机器人",更像一个真的会工作的开发者。

不能只停留在会用的阶段,还要清楚它如何能做到这些。本文将通过从零构建一个属于你自己的 Agent,帮助你深入理解以下核心概念:

  • Agent如何管理上下文

  • 为什么能调用工具并据此继续推理

  • MCP 是如何把外部能力接进来的

  • Skills 为什么能降低提示词成本

  • Subagent 又是如何解决上下文污染问题的

这篇文章不讲复杂框架,也不讲大而全的系统设计,而是从零开始,搭一个最小但完整的 Agent。

本文将通过 6 个渐进步骤,带你构建一个类似 Claude Code 的 AI Agent。当你走完这 6 步后,你不仅会更会用 Agent,也会真正理解 Agent。


步骤 1:记住对话

Commit: be88f03 - 实现带有流式输出的基础对话

作用

如果没有对话历史,模型每一轮都只能看到当前输入。那它既记不住你刚才说过什么,也不知道自己上一轮回答了什么,更不可能形成连续任务执行能力。

在这一步中,我们会完成:

  • 用 CLI(命令行)接收用户输入

  • 接入 LLM(通过 OpenAI 协议)

  • 支持流式输出,让生成结果边产出边显示

  • 管理完整对话历史

注意:这一步构建的只是一个带记忆能力的聊天机器人,而不是 Agent。但这一层非常关键,因为后面所有能力,无论是工具调用、任务分解,还是子智能体协作都要建立在"历史上下文可持续传递"这个基础上。

核心组件

1. 会话管理器 它负责把消息按顺序存下来,例如:

System → User → Assistant → User → Assistant ...

一轮请求模型时,把这整段历史一起发给模型,这样模型看到的就不再是一次性的输入,而是一段连续对话。

2. LLM 客户端 封装 OpenAI API,并支持流式返回。 通过 AsyncGenerator,可以在 Token 到达时立即输出,从而获得更自然的交互体验。

3. CLI 循环 整体逻辑非常简单:

读取用户输入 → 调用模型 → 输出结果 → 继续等待下一轮输入

这个持续运行的 while (true)循环维持了整个交互式会话


步骤 2:工具调用

Commit: dae56bc - 包含 6 个工具的函数调用

作用

语言模型本身只能生成文本,它可以告诉你"应该执行 ls",但它并不能真的去执行 ls

而一旦支持函数调用,模型不再只是"建议你怎么做",而是可以直接触发系统能力,当前代码实现已经内置了常用的工具函数,例如:

  • 执行 Shell 命令(bash
  • 读写文件(read / write / edit
  • 搜索代码(glob / grep

模型第一次拥有了感知和操控外部世界的能力,此时聊天机器人变为 Agent。

核心逻辑:ReAct 循环

几乎所有 Agent 的底层都可以抽象为一个简单循环:

推理 → 行动 → 观察 → 再推理

也就是经典的 ReAct 模式(Reasoning + Acting),基本过程如下:

  • 推理 (Reasoning):模型判断下一步该做什么
  • 执行 (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

这个循环看起来很简单,但它已经足够支撑一个基础 Agent 的运行机制。它的威力来自一个闭环:

推理 → 行动 → 观察 → 再推理

一旦工具结果持续回流给模型,模型就不再是凭空猜答案,而是在不断根据真实反馈修正自己的行动。

这也是为什么 Agent 看起来比普通对话更"像在解决问题",因为它真的在一步一步试、一步一步看、一步一步往前推进。


步骤 3:MCP——接入更多外部服务

Commit: d238b9d - 支持模型上下文协议 (Model Context Protocol)

作用

当你给 Agent 几个内置工具后,它已经能做不少事,但很快你就会碰到一个边界:

内置工具再多,也始终是你自己预先写死的那一小部分能力。

现实中的任务往往需要更多外部系统支持,比如:

  • 操作 GitHub
  • 查询数据库
  • 做网络搜索
  • 收发邮件
  • 接入社区已有工具

核心组件

  1. MCP 客户端:本地 MCP Server 通常通过 stdio 连接,远程 MCP Server 则可以通过 HTTP Stream 或 SSE 访问。

  2. 自动加载机制:启动时读取 mcp-servers.json,自动连接所有已配置的 MCP Server,并拉取它们暴露的工具定义。

  3. 统一工具注册表:把 MCP 返回的工具格式转换成和内置工具一致的结构,统一注册到同一个工具列表里。

对于模型来说,它最终看到的是一组扁平化工具列表,模型不需要知道工具来自哪里,只需要知道:

  • 工具名称
  • 参数结构
  • 能完成什么事

步骤 4:TODO 管理——对抗遗忘

Commit: 6e25680 - 任务追踪

作用

当你给 Agent 一个多步骤任务时,比如:

"重构 auth 模块,补充测试,并更新文档。"

理论上它应该按顺序推进。但实际上,很多 Agent 做着做着就会出现这些问题:

  • 忘记原始目标
  • 只完成一部分任务
  • 中途被局部信息带偏
  • 在多个子任务之间来回跳转

这种现象称为上下文遗忘(Context Fade)。模型想出一个计划,但这个计划如果只存在于它当下的上下文里,就很容易在几轮工具调用之后被新的信息淹没掉。

TODO管理功能

TODO列表**把模型脑海中的隐式计划,变成外部持久、可更新的显式状态。**不再依赖模型去记住计划,而是让模型随时能看到计划,它的主要作用为:

  1. 固定任务锚点:复杂任务不再只存在于短期上下文中,而是被写成明确列表。

  2. 形成执行轨迹:任务状态可以从 pending 更新为 in_progress,再更新为 completed,让执行过程具备连续性。

  3. 约束执行:当任务以列表方式可视化后,模型更容易保持按步骤推进,而不是被局部问题带偏。

所以 TODO 管理不只是一个"用户体验功能",更是一种真正提升 Agent 执行稳定性的机制。

核心组件

TODO工具:它本质上仍然是一个工具。你可以提供一个像 todo_write 这样的内置工具,并在系统提示里添加如下描述:

IMPORTANT: Use the todo_write tool to track your tasks when working on multi-step tasks.
- Create a todo list at the start of complex tasks
- Update task status as you work (pending -> in_progress -> completed)
- This helps the user see your progress

Agent触发TODO示例

User: "重构 auth.ts 以提取验证器"

Agent 调用 todo_write:
任务列表:
  ⏳ 读取 auth.ts
  ⏸ 提取验证函数
  ⏸ 创建 validators.ts
  ⏸ 更新导入引用

[Agent 工作中...]
  ✓ 已完成: 读取 auth.ts
  ⏳ 提取验证函数
...

步骤 5:SubAgent

Commit: 0f25524 - 专用子智能体

功能

从系统设计角度看,SubAgent 是一种面向复杂任务的分治架构。主 Agent 负责面向用户理解目标、拆解任务和编排流程,而 SubAgent 负责完成某个明确范围内的子任务,例如信息检索、数据分析、代码执行或内容生成。

SubAgent的作用不只是分担工作,更重要的是把复杂问题拆成多个边界清晰的执行单元。这样既能降低单个 Agent 的上下文负担,提高结果稳定性,也便于系统进行并行处理、模块优化和错误定位。所以,在复杂工作流中SubAgent是实现可扩展性与工程化落地的关键机制。

实现

实现上同样添加内置工具delegate_task,主Agent根据系统提示词调用delegate_task工具,比如:

delegate_task(agent_name="explorer", task="Find all API endpoints")

当主 Agent 调用这个工具时,你就启动一个独立的子智能体去执行任务。它有自己的系统提示词、自己的历史记录、自己的工具权限。

例如已经内置的SubAgent:

  • explorer 负责搜索代码库结构
  • researcher 负责读代码、理解逻辑
  • planner 负责产出实施计划

SubAgent 的核心价值并不只是分工,而是让主 Agent 的上下文始终保持干净和聚焦。


步骤 6:技能 (Skills)——知识可以按需加载

Commit: aa15e81 - 按需加载知识技能

作用

Agent 并不天然具备所有领域知识,你当然可以把各种规范、流程、最佳实践全部塞进系统提示词里,但这种做法很快会遇到三个问题:

  • 成本高:每轮请求都要重复携带大量 Token

  • 噪音大:不相关知识会分散模型注意力

  • 系统提示词脆弱:每次增删内容,都可能影响整体行为

Skills 的作用就是把这些"知识性提示"从主系统提示词中拆出来,变成一种按需注入的外部知识单元

Tools 和 Skills 的区别

这个区分非常重要:

  • Tools能做什么

    • 例如 bashreadwrite
  • Skills怎么做

    • 例如代码审查规范、PDF 处理流程、MCP 开发指南

Skills 的工作方式

Skills 采用"元数据常驻、内容按需加载"的方式:

  1. 启动时扫描 skills/ 目录
  2. 只加载每个 Skill 的简短元数据
  3. 在系统提示词中仅列出可用 Skill 名称
  4. 当模型判断需要某个 Skill 时,调用 load_skill
  5. 将完整 SKILL.md 内容作为工具结果注入上下文
  6. 模型基于这份知识完成当前任务

这样做的好处很明显:

  • 平时几乎不占上下文
  • 真正需要时再加载
  • 技能系统可以独立扩展,不必频繁改动基础系统提示词

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步回顾

到这里你已经完成了一个简单的Agent

步骤能力关键技术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) → (重复直到完成)

这 6 步并不是彼此割裂的功能堆叠。它们共同组成了一个不断增强的 Agent 架构:

  • 对话历史提供基础记忆
  • 工具调用提供行动能力
  • MCP 提供生态扩展
  • TODO 提供任务锚点
  • SubAgent 提供上下文隔离
  • Skills 提供按需知识注入

每一个模块都是在不破坏前一层能力的基础上,持续增强 Agent 的完成复杂任务能力。


完整代码

代码仓库:mini-agent

  • be88f03: 步骤 1 - 基础对话功能
  • dae56bc: 步骤 2 - 工具调用 + ReAct 模式
  • d238b9d: 步骤 3 - 支持 MCP (模型上下文协议)
  • 6e25680: 步骤 4 - TODO 任务管理
  • 0f25524: 步骤 5 - 子智能体 (Subagents)
  • aa15e81: 步骤 6 - 技能系统 (Skill System)

你可以使用 git checkout <commit> 来查看代码的演变过程。

留言

欢迎补充观点、指出疏漏,或者继续展开文中的问题。

Table of Contents