Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Book Cover

前言:一次全新的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),每个版本都聚焦于一个核心能力的从无到有,可以随时切换到任意阶段独立运行和研读源码。

为了让这套“渐进式”开发和学习真正高效,每个章节都被设计为统一的七段式结构。它既不是干瘪的源码注释,也不是纯理论的白皮书,而是一条从问题到方案的完整思考路径。

每个章节的结构

当你打开任何一个版本对应的章节时,都会看到以下七个部分,它们承担着不同的角色:

  1. 概念引入 本章不会一上来就丢出代码或术语,而是从一个真实场景中的棘手问题出发:为什么单轮问答不够用?为什么大模型需要借助搜索引擎?为什么 Agent 会陷入死循环?通过还原这些痛点,你会清楚地知道这一章要解决什么,以及我们为什么非得引入一套新机制不可。

  2. 整体方案 在动手之前,先用一张架构图把整章的设计思路“鸟瞰”一遍。这里会梳理系统由哪些模块组成,数据如何流转,新增的组件如何与已有部分协作。读完这一节,你应该能够明白“请求从哪进来,经过哪些步骤,最后从哪出去”。

  3. 核心概念 这是最重要的原理层。我们会把本章的关键知识点拆解成若干个小概念逐一讲解,比如“什么是 SSE 协议”、“Message Protocol 中的角色分工”、“ReAct 循环的 Thought → Action → Observation 三态是怎样推导的”。这一节的目标是让你不仅会做,而且真正懂原理。

  4. 工程实现 开始接触项目代码,但绝不是把几百行源码直接贴出来让你硬读。我们会围绕本章新增或修改的关键文件,说明每个模块的职责、核心类的关系以及最重要的运行时流程。你将被引导去关注“数据在哪里被转换”“控制权在哪里被交接”这类结构性问题,而非在细节里迷失。

  5. Git Diff 导读 因为整个项目被切分到了不同的 Git Tag 中,所以这一节会像一份精炼的代码对比报告:相比上一版本,我们新增了哪些文件、修改了哪些模块、重构了哪些部分,每一项变化分别是为了解决上一版本的什么具体问题。你甚至可以把这一节当作检查清单,用来核对自己的理解是否到位。

  6. 架构思考 到此我们并不满足于“跑通了”。这一节会追问三个问题:

    • 为什么这样设计?(方案的合理性)
    • 有没有其他实现方式?(替代方案及其利弊权衡)
    • 当前的局限是什么?(工程落地的优化方向)
  7. 本章小结 简短回顾本章交付的能力,然后自然而然地引出下一个版本将要面对的挑战。你会发现,每个版本的终点,恰好就是下一版本的起点。

整个演进路线一览

全书共分五个部分,恰好对应了 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(你好,大模型)

第一章: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 输出”的最小方案,先跑通单轮文本输入与输出。

整体链路可以拆成四步:

  1. .env 读取模型服务配置。
  2. 使用 OpenAI SDK 创建兼容 OpenAI 协议的客户端。
  3. 把用户输入包装成一条 user 消息,调用 client.chat.completions.create()
  4. 从响应中取出 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_KEYLLM_BASE_URLLLM_MODEL,而不是把变量名绑定到某个厂商。

这样做是为了保留替换空间:如果模型服务兼容 OpenAI 协议,通常只需要改 .env,不需要改调用代码。

6.5 为什么第一步就要引入 venv 虚拟环境?

有些初学者教程为了图省事,会让读者直接 pip install 到全局环境。我们从第一章的第一行代码起就强制要求使用 venv,原因很简单:

  • 防止环境污染:读者的电脑里可能跑着其他 Python 项目。直接在全局安装依赖极易引发库版本冲突,导致“新项目跑通了,老项目却挂了”的惨剧。通过虚拟环境隔离,并配合 requirements.txt 锁定版本,能彻底杜绝“在作者电脑上能跑,在读者电脑上报错”的诡异 bug。
  • 零门槛与零依赖:我们没有选择 Condauv 等更复杂的工具,是因为 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_KEYLLM_BASE_URLLLM_MODEL
  • 使用 OpenAI SDK 创建兼容 OpenAI 协议的客户端。
  • 使用 messages=[{"role": "user", "content": message}] 发起单轮请求。
  • 使用 response.choices[0].message.content 读取模型回复。
  • 使用 exit 退出最小 CLI。

下一章,我们会在这个基础上引入 Chat History,让 Tiny Agent 从“一问一答”走向“连续对话”。

→ 进入第二章:Chat(基础对话)

第二章: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 的执行步骤,展示每一轮的思考决策、工具调用决策以及返回的观察结果。