从「会聊天」到「会做事」:一文读懂 AI Agent、MCP、Harness、Skill 等核心技术

从大模型到智能体,一条主线讲透 Agent 技术栈

Posted by Ryan on 2026-06-17
Estimated Reading Time 117 Minutes
Words 28.9k In Total
Viewed Times

本文主线:一个原始的大语言模型(LLM)本质上只是一个"文本接龙"引擎——你给它一段话,它预测下一段话。它不能上网、不能读文件、不能记住昨天的事、更不能自己动手完成任务。

整篇文章只回答一个问题:我们如何一层一层地给这个"只会说话的大脑"装上知识、记忆、手脚、工具、协作能力和技能,最终把它变成一个能独立干活的智能体(Agent)?

Prompt、RAG、Tool、Harness、Agent、MCP、Skill、开发框架……这些技术全都是这条进化路上的一块块"拼图"。我们会沿着这条进化线,由浅入深地把它们一一拼起来。


目录

第一部分 地基:大模型本身

  • 第一章 起点:一个"只会说话的大脑"
  • 第二章 当今大模型全景

第二部分 第一次进化:让大脑"会用知识、会说话"

  • 第三章 Prompt Engineering:学会"好好说话"
  • 第四章 RAG:给大脑接上"外部图书馆"

第三部分 第二次进化:让大脑"长出手脚"

  • 第五章 Tool / Function Calling:工具调用
  • 第六章 Harness:让模型"循环"起来

第四部分 第三次进化:成为真正的"智能体"

  • 第七章 Agent:从"工具"到"自主"
  • 第八章 MCP:工具世界的"USB 接口"
  • 第九章 Skill:把"经验"打包复用

第五部分 把拼图拼起来:框架与实战

  • 第十章 主流开发框架
  • 第十一章 全景图:所有拼图如何协同

第六部分 展望

  • 第十二章 结语:技术演进的本质与趋势

第一部分 地基:大模型本身

在给大脑装手脚之前,我们得先搞清楚这个"大脑"本身是什么、它怎么运作、它强在哪、又弱在哪。这是理解后面一切技术的基础。


第一章 起点:一个"只会说话的大脑"

1.1 LLM 是什么——一个超级"文本接龙"引擎

想象一个玩过千万亿次"文字接龙"游戏的高手。你说"今天天气真",它几乎能脱口而出"好";你说"床前明月",它会接"光"。

大语言模型(Large Language Model, LLM)本质上就是这样一个文本接龙引擎。它的唯一核心能力是:

给定前面的一段文字,预测下一个最可能出现的"字"(准确说是 Token)是什么。

就这么简单。你之所以觉得它"很聪明、会回答问题、会写代码",是因为它在海量文本(几乎整个互联网)上练习接龙练到了出神入化的地步——为了能准确接出下一个字,它被迫"理解"了语法、事实、逻辑、推理、甚至代码规则。

一个生动的比喻:LLM 像一个读遍了天下所有书、但被关在一间没有窗户也没有电话的房间里的博学者。他知识渊博、能言善辩,但他看不到外面的世界、碰不到任何东西、也记不住你上次来访说过什么。这间"封闭房间"的局限,正是后面所有技术要逐一打破的。

1.2 核心原理:Transformer 与"注意力机制"

今天几乎所有主流大模型(GPT、Claude、Gemini、Llama……)都建立在 2017 年 Google 提出的 Transformer 架构之上。我们不深究数学,只讲清楚两个关键直觉。

(1)一切皆 Token

模型并不直接处理"字"或"词",而是先把文本切成一个个 Token(词元)。Token 可以是一个词、半个词、一个标点。例如:

