前言:一次全新的AI智能体教程尝试
大模型正在重塑软件开发,AI Agent 作为智能应用的核心形态,已经从实验探索快步进入生产落地阶段。
然而,市面上的教程大多停留在框架调用和 API 使用层面,很少解释一个 Agent 为什么这样设计、各个模块如何协同工作,以及如何将一个简单的 LLM 对话程序,一步步演进为真正具备感知、记忆、规划、行动能力的智能体。
本书面向有 Python 基础的开发者,采用「渐进式开发」的方式,以 Tiny Agent(极简智能体)项目为主线,带你从原理层面看透 Agent 的内部构造。不绑定任何特定框架,让你掌握可迁移的架构思想,而非特定工具的配置技巧。
Tiny Agent 是一个从零生长的教学型智能体,麻雀虽小,五脏俱全。我们从一次最简单的 LLM 调用开始,依次完成提示词工程、检索增强生成、工具调用、智能体循环等核心能力的构建。项目代码完全开源,书籍每一章对应一个可运行的代码版本,每一次迭代都能看到真实的代码演进、架构变化和设计思考。
希望读完这本书后,你不仅能够使用 Agent,更能够理解和设计 Agent,并亲手构建出属于自己的、能解决真实问题的 Agent。
本书采用 CC BY-NC-ND 4.0 许可协议,转载请注明出处。
导读:如何从零构建一个 AI 智能体?
你是否曾被‘Agent框架’的黑盒搞得晕头转向?是否在看了一堆理论后,依然不知道一个ReAct循环到底如何落地?我们相信,征服复杂概念的最好方法,不是仰望它,而是亲手拆解、重构它。欢迎来到Tiny Agent的世界,这里没有魔法,只有源码。
本教程的核心理念只有一句话:最好的学习方式是亲手构建。整个演进过程被精心拆解为迭代的版本(Git Tag),每个版本都聚焦于一个核心能力的从无到有,可以随时切换到任意阶段独立运行和研读源码。
为了让这套“渐进式”开发和学习真正高效,每个章节都被设计为统一的七段式结构。它既不是干瘪的源码注释,也不是纯理论的白皮书,而是一条从问题到方案的完整思考路径。
每个章节的结构
当你打开任何一个版本对应的章节时,都会看到以下七个部分,它们承担着不同的角色:
-
概念引入 本章不会一上来就丢出代码或术语,而是从一个真实场景中的棘手问题出发:为什么单轮问答不够用?为什么大模型需要借助搜索引擎?为什么 Agent 会陷入死循环?通过还原这些痛点,你会清楚地知道这一章要解决什么,以及我们为什么非得引入一套新机制不可。
-
整体方案 在动手之前,先用一张架构图把整章的设计思路“鸟瞰”一遍。这里会梳理系统由哪些模块组成,数据如何流转,新增的组件如何与已有部分协作。读完这一节,你应该能够明白“请求从哪进来,经过哪些步骤,最后从哪出去”。
-
核心概念 这是最重要的原理层。我们会把本章的关键知识点拆解成若干个小概念逐一讲解,比如“什么是 SSE 协议”、“Message Protocol 中的角色分工”、“ReAct 循环的 Thought → Action → Observation 三态是怎样推导的”。这一节的目标是让你不仅会做,而且真正懂原理。
-
工程实现 开始接触项目代码,但绝不是把几百行源码直接贴出来让你硬读。我们会围绕本章新增或修改的关键文件,说明每个模块的职责、核心类的关系以及最重要的运行时流程。你将被引导去关注“数据在哪里被转换”“控制权在哪里被交接”这类结构性问题,而非在细节里迷失。
-
Git Diff 导读 因为整个项目被切分到了不同的 Git Tag 中,所以这一节会像一份精炼的代码对比报告:相比上一版本,我们新增了哪些文件、修改了哪些模块、重构了哪些部分,每一项变化分别是为了解决上一版本的什么具体问题。你甚至可以把这一节当作检查清单,用来核对自己的理解是否到位。
-
架构思考 到此我们并不满足于“跑通了”。这一节会追问三个问题:
- 为什么这样设计?(方案的合理性)
- 有没有其他实现方式?(替代方案及其利弊权衡)
- 当前的局限是什么?(工程落地的优化方向)
-
本章小结 简短回顾本章交付的能力,然后自然而然地引出下一个版本将要面对的挑战。你会发现,每个版本的终点,恰好就是下一版本的起点。
整个演进路线一览
全书共分五个部分,恰好对应了 Tiny Agent 渐进式演进路线的关键版本。每一部分都承担着明确的使命:
-
第一部分:模型基础与全栈通信(基础篇)
从终端里的第一次 LLM 调用开始,搭建流式 Web 前后端通信,实现多轮连续对话,并通过 Prompt Template、System Prompt 与结构化输出让模型“按规矩办事”。这一部分将为你打下不可或缺的全栈基础。
里程碑:构建一个 AI 聊天助手。 -
第二部分:外部知识与规划行动(连接篇)
引入 RAG 打通文档切片、向量检索与引用回复的完整链路,用 Function Calling 为 Agent 装上调用外部工具的双手,最后手写 ReAct 核心循环,让 Agent 从被动调用工具走向自主思考、感知与行动。完成这一部分后,你的 Agent 将不再困于训练数据之内。
里程碑:构建一个能够检索知识并自主调用工具的智能体。 -
第三部分:能力拓展与流程编排(拓展篇)
通过 MCP 协议统一工具接入标准,以 Skills 实现能力的模块化封装与动态加载,再用 Workflow 将单次调用编排为可复用的多步骤任务流。由此,Agent 的能力边界变得开放、可组合且可持续扩展。
里程碑:构建一个支持标准工具协议、技能可插拔的智能体工作流平台。 -
第四部分:工程强化与生产就绪(强化篇)
为 Agent 加入上下文管理与 Token 熔断,构建长期记忆漏斗实现跨会话知识留存;引入混合检索、重排序与向量数据库提升检索精度;通过评估指标与全链路追踪让效果可验证;并建立工具权限分级与确认机制,构筑安全边界,使 Agent 走向可靠、可观测、可审计。
里程碑:构建一个记忆持久、检索精准、行为可审计的高可靠 Agent 系统。 -
第五部分:多模交互与群智协同(前沿篇)
突破纯文本界面,赋予 Agent 语音与视觉能力,最终探索多智能体协作模式,让多个不同角色的 Agent 共同完成复杂任务。这是迈向生产级智能体的最后一站。 里程碑:构建一个能听会说、看懂世界并协同工作的多模态多智能体系统。
无论是想深入理解 Agent 底层原理的工程师,还是正做技术选型的架构师,这份教程都会是一张清晰、可动手验证的地图。
技术栈与前置技能
在开始之前,让我们先对齐一下“装备库”。为了保持轻量与纯粹,我们尽量避免了笨重的框架,选择了一套最符合现代 AI 开发直觉的轻量级技术栈。
我们的技术栈
- 核心语言:Python 3.12+(主打简洁与生态,零门槛上手)
- Web 框架:FastAPI(用于构建高性能的 Agent 后端 API)
- 前端交互:原生 HTML + CSS + JavaScript (用于构建极简 Web 页面)
- 版本控制:Git(我们唯一的“时光机”,用于切换版本代码)
你需要具备的基础
- Python 基本功:熟悉基础语法、异步编程及基本的数据结构。
- Git 基础操作:知道如何 git clone 和 git checkout(我们会带你完成其余操作)。
- 无需大模型开发经验:你不必提前了解其他 Agent概念,我们将从最底层的 API 调用开始,带你手写属于自己的控制流。
现在,让我们从第一部分开始——你只需要一个 LLM 的 API Key 和一个终端。
第一章:Hello LLM(你好,大模型)
导语:本章是 Tiny Agent 的起点,不用前端、不做多轮历史,只先跑通“用户输入一句话,大模型回复一句话”的最小闭环。
源码版本:v0.1
1. 让 Agent 第一次开口
在构建 Agent 之前,我们先让程序具备最基础的能力:把一段文本发送给大模型,并把模型返回的文本打印出来。
一个完整 Agent 以后会有工具、记忆、知识库、规划循环,但它们最终都会回到一个核心动作:向 LLM 发起请求,并读取返回结果。所以第一章刻意只保留 CLI 终端交互,让注意力集中在后端请求链路本身。
本章完成后的运行效果如下:
$ python -m app.main
==================================================
Tiny Agent - Hello LLM
==================================================
输入一条消息发送给大模型,输入 exit 退出。
你: 你好,请用一句话介绍你自己
AI: 你好,我是一个可以理解和生成文本的 AI 助手。
你: exit
再见!
2. 整体方案
本章采用“CLI 输入 → Python 函数 → OpenAI 兼容 SDK → LLM 服务 → CLI 输出”的最小方案,先跑通单轮文本输入与输出。
整体链路可以拆成四步:
- 从
.env读取模型服务配置。 - 使用
OpenAISDK 创建兼容 OpenAI 协议的客户端。 - 把用户输入包装成一条
user消息,调用client.chat.completions.create()。 - 从响应中取出
response.choices[0].message.content并打印到终端。
本版本没有 Web UI、没有多轮对话,它只解决一个问题:确认代码能够真正访问 LLM,并拿到一次回复。
flowchart LR
User[CLI 终端<br/>用户输入/输出] -->|输入| App[Python 应用<br/>simple_call.py]
App -->|读取配置| Env[.env 环境变量]
App -->|API 调用| SDK[OpenAI SDK<br/>兼容客户端]
SDK -->|HTTP 请求| LLM[LLM 服务<br/>大模型]
LLM -->|响应数据| SDK
SDK -->|回复文本| App
App -->|打印| User
3. 核心概念
本章只保留实现 Hello LLM 必需的概念。
3.1 OpenAI 兼容 SDK
OpenAI Python SDK 可以调用 OpenAI 官方接口,也可以调用兼容 OpenAI 协议的第三方大模型服务。
本章通过两个参数创建一个请求客户端:
client = OpenAI(api_key=api_key, base_url=base_url)
api_key:模型服务的访问密钥。base_url:模型服务的接口地址。
3.2 环境变量
环境变量用于把密钥、服务地址、模型名称从代码里移出去,避免把敏感信息写死在源码中。
本章使用三个配置项:
LLM_API_KEY:访问模型服务需要的 API Key。LLM_BASE_URL:模型服务的 OpenAI 兼容接口地址。LLM_MODEL:本次调用使用的模型名称。
3.3 大模型消息
LLM 的 Chat 接口不是直接传入一段字符串,而是传入一个消息列表。设计成消息列表,是因为后续可以放入多条历史消息和系统提示词——这让多轮对话成为可能。
本章只需要一条用户消息:
messages=[{"role": "user", "content": message}]
role="user"表示这条消息来自用户。content是用户输入的文本内容。
后续章节会继续加入 assistant 历史消息和 system 提示词。本章先保持最小结构。
3.4 单轮调用
单轮调用表示每次请求只包含当前这一条用户输入,模型不会自动记住上一次对话。
核心调用如下:
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": message}],
timeout=30.0,
)
返回结果中,真正要打印的回复文本位于:
response.choices[0].message.content
4. 工程实现
本章新增一个最小 Python 后端目录,让 backend/app/main.py 作为入口,实际逻辑放在 backend/app/simple_call.py。
4.1 目录结构
backend/
├── .env.example
├── requirements.txt
└── app/
├── __init__.py
├── main.py
└── simple_call.py
4.2 新增依赖
backend/requirements.txt:
openai>=1.0.0
python-dotenv>=1.0.0
4.3 环境变量管理
项目基于.env 文件管理环境变量参数,通过 python-dotenv 读取 .env 文件:
from dotenv import load_dotenv
load_dotenv()
首次需要复制环境变量模板:
cp .env.example .env
然后根据自己的模型服务填写 .env,以DeepSeek为例:
LLM_API_KEY=your_api_key_here
LLM_BASE_URL=https://api.deepseek.com
LLM_MODEL=deepseek-v4-flash
安全提醒:.env 文件包含 API Key 等敏感信息,切记将其加入 .gitignore,避免意外提交到版本控制系统。
💡 小贴士:如何快速获取 API Key?
本项目兼容标准的 OpenAI 协议,国内外的诸多主流平台都可以无缝接入。推荐注册并使用国内极速且高性价比的平台,例如 DeepSeek 或 硅基流动 等 API 聚合服务。注册获取
sk-...格式的密钥后,直接填入 .env 即可无缝运行。
4.4 运行入口
backend/app/main.py 只负责暴露运行入口:
"""Tiny Agent 运行入口"""
from app.simple_call import main
if __name__ == "__main__":
main()
这样用户可以在 backend 目录下运行:
.venv/bin/python -m app.main
注意:项目的运行工作目录是 backend,如果从其他目录运行会失败。
4.5 LLM 调用逻辑
backend/app/simple_call.py 承担本章的核心逻辑。
首先,启动时读取并检查环境变量:
from openai import OpenAI
def create_client() -> tuple[OpenAI, str]:
"""从环境变量创建兼容 OpenAI 协议的 LLM 客户端,并返回模型名称。"""
load_dotenv()
# 提前检查环境变量是否配置完整
api_key = os.getenv("LLM_API_KEY")
base_url = os.getenv("LLM_BASE_URL")
model = os.getenv("LLM_MODEL")
if not api_key or api_key == "your_api_key_here":
print("错误:未配置 LLM_API_KEY。", file=sys.stderr)
print("请复制 .env.example 为 .env,并填写你的 API Key。", file=sys.stderr)
raise SystemExit(1)
if not base_url:
print("错误:未配置 LLM_BASE_URL。", file=sys.stderr)
print("请复制 .env.example 为 .env,并填写你的模型服务地址。", file=sys.stderr)
raise SystemExit(1)
if not model:
print("错误:未配置 LLM_MODEL。", file=sys.stderr)
print("请复制 .env.example 为 .env,并填写你的模型名称。", file=sys.stderr)
raise SystemExit(1)
# 构造 LLM 请求客户端
return OpenAI(api_key=api_key, base_url=base_url), model
然后,封装一次单轮调用:
def chat_once(client: OpenAI, model: str, message: str) -> str:
"""向大模型发送一条用户消息,并返回模型回复文本。"""
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": message}],
timeout=30.0,
)
content = response.choices[0].message.content
return content or ""
最后,main() 负责终端交互:
def main() -> None:
"""运行最小 CLI,实现单轮文本输入与输出。"""
# 打印启动提示
print("=" * 50)
print("Tiny Agent - Hello LLM")
print("=" * 50)
print("输入一条消息发送给大模型,输入 exit 退出。")
print()
sys.stdout.flush()
# 启动时先创建 LLM 请求客户端
client, model = create_client()
while True:
try:
# 读取用户输入,并去掉首尾空白字符。
user_input = input("你: ").strip()
except (EOFError, KeyboardInterrupt):
print("\n再见!")
return
# 空输入不发送给模型,直接等待下一次输入。
if not user_input:
continue
# 支持exit退出命令
if user_input.lower() == "exit":
print("再见!")
return
try:
# 发起一次 LLM 调用,并将回复直接打印到终端。
print("AI: ", end="", flush=True)
print(chat_once(client, model, user_input))
print()
except Exception as exc:
print(f"\n调用失败:{exc}\n", file=sys.stderr)
4.6 运行验证
进入 backend 目录:
.venv/bin/python -m app.main
如果没有配置 API Key,会看到:
错误:未配置 LLM_API_KEY。
请复制 .env.example 为 .env,并填写你的 API Key。
配置正确后,输入任意文本即可得到模型回复;输入 exit 退出。
5. Git Diff 导读
本版本相对于空工程,核心变化是新增 backend 目录,上文已经详细讲解,完整代码已提交至 GitHub 仓库,你可以切换到 v0.0.1 Tag查看或直接运行 git checkout v0.0.1查看。
6. 架构思考
本章代码很小,但它已经刻意做了几个取舍。
6.1 为什么先做 CLI,而不是 Web UI?
CLI 能把干扰降到最低:没有页面、没有接口路由、没有前端状态管理,只有一次真实的 LLM 网络请求。
这有助于建立一个重要直觉:LLM 调用本质上是一次请求-响应过程。后续无论接入 Web UI、工具调用还是 Agent Loop,底层都离不开这个动作。
6.2 为什么是 OpenAI SDK,而不是 LiteLLM 等聚合库?
在起步阶段,我们面临三种选择:大模型厂商原生 SDK、LiteLLM 等聚合库,以及 LangChain 等重型框架。我们最终选择最基础的 OpenAI SDK,基于以下考量:
- 事实上的行业标准:如今几乎所有主流大模型服务商(如 DeepSeek、Qwen 等)都原生兼容 OpenAI 协议。只需开放
base_url就能接入 90% 的模型,不需要引入任何第三方抽象层。 - 拒绝“中间商”:
LiteLLM虽好,但它是一层“胶水”。它会屏蔽原始请求和响应的真实结构(如choices[0].message),让读者失去对大模型原生协议的直觉。一旦报错,你也很难分清是底层接口还是适配层的 bug。
6.3 为什么只做单轮,不做聊天历史?
单轮调用更容易观察输入和输出的关系。
现在每次请求只包含当前用户输入:
messages=[{"role": "user", "content": message}]
所以模型不会自动记住上一轮内容。下一章引入 Chat History 后,我们会把历史消息一起传给模型,让它具备连续对话的上下文。
6.4 为什么环境变量使用通用命名?
本章使用 LLM_API_KEY、LLM_BASE_URL、LLM_MODEL,而不是把变量名绑定到某个厂商。
这样做是为了保留替换空间:如果模型服务兼容 OpenAI 协议,通常只需要改 .env,不需要改调用代码。
6.5 为什么第一步就要引入 venv 虚拟环境?
有些初学者教程为了图省事,会让读者直接 pip install 到全局环境。我们从第一章的第一行代码起就强制要求使用 venv,原因很简单:
- 防止环境污染:读者的电脑里可能跑着其他 Python 项目。直接在全局安装依赖极易引发库版本冲突,导致“新项目跑通了,老项目却挂了”的惨剧。通过虚拟环境隔离,并配合
requirements.txt锁定版本,能彻底杜绝“在作者电脑上能跑,在读者电脑上报错”的诡异 bug。 - 零门槛与零依赖:我们没有选择
Conda或uv等更复杂的工具,是因为venv是 Python 3 标准库自带的官方工具。读者不需要额外安装任何软件,用最纯粹的方式解决最核心的隔离问题。
6.6 为什么通过 python-dotenv 管理环境变量?
在管理 API Key 等敏感信息时,传统的方法是在终端来导入系统环境变量。我们选择引入 python-dotenv 则是基于以下考量:
- 开发体验的确定性:依赖操作系统的
export具有很强的“临时性”。读者一旦关闭终端窗口、重启电脑,或者在 IDE内置的终端中运行,环境变量就会失效。.env文件让配置紧贴项目目录,即开即用。 - 避免跨平台命令混乱:不同操作系统的环境变量设置命令完全不同(Linux/macOS 用
export,Windows CMD 用set,PowerShell 用$env:),而.env文件是全平台通用的。
6.7 为什么错误处理非常简单?
v0.0.1 的目标是跑通闭环,不是建立完整的错误分类体系。
因此代码只做两类处理:
- 启动时检查环境变量,缺配置就直接提示并退出。
- 调用时统一捕获异常,打印“调用失败”。
更细的网络错误、鉴权错误、限流错误、重试策略,会在后续版本更接近真实应用时再展开。
7. 本章小结
本章完成了 Tiny Agent 的第一个可运行版本:通过 CLI 读取用户输入,调用大模型,并把回复打印回终端。
你现在已经拥有了后续所有 Agent 能力的起点:
- 使用
.env管理LLM_API_KEY、LLM_BASE_URL、LLM_MODEL。 - 使用
OpenAISDK 创建兼容 OpenAI 协议的客户端。 - 使用
messages=[{"role": "user", "content": message}]发起单轮请求。 - 使用
response.choices[0].message.content读取模型回复。 - 使用
exit退出最小 CLI。
下一章,我们会在这个基础上引入 Chat History,让 Tiny Agent 从“一问一答”走向“连续对话”。
第二章:Streaming Web(流式 Web 输出)
渐进式演进路线图 (Full Roadmap)
Tiny Agent 坚信“最好的学习方式是亲手构建”。为了避免一上来就面对复杂的完整系统,本项目将 Agent 的核心能力拆解到不同的 Git Tag 中。
注:你可以通过
git checkout命令切换到任意版本进行独立运行和源码研读。如git checkout v0.3将切换到 RAG 版本。
第一阶段:模型基础与全栈通信
[v0.1] Hello LLM(你好,大模型)
- 定位:项目起步,跑通最基础的闭环。
- 核心能力:学习如何配置和初始化大语言模型(LLM)SDK,实现单轮文本输入与输出。
- UI演进:无前端 UI,纯 CLI 终端交互,方便开发者聚焦后端网络请求本身。
[v0.2] Streaming Web(流式 Web 输出)
- 定位:从 CLI 迈向实时响应的浏览器交互。
- 核心能力:搭建最小后端服务与前端页面,通过 SSE 协议实现 LLM 流式输出的实时渲染。
- UI演进:引入极简 Web页面 (输入框 + 发送按钮 + 结果展示区),动态解析并逐字打印流式文本。
[v0.3] Multi-turn Chat(多轮对话)
- 定位:从“一问一答”走向“连续对话”。
- 核心能力:理解 Message Protocol 的设计(System / User / Assistant),设计 Chat History 数据结构,实现多轮连续对话。
- UI演进:增加消息列表,可保留对话历史,支持连续追问。
[v0.4] Structured Output(结构化输出)
- 定位:从自然语言输出,演进到可被程序可靠消费的结构化输出。
- 核心能力:引入Prompt Template、Structured Outputs、JSON Schema 等现代 LLM 交互方式,以及Temperature参数。
- UI演进:对话框支持JSON等特殊格式渲染。
第二阶段:外部知识与规划行动
[v0.5] Basic RAG(基础检索增强生成)
- 定位:为模型提供可检索的外部知识库,降低无依据生成的概率。
- 核心能力:对接 Embedding 模型,构建内存简易向量库,实现文档读取、文本切分、向量化与本地检索,打通「切片 → 检索 → Prompt 拼装 → 生成」的最简闭环链路。
- UI演进:对话框中支持显示文档引用来源。
[v0.6] Tool Calling(工具调用)
- 定位:赋予 Agent 改变世界、连接外部系统的双手。
- 核心能力:深入 Function Calling 原理,学习工具声明、参数生成、本地函数执行以及工具结果回填,理解 LLM 如何调用外部能力;加入最小安全机制。
- UI演进:在对话流中展示 Tool 的调用信息(参数 → 调用状态 → 返回结果)。
[v0.7] Agent Loop(智能体循环)
- 定位:从被动调用工具到自主思考。
- 核心能力:手写 ReAct 范式核心循环(Thought → Action → Observation),让 Agent 能够自主感知复杂任务、调用工具,并加入最大步数限制与 Token 防御机制。
- UI演进:工具调用模式演变为智能体模式;对话流展示 Agent 的执行步骤,展示每一轮的思考决策、工具调用决策以及返回的观察结果。