从 RAG 到 ReAct:继续理解 Agent 的“记忆、工具与行动能力
今天的 Agent 学习总结:从 RAG 到 ReAct,理解智能体的记忆与工具调用
一、今天学习的整体方向
前几天我主要学习了 Agent 开发中的一些基础概念,比如:
Prompt
Tool
Function Calling
MCP
Skill
RAG
Embedding
Vector Store
这些内容让我对 Agent 有了一个初步认识:
Agent 并不是简单地调用大模型接口,而是要让大模型具备一定的“任务理解能力”和“工具使用能力”。
今天的学习是在前几天的基础上继续递进,重点放在了几个更具体的问题上:
1. RAG 在 Agent 中到底起什么作用?
2. ReAct 模式为什么适合做对话型 Agent?
3. Agent 是如何选择和调用工具的?
4. 多轮对话记忆是怎么实现的?
5. 最近窗口加载和 Token 限制有什么关系?
今天最大的收获是:
Agent 的核心不只是“回答问题”,而是能够结合上下文、调用工具、检索知识,并根据任务一步步完成目标。
二、重新理解 RAG:给 Agent 补充外部知识
前几天学习 RAG 时,我知道它的基本作用是:
让大模型能够结合外部资料回答问题。
今天进一步理解到,RAG 在 Agent 系统中更像是一个知识检索工具。
大模型本身的知识来自训练数据,但训练数据存在几个问题:
1. 可能不是最新的
2. 不包含企业内部资料
3. 不包含用户上传的文档
4. 不知道具体项目中的业务规则
所以如果我们希望 Agent 回答某些私有知识、业务文档、接口说明、运维手册等内容,就需要通过 RAG 给它补充上下文。
RAG 的基本流程可以理解为:
文档上传
↓
文档解析
↓
文本切分
↓
生成 Embedding 向量
↓
存入向量数据库
↓
用户提问
↓
问题向量化
↓
相似度检索
↓
召回相关文本片段
↓
将文本片段和问题一起交给大模型
↓
生成最终回答
所以 RAG 并不是让大模型重新训练,也不是直接修改大模型参数,而是在提问时临时给大模型补充相关资料。
可以这样理解:
大模型负责理解和生成
RAG 负责查找外部知识
Embedding 负责语义向量化
向量数据库负责相似度检索
三、Chunk Size 和 Overlap 的理解
今天还继续理解了 RAG 中的文本切分问题。
文档通常不会直接整篇交给大模型,因为文档可能很长,超过上下文窗口限制。
所以需要把文档拆成一个个小块,也就是 Chunk。
1. Chunk Size 是什么?
chunk size 指的是每个文本块的大小。
例如一篇文档有 5000 个 token,我们可以按照一定大小切成多个 chunk:
chunk 1:第 1 - 500 token
chunk 2:第 501 - 1000 token
chunk 3:第 1001 - 1500 token
这样做的好处是:
1. 便于向量化
2. 便于相似度检索
3. 避免一次性塞入过长文本
4. 提高检索的精确度
2. Overlap 是什么?
overlap 指的是相邻两个 chunk 之间保留一部分重复内容。
比如:
chunk 1:第 1 - 500 token
chunk 2:第 451 - 950 token
chunk 3:第 901 - 1400 token
这里 chunk 1 和 chunk 2 之间就有 50 个 token 的重叠。
为什么要这样做?
因为如果完全硬切,可能会把一个完整语义拆开。
例如原文是:
Prometheus 可以采集服务的 CPU、内存、QPS 等指标,
这些指标可以用于判断服务是否存在异常。
如果刚好从中间切开,可能变成:
chunk 1:Prometheus 可以采集服务的 CPU、内存、QPS 等指标
chunk 2:这些指标可以用于判断服务是否存在异常
第二个 chunk 中的“这些指标”指代不清楚。
加入 overlap 之后,第二个 chunk 会带上一部分前文内容,让语义更加完整。
所以 overlap 的作用是:
防止语义被切断
保留上下文衔接
提高检索结果质量
让大模型获得更完整的背景
四、RAG 不是每次都必须使用
今天还理解了一个很重要的问题:
引入 RAG 之后,Agent 是不是每次回答都必须先查知识库?
答案是不一定。
如果用户问的是通用知识,比如:
Java 中 HashMap 的底层原理是什么?
什么是 HTTP?
什么是线程池?
这些内容大模型本身通常就可以回答,不一定需要查知识库。
但是如果用户问的是和外部资料、私有文档、上传文件相关的问题,例如:
这个文档里是怎么描述部署流程的?
系统异常码 1001 表示什么?
某个接口的业务规则是什么?
这时候就需要使用 RAG。
所以更合理的理解是:
通用问题:大模型直接回答
私有知识问题:通过 RAG 检索后回答
实时数据问题:通过工具查询后回答
这也说明 Agent 不应该只是固定地走一条流程,而应该根据用户问题判断是否需要调用外部能力。
五、从 Function Calling 到 Tool:让 Agent 具备行动能力
前几天学习 Function Calling 时,我理解到大模型可以返回一个结构化调用请求,例如:
{
"name": "getWeather",
"arguments": {
"city": "北京"
}
}
今天继续理解后发现,Function Calling 和 Tool Calling 本质上是在解决同一个问题:
如何让大模型从“只会生成文字”,变成“可以调用外部能力”。
普通的大模型只能回答:
我不知道当前天气。
但是接入工具后,它可以先调用天气工具,然后根据工具结果回答:
北京今天多云,气温约为……
在 Agent 中,工具可以有很多种:
知识库检索工具
天气查询工具
时间查询工具
数据库查询工具
日志查询工具
监控指标查询工具
文件解析工具
这些工具本质上可能只是后端中的普通方法,但当它们注册给大模型后,大模型就可以根据用户意图选择是否调用。
所以 Tool 的意义不是“多写几个方法”,而是给 Agent 增加外部行动能力。
可以这样理解:
Prompt 让模型知道要做什么
Tool 让模型知道可以怎么做
Function Calling 让模型能够表达要调用哪个工具
后端程序负责真正执行工具
六、ReAct:让 Agent 具备“思考 + 行动”的过程
今天学习中最关键的一个概念是 ReAct。
ReAct 可以拆成两个部分:
Reasoning + Acting
也就是:
推理 + 行动
普通问答模型的流程是:
用户提问
↓
大模型回答
而 ReAct Agent 的流程更像是:
用户提问
↓
模型分析问题
↓
判断是否需要工具
↓
选择合适工具
↓
调用工具
↓
观察工具返回结果
↓
继续推理
↓
生成最终回答
这就是 Agent 比普通大模型应用更进一步的地方。
例如用户问:
帮我分析一下为什么服务响应变慢。
普通大模型可能只能给出一些通用建议:
可能是 CPU 高、数据库慢、网络延迟、接口阻塞等。
但 ReAct Agent 可以进一步行动:
1. 先查询监控指标
2. 再查询最近日志
3. 根据错误信息判断可能原因
4. 最后生成排查建议
所以 ReAct 的价值在于,它让模型不仅能“想”,还能“做”。
七、多轮对话记忆:让 Agent 不只是单轮问答
今天还学习了 Agent 的多轮对话记忆。
如果没有记忆,模型每次只能看到当前这一句话。
例如:
用户:我正在学习 RAG。
AI:RAG 是检索增强生成……
用户:那它和 Embedding 有什么关系?
如果没有历史上下文,模型可能不知道“它”指的是 RAG。
所以 Agent 需要保存一定的对话历史,让模型理解上下文。
最简单的实现方式可以是:
sessionId
↓
保存当前会话的历史消息
↓
每次请求时加载最近几轮对话
↓
和用户新问题一起发送给大模型
这里的 sessionId 很重要,它用来区分不同会话。
例如:
sessionId = A:讨论 RAG
sessionId = B:讨论 Tool Calling
sessionId = C:讨论 Docker
如果没有 sessionId,不同会话的上下文可能混在一起,导致模型回答混乱。
所以 sessionId 的作用可以理解为:
区分会话
隔离上下文
支持多轮对话
避免历史串扰
八、最近窗口加载:解决上下文过长问题
今天还理解了“最近窗口加载”。
既然要支持多轮对话,那是不是应该把所有历史记录都传给大模型?
答案是否定的。
因为大模型有上下文窗口限制。
历史消息越多,占用的 token 就越多。
如果把所有历史都放进去,可能会带来几个问题:
1. Token 消耗变大
2. 请求成本上升
3. 响应速度变慢
4. 可能超过模型上下文限制
5. 太久远的历史还可能干扰当前回答
所以通常会采用最近窗口加载。
比如只保留最近 6 轮对话:
第 1 轮:丢弃
第 2 轮:丢弃
第 3 轮:保留
第 4 轮:保留
第 5 轮:保留
第 6 轮:保留
第 7 轮:保留
第 8 轮:保留
这样既可以保留最近上下文,又不会让历史无限增长。
最近窗口加载的作用是:
控制上下文长度
降低 token 消耗
保留近期语义
减少无关历史干扰
防止上下文超限
如果后续系统变得更复杂,还可以加入历史摘要压缩。
例如把很早之前的对话总结成一段摘要:
用户之前主要学习了 RAG、Embedding、向量数据库和 Tool Calling。
这样既保留长期记忆,又不会占用太多 token。
九、ChatMemory 的简单理解
今天还接触到 ChatMemory 的概念。
ChatMemory 可以理解为:
用于保存和管理对话历史的组件
在简单 Demo 中,可以先使用内存保存:
Map<sessionId, List<Message>>
这种方式简单直观,但有一个问题:
服务重启后,历史记录就没了。
所以它更适合学习 Demo 或临时会话。
如果要做得更完整,可以使用 Redis 或数据库保存历史。
例如:
Redis:适合保存短期会话,读写快,可以设置过期时间
数据库:适合保存长期历史,便于查询和审计
所以 ChatMemory 不完全等于缓存。
它的核心目的不是为了“加快查询”,而是为了让 Agent 能够理解多轮对话上下文。
可以这样区分:
缓存:主要为了提高访问速度
ChatMemory:主要为了保存对话上下文
当然,在工程实现中,也可以用 Redis 来实现 ChatMemory。
十、今天对 Agent 的理解进一步加深了
经过今天的学习,我对 Agent 的理解又清晰了一层。
最开始,我以为 Agent 可能就是:
大模型 + Prompt
后来学习 Tool 和 Function Calling 后,理解到 Agent 还需要:
大模型 + Prompt + 工具调用
学习 RAG 后,又发现 Agent 还需要:
大模型 + Prompt + 工具调用 + 外部知识检索
今天理解 ReAct 和 ChatMemory 后,认识更加完整:
Agent = 大模型 + Prompt + Tool + RAG + Memory + Reasoning
也就是说,Agent 不是一个单独的技术点,而是一套组合系统。
它至少包含几种能力:
理解用户目标
维护上下文
判断是否需要外部知识
选择合适工具
调用工具获取结果
根据结果继续推理
生成最终回答
这才是一个智能体逐渐成型的过程。
十一、今天的学习总结
今天主要学习和理解了 Agent 中几个比较核心的内容:
1. RAG 是 Agent 获取外部知识的重要方式
2. Chunk Size 控制文本块大小
3. Overlap 用来避免语义被切断
4. Tool Calling 让大模型具备调用外部能力的方式
5. ReAct 让 Agent 拥有“推理 + 行动”的流程
6. sessionId 可以实现多轮会话隔离
7. ChatMemory 可以保存上下文
8. 最近窗口加载可以降低 token 消耗
9. Redis 可以作为 ChatMemory 的一种实现方式更多推荐
所有评论(0)