1
2
"我爱编程"        →  ["我", "爱", "编程"]          (3token
"ChatGPT is great" → ["Chat", "G", "PT", " is", " great"] (5token

模型把每个 Token 变成一串数字(向量),然后在数字世界里做运算。

(2)注意力机制(Attention)——理解的核心

这是 Transformer 最关键的发明。所谓"注意力",就是让模型在理解一个词时,自动判断句子里其他哪些词跟它最相关、该"重点看"哪些词

看这个经典例子:

1
2
句子 A:小狗追着球跑,因为「它」很兴奋。   →「它」指小狗
句子 B:小狗追着球跑,因为「它」滚得很远。 →「它」指球

同样一个"它",模型必须根据上下文判断它指代谁。注意力机制就是让模型在处理"它"这个词时,把更多"注意力权重"分配到"小狗"或"球"上。正是这种机制,让模型能真正"读懂"长句子里错综复杂的关系。

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph LR
    A["输入文本
小狗追着球跑,因为它很兴奋"] --> B["切分成 Token"] B --> C["每个 Token 转成向量"] C --> D["注意力机制
计算词与词之间的关联权重"] D --> E["多层 Transformer 叠加
逐层提炼语义"] E --> F["预测下一个 Token
的概率分布"] F --> G["输出:好 / 兴奋 / ..."] classDef inputN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 classDef procN fill:#e8f5e9,stroke:#388e3c,stroke-width:2px,color:#1b5e20 classDef coreN fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#4a148c classDef outN fill:#fff3e0,stroke:#f57c00,stroke-width:2px,color:#e65100 class A inputN class B,C,E procN class D coreN class F,G outN

官方架构图参考:Transformer 的经典结构图来自原始论文 Attention Is All You Need,可在论文页查看:
https://arxiv.org/abs/1706.03762

1.3 预训练 + 微调:模型是怎么"练成"的

一个可用的大模型,通常经历两大阶段:

阶段 做什么 通俗比喻
预训练(Pre-training) 在海量互联网文本上做"文本接龙"训练,学会语言、知识、推理 一个人读完了图书馆所有的书,博览群书
微调(Fine-tuning)/ 对齐 用人类标注的高质量问答、人类反馈(RLHF)来调教,让它"听话、有用、无害" 这个博学者经过专门的"待人接物"培训,学会好好回答问题

预训练决定了模型"懂多少",微调决定了它"好不好用、安不安全"。

1.4 几个必懂的"行话"

初学者常被这些术语劝退,这里一次讲清:

术语 含义 打个比方
Token(词元) 模型处理文本的最小单位 文本的"原子"。中文约 1 字 ≈ 1-2 token,英文约 1 词 ≈ 1-3 token
参数量(Parameters) 模型内部可调节的"旋钮"数量,如 7B(70亿)、70B(700亿) 大脑里神经元连接的数量,越多通常越聪明,但也越贵越慢
上下文窗口(Context Window) 模型一次能"看到"的最大 Token 数,如 8K、128K、200K、1M 大脑的"工作记忆容量"。超出窗口的内容它就"看不见"了
温度(Temperature) 控制输出的随机性,0 最稳定保守,越高越天马行空 创作时的"放飞程度"。写代码调低,写诗调高

⚠️ 特别注意"上下文窗口":这是后面理解 RAG、记忆系统、Harness 上下文管理的关键。模型不是"无限记忆"的,它每次能看的内容有上限,超出就得想办法取舍——这个限制催生了一大批技术。

1.5 它的三大"天生缺陷"

回到那个"封闭房间里的博学者"比喻。纯粹的 LLM 有三个无法回避的硬伤,这三个缺陷正是后续所有技术演进的出发点

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    LLM["纯 LLM
封闭房间里的博学者"] LLM --> P1["缺陷一:不能行动
只能输出文字,
不能上网 / 读文件 / 执行代码"] LLM --> P2["缺陷二:没有记忆
每次对话都是失忆重来,
记不住历史"] LLM --> P3["缺陷三:知识会过时
训练完成那天起知识就冻结了,
还会'一本正经地胡说'(幻觉)"] P1 -.解决方案.-> S1["Tool / MCP / Agent
(装手脚)"] P2 -.解决方案.-> S2["记忆系统 / 上下文管理
(装记忆)"] P3 -.解决方案.-> S3["RAG / 联网检索
(接图书馆)"] classDef brainN fill:#ede7f6,stroke:#5e35b1,stroke-width:3px,color:#311b92 classDef flawN fill:#ffebee,stroke:#e53935,stroke-width:2px,color:#b71c1c classDef fixN fill:#e0f2f1,stroke:#00897b,stroke-width:2px,color:#004d40 class LLM brainN class P1,P2,P3 flawN class S1,S2,S3 fixN
缺陷 具体表现 后文对应的解决方案
① 不能行动 你问"帮我把这个文件夹整理一下",它只能告诉你"步骤是……",但一个文件都动不了 第五~八章:Tool、Harness、Agent、MCP
② 没有记忆 关掉对话再打开,它完全不记得你是谁、聊过什么 第七章:记忆系统
③ 知识过时 + 幻觉 它不知道今天的新闻、不知道你公司的内部资料,还可能编造看似合理实则错误的答案 第三、四章:Prompt、RAG

1.6 本章小结 & 进化进度

🧬 我们的大脑现在进化到哪一步了?

第 0 阶段:一个封闭房间里的博学者。 知识渊博、能说会道,但看不见世界、没有记忆、知识冻结。

接下来,我们要做的第一件事,是教它"好好说话"和"会查资料"——这是成本最低、见效最快的两次进化。


第二章 当今大模型全景

在继续进化之前,我们先环顾四周:今天市面上都有哪些"大脑"可选?它们各有什么特点?这一章帮你建立对整个大模型生态的全局认知。

2.1 主流模型家族

截至 2025-2026 年,大模型领域已形成几大阵营:

模型家族 开发方 特点 开源/闭源
Claude Anthropic 长上下文、强推理、强代码能力、注重安全对齐 闭源
GPT OpenAI 综合能力强、生态成熟、多模态领先 闭源
Gemini Google 原生多模态、超长上下文(百万级 Token)、与谷歌生态深度整合 闭源
Llama Meta 性能强劲的开源标杆,可本地部署、自由微调 开源
DeepSeek 深度求索(中国) 推理能力突出、训练成本极低、开源 开源
Qwen(通义千问) 阿里巴巴(中国) 模型尺寸齐全、中文能力强、生态活跃 开源

此外还有 Mistral(法国,开源)、文心一言(百度)、豆包(字节)、Kimi(月之暗面,长文本见长)、GLM(智谱)等众多玩家。整个领域更新极快,几乎每个月都有新模型刷新榜单。

2.2 闭源 vs 开源之争

这是当下生态最核心的一条分界线:

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TB
    subgraph 闭源模型["闭源模型(Claude / GPT / Gemini)"]
        C1["通过 API 调用,按 Token 付费"]
        C2["能力通常最强、最前沿"]
        C3["数据要发到对方服务器
无法私有化部署"] C4["无法修改模型本身"] end subgraph 开源模型["开源模型(Llama / DeepSeek / Qwen)"] O1["可免费下载,本地 / 私有云部署"] O2["数据不出门,隐私安全可控"] O3["可自由微调成专属模型"] O4["需要自备 GPU 算力,有运维成本"] end classDef closeN fill:#e8eaf6,stroke:#3949ab,stroke-width:2px,color:#1a237e classDef openN fill:#e0f7fa,stroke:#00acc1,stroke-width:2px,color:#006064 class C1,C2,C3,C4 closeN class O1,O2,O3,O4 openN
维度 闭源(API 调用) 开源(自行部署)
能力上限 通常最强 紧追,部分场景已接近
数据隐私 数据需发送给厂商 数据完全自己掌控 ✅
成本模型 按用量付费,无需买卡 需自备 GPU,前期投入大
可定制性 几乎不能改 可自由微调 ✅
适用场景 快速开发、追求最强效果 数据敏感、需深度定制、高频大量调用

2.3 关键能力维度:怎么评判一个模型好不好

选模型时,主要看这几个维度:

维度 说明 为什么重要
推理能力 解决复杂逻辑、数学、代码问题的能力 决定能否胜任 Agent 这类需要"思考规划"的任务
多模态 能否处理图片、音频、视频,而不只是文字 决定应用场景的广度(如看图、读 PDF、分析视频)
上下文长度 一次能处理多长的内容(8K ~ 1M Token) 决定能否一次性读完长文档、长代码库
速度与价格 响应快慢、每百万 Token 多少钱 决定大规模商用是否划算
对齐与安全 是否听话、是否会产生有害内容 决定能否放心交给用户使用

2.4 新趋势:会"思考"的推理模型

2024 年底以来,业界出现一个重要新物种——推理模型(Reasoning Models),代表如 OpenAI 的 o 系列、DeepSeek-R1、Claude 的扩展思考模式等。

普通模型像"脱口而出",推理模型则像"先打草稿、深思熟虑再作答":

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph LR
    subgraph 普通模型["普通模型(脱口而出)"]
        Q1[问题] --> A1[直接输出答案]
    end
    subgraph 推理模型["推理模型(深思熟虑)"]
        Q2[问题] --> T2["内部'思考链'
一步步推演、自我检查、
甚至推翻重来"] --> A2[输出更可靠的答案] end classDef normalN fill:#fce4ec,stroke:#d81b60,stroke-width:2px,color:#880e4f classDef reasonN fill:#e8f5e9,stroke:#43a047,stroke-width:2px,color:#1b5e20 classDef thinkN fill:#fff8e1,stroke:#fb8c00,stroke-width:2px,color:#e65100 class Q1,A1 normalN class Q2,A2 reasonN class T2 thinkN

它的核心思想是:让模型在给出最终答案前,先生成一长串内部"思考过程"(Chain-of-Thought),用更多的计算量换取更高的准确率。在数学、编程、复杂规划这类硬核任务上,推理模型的表现远超普通模型——这也为后面 Agent 的"自主规划"能力打下了基础。

2.5 本章小结 & 进化进度

🧬 进化进度更新

我们现在知道了:市面上有一大批能力各异的"大脑"可供选择,闭源的强在能力、开源的强在可控,新兴的推理模型则强在"深度思考"。

但无论选哪个"大脑",它们都逃不开第一章说的三大缺陷。从下一章开始,我们正式动手改造——第一次进化:教它好好说话、教它会查资料。


第二部分 第一次进化:让大脑"会用知识、会说话"

我们已经有了一个"封闭房间里的博学者"。在给他装"手脚"(工具)这种大工程之前,有两件成本最低、见效最快的事可以先做:

  1. 教他好好说话——同样的大脑,你问得好不好,回答的质量天差地别。这就是 Prompt Engineering(提示词工程)
  2. 给他递资料——他知识会过时、不知道你的私有数据,那我们就在提问时把相关资料一起递进去。这就是 RAG(检索增强生成)

这两项技术的共同点是:它们都不改动模型本身,只改动"喂给模型的输入"。这是最轻量的进化,却往往能带来 80% 的效果提升。


第三章 Prompt Engineering:学会"好好说话"

3.1 核心思想:模型的能力是"问"出来的

有一个反直觉但极其重要的事实:

同一个模型,回答质量的差异,常常不取决于模型有多强,而取决于你的提问(Prompt)有多好。

这是因为 LLM 是个"文本接龙"引擎——它接出来的内容,完全由你给的"开头"决定。你给的开头信息越充分、要求越清晰、引导越到位,它就越能接出你想要的高质量内容。

打个比方:一个博士生导师(模型)能力很强,但如果你只丢给他一句"帮我搞一下那个东西",他也只能一脸茫然;可如果你说清楚"我要研究 X 问题,目标是 Y,约束是 Z,请按论文格式给我三个方案并对比优劣",他立刻就能输出高水平的成果。Prompt Engineering 就是学会如何"把话说到位"。

3.2 一个 Prompt 的标准结构

一个高质量的 Prompt,通常包含以下几个要素(不一定全有,按需取用):

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    P["一个高质量 Prompt"]
    P --> R["① 角色设定
你是一位资深财务分析师..."] P --> T["② 任务描述
请分析这份财报并指出三个风险点"] P --> C["③ 上下文 / 背景
这是一家 SaaS 公司,处于早期阶段..."] P --> E["④ 示例 Few-shot
给 1-3 个'输入→输出'的范例"] P --> F["⑤ 输出格式
请用 Markdown 表格,包含'风险/影响/建议'三列"] P --> Con["⑥ 约束条件
不超过 500 字,不要使用专业术语"] classDef rootN fill:#ede7f6,stroke:#5e35b1,stroke-width:3px,color:#311b92 classDef partN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 class P rootN class R,T,C,E,F,Con partN

3.3 三个最有用的核心技巧

技巧一:角色设定(Role Prompting)

给模型一个明确的身份,它会自动调用与该身份相关的知识和语气。

1
2
3
❌ 普通问法:什么是期权?
✅ 角色设定:你是一位有 15 年经验的期权交易员,请用通俗的语言、
结合一个买股票的类比,向一个完全不懂金融的新手解释什么是期权。

后者会得到一个有比喻、有深度、面向小白的回答,而非教科书式的干巴巴定义。

技巧二:Few-shot(少样本示例)

与其费力描述你想要什么,不如直接给几个例子。模型极擅长"照葫芦画瓢"。

1
2
3
4
5
6
7
8
9
10
11
12
13
任务:把产品名称改写成营销标语。

示例 1:
输入:保温杯
输出:24 小时锁温,让每一口都恰到好处。

示例 2:
输入:降噪耳机
输出:一键静音世界,只留下你想听的。

现在请处理:
输入:机械键盘
输出:?

模型会精准模仿示例的风格、长度、句式,输出"指尖的每一次敲击,都是清脆的回响。"这种效果,靠纯文字描述要求是很难达到的。

术语澄清

  • Zero-shot(零样本):不给任何示例,直接提问。
  • Few-shot(少样本):给几个示例再提问。
  • 示例越多,模型越能领会你的"言外之意",但也越占用上下文窗口。

技巧三:思维链(Chain-of-Thought, CoT)

对于需要推理的复杂问题,加一句魔法咒语——“请一步一步思考”(Let’s think step by step)——往往能显著提升正确率。

1
2
3
4
5
6
7
8
9
问题:一个水池,进水管 5 小时注满,出水管 8 小时放空。
两管同时开,多久注满?

❌ 直接问:模型可能脱口而出一个错误数字。

✅ 加 CoT:请一步一步思考并列出计算过程。
→ 模型会先算进水速率 1/5、出水速率 1/8
再算净速率 1/51/8 = 3/40
最后得出 40/313.3 小时。正确率大幅提升。

为什么有效? 因为模型是"文本接龙"——当它先把推理步骤"说"出来,这些中间步骤就成了后续推理的"上下文垫脚石",让它能基于正确的中间结果继续往下推,而不是一步登天地猜答案。

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph LR
    subgraph 不用CoT["不用思维链"]
        Q1["复杂问题"] --> A1["❌ 直接猜答案
容易出错"] end subgraph 用CoT["使用思维链"] Q2["复杂问题"] --> S1["第 1 步推理"] --> S2["第 2 步推理"] --> S3["第 3 步推理"] --> A2["✅ 基于推理得出答案"] end classDef badN fill:#ffebee,stroke:#e53935,stroke-width:2px,color:#b71c1c classDef stepN fill:#e8f5e9,stroke:#43a047,stroke-width:2px,color:#1b5e20 classDef qN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 class Q1,Q2 qN class A1 badN class S1,S2,S3,A2 stepN

3.4 进阶技巧速览

随着实践深入,还有一系列更高级的技巧,这里列出供你建立认知地图:

技巧 一句话说明 适用场景
思维链 CoT 让模型一步步推理 数学、逻辑、复杂分析
自洽性 Self-Consistency 让模型多次回答,取多数一致的答案 高准确率要求的推理题
思维树 ToT(Tree of Thoughts) 让模型同时探索多条推理路径,再择优 需要试错、规划的难题
ReAct 推理(Reason)+ 行动(Act)交替进行 Agent 的基础(第六章详解)
结构化输出 要求模型输出 JSON / 表格等固定格式 程序需要解析模型输出时
分隔符隔离 用 ``` 或 XML 标签把指令和数据分开 防止数据内容被误当成指令(防注入)

3.5 完整示例:把一个模糊指令优化成高质量 Prompt

场景:你想让 AI 帮你给一封客户投诉邮件写回复。

第 0 版(模糊):

1
帮我回复这封投诉邮件。

结果:模型不知道你的身份、语气要求、能给什么补偿,只能写出一封空洞通用的模板。

优化版(应用上述技巧):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 角色
你是「云图科技」的资深客户成功经理,专业、共情、解决问题导向。

# 任务
针对下面这封客户投诉邮件,写一封回复。

# 背景
- 客户因为我们系统宕机 2 小时导致他的业务受损,非常愤怒。
- 我们的政策:可以补偿最多 1 个月的免费服务,但不能承认法律责任。

# 要求
1. 先真诚共情和道歉,再解释、给方案。
2. 语气专业但有温度,不要官腔。
3. 必须提出"1 个月免费服务"作为补偿。
4. 不要出现任何承认法律责任的措辞。
5. 字数 200-300 字,中文。

# 客户邮件原文
"""
(此处粘贴客户邮件……)
"""

结果:模型会输出一封身份契合、分寸拿捏到位、补偿明确、规避了法律风险的专业回复。两个 Prompt 用的是同一个模型,效果却判若云泥——这就是 Prompt Engineering 的威力。

3.6 本章小结 & 进化进度

🧬 进化进度更新

我们的博学者学会了"听懂好问题"。通过精心设计 Prompt,我们能在不改动模型、不增加任何外部系统的前提下,把它的能力压榨到极致。

但 Prompt 再好,也解决不了一个根本问题:模型不知道它没学过的东西——比如你公司的内部文档、今天的最新数据。要解决这个,我们需要下一项技术:RAG。


第四章 RAG:给大脑接上"外部图书馆"

4.1 痛点:博学者的"知识盲区"与"幻觉"

我们的博学者有两个致命问题:

  1. 知识冻结:他的知识停留在"毕业那天"(训练截止日),之后的事一概不知。
  2. 不知道你的私事:你公司的内部规章、产品手册、客户数据,他从没读过。
  3. 会"一本正经地胡说"(幻觉,Hallucination):当他不知道答案时,不会说"我不知道",反而会编造一个看起来非常合理、实则错误的答案——因为他的本能是"接龙",而不是"求真"。
%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    Q["用户提问:
我们公司的报销额度上限是多少?"] Q --> LLM["纯 LLM"] LLM --> H["❌ 幻觉回答:
'根据一般惯例,上限通常是 5000 元'
(完全是编造的,它根本没见过你公司制度)"] classDef qN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 classDef llmN fill:#ede7f6,stroke:#5e35b1,stroke-width:2px,color:#311b92 classDef badN fill:#ffebee,stroke:#e53935,stroke-width:2px,color:#b71c1c class Q qN class LLM llmN class H badN

4.2 核心思想:开卷考试

RAG(Retrieval-Augmented Generation,检索增强生成)的思想朴素得惊人:

既然模型不知道答案,那就在提问前,先帮它把相关资料"翻"出来,连同问题一起递给它,让它"看着资料回答"。

这就像把一场闭卷考试变成开卷考试

  • 闭卷(纯 LLM):全凭记忆答题,记不清就瞎编。
  • 开卷(RAG):先翻到课本相关章节,再照着权威资料作答,又准又有出处。
%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    Q["用户提问:
我们公司的报销额度上限是多少?"] Q --> R["① 检索:去公司文档库里
找到《财务报销制度》相关段落"] R --> M["② 拼接:把'制度原文 + 问题'
一起递给模型"] M --> A["✅ 准确回答:
'根据《财务报销制度》第 3 条,
差旅报销上限为每日 800 元'
(有据可查,附带出处)"] classDef qN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 classDef retN fill:#e0f7fa,stroke:#00acc1,stroke-width:2px,color:#006064 classDef mergeN fill:#fff8e1,stroke:#fb8c00,stroke-width:2px,color:#e65100 classDef goodN fill:#e8f5e9,stroke:#43a047,stroke-width:2px,color:#1b5e20 class Q qN class R retN class M mergeN class A goodN

4.3 技术原理:机器是怎么"找到相关资料"的?

这里的核心难题是:怎么从成千上万的文档里,快速找到和问题最相关的那几段?

传统的"关键词搜索"不够用——用户问"怎么把钱要回来",但文档里写的是"退款流程",关键词对不上就搜不到。我们需要让机器理解语义上的相近,而不只是字面相同。这就引出了 RAG 的三大基石:

基石一:Embedding(向量化)—— 把文字变成"语义坐标"

Embedding 是一种技术,能把任意一段文字,转换成一串数字(一个向量,比如 1536 个数字组成的数组)。神奇之处在于:

语义越相近的文字,转换出的向量在"空间"里离得越近。

1
2
3
4
"退款"   → [0.21, -0.88, 0.43, ...]  ┐
"把钱要回来" → [0.19, -0.85, 0.45, ...] ┘ 这两个向量很接近(语义相近)

"今天天气真好" → [-0.77, 0.32, 0.10, ...] 离上面两个很远(语义无关)

你可以把它想象成:每段话都被放到了一张巨大的"语义地图"上,意思相近的话被放在相邻的位置。

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph LR
    subgraph 语义空间["语义向量空间(简化为 2D 示意)"]
        A["退款"]
        B["把钱要回来"]
        C["申请退货"]
        D["今天天气真好"]
        E["周末去爬山"]
    end
    A -.很近.- B
    A -.较近.- C
    D -.很近.- E
    A -.很远.- D

    classDef g1 fill:#e0f7fa,stroke:#00acc1,stroke-width:2px,color:#006064
    classDef g2 fill:#fce4ec,stroke:#d81b60,stroke-width:2px,color:#880e4f
    class A,B,C g1
    class D,E g2

基石二:向量数据库(Vector Database)—— 存放与极速检索

我们把所有文档切成小块、逐块算出向量,存进向量数据库(如 Pinecone、Milvus、Chroma、pgvector 等)。它的特长是:给定一个查询向量,能在百万、千万条向量里毫秒级地找出"距离最近"的那几条。

基石三:语义检索(Semantic Search)

当用户提问时,把问题也转成向量,拿去向量数据库里找最近邻——找到的就是语义上最相关的文档片段,哪怕它们一个相同的关键词都没有。

4.4 RAG 完整流程全图

把上面三块基石串起来,一个完整的 RAG 系统分为两个阶段

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    subgraph 离线阶段["① 离线准备阶段(建库,一次性)"]
        D1["原始文档
PDF / Word / 网页 / 数据库"] --> D2["切分 Chunking
切成一段段小文本块"] D2 --> D3["向量化 Embedding
每块算出一个向量"] D3 --> D4["存入向量数据库"] end subgraph 在线阶段["② 在线问答阶段(每次提问)"] U1["用户提问"] --> U2["问题向量化"] U2 --> U3["语义检索
从向量库找最相关的 N 段"] U3 --> U4["拼接 Prompt
检索到的资料 + 原始问题"] U4 --> U5["送入 LLM 生成答案"] U5 --> U6["输出带出处的回答"] end D4 -.提供检索源.-> U3 classDef offN fill:#e8eaf6,stroke:#3949ab,stroke-width:2px,color:#1a237e classDef onN fill:#e0f2f1,stroke:#00897b,stroke-width:2px,color:#004d40 class D1,D2,D3,D4 offN class U1,U2,U3,U4,U5,U6 onN

两个阶段通俗解释:

  • 离线阶段(建图书馆):提前把所有文档"消化"成向量,整整齐齐放进数据库。好比图书管理员提前把所有书编好目录、分门别类上架。
  • 在线阶段(查图书馆):用户一提问,立刻去库里检索相关段落,连同问题一起交给模型作答。好比读者来问问题,管理员飞速翻出相关书页递过去。

4.5 完整示例:搭一个"公司内部文档问答机器人"

假设你要给公司做一个 HR 问答机器人,员工能问"年假有几天"“怎么报销差旅费”。

第 1 步:准备与切分文档

1
2
3
4
5
6
7
把《员工手册.pdf》《报销制度.docx》《考勤规定.md》等
切分成一个个段落块(chunk),例如每 500 字一块:

chunk_1: "年假政策:入职满 1 年享 5 天年假,满 3 年 10 天……"
chunk_2: "差旅报销:需提前申请,住宿费上限每晚 500 元……"
chunk_3: "考勤规定:迟到 15 分钟以内记为……"
...

第 2 步:向量化并入库(离线,伪代码)

1
2
3
for chunk in all_chunks:
vector = embedding_model.encode(chunk) # 文字 → 向量
vector_db.insert(id=chunk.id, vector=vector, text=chunk.text)

第 3 步:用户提问,检索 + 生成(在线,伪代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def answer(question):
# 1. 问题向量化
q_vector = embedding_model.encode(question)

# 2. 语义检索:取最相关的 3 段
top_chunks = vector_db.search(q_vector, top_k=3)

# 3. 拼接 Prompt(这是 RAG 的灵魂)
prompt = f"""
请严格根据下面提供的【参考资料】回答用户问题。
如果资料中没有相关信息,请直接说"未在公司文档中找到相关规定",不要编造。

【参考资料】
{top_chunks[0].text}
{top_chunks[1].text}
{top_chunks[2].text}

【用户问题】
{question}
"""

# 4. 交给大模型生成
return llm.generate(prompt)

实际效果:

1
2
3
4
5
6
7
员工提问:入职两年能休几天年假?

→ 检索命中 chunk_1(年假政策)
→ 模型基于资料回答:
"根据《员工手册》年假政策,入职满 1 年享 5 天年假,
3 年享 10 天。您入职两年,属于满 1 年不满 3 年区间,
因此可享受 5 天年假。"

注意那句关键的 Prompt——“如果资料中没有就说没有,不要编造”,这正是 RAG 用来压制幻觉的核心手段。

4.6 RAG vs 微调(Fine-tuning):到底用哪个?

初学者常困惑:让模型掌握"专属知识",到底该用 RAG 还是微调?这是个高频问题,一表讲清:

对比维度 RAG(检索增强) Fine-tuning(微调)
本质 把知识放在"外部书架",用时去查 把知识"训练"进模型大脑里
知识更新 改文档即可,实时生效 ✅ 需重新训练,成本高、慢
可解释性 答案有出处,可溯源 ✅ 黑盒,不知答案从何而来
抑制幻觉 强(基于真实资料) ✅
擅长 注入事实性知识(文档、数据) 改变模型的风格、语气、技能(如固定输出某种格式、模仿某种文风)
成本 低,无需训练 ✅ 高,需要算力和标注数据
类比 给学生一本可随时翻阅的参考书 把知识刻进学生的长期记忆

经验法则:

  • 要让模型知道新事实 → 优先用 RAG
  • 要让模型改变行为风格 / 掌握某种固定技能 → 用微调
  • 二者可以结合:用微调调教语气,用 RAG 注入实时知识。

💡 延伸:RAG 不只是"查公司文档"。今天大模型的联网搜索功能(先搜网页、再基于搜索结果回答),本质上就是一种以"整个互联网"为知识库的 RAG。

4.7 本章小结 & 进化进度

🧬 进化进度更新

我们的博学者现在会查资料了!通过 RAG,他能基于最新的、私有的、权威的资料来回答,幻觉大幅减少,答案还能附上出处。

至此,第一次进化完成:通过 Prompt 和 RAG,我们在"不动模型本体"的前提下,让大脑既"会说话"又"会用知识"。

但他依然被困在那间封闭的房间里——他能说、能查,却还是不能"动手做事"。他不能帮你发邮件、不能查实时天气、不能运行一段代码。要打破这堵墙,我们必须给他装上真正的"手脚"。这就是第二次进化的主题。


第三部分 第二次进化:让大脑"长出手脚"

到目前为止,我们的博学者已经"会说话、会查资料",但他仍被关在那间封闭的房间里——他能动嘴,不能动手

  • 你问他"北京现在几度?“,他说不出来(他不知道"现在”,更没法查天气)。
  • 你说"帮我把这段代码跑一下看看对不对",他只能"猜"结果,不能真的执行。
  • 你说"把这封邮件发给张三",他只能写出邮件内容,发不出去。

这一部分,我们要给他装上真正的"手脚"。这需要两项关键技术配合:

  1. Tool / Function Calling(工具调用)——让模型学会"求助外部工具",这是"手"。
  2. Harness(运行外壳)——让模型能"想—做—看—再想"地循环工作,这是驱动手脚的"神经与循环系统"。

这是从"聊天机器人"迈向"智能体(Agent)"的最关键一跃


第五章 Tool / Function Calling:工具调用

5.1 核心思想:模型不必"全能",但要会"求助"

人类很聪明,但我们算大数也得用计算器、查路况也得用地图 App。真正的智能不是什么都自己干,而是知道"该用什么工具、什么时候用"。

工具调用(Function Calling)就是把这个能力赋予模型:

模型本身不上网、不算数、不发邮件。但当它判断需要这些能力时,它会输出一个结构化的"调用请求",告诉外部程序:‘请帮我调用 XX 工具,参数是 YY’。外部程序执行后,把结果再喂回给模型。

关键要理解一件事:模型自己并不会、也不能真的执行工具。 它能做的,依然只是它唯一会做的事——“输出文本”。只不过这次输出的不是给人看的话,而是一段结构化的、程序能读懂的"我想调用工具"的指令。真正动手执行的,是模型外面的程序。

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph LR
    A["模型判断:
这个问题我得查天气"] --> B["模型输出结构化请求
(不是答案,是'调用意图')"] B --> C["外部程序
真正执行天气 API"] C --> D["把结果
'北京 25℃' 喂回模型"] D --> E["模型基于结果
组织成自然语言回答用户"] classDef thinkN fill:#ede7f6,stroke:#5e35b1,stroke-width:2px,color:#311b92 classDef outN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 classDef execN fill:#e0f7fa,stroke:#00acc1,stroke-width:2px,color:#006064 classDef resN fill:#e8f5e9,stroke:#43a047,stroke-width:2px,color:#1b5e20 class A thinkN class B,E outN class C execN class D resN

5.2 技术原理:模型怎么知道"有哪些工具可用"?

整个机制分三步走,核心是一份"工具说明书"。

第一步:告诉模型"你有哪些工具"

在每次对话时,程序会把可用工具的清单和说明一起塞进给模型的输入里。每个工具的描述通常包括:工具名、功能描述、需要哪些参数。这份说明书通常用 JSON 格式描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"name": "get_weather",
"description": "查询指定城市的实时天气",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如:北京、上海"
}
},
"required": ["city"]
}
}

这相当于给博学者递上一份工具使用手册:“你现在有一个叫 get_weather 的工具,能查天气,用它的时候要告诉我查哪个城市。”

第二步:模型决定"用不用、怎么用"

当用户问"北京天气怎么样"时,模型读完工具手册,判断这事得用 get_weather,于是它不直接回答,而是输出一段结构化请求:

1
2
3
4
5
6
{
"tool_call": {
"name": "get_weather",
"arguments": { "city": "北京" }
}
}

第三步:程序执行并把结果喂回

外部程序(即下一章要讲的 Harness)解析这段请求,真正去调用天气 API,拿到结果 {"temp": "25℃", "weather": "晴"},再把它作为新的输入喂回给模型。模型这才生成最终回答:“北京现在 25℃,晴天,很适合出门~”

5.3 一次工具调用的完整数据流

把三步串起来,完整的数据流如下(注意箭头的往返):

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
sequenceDiagram
    participant U as 用户
    participant P as 外部程序
(Harness) participant M as 大模型 participant API as 天气 API U->>P: "北京天气怎么样?" P->>M: 问题 + 工具说明书 Note over M: 判断:需要查天气 M->>P: 输出调用请求
get_weather(city="北京") Note over P: 模型没有真正执行,
只是表达了意图 P->>API: 真正调用天气接口 API->>P: 返回 {25℃, 晴} P->>M: 把工具结果喂回模型 Note over M: 基于真实结果组织语言 M->>P: "北京现在 25℃,晴~" P->>U: 展示最终回答

这张时序图是整个 Agent 技术的核心骨架,请务必看懂。后面的 Harness、Agent、MCP,全都是围绕"如何更好地完成这一来一回"而展开的。

5.4 完整示例:一个"查天气"工具的端到端伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 1. 定义工具说明书
tools = [{
"name": "get_weather",
"description": "查询指定城市的实时天气",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}]

# 2. 定义工具的真正实现(这是模型碰不到的、程序自己的代码)
def get_weather(city):
# 真实地调用某个天气服务
return weather_service.query(city) # → {"temp": "25℃", "weather": "晴"}

# 3. 主流程
def chat(user_message):
# 第一次请求:把问题和工具手册一起给模型
response = llm.generate(messages=[user_message], tools=tools)

# 检查模型是否要求调用工具
if response.has_tool_call():
call = response.tool_call # get_weather(city="北京")

# 程序真正执行工具
result = get_weather(**call.arguments) # {"temp": "25℃", ...}

# 第二次请求:把工具结果喂回模型,让它生成最终回答
final = llm.generate(messages=[
user_message,
response, # 模型上次的"调用请求"
{"role": "tool", "content": result} # 工具执行结果
])
return final.text # "北京现在 25℃,晴~"
else:
# 不需要工具,直接回答
return response.text

关键观察: 一次工具调用,模型实际上被请求了两次——第一次是"决定调用工具",第二次是"拿到结果后组织回答"。这一来一回,正是下一章 Harness 要自动化管理的核心。

5.5 工具调用打开的能力边界

一旦模型会用工具,它的能力边界就被极大地拓宽了。常见的工具类型:

工具类型 例子 打破了哪个缺陷
信息获取 联网搜索、查天气、查数据库、读文件 知识过时 / 私有数据盲区
计算执行 运行代码、调用计算器、跑 SQL 不擅长精确计算
动作执行 发邮件、创建日程、下单、操作软件 不能行动
与其他系统交互 调用公司内部 API、控制智能家居 与外部世界隔绝

💡 注意:RAG 本质上也可以看作一种特殊的"检索工具"。现代 Agent 里,"检索知识库"常常就是模型可调用的工具之一。各项技术在这里开始交汇融合。

5.6 本章小结 & 进化进度

🧬 进化进度更新

我们的博学者长出"手"了!通过工具调用,他能查天气、跑代码、发邮件、操作各种外部系统。封闭房间的墙,被凿开了一个大洞。

但目前他还只能"一问一答地用一次工具"。真实任务往往复杂得多——比如"帮我规划一次旅行",需要查天气→订机票→订酒店→加日程,环环相扣、还可能出错重来。模型需要一个能让它反复"想—做—看—再想"的循环机制。这就是 Harness 的使命。


第六章 Harness:让模型"循环"起来

6.1 为什么"一问一答"不够?

设想一个真实任务:“帮我把这个项目里所有用到旧 API 的地方找出来并改成新的。”

这件事不可能一步完成,它需要:

  1. 搜索整个项目,找出哪些文件用了旧 API;
  2. 读取每个文件,看具体怎么用的;
  3. 修改代码;
  4. 运行测试,看改得对不对;
  5. 如果测试失败,根据报错再改……如此反复,直到全部通过。

这是一个**"想一步、做一步、看结果、再想下一步"的循环过程**,而且中途充满不确定性(测试可能失败、文件可能读不到)。单次问答的模型根本扛不住。我们需要一个外壳程序,来驱动这个循环。这个外壳,就是 Harness(运行外壳 / 智能体运行时)

6.2 核心原理:Agent Loop(智能体循环)

Harness 的灵魂是一个循环。它不断地把"当前进展"喂给模型,模型给出"下一步动作",Harness 执行后把"新结果"再喂回去,如此往复,直到任务完成。

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    Start["接到任务"] --> Think["① 模型思考:
当前该做什么?"] Think --> Decide{"② 需要调用工具吗?"} Decide -->|需要| Act["③ Harness 执行工具
(搜索/读写文件/跑测试…)"] Act --> Observe["④ 观察结果
把执行结果喂回模型"] Observe --> Think Decide -->|任务已完成| Done["⑤ 输出最终结果给用户"] classDef startN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 classDef thinkN fill:#ede7f6,stroke:#5e35b1,stroke-width:2px,color:#311b92 classDef actN fill:#e0f7fa,stroke:#00acc1,stroke-width:2px,color:#006064 classDef obsN fill:#fff8e1,stroke:#fb8c00,stroke-width:2px,color:#e65100 classDef doneN fill:#e8f5e9,stroke:#43a047,stroke-width:2px,color:#1b5e20 class Start startN class Think,Decide thinkN class Act actN class Observe obsN class Done doneN

这个"思考→行动→观察→再思考"的循环,有一个经典的名字叫 ReAct(Reasoning + Acting,推理与行动结合),是几乎所有 Agent 的运行基础。

6.3 ReAct 循环:一个真实的运转片段

我们用前面"修改旧 API"的任务,看看循环内部模型的"内心独白"长什么样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
【第 1 轮】
💭 思考:我得先找出哪些文件用了旧 API `oldFetch`。
🔧 行动:调用工具 search_code("oldFetch")
👀 观察:找到 3 个文件:a.js, b.js, c.js

【第 2 轮】
💭 思考:先看看 a.js 里是怎么用的。
🔧 行动:调用工具 read_file("a.js")
👀 观察:(返回 a.js 的内容,第 12 行用了 oldFetch)

【第 3 轮】
💭 思考:把第 12 行的 oldFetch 改成 newFetch。
🔧 行动:调用工具 edit_file("a.js", line=12, ...)
👀 观察:修改成功

【第 4 轮】
💭 思考:改完了,跑测试验证一下。
🔧 行动:调用工具 run_tests()
👀 观察:测试通过 ✅

…(对 b.js、c.js 重复上述过程)…

【最后一轮】
💭 思考:3 个文件全改完,测试都通过了,任务完成。
✅ 输出:已将 3 处 oldFetch 全部替换为 newFetch,测试通过。

看到了吗?模型在循环里一步步地"自主决策下一步该干嘛",Harness 则忠实地执行每一个工具调用、把结果喂回去。这种配合,让模型具备了完成复杂多步任务的能力。

6.4 Harness 到底负责什么?

Harness 远不止"跑个循环"那么简单。它是承载 Agent 运行的整个运行时环境,主要职责包括:

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    H["Harness
运行外壳 / 智能体运行时"] H --> J1["① 循环控制
驱动'思考-行动-观察'循环
判断何时该停"] H --> J2["② 工具执行
解析模型的调用请求,
安全地真正执行工具"] H --> J3["③ 上下文管理
把历史对话/结果塞进有限的
上下文窗口,超了就压缩裁剪"] H --> J4["④ 错误恢复
工具报错/超时怎么办,
要不要重试或换策略"] H --> J5["⑤ 安全与权限
危险操作(删文件/发消息)
是否需要用户确认"] classDef hN fill:#ede7f6,stroke:#5e35b1,stroke-width:3px,color:#311b92 classDef jN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 class H hN class J1,J2,J3,J4,J5 jN

其中 ③ 上下文管理尤其关键,直接呼应第一章讲的"上下文窗口有限"这一硬限制:

任务一长,对话历史 + 工具结果会迅速撑爆上下文窗口。Harness 必须聪明地取舍——哪些保留、哪些摘要压缩、哪些丢弃。这就是为什么本文开头的系统说明里会提到"当对话变长时会自动压缩"——那正是 Harness 在做上下文管理。

6.5 完整示例:一个最简 Harness 的伪代码

下面这段伪代码,剥去所有花哨功能,展示 Harness 最核心的循环骨架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def harness_run(user_task, tools, max_steps=20):
# 初始化对话历史:系统提示 + 用户任务
messages = [
{"role": "system", "content": "你是一个能使用工具完成任务的助手"},
{"role": "user", "content": user_task}
]

# ===== 核心:Agent Loop =====
for step in range(max_steps): # max_steps 防止无限循环
# 1. 让模型思考下一步(带上全部工具说明)
response = llm.generate(messages, tools=tools)

# 2. 模型认为任务完成了,没有再调用工具 → 退出循环
if not response.has_tool_call():
return response.text # 返回最终答案

# 3. 模型要求调用工具 → Harness 真正执行
call = response.tool_call
try:
result = execute_tool(call.name, call.arguments)
except Exception as e:
result = f"工具执行出错:{e}" # ④ 错误也喂回去,让模型自己想办法

# 4. 把"模型的请求"和"执行结果"都追加进历史,进入下一轮
messages.append(response)
messages.append({"role": "tool", "content": result})

# 5.(真实 Harness 还会在这里做上下文管理:太长就压缩 messages)

return "已达到最大步数,任务未完成"

这短短二十行,就是一切 Agent 的内核。 Claude Code、各种 AI 编程助手、自动化智能体,其最底层都是这样一个被不断强化、加上无数安全与优化措施的循环。

6.6 本章小结 & 进化进度

🧬 进化进度更新

我们的博学者不仅长出了"手",还获得了驱动手脚反复工作的"循环神经系统"。现在他能接下一个复杂任务,自主地"想—做—看—再想",一步步把它完成,中途出错还能自我调整。

第二次进化完成。 此刻,这个被关在房间里的博学者,已经能凿开墙壁、伸手与外部世界互动、并坚持不懈地完成多步任务了。

他离一个真正的"智能体"只差一层窗户纸——自主性、记忆、以及与其他智能体协作的能力。捅破这层纸,他就正式成为 Agent。这是第三次、也是最激动人心的一次进化。


第四部分 第三次进化:成为真正的"智能体"

我们的博学者已经能"说、查、做、循环"。但他还缺最后几样东西,才能称得上真正的智能体(Agent)

  • 自主性——不需要你一步步指挥,给个目标他就能自己规划、分解、执行。
  • 记忆——记得住你是谁、之前聊过什么、积累的经验。
  • 协作——一个人忙不过来时,能调度"子智能体"分工合作。
  • 标准化的工具接入(MCP)——能即插即用地接上海量外部工具。
  • 可复用的技能(Skill)——把成熟的"做事方法论"打包,随取随用。

这一部分,我们捅破最后一层窗户纸。


第七章 Agent:从"工具"到"自主"

7.1 到底什么才算一个 Agent?

"Agent"是当下最火也最被滥用的词。我们给一个清晰的判定标准:

Agent = 大模型(大脑)+ 自主规划 + 工具调用 + 记忆,能够在给定一个目标后,自主地分解任务、决策、执行、并根据反馈调整,直到达成目标。

关键词是**“自主”**。我们用一个对比看清楚界限:

形态 你需要怎么指挥 例子
普通聊天机器人 你问一句,它答一句 “翻译这句话”
工具调用(第五章) 你问一个需要工具的问题,它用一次工具答你 “北京天气怎么样”
Agent(智能体) 你只给目标,它自己拆解成几十步、自主执行完 “帮我策划并预订一次三天的东京之旅”

最后这个旅行例子,Agent 要自己想到:查天气定日期 → 比价订机票 → 选订酒店 → 安排每日行程 → 加进日历 → 遇到航班售罄还要换方案……全程不需要你手把手指挥每一步,这就是质变。

7.2 Agent 的核心架构:四大要素

一个完整的 Agent,由四大部件构成。这张图是本章的核心:

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    Goal["用户给定目标"] --> Brain

    subgraph Agent["一个完整的 Agent"]
        Brain["🧠 大脑 LLM
理解、推理、决策"] Brain <--> Plan["📋 规划 Planning
把大目标拆成小步骤,
排出执行顺序"] Brain <--> Memory["💾 记忆 Memory
短期:本次任务的进展
长期:跨会话的经验积累"] Brain <--> Tools["🔧 工具 Tools
搜索/读写/执行/调用 API
(含 MCP、Skill)"] end Tools <--> World["🌍 外部世界
文件/网络/数据库/其他系统"] classDef goalN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 classDef brainN fill:#ede7f6,stroke:#5e35b1,stroke-width:3px,color:#311b92 classDef compN fill:#e0f2f1,stroke:#00897b,stroke-width:2px,color:#004d40 classDef worldN fill:#fff8e1,stroke:#fb8c00,stroke-width:2px,color:#e65100 class Goal goalN class Brain brainN class Plan,Memory,Tools compN class World worldN
  • 🧠 大脑(LLM):核心决策者,前面几章的一切都为它服务。
  • 📋 规划(Planning):把"东京之旅"这种大目标,拆解成有序的小任务。复杂任务还会动态调整计划。
  • 💾 记忆(Memory):让 Agent 不"失忆",下一节专门讲。
  • 🔧 工具(Tools):Agent 的手脚,包括普通工具、MCP 接入的工具、以及 Skill。

而驱动这四个部件协同运转的"引擎",正是第六章讲的 Harness 循环。可以说:Harness 是 Agent 的运行时,Agent 是 Harness 跑起来之后涌现出的"智能行为"。

7.3 记忆系统:让 Agent 不再"失忆"

第一章说过,纯 LLM 的硬伤之一是"没有记忆"。Agent 通过一套记忆系统来弥补,通常分两层:

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    M["Agent 记忆系统"]
    M --> S["短期记忆
Short-term Memory"] M --> L["长期记忆
Long-term Memory"] S --> S1["存什么:当前任务的对话、
中间结果、刚执行的工具输出"] S --> S2["存在哪:上下文窗口里"] S --> S3["特点:容量有限、
任务结束就清空"] L --> L1["存什么:用户偏好、历史经验、
跨会话的长期知识"] L --> L2["存在哪:外部数据库 /
向量库 / 文件"] L --> L3["特点:持久化、
用时通过检索(RAG)调回"] classDef rootN fill:#ede7f6,stroke:#5e35b1,stroke-width:3px,color:#311b92 classDef shortN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 classDef longN fill:#e0f2f1,stroke:#00897b,stroke-width:2px,color:#004d40 class M rootN class S,S1,S2,S3 shortN class L,L1,L2,L3 longN
记忆类型 通俗比喻 实现方式
短期记忆 你正在做一道题时,草稿纸上的演算过程 放在上下文窗口里,由 Harness 管理(超了就压缩)
长期记忆 你多年积累、记在笔记本里随时能翻的经验 存到外部数据库/向量库,需要时用 RAG 检索回来

💡 技术交汇点:你发现没有?长期记忆的实现,本质就是 RAG——把经验存进向量库,用时检索调回。本文开头系统提示里提到的"持久化文件记忆系统",正是一种 Agent 长期记忆的实现。各项技术在 Agent 这里彻底融会贯通。

7.4 单 Agent vs 多 Agent:一个人 vs 一个团队

当任务极其复杂时,单个 Agent 会力不从心(上下文塞不下、什么都自己干效率低)。于是出现了**多智能体(Multi-Agent)**架构——像一个公司团队那样分工协作。

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    User["用户:写一篇关于'新能源汽车'的深度报告"]
    User --> Lead["🎯 主管 Agent (Orchestrator)
负责拆解任务、调度、汇总"] Lead --> A1["🔍 研究员 Agent
负责搜集资料"] Lead --> A2["📊 分析师 Agent
负责数据分析"] Lead --> A3["✍️ 写作 Agent
负责撰写成文"] A1 --> Lead A2 --> Lead A3 --> Lead Lead --> Result["汇总成最终报告"] classDef userN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 classDef leadN fill:#ede7f6,stroke:#5e35b1,stroke-width:3px,color:#311b92 classDef subN fill:#e0f2f1,stroke:#00897b,stroke-width:2px,color:#004d40 classDef resN fill:#e8f5e9,stroke:#43a047,stroke-width:2px,color:#1b5e20 class User userN class Lead leadN class A1,A2,A3 subN class Result resN
模式 特点 适用场景
单 Agent 一个智能体包打天下 任务相对单一、步骤不太多
多 Agent 主管 + 多个专精子智能体分工 复杂任务、需要不同专长、可并行

多 Agent 的好处:分工专精(每个 Agent 只管一摊,提示和工具更聚焦)、并行提效(研究员和分析师可同时干活)、上下文隔离(各自的细节不互相干扰主管的视野)。

7.5 完整示例:"自动整理下载文件夹"的 Agent 拆解

给 Agent 一个目标:“帮我把杂乱的下载文件夹整理好。” 看它如何自主运转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
🎯 目标:整理下载文件夹

📋 规划阶段(Agent 自己想出的步骤):
1. 先看看文件夹里都有什么
2. 按类型给文件分类(图片/文档/安装包/压缩包…)
3. 为每类创建子文件夹
4. 把文件移动到对应子文件夹
5. 报告整理结果

🔄 执行阶段(Harness 驱动的 ReAct 循环):
第1轮 💭 该先了解文件夹内容
🔧 list_files("~/Downloads")
👀 收到 87 个文件清单

第2轮 💭 分析这些文件,按扩展名归类
(.jpg/.png→图片, .pdf/.docx→文档, .dmg/.exe→安装包…)
🔧 create_folder("图片"), create_folder("文档")...
👀 子文件夹创建成功

第3轮 💭 开始搬运文件
🔧 move_file("photo.jpg", "图片/") …(批量)
👀 移动完成

第4轮 💭 任务完成,汇总
✅ 输出:"已整理 87 个文件:32 张图片、20 份文档、
15 个安装包、20 个其他,分别归入对应文件夹。"

💾 记忆:(可选)记下"该用户喜欢按文件类型分类"
下次整理时直接沿用这个偏好。

注意:你只给了一句目标,分几步、怎么分类、先做啥后做啥,全是 Agent 自己决定的。这就是 Agent 的自主性。

7.6 本章小结 & 进化进度

🧬 进化进度更新

第三次进化的核心已经达成:我们的博学者正式成为了 Agent——有大脑、会规划、有记忆、能调用工具,给个目标就能自主干完一整件复杂的事,还能呼朋唤友组队协作。

但还有两个"工程层面"的难题待解:

  1. 世界上工具千千万,难道每接一个工具都要重写一遍对接代码?→ 第八章 MCP 解决"工具标准化接入"。
  2. 每次都要从头教 Agent 怎么做某类专业任务,太低效。→ 第九章 Skill 解决"经验复用"。

第八章 MCP:工具世界的"USB 接口"

8.1 痛点:N×M 的对接噩梦

第五章我们让模型学会了用工具。但现实很骨感:

  • AI 应用有很多个(Claude、各种 IDE 插件、各家聊天助手……)。
  • 外部工具/数据源也有很多个(GitHub、数据库、Slack、Google Drive、企业内部系统……)。

如果每个 AI 应用都要为每个工具单独写一套对接代码,那就是 N 个应用 × M 个工具 = N×M 套对接的噩梦。工具方改个接口,所有对接它的应用都得跟着改。

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    subgraph 没有MCP["❌ 没有 MCP:N×M 套混乱对接"]
        App1["AI 应用 A"] --- T1["GitHub"]
        App1 --- T2["数据库"]
        App1 --- T3["Slack"]
        App2["AI 应用 B"] --- T1
        App2 --- T2
        App2 --- T3
        App3["AI 应用 C"] --- T1
        App3 --- T2
        App3 --- T3
    end

    classDef appN fill:#ede7f6,stroke:#5e35b1,stroke-width:2px,color:#311b92
    classDef toolN fill:#ffebee,stroke:#e53935,stroke-width:2px,color:#b71c1c
    class App1,App2,App3 appN
    class T1,T2,T3 toolN

每根连线都是一套要单独开发和维护的代码——连线密密麻麻,是个维护灾难。

8.2 核心思想:定一个标准,“一次对接,处处可用”

MCP(Model Context Protocol,模型上下文协议) 是 Anthropic 在 2024 年底推出的开放标准,它的思路和 USB 接口一模一样:

当年每种设备都有自己的接口(打印机口、PS/2 鼠标口、各种专有口),混乱不堪。USB 出现后,定义了一个统一标准:不管是鼠标、U 盘还是键盘,只要做成 USB 接口,插上任何有 USB 口的电脑都能用。

MCP 就是 AI 世界的 USB:只要工具方按 MCP 标准做一个"MCP Server",任何支持 MCP 的 AI 应用都能即插即用,无需为它单独写对接代码。

于是 N×M 的噩梦,变成了 N+M 的清爽:每个应用只需支持 MCP 一次,每个工具只需实现 MCP 一次。

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    subgraph 有MCP["✅ 有 MCP:N+M 清爽标准"]
        App1["AI 应用 A"] --- MCP(("MCP
统一协议")) App2["AI 应用 B"] --- MCP App3["AI 应用 C"] --- MCP MCP --- T1["GitHub
MCP Server"] MCP --- T2["数据库
MCP Server"] MCP --- T3["Slack
MCP Server"] end classDef appN fill:#ede7f6,stroke:#5e35b1,stroke-width:2px,color:#311b92 classDef mcpN fill:#e8f5e9,stroke:#43a047,stroke-width:3px,color:#1b5e20 classDef toolN fill:#e0f7fa,stroke:#00acc1,stroke-width:2px,color:#006064 class App1,App2,App3 appN class MCP mcpN class T1,T2,T3 toolN

官方资料:MCP 是开放协议,官方文档与架构图见 https://modelcontextprotocol.io ,介绍博客见 https://www.anthropic.com/news/model-context-protocol

8.3 技术原理:Client-Server 架构

MCP 采用经典的客户端-服务器模式:

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph LR
    subgraph Host["AI 应用(Host)"]
        Agent["Agent / LLM"]
        Client["MCP Client
(协议客户端)"] Agent <--> Client end Client <-->|"MCP 标准协议
(JSON-RPC)"| Server["MCP Server
(由工具方提供)"] Server --> R1["🔧 Tools 工具
可执行的操作"] Server --> R2["📄 Resources 资源
可读取的数据"] Server --> R3["💬 Prompts 提示模板
预设的提示词"] classDef hostN fill:#ede7f6,stroke:#5e35b1,stroke-width:2px,color:#311b92 classDef clientN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 classDef serverN fill:#e0f7fa,stroke:#00acc1,stroke-width:2px,color:#006064 classDef capN fill:#e0f2f1,stroke:#00897b,stroke-width:2px,color:#004d40 class Agent hostN class Client clientN class Server serverN class R1,R2,R3 capN
  • MCP Host(宿主):你用的 AI 应用,比如 Claude Code。
  • MCP Client(客户端):宿主内部负责说 MCP "方言"的模块。
  • MCP Server(服务器):由工具/数据源提供方开发,对外暴露能力。

一个 MCP Server 可以暴露三种能力

能力 含义 例子
Tools(工具) 可执行的动作(会改变状态) 发送消息、创建文件、执行查询
Resources(资源) 可读取的数据(只读) 读取某个文件、获取某条记录
Prompts(提示模板) 预设好的提示词模板,供用户快捷调用 "代码审查"模板、"总结文档"模板

8.4 完整示例:一个暴露"数据库查询"能力的 MCP Server

假设我们想让任何 AI 应用都能查询我们的产品数据库,就写一个 MCP Server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 用 MCP SDK 定义一个 Server(伪代码,结构化示意)
mcp_server = MCPServer(name="product-db")

# 暴露一个工具:查询产品库存
@mcp_server.tool()
def query_stock(product_name: str):
"""根据产品名查询当前库存数量"""
result = database.query(
"SELECT stock FROM products WHERE name = ?", product_name
)
return {"product": product_name, "stock": result}

# 暴露一个资源:只读的产品目录
@mcp_server.resource("catalog://products")
def get_catalog():
"""返回完整产品目录"""
return database.query("SELECT name, price FROM products")

mcp_server.run() # 启动,等待 AI 应用通过 MCP 协议连接

接下来发生的事:

  1. 你在 Claude(或任何支持 MCP 的应用)里配置一下这个 Server 的地址。
  2. AI 应用通过 MCP Client 连上,自动发现这个 Server 提供了 query_stock 工具和 catalog://products 资源。
  3. 当你问"iPhone 还有多少库存?",Agent 自动判断该调用 query_stock,通过 MCP 协议发起调用,拿到结果回答你。

最大的好处:这个 product-db Server 写一次,Claude 能用、IDE 插件能用、未来任何支持 MCP 的新应用都能直接用,再不用为每个应用单独对接。这就是标准的力量。

8.5 本章小结 & 进化进度

🧬 进化进度更新

通过 MCP,我们的 Agent 获得了一个标准化的"万能工具插座"。整个世界的工具和数据源,只要做成 MCP Server,就能被它即插即用。Agent 的能力边界,从此可以无限扩展,且生态越大,人人受益。

还剩最后一块拼图:Agent 现在工具齐备,但每遇到一类专业任务(比如"接入微信支付"“做代码审查”),还得有人现场教它一整套方法。能不能把这些成熟的做事套路打包,让它随取随用?这就是 Skill


第九章 Skill:把"经验"打包复用

9.1 痛点:每次都要"重新教一遍"

想象你雇了一个绝顶聪明但毫无经验的新员工(这就是 Agent)。他什么都能学得很快,但问题是:

  • 今天教他怎么做"微信支付接入",讲了一大套规范、流程、注意事项。
  • 明天换个对话,他又"忘了"(无记忆),你还得从头再教一遍。
  • 而且这套经验只存在你脑子里,没法分享给别人的 Agent。

痛点很清楚:成熟的"做事方法论"无法被沉淀和复用。 每次都要在 Prompt 里重新堆砌大量的背景知识、步骤说明、示例代码——既费 token,又难以维护。

9.2 核心思想:把"方法论 + 工具 + 示例"打包成一个单元

Skill(技能) 的思路是:

把完成某一类专业任务所需的一切——操作指南(方法论)、要用到的工具、参考示例、相关资源——打包成一个自包含、可复用、可分享的单元。需要时,Agent 自动加载这个 Skill,瞬间"获得"这项专业技能。

这就像给那个聪明的新员工一本**《某项业务的标准作业手册(SOP)》**:手册里写清了"遇到这类任务,第一步做什么、第二步做什么、有哪些坑、附带模板和示例代码"。他需要做这类任务时翻开手册,立刻就是个熟练工。

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    Skill["一个 Skill 技能包"]
    Skill --> D["📖 说明 / 方法论
这个技能干什么、
什么时候该用、操作步骤"] Skill --> S["📝 示例 / 模板
参考代码、范例、最佳实践"] Skill --> R["📂 资源文件
配置、文档、脚本等"] Skill --> T["🔧 关联工具
(可选)该技能要调用的工具"] classDef rootN fill:#ede7f6,stroke:#5e35b1,stroke-width:3px,color:#311b92 classDef partN fill:#e0f2f1,stroke:#00897b,stroke-width:2px,color:#004d40 class Skill rootN class D,S,R,T partN

9.3 Skill 与 Tool / MCP 的区别

这是初学者最容易混淆的地方,一张表彻底厘清:

对比 Tool(工具) MCP Skill(技能)
本质 一个能执行的函数 工具接入的标准协议 一套打包的做事方法论
回答的问题 “我能做这个动作 “我用什么标准接工具” “做这类任务该怎么做”
粒度 单个原子操作(查天气) 连接层 完整任务流程(接入支付)
类比 一把螺丝刀 工具的标准插座 一本《装修施工手册》
包含什么 函数定义 + 实现 通信规范 指南 + 示例 + 资源 + 可能用到多个工具

一句话总结三者关系:

Tool 是"能力",MCP 是"接能力的标准插座",Skill 是"运用这些能力把一类事做好的方法论"。 Skill 往往会在内部组合调用多个 Tool。

9.4 Skill 的加载与触发机制

Skill 一个精妙的设计是按需加载:不是一股脑把所有技能手册都塞给 Agent(那会撑爆上下文),而是平时只让它知道"有哪些技能、各自管什么",等真正用到时才加载完整内容。

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    Start["用户提出请求
'帮我接入微信 JSAPI 支付'"] --> Match{"匹配:有没有
相关 Skill?"} Match -->|"命中 wechatpay Skill"| Load["加载该 Skill 的
完整方法论 + 示例 + 工具"] Match -->|没有匹配| Normal["按普通方式处理"] Load --> Exec["Agent 依照 Skill 指南
专业地完成任务"] Exec --> Done["交付高质量结果"] classDef startN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 classDef matchN fill:#ede7f6,stroke:#5e35b1,stroke-width:2px,color:#311b92 classDef loadN fill:#e0f7fa,stroke:#00acc1,stroke-width:2px,color:#006064 classDef doneN fill:#e8f5e9,stroke:#43a047,stroke-width:2px,color:#1b5e20 class Start startN class Match,Normal matchN class Load,Exec loadN class Done doneN

关键机制:

  1. 轻量索引:平时 Agent 只持有每个 Skill 的"名字 + 一句话描述 + 触发条件",占用极少上下文。
  2. 按需触发:当用户请求匹配上某个 Skill 的触发条件,才加载它的完整内容。
  3. 即时专精:加载后,Agent 立刻"上岗",按手册里的专业流程办事。

这套"轻量索引 + 按需加载"的设计,完美呼应了第一章的"上下文窗口有限"约束——既要技能丰富,又不能撑爆窗口,按需加载是最优解。

9.5 完整示例:一个"微信支付接入"Skill 的结构剖析

本对话环境里就真实挂载着一批微信支付相关的 Skill,我们以它为例剖析其结构(这是真实可见的 Skill):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
wechatpay-basic-payment(微信支付基础支付 Skill)

├── 触发条件(什么时候自动启用)
"当用户提到 JSAPI支付 / APP支付 / H5支付 / Native支付 /
│ 小程序支付,或要求'推荐支付方式'、'要支付接口代码示例'、
│ '排查支付或退款问题' 时"

├── 方法论(核心能力,覆盖五大块)
│ ├─ 选型:帮你判断该用哪种支付方式
│ ├─ 代码示例:各支付方式的接口调用示例代码
│ ├─ 业务速查:关键参数、流程、规则速查
│ ├─ 质量评估:检查接入是否规范
│ └─ 排障:支付/退款问题的排查指南

├── 示例与模板(各场景的参考代码)
└── 关联资源(接口文档、参数说明等)

实际触发效果:

1
2
3
4
5
6
7
8
9
用户说:"帮我写一个小程序支付的接口示例"

系统检测到这句话命中了 wechatpay-basic-payment 的触发条件

自动加载该 Skill 的完整方法论和示例

Agent 此刻"变身"为微信支付接入专家,
产出符合官方规范的小程序支付(JSAPI)接入代码、
关键参数说明、以及常见坑的提醒

对比一下:没有这个 Skill,Agent 可能给出泛泛的、不够准确甚至过时的支付代码;有了这个 Skill,它直接调用沉淀好的专业方法论,产出可靠得多。这就是 Skill"经验复用"的价值。

9.6 本章小结 & 进化进度

🧬 进化进度更新

第三次进化全部完成! 我们的博学者,已经从那个被关在封闭房间里、只会"文本接龙"的大脑,彻底蜕变为一个:

  • 🧠 有强大大脑(大模型)、
  • 🗣️ 会好好说话(Prompt)、
  • 📚 会查阅资料(RAG)、
  • 🔧 长着灵巧双手(Tool)、
  • 🔄 拥有循环神经系统(Harness)、
  • 🎯 能自主规划决策、有记忆、会组队(Agent)、
  • 🔌 配备万能工具插座(MCP)、
  • 📖 还揣着一身可随取随用的专业技能(Skill)

……的完整智能体

接下来,我们看看在工程实践中,怎么不必从零造这一切轮子——这就是第五部分:开发框架与全景实战


第五部分 把拼图拼起来:框架与实战

我们已经认识了所有"拼图"。但在真实开发中,没人会从零手写 Harness 循环、记忆系统、工具调度——那太重复了。社区已经造好了一批成熟的"脚手架",让你站在巨人的肩膀上。这一部分先讲框架,再用一个完整实战场景把所有技术串起来跑一遍。


第十章 主流开发框架:不必从零造轮子

10.1 为什么需要框架

回顾前面的章节,要亲手搭一个 Agent,你得自己实现:工具调用的解析、ReAct 循环、上下文管理与压缩、记忆的存取、多 Agent 调度、错误重试……这些都是通用的、重复的脏活累活

开发框架就是把这些通用能力封装好,你只需关注业务逻辑。就像盖房子不必自己烧砖——直接用预制构件,又快又稳。

10.2 主流框架一览

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    Need["我要开发一个 AI 应用"]
    Need --> Q1{"主要需求是什么?"}
    Q1 -->|"灵活编排复杂流程"| LC["LangChain / LangGraph"]
    Q1 -->|"官方原生、稳健的 Agent"| SDK["Claude Agent SDK"]
    Q1 -->|"专注知识库问答 (RAG)"| LI["LlamaIndex"]
    Q1 -->|"多智能体协作"| Crew["CrewAI / AutoGen"]
    Q1 -->|"低代码、可视化搭建"| Dify["Dify / Coze"]

    classDef needN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1
    classDef qN fill:#ede7f6,stroke:#5e35b1,stroke-width:2px,color:#311b92
    classDef fwN fill:#e0f2f1,stroke:#00897b,stroke-width:2px,color:#004d40
    class Need needN
    class Q1 qN
    class LC,SDK,LI,Crew,Dify fwN
框架 定位 特点 适合谁
LangChain 最流行的通用编排框架 组件丰富、生态庞大、集成多 想快速拼装各种 LLM 应用
LangGraph LangChain 的"图编排"升级 用图(Graph)描述复杂的、有循环和分支的 Agent 流程 需要精细控制 Agent 流程
Claude Agent SDK Anthropic 官方智能体框架 原生支持工具、MCP、子智能体,稳健可靠(本对话环境即基于它) 构建生产级 Claude Agent
LlamaIndex 专注数据/RAG 的框架 在文档加载、索引、检索上做到极致 重度知识库问答场景
CrewAI / AutoGen 多智能体协作框架 擅长编排"一个团队"的 Agent 分工 复杂的多角色协作任务
Dify / Coze(扣子) 低代码 / 可视化平台 拖拽式搭建,无需写太多代码 非专业开发者、快速验证想法

10.3 框架在技术栈中的位置

框架不是一项"新拼图",而是把前面所有拼图封装、串联起来的"底座"

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    subgraph 应用层["① 你的应用(业务逻辑)"]
        BizApp["客服机器人 / 编程助手 / 数据分析 Agent ..."]
    end
    subgraph 框架层["② 开发框架层(LangChain / Claude Agent SDK ...)"]
        FW["封装:Agent 循环 · 工具调度 · 记忆 · 多 Agent · 上下文管理"]
    end
    subgraph 能力层["③ 核心能力层(前面各章的拼图)"]
        Cap["Prompt · RAG · Tool · Harness · MCP · Skill"]
    end
    subgraph 模型层["④ 大模型层"]
        Model["Claude / GPT / Gemini / 开源模型 ..."]
    end

    BizApp --> FW --> Cap --> Model

    classDef appN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1
    classDef fwN fill:#e0f2f1,stroke:#00897b,stroke-width:2px,color:#004d40
    classDef capN fill:#ede7f6,stroke:#5e35b1,stroke-width:2px,color:#311b92
    classDef modelN fill:#fff8e1,stroke:#fb8c00,stroke-width:2px,color:#e65100
    class BizApp appN
    class FW fwN
    class Cap capN
    class Model modelN

10.4 选型建议

  • 初学/快速验证想法 → Dify、Coze 这类低代码平台,或直接用 LangChain 快速搭原型。
  • 认真做生产级 Agent → Claude Agent SDK(官方、稳健)或 LangGraph(流程可控)。
  • 核心是知识库问答 → LlamaIndex。
  • 需要多角色团队协作 → CrewAI / AutoGen。
  • 不确定时:从最简单的开始,需求复杂了再迁移。别一上来就追求"全家桶"。

第十一章 全景图:所有拼图如何协同

到这里,所有技术都讲完了。我们用一张总图一个完整实战场景,把它们彻底串成一个有机整体。

11.1 技术全景总图

这张图是全文的"集大成"——从用户的一句话,到最终交付,每一项技术各司其职:

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    User["👤 用户:给出目标"] --> Prompt["🗣️ Prompt 工程
把意图表达清楚"] Prompt --> Agent subgraph Agent["🎯 Agent(运行在 Harness 之上)"] direction TB Brain["🧠 大模型
理解·推理·决策"] Brain <--> Planning["📋 规划
拆解任务"] Brain <--> Mem["💾 记忆
短期 + 长期"] Brain <--> Loop["🔄 Harness 循环
想-做-看-再想"] end Loop <--> Capability subgraph Capability["🔧 能力来源"] direction TB Tools["普通工具"] MCP["🔌 MCP
标准接入外部工具"] Skill["📖 Skill
专业方法论"] RAG["📚 RAG
检索知识/长期记忆"] end MCP <--> World["🌍 外部世界
数据库·API·文件·其他系统"] RAG <--> KB["📂 知识库
向量数据库"] Agent --> Result["✅ 交付结果"] classDef userN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 classDef promptN fill:#fce4ec,stroke:#d81b60,stroke-width:2px,color:#880e4f classDef brainN fill:#ede7f6,stroke:#5e35b1,stroke-width:3px,color:#311b92 classDef compN fill:#e0f2f1,stroke:#00897b,stroke-width:2px,color:#004d40 classDef capN fill:#e0f7fa,stroke:#00acc1,stroke-width:2px,color:#006064 classDef worldN fill:#fff8e1,stroke:#fb8c00,stroke-width:2px,color:#e65100 classDef resN fill:#e8f5e9,stroke:#43a047,stroke-width:2px,color:#1b5e20 class User userN class Prompt promptN class Brain brainN class Planning,Mem,Loop compN class Tools,MCP,Skill,RAG capN class World,KB worldN class Result resN

11.2 完整实战场景:“帮我分析上月广告投放数据并生成报告”

现在,我们让一个装备齐全的 Agent 来完成这个真实任务,看每项技术如何登场:

1
👤 用户目标:"帮我分析上个月的广告投放数据,找出问题,生成一份优化建议报告。"

【第 0 步:Prompt 工程】
用户的目标被组织成清晰的指令(角色=广告优化专家,任务=分析+报告,输出=结构化报告)。

【第 1 步:规划 Planning】
Agent 的大脑把大目标拆解为:

1
2
3
4
5
1. 拉取上月广告投放数据
2. 计算关键指标(消耗、点击率、转化率、ROI)
3. 找出表现异常的广告计划
4. 结合优化经验给出建议
5. 生成报告

【第 2 步:Harness 循环 + 工具/MCP 登场】

1
2
3
4
5
6
7
8
9
10
11
12
1轮 💭 先拉数据
🔧 通过 MCP 调用广告平台的数据接口(这就是本环境里挂载的
广告 MCP 工具,如 report_custom_get / account_list 等)
👀 拿到上月各广告计划的投放明细

2轮 💭 计算指标、找异常
🔧 调用代码执行工具,跑数据分析脚本
👀 发现:计划 A 的转化率暴跌、计划 B 的 ROI 为负

3轮 💭 该怎么优化?调取专业经验
📚 RAG / Skill:检索"广告优化方法论"知识库
👀 得到针对"转化率下降""ROI 为负"的标准优化策略

【第 3 步:记忆 Memory】
Agent 调取长期记忆——“这个用户偏好详细的数据表格 + 中文报告”(可能来自过往交互的沉淀),据此调整输出风格。

【第 4 步:交付】

1
2
3
4
5
6
7
8
✅ 生成报告:
一、上月投放总览(消耗 ¥XX 万,整体 ROI 1.8
二、问题诊断
· 计划 A:转化率从 3.2% 跌至 0.8%,疑似落地页问题
· 计划 B:ROI -0.3,建议暂停
三、优化建议(基于方法论)
· 计划 A:检查落地页加载速度、更换素材
· 预算重新分配:砍掉 B,倾斜给高 ROI 的计划 C

各技术在此场景中的角色一览:

技术 在本场景扮演的角色
大模型 全程的理解、推理、决策核心
Prompt 把模糊目标转成清晰指令
规划 Planning 把大目标拆成 5 个步骤
Harness 驱动"拉数据→算指标→找经验"的多轮循环
Tool / MCP 真正拉取广告数据、执行分析脚本
RAG / Skill 提供专业的广告优化方法论
记忆 Memory 记住用户偏好,定制报告风格
框架 在底层把上述一切封装、串联起来

看,没有任何一项技术是孤立的。它们像一支配合默契的乐队,在 Agent 这个"指挥"的调度下,共同完成了单靠大模型绝无可能完成的复杂任务。这正是本文主线的终点:大脑,终于彻底学会了’做事’。


第六部分 展望

第十二章 结语:技术演进的本质与趋势

12.1 一句话总结每项技术解决的核心问题

我们用一张表,为整篇文章画上句号。回看这张表,你会发现每项技术都精准对应着"让大脑会做事"这条主线上的某一个具体障碍:

技术 解决的核心问题 一句话本质
大模型 让机器理解和生成语言 一个超级"文本接龙"引擎
Prompt 工程 同样的模型如何发挥更好 学会"把话说到位"
RAG 知识过时、私有数据、幻觉 把闭卷考试变成开卷考试
Tool / Function Calling 模型不能行动 让模型学会"求助工具"
Harness 复杂任务需要多步循环 "想-做-看-再想"的运行外壳
Agent 缺乏自主性 给个目标就能自主干完一整件事
记忆系统 模型会"失忆" 短期靠上下文,长期靠检索
MCP 工具对接的 N×M 噩梦 AI 世界的"USB 标准接口"
Skill 经验无法沉淀复用 把"做事方法论"打包成手册
开发框架 不必重复造轮子 封装一切的"脚手架"

12.2 当下最热的方向

技术仍在飞速演进,几个最值得关注的前沿方向:

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    Future["AI 技术前沿方向"]
    Future --> A["🤖 Agentic AI
更强自主性,能独立完成
越来越长、越复杂的任务链"] Future --> B["👁️ 多模态
同时理解文字·图像·音频·视频,
感知更接近人类"] Future --> C["🖥️ Computer Use
直接操作电脑(点击·输入),
像人一样使用任意软件"] Future --> D["🏠 本地化 / 小模型
更小更强的模型可在手机/
个人电脑本地运行,隐私友好"] Future --> E["🧩 标准化协作
MCP 等协议普及,
Agent 与工具/Agent 间自由协作"] classDef rootN fill:#ede7f6,stroke:#5e35b1,stroke-width:3px,color:#311b92 classDef dirN fill:#e0f2f1,stroke:#00897b,stroke-width:2px,color:#004d40 class Future rootN class A,B,C,D,E dirN
  • Agentic AI(智能体化):这是最大的趋势。AI 正从"你问我答的工具",变成"能独立承接并完成任务的数字员工"。
  • 多模态:模型不再只读文字,而能看图、听声、观视频,感知世界的方式越来越像人。
  • Computer Use(操作电脑):让 Agent 能直接控制鼠标键盘、操作任意软件界面,彻底打通"最后一公里"。
  • 本地化与小模型:模型越做越高效,未来强大的 AI 可在你手机上本地运行,兼顾能力与隐私。
  • 标准化协作:MCP 这类协议让工具和 Agent 即插即用,一个互联互通的"Agent 生态"正在形成。

12.3 给初学者的学习路径建议

如果你想动手实践,这是一条循序渐进的路径:

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph LR
    S1["1️⃣ 玩转 Prompt
在 Claude/ChatGPT 上
练好提问"] --> S2["2️⃣ 调用 API
用代码跑通
一次模型调用"] S2 --> S3["3️⃣ 实现工具调用
让模型调用
一个简单函数"] S3 --> S4["4️⃣ 搭个 RAG
做一个文档
问答小应用"] S4 --> S5["5️⃣ 用框架做 Agent
用 LangChain/SDK
搭一个能干活的 Agent"] S5 --> S6["6️⃣ 接入 MCP / 写 Skill
扩展能力,
沉淀方法论"] classDef stepN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 class S1,S2,S3,S4,S5,S6 stepN

12.4 结语

回到最初的那间封闭房间。

我们从一个"只会文本接龙、博学却无法行动"的大脑出发,一层一层地为它装上了会说话的嘴(Prompt)、查资料的眼(RAG)、做事的手(Tool)、循环的神经(Harness)、自主的灵魂(Agent)、记忆、万能的工具插座(MCP),以及一身随取随用的技能(Skill),最后用框架把这一切优雅地组装起来。

这条进化之路的本质,其实是一个朴素而深刻的命题:

智能的价值,不在于"知道多少",而在于"能做成多少事"。

大模型给了我们前所未有的"理解与生成"能力,而 Agent、MCP、Harness、Skill 这些技术,则是把这份能力转化为真实世界行动力的桥梁。理解了这条主线,你就握住了理解整个 AI Agent 时代的钥匙。

愿你也能亲手,为你的"大脑"装上手脚,让它真正地——会做事


附录 动手实战:从零到一,把每块拼图跑起来

正文讲的是"原理",附录讲的是"动手"。这一部分把前面每一项技术都做成可在真实环境运行的完整代码,你跟着敲一遍,就能亲手体验。最后用一个整合所有能力的"智能旅游规划 Agent"项目,走完需求设计 → 产品设计 → 技术设计 → 编码 → 测试 → 部署 → 运行的完整开发上线链路。

技术栈:Python 3.10+ ,大模型用 Anthropic 的 Claude(也可换成任意兼容的模型)。所有代码都经过结构化设计,复制下来配好环境即可运行。

附录目录

  • 附录 A 环境准备(10 分钟搞定)
  • 附录 B 如何使用工具(Function Calling 实战)
  • 附录 C 从零编写一个 Harness(Agent 循环)
  • 附录 D 从零搭建 RAG(文档问答)
  • 附录 E 从零搭建记忆系统(短期 + 长期)
  • 附录 F 开发一个 MCP Server
  • 附录 G 编写一个 Skill
  • 附录 H 从零到一组装一个完整 Agent
  • 附录 I 综合实战项目:智能旅游规划 Agent(全链路)

附录 A 环境准备(10 分钟搞定)

A.1 安装 Python 与依赖

确保你的电脑装了 Python 3.10 或更高版本(命令行运行 python --version 检查)。然后创建一个项目目录并安装依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 创建项目目录
mkdir ai-agent-lab && cd ai-agent-lab

# 2. 创建虚拟环境(隔离依赖,避免污染系统)
python -m venv venv

# 3. 激活虚拟环境
# macOS / Linux:
source venv/bin/activate
# Windows:
# venv\Scripts\activate

# 4. 安装本附录会用到的依赖
pip install anthropic # Claude 官方 SDK
pip install chromadb # 向量数据库(搭 RAG 用)
pip install mcp # MCP 官方 SDK(开发 MCP Server 用)
pip install requests # 调用 HTTP 接口(工具用)

A.2 获取并配置 API Key

  1. 访问 Anthropic 控制台 https://console.anthropic.com 注册并创建一个 API Key。
  2. 把 Key 配置成环境变量(不要硬编码进代码,更安全):
1
2
3
4
5
# macOS / Linux(也可写进 ~/.zshrc 或 ~/.bashrc 永久生效)
export ANTHROPIC_API_KEY="sk-ant-你的密钥"

# Windows (PowerShell)
# $env:ANTHROPIC_API_KEY="sk-ant-你的密钥"

A.3 跑通第一个调用(Hello, Claude)

新建文件 hello.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import os
from anthropic import Anthropic

# SDK 会自动读取环境变量 ANTHROPIC_API_KEY
client = Anthropic()

response = client.messages.create(
model="claude-haiku-4-5", # 模型 ID,可换成更强的 claude-opus-4-8 等
max_tokens=1024,
messages=[
{"role": "user", "content": "用一句话解释什么是大语言模型"}
],
)

print(response.content[0].text)

运行:

1
python hello.py

如果看到 Claude 返回的一句话解释,恭喜,你的环境已就绪,可以开始后面所有实战了。

📌 说明:后续所有示例都默认你已完成本附录的环境准备(装好依赖、配好 Key)。为节省篇幅,后面的代码不再重复 import os 等通用导入,请按需补齐。


附录 B 如何使用工具(Function Calling 实战)

目标:对应正文第五章,让 Claude 真正调用一个"查天气"工具。

B.1 完整代码

新建 tool_demo.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
from anthropic import Anthropic

client = Anthropic()

# ① 定义工具说明书(告诉模型有哪些工具、怎么用)
tools = [
{
"name": "get_weather",
"description": "查询指定城市的实时天气",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名,如:北京"}
},
"required": ["city"],
},
}
]

# ② 工具的真正实现(模型碰不到,是我们自己的代码)
def get_weather(city: str) -> str:
# 这里用假数据演示;真实场景应调用天气 API
fake_db = {"北京": "晴,25℃", "上海": "多云,27℃", "广州": "小雨,30℃"}
return fake_db.get(city, f"暂无 {city} 的天气数据")

# ③ 主流程:处理一次可能需要工具的对话
def chat(user_input: str) -> str:
messages = [{"role": "user", "content": user_input}]

# 第一次请求:模型决定要不要用工具
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
tools=tools,
messages=messages,
)

# 模型没要求用工具 → 直接返回文本
if response.stop_reason != "tool_use":
return response.content[0].text

# 模型要求用工具 → 找到工具调用块
tool_use = next(b for b in response.content if b.type == "tool_use")
print(f"[模型决定调用] {tool_use.name}({tool_use.input})")

# 真正执行工具
result = get_weather(**tool_use.input)
print(f"[工具返回] {result}")

# 第二次请求:把工具结果喂回去,让模型组织最终回答
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": result,
}],
})
final = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
return final.content[0].text

if __name__ == "__main__":
print(chat("北京今天天气怎么样?适合穿短袖吗?"))

B.2 运行与预期输出

1
python tool_demo.py
1
2
3
4
[模型决定调用] get_weather({'city': '北京'})
[工具返回] 晴,25
北京今天晴,气温 25℃,比较舒适。25℃ 穿短袖会有点凉,建议穿长袖
或搭一件薄外套更稳妥~

你观察到的关键点:模型被请求了两次——第一次输出"我要调 get_weather",第二次拿到天气结果后才组织出自然语言回答。这正是正文第五章时序图的真实复现。


附录 C 从零编写一个 Harness(Agent 循环)

目标:对应正文第六章,把"单次工具调用"升级成能自动多轮循环的 Harness,让模型能连续使用工具直到任务完成。

C.1 完整代码

新建 harness.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
from anthropic import Anthropic

client = Anthropic()

# ── 准备几个工具 ──────────────────────────────
tools = [
{
"name": "calculator",
"description": "做一次四则运算,输入一个数学表达式字符串",
"input_schema": {
"type": "object",
"properties": {"expr": {"type": "string", "description": "如 3*5+2"}},
"required": ["expr"],
},
},
{
"name": "get_price",
"description": "查询某个商品的单价(元)",
"input_schema": {
"type": "object",
"properties": {"item": {"type": "string"}},
"required": ["item"],
},
},
]

def calculator(expr: str):
return str(eval(expr)) # 演示用;生产环境勿直接 eval

def get_price(item: str):
prices = {"苹果": 5, "香蕉": 3, "牛奶": 12}
return f"{item} 单价 {prices.get(item, 0)} 元"

TOOL_IMPL = {"calculator": calculator, "get_price": get_price}

# ── 核心:Harness 循环 ────────────────────────
def harness_run(task: str, max_steps: int = 10) -> str:
messages = [{"role": "user", "content": task}]

for step in range(max_steps):
resp = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
tools=tools,
messages=messages,
)

# 模型不再需要工具 → 任务完成,返回最终文本
if resp.stop_reason != "tool_use":
return resp.content[-1].text

# 把模型的回复(含工具调用)记入历史
messages.append({"role": "assistant", "content": resp.content})

# 执行本轮所有工具调用,收集结果
tool_results = []
for block in resp.content:
if block.type == "tool_use":
print(f"[第{step+1}轮] 调用 {block.name}({block.input})")
try:
out = TOOL_IMPL[block.name](**block.input)
except Exception as e:
out = f"工具出错: {e}"
print(f" → {out}")
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(out),
})

# 把工具结果喂回,进入下一轮
messages.append({"role": "user", "content": tool_results})

return "已达最大步数,未完成"

if __name__ == "__main__":
# 这个任务需要"先查价、再计算"两步工具调用,正好考验循环
print(harness_run("我买 3 斤苹果和 2 盒牛奶,一共多少钱?"))

C.2 运行与预期输出

1
python harness.py
1
2
3
4
5
6
7
8
[第1轮] 调用 get_price({'item': '苹果'})
→ 苹果 单价 5
[第2轮] 调用 get_price({'item': '牛奶'})
→ 牛奶 单价 12
[第3轮] 调用 calculator({'expr': '3*5 + 2*12'})
39
3 斤苹果(5 元/斤)共 15 元,2 盒牛奶(12 元/盒)共 24 元,
合计 39 元。

关键点:你只给了一句话,Harness 自动驱动模型走了 3 轮(查苹果价→查牛奶价→算总和),每轮把结果喂回,直到模型认为任务完成才退出。这就是 Agent 的"想-做-看-再想"循环。


附录 D 从零搭建 RAG(文档问答)

目标:对应正文第四章,做一个"基于自有文档回答问题"的最小 RAG 系统。

D.1 完整代码

新建 rag_demo.py。这里用 chromadb(自带内置 Embedding,开箱即用)做向量库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import chromadb
from anthropic import Anthropic

client = Anthropic()

# ① 准备知识库文档(模拟公司内部制度)
documents = [
"年假政策:入职满 1 年享 5 天年假,满 3 年享 10 天,满 5 年享 15 天。",
"差旅报销:住宿费上限每晚 500 元,需提前在系统申请,凭发票报销。",
"考勤规定:迟到 15 分钟以内不扣款,超过记为旷工半天。",
"远程办公:每周最多可申请 2 天远程办公,需主管审批。",
]

# ② 建向量库并入库(离线阶段)
chroma = chromadb.Client()
collection = chroma.create_collection(name="company_docs")
collection.add(
documents=documents,
ids=[f"doc_{i}" for i in range(len(documents))],
) # chromadb 会自动把文本向量化

# ③ 在线问答:检索 + 生成
def rag_answer(question: str) -> str:
# 语义检索:取最相关的 2 段
hits = collection.query(query_texts=[question], n_results=2)
context = "\n".join(hits["documents"][0])

# 拼接 Prompt(RAG 的灵魂)——强调"没有就说没有",压制幻觉
prompt = f"""请严格根据下面的【参考资料】回答问题。
若资料中没有相关信息,就回答"未在公司文档中找到相关规定",不要编造。

【参考资料】
{context}

【问题】
{question}"""

resp = client.messages.create(
model="claude-haiku-4-5",
max_tokens=512,
messages=[{"role": "user", "content": prompt}],
)
print(f"[检索到的资料]\n{context}\n")
return resp.content[0].text

if __name__ == "__main__":
print(rag_answer("我入职两年,能休几天年假?"))
print("-" * 40)
print(rag_answer("公司食堂几点开门?")) # 知识库里没有,测试抗幻觉

D.2 运行与预期输出

1
python rag_demo.py
1
2
3
4
5
6
7
8
9
10
11
[检索到的资料]
年假政策:入职满 1 年享 5 天年假,满 3 年享 10 天,满 5 年享 15 天。
远程办公:每周最多可申请 2 天远程办公,需主管审批。

入职满 1 年不满 3 年享 5 天年假。您入职两年,属于这一区间,可享 5 天年假。
----------------------------------------
[检索到的资料]
考勤规定:迟到 15 分钟以内不扣款,超过记为旷工半天。
远程办公:每周最多可申请 2 天远程办公,需主管审批。

未在公司文档中找到相关规定。

关键点:第一个问题命中了"年假政策",模型据此准确作答;第二个问题知识库里没有,模型老实回答"未找到",没有编造——这就是 RAG 抑制幻觉的威力。


附录 E 从零搭建记忆系统(短期 + 长期)

目标:对应正文第七章,给 Agent 加上"记得住"的能力。短期记忆放在对话历史里,长期记忆持久化到本地文件(用向量库实现跨会话检索)。

E.1 完整代码

新建 memory_demo.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import json, os
import chromadb
from anthropic import Anthropic

client = Anthropic()

class MemorySystem:
def __init__(self, persist_dir="./memory_store"):
# 短期记忆:本次会话的对话历史
self.short_term = []
# 长期记忆:持久化的向量库(重启后依然存在)
self.chroma = chromadb.PersistentClient(path=persist_dir)
self.long_term = self.chroma.get_or_create_collection("long_term")

# —— 短期记忆 ——
def add_turn(self, role, content):
self.short_term.append({"role": role, "content": content})

# —— 长期记忆:写入 ——
def remember(self, fact: str):
count = self.long_term.count()
self.long_term.add(documents=[fact], ids=[f"mem_{count}"])
print(f"[已存入长期记忆] {fact}")

# —— 长期记忆:检索 ——
def recall(self, query: str, k=2):
if self.long_term.count() == 0:
return []
hits = self.long_term.query(query_texts=[query], n_results=k)
return hits["documents"][0]

def chat_with_memory(mem: MemorySystem, user_input: str) -> str:
# 1. 从长期记忆检索与当前问题相关的内容
recalled = mem.recall(user_input)
memory_context = "\n".join(recalled) if recalled else "(无相关长期记忆)"

# 2. 组装 Prompt:长期记忆 + 短期对话
system = f"你是用户的私人助理。已知关于用户的长期记忆:\n{memory_context}"
mem.add_turn("user", user_input)

resp = client.messages.create(
model="claude-haiku-4-5",
max_tokens=512,
system=system,
messages=mem.short_term,
)
reply = resp.content[0].text
mem.add_turn("assistant", reply)
return reply

if __name__ == "__main__":
mem = MemorySystem()

# 第一次会话:告诉它一些偏好,存入长期记忆
mem.remember("用户对花生过敏")
mem.remember("用户喜欢靠窗的座位")

# 之后(哪怕是重启程序后的新会话)提问,它能调取长期记忆
print(chat_with_memory(mem, "帮我推荐一道适合我的菜,并说明理由"))

E.2 运行与预期输出

1
python memory_demo.py
1
2
3
4
[已存入长期记忆] 用户对花生过敏
[已存入长期记忆] 用户喜欢靠窗的座位
向您推荐「清蒸鲈鱼」。考虑到您对花生过敏,这道菜不含任何花生及
坚果类配料,清淡健康、无过敏风险,很适合您。

关键点

  1. 长期记忆持久化到了 ./memory_store 目录,下次重启程序依然存在(这正是跨会话记忆)。
  2. 回答时,系统先从长期记忆里检索到"对花生过敏",再据此给出安全的推荐——长期记忆的底层实现,本质就是附录 D 的 RAG。

附录 F 开发一个 MCP Server

目标:对应正文第八章,亲手写一个符合 MCP 标准的 Server,对外暴露工具。写好后,任何支持 MCP 的应用(如 Claude Desktop、Claude Code)都能即插即用。

F.1 完整代码

新建 weather_mcp_server.py,用官方 mcp SDK:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# weather_mcp_server.py
from mcp.server.fastmcp import FastMCP

# 创建一个 MCP Server,命名为 "weather"
mcp = FastMCP("weather")

# 用装饰器暴露一个【工具】(可执行的动作)
@mcp.tool()
def get_weather(city: str) -> str:
"""查询指定城市的实时天气。

Args:
city: 城市名称,例如 北京、上海
"""
fake_db = {"北京": "晴,25℃", "上海": "多云,27℃", "广州": "小雨,30℃"}
return fake_db.get(city, f"暂无 {city} 的天气数据")

# 用装饰器暴露一个【资源】(可读取的数据,只读)
@mcp.resource("city://list")
def city_list() -> str:
"""返回支持查询的城市列表"""
return "支持的城市:北京、上海、广州"

if __name__ == "__main__":
# 以 stdio 方式运行,等待 MCP 客户端连接
mcp.run(transport="stdio")

F.2 本地自测(不依赖任何客户端)

MCP 官方提供了调试工具 inspector,可直接验证 Server 是否正常:

1
2
# 安装 Node 后运行官方 Inspector,连上我们的 Server
npx @modelcontextprotocol/inspector python weather_mcp_server.py

它会打开一个网页界面,你能在里面看到 get_weather 工具和 city://list 资源,并手动调用测试。

F.3 接入 Claude Desktop 真实使用

编辑 Claude Desktop 的配置文件(macOS 路径 ~/Library/Application Support/Claude/claude_desktop_config.json):

1
2
3
4
5
6
7
8
{
"mcpServers": {
"weather": {
"command": "python",
"args": ["/你的绝对路径/weather_mcp_server.py"]
}
}
}

重启 Claude Desktop 后,你直接问"北京天气怎么样",Claude 就会自动发现并调用你写的这个 MCP 工具。

关键点:你只写了一个独立的 Server 文件,没有为 Claude 写任何对接代码。这就是 MCP "一次开发、处处可用"的标准化价值——同一个 Server,Claude Desktop、Claude Code、其他任何 MCP 客户端都能直接用。


附录 G 编写一个 Skill

目标:对应正文第九章,把一套"做事方法论"打包成可复用的 Skill。我们以 Claude Code/Claude Agent SDK 的 Skill 格式为例,编写一个"周报生成"技能。

G.1 Skill 的目录结构

一个 Skill 本质是一个文件夹,核心是一个带元信息的 SKILL.md

1
2
3
4
5
6
weekly-report/                    # Skill 根目录
├── SKILL.md # 核心:元信息 + 方法论
├── templates/
│ └── report_template.md # 周报模板
└── examples/
└── sample_report.md # 一份范例周报

G.2 编写 SKILL.md

SKILL.md 的开头是 YAML 元信息(name + description 决定何时被触发加载),正文是方法论:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
---
name: weekly-report
description: >
生成结构化的工作周报。当用户提到"写周报"、"生成周报"、
"总结本周工作",或要求把零散的工作记录整理成周报时使用。
---

# 周报生成技能

## 何时使用
当用户需要把零散的工作事项整理成一份结构清晰的周报时。

## 操作步骤
1. 收集用户本周做的事项(如未提供,主动询问)。
2. 按以下四个板块归类:
- 本周完成
- 进行中
- 下周计划
- 风险与求助
3. 套用 templates/report_template.md 的格式输出。
4. 语言精炼,每条用动词开头,突出结果与数据。

## 输出要求
- 用 Markdown 格式
- 每个板块用无序列表
- 完成项要量化(如"完成 3 个接口联调"而非"做了一些接口")

## 参考
- 模板见 templates/report_template.md
- 范例见 examples/sample_report.md

G.3 配套模板文件

templates/report_template.md

1
2
3
4
5
6
7
8
9
10
11
12
13
# 本周工作周报(YYYY.MM.DD - YYYY.MM.DD)

## 一、本周完成
-

## 二、进行中
-

## 三、下周计划
-

## 四、风险与求助
-

G.4 如何触发使用

weekly-report/ 目录放进 Claude Code 的 Skills 目录(如 ~/.claude/skills/)后,当你说:

1
2
帮我写本周周报:这周我修复了登录 bug、和产品开了 3 次需求评审会、
开始做支付模块(写了一半)、下周要上线支付功能,但缺一个测试环境。

Claude 会自动匹配并加载 weekly-report 这个 Skill,然后按其中的方法论输出一份规整的四板块周报。

关键点:方法论被沉淀成文件、可复用、可分享。下次任何人、任何新会话要写周报,都不必重新教——Skill 一加载,Agent 立刻变身"周报专家"。这就是正文第九章"经验复用"的实操。


附录 H 从零到一组装一个完整 Agent

目标:把前面的拼图(工具 + Harness 循环 + 记忆)组装成一个有自主性、有记忆、会用多种工具的通用 Agent 类,为最终的旅游项目打基础。

H.1 完整代码

新建 agent.py,这是一个可复用的 Agent 框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# agent.py
from anthropic import Anthropic
import chromadb

class Agent:
def __init__(self, name, system_prompt, tools_schema, tools_impl,
memory_dir="./agent_memory"):
self.client = Anthropic()
self.name = name
self.system_prompt = system_prompt
self.tools_schema = tools_schema # 工具说明书列表
self.tools_impl = tools_impl # {工具名: 函数}
self.messages = [] # 短期记忆(对话历史)
# 长期记忆
chroma = chromadb.PersistentClient(path=memory_dir)
self.long_term = chroma.get_or_create_collection(name)

# —— 长期记忆 ——
def remember(self, fact):
self.long_term.add(documents=[fact],
ids=[f"m{self.long_term.count()}"])

def recall(self, query, k=3):
if self.long_term.count() == 0:
return ""
hits = self.long_term.query(query_texts=[query], n_results=k)
return "\n".join(hits["documents"][0])

# —— 核心:带工具循环 + 记忆的 run ——
def run(self, task, max_steps=15):
# 1. 检索长期记忆,注入 system
memory = self.recall(task)
system = self.system_prompt
if memory:
system += f"\n\n# 关于用户的长期记忆\n{memory}"

self.messages.append({"role": "user", "content": task})

# 2. Harness 循环
for step in range(max_steps):
resp = self.client.messages.create(
model="claude-haiku-4-5",
max_tokens=2048,
system=system,
tools=self.tools_schema,
messages=self.messages,
)

if resp.stop_reason != "tool_use":
final = resp.content[-1].text
self.messages.append({"role": "assistant",
"content": resp.content})
return final

self.messages.append({"role": "assistant", "content": resp.content})
results = []
for b in resp.content:
if b.type == "tool_use":
print(f" [{self.name}] 调用 {b.name}({b.input})")
try:
out = self.tools_impl[b.name](**b.input)
except Exception as e:
out = f"出错: {e}"
results.append({"type": "tool_result",
"tool_use_id": b.id, "content": str(out)})
self.messages.append({"role": "user", "content": results})

return "已达最大步数"

H.2 用它跑一个例子

新建 agent_test.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from agent import Agent

# 定义两个工具
tools_schema = [
{"name": "search_web", "description": "联网搜索信息",
"input_schema": {"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"]}},
{"name": "save_note", "description": "保存一条笔记到文件",
"input_schema": {"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"]}},
]

def search_web(query):
return f"(模拟搜索结果)关于「{query}」的最新资讯……"

def save_note(text):
with open("notes.txt", "a", encoding="utf-8") as f:
f.write(text + "\n")
return "已保存"

agent = Agent(
name="助理",
system_prompt="你是一个能搜索和记笔记的助理,自主完成用户任务。",
tools_schema=tools_schema,
tools_impl={"search_web": search_web, "save_note": save_note},
)

# 记住用户偏好
agent.remember("用户正在研究新能源汽车行业")

# 给一个需要多步的目标
print(agent.run("搜索一下行业最新动态,并把要点存成笔记"))
1
python agent_test.py
1
2
3
  [助理] 调用 search_web({'query': '新能源汽车行业最新动态'})
[助理] 调用 save_note({'text': '新能源汽车行业要点:...'})
已为您搜索了新能源汽车行业的最新动态,并将三条要点保存到了笔记中。

关键点:这个 Agent 类把记忆检索 → 规划 → 工具循环 → 记忆全串了起来,而且它注意到了长期记忆里"用户研究新能源汽车",自动把搜索词聚焦到了该行业。这个类会直接用在下面的旅游项目里。


附录 I 综合实战项目:智能旅游规划 Agent(全链路)

这是全书的"毕业设计"。我们将整合前面所有技术,做一个真实可运行的智能旅游规划 Agent,并走完需求 → 产品 → 技术 → 编码 → 测试 → 部署 → 运行的完整开发上线链路。

I.1 需求设计

一句话需求:用户说一句"我想去某地玩几天",Agent 自动生成一份包含天气、行程、预算的完整旅游计划。

用户故事(User Story)

编号 作为 我想要 以便
US-1 用户 输入目的地和天数就能拿到行程 不用自己查攻略
US-2 用户 计划里包含当地天气 决定带什么衣服
US-3 用户 计划里有每天的景点安排 玩得不重复、不遗漏
US-4 用户 计划里有预算估算 提前准备钱
US-5 用户 它记得我的偏好(如不爱爬山) 行程更贴合我

功能性需求

  • 解析用户输入(目的地、天数、偏好)
  • 查询目的地天气
  • 推荐景点并编排每日行程
  • 估算预算(交通+住宿+门票+餐饮)
  • 记住用户偏好,跨会话生效

非功能性需求:响应时间 < 30 秒;调用失败要有兜底;Key 不能泄露。

I.2 产品设计

核心流程图(用户视角)

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    U["用户输入:
'我想去成都玩 3 天,预算中等,不爱爬山'"] --> P["Agent 解析需求"] P --> W["查天气"] P --> S["推荐景点"] W --> Plan["编排每日行程"] S --> Plan Plan --> B["估算预算"] B --> Out["输出完整旅游计划"] Out --> M["记住偏好:不爱爬山"] classDef uN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 classDef pN fill:#ede7f6,stroke:#5e35b1,stroke-width:2px,color:#311b92 classDef tN fill:#e0f7fa,stroke:#00acc1,stroke-width:2px,color:#006064 classDef oN fill:#e8f5e9,stroke:#43a047,stroke-width:2px,color:#1b5e20 class U uN class P,Plan pN class W,S,B tN class Out,M oN

输出样例(产品期望的最终形态)

1
2
3
4
5
6
7
8
9
10
🗺️ 成都 3 日游计划(预算中等 · 已避开爬山项目)

🌤️ 天气:晴转多云,18-26℃,建议带薄外套

📅 Day 1:宽窄巷子 → 人民公园喝茶 → 锦里夜游
📅 Day 2:成都大熊猫繁育研究基地 → 春熙路购物
📅 Day 3:武侯祠 → 杜甫草堂 → 返程

💰 预算估算(单人):约 1500
交通 300 / 住宿 600 / 门票 250 / 餐饮 350

I.3 技术设计

架构图

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph TD
    User["用户"] --> Agent["旅游规划 Agent
(复用附录 H 的 Agent 类)"] subgraph Core["Agent 内核"] Brain["🧠 Claude 大脑"] Loop["🔄 Harness 循环"] Mem["💾 记忆(用户偏好)"] end Agent --> Core Loop --> T1["🔧 get_weather
查天气"] Loop --> T2["🔧 get_attractions
查景点"] Loop --> T3["🔧 estimate_budget
算预算"] Mem --> DB[("向量库
偏好存储")] classDef uN fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 classDef aN fill:#ede7f6,stroke:#5e35b1,stroke-width:3px,color:#311b92 classDef cN fill:#e0f2f1,stroke:#00897b,stroke-width:2px,color:#004d40 classDef tN fill:#e0f7fa,stroke:#00acc1,stroke-width:2px,color:#006064 class User uN class Agent,Brain aN class Loop,Mem cN class T1,T2,T3,DB tN

技术选型

  • Agent 框架:复用附录 H 的 Agent
  • 大模型:Claude(claude-haiku-4-5
  • 工具:3 个自定义工具(天气/景点/预算)
  • 记忆:chromadb 持久化
  • 部署:用 FastAPI 包成 Web 服务

项目结构

1
2
3
4
5
6
travel-agent/
├── tools.py # 三个工具的实现
├── travel_agent.py # Agent 主逻辑
├── test_agent.py # 测试
├── app.py # FastAPI Web 服务(部署用)
└── requirements.txt # 依赖清单

I.4 代码编写

第 1 步:requirements.txt

1
2
3
4
anthropic
chromadb
fastapi
uvicorn

第 2 步:tools.py(三个工具)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# tools.py
# 演示用假数据;真实项目应替换为高德/携程等真实 API

def get_weather(city: str) -> str:
"""查询城市未来几天天气"""
db = {
"成都": "晴转多云,18-26℃,建议带薄外套",
"三亚": "晴,26-32℃,注意防晒",
"哈尔滨": "雪,-15-(-5)℃,需厚羽绒服",
}
return db.get(city, f"{city}:晴,20-28℃")

def get_attractions(city: str, avoid: str = "") -> str:
"""查询城市热门景点,avoid 为需规避的类型(如'爬山')"""
db = {
"成都": ["宽窄巷子", "大熊猫基地", "锦里", "武侯祠",
"杜甫草堂", "青城山(爬山)", "都江堰"],
"三亚": ["亚龙湾", "天涯海角", "蜈支洲岛", "南山寺"],
}
spots = db.get(city, ["市中心", "当地博物馆", "特色街区"])
if avoid:
spots = [s for s in spots if avoid not in s]
return "、".join(spots)

def estimate_budget(days: int, level: str = "中等") -> str:
"""根据天数和消费档次估算预算"""
daily = {"经济": 300, "中等": 500, "豪华": 1000}.get(level, 500)
total = daily * days
return (f"约 {total} 元({level}档,{days}天):"
f"交通 {int(total*0.2)} / 住宿 {int(total*0.4)} / "
f"门票 {int(total*0.17)} / 餐饮 {int(total*0.23)}")

第 3 步:travel_agent.py(主逻辑,复用附录 H 的 Agent 类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# travel_agent.py
from agent import Agent # 复用附录 H 写好的 Agent 类
import tools

TOOLS_SCHEMA = [
{"name": "get_weather", "description": "查询目的地天气",
"input_schema": {"type": "object",
"properties": {"city": {"type": "string"}}, "required": ["city"]}},
{"name": "get_attractions", "description": "查询景点,可指定要规避的类型",
"input_schema": {"type": "object",
"properties": {"city": {"type": "string"},
"avoid": {"type": "string"}}, "required": ["city"]}},
{"name": "estimate_budget", "description": "估算旅行预算",
"input_schema": {"type": "object",
"properties": {"days": {"type": "integer"},
"level": {"type": "string"}}, "required": ["days"]}},
]

SYSTEM = """你是一位专业的旅游规划师。根据用户的目的地、天数和偏好,
必须依次:①查天气 ②查景点(结合用户偏好规避不喜欢的类型)
③估算预算,最后编排出图文清晰的每日行程计划。
输出用 emoji 分区,包含天气、每日行程、预算四个部分。"""

def build_travel_agent():
return Agent(
name="travel_planner",
system_prompt=SYSTEM,
tools_schema=TOOLS_SCHEMA,
tools_impl={
"get_weather": tools.get_weather,
"get_attractions": tools.get_attractions,
"estimate_budget": tools.estimate_budget,
},
)

if __name__ == "__main__":
agent = build_travel_agent()
# 记住用户偏好(长期记忆,跨会话生效)
agent.remember("用户不喜欢爬山类景点")
result = agent.run("我想去成都玩 3 天,预算中等")
print("\n" + "=" * 50)
print(result)

I.5 测试

第 4 步:test_agent.py(自动化测试)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# test_agent.py
import tools

def test_weather():
assert "℃" in tools.get_weather("成都")

def test_attractions_avoid():
# 规避"爬山"后,青城山(爬山)应被过滤掉
result = tools.get_attractions("成都", avoid="爬山")
assert "青城山" not in result
assert "宽窄巷子" in result

def test_budget():
result = tools.estimate_budget(3, "中等")
assert "1500" in result # 500 * 3

if __name__ == "__main__":
test_weather(); test_attractions_avoid(); test_budget()
print("✅ 所有测试通过")

运行测试:

1
2
python test_agent.py
# ✅ 所有测试通过

跑通后,运行完整 Agent:

1
python travel_agent.py
1
2
3
4
5
6
7
8
9
10
11
12
13
  [travel_planner] 调用 get_weather({'city': '成都'})
[travel_planner] 调用 get_attractions({'city': '成都', 'avoid': '爬山'})
[travel_planner] 调用 estimate_budget({'days': 3, 'level': '中等'})
==================================================
🗺️ 成都 3 日游计划(预算中等 · 已避开爬山项目)

🌤️ 天气:晴转多云,18-26℃,建议带薄外套

📅 Day 1:宽窄巷子 → 锦里 → 武侯祠
📅 Day 2:成都大熊猫繁育研究基地 → 杜甫草堂
📅 Day 3:都江堰 → 返程

💰 预算:约 1500 元(交通 300 / 住宿 600 / 门票 250 / 餐饮 350

注意:行程里自动避开了"青城山(爬山)"——因为长期记忆里存了"用户不喜欢爬山"。各项技术在此协同生效。

I.6 部署上线

第 5 步:app.py(用 FastAPI 包成 Web 服务)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# app.py
from fastapi import FastAPI
from pydantic import BaseModel
from travel_agent import build_travel_agent

app = FastAPI(title="智能旅游规划 Agent")

class TravelRequest(BaseModel):
message: str # 用户的自然语言请求
preferences: list[str] = [] # 可选:用户偏好

@app.post("/plan")
def make_plan(req: TravelRequest):
agent = build_travel_agent()
for p in req.preferences: # 写入长期记忆
agent.remember(p)
plan = agent.run(req.message)
return {"plan": plan}

@app.get("/health")
def health():
return {"status": "ok"}

本地启动服务

1
uvicorn app:app --host 0.0.0.0 --port 8000

测试接口

1
2
3
curl -X POST http://localhost:8000/plan \
-H "Content-Type: application/json" \
-d '{"message": "我想去三亚玩 4 天,预算豪华", "preferences": ["喜欢海边", "怕晒"]}'

返回:

1
2
3
{
"plan": "🗺️ 三亚 4 日游计划(豪华 · 注重防晒)\n🌤️ 天气:晴,26-32℃..."
}

生产部署(Docker)——新建 Dockerfile

1
2
3
4
5
6
7
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

构建并运行容器(注意通过环境变量传入 Key,不写进镜像):

1
2
3
4
docker build -t travel-agent .
docker run -d -p 8000:8000 \
-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
--name travel-agent travel-agent

至此,你的旅游规划 Agent 已作为一个标准 Web 服务上线,可被任何前端、小程序、App 调用。

I.7 全链路回顾

我们完整走过了一个 AI 产品的生命周期:

%%{init: {'theme':'base', 'flowchart':{'useMaxWidth':true,'htmlLabels':true}, 'sequence':{'useMaxWidth':true}}}%%
graph LR
    R["①需求设计
User Story"] --> P["②产品设计
流程+输出样例"] P --> T["③技术设计
架构+选型"] T --> C["④编码
tools/agent/app"] C --> Test["⑤测试
单元测试"] Test --> D["⑥部署
FastAPI+Docker"] D --> Run["⑦运行
对外提供服务"] classDef s1 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#0d47a1 classDef s2 fill:#ede7f6,stroke:#5e35b1,stroke-width:2px,color:#311b92 classDef s3 fill:#e0f7fa,stroke:#00acc1,stroke-width:2px,color:#006064 classDef s4 fill:#e8f5e9,stroke:#43a047,stroke-width:2px,color:#1b5e20 class R,P s1 class T,C s2 class Test,D s3 class Run s4

这个项目用到了本书的哪些技术?

本书技术 在项目中的体现
大模型(第一/二章) Claude 作为 Agent 大脑
Prompt 工程(第三章) 精心设计的 SYSTEM 提示词
RAG(第四章) 长期记忆的底层检索机制
Tool 工具调用(第五章) 天气/景点/预算三个工具
Harness(第六章) Agent 类里的多轮循环
Agent(第七章) 整个项目的核心形态
记忆系统(第七章) 记住"不爱爬山"等偏好
MCP(第八章) 工具可进一步改造为 MCP Server 对外复用
Skill(第九章) SYSTEM 可沉淀为"旅游规划 Skill"
框架(第十章) FastAPI 部署、Agent 类即简易框架

🎓 毕业了! 从附录 A 配好第一个 API Key,到附录 I 上线一个真实的 Agent 服务,你已经亲手把本书所有技术拼图都跑通了一遍。现在,去构建属于你自己的智能体吧。


(全文完)


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !