【AI】从最小 Agent 到向量化 RAG
LangChain 是什么Agent 是什么Tool 是怎么接进去的一个最小智能体应该怎么搭如果模型不知道我的本地资料、团队文档和私有知识,该怎么办?这就是RAG出场的地方。GitHub:已经跑通过最小 LangChain Agent,准备进入 RAG知道“向量检索”这个词,但还不清楚完整调用链路想做一个本地知识库问答 demo,但不想一上来堆太多组件想知道 demo 代码和生产方案之间差了哪些关
从最小 Agent 到向量化 RAG
从最小 Agent 到向量化 RAG
前言
上一篇文章《从零理解 LangChain Agent:一篇写给初学者的入门指南》主要解决的是:
- LangChain 是什么
- Agent 是什么
- Tool 是怎么接进去的
- 一个最小智能体应该怎么搭
当你把最小 Agent 跑通以后,下一个非常自然的问题就是:
如果模型不知道我的本地资料、团队文档和私有知识,该怎么办?
这就是 RAG 出场的地方。
本文配套项目地址:
如果你是下面这几类读者,这篇文章会比较适合你:
- 已经跑通过最小 LangChain Agent,准备进入 RAG
- 知道“向量检索”这个词,但还不清楚完整调用链路
- 想做一个本地知识库问答 demo,但不想一上来堆太多组件
- 想知道 demo 代码和生产方案之间差了哪些关键环节
这篇文章会继续沿着当前这个项目往下讲,重点回答这些问题:
- RAG 的原理到底是什么
- 一个最小可运行的 RAG 流程长什么样
- 我们这个 demo 里到底用了哪些三方框架和库
- 从 demo 走向生产,还可能接哪些更正式的组件
- “命中向量后,内容是怎么传回 Agent 的”
如果说 Agent 解决的是“模型如何调用外部能力”,那么 RAG 解决的就是:
模型如何基于你自己的知识来回答问题。
你可以把这篇文章的目标理解成一句话:
把“文档是怎么变成模型回答依据的”这条链路讲清楚。
导读
如果你想先抓住重点,再慢慢往下看,可以先记住这 4 句话:
RAG不是训练模型,而是先找资料再回答- 向量检索命中的不是“答案”,而是“证据片段”
- 真正回流给模型的不是向量,而是整理后的
ToolMessage - 一个最小 RAG 系统,本质上就是:
文档 -> 切分 -> embedding -> 向量库 -> 检索 -> 回答

