标题:同一套 RAG 助手,三种构建方式:LangChain vs LangGraph vs Strands Agents
原文:
Building the Same RAG Assistant 3 Ways: LangChain vs LangGraph vs Strands Agents
标题:用三种方式构建同一个 RAG 助手:LangChain vs LangGraph vs Strands Agents
同一个文档助手,分别用链式结构、受控图流程和工具调用型智能体来实现,到底有什么不同?这是我做这个项目的初衷。
RAG(Retrieval-Augmented Generation,检索增强生成)是当代 AI 工程中最实用的模式之一。普通语言模型只能根据训练时学到的知识作答,而 RAG 系统在生成答案之前多加了一步:先从可信的知识源中检索,再把相关上下文带入提示词里。这一步看似微小,效果却天差地别。它不再让模型凭记忆猜答案,而是让它依据 PDF、Word 文档、政策手册、Excel 表格、培训笔记、产品 FAQ、创意简报或内部文档等文件来回答。正因如此,RAG 如今在企业助手、HR 聊天机器人、客服系统、法律研究辅助工具、医疗知识平台和教育类应用中随处可见。
做这个项目时,我想搞清楚一个更大的问题:如果目标一致——都是构建一个基于文档的 AI 助手——那么分别用 LangChain、LangGraph 和 Strands Agents 来实现,代码和实现方式会发生怎样的变化?于是我用三种方式构建了同一个 Streamlit RAG 助手:
- LangChain——基于链式结构的 RAG 应用。
- LangGraph——基于状态图的工作流,用于受控 RAG。
- Strands Agents——智能体风格实现,把文档检索当作一个工具来用。
三个版本的用户体验完全一样:用户上传文档,点击处理按钮,在聊天框里提问,收到答案并附上检索到的来源片段。区别不在用户能看到什么,而在系统背后是怎么"思考"的。
共同的基石:每个 RAG 系统都需要什么
在对比框架之前,先理解 RAG 的共同基础会更有帮助。这个项目的每个版本都遵循同一条核心流水线:
文档 → 通用加载器 → 文本分块 → 向量化 → 向量数据库 → 检索器 → 大模型回答 → 会话记忆
文档加载
第一步是数据摄入(ingestion)。真实用户上传的不一定都是干净的纯文本文件,可能是 PDF、Word 文档、Excel 表格、CSV 文件、Markdown 笔记或 HTML 页面。
因此,这个项目包含一个通用的文档加载器。加载器会检测文件扩展名,并使用对应的加载策略。LangChain 的加载器生态在这里很有用,因为它能把多种外部格式统一成一种通用的文档表示形式。这意味着,即使来源格式不同,下游代码也能一致地处理文档。
分块(Chunking)
文档加载完成后,下一步是分块。分块就是把长文本拆成更小的片段。这一步很重要,因为语言模型和嵌入模型都有 token 数量限制。更重要的是,当每个分块只包含一个集中的主题时,检索效果会更好。分块太大可能混杂过多主题,太小又会丢失有用的上下文。常见的起步参数是:chunk_size ≈ 500 到 1000 个 token,chunk_overlap ≈ 50 到 150 个 token。
重叠也很关键,因为一个意思常常会跨越分块边界。例如,某条政策规则可能从一段的末尾开始,延续到下一段。重叠有助于保留这段上下文。
嵌入(Embeddings)
嵌入把文本转换为向量。向量是一串数字,用来捕捉语义。比如,下面两个短语在关键词层面完全不同:remote work policy 和 working from home rules。但在语义上,它们很接近。嵌入帮助系统理解这种接近性。
向量数据库(Vector Store)
向量数据库保存嵌入,并支持相似度搜索。用户提问时,系统先嵌入问题,再与已存储的文档向量比较,把最相似的分块作为上下文返回。本项目在设计上让本地演示无需昂贵的基础设施也能运行。对真正的生产系统,团队可能会用 FAISS、ChromaDB、Pinecone、Weaviate、Qdrant、Milvus、Elasticsearch 向量检索,或云原生数据库。
生成(Generation)
检索完成后,选中的分块会和用户问题一起发送给语言模型。模型被要求只能依据检索到的上下文回答。一个良好的 RAG 提示词通常是这样:“请使用提供的上下文来回答问题。”
如果答案不在上下文中,就明确说文档没有提供足够的信息。这样做可以降低幻觉,让回答更有依据。
记忆(Memory)
Memory 用来保存对话历史。没有记忆,每个问题都是孤立的;有了记忆,助理才能理解追问。
例子:
用户:远程办公政策是什么?
助理:远程办公需要经理批准。
用户:谁批准?
这里的“谁”指的就是批准远程办公的那个人。有记忆能力的助理能理解这层关联。
方法一:LangChain——通往 RAG 最快的路径
LangChain 是构建经典 RAG 应用最直接的方式。它提供了文档加载、切分、嵌入、向量库、检索器、提示词、聊天模型和记忆等组件。
在这个项目里,LangChain 版本遵循一个简单流程:
用户提问 ↓ 检索器找出相关片段 ↓ 提示词把问题、上下文和聊天历史组合在一起 ↓ LLM 生成回答 ↓ 更新对话历史
LangChain 为什么有用
因为它给开发者提供了现成的积木组件。如果你想快速搭建一个文档问答系统,LangChain 能帮你省下大量时间。它特别适合这些场景:
- 文档问答
- 内部知识助理
- 基于政策或手册的聊天机器人
- 客服助手
- 研究副驾
- 快速原型
LangChain 的心智模型
我把 LangChain 看成一套组件框架。你只需要把组件连起来:加载器 + 切分器 + 嵌入 + 向量库 + 检索器 + LLM + 记忆。
这样设计,既容易解释,也容易教学。
优势
LangChain 的主要优势是快。你可以很快做出一个能用的 RAG 应用。它还有庞大的集成生态,在处理各种文件格式和外部服务时非常有用。
局限
一旦工作流变得复杂,挑战就来了。比如:
- 第一次检索结果不理想怎么办?
- 回答需要校验怎么办?
- 最终回答要不要经过人工批准?
- 是否需要换一个更好的查询重试?
- 不同类型的问题是不是该走不同的处理路线?
这些情况在 LangChain 里也能处理,但逻辑会变得越来越难组织。
这正是 LangGraph 派上用场的地方。
方法二:LangGraph —— 把 RAG 做成有状态的工作流
LangGraph 专为有状态的工作流和智能体而设计。它不要求你只从"链"的角度去思考,而是定义一个由节点和边构成的图。节点是工作流中的一步,边控制工作流下一步往哪走。就本项目来说,LangGraph 版本可以这样理解:
START
↓
retrieve_node
↓
generate_node
↓
critique_node
↓
回答是否有据可依?
├── 是 → final_node → END
└── 否 → retry_node → retrieve_node
LangGraph 为什么有用:当你的 RAG 助手需要更多控制时,LangGraph 就变得重要了。例如,标准 RAG 系统可能检索到相关文档块就直接作答;LangGraph 系统则可以加入各种检查:检索结果是否提供了足够的证据?生成的答案有没有源文档块支撑?要不要用改写后的查询重试一次?要不要暂停等待人工审核?工作流是继续、走分支还是停下来?
这让 LangGraph 更适合更严肃的 AI 工作流。
LangGraph 心智模型:我把 LangGraph 看作 AI 应用的状态机。它不会把过程藏在一个链里,而是把工作流一步步暴露出来:
状态 = 问题 + 历史记录 + 检索到的文档块 + 答案草稿 + 评审意见 + 重试次数
每个节点都会读取并更新状态。这很强大,因为调试变得更简单了——你可以检查每一步发生了什么。
优势:LangGraph 在这些场景下很强:
- 有状态执行
- 条件路由
- 重试循环
- 人工介入的检查点
- 长时间运行的工作流
- 多步推理
- 更好的可观测性
局限:LangGraph 需要更多的设计思考。对于简单的文档问答助手,你可能会觉得这是额外的工作量。但当系统规模变大后,图结构就会变成优势。
方法三:Strands Agents —— 把 RAG 当作会使用工具的智能体
Strands Agents 走了另一条路。它不把每一步硬编码成链或图,而是创建一个带工具的智能体,由模型自行决定何时以及如何使用这些工具。在这个项目中,文档检索变成了一个工具:
Tool: search_uploaded_documents(query)
智能体可以自己判断:我该