快速补全你的认知差——一次搞懂大模型应用开发的核心概念
面向传统开发者的 LLM 应用开发核心概念入门。从 LLM 的本质出发,沿 Token、Prompt、Context、Memory、Function Calling、Agent、Skill、MCP、RAG 逐层展开,每个概念从上一个概念中自然生长,帮助开发者快速建立完整的大模型应用认知链路。
写在前面
如果你是一个传统开发者——写了几年甚至十几年的代码,习惯了 REST API、数据库、消息队列这些确定性的东西——然后某一天,你决定开始接触大模型。
你会发现,满世界都在讲 LLM、Token、Prompt、Agent、RAG、MCP……每一个词你大概都见过,但串不成一条线。感觉就像站在一间堆满零件的房间里,知道每个零件都有用,但不知道怎么组装。
这篇文章就是为你写的。
我会从最底层的 LLM 开始,沿着一条清晰的链路,把大模型应用开发中最核心的概念逐个讲清楚。每个概念都不是孤立的——它是从上一个概念中生长出来的,是你遇到某个具体问题之后,自然需要的解决方案。
读完之后,你不需要再到处搜零散的科普文章了。这些概念之间的关系,会形成一条完整的认知链。
一、LLM:一切的起点
LLM,Large Language Model,大语言模型。
我们先忘掉所有花哨的定义。作为一个程序员,你可以用你最熟悉的方式去理解它:
LLM 就是一个函数。
output = LLM(input)
你给它一段文本作为输入,它经过内部的推理过程,返回一段新的文本作为输出。就是这么简单。
当然,这个"函数"的内部极其复杂——它包含了数千亿个参数、经过了海量数据的训练、运行在成百上千张 GPU 上。但对调用它的人来说,你不需要关心这些。你只需要知道:
你给它文本,它返回文本。
这是理解后续所有概念的根基。请牢牢记住这一点,因为后面我们会反复回到这个事实——LLM 本身只能处理文本、生成文本,它没有手,没有脚,不能打开浏览器,不能调用 API,不能帮你操作任何东西。它唯一的本领就是"读懂你给它的文字,然后生成一段新的文字作为回答"。
这一点后面讲 Function Calling 和 Agent 的时候会变得极其重要,现在先埋下这颗种子。
二、Token:LLM 眼中的文字
我们说 LLM 处理的是"文本",但准确地说,它处理的不是我们肉眼看到的字符,而是 Token。
Token 是什么呢?
LLM 并不认识"你好世界"这四个字。在它看来,这四个字首先会被切分成若干个 Token,然后它处理的是这些 Token。一个 Token 可能是一个汉字、一个英文单词、一个词缀,甚至是一个标点符号。
举个例子:
输入:"今天天气真好"
可能被切分为这样的 Token 序列:
["今", "天", "天气", "真", "好"]
英文也一样:
输入:"I love programming"
可能被切分为:
["I", " love", " programming"]
注意,Token 的切分方式不是按字来的,而是按"子词"来的。常用的短词可能是一个完整的 Token,而生僻的长词可能会被拆成多个 Token。具体的切分方式取决于模型使用的分词器(Tokenizer),不同的模型用的分词器也不同。
为什么你要关心 Token?
因为 Token 是大模型的计费单位和能力边界。
- 你调用 OpenAI 的 API,收费是按 Token 数量算的。
- 你发送的输入和收到的输出,都是按 Token 计量的。
- 而且,每个模型都有一个 Token 上限,叫做上下文窗口(Context Window)。比如某个模型的上下文窗口是 128K Token,意味着你输入的全部内容加起来,不能超过这个数。
你可以把 Token 理解为大模型世界的"流量"——就像手机流量一样,用多少算多少,而且有上限。只不过手机流量是按字节算的,大模型是按 Token 算的。
到这里,我们知道了两件事:第一,LLM 处理的是 Token;第二,Token 有上限。这两点会直接影响后面所有的设计决策。
三、Prompt:你和 LLM 说话的方式
知道了 LLM 是一个函数,输入是文本、输出是文本。那问题来了:
你输入什么样的文本,会直接影响输出的质量。
这段输入文本,就叫做 Prompt,提示词。
Prompt 是你和 LLM 之间的唯一沟通方式。LLM 看不到你的表情,听不到你的语气,它只看你给它写了什么。所以你怎么写、写什么、写多写少,对结果的影响是巨大的。
举个最简单的例子:
Prompt A:
"翻译成英文:今天天气真好"
Prompt B:
"你是一位专业翻译,请将以下中文翻译成地道的英文,保持口语化风格:
今天天气真好"
同一个问题,两个 Prompt,得到的回答质量可能完全不同。Prompt A 可能给你一个平淡的 "The weather is nice today",而 Prompt B 更可能给你 "It's a beautiful day" 或者 "Gorgeous day out, isn't it?"。
这就是 Prompt 的力量。
对于开发者来说,写 Prompt 就像是在写一种新的代码——它不是传统意义上的编程语言,但它确实在"编程"。你在用自然语言告诉 LLM 该怎么做,而 LLM 会严格按照你的指示去推理。写得越清晰、越具体、越有结构,输出就越可控。
Prompt 的几种常见结构:
- 角色设定:告诉 LLM 它是谁——"你是一个资深的 Go 语言工程师"。
- 任务描述:告诉它要做什么——"请帮我审查以下代码,找出潜在的并发安全问题"。
- 输入数据:把需要处理的内容放进去——具体代码。
- 输出格式:告诉它怎么回答——"请用 Markdown 表格列出问题、位置和建议"。
你是一个资深的 Go 语言工程师。
请帮我审查以下代码,找出潜在的并发安全问题。
请用 Markdown 表格列出:问题描述、代码位置、修改建议。
---代码开始---
var count int
func increment() {
count++
}
---代码结束---
对于 LLM 来说,这段 Prompt 和一段 Go 代码没有本质区别——都是 Token 序列。但它能从中理解你的意图,并给出结构化的回应。
Prompt 不是随便写的,Prompt 是需要设计的。 后面会讲到,Prompt 的设计会直接影响到 Context 的消耗,进而影响到你整个应用的架构。
四、Context:对话的生命线
有了 Prompt 的概念,我们往前走一步。
当你和大模型开始对话的时候,情况就变了。你不再是发一个 Prompt 然后得到一个回答,而是要连续多轮对话。
比如:
第1轮:
用户:你好,我是一个后端开发,想学大模型。
助手:你好!很高兴你对大模型感兴趣。作为一个后端开发者……
第2轮:
用户:我应该从哪里开始?
助手:建议你先了解 Transformer 架构……
第3轮:
用户:Transformer 和 RNN 有什么区别?
助手:RNN 是按顺序处理的……
注意看第3轮,助手能回答"Transformer 和 RNN 的区别",是因为它知道你在"学习大模型"这个语境下问的。如果你突然问"今天几号",它也不会觉得突兀——但它能理解你之前的对话脉络,是因为它能看到前面所有的对话内容。
这个"前面所有的对话内容",就是 Context,上下文。
实际上,对于 LLM 来说,根本不存在什么"多轮对话"的概念。它看到的就是一整段文本:
[系统提示]
你是一个耐心的技术导师。
[用户]
你好,我是一个后端开发,想学大模型。
[助手]
你好!很高兴你对大模型感兴趣。作为一个后端开发者……
[用户]
我应该从哪里开始?
[助手]
建议你先了解 Transformer 架构……
[用户]
Transformer 和 RNN 有什么区别?
没错,每次你发起新一轮对话时,前面所有轮次的内容都会被拼接在一起,作为一个整体发送给 LLM。LLM 从头到尾阅读整个 Context,然后生成回答。
所谓的"多轮对话",其实是客户端把历史记录拼成了一段长长的文本,每次一起发过去。 LLM 的记忆并不是像数据库一样存储在某个地方,它是每次都重新阅读所有的历史记录。
所以 Context 就等于:系统提示 + 所有历史对话 + 当前用户输入。
这也是为什么 Token 上限如此重要——你的对话轮次越多,Context 就越大,占用的 Token 就越多。终有一天,你会撑爆这个上限。
而且,Context 越大,有两个直接后果:
- 费用越高:因为你每次都要把所有历史发过去,Token 数一直在增长。
- 质量可能下降:虽然技术在进步,但在极长的 Context 中,模型对中间部分内容的关注度确实可能下降(学术上叫做 "Lost in the Middle" 问题)。
那么问题来了——如果我确实需要长时间的对话,Context 撑爆了怎么办?
这就引出了下一个概念。
五、Memory:给大模型装上记忆
前面说了,LLM 没有真正的记忆。每次对话它都是把所有历史从头读一遍。那如果对话轮次太多,历史文本超出了上下文窗口的限制,怎么办?
答案是:你自己来管理这段历史。
这就是 Memory,记忆。Memory 不是 LLM 自带的能力,而是你在应用层实现的一套策略。它的核心目标只有一个:
在有限的上下文窗口里,尽可能保留有价值的信息。
想象一个场景:你和助手已经聊了 50 轮,总 Token 数已经快撑爆上下文窗口了。你不可能把这 50 轮全部塞进去。但你也不希望助手突然"失忆"——它应该还记得你的身份、你在做什么、之前讨论过哪些关键结论。
怎么办?
常见的记忆策略有几种:
1. 滑动窗口(Sliding Window)
最简单粗暴的方式:只保留最近 N 轮对话,更早的直接丢掉。
原始对话:[第1轮] [第2轮] [第3轮] ... [第48轮] [第49轮] [第50轮]
保留:[第46轮] [第47轮] [第48轮] [第49轮] [第50轮]
优点是实现简单、Token 消耗可控。缺点是早期对话中的重要信息会丢失。如果用户在第 3 轮说"我是一个 Java 后端开发",到了第 50 轮这个信息已经没了。
2. 摘要压缩(Summary)
定期把早期的对话交给 LLM 进行总结,然后用总结来替代原文。
原始对话(第1-30轮)→ 交给 LLM 压缩为一段摘要:
"用户是一个 Java 后端开发,正在学习大模型应用开发,
之前讨论了 Token、Prompt、Context 的概念,用户对
Transformer 架构有基本了解。"
[摘要] [第31轮] [第32轮] ... [第50轮]
这样就用很短的 Token 保留了早期对话的精华。代价是细节会丢失,但核心脉络还在。
3. 关键信息提取
比摘要更精细的做法:把特定的重要信息提取出来,以结构化的方式存储。
用户画像:
- 身份:Java 后端开发,5年经验
- 目标:学习大模型应用开发
- 偏好:喜欢通过代码示例学习
当前状态:
- 已了解:Token、Prompt、Context
- 待学习:Memory、Function Calling、Agent
每次对话时,把这些结构化信息注入到 Context 中。这样即使聊了几百轮,关键信息也不会丢。
4. 持久化存储
把记忆存到数据库或者文件中,下次开新对话时加载回来。这就像人类的"长期记忆"——短期对话靠 Context,长期信息靠外部存储。
在实际开发中,通常会混合使用以上几种策略。比如最近几轮用滑动窗口保留原文,再往前用摘要压缩,核心信息用结构化方式持久化。
Memory 是你在应用层设计的东西,不是 LLM 的能力。 这一点很重要。LLM 本身是无状态的——每次调用它,它都是一个全新的、什么都不记得的"大脑"。你看到的那些能记住你、能持续对话的 AI 产品,都是在应用层用 Memory 策略精心维护出来的效果。
到这里,我们已经有了让大模型进行长时间、高质量对话的全部基础:用 Prompt 指导 LLM 怎么做,用 Context 维护对话的连续性,用 Memory 在上下文窗口的限制下保留关键信息。
有了这些,你就能做出一个聊天机器人了。
但接下来,一个根本性的问题出现了。
六、Function Calling(Tools):让大模型长出"手"
我们再次回到那个最根本的事实:
LLM 只能处理文本,生成文本。它没有能力去操作任何东西。
这句话在前面讲 LLM 的时候埋下的种子,现在该开花结果了。
举个具体场景:你让大模型帮你发一条推文。
大模型能做什么?它能帮你写出推文内容——这没问题,文本生成是它的强项。但是,它能帮你登录推特、点击发布按钮、真正把推文发出去吗?
不能。
因为它没有手。它只能给你输出一段文字:"推文已写好,请访问 https://twitter.com 发布。"
再比如:你问大模型"北京今天天气怎么样?"它能回答吗?它的训练数据截止到某个时间点,它不知道今天北京的天气。它只能告诉你"我没有实时信息"。
大模型很聪明,但它被锁在了一个盒子里——这个盒子只有输入和输出两个端口,而且只接受文本。
怎么办?
答案是:让大模型在需要的时候,告诉我们它想做什么,然后由我们(程序员)的代码去帮它完成。
这就是 Function Calling 的核心思想。在最近的规范中,它也被称为 Tools(工具)。
让我用一个完整的例子来解释这个过程:
假设你正在开发一个助手,它需要具备查询天气的能力。你作为开发者,先写好一个天气查询函数:
def get_weather(city: str) -> str:
# 调用天气 API,获取实时天气数据
response = requests.get(f"https://weather-api.com/{city}")
return response.json()
然后,你需要告诉大模型:你有这个能力。你怎么告诉它呢?还是用 Prompt——但这次不是普通的 Prompt,而是一段工具描述:
{
"name": "get_weather",
"description": "查询指定城市的实时天气信息",
"parameters": {
"city": {
"type": "string",
"description": "城市名称,如"北京"、"上海""
}
}
}
你把这段描述随着 Prompt 一起发给大模型。大模型就知道了:"哦,我有一个叫 get_weather 的工具,可以查天气。"
然后用户问:"北京今天天气怎么样?"
大模型推理之后,它的输出不再是自然语言的回答,而是一段结构化的文本:
{
"tool_call": {
"name": "get_weather",
"arguments": {
"city": "北京"
}
}
}
注意,大模型没有真的去查天气。它只是输出了一段文本,告诉我们:"我想调用 get_weather 这个工具,参数是"北京"。"
接下来,轮到你——程序员——出场了。你的程序拿到这段结构化文本,解析出来,然后执行真正的 get_weather("北京") 函数,拿到真实的天气数据,再把结果发回给大模型:
[工具调用结果]
北京今天:晴,25°C,湿度 40%,东北风 3 级
大模型拿到这个结果,再用自然语言组织回答:
北京今天天气不错,晴天,气温 25°C,湿度 40%,吹东北风。
适合外出,建议做好防晒哦。
整个过程是这样的:
用户:"北京今天天气怎么样?"
↓
[你的程序] 把用户消息 + 工具描述 发给 LLM
↓
[LLM] 推理后输出:我想调用 get_weather("北京")
↓
[你的程序] 解析 LLM 的输出,执行 get_weather("北京")
↓
[你的程序] 拿到天气结果,发回给 LLM
↓
[LLM] 将天气结果组织成自然语言回复
↓
用户:"北京今天晴天,25°C,适合外出。"
看明白了吗?大模型始终在做它最擅长的事——理解和生成文本。 真正执行操作的,是你的代码。Function Calling 只是建立了一种"沟通协议",让大模型能够以一种结构化的方式表达"我想做什么",然后由你的程序去真正执行。
这就像一个极其聪明的大脑,装在一个没有身体的罐子里。Function Calling 就是给这个大脑接上了一双虚拟的手——手是你的代码提供的,大脑只需要决定"我要用这双手做什么"。
到此为止,我们已经突破了大模型的物理限制。 有了 Function Calling / Tools,大模型终于不再只能聊天了。它可以查天气、查数据库、调 API、发邮件、操作文件系统——只要你在代码里实现了这些能力,再告诉大模型怎么使用,它就能在需要的时候调用它们。
但新的问题随之而来:如果一个任务需要连续调用多个工具,而且每一步都需要根据上一步的结果来决定下一步怎么做——这时候该怎么办?
这就引出了一个更大的概念。
七、Agent:大模型自己决定"下一步做什么"
有了 Function Calling,大模型可以调用工具了。但到目前为止,我们讨论的都是"一问一答"的模式:
- 用户问一个问题
- 大模型调用一次工具
- 拿到结果,回答用户
然而,现实世界中很多任务不是一步就能完成的。比如:
"帮我查一下北京和上海今天的天气,然后比较一下哪个城市更适合户外活动,再推荐一个北京附近的周末出游地点。"
这个任务涉及多个步骤:
- 查北京天气
- 查上海天气
- 比较两座城市的天气
- 推荐出游地点(可能需要搜索)
如果每一步都需要用户手动触发,那体验就太差了。理想情况下,大模型应该自己决定需要调用哪些工具、以什么顺序调用、什么时候停止,最终把完整的结果交给用户。
这就是 Agent。
Agent 的核心定义非常简单:
Agent = LLM + Tools + 自主循环推理
什么意思呢?传统的一问一答是这样的:
输入 → LLM 推理 → 输出(结束)
而 Agent 是这样的:
输入 → LLM 推理 → [需要工具 A] → 执行 → 拿到结果 → LLM 再推理
→ [需要工具 B] → 执行 → 拿到结果 → LLM 再推理
→ [不需要工具了] → 输出最终回答(结束)
看到区别了吗?Agent 有一个循环:LLM 不断推理、不断决定下一步、不断执行工具,直到它认为任务完成为止。LLM 自己就是决策者——它不需要人来告诉它"接下来该做什么",它自己判断。
用代码来表示,Agent 的核心循环大概是这样的:
def run_agent(user_input, tools):
messages = [{"role": "user", "content": user_input}]
while True:
# 让 LLM 推理
response = llm.chat(messages, tools=tools)
# 如果 LLM 认为不需要调用任何工具,任务完成
if response.finish_reason == "stop":
return response.content
# 如果 LLM 想调用工具
for tool_call in response.tool_calls:
result = execute_tool(tool_call.name, tool_call.arguments)
messages.append({"role": "tool", "content": result})
# 继续循环,让 LLM 根据工具结果继续推理
就是一个 while True 循环。LLM 每次推理后,要么决定继续调用工具,要么决定输出最终回答。这个循环什么时候结束、中间经历多少步,完全是 LLM 自己决定的。
这就是 Agent 最强大的地方,也是最让传统程序员不适应的地方。传统编程中,流程是由开发者控制的——if、else、for、while,每一行代码的执行路径都是你写的。但 Agent 的流程是由 LLM 决定的。你写的只是框架(那个 while 循环),具体的"走哪条路"是 LLM 在运行时根据情况推理出来的。
用一个比喻来说:
- 传统程序像一条铁轨——火车只能沿着你铺设的轨道走。
- Agent 像一个有导航的越野车——你告诉它目的地,它自己选择路线,遇到障碍自己绕行。
以一个写代码的 Agent 为例,它可能配备这些工具:
read_file:读取文件内容write_file:写入文件execute_command:执行终端命令search_code:在代码库中搜索
当你给它一个任务:"帮我在这个项目里添加一个用户登录功能",Agent 可能的执行过程是:
1. [调用 read_file] 读取项目结构 → 了解项目是 React + Express
2. [调用 search_code] 搜索现有的认证相关代码 → 发现没有任何认证
3. [LLM 推理] 决定需要创建 JWT 认证中间件
4. [调用 write_file] 写入 authMiddleware.js
5. [调用 write_file] 写入 login 路由
6. [调用 write_file] 修改前端添加登录页面
7. [调用 execute_command] 运行 npm install jsonwebtoken
8. [调用 execute_command] 启动服务测试
9. [LLM 推理] 服务启动成功,任务完成
10. [输出回答] "已添加用户登录功能,使用 JWT 认证……"
整个过程没有人干预,Agent 自主完成了从分析、编码、安装依赖到测试的全流程。每一步该做什么,都是 LLM 根据上一步的结果推理出来的。
这就是 Agent 的魅力。它把大模型从一个"问答机器"变成了一个"能独立工作的数字员工"。
八、Skill:聪明的上下文管理
Agent 能力越强,问题也来了。
假设你正在开发一个全能型 Agent,它需要具备以下能力:
- 写前端代码(React、Vue)
- 写后端代码(Node.js、Python)
- 数据库操作(MySQL、MongoDB)
- DevOps 部署(Docker、K8s)
- 安全审查(SQL 注入、XSS 检测)
- ……
如果把这些领域的知识、规范、最佳实践全部写进 Prompt 里,会发生什么?
Context 会爆掉。
光是 React 的编码规范可能就有几千字,再加上 Python 的、数据库的、Docker 的……还没开始对话,Prompt 本身就已经占满了上下文窗口。而且这些信息里,绝大多数在当前对话中根本用不到——你跟 Agent 聊前端的时候,Docker 的规范完全不需要出现。
这就是问题所在:
你希望 Agent 越智能越好,但越智能意味着注入的 Prompt 越多,上下文消耗越大。
怎么办?
我们需要一种机制:不是一开始就告诉 Agent 所有能力,而是在它需要某项能力的时候,再把相关知识注入进去。
这就是 Skill。
Skill 可以理解为按需加载的 Prompt 片段。
让我用一个生活化的比喻来解释 Prompt 和 Skill 的区别:
- Prompt 就像是一个人的简历——你把所有的能力、经历、背景都写在上面,每次见人都带着。
- Skill 就像是一个人的抽屉——各种工具和资料分门别类放在不同的抽屉里,需要用哪个,打开哪个。
Prompt 是静态的、始终存在于上下文中的——从对话开始到结束,它一直在。
Skill 是动态的、按需注入到上下文中的——只有当对话涉及某个领域时,才会把对应的 Skill 加载进来。
举个例子,你的 Agent 配备了这些 Skill:
Skills:
├── react-coding-skill.md (React 编码规范,3000 字)
├── python-backend-skill.md (Python 后端规范,2500 字)
├── mysql-skill.md (MySQL 最佳实践,2000 字)
├── docker-skill.md (Docker 部署规范,1500 字)
└── security-review-skill.md (安全审查清单,1800 字)
如果全部注入,大约 10800 字,占用大量 Token。但实际对话中:
- 当用户说"帮我写一个 React 组件"时,Agent 感知到需要前端能力 → 注入
react-coding-skill.md - 当用户说"帮我写一个数据库查询"时,Agent 感知到需要数据库能力 → 注入
mysql-skill.md
通常情况下,每次对话只需要加载 1-2 个 Skill,上下文消耗大幅减少。
Skill 的实现方式,通常有两种:
- 基于关键词/意图识别:在应用层分析用户的输入,匹配到相关 Skill,然后注入。
- 由 LLM 自己决定:告诉 LLM 有哪些 Skill 可用(只注入 Skill 的名称和简介),让 LLM 在需要时请求加载特定 Skill。这其实和 Function Calling 的思路类似——只不过调用的不是工具函数,而是一段知识文档。
第二种方式更灵活,也更符合 Agent 的"自主决策"理念:
// LLM 的输出
{
"skill_call": {
"name": "react-coding-skill",
"reason": "用户需要编写 React 组件,需要加载 React 编码规范"
}
}
你的程序看到这段输出,把 react-coding-skill.md 的内容注入到上下文中,然后让 LLM 继续推理。
Skill 解决了"能力越强、上下文越臃肿"的矛盾。 它让 Agent 可以承载几乎无限的能力,而不需要在每次对话中都付出全部的上下文代价。
到这里,我们已经具备了构建高质量智能体的全部核心概念:
- LLM 负责推理
- Token 是计算的最小单位
- Prompt 指导 LLM 的行为
- Context 维护对话的连续性
- Memory 管理长期记忆
- Tools 让 LLM 具备操作外部世界的能力
- Agent 让 LLM 自主决策和循环推理
- Skill 按需注入领域知识,节约上下文
但是,还有一个问题一直困扰着我们。
九、MCP:给 Tools 定一个标准
到目前为止,我们讲的 Tools 都有一个共同特点:每个工具都需要你手动描述、手动实现。
假设你要给 Agent 接入 10 个工具,你需要:
- 为每个工具编写描述(名称、参数、用途)
- 为每个工具编写执行逻辑(具体的函数代码)
- 在不同的 Agent 中重复以上工作
如果你要开发一个新的 Agent,需要用到其中 5 个已有的工具,你还是得把这 5 个工具的描述和实现再接入一遍。如果你要和别人共享工具,更麻烦——你需要把代码发给别人,别人还得改成适配自己项目的格式。
每次接入一个工具,都像是在重新造一个接口。没有标准,没有复用。
这就是 MCP 要解决的问题。
MCP(Model Context Protocol,模型上下文协议) 是 Anthropic 提出的一个开放协议。准确地说,它不是一个框架、不是一个库,而是一个协议——就像 HTTP 是网页通信的协议、WebSocket 是实时通信的协议一样,MCP 是 AI 应用与外部工具/数据源之间的通信协议。
MCP 和 Tools 的本质区别是什么?
Tools 是你自己定义的——它的格式、它的调用方式、它的返回值,都是你自己定的。你在这个项目里写的 Tools,换到另一个项目可能就用不了。
MCP 是一个标准——它规定了工具应该怎样描述、怎样调用、怎样返回结果。任何遵循 MCP 协议的工具,都可以被任何支持 MCP 的 Agent 直接使用,不需要额外适配。
用一个你更熟悉的比喻:
- Tools 就像是各家公司自建的 API——A 公司的接口和 B 公司的接口格式完全不同。
- MCP 就像是 RESTful 规范——大家都遵循同一套标准,接口的定义方式、调用方式都是统一的。
MCP 协议定义了三个核心角色:
MCP Host(宿主) ←→ MCP Client(客户端) ←→ MCP Server(服务端)
│ │ │
│ │ │
你的 Agent 协议层通信 提供工具/数据的服务
- MCP Host:你的 AI 应用(比如你的 Agent)
- MCP Client:嵌入在 Host 中,负责按照 MCP 协议与 Server 通信
- MCP Server:提供具体工具能力的服务,每个 Server 可以提供一个或多个工具
MCP Server 是可复用的。比如有人写好了一个"GitHub MCP Server",它提供了读取仓库、创建 Issue、提交 PR 等工具。任何支持 MCP 的 Agent 都可以直接接入这个 Server,立刻获得操作 GitHub 的能力——不需要自己再写一遍工具描述和执行逻辑。
Agent A ──接入──→ GitHub MCP Server ✓ 直接可用
Agent B ──接入──→ GitHub MCP Server ✓ 直接可用
Agent C ──接入──→ GitHub MCP Server ✓ 直接可用
如果没有 MCP,每个 Agent 都要自己封装一套 GitHub 操作的工具。有了 MCP,写一次,到处用。
MCP 还带来了一个更深层的变化:工具的提供者和使用者被彻底解耦了。
以前,工具是你作为 Agent 开发者自己写的。你既是工具的创造者,也是工具的使用者。
有了 MCP 之后,工具可以由任何人开发和发布。你作为 Agent 开发者,只需要"接入"即可。就像你开发 Web 应用时,不需要自己实现一个支付系统——你接入支付宝或微信的 SDK 就行了。MCP 让工具生态变成了一个开放的、可共享的市场。
到这里,你可以想象一个未来:社区里有成千上万个 MCP Server——操作数据库的、操作浏览器的、操作文件系统的、操作 Notion 的、操作 Figma 的——你开发一个新 Agent 的时候,像逛应用商店一样挑选你需要的 MCP Server,接入就能用。
这就是标准化带来的力量。
十、RAG:让大模型拥有你的知识
到目前为止,我们一直在讲如何让大模型"做事"——调用工具、自主推理、管理记忆。但还有一个问题一直没碰:
大模型不知道你的私有数据。
大模型的训练数据是公开的互联网文本。它知道 Python 怎么写、React 怎么用、历史上的今天发生了什么。但它不知道:
- 你公司的内部文档写了什么
- 你项目的代码库长什么样
- 你上周的会议纪要记录了什么
- 你产品的 API 文档有哪些接口
如果你问它:"我们公司的退款流程是什么?"它只能回答:"我不知道你公司的退款流程,请查阅内部文档。"
怎么办?
方案一:把所有文档都塞进 Prompt。
这是最直觉的想法——把公司的退款流程文档复制粘贴到 Prompt 里,然后问问题。大模型看到文档内容,自然就能回答了。
但这有明显的问题:
- 文档可能有几百页,塞不进上下文窗口
- 即使塞进去了,Token 消耗巨大
- 每次问不同的问题,都要把全部文档发一遍
方案二:只塞相关的部分。
如果用户问"退款流程是什么",我只需要把退款相关的段落找出来,塞进 Prompt 就行了。文档有 500 页,但真正相关的可能只有 2 页。
这个思路就是 RAG 的核心。
RAG,Retrieval-Augmented Generation,检索增强生成。
拆开来理解:
- Retrieval(检索):根据用户的问题,从你的知识库中检索出相关的文档片段
- Augmented(增强):把这些检索到的片段注入到 Prompt 中,作为上下文
- Generation(生成):大模型根据增强后的 Prompt 生成回答
流程是这样的:
用户提问:"我们公司的退款流程是什么?"
↓
[检索阶段] 从知识库中找到最相关的文档片段
→ "退款流程:用户需在购买后 7 天内提交退款申请……"
→ "退款审核:财务部会在 3 个工作日内审核……"
↓
[增强阶段] 把检索结果拼入 Prompt
→ "请根据以下参考资料回答用户的问题:
[参考资料1] 退款流程:用户需在购买后 7 天内……
[参考资料2] 退款审核:财务部会在 3 个工作日内……
用户问题:我们公司的退款流程是什么?"
↓
[生成阶段] LLM 根据 Prompt 生成回答
→ "你们公司的退款流程是这样的:首先在购买后 7 天内
提交申请,然后财务部会在 3 个工作日内审核……"
RAG 的关键在于"检索"这一步是怎么实现的。
你可能第一个想法是:用关键词搜索嘛,比如搜索"退款"这个词。但关键词搜索太粗糙了——如果文档里写的是"退货款处理"或"财务返还",关键词"退款"可能搜不到。
RAG 使用的是语义搜索。它的原理是:
- 提前把所有文档切成小段(比如每段 500 字)
- 把每一段文本转换成一个向量(一串数字,代表这段文本的"语义"),这叫 Embedding
- 用户提问时,把问题也转换成向量
- 计算问题向量和所有文档向量之间的相似度,找出最接近的几段
这就是为什么你可能听过"向量数据库"这个词——它是专门用来存储和检索这些向量的数据库。Pinecone、Milvus、Weaviate、Chroma 都是常见的向量数据库。
RAG 和前面讲的那些概念是什么关系?
RAG 是一个独立的领域,但它与 Agent 的关系是相辅相成的。
- RAG 补充了 Agent 的知识:Agent 本身不知道你的私有数据,但通过 RAG,它可以在需要时检索并获取这些知识。
- RAG 本身也可以作为一个 Tool:在 Agent 的框架下,RAG 的检索过程可以被封装成一个工具。Agent 在需要查询私有知识时,调用这个 RAG 工具即可。
- RAG 不依赖 Agent 也能用:即使你不做 Agent,只是做一个简单的问答系统,RAG 也是非常有用的。
你可以这样理解:
前面讲的 Prompt、Context、Memory、Tools、Agent、Skill、MCP,解决的是"大模型怎么做事"的问题。
RAG 解决的是"大模型怎么获取知识"的问题。
它们是两个维度——一个关乎行动能力,一个关乎知识获取。在实际应用中,它们通常组合使用:一个 Agent 既能通过 Tools 操作外部系统,又能通过 RAG 查询私有知识库。
串起来:一张完整的地图
好了,让我把所有概念串成一条线:
LLM(大模型)
│
├── 它处理的基本单位是什么?
│ └── Token
│
├── 你用什么方式跟它沟通?
│ └── Prompt(提示词)
│
├── 连续对话时,历史信息怎么传给它?
│ └── Context(上下文)
│
├── 上下文太长撑爆了怎么办?
│ └── Memory(记忆管理)
│
├── 它只能生成文本,不能操作外部世界怎么办?
│ └── Function Calling / Tools(工具调用)
│
├── 需要连续调用多个工具,而且步骤不确定怎么办?
│ └── Agent(智能体:LLM + Tools + 自主循环)
│
├── Agent 的能力太多,上下文放不下怎么办?
│ └── Skill(按需加载的领域知识)
│
├── 每个 Agent 都要重复实现工具,太麻烦怎么办?
│ └── MCP(标准化的工具协议)
│
└── 大模型不知道你的私有数据怎么办?
└── RAG(检索增强生成)
每一个概念,都是在解决上一个概念带来的问题。它们不是孤立的知识点,而是一条不断生长的链路。
当你理解了这条链路,你就理解了大模型应用开发的全景图。剩下的,就是在实践中去深入每个细节了。
写给曾经的我,也写给正在路上的你。