一、什么是 RAG
RAG,全称是 Retrieval-Augmented Generation,中文通常翻译为“检索增强生成”。
它解决的问题很直接:
- 模型有通用知识
- 但模型默认不知道你的本地文档
- 也不知道你的业务资料、团队知识、项目笔记
所以在很多实际场景里,单纯把问题发给模型是不够的。
这时候就需要先做一层“检索”:
- 先从知识库中找出和问题最相关的内容
- 再把这些内容交给模型组织答案
所以 RAG 的本质不是“让模型变聪明”,而是:
在回答前,先把合适的资料拿给模型看。
二、RAG 和微调有什么区别
这是很多初学者非常容易混淆的点。
1. RAG
RAG 的思路是:
- 不改模型参数
- 不重新训练模型
- 只是先检索,再把资料喂给模型
适合:
- 企业知识库问答
- 项目文档助手
- 本地笔记搜索
- 法务、客服、运维文档问答
2. 微调
微调的思路是:
- 在模型参数层面做训练
- 改变模型的行为模式或风格
更适合:
- 固定输出格式
- 特定语气风格
- 特定任务偏好
- 领域行为适配
3. 一句话区分
你可以这样记:
RAG:给模型补资料微调:给模型改习惯
大部分“知识问答类”场景,第一反应通常都应该先考虑 RAG,而不是一上来做微调。
三、RAG 的核心原理
从原理上说,一个 RAG 系统通常包含两段:
1. 索引阶段
把原始资料变成“可检索”的形式。
常见步骤包括:
- 读取文档
- 文本切分
- 生成 embedding 向量
- 存入向量库
2. 检索与生成阶段
当用户提问时:
- 把用户问题也转成 embedding
- 在向量库里找最相近的文本片段
- 把这些片段交给模型
- 模型基于这些片段生成最终回答
所以 RAG 的技术关键在于:
- 文档怎么切
- 向量怎么生成
- 相似度怎么搜
- 检索结果怎么组织给模型
四、当前 Demo 的完整调用链路
我们这次在项目里实现的是一个 最小向量化本地 RAG。
它的调用链路可以概括为:
用户问题
-> LangChain create_agent
-> search_local_knowledge 工具
-> app.rag.search_knowledge
-> OpenAIEmbeddings(实际走智谱兼容接口)
-> InMemoryVectorStore.similarity_search
-> 命中若干 Document 分片
-> format_search_results
-> ToolMessage
-> 最终 AIMessage
也就是说:
- 向量命中后,并不是把“向量本身”传回模型
- 而是把命中的
Document分片整理成文本 - 再作为工具返回值交给 LangChain
- LangChain 再把它包装成
ToolMessage - 最后模型基于
ToolMessage生成面向用户的回答
这是理解 RAG 和 Agent 如何协作的关键。
如果再压缩成更好记的版本,就是这三句话:
- Agent 负责决定“要不要查知识库”
- RAG 负责把“相关资料片段”找出来
- 模型负责把“资料片段”组织成最终答案
五、当前 Demo 的流程图
下面这张图基本就是当前项目里向量化 RAG 的真实执行过程:
如果你把这张图理解透了,RAG 的主流程就已经抓住了。
六、当前 Demo 里到底用了哪些三方框架和库
如果只看这次 RAG 主链路,核心相关的库其实不多,主要就是下面 4 类。
1. LangChain
LangChain 在这里负责“把 RAG 工具接进 Agent”。
主要承担的角色包括:
create_agent- Tool 调用
- 消息链管理
- ToolMessage 回流
你可以把它理解成:
负责把“模型、工具、消息”组织起来的应用层框架。
2. langchain_openai
这里主要用到了:
ChatOpenAIOpenAIEmbeddings
它们分别负责:
- 聊天模型调用
- 文本向量化
虽然封装名里带 OpenAI,但当前项目实际接的是智谱兼容接口:
GLM-5.1embedding-3
3. langchain_core
RAG 这一层最关键的两个对象来自这里:
DocumentInMemoryVectorStore
其中:
Document负责承载文本内容和来源信息InMemoryVectorStore负责保存向量并执行相似度检索
这两个对象基本就是当前 demo 的“最小 RAG 数据骨架”。
4. numpy
numpy 是向量检索里一个非常底层但必要的依赖。
在当前项目中,它主要用于:
- 支撑
InMemoryVectorStore的向量相似度计算
也就是说:
- embedding 模型负责“生成向量”
numpy负责“比较向量”
七、当前 Demo 的实现结构怎么理解
这次项目里的向量化 RAG 主要分布在三个地方。
1. app/rag.py
这是 RAG 的核心实现层。
它主要负责:
- 列出知识库文件
- 加载文档
- 文本切分
- 构建 embedding 模型
- 构建内存向量库
- 做相似度搜索
- 格式化检索结果
你可以把它理解成:
“RAG 引擎层”
2. app/tools.py
这里把 RAG 包装成了 Agent 可调用工具:
list_knowledge_base_files()search_local_knowledge()
也就是说,RAG 在 Agent 眼里并不是一个抽象系统,而是:
一个可以调用的 Tool。
3. app/main.py
这里负责:
- 创建 Agent
- 注册工具
- 维护多轮对话消息
- 打印 ToolMessage
- 打印最终回答
所以主程序真正做的是:
把模型、RAG 工具和消息链路串起来。
补充:关键代码骨架长什么样
如果你想把“概念”快速落到“代码理解”上,最值得先看的其实是下面这三段。
1. 文档切分
这一步的目的,是把原始知识文件拆成更适合检索的小块。
def split_text_into_chunks(
text: str,
chunk_size: int = 400,
chunk_overlap: int = 80,
) -> list[str]:
chunks: list[str] = []
start = 0
while start < len(text):
end = min(start + chunk_size, len(text))
chunk = text[start:end].strip()
if chunk:
chunks.append(chunk)
if end == len(text):
break
start = end - chunk_overlap
return chunks
你不用一开始就纠结最优切分算法。
先理解:
- 为什么要切分
- 为什么要 overlap
- chunk 大小会怎样影响召回
这三个问题更重要。
2. 构建 embedding 和向量库
这一步的目标,是把文本 chunk 变成可做相似度搜索的向量索引。
def build_embeddings_model() -> OpenAIEmbeddings:
return OpenAIEmbeddings(
model=os.getenv("EMBEDDING_MODEL", "embedding-3"),
api_key=os.getenv("ZAI_API_KEY"),
base_url=os.getenv("ZAI_BASE_URL"),
dimensions=int(os.getenv("EMBEDDING_DIMENSIONS", "1024")),
)
def build_vector_store() -> InMemoryVectorStore:
documents = load_knowledge_documents()
chunked_documents = split_documents_into_chunks(documents)
embeddings = build_embeddings_model()
vector_store = InMemoryVectorStore(embedding=embeddings)
vector_store.add_documents(chunked_documents)
return vector_store
这里要抓住的重点不是语法,而是职责划分:
Embeddings负责把文本变成向量VectorStore负责保存向量并支持检索
3. 检索结果是如何回流给 Agent 的
这一步是很多人第一次学 RAG 时最容易模糊的地方。
def search_local_knowledge(query: str, max_results: int = 3) -> str:
results = search_knowledge(query=query, max_results=max_results)
return format_search_results(results)
这里看起来很短,但背后真正发生的是:
search_knowledge()返回命中的Documentformat_search_results()把它们整理成普通文本- 这段文本作为工具返回值被 LangChain 包装成
ToolMessage - 模型再基于这条
ToolMessage继续回答
所以你可以把这段代码理解成:
把“向量检索结果”翻译成“模型能继续使用的上下文消息”。
八、为什么一定要文本切分
很多人第一次学 RAG 时会问:
为什么不直接把整篇文档拿去做检索?
原因很简单:
1. 整篇文档通常太长
长文会导致:
- 检索粒度太粗
- 命中不精准
- 无关上下文太多
2. 小块更适合定位知识点
一个问题通常只需要命中:
- 某个段落
- 某几句定义
- 某个步骤说明
而不是整篇文章。
3. 更利于后续拼接上下文
RAG 最后送给模型的上下文预算是有限的。
如果你每次都塞整篇文档:
- 浪费上下文
- 降低回答聚焦度
- 成本更高
所以文本切分几乎是所有 RAG 系统的基础动作。
九、向量检索和关键词检索有什么不同
你这次项目升级里,最大的变化之一就是:
从“关键词匹配”升级成了“向量语义检索”。
1. 关键词检索
优点:
- 容易理解
- 容易调试
- 不依赖 embedding 模型
缺点:
- 更依赖字面重合
- 对同义表达不够敏感
- 中文场景下更容易受分词影响
2. 向量检索
优点:
- 更能处理语义相近表达
- 不要求用户问题和原文措辞一模一样
- 更接近真实生产效果
缺点:
- 需要 embedding 模型
- 需要向量库
- 调试成本更高
对学习顺序来说,最合理的路径通常是:
- 先做最小关键词检索,理解主流程
- 再升级成 embedding + 向量库
你当前这个项目,正好就走完了这条路线。
十、向量命中之后,是怎么传给 Agent 的
这是这次实现里最值得你真正吃透的一段。
很多人会误以为:
- 模型直接看到向量
- 或者模型直接访问向量库
其实都不是。
真实链路是:
第一步
similarity_search(...) 命中若干 Document
这些 Document 里包含:
- 文本内容
- 来源文件名
- chunk 编号
第二步
把这些 Document 整理成一段普通文本
也就是类似这种结构:
[来源] langchain_rag.md (chunk #1)
[内容]
RAG 是 Retrieval-Augmented Generation...
第三步
工具函数把这段文本作为返回值交给 LangChain
第四步
LangChain 自动把它包装成 ToolMessage
第五步
模型继续读取这条 ToolMessage,生成最终回答
所以你一定要记住:
传回模型的不是“向量”,而是“向量检索命中的证据文本”。
这也是为什么我们后来又加了 ToolMessage 打印功能。
因为只有看见这条消息,你才会真正理解:
- RAG 返回了什么
- 模型是基于什么作答的
十一、生产环境里常见会用到哪些三方库或组件
当前项目是一个非常适合学习的最小实现,但如果走向生产,通常会逐步换成更正式的组件。
下面这些都是常见选择。
1. 向量数据库
当前 demo 用的是:
InMemoryVectorStore
生产里更常见的是:
QdrantMilvusWeaviatePineconepgvectorElasticsearch / OpenSearchRedis Vector Search
它们通常提供:
- 持久化存储
- 更大规模数据支持
- 过滤条件
- 更好的查询能力
- 分布式能力
2. 文档解析库
当前 demo 只读 .txt 和 .md,但生产里常常要处理:
- Word
- HTML
- 网页
- 表格
常见会搭配的库包括:
pypdfunstructuredbeautifulsoup4python-docxpandas
3. 更正式的切分器
当前 demo 用的是手写固定窗口切分。
生产里更常见的是:
RecursiveCharacterTextSplitter- markdown-aware splitter
- token-aware splitter
这些切分器通常更适合:
- 保持段落结构
- 控制 token 预算
- 减少语义断裂
4. 重排与混合检索
很多生产系统不会只做单次向量检索。
常见增强包括:
- 关键词检索 + 向量检索混合
- reranker 重排
- 多路召回
常见会接入:
bge-rerankerCohere rerank- Elasticsearch BM25
5. 观测与调试
生产里你通常还会需要:
- 调用链路追踪
- 检索结果评估
- 提示词调试
- 成本与延迟观察
常见会考虑:
LangSmith- 自己的日志与埋点系统
- APM / tracing 平台
十二、补充:如果你想快速自己做一个最小 RAG Demo
可以按下面这个顺序来,尽量不要一口气引入太多组件:
- 先只支持
.txt或.md - 先做固定窗口切分
- 先用一个 embedding 模型 + 一个最小向量库
- 先做 Top-K 相似度检索
- 最后再把结果包装成 Tool 返回给 Agent
换句话说,第一版不要着急上:
- PDF 解析
- 多向量库切换
- 混合检索
- rerank
- LangGraph 多步骤流程
先把最短链路跑通,比一开始把系统做复杂更重要。
十三、当前 Demo 的局限在哪里
这一步也很重要,因为它能帮你区分:
哪些是学习阶段的“刻意简化”,哪些是以后要升级的地方。
1. 向量库存放在内存里
优点:
- 简单
- 易懂
缺点:
- 进程结束就丢
- 不适合大规模
2. 文档格式支持少
当前只支持:
.txt.md
生产里通常远远不够。
3. 没有持久化索引
现在每次真正需要时会自动构建或重建索引。
更正式的方案通常会做:
- 向量持久化
- 增量更新
- 重建策略
4. 没有 rerank
现在是“直接相似度搜索 -> 返回结果”。
生产里为了提高精度,经常会:
- 先粗召回
- 再精排
十四、如果你继续往下学,下一步应该是什么
如果你已经把这一版向量化 RAG 跑通,下一阶段我建议按这个顺序继续:
1. 先学持久化向量库
比如:
QdrantpgvectorMilvus
目标是理解:
- 为什么 demo 版内存向量库不够
- 生产里为什么需要持久化
2. 再学更正式的文档解析
把知识库来源从:
- txt / md
扩展到:
- Word
- HTML
3. 再学混合检索和重排
目标是理解:
- 召回不等于最终排序
- RAG 精度往往靠检索策略细化出来
4. 最后再进入 LangGraph
当你开始想做这些时,再上 LangGraph:
- 多步骤 RAG 流程
- 人工审核
- 多 Agent 协作
- 可恢复状态流
结语
从学习路径上看,RAG 是 Agent 入门之后最值得尽快掌握的一步。
因为它会让你第一次真正接触到:
- 模型之外的私有知识
- 文档到检索的完整链路
- 向量化和相似度搜索
- ToolMessage 如何成为模型回答的依据
如果说最小 Agent 帮你理解的是:
模型如何调用外部能力
那么向量化 RAG 帮你理解的就是:
模型如何基于你的知识来回答问题
当你把这条链路真正跑通之后,你对大模型应用开发的理解会明显进入下一层。
所以这一步最重要的不是“先记住多少名词”,而是:
把文档加载、文本切分、向量检索、ToolMessage 回流、最终回答这整条链路看清楚。
这一步看清楚了,后面无论你做知识库助手、企业问答还是代码问答,都会顺很多。
更多推荐



所有评论(0)