我的认知
一切皆是对 LLM 输入/输出的加工
你总结得非常精准——我们现在学的所有技术,本质上都是在做两件事:
- 输入加工:把用户原始的、模糊的需求,转化成 LLM 能理解、能执行的精确指令(Prompt)。
- 输出加工:把 LLM 生成的原始文本,解析、验证、转换成用户需要的最终结果。
| 技术/概念 | 本质 | 输入加工 | 输出加工 | ||
|---|---|---|---|---|---|
| RAG | 把外部知识库的信息,加工成 Prompt 中的“上下文” | 将检索到的文档片段拼接到用户问题前面 | 让 LLM 基于这些事实生成答案,减少幻觉 | ||
| Function Calling | 把工具描述加工成 Prompt,再把 LLM 输出的工具指令解析成实际调用 | 将工具的名称、描述、参数结构写入 System Prompt | 解析 LLM 输出的 Action(如 get_weather),触发实际函数 | ||
| MCP | 标准化工具的发现和调用,本质上也是输入输出格式的统一加工 | 客户端通过 MCP 获取工具列表,转换成 LLM 能理解的格式 | 服务器执行工具后,将结果加工成 LLM 能读的 Observation | ||
| SKILLs | 把业务专家的经验加工成标准化的执行流程 | 在 System Prompt 中注入完整的步骤、规则、注意事项 | 确保 LLM 的输出符合业务规范(如固定的报表格式) | ||
| Agent (ReAct) | 把“思考-行动-观察”的循环规则加工成 Prompt,再把每一步的结果解析并拼回对话 | 用 System Prompt 定义思考格式(Thought/Action/Observation) | 解析每一步的 Action,执行工具,将 Observation 拼回对话 | ||
| 记忆模块 | 把对话历史加工成当前 Prompt 的一部分 | 短期记忆:拼接近期对话;长期记忆:检索相关历史拼入 | (间接作用)让 LLM 生成更连贯、个性化的回答 | ||
| 输出解析器 | 纯粹的输出加工 | 无 | 把 LLM 返回的文本解析成 JSON、Pydantic 对象等结构 | ||
| LCEL | 把输入输出加工的流程用统一语法编排起来 | 用 ` | ` 串联各种加工步骤 | 用 ` | ` 串联各种加工步骤 |
| 评估监控 | 对 LLM 的输出进行二次质检 | 无 | 对 LLM 回答进行打分、校验,发现异常 |
🧠 为什么会是这样?
因为 LLM 本质上是一个“文本进,文本出”的超级预测引擎。它内部是黑盒,它只懂文本。为了让这个“文本引擎”完成复杂的业务任务,我们必须:
- 在输入侧:把所有业务信息(知识、工具、历史、规则)都翻译成它懂的语言——也就是精心构造的 Prompt。
- 在输出侧:把它吐出来的“原始文本”翻译回业务系统能用的语言——也就是结构化数据、工具调用、数据库操作。
所以,整个 AI 应用开发,就是围绕 LLM 这个核心,搭建一套双向翻译系统。
RAG 是什么?
RAG 是 Retrieval-Augmented Generation 的缩写,中文叫检索增强生成。
通俗地说:让大模型在回答问题之前,先“查资料”,再“开口”。 让 LLM 能够实时查阅外部知识库(比如最新的网页、公司文档、数据库),然后基于查到的真实资料来回答。尽量避免大模型出现所谓的幻觉
RAG 的工作原理(三步走)
整个过程就像学生参加“开卷考试”:
- 索引(准备资料) :提前把各种文档(比如公司手册、维基百科)切分成片段,向量化,存入向量数据库。
- 检索(查资料) :用户问一个问题(比如“RAG 怎么用?”)。系统先把问题也转换成向量,然后去向量数据库里搜索最相关的几个片段(这里可能用到混合搜索,既匹配关键词又匹配语义)。
- 生成(写答案) :把用户的问题 + 检索到的相关片段,一起塞给大模型,并提示它:“请根据这些资料回答问题”。大模型就会基于提供的资料,生成一个有理有据的答案,而不是凭空想象。
RAG 的关键组件(和你之前了解的概念完美对应)
-
知识库(文档) :原始的 PDF、网页、数据库。
-
文本切分器:把长文切成合适的小块。
-
嵌入模型:把文本块变成向量(计算机可以理解的多维数组)。
-
向量数据库:存储向量并支持快速检索(类比于数据库)。
-
检索器:执行搜索(可以用混合搜索来提升精度)。
-
大语言模型(LLM) :根据问题+检索结果,生成最终答案。
-
向量库 & 嵌入模型:嵌入模型将文本转换为多维数组(向量),存入向量库。计算机通过比较向量间的距离(如余弦相似度)来衡量语义相似度,从而实现语义检索。
-
余弦相似度:通过计算两个向量在高维空间中的夹角余弦值(0°表示完全相同,90°表示不相关 180°代表意思相反)来判断语义相似度,是向量检索的核心算法。
多维数组当维度达到一定值(比如 768 或 1024)后,再增加维度带来的效果提升会逐渐变小。因此很多主流模型选择 768 或 1024 作为平衡点。
实际选择建议:
如果做通用知识库检索,用 1024 或 1536 维的模型通常就足够了;如果做实时对话系统且数据量巨大,可以考虑 384 维的轻量模型来降低成本。
客服RAG的核心架构(5层结构)
第一层:知识库准备(预处理阶段)
这是基础,质量决定了RAG效果的天花板。
-
数据源:
- 非结构化:产品说明书、帮助文档(PDF/Word)、过往的客服对话记录(这部分非常有价值,包含了真实用户的各种问法)、培训手册。
- 半结构化:FAQ(常见问题解答)、商品库(规格、价格)。
- 结构化:订单系统、物流系统(实时数据)。
-
处理流程:
- 清洗:去掉对话记录里的语气词、重复信息。
- 切分:你之前关心的切分问题,在客服场景里通常采用层级切分。比如,把一个产品的帮助文档,按"产品线 → 功能模块 → 具体操作步骤"进行切分,保证每个片段语义完整。
- 向量化:将这些片段转换成向量,存入向量数据库。
第二层:用户意图识别与路由
用户发来一句话:"我的订单怎么还没到?"
系统首先要判断:这是查知识还是要办事?
-
意图识别:可以用一个轻量级的分类模型,或者直接让大模型判断。
- 如果是"查知识" :比如"怎么退款?",走下面的RAG检索流程。
- 如果是"要办事" :比如"帮我改一下收货地址"、"我要退货",则触发 Function Calling / MCP,调用后台API去执行操作。
- 如果是闲聊:比如"你好",可以走预设的欢迎语。
第三层:混合检索(你之前问过的重点)
当判断用户是来问问题后,系统开始找答案。为了确保准确率,不会只用向量搜索。
-
多路召回:
- 向量检索:找语义相似的内容(比如用户说"东西坏了",知识库里写的是"产品质量问题",向量能匹配上)。
- 关键词检索:利用ES(Elasticsearch)进行精确匹配,尤其对产品型号(iPhone 15 Pro)、订单号这种专有名词,必须精确命中。
- 知识图谱:如果有,可以用于多跳问答。比如用户问"iPhone 15 Pro支持快充吗?",系统需要知道"iPhone 15 Pro"属于哪类产品,快充协议是什么,然后从图谱里找到关系。
-
重排序:
- 多路召回可能会拿回几十个候选片段。
- 用一个专门的重排序模型(Reranker) ,把这些候选片段和用户问题放在一起,再进行一次更精细的相关性打分,选出最相关的3-5个片段。
第四层:答案生成(大模型登场)
把用户的问题和筛选出的相关片段,一起交给大模型,并配上精心设计的提示词。
提示词示例:
text
复制下载
你是一个专业的客服助手。请基于以下参考资料,回答用户的问题。
要求:
1. 如果参考资料里有答案,请用礼貌、专业的语气回答。
2. 如果参考资料里没有答案,请直接说“我没有查到相关信息,请尝试换个问法或联系人工客服”,不要编造。
3. 如果问题涉及订单号、手机号等个人信息,提醒用户注意保护隐私。
4. 回答结束时,可以提供1-2个相关的后续问题建议。
参考资料:
{这里放入检索到的3个片段}
用户问题:{用户输入}
第五层:对接业务系统与人工(闭环)
AI生成答案后,怎么给到用户?
-
纯AI模式:直接回复给用户。适用于FAQ类问题。
-
人机协同模式(辅助人工) :
- AI生成的答案和推荐的知识,不直接发给用户,而是弹窗显示在人工客服的操作界面上。
- 人工客服看一眼,觉得没问题,点击"发送";觉得有问题,可以修改后再发送。
- 这是一个非常重要的兜底策略,既能保证准确率,又能让人工客服感受到AI是在帮自己,而不是抢自己的工作。
-
反馈循环:
- 用户是否满意?可以对回答点👍或👎。
- 客服是否采纳了AI的建议?
- 这些反馈数据收集起来,用于评估RAG效果,发现哪些问题答不好,进而优化知识库或检索策略。
MCP 是什么?
MCP 是由 Anthropic 发起并开源的开放协议,全称是 Model Context Protocol(模型上下文协议)。它的目标是统一 AI 应用程序与外部数据源、工具之间的通信方式。
你可以把它理解为 AI 世界的 “USB-C 接口” ——无论你用什么 AI 客户端(比如 Claude Desktop、IDE 插件、自定义应用),只要它支持 MCP,就可以通过一套标准的方式,连接到任何实现了 MCP 协议的“工具服务器”(比如本地文件系统、数据库、API 服务、搜索引擎等),并自动发现和使用这些工具。
MCP 解决了什么问题?
在没有 MCP 之前,如果你想给 AI 接入一个自定义工具(比如查询公司内部数据库),你需要:
- 为每个 AI 模型单独写一套工具描述(OpenAI 的 function calling 格式、Anthropic 的 tool use 格式……)。
- 每次新增工具,都要修改 AI 客户端的代码,重新部署。
- 工具与客户端紧耦合,无法复用。
MCP 希望解耦:让工具开发者只写一次“MCP 服务器”,就能被所有支持 MCP 的 AI 客户端使用;AI 客户端也只需实现 MCP 协议,就能无缝接入成千上万的工具生态。
MCP 的核心组件
-
MCP 客户端:AI 应用(如 Claude Desktop、聊天机器人后端)负责连接用户和服务器。
-
MCP 服务器:一个轻量级程序,封装了具体的能力(如读取本地文件、查询天气 API、操作数据库)。服务器通过 MCP 协议暴露三样东西:
- 资源:可读的数据(如文件内容、表格)。
- 工具:可执行的函数(如
send_email、get_weather)。 - 提示词模板:可复用的对话模板。
-
协议:基于 JSON-RPC 的消息格式,定义了客户端与服务器如何握手、如何发现能力、如何调用工具、如何传输结果。
MCP 客户端
MCP客户端不是一个简单的数据管道,它内嵌于AI应用(主机)中,是连接AI模型与外部世界的智能调度与管理中心。它的核心工作可以概括为以下几点:
- 1. 连接的建立与管理者:根据配置,启动、连接并维护与一个或多个MCP服务端的独立通信通道。
- 2. 能力的发现与注册者:在连接建立后,主动向服务端查询其提供的工具(Tools)清单,并将这些工具的能力(名称、描述、参数格式)在本地注册和缓存起来。
- 3. 协议的翻译与适配器:这是最核心的适配工作。它需要将AI模型(如OpenAI、Claude、文心一言)特有的Function Calling格式,与MCP服务端统一的JSON-RPC格式进行双向转换。
- 4. 调用的执行与流程控制器:接收AI模型发出的工具调用指令,找到正确的服务端连接,发起调用请求,并将服务端返回的结果反馈给AI模型,完成一次完整的“思考-行动-反馈”循环。
- 5. 安全的守卫者:管理认证信息(如API密钥),在执行调用前进行权限校验,确保操作的安全性。
MCP客户端流程
-
连接与发现(步骤1-4)
- 通信协议:客户端通过stdio(用于本地进程间通信)或SSE (Server-Sent Events) (用于远程HTTP通信)与服务端建立1:1连接。启动时,它会发送一个标准化的
initialize请求,与服务端协商协议版本和能力。 - 工具发现:初始化后,客户端会发送
tools/list请求。服务端返回一个包含工具名称、描述和输入参数(JSON Schema)的清单。客户端会将这些信息缓存起来,供后续使用。
- 通信协议:客户端通过stdio(用于本地进程间通信)或SSE (Server-Sent Events) (用于远程HTTP通信)与服务端建立1:1连接。启动时,它会发送一个标准化的
-
协议转换与模型交互(步骤5-7, 11-12)
- 当用户提问后,客户端需要将缓存的MCP工具列表,动态转换成目标大模型(如文心一言的
ernie-4.0-turbo-8k)所能识别的tools参数格式。 - 当模型返回工具调用指令后,客户端需要解析这个模型特有的格式,提取出工具名和参数。
- 当用户提问后,客户端需要将缓存的MCP工具列表,动态转换成目标大模型(如文心一言的
-
稳健的进程管理(步骤8-10)
- 这是客户端底层非常关键的一环,尤其对于通过
stdio连接的本地服务端。一个成熟的客户端需要能稳健地处理这些服务端子进程。例如,它能过滤掉服务端进程可能输出的非协议信息(如print调试语句),防止这些“噪声”污染JSON-RPC通信信道。 - 当需要调用工具时,客户端将模型指令转换为MCP协议的JSON-RPC请求,通过预建立的连接发送给正确的服务端。请求中还可以包含
context信息(如用户ID、会话ID),以便服务端进行更精准的操作。
- 这是客户端底层非常关键的一环,尤其对于通过
MCP客户端远非一个简单的数据管道。它是一个智能的适配层,负责将AI模型与外部世界连接起来。它不仅要管理服务端的生命周期、发现并理解其能力,更要将AI模型的思考“翻译”成服务端能执行的指令,再将执行结果“反馈”给AI模型以形成最终答案。它背后是协议转换、进程管理、上下文传递等一系列复杂但精密的工程实现,确保了整个系统的稳健、安全和可扩展性
MCP 服务端
MCP服务端的底层原理可以概括为:一个遵循JSON-RPC 2.0规范,通过特定传输方式,负责处理来自客户端的标准化请求、路由到具体业务逻辑并返回结果的后端程序
四层核心原理详解
结合上图,我们来看看服务端底层的这四个层面是如何协同工作的。
1. 协议层:以JSON-RPC 2.0为“通用语言”
这是MCP服务端的基石。它并不创造新协议,而是建立在成熟的JSON-RPC 2.0规范之上。这意味着客户端和服务端之间的所有通信,都是通过结构化的JSON消息来完成的。一个典型的请求消息会包含method(要调用的方法,如tools/call)、params(参数,包含工具名name和入参arguments)和id(请求标识)。服务端的核心任务之一,就是精确地解析这些JSON消息,并构建出符合规范的响应。
2. 发现层:通过“能力宣告”让客户端了解自己
服务端不能被动地等着被调用,它需要主动告诉客户端“我能做什么”。这就是能力发现(Capability Discovery) 机制。在连接初始化阶段,服务端会通过tools/list这个预定义的method,向客户端返回一个详细的工具清单。这个清单里包含了每个工具的名字、功能描述,以及最重要的——输入参数的JSON Schema(一种描述数据格式的规范),它精确地定义了工具需要什么样的输入。这样,客户端就能知道在什么情况下、用什么参数来调用哪个工具。
3. 执行层:从“协议请求”到“业务逻辑”的路由
这是服务端“真正干活”的部分,也是和你之前理解的“常规服务”最接近的一层。当协议处理层解析完客户端的tools/call请求后,会提取出name和arguments。然后,一个内部的 “工具路由器” 会根据name,将请求和参数分发给事先注册好的、对应的业务函数。这个函数可以是任何你能想到的逻辑:查询数据库、调用第三方REST API、读写文件、执行计算等等。函数执行完毕后,结果会被返回给协议层进行封装。
4. 通信层:通过stdio或HTTP(S)作为传输载体
服务端需要通过网络或进程管道来收发消息,这由传输层(Transport) 负责。MCP主要定义了两种标准传输方式:
stdio(标准输入/输出) :主要用于本地通信。服务端作为客户端的子进程启动,双方通过标准输入输出流进行JSON-RPC消息的交换。这种方式简单、安全,适合与桌面应用(如Claude Desktop)集成。- Streamable HTTP:用于远程通信。服务端作为一个独立的HTTP服务运行。它通过一个HTTP端点,支持客户端发送请求,并能通过SSE (Server-Sent Events, 服务器推送事件) 等技术将服务器的数据实时推送给客户端,适合部署在云端。
💡 高级特性:不仅仅是单向调用
除了上述基础,MCP服务端还有一些更强大的能力:
- 上下文传递(Context Propagation) :客户端在每次请求中可以携带一个
context对象,里面包含用户ID、会话ID等信息。服务端在执行工具函数时,可以读取这个context来做权限校验或记录日志,实现有状态的交互。 - 采样(Sampling) :这是一个更高级的特性。它允许服务端在处理请求的过程中,反过来向客户端请求调用大语言模型。例如,一个数据分析服务端可以在获取到原始数据后,通过采样机制请求客户端的LLM来生成数据摘要,从而实现更复杂的智能工作流
关键点在于:
- 一个AI应用(主机)可以创建多个客户端实例,每个客户端独立连接到一个服务端。
- 每个MCP客户端和它对应的MCP服务端之间,是一条独占的通信通道——这就是“1:1”的意思。
- 但整体上看,是一个主机连接多个服务端,形成“1:N”的关系。
为什么这样设计?
这种“每个通道独占”的设计有几个重要的好处:
| 考虑维度 | 解释 |
|---|---|
| 隔离性 | 如果服务端1崩溃了,只影响它自己的那条通道。服务端2的通道依然正常,你还可以继续查数据库。 |
| 安全性 | 每个服务端的凭证(API密钥、数据库密码)只在自己的通道内使用,不会混在一起,降低了泄漏风险。 |
| 通信简化 | 不需要在同一个通道里做复杂的“多路复用”,协议实现更简单、更稳定。 |
| 资源控制 | 可以独立控制每个通道的速率、超时等参数,精细化管理。 |
在真实的MCP实现中(比如Claude Desktop),当你配置多个MCP服务端时:
- 启动时:Claude Desktop会为每个配置好的服务端启动一个独立的子进程(服务端实例),并建立一个对应的客户端通道。
- 运行时:当AI需要调用某个工具时,它会通过对应的那个客户端通道发送请求。
- 多工具调用:如果一次回答需要同时调用多个工具(比如先查文件、再查数据库),AI会分别通过不同的通道并行发送请求,互不干扰。
一种常见的扩展模式
虽然基础是“1:1”,但社区也发展出了一些扩展模式来满足更复杂的需求:
- MCP Router(路由器) :一个中间层,它作为一个“超级客户端”连接多个服务端,对外提供一个统一的接入点。这样你的应用只需要维护一个客户端连接,就能访问多个服务端的能力。
- 服务端聚合:有些服务端本身可以聚合多个功能(比如一个服务端同时提供文件访问、数据库查询、API调用),这样你只需要一个客户端连接它,就能获得多种工具。 天气查询 MCP 服务器
1. 连接与初始化
客户端启动时,会通过 stdio 或 HTTP 连接服务器,并发送一个 initialize 请求,协商协议版本和能力。
客户端 → 服务器(请求) :
json
复制下载
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"clientInfo": {
"name": "my-ai-client",
"version": "1.0.0"
},
"capabilities": {
"sampling": {},
"roots": {
"listChanged": true
}
}
}
}
服务器 → 客户端(响应) :
json
复制下载
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"serverInfo": {
"name": "weather-server",
"version": "1.0.0"
},
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
}
}
}
}
初始化完成后,客户端发送一个 notifications/initialized 通知,表示准备就绪。
客户端 → 服务器(通知) :
json
复制下载
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
2. 发现工具列表
客户端想知道服务器提供了哪些工具,于是发送 tools/list 请求。
客户端 → 服务器(请求) :
json
复制下载
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
}
服务器 → 客户端(响应) :
json
复制下载
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "get_forecast",
"description": "获取指定城市未来几天的天气预报",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如 '北京'、'上海'"
},
"days": {
"type": "integer",
"description": "预报天数,默认3",
"minimum": 1,
"maximum": 7
}
},
"required": ["city"]
},
"outputSchema": {
"type": "object",
"properties": {
"city": { "type": "string" },
"forecast": {
"type": "array",
"items": {
"type": "object",
"properties": {
"date": { "type": "string" },
"condition": { "type": "string" },
"temperature": {
"type": "object",
"properties": {
"high": { "type": "number" },
"low": { "type": "number" }
}
}
}
}
}
}
}
}
]
}
}
客户端现在知道了工具的存在以及它的输入/输出格式。
3. 用户提问,模型决定调用工具
假设用户问:“北京明天天气怎么样?” AI 模型根据 Function Calling 能力,决定调用 get_forecast。客户端将模型的调用意图转化为 MCP 的 tools/call 请求。
客户端 → 服务器(请求) :
json
复制下载
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "get_forecast",
"arguments": {
"city": "北京",
"days": 1
}
}
}
4. 服务器执行工具并返回结果
天气 MCP 服务器收到请求后,调用真实的天气 API,获取数据,然后按照 MCP 协议返回结果。这里利用了结构化输出特性。
服务器 → 客户端(响应) :
json
复制下载
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"structuredContent": {
"city": "北京",
"forecast": [
{
"date": "2025-03-26",
"condition": "晴",
"temperature": {
"high": 18,
"low": 5
}
}
]
},
"content": [
{
"type": "text",
"text": "北京明天(3月26日)晴,最高温度18℃,最低温度5℃。"
}
]
}
}
这里 structuredContent 是结构化数据,供客户端程序化使用;content 是自然语言描述,可直接展示给用户或 AI。
5. 客户端将结果返回给 AI 模型
客户端收到工具结果后,将其作为 tool 消息发送给 AI 模型(以 OpenAI 格式为例):
json
复制下载
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "北京明天(3月26日)晴,最高温度18℃,最低温度5℃。"
}
AI 模型结合上下文,生成最终回答给用户。
Function Calling 是什么?
Function Calling 是 AI 模型(如 OpenAI 的 GPT、Anthropic 的 Claude 等)提供的一项核心能力。它允许你将自定义的工具或函数描述给模型,模型在回答问题时,可以根据需要,智能地选择调用哪个函数,并返回一个结构化的调用请求。
简单来说:让 AI 模型学会“按说明书使用工具” 。
Function Calling 的工作流程(三步走)
我们拆解一下具体步骤,以 OpenAI 的 API 为例:
第一步:定义函数(写说明书)
你在调用 OpenAI 的 API 时,在请求里加上 tools 参数,描述你的工具有哪些、怎么用。
json
复制下载
{
"model": "gpt-4",
"messages": [{"role": "user", "content": "北京明天天气怎么样?"}],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名,比如 北京、上海"
}
},
"required": ["location"]
}
}
}
]
}
第二步:模型决策并返回指令
OpenAI 的模型看到用户问题和你的工具列表,如果它觉得需要用到 get_weather,它会返回一个特殊的响应,而不是直接返回文本。
json
复制下载
{
"choices": [
{
"message": {
"role": "assistant",
"content": null, // 注意,content 是空的
"tool_calls": [
{
"id": "call_123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{"location":"北京"}" // 模型生成的参数
}
}
]
}
}
]
}
第三步:客户端执行并返回结果
你的代码(客户端)收到这个响应后:
- 解析:取出
name和arguments。 - 路由:根据
name,找到真正的get_weather函数并执行(调用天气 API、查数据库等)。 - 获取结果:得到天气数据,比如
{ "temperature": 22, "condition": "晴" }。 - 送回模型:再调用一次 API,把工具执行结果以
tool角色的消息发回去。
json
复制下载
{
"model": "gpt-4",
"messages": [
{"role": "user", "content": "北京明天天气怎么样?"},
{"role": "assistant", "tool_calls": [...]}, // 上一步的调用记录
{"role": "tool", "tool_call_id": "call_123", "content": "{"temperature": 22, "condition": "晴"}"}
]
}
第四步:模型生成最终答案
模型看到工具返回的结果,生成自然语言回答:
“北京明天天气晴朗,气温 22 摄氏度。”
Function Calling 的几个关键点
-
模型不执行代码,只输出指令:
- 这是安全和灵活的核心。执行权永远在你手里,你可以做任何操作(调 API、查数据库、写文件),模型只负责“建议”。
-
需要两次 API 调用:
- 第一次:模型返回工具调用指令。
- 第二次:你把工具结果送回模型,模型生成最终回答。
-
函数描述要清晰:
- 函数名、描述、参数说明写得越清楚,模型选择越准确。这有点像写提示词,只是换成了结构化格式。
-
支持并行调用:
- 模型可以在一次响应中要求同时调用多个函数(比如查天气 + 查机票)。
- Function Calling 是 AI 模型的能力:当模型觉得需要调用外部工具时,它会输出一个结构化的“调用请求”(函数名+参数)。这是模型本身具备的功能,由模型提供商(OpenAI、Anthropic 等)在模型层实现。
- MCP 是 AI 客户端与工具之间的通信标准:它定义了“模型生成的调用请求”如何从客户端传递到具体的工具执行,以及结果如何返回。
它们不是替代关系,而是不同层次的分工:
| Function Calling | MCP | |
|---|---|---|
| 层级 | 模型层(AI 模型内部) | 应用层(AI 客户端与外部系统之间) |
| 职责 | 决定“要不要调用工具”并输出调用参数 | 将调用请求“路由”到正确的工具并执行 |
| 定义方 | 模型提供商(如 OpenAI API 的 tools 参数) | 开放协议(由 Anthropic 发起,社区共建) |
| 是否依赖具体模型 | 是,不同模型格式不同 | 否,协议统一,与模型无关 |
| 典型作用 | 让模型“理解”有哪些工具可用,并“表达”调用意图 | 让客户端“连接”任意工具,并“执行”调用 |
到底什么是 Skills?
Skills 就是让大模型按照某种特定的方法论去行动的机制
一个标准SKILL的完整组件构成如下表所示:
| 组件 | 核心功能 | 关键内容 |
|---|---|---|
| 元数据 (Metadata) | 作为SKILL的“目录”或“岗位名称”,用于AI发现和决策-1-3。 | SKILL的名称、描述、适用场景及触发条件 |
| 指令 (Instruction) | 作为SKILL的“操作手册”,定义AI执行任务的核心流程和逻辑-1-3-6。 | 具体的操作步骤、判断逻辑、输入/输出约束及风格规则 |
| 资源 (Resource) | 作为SKILL的“附录”和“工具包”,为指令执行提供数据和工具支持-1-3-6。 | 参考文档、可执行脚本、静态资产(如图片、模板) |
1. 元数据 (Metadata):让AI“知道”你
元数据是AI在不加载整个SKILL的情况下就能看到的信息,通常位于SKILL.md文件的开头。它的作用是让AI能够将用户的任务与正确的SKILL匹配起来,类似一本技能目录。
- 名称 (
name) :SKILL的唯一标识,如invoice-processor。 - 描述 (
description) :清晰说明该SKILL的用途,帮助AI判断何时调用,例如:“用于处理供应商发票、费用报销单或付款凭证的提取和验证。” - 触发条件:定义在什么情况下应该激活此SKILL。
2. 指令 (Instruction):给AI的“操作手册”
这是SKILL的核心执行逻辑,同样写在SKILL.md中,但仅在AI确定调用此SKILL时才会被加载到上下文中。它指导AI“如何一步步完成工作”。
-
工作流程:以清晰的步骤定义任务流程,例如:
- 读取发票PDF文件。
- 提取供应商名称、发票号、日期等字段。
- 根据验证规则进行校验。
- 按指定格式输出JSON。
-
操作指南:包含处理任务时的特殊规则、注意事项或风格要求。
-
索引逻辑:对于复杂任务,指令中只保留核心逻辑,并通过“索引”的方式,引导AI在需要时去读取
references文件夹中的详细规范。
3. 资源 (Resource):AI的“工具箱”
资源层为SKILL的执行提供具体的数据和工具支持。这部分遵循“按需加载”原则,AI只在执行到特定步骤时,才会去读取或调用它们,从而有效控制Token消耗。它通常由以下三个子目录构成:
references/(参考文档) :存放详细的规范、数据映射或规则文件。例如,在发票处理SKILL中,field-mappings.md定义了发票上的字段应如何映射到系统的数据结构,validation-rules.md则列出了所有的校验规则。这些文件被AI读取,作为执行依据。scripts/(可执行脚本) :存放预先编写好的程序脚本(如extract_data.py)。AI本身不读取脚本内容,而是在满足条件时触发执行该脚本,从而完成精确、可靠的操作,不占用上下文Token。assets/(静态资产) :存放AI在执行任务时需要参考但不修改的确定性资源,如公司Logo、固定模板、示例图片等。将这些资源放入assets并用确定性资源替代概率性生成,能显著提升输出结果的稳定性。
一个典型的SKILL目录结构如下:
invoice-processor/ # SKILL 根目录
├── SKILL.md # 包含元数据和核心指令
├── references/ # 参考文档目录
│ ├── field-mappings.md # 字段映射规范(AI按需读取)
│ └── validation-rules.md # 验证规则(AI按需读取)
└── scripts/ # 可执行脚本目录
└── extract_data.py # PDF数据提取脚本(AI触发执行)
在这种结构下,AI的工作流程非常清晰:
- 任务匹配:通过
SKILL.md的元数据,AI判断当前任务需要调用invoice-processor。 - 加载指令:系统将
SKILL.md中的指令部分加载到AI的上下文,AI据此制定工作计划。 - 执行与查阅:在执行到“提取数据”步骤时,AI触发
scripts/extract_data.py运行。在“验证数据”时,AI读取references/validation-rules.md中的规则来进行判断。 - 完成输出:最终,AI整合所有信息,完成发票处理任务。
| 生成途径 | 一句话描述 | 适合谁 | 核心步骤 |
|---|---|---|---|
| 1. 对话式创建 | 像聊天一样,把你的工作流程说给AI听,让它帮你打包成技能。 | 非技术用户、业务专家 | 自然对话 → AI拆解 → 自动生成并打包 |
| 2. 手动构建 | 按照官方规范,自己动手创建文件夹、编写文件,完全掌控细节。 | 开发者、追求精细控制者 | 理解架构 → 规划内容 → 编写核心 → 集成与测试 |
| 3. 工具辅助生成 | 用“技能生成器”这个高级工具,通过自然语言描述,自动生成技能脚手架。 | 开发者、追求效率者 | 配置工具 → 自然语言描述 → 审查并完善 → 部署 |
SKILLs和Tools在实践中如何配合使用?可以结合具体场景说明。
核心考点:考察对两个概念协同关系的理解。
参考答案思路(以客服场景为例):
在一个客服RAG系统中:
-
Tools(工具层) :原子能力,如
query_order(查订单)、refund_apply(申请退款)、search_knowledge(搜索知识库) -
SKILLs(技能层) :封装完整业务流程
handle_refundSKILL:定义处理退款的完整流程(验证订单状态 → 检查退款条件 → 调用refund_apply工具 → 生成回复)troubleshoot_productSKILL:定义故障排查步骤(询问症状 → 搜索知识库 → 推荐解决方案)
配合方式:SKILLs在指令中定义“在哪个步骤调用哪个Tool”,把多个原子操作组合成完整业务流程
如果系统中有超过30个SKILLs,直接把所有SKILL描述都传给AI模型会有什么问题?如何解决?
核心考点:考察对AI上下文限制的理解和工程优化能力。
参考答案思路:
问题:把所有SKILL描述都传给模型,会占用大量上下文token,导致:
- 成本大幅上升
- 模型可能“看不过来”,忽略部分SKILL
- 核心任务可用的上下文空间被挤占
解决方案:
- 分层路由:设计一个“SKILL管理器”Agent,先粗粒度分类,再路由到具体SKILL
- 动态加载:只把与当前任务最相关的SKILL描述传给模型(通过向量检索或规则匹配)
- SKILL目录:给SKILLs设计清晰的元数据(名称、简短描述),让模型先基于元数据选择,再加载完整SKILL
Agent
Agent(智能体) 是当前AI领域最前沿、最综合的概念,可以把它理解为 “一个有自主行动能力的AI大脑” 。你之前了解的RAG、Skills、Function Calling、MCP,实际上都是构建Agent的“零部件”。 Agent是一个能够感知环境、进行决策、并执行动作以实现特定目标的自主系统。在大模型背景下,Agent通常以大语言模型(LLM)为核心控制器,结合规划能力、记忆模块和工具使用能力,去完成复杂的任务。
Agent的四大核心组件
1. 感知模块
- 职责:接收用户输入(文本、语音、图像等),进行意图识别、实体抽取、情感分析等预处理。
- 依赖技术:LLM本身的多模态能力、提示工程、少量分类模型。
2. 大脑(核心控制器)
-
职责:LLM作为“思考中枢”,负责规划、推理、决策。它决定“下一步该做什么”,并调用其他模块。
-
关键技术:
- 思维链(Chain-of-Thought, CoT) :让模型逐步推理,而不是直接给答案。
- ReAct模式:将思考(Reason) 和行动(Act) 交错进行,每步思考后可能执行一个动作,再观察结果继续思考。
- 自我反思(Self-reflection) :Agent能评估自己的行动效果,遇到错误时调整策略。
3. 记忆模块
- 短期记忆:当前对话的上下文(通过LLM的上下文窗口实现)。
- 长期记忆:存储历史经验、用户偏好、领域知识等。通常用向量数据库实现(你熟悉的RAG技术),需要时检索相关记忆注入大脑。
- 记忆的读写:Agent可以主动写入重要信息(如“用户喜欢清淡口味”),也能在决策时查询相关记忆。
4. 行动模块
-
职责:执行大脑的决策,与外部环境交互。
-
依赖你之前学的:
- Function Calling:调用具体的工具函数(如查天气、订票)。
- MCP:通过标准化协议连接各种工具服务器,实现动态工具发现和调用。
- Skills:执行封装好的复杂流程(如“处理投诉”Skill内部可能包含多个步骤和工具调用)。
工程师在感知模块的“可做之事”
感知模块的核心任务可以概括为:把用户的原始输入(文本、语音、图像等),转换成Agent大脑(LLM)能够准确理解和处理的结构化信息
| 工作领域 | 具体可做之事 | 目的 |
|---|---|---|
| 输入清洗 | 拼写纠错、敏感信息过滤、语言检测 | 提高输入质量,保障安全,节省token |
| 意图识别 | 设计few-shot提示、混合规则/小模型、处理多轮指代 | 准确理解用户目的,输出结构化信息 |
| 多模态处理 | 集成ASR/OCR服务、配置热词、版面分析 | 拓展Agent的输入感知能力 |
| 工程保障 | 超时降级、结果缓存、AB测试平台 | 提升系统的稳定性、效率和可优化性 |
大脑模块的核心工作
| 工作领域 | 具体可做之事 | 目的 |
|---|---|---|
| 规划能力 | 设计ReAct提示词、实现循环控制、引导思维链/思维树 | 让Agent能分解复杂任务,有步骤地执行 |
| 决策机制 | 优化工具描述、设计意图-工具映射、处理多工具调用 | 让Agent能准确选择工具,高效完成任务 |
| 记忆整合 | 设计记忆读写触发逻辑、集成向量检索 | 让Agent具备长期记忆,实现个性化交互 |
| 反思纠错 | 设计自我反思提示、实现错误处理与重试流程 | 提升Agent的鲁棒性和可靠性 |
| 系统提示 | 编写并持续迭代高质量的系统提示词 | 奠定Agent所有行为的基础框架 |
大脑模块是Agent的“灵魂”所在。作为工程师,你通过提示词工程、流程设计和各种增强技术,将通用的LLM塑造成能解决特定领域复杂问题的智能体。
主流的大模型框架(如LangChain、LlamaIndex、AutoGen、CrewAI等)主要聚焦的就是“大脑模块”的工作,同时也会为其他模块(记忆、行动)提供便利的接入方式。 大脑模块的核心职责是:规划、决策、控制循环。这些恰恰是框架提供的最核心价值:
| 大脑模块的职责 | 框架提供的对应能力(以LangChain为例) |
|---|---|
| 规划(Planning) | 内置的Chain(链)和Agent抽象,支持思维链(CoT)、ReAct等规划模式。 |
| 决策(Decision Making) | AgentExecutor 负责解析LLM输出的Action,决定调用哪个工具,并将Observation反馈给LLM,自动维护整个“思考-行动”循环。 |
| 工具选择 | 统一的Tool接口,开发者只需注册工具,框架负责将工具描述传给LLM,并处理调用。 |
| 记忆整合 | 提供Memory组件(如向量存储、对话缓冲区),自动将记忆注入上下文。 |
| 多步推理 | 支持复杂的SequentialChain、RouterChain等,编排多步骤任务。 |
Agent的底层原理
Agent的工作流程通常是一个循环,称为 “感知-规划-行动-观察”循环(或称 ReAct循环)
底层关键技术详解
1. ReAct 模式
这是Agent最核心的思维模式。它将推理痕迹(Thought) 和动作(Action) 交替输出。例如:
text
复制下载
Thought: 用户需要找聚会场地,我应该先搜索附近的场地。
Action: search_venues(location="北京", capacity=8, budget=2000)
Observation: 返回了3个符合条件的场地。
Thought: 第一个场地评论不错,但需要确认是否有餐饮服务。
Action: get_venue_details(venue_id=101)
...
模型通过这样的文本交互,一步步推进任务。
2. 规划与子目标分解
复杂任务需要分解成多个可执行的子任务。这可以通过:
- 思维树(Tree-of-Thoughts) :探索多种可能的分解路径,选择最优。
- LLM作为规划器:直接提示模型“请将任务分解为步骤”。
3. 自我反思与纠错
Agent在执行过程中可能失败(如工具调用出错、结果不符合预期)。高级Agent会:
- 记录失败原因
- 调整策略重试
- 甚至向用户请求澄清
4. 记忆的读写机制
- 写入:Agent可以在对话中主动将重要信息写入长期记忆(如调用
save_memory工具)。 - 读取:在规划阶段,Agent可以检索与当前任务相关的历史记忆,利用RAG技术。
5. 工具使用的统一接口
你之前学的Function Calling和MCP正是为此而生。Agent通过它们:
- 发现可用工具(MCP的
tools/list) - 按需调用(Function Calling的
tool_calls) - 处理结果
思维链(Chain-of-Thought, CoT) 和 ReAct模式(Reason + Act)
它们就像AI的两种思考方式:CoT是让AI学会“自言自语”地推理,ReAct则是让AI在推理的同时,还能动手执行外部操作。 下面这张表可以先帮你建立直观印象:
| 维度 | 思维链 (Chain-of-Thought, CoT) | ReAct 模式 (Reason + Act) |
|---|---|---|
| 核心思想 | 在给出答案前,显式地生成中间的推理步骤。 | 在推理的同时,与外部环境(如工具、API)交互并获取反馈。 |
| 关键词 | 思考、推理、解释 | 思考、行动、观察 |
| 工作方式 | 让模型把黑盒的思考过程,变成白盒的推理链输出出来。 | 通过“思考 (Thought) → 行动 (Action) → 观察 (Observation) ”的循环来推进任务。 |
| 主要目标 | 提升复杂推理任务的准确性和可解释性(如数学、常识推理)id=5312247)。 | 让模型能够解决需要多步推理和与外部世界交互的复杂任务(如信息查询、操作软件)。 |
| 与外部世界关系 | 不直接交互,完全依赖模型的内部知识。 | 紧密交互,通过调用工具来获取实时信息并执行动作。 |
| 典型应用 | 数学应用题、复杂逻辑推理、代码生成。 | 需要实时信息的问答(如“今天的天气”)、网页自动化操作、充当智能体(Agent)。 |
思维链 (Chain-of-Thought, CoT):让AI学会“自言自语”地思考
简单来说,思维链就是引导大模型在给出最终答案之前,生成一系列中间的推理步骤
1. 它是如何工作的?
CoT的核心是通过提示工程(Prompt Engineering) 来激发模型的推理能力,主要有两种方式
- 少样本学习(Few-shot CoT) :在提示词里给模型提供几个包含“问题-推理过程-答案”的完整示例,让模型学会这种“分步思考”的模式。
- 零样本学习(Zero-shot CoT) :更简单,只需要在问题后面加上一句“让我们一步一步地思考”,就能显著提升模型在复杂任务上的表现。这背后的原理是,这句话触发了模型在训练中学到的、按步骤生成答案的模式。
2. 它带来了什么效果?
实验数据表明,CoT技术能让大模型在推理任务上的准确率提升30%-50% 。更重要的是,它让模型的“思考过程”变得可解释、可调试,我们能看清它是在哪一步犯错的
ReAct模式 (Reason + Act):让AI能“边想边做”
如果说CoT让AI学会了思考,那么ReAct则在此基础上,赋予了它 “动手”的能力。ReAct这个名称本身就很传神,它是 “Reasoning”(推理)和“Acting”(行动) 的结合体
1. 核心机制:“思考-行动-观察”循环
ReAct的工作方式是一个清晰的循环:
- 思考 (Thought) :模型首先“自言自语”,分析当前状态,规划下一步该做什么。
- 行动 (Action) :根据思考,模型调用一个外部工具或API来执行具体动作,比如执行一次搜索
Search[xxx]或调用计算器Calculator[1+2]。 - 观察 (Observation) :系统执行完动作后,将结果(如搜索结果、API返回值)反馈给模型。
- 循环:模型根据“观察”到的新信息,再次进入“思考”,如此反复,直到能给出最终答案
2. 为什么它如此重要?
- 突破知识边界:ReAct让模型能实时查询外部信息,解决了模型知识“过期”或“不足”的问题。
- 降低幻觉:通过工具返回的事实来验证和修正自己的推理,能有效减少胡说八道。
- 实现复杂任务:它是构建AI Agent的基础模式,让AI能真正完成订票、查询、分析等需要多步操作的真实任务
CoT和ReAct并非替代关系,而是递进和互补的。
- CoT是ReAct的“思想内核” :ReAct中的“Thought”环节,本质上就是一个微型的、面向当前步骤的CoT。
- ReAct是CoT的“手脚延伸” :ReAct在CoT的基础上,增加了“Action”和“Observation”环节,让思考不再局限于“空想”,而是能与现实世界互动,并根据反馈动态调整计划
自我反思
“自我反思”在工程上实现的核心,就是让Agent拥有一个 “纠错-记忆”的闭环。它不只是让模型口头承认错误,而是要能定位错误、生成修正方案,并把经验存下来用于未来
Reflection(即时自我批判)
这是一种轻量级的实现,就像给每个任务配了一个临时的“校对员”,主要针对单次任务的输出进行优化
交互内自我批判:最常见的模式是 “生成 -> 批判 -> 修正” 的单次或少数几次循环。例如,你可以通过一个精心设计的提示词,让LLM在给出答案后,立即进行自我检查
技术要点:
- 批判标准:需要定义明确的批判维度,如正确性、完整性、成本、延迟等[-3]
- 成本控制:为了避免无限的反思循环和无谓的token消耗,工程上需要设置最大迭代次数,或当批判结果满足预设阈值(如置信度 > 0.9)时提前退出
Reflexion(持久化学习与记忆)
这是一种更强大的实现,旨在让Agent能够“吃一堑,长一智”。它的核心是将反思的结果沉淀下来,形成一个不断进化的经验知识库,供未来的任务参考。这可以拆解为三个核心环节:
-
记忆结构设计:关键在于存储什么。一个好的“经验教训”不应只是文本,而应是结构化的数据,方便检索和应用。一个典型的记忆条目可能包含:
- 任务签名 (Task Signature) :任务的唯一标识或特征向量(例如,一个复杂任务的Embedding)。
- 失败模式 (Failure Pattern) :描述哪里出错了(例如,“日期解析失败”)。
- 补救措施 (Remedy) :总结出的正确做法或解决方案(例如,“对于模糊日期,先检测地区格式”)。
- 置信度得分 (Confidence Score) :这条经验的可靠程度。
-
记忆的写入(经验沉淀) :在任务执行完毕后,如果任务失败或结果不理想,就触发一次“复盘”。系统会提取上述的结构化信息,并存入向量数据库或图数据库中。有些研究(如GUI-Reflection)甚至通过自动化数据管道,从成功的轨迹中逆向构造出“错误-修正”的样本数据,用于模型微调,从而习得反思能力。
-
记忆的读取与应用(指导未来) :当Agent开始执行一个新任务时,它会首先根据当前任务的特征(任务签名)去记忆库中检索最相关的历史经验。检索到的经验会作为系统提示词或上下文的一部分注入给LLM,指导其在新任务中避开曾经的坑
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 原型开发、内部工具 | 单模型自我批判 | 快速、简单、成本低 |
| 面向用户的通用产品 | 单模型自检 + 特定条件触发专门检查 | 平衡成本与质量 |
| 金融、医疗等高风险领域 | 多模型交叉验证 + 人工抽检 | 准确性优先 |
| 学术研究、复杂推理 | 多智能体辩论架构 | 探索能力上限 |
| 资源受限环境 | 只用单模型,但优化批判提示词 | 在限制内最大化效果 |
Agent记忆模块
记忆模块是Agent的“经验存储库”,让AI不仅能“思考”,还能“记住过去”
记忆的分类:短期 vs 长期
在工程上,我们通常将记忆分为两类,它们存储方式、生命周期和用途都不同:
| 类型 | 别称 | 生命周期 | 存储介质 | 主要作用 |
|---|---|---|---|---|
| 短期记忆 | 工作记忆、对话上下文 | 一次会话(几轮对话) | 内存(如列表、缓存) | 保持当前对话的连贯性,理解指代 |
| 长期记忆 | 知识记忆、经验库 | 跨会话、永久 | 向量数据库、图数据库、关系数据库 | 记住用户偏好、历史事实、任务经验 |
二、短期记忆:对话上下文管理
短期记忆就是当前会话的“工作区” ,通常包含最近几轮的用户消息和Agent回复。它的核心任务是让Agent能理解“你刚才说的”和“我之前提过的”。
2.1 实现方式
最朴素的方式是维护一个消息列表,每次请求时把整个列表传给LLM。但随着对话变长,会遇到两个问题:
- Token超限:LLM上下文窗口有限(如4k、8k、128k tokens)
- 成本飙升:长文本每次调用都收费
2.2 工程优化策略
| 策略 | 做法 | 适用场景 |
|---|---|---|
| 滑动窗口 | 只保留最近N轮对话(如最近10轮) | 通用对话,对早期信息依赖弱 |
| 对话摘要 | 定期将旧对话压缩成摘要,替换原始内容 | 需要长期记忆但不想保留细节 |
| 关键信息提取 | 提取重要实体/事实存入长期记忆,丢弃原文 | 问答型Agent,只关心事实 |
| 混合策略 | 窗口+摘要:保留最近几轮+之前对话的摘要 | 大多数生产系统 |
三、长期记忆:让Agent拥有“永生记忆”
长期记忆使Agent能记住跨会话的用户信息、领域知识、甚至自己过去的经验(如反思结果)。实现上分为存储和检索两部分。
3.1 存储结构
| 记忆类型 | 存储格式 | 检索方式 | 典型场景 |
|---|---|---|---|
| 事实性记忆 | 键值对 (key-value) | 精确查询 | 用户姓名、生日、配置选项 |
| 语义记忆 | 向量 (embedding) | 相似度检索 | 用户兴趣、对话片段、知识库 |
| 结构性记忆 | 图 (节点+关系) | 图查询 | 实体关系、知识图谱 |
| 经验性记忆 | 向量+元数据 | 相似度+过滤 | 反思教训、成功案例 |
常用技术栈:
- 向量数据库:Pinecone、Weaviate、Qdrant、Milvus,或轻量的Chroma、LanceDB
- 关系数据库:PostgreSQL(配合pgvector)、SQLite(配合sqlite-vss)
- 图数据库:Neo4j、Dgraph
3.2 记忆的写入(记什么?何时记?)
这是工程难点——需要决定哪些信息值得永久保存。常见策略:
- 显式用户指令:用户明确说“记住我喜欢靠窗的座位”
- 关键信息提取:对话中检测到新的实体/偏好(如通过NER或LLM判断)
- 任务完成总结:任务结束后,将关键结果存入记忆(如“上次查询了iPhone 15价格”)
- 失败反思:将错误教训存入记忆(如Reflexion模式)
| 记忆类型 | 可选技术 | 选型考量因素 |
|---|---|---|
| 短期记忆 | Redis、内存缓存、简单的列表 | 访问速度、容量上限、是否需要持久化 |
| 长期记忆(向量化) | Chroma(本地)、Pinecone(云)、Milvus(自托管)、Qdrant | 数据规模、查询性能、成本、运维复杂度 |
| 长期记忆(结构化) | PostgreSQL(+pgvector)、MongoDB | 是否需要混合查询(向量+结构化过滤)、事务支持 |
| 长期记忆(关系型) | Neo4j、Dgraph | 实体间关联复杂,需要图遍历 |
1. 向量化存储
向量化存储是指将文本、图像等非结构化数据通过嵌入模型(Embedding Model)转换为固定长度的向量(如 384维、1536维),然后将这些向量存储到专门的向量数据库中。查询时,将查询内容也转换为向量,通过计算向量间的相似度(如余弦相似度)来检索最相关的内容。
工作原理
- 写入:记忆内容 → 嵌入模型 → 向量 → 存入向量库,同时可附加元数据(如时间戳、用户ID)。
- 读取:用户查询 → 嵌入模型 → 查询向量 → 向量库进行近似最近邻搜索 → 返回最相似的记忆片段。
适用场景
- 语义搜索:需要根据意思找记忆,而非精确匹配(如“用户上次提到喜欢的餐厅风格”)。
- 开放域问答:从大量非结构化对话历史中检索相关片段。
- 个性化推荐:基于用户历史兴趣向量推荐内容。
- 反思经验检索:Reflexion中存储失败教训,按语义相似度检索。
优点
- 能捕捉语义相似性,即使关键词不匹配也能找到相关内容。
- 适合处理海量非结构化文本。
- 查询速度快(近似最近邻搜索)。
缺点
- 需要提前用嵌入模型向量化,有计算成本。
- 语义边界可能模糊,检索结果需要重排序来提升精度。
- 缺乏精确匹配能力(如按日期、ID精确查找)。
2. 结构化存储
结构化存储是指将记忆以键值对、表格行或文档的形式组织,每条记忆有明确的字段定义。查询时可以通过精确条件(如 user_id = 123)或范围条件进行过滤。
工作原理
- 写入:将提取的结构化信息(如用户姓名、偏好设置)存入数据库的特定字段。
- 读取:通过查询语言(SQL、MongoDB查询语法)按字段值精确或范围检索。
适用场景
- 用户显式信息:姓名、生日、联系方式、配置选项。
- 事实性记忆:“用户上次购买的商品ID”、“会员等级”。
- 对话状态:当前会话的临时变量(虽然属于短期记忆,但有时也需持久化)。
- 元数据存储:用于向量化记忆的过滤条件(如按时间范围检索向量)。
优点
- 查询精确,支持复杂条件组合(AND/OR、范围、排序)。
- 事务支持,保证数据一致性。
- 成熟的技术栈,运维工具丰富。
缺点
- 不擅长语义搜索,无法处理“意思相近但表述不同”的查询。
- 需要预先定义schema,扩展性相对较弱。
- 对非结构化文本支持差(除非配合全文索引,但效果不如向量)。
关系性存储(图存储)
关系性存储(图存储)是指将记忆表示为节点和关系,节点代表实体(如人、地点、事物),关系代表实体之间的连接(如“喜欢”、“位于”、“购买过”)。查询时通过图遍历算法来发现关联路径。
工作原理
- 写入:从对话中提取实体和关系,创建/更新图节点和边。
- 读取:通过图查询语言(如Cypher)进行模式匹配,查找多跳关系。
适用场景
- 知识图谱构建:需要存储实体间的复杂关联(如“张三的同事李四的妻子是王五”)。
- 多跳推理:Agent需要推导间接关系(如“推荐张三的朋友可能喜欢的餐厅”)。
- 社交关系:用户之间的关系网络。
- 业务逻辑规则:如权限继承、产品分类层级。
优点
- 擅长处理深度关联和多跳查询。
- 直观地表示复杂关系网络。
- 支持灵活的图算法(如最短路径、社区发现)。
缺点
- 对简单查询(如单实体属性)可能过度设计。
- 学习曲线较陡(需要掌握图查询语言)。
- 水平扩展相对复杂。
| 维度 | 向量化存储 | 结构化存储 | 关系性存储(图) |
|---|---|---|---|
| 数据本质 | 非结构化文本/图像的语义向量 | 结构化的事实/属性 | 实体间的复杂关联 |
| 查询方式 | 语义相似度搜索 | 精确匹配/范围过滤 | 图遍历/模式匹配 |
| 典型场景 | 对话历史检索、兴趣匹配 | 用户配置、交易记录 | 社交图谱、知识推理 |
| 优点 | 语义理解强,检索灵活 | 精确、可靠、事务支持 | 深度关联查询强大 |
| 缺点 | 缺乏精确性,需重排序 | 无法语义匹配,schema固定 | 学习成本高,简单查询笨重 |
| 常用技术 | Pinecone, Milvus, Chroma | PostgreSQL, Redis, MongoDB | Neo4j, Neptune |
实际项目中的组合策略
在一个成熟的Agent系统中,这三种存储往往同时存在,各司其职:
- 向量化存储:负责对话历史、用户兴趣、反思经验的语义检索。
- 结构化存储:负责用户基本资料、配置选项、精确的事实记录。
- 关系性存储:负责用户社交关系、知识图谱、复杂的业务规则链。
例如,当用户问“推荐一个和我口味相似的朋友喜欢的餐厅”:
- 图存储找出“口味相似的朋友”;
- 结构化存储获取这些朋友的餐厅历史记录;
- 向量化存储从餐厅描述中筛选符合当前偏好的语义。
这种组合能最大化发挥每种存储的优势,让Agent既懂语义,又精确,还能推理复杂关系。
Agent评估监控
为什么Agent需要专门的评估监控?
传统软件监控关注的是系统健康(CPU、内存、错误率),而Agent监控除了这些,还必须关注行为质量——因为Agent的输出不是预设的,而是动态生成的。你需要回答:
- 它做对了吗? (最终答案的正确性)
- 它做得高效吗? (用了多少步?花了多少token?)
- 它为什么这么做? (当出错时,是哪一步思考出了问题?)
- 它能更好吗? (如何从失败中学习?)
二、可观测性三大支柱在Agent中的体现
| 支柱 | 在Agent中的含义 | 示例 |
|---|---|---|
| 日志 (Logs) | 记录Agent的每一步思考、行动、观察 | 每个Thought/Action/Observation的完整文本 |
| 指标 (Metrics) | 聚合的数值化度量 | 成功率、平均步数、工具调用次数、token消耗 |
| 追踪 (Traces) | 串联一次完整请求的调用链 | 从用户输入到最终回答,经过的LLM调用、工具调用序列 |
Agent评估监控的核心工作
1. 链路追踪:记录“思考-行动”全过程
Agent的运行是一个多步循环,每一步都可能出错。你需要能像放电影一样回放整个过程。
具体实现:
-
为每个会话/请求生成唯一Trace ID,贯穿始终
-
记录每个步骤:
- 用户输入
- 系统提示词(可能包含记忆、工具描述)
- LLM的每次输出(包括Thought和Action)
- 工具调用的请求和响应
- 最终答案
-
存储为可检索的结构化数据(如JSON Lines存入对象存储,或导入日志系统)
指标定义:从海量数据中提炼关键信号
你需要定义一套衡量Agent质量的指标,并持续采集。
基础指标:
- 任务成功率:最终答案是否满足用户需求(需要人工或自动评估)
- 平均响应时间:从收到请求到返回完整答案的耗时
- token消耗:每次请求的总token数(输入+输出),直接关联成本
- 工具调用次数:平均调用几个工具,是否有无效调用
质量指标:
- 步数效率:实际步数 vs 预期最少步数(比如明明一步能查完,却绕了三步)
- 工具选择准确率:在需要调用工具的场景下,选对了工具吗?
- 错误恢复率:当工具调用失败时,Agent能正确处理的比例
- 用户反馈分:点赞/点踩、后续是否继续咨询等
3. 评估自动化:让机器评判“好坏”
你不能靠人工看每一条日志来评估。需要建立自动评估流水线。
常用方法:
- 规则检查:如答案是否包含“我不知道”但本应知道,或是否包含敏感词
- 模型打分:用更强或专门的LLM(如GPT-4作为裁判)对输出进行评分
- 对比测试:将输出与预期答案(如果有)计算相似度(ROUGE、BLEU、语义相似度)
- A/B测试:比较不同提示词、不同模型版本的效果差异
可视化与告警:让数据驱动决策
采集的数据要变成可理解的看板,并在异常时通知你。
常用工具:
- LangSmith:专为LLM应用设计的可观测平台,自动追踪链式调用,提供评估和调试界面
- Weights & Biases (WandB) :适合记录实验指标,对比不同版本
- 传统监控组合:Prometheus + Grafana(指标)+ Loki(日志)+ Tempo(追踪)
- 自定义看板:用Streamlit/Flask快速搭建内部调试面板
你需要关注:
- 成功率趋势:是否随着版本更新而下降?
- 成本趋势:token消耗是否异常上涨?
- 慢请求追踪:哪些请求耗时特别长?是工具调用慢还是LLM思考久?
- 失败模式聚类:自动将失败案例按错误类型分组,找出高频问题
| 维度 | 传统微服务监控 | AI应用监控 |
|---|---|---|
| 核心关注点 | 服务是否活着?响应快不快?报错多不多? | 模型输出的结果好不好? 推理过程对不对? |
| 主要指标 | QPS、延迟、错误率、CPU/内存使用率 | 答案正确率、步数效率、工具选择准确率、幻觉率 |
| 异常定义 | 5xx错误、超时、资源饱和 | 答非所问、逻辑错误、有害内容、浪费token |
| 排查方式 | 查看日志、调用链、 metrics | 需要回放思考过程(Trace)、评估语义质量 |
大模型框架
| 框架类别 | 框架名称 | 一句话总结 | 核心价值 | 与你之前所学知识的对应关系 |
|---|---|---|---|---|
| 核心应用框架 | LangChain | 全能型“乐高工厂”,提供构建 LLM 应用的全套组件。 | 抽象并标准化 Tools, Memory, Chains, Agent 等模块,并提供 LCEL 和 LangGraph 进行复杂流程编排 。 | 将我们讨论的所有模块(感知、大脑、记忆、工具) 都变成了可插拔的标准件。 |
| LlamaIndex | 专精 RAG 的“数据检索大师”,聚焦于数据和索引。 | 围绕 Document/Node/Index 提供了一套完整的 RAG 数据处理和检索链路,让连接私有数据变得极其简单 。 | 完美对应 长期记忆(向量化存储) 模块,并提供了标准化的实现。 | |
| 多智能体协作框架 | AutoGen | 微软出品的“专业协调员”,让 Agent 通过对话协同工作。 | 允许多个 Agent 通过对话交流,自主分工、调用工具,共同解决复杂问题 。 | 对应 大脑模块 在复杂任务下的多智能体协作形式。 |
| CrewAI | 角色扮演的“AI 梦之队”,像组建团队一样组建 AI。 | 通过定义不同的角色、目标和任务,让 AI 像人类团队一样分工协作 。 | 对应 大脑模块 的高级规划和角色分工。 | |
| 底层服务与部署框架 | vLLM | 极致吞吐的“性能发动机”,通过 PagedAttention 技术提升并发。 | 通过创新的 PagedAttention 技术大幅提升显存利用率和并发处理能力,吞吐量领先 。 | 为 工具模块 和 大脑模块 提供高性能、低成本的算力支持。 |
| TGI | 稳定可靠的“生产老兵”,Hugging Face 出品,开箱即用。 | 与 HF 生态无缝集成,提供量化、流式输出等企业级功能,部署简单、运行稳定 。 | 为 工具模块 和 大脑模块 提供稳定、可靠的推理服务。 |
LangChain
LangChain 本质上是一个“胶水框架”,它的核心原理可以概括为:提供一套标准化的接口和组件,让你能用“乐高积木”的方式,把大模型和各种工具、数据、逻辑拼接起来,构建复杂的 AI 应用。
它不是为了取代之前学的那些概念(如 RAG、Agent),而是为它们提供了工程化的最佳实践和实现方式。
LangChain 的定位:AI 应用的“积木盒”
LangChain 的定位:它位于你的应用代码和各种基础设施(模型、数据)之间,通过标准化的组件和编排语言,让你能灵活地构建上层应用
LangChain 的定位:它位于你的应用代码和各种基础设施(模型、数据)之间,通过标准化的组件和编排语言,让你能灵活地构建上层应用-8。
🧱 第一层:六大标准化组件
| 模块类别 | 核心作用 | 与你已学知识的对应 |
|---|---|---|
| 模型 I/O | 标准化地管理模型调用、提示词模板和输出解析。 | 对应大脑模块与外界交互的“语言”部分。 |
| 检索 (Retrieval) | 处理数据加载、切分、向量化和检索,是 RAG 的核心。 | 对应长期记忆(向量化存储) 和 RAG 的实现。 |
| 链 (Chains) | 将多个组件(如提示词、模型、其他链)组合成一个可复用的任务流程。 | 对应大脑模块中固定的、确定的流程编排。 |
| 代理 (Agents) | 实现 ReAct 等循环,让 LLM 动态决策并调用工具。 | 对应大脑模块中动态的、需决策的核心循环。 |
| 记忆 (Memory) | 在链或代理的多次调用之间传递和存储状态。 | 对应短期记忆和长期记忆的管理。 |
| 工具 (Tools) | 封装外部服务或 API,让代理可以调用。 | 对应行动模块,即具体的 Tools。 |
🔗 第二层:核心编排语言 LCEL
这是 LangChain 最新也是最核心的设计精髓。LCEL(LangChain Expression Language)是一种声明式的、基于管道操作符(|)的语法,用来组合上述各种组件。
-
核心逻辑:
输入|组件A|组件B|组件C。前一个组件的输出会自动成为下一个组件的输入。 -
示例:一个简单的问答链可以这样构建:
chain = prompt_template | model | output_parser这行代码就完成了一个完整流程:格式化提示词 -> 调用模型 -> 解析输出。
-
为什么重要:LCEL 让构建复杂流程变得像搭积木一样简单和直观。它提供了“开箱即用”的可靠性,自动支持流式输出、异步调用、重试和跟踪等高级功能。
🚀 第三层:从链到图的进化(LangGraph)
当你需要构建更复杂、非线性的应用(如带循环、分支或多智能体协作的 Agent)时,基础的链就不够用了。LangGraph 应运而生。
- 核心思想:LangGraph 将应用看作一个图(Graph) 。节点是你要执行的步骤(可以是一个函数、一个 LCEL 链或一个 Agent),边是节点之间的流转逻辑和控制条件。
- 适用场景:它非常适合构建需要精细控制的 Agent 工作流。实际上,LangChain 的 Agent 底层就是基于 LangGraph 实现的-3。你可以从简单的 Agent 入门,当需要更高级的自定义时,再深入到 LangGraph。
📊 额外的生产利器:LangSmith
当我们聊到 评估监控 时,LangSmith 就是 LangChain 生态给出的答案-2。它是一个生产级平台,专门用于:
- 追踪 (Tracing) :可视化地记录 Agent 的每一步“思考-行动-观察”循环。
- 调试与评估:查看详细的调用链、输入输出、token 消耗,并对不同版本的应用进行对比评估。
- 监控:上线后持续监控应用性能和质量。
原理总结:一个层层递进的体系
你可以把 LangChain 的原理理解为一个从具体到抽象的金字塔体系:
- 底层:提供各种标准化组件(模型、工具、记忆...),帮你把“零件”准备好。
- 中层:通过 LCEL 这个“魔法胶水”,让你能轻松地将零件组合成确定性的流程(链) 。
- 高层:对于需要动态决策的复杂流程,提供了 Agent 和 LangGraph,让你能构建出带循环和判断的非线性应用。
- 塔尖:最后,用 LangSmith 这个“监控室”来观察、评估和优化整个应用的运行。
LangChain记忆模块
LangChain的记忆模块是一个分层管理系统,其核心原理可以概括为:为LLM提供一个动态扩展的“外脑”,使其能够突破单次对话的上下文限制,通过“短期快速存取”和“长期语义检索”两种方式,维持对话的连贯性并积累知识
短期记忆 vs. 长期记忆:原理与实现对比
| 特性 | 短期记忆 (Short-term Memory) | 长期记忆 (Long-term Memory) |
|---|---|---|
| 核心原理 | 缓存与滑动窗口。将最近的对话历史直接拼接到提示词(Prompt)中,让LLM“看到”刚刚发生了什么。 | 向量化与语义检索。将历史信息通过嵌入模型转化为向量存入数据库,新问题来时,通过语义相似度召回最相关的信息,再注入提示词。 |
| 典型组件 | ConversationBufferMemory(完整缓存)、ConversationBufferWindowMemory(窗口缓存)、ConversationSummaryMemory(摘要缓存)、结合LangGraph的Store | VectorStoreRetrieverMemory、结合LangGraph的Store |
| 数据存储 | 内存 (RAM) 。以键值对或列表形式存在。 | 外部向量数据库(如Chroma、Pinecone、FAISS)或支持向量检索的传统数据库(如MongoDB、PostgreSQL)。 |
| 生命周期 | 一次会话。对话结束或窗口滑动后,数据即被清除。 | 跨会话、持久化。信息可以被永久保存,供未来所有对话使用。 |
| 数据形式 | 原始文本消息。 | 文本的向量表示 + 原始文本 + 元数据(时间戳、用户ID等)。 |
| 会计场景示例 | Agent与用户正在核对上个月的三笔报销单,它需要记住刚刚核对了哪几笔。 | Agent记得用户“张三”习惯在报销单的“备注”栏填写项目编号,下次帮他处理时就能自动按此格式操作。 |
短期记忆组件
| 记忆组件 | 核心原理 | 工作方式 | 优点 | 缺点 | 推荐使用场景(含会计示例) |
|---|---|---|---|---|---|
| ConversationBufferMemory | 完整缓存 | 将整个对话历史原封不动地保存在内存中,每次请求时将所有消息拼接到提示词中。 | - 信息无损失 - 实现简单,适合调试 | - 对话一长,token 消耗巨大 - 可能超出模型上下文窗口 | - 短对话(如单次发票咨询) - 开发测试阶段快速验证 |
| ConversationBufferWindowMemory | 滑动窗口 | 只保留最近 K 轮对话,超出窗口的旧消息被丢弃。 | - 控制 token 长度,防止无限增长 - 保留最关键的最新上下文 | - 关键信息可能因窗口限制而丢失 | - 多步操作流程(如报销审批:每一步依赖前一步结果) - 会计场景:核对多张发票时,只需记住最近几轮的核对状态 |
| ConversationSummaryMemory | 摘要缓存 | 每次交互后让 LLM 对历史进行摘要总结,将摘要作为上下文保留。 | - 极大压缩 token - 保留核心信息,适合超长对话 | - 有信息丢失风险 - 每次对话后需额外 LLM 调用,增加延迟和成本 | - 超长会话(如月度账务咨询,需持续记住项目背景) - 会计场景:跨日期的审计问答,需记住前期讨论的关键点 |
AgentExecutor
AgentExecutor 是 LangChain 中驱动 Agent 运转的“心脏”或“大脑执行官”。它负责维护我们之前深入讨论过的 ReAct 循环(思考-行动-观察),让大模型能够动态决策、调用工具,并最终完成任务
1. AgentExecutor 的核心职责:执行“思考-行动-观察”循环
-
输入:接收用户问题和当前状态(对话历史、已获取的信息)。
-
思考:调用 LLM,要求模型以特定格式输出(通常包含
Thought和Action)。 -
判断:解析 LLM 的输出:
- 如果是
Final Answer,则结束循环,返回结果给用户。 - 如果是
Action(如get_weather工具调用),则执行对应的工具。
- 如果是
-
观察:将工具执行的结果(
Observation)拼接到对话中。 -
循环:带着新的
Observation回到步骤 2,让 LLM 继续思考下一步,直到输出Final Answer或达到最大迭代次数。
2. AgentExecutor 具体是什么?
- 它是一个循环控制器:AgentExecutor 本身不执行工具,也不直接调用LLM,而是协调这两者。它从 LLM 拿到指令,调用你注册好的工具,把结果喂回去,再让 LLM 继续,如此反复。
- 它是一个状态维护者:在多次调用之间,它需要维护完整的对话历史(包括
Thought、Action、Observation),确保 LLM 拥有完整的上下文。 - 它是一个安全阀:它内置了最大迭代次数限制,防止 Agent 陷入无限循环。它还会处理一些异常情况,比如工具调用失败时如何反馈给 LLM。
总结:AgentExecutor 就是你之前理解的“解析与循环控制”这个概念的工程化实现。它把思考-行动-观察的循环固化成一个可复用的组件,让你无需手动处理每次 LLM 调用和工具执行的衔接。在 LangChain 中,它是所有 Agent 变体的共同执行引擎。
核心逻辑:就是 ReAct 循环
用最简单的伪代码描述,AgentExecutor 的核心就是一个 while 循环:
python
复制下载
def agent_executor(user_input):
# 初始化:把用户输入和可用工具告诉模型
messages = [system_prompt, human_input]
while iteration < max_iterations:
# 1. 思考(Reason):调用 LLM,获取下一步指令
llm_output = llm.invoke(messages)
# 2. 解析输出
if llm_output.contains("Final Answer"):
return llm_output.final_answer # 结束循环
# 3. 行动(Act):如果是 Action,解析出工具名和参数
action = parse_action(llm_output) # 例如 {"tool": "get_weather", "args": {"city": "北京"}}
# 4. 观察(Observe):执行工具,获取结果
observation = tools[action.tool].run(action.args)
# 5. 将观察结果拼回对话,继续循环
messages.append(observation)
iteration += 1
return "已达最大迭代次数,任务可能未完成"
补充:LangGraph 是 AgentExecutor 的进化版
当你需要更精细的控制(比如分支、循环、人机交互)时,LangGraph 提供了更底层的图执行引擎。可以理解为:
- AgentExecutor = 固定流程的循环控制器(适合大多数场景)
- LangGraph = 可自定义流程的图执行器(适合复杂业务流)
LangChain Expression Language (LCEL)
LCEL 是一种声明式的、基于管道操作符 | 的语言,用于将 LangChain 的各种组件(提示词、模型、解析器、检索器等)组合成可执行的流水线(Chain)。
AI 世界的 Unix 管道——就像在命令行里用 | 把多个命令串起来一样,LCEL 让你用同样的思维方式组装 AI 组件
# Unix 管道:一个命令的输出是下一个命令的输入
cat file.txt | grep "error" | sort | uniq -c
# LCEL 管道:一个组件的输出是下一个组件的输入
chain = prompt | model | output_parser
你能解决什么问题:用简洁的代码构建可复用的链,比如将检索、提示、模型调用、输出解析组合成一个 RAG 链,并支持流式输出、异步执行、重试等高级特性。
LCEL 的核心语法
1. 最基本的管道:组件1 | 组件2 | 组件3
这是 LCEL 最核心的用法。每个组件都必须是 Runnable(可运行对象),LangChain 的大部分核心组件都已经实现了 Runnable 接口
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# 1. 定义组件
prompt = ChatPromptTemplate.from_template("讲一个关于 {topic} 的笑话")
model = ChatOpenAI(model="gpt-4")
parser = StrOutputParser()
# 2. 用 | 组合成链
chain = prompt | model | parser
# 3. 调用链
result = chain.invoke({"topic": "程序员"})
print(result)
2. 更复杂的组合:RunnableParallel 和 RunnablePassthrough
当你需要并行处理或保留原始输入时,可以使用这两个工具
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
# 创建一个并行任务:同时做两件事
parallel_chain = RunnableParallel(
# 任务1:调用检索器获取上下文
context = retriever,
# 任务2:把原始问题原样传递下去
question = RunnablePassthrough()
)
# 组合进完整流程
rag_chain = parallel_chain | prompt | model | parser
LCEL 的底层原理:Runnable 协议
LCEL 能工作的基础是 Runnable 协议——所有用 | 连接的组件都必须实现这个统一的接口
Runnable 接口定义了四个核心方法,这意味着你构建的任何 LCEL 链都自动获得这些能力:
| 方法 | 作用 | 适用场景 |
|---|---|---|
.invoke() | 同步调用 | 普通的单次请求 |
.ainvoke() | 异步调用 | 需要并发处理多个请求 |
.stream() | 流式输出 | 逐字返回生成结果,提升用户体验 |
.batch() | 批量处理 | 同时处理多个输入,提高吞吐量 |
AI Agent 上下文管理
1. 什么是上下文管理?
在 AI Agent 中,上下文 是指 Agent 在处理用户请求时所依赖的全部信息,包括:
- 对话历史:用户和 Agent 之间的历史消息。
- 状态信息:当前任务进度、已执行的操作、中间结果。
- 外部知识:从知识库检索到的相关文档、记忆。
- 工具定义:可用的工具及其描述。
- 用户偏好:长期记忆中存储的个人化信息。
上下文管理 就是对这些信息的 收集、组织、存储、检索、更新和裁剪 的过程,确保 LLM 在每次调用时都能获得最相关、最必要的上下文,同时不超过模型的上下文窗口限制。
2. 为什么上下文管理如此重要?
| 挑战 | 无良好管理的后果 | 管理良好的价值 |
|---|---|---|
| LLM 上下文窗口有限 | 长对话被截断,丢失关键信息 | 通过摘要或检索保留核心内容 |
| 无关信息干扰 | 模型被噪声干扰,回答质量下降 | 只注入最相关的上下文 |
| 成本爆炸 | 每次请求都塞入大量 token | 减少 token 消耗,降低成本 |
| 多轮一致性 | Agent 忘记之前的约定 | 保持对话连贯性 |
| 个性化缺失 | 每次都是“陌生人” | 记住用户偏好,提供个性化服务 |
3. 上下文管理的四层体系
第一层:短期对话上下文
-
内容:最近几轮用户和 Agent 的对话。
-
管理策略:
- 滑动窗口:保留最近 K 轮对话(如
ConversationBufferWindowMemory)。 - 对话摘要:当对话过长时,用 LLM 生成摘要替换旧对话(如
ConversationSummaryMemory)。 - 混合策略:保留最近 N 轮 + 之前对话的摘要。
- 滑动窗口:保留最近 K 轮对话(如
-
LangChain 实现:
ConversationBufferWindowMemory、ConversationSummaryMemory、ConversationTokenBufferMemory。
第二层:任务状态上下文
-
内容:当前多步任务中的进度、已收集的信息、中间结果。
-
管理策略:
- 状态对象:维护一个状态字典,在每一步更新。
- 图状态管理:LangGraph 的
StateGraph自动管理节点间的状态传递。 - 工具调用结果:每次工具返回的
Observation拼接到上下文中。
-
LangChain 实现:
AgentExecutor内部维护消息列表,LangGraph 的State。
第三层:长期记忆上下文
-
内容:跨会话的用户信息、偏好、历史经验。
-
管理策略:
- 向量存储:将重要信息向量化,需要时语义检索。
- 结构化存储:用户属性用键值对存储。
- 写入时机:用户显式告知、关键信息提取、任务完成总结。
-
LangChain 实现:
VectorStoreRetrieverMemory、EntityMemory。
第四层:外部知识上下文
-
内容:从知识库、文档、API 实时检索的信息。
-
管理策略:
- RAG:每次请求时检索相关文档片段。
- 工具调用结果:工具返回的数据作为临时知识。
- 缓存:相同查询可缓存检索结果。
-
LangChain 实现:检索器组件、
create_retrieval_chain。
缓存机制对比:常规业务 vs AI Agent 场景
| 维度 | 常规业务缓存(如 Web 应用) | AI Agent 缓存 | 核心差异 |
|---|---|---|---|
| 缓存对象 | 数据库查询结果、API 响应、计算密集型结果 | 嵌入向量、大模型响应、检索结果 | AI 场景缓存的是“语义计算结果”而非确定性数据 |
| 缓存粒度 | 通常是完整的响应(如用户信息、商品详情) | 可以是分块缓存(如文本块的嵌入)或完整结果缓存 | 更细粒度,需要组合使用 |
| 缓存键设计 | 基于请求参数(如 user_id=123) | 基于语义内容(文本的哈希值或内容本身) | 相同文本在不同请求中应返回相同嵌入 |
| 一致性要求 | 强一致性(用户修改后需立即更新) | 弱一致性可接受(模型版本升级可能导致嵌入变化) | AI 缓存可以容忍一定程度的“过时” |
| 失效策略 | 基于时间(TTL)或主动失效(更新时删除) | 基于模型版本或业务规则(如简历更新) | 失效条件更复杂 |
| 命中率考量 | 高重复性请求(如热门商品页) | 相同或相似文本的重用(如常见问题) | 语义相似性也可能命中(需要近似匹配缓存) |
AI Agent 场景的特殊缓存设计
1. 嵌入向量缓存(最常用)
from functools import lru_cache
@lru_cache(maxsize=1024)
def cached_embedding(text: str) -> list[float]:
"""缓存文本的嵌入向量,相同文本只调用一次 API"""
return _get_embedding(text) # 原生 API 调用
适用场景:同一份简历的不同检索查询可能用到相同的查询文本,或者多份简历中有相同的关键词(如“项目管理”)。
2. 检索结果缓存
# 缓存检索到的文本块,避免重复检索
retrieval_cache = {} # 可用 Redis 等替代
def get_cached_retrieval(query_embedding_hash: str, resume_id: str) -> list[str] | None:
cache_key = f"{resume_id}:{query_embedding_hash}"
return retrieval_cache.get(cache_key)
def set_cached_retrieval(query_embedding_hash: str, resume_id: str, chunks: list[str]):
cache_key = f"{resume_id}:{query_embedding_hash}"
retrieval_cache[cache_key] = chunks # 可设置 TTL
完整评价结果缓存
def get_cached_evaluation(resume_id: str, industry: str, role: str) -> ResumeEvaluationResult | None:
"""检查是否已有相同输入的评价结果"""
cache_key = f"eval:{resume_id}:{industry}:{role}"
return cache.get(cache_key)
def cache_evaluation(resume_id: str, industry: str, role: str, result: ResumeEvaluationResult):
cache_key = f"eval:{resume_id}:{industry}:{role}"
cache.set(cache_key, result, ttl=3600)
缓存失效策略的特殊考虑
在 AI 场景中,缓存失效不仅基于时间,还要考虑:
- 模型版本更新:如果更换了嵌入模型(如从
v2升到v3),所有嵌入缓存应失效。 - 简历内容更新:如果用户重新上传简历,对应
resume_id的所有缓存应删除。 - 评价标准变化:如果修改了 prompt 或评分维度,历史评价结果可能不再适用。
解决方案:在缓存键中加入版本号或内容哈希:
EMBEDDING_MODEL_VERSION = "text-embedding-v3"
CACHE_KEY_PREFIX = f"eval:v1" # 评价逻辑版本
def get_cache_key(resume_id: str, industry: str, role: str) -> str:
return f"{CACHE_KEY_PREFIX}:{resume_id}:{industry}:{role}"
AI 场景缓存更关注语义重用(相同文本的嵌入、相似查询的检索)、细粒度组合(嵌入 + 检索 + 评价),以及版本化失效(模型/提示词升级)。
嵌入批量生成的核心是:
- 使用
embed_documents而非循环调用embed_query - 结合缓存避免重复计算相同文本
- 根据数据规模选择实时批量或离线批量推理
ai agent中的重试与降级
AI 应用(如你的简历系统)面临不同的挑战:
| 挑战 | 说明 | 示例 |
|---|---|---|
| 调用成本高 | 一次失败可能浪费大量 token 或时间 | 大模型调用中途失败,已消耗的 token 无法挽回 |
| 结果不确定性 | 重试可能得到不同结果 | 同样 prompt 两次调用可能输出略有差异 |
| 降级结果难替代 | 无法简单用缓存代替实时生成 | 简历评价必须基于最新简历,不能返回旧结果 |
| API 限制多样 | 限流(rate limit)、超时、模型不兼容等 | 阿里云 QPS 限制、模型负载过高 |
| 维度 | 常规业务重试/降级 | AI 应用重试/降级 | 差异点 |
|---|---|---|---|
| 重试成本 | 低(接口调用轻量) | 高(每次调用消耗 token/时间) | AI 重试可能造成更大损失 |
| 结果确定性 | 幂等接口,重试结果相同 | 非确定性,重试可能结果不同 | 需考虑结果一致性 |
| 降级难度 | 容易(返回缓存/默认值) | 困难,无法简单用旧结果替代 | AI 输出高度依赖上下文 |
| 超时处理 | 直接失败或重试 | 需权衡等待时间与成本 | 大模型调用可能长达数十秒 |
| 限流应对 | 简单等待后重试 | 需精细控制,避免积压 | AI API 限流更严格 |
LangSmith vs Prometheus/Grafana 的核心差异
监控维度的根本不同
| 维度 | LangSmith | Prometheus/Grafana |
|---|---|---|
| 核心关注点 | 定性监控:模型输入输出、推理逻辑、工具调用链、评估分数 | 定量监控:QPS、延迟、错误率、资源利用率 |
| 监控对象 | Prompt内容、Token消耗、Agent决策路径、RAG检索结果 | CPU/内存/GPU、网络IO、请求量、服务状态 |
| 数据性质 | 语义级数据(结构化日志 + 追踪) | 时序指标(数值聚合) |
| 典型问题 | “为什么这个请求回答错误?” “Agent为什么选择了这个工具?” “这个Prompt版本效果如何?” | “服务响应变慢了吗?” “GPU利用率是否饱和?” “错误率是否超过阈值?” |
一个具体例子:你的简历系统
在你的简历评价系统中,两者的分工应该是:
LangSmith 负责:
- 记录每次评价的完整执行链路:
_retrieve → _skills → _evaluate - 查看每个节点的输入输出(检索到的证据、信号JSON、最终评价结果)
- 查看大模型调用详情:Prompt内容、Token消耗、响应时间
- 对比不同版本的评价效果(如修改Prompt后的变化)
Prometheus/Grafana 负责:
- 监控
/api/resumes/upload和/evaluate接口的QPS、P99延迟 - 监控嵌入API调用失败率、重试次数
- 监控Token消耗趋势,预估成本
- 告警:当错误率超过5%时通知值班工程师
LangSmith 的追踪结束判断机制
1. 根调用(Root Run)的完成
每次请求在 LangChain/LangGraph 中都有一个根调用。在你的简历评价系统中:
- 根调用:
evaluate_resume函数调用 - 子调用:
_retrieve→_skills→_evaluate每个节点的执行
当根调用函数执行完毕并返回结果时,LangSmith 就知道这次请求结束了。
代码层面的表现:
def evaluate_resume(resume_id: str, industry: str, role: str) -> ResumeEvaluationResult:
# 这是根调用开始
initial_state = {...}
out = GRAPH.invoke(initial_state) # 这里面会有多个子调用
# 当这行执行完毕,返回 result 时,根调用结束
return out["result"]
2. 回调机制(Callback)
LangChain 内部有一个完善的 回调系统,每次执行开始时都会触发 on_chain_start,结束时触发 on_chain_end:
# LangChain 内部简化逻辑
class BaseCallbackHandler:
def on_chain_start(self, serialized, inputs, **kwargs):
# 记录开始时间、输入
pass
def on_chain_end(self, outputs, **kwargs):
# 记录结束时间、输出,计算耗时
# 此时知道这个节点执行完毕
pass
def on_chain_error(self, error, **kwargs):
# 如果出错,记录错误
pass
LangSmith 通过注册自己的回调处理器,监听这些事件,从而知道每个节点何时开始、何时结束。
3. 嵌套调用的追踪
对于嵌套调用(比如 GRAPH.invoke 内部又调用多个节点),LangSmith 通过父子关系来追踪:
# 伪代码表示调用栈
evaluate_resume (根调用)
├─ GRAPH.invoke (子调用1)
│ ├─ _retrieve (子调用1.1)
│ ├─ _skills (子调用1.2)
│ └─ _evaluate (子调用1.3)
└─ return result (根调用结束)
只有当所有子调用都完成后,根调用才会结束,此时 LangSmith 知道整个 trace 完成。
4. 异步和流式处理
对于异步或流式输出,LangSmith 的处理更精细:
- 开始:收到第一个 token 时记录开始
- 结束:收到最后一个 token 或连接关闭时记录结束
- 中间状态:可以记录流式输出的片段
5. 超时和错误情况
如果请求异常中断:
- 错误:
on_chain_error被触发,记录错误信息 - 超时:LangChain 客户端会触发超时异常,同样被回调捕获
- 手动中断:如果程序被 kill,可能来不及上报,但 LangSmith 会在服务端通过心跳机制发现连接中断
🧠 在你的简历系统中的实际表现
当你运行一次评价请求时,LangSmith 看到的执行树是这样的:
Root Run: evaluate_resume
├─ Run: GRAPH.invoke
│ ├─ Run: _retrieve
│ │ ├─ Run: _get_embedding (LLM调用)
│ │ └─ Run: search_resume_chunks (工具调用)
│ ├─ Run: _skills
│ │ ├─ Run: load_resume_raw_text (文件读取)
│ │ └─ Run: extract_signals_from_text (规则函数)
│ └─ Run: _evaluate
│ └─ Run: structured.invoke (大模型调用)
└─ (返回结果)
每个节点都有:
- 开始时间:
start_time - 结束时间:
end_time - 耗时:自动计算
- 状态:成功/失败
OpenSpec
规范驱动开发的 AI 编程新范式
它是一套轻量级的规范驱动开发框架,让 AI 成为需求的“执行者”而非“创造者”。通过将规范(Specs)与代码同仓管理,OpenSpec 实现了需求、设计、任务、代码、测试的完整闭环。
OpenSpec 的核心价值
| 痛点 | OpenSpec 解法 |
|---|---|
| AI 偏离需求 | 先写规范(Spec),AI 严格按规范生成代码 |
| 需求与代码脱节 | 规范与代码同版本管理,自动归档 |
| 变更不可追溯 | 每个变更独立目录,完整保留 proposal、design、tasks |
| 团队协作困难 | 规范是“单一事实来源”,评审只需看 proposal + delta |
| 技术债积累 | 强制测试任务,归档前验证,规范随代码演进 |
目录结构与文件说明
your-project/
├── AGENTS.md # 根目录:AI 工作流指引(告诉 AI 何时使用 OpenSpec)
├── openspec/
│ ├── specs/ # 【真相来源】系统当前已实现的所有能力
│ │ └── [capability]/ # 按能力模块划分
│ │ └── spec.md # 该能力的规范(BDD格式)
│ │
│ ├── changes/ # 【施工现场】正在进行中的变更提案
│ │ └── [change-name]/ # 每个变更一个独立目录
│ │ ├── proposal.md # 为什么要改?改什么?
│ │ ├── design.md # 技术方案(可选)
│ │ ├── tasks.md # 实施任务清单
│ │ └── specs/ # 规范增量(相对于 specs/ 的变更补丁)
│ │ └── [capability]/
│ │ └── spec.md
│ │
│ └── changes/archive/ # 【历史档案】已完成的所有变更
│ └── YYYY-MM-DD-[name]/ # 完整保留 proposal、design、tasks、specs
│
├── .cursor/ # 如果你使用 Cursor,会自动生成 Slash 命令
│ └── commands/
│ ├── opsx-explore.md
│ ├── opsx-ff.md
│ ├── opsx-apply.md
│ └── ...
└── .codebuddy/ # 如果你使用 CodeBuddy,相关配置会放在这里
关键文件解读
| 文件 | 内容 | 为什么重要 |
|---|---|---|
specs/<capability>/spec.md | BDD 格式的系统行为描述 | 单一事实来源,AI 和人类共享 |
proposal.md | 变更的“为什么”和“改什么” | 评审入口,强制思考价值 |
tasks.md | 可执行的 checkbox 任务清单 | AI 按此逐步实现,进度可追踪 |
specs/ 增量 | 本次变更新增/修改的场景 | 清晰界定范围,归档时自动合并 |
AGENTS.md | AI 工作流指令 | 让 AI 自动遵守 OpenSpec 规范 |
| 命令 | 用途 |
|---|---|
/opsx:explore | 探索模式,与 AI 讨论需求,不产生文件 |
/opsx:new "功能名" | 创建变更,逐步引导填写文档 |
/opsx:ff "功能名" | 快速模式:一次性生成 proposal、specs、design、tasks |
/opsx:continue | 继续生成下一个缺失的文档(配合 /opsx:new 使用) |
/opsx:apply "变更名" | 开始按 tasks.md 实现代码 |
/opsx:verify "变更名" | 验证代码实现与规范是否一致 |
/opsx:archive "变更名" | 归档完成的变更 |
Spec-Driven Development
规范驱动开发(Spec-Driven Development,简称 SDD)是一种以规范(Specification)为核心的软件开发方法论。核心理念是:
在编写任何代码之前,先以机器可读、人类可理解的方式,精确描述系统“应该做什么”,然后将规范作为唯一真相来源,驱动后续的设计、实现、测试和维护。
用一句话概括:规范即代码,代码即规范的实现。
Spec-Driven Development 的解决方案
SDD 通过“规范先行”+“规范即代码”+“AI 可读”三大原则,系统性地解决上述问题:
- 规范与代码同版本管理:任何时候 checkout 到某个 commit,都能看到当时的完整规范
- 规范作为 AI 的“合同” :AI 不再是自由发挥,而是严格按规范执行
- 变更有完整上下文:每个功能都对应独立的 proposal、specs、tasks,可追溯完整决策过程
- 测试从规范自动生成:规范中的场景(Scenario)直接映射为测试用例
- 规范是团队的唯一真相来源:产品、开发、测试共享同一份规范
Spec-Driven Development 的核心原则
1. 规范即真相(Specs as Source of Truth)
规范文件是系统行为的唯一权威描述。代码只是规范的实现,文档是规范的派生。
- 要了解系统能做什么 → 看
specs/ - 要修改系统行为 → 先修改规范,再修改代码
- 要评审需求 → 看
proposal.md+ 规范增量
2. 规范即代码(Specs as Code)
规范文件与代码文件同级管理,受版本控制,参与 CI/CD:
- 规范变更必须通过 PR 流程
- CI 可以自动验证规范格式、检查与代码的一致性
- 规范文件可以像代码一样进行 diff、review、blame
3. 变更即 PR(Change as PR)
每个功能、重构或 bug 修复,都对应一个独立的变更提案(Change Proposal),包含:
proposal.md:为什么要改?(业务价值、影响范围)specs/:规范增量(哪些场景新增/修改)design.md:技术方案(复杂变更需要)tasks.md:可执行的任务清单
4. 增量演进(Incremental Evolution)
系统规范不是一次性写成的,而是随变更增量演进:
- 每个变更只描述增量(新增/修改的场景)
- 归档时,增量被智能合并到主规范
- 主规范始终保持完整、干净、可读
5. AI 原生设计(AI-Native)
规范文件的格式和结构经过专门设计,让 AI 能够:
- 精确理解:BDD(行为驱动开发)格式的场景描述,AI 可准确理解预期行为
- 可靠执行:tasks.md 的 checkbox 格式,AI 可逐项完成并追踪进度
- 自主验证:AI 可以对比规范与代码,发现偏差并修正
Spec-Driven Development 的工作流
SDD 通常遵循 “提案 → 规范 → 设计 → 任务 → 实现 → 验证 → 归档” 的七阶段工作流:
各阶段产出物
| 阶段 | 产出物 | 作用 |
|---|---|---|
| 提案 | proposal.md | 记录“为什么改、改什么” |
| 规范 | specs/ 增量 | 描述系统行为的变化(BDD 格式) |
| 设计 | design.md | 技术方案、架构决策(可选) |
| 任务 | tasks.md | 可执行的 checkbox 清单 |
| 实现 | 代码文件 | 规范的实现 |
| 测试 | 测试文件 | 从规范场景映射的测试用例 |
| 验证 | 验证报告 | 规范与代码一致性检查 |
| 归档 | 归档目录 | 历史决策的完整记录 |
- OpenSpec:负责 “定义契约” ——把需求转化为结构化的规范(Specs)、设计文档(Design)和任务清单(Tasks),回答“我们要做什么、为什么要做”。
- Superpowers:负责 “执行契约” ——在代码实现阶段强制遵循工程纪律(如 TDD、代码审查、子代理并行工作),确保“我们怎么做才规范、可靠”。
在实际工作流中,你可以先用 OpenSpec 的 /opsx:ff 生成完整的变更提案和任务清单,然后在 Cursor 或 CodeBuddy 中让 Superpowers 的技能(如 TDD、代码审查)接管实施过程,这样既保证了需求的精确性,又保证了代码的规范性和可靠性。