Skip to main content

LangChain 生态

Written: 2026.06 上一讲RAG 核心概念深入
下一讲Milvus 索引机制与基本操作

1. 本讲导入

LangChain 的组件很多:Runnable、Prompt、Message、Parser、History、Loader、Splitter、VectorStore 都是常见概念。学习这些组件时,最重要的不是记住每个类名,而是看清它们在企业级 RAG 项目中分别出现在什么位置、解决什么工程问题。 本讲只围绕三个问题:
  1. LangChain 在本项目里到底是什么角色?
  2. 一次在线问答中,哪些步骤用到了 LangChain?
  3. 一次离线入库中,哪些步骤用到了 LangChain?
先记住一句话:
本项目没有把 LangChain 当成“一键 RAG 框架”,而是把它当成一组可靠的工程适配器:模型适配器、消息对象、结构化输出、历史记录、文档对象、加载器、切分器和向量库封装。

2. 本讲目标

学完本讲后,需要能说清楚:
  • 为什么项目用 ChatOpenAI 接入 DashScope,而不是直接绑定某个厂商 SDK。
  • SystemMessageHumanMessageAIMessage 在多轮对话和 Prompt 中分别承担什么角色。
  • with_structured_output() 为什么比让模型返回自由文本更适合意图识别和查询变体生成。
  • SQLChatMessageHistoryDocumentTextSplitterMilvus VectorStore 分别位于项目哪条链路。
  • 为什么本项目没有直接使用 RetrievalQAConversationalRetrievalChain 或一条 LCEL 管道完成全部 RAG。

3. LangChain 在项目中的角色

3.1 LangChain 不是完整业务流程

LangChain 不是模型,也不是知识库,更不是项目的业务大脑。它更像一组标准接口: 也就是说,LangChain 解决的是接口标准化工程胶水问题。本项目真正的业务流程仍然由 qa_core 自己编排。

3.2 本项目的使用边界

这里要特别区分:不用高层 Chain,不代表不用 LangChain。本项目用的是 LangChain 的底层稳定组件,把分支、阈值、追问改写、FAQ 直出、文档检索、Prompt 档位这些业务决策留在项目代码里。

4. 一张图看清两条主线

本项目中 LangChain 的使用分成两条线:
  1. 在线问答链路:用户提问 -> 意图识别 -> 检索准备 -> Prompt -> LLM 流式生成。
  2. 离线入库链路:业务文件 -> Document -> 切分 -> Milvus VectorStore 写入。
这张图就是本讲主线。后面所有组件都放回这两条线里讲。

5. 在线问答链路中的 LangChain

5.1 ChatOpenAI:统一模型调用入口

项目文件:qa_core/llm/client.py 本项目实际使用 DashScope 的 OpenAI-compatible 接口,但代码里不直接写 DashScope SDK,而是统一用 LangChain 的 ChatOpenAI
这里的设计点:
  • base_url 可以指向 DashScope、DeepSeek 或其他 OpenAI-compatible 服务。
  • streaming=False 用于意图识别、查询改写、结构化输出。
  • streaming=True 用于最终答案生成,方便 WebSocket 逐 token 推送。
  • @lru_cache(maxsize=2) 只缓存两个客户端实例,不缓存模型答案。

5.2 Message:让多轮对话结构稳定

LangChain 的消息对象对应 OpenAI API 的角色格式: 项目文件:qa_core/memory/history.py
需要理解:LLM 本身没有会话记忆。所谓“多轮对话”,本质是每次调用模型时,把必要的历史消息重新发给模型。

5.3 Structured Output:把 LLM 输出变成业务对象

项目文件:qa_core/intent/classifier.py 意图识别不能让模型自由发挥。自由文本会出现这些情况:
这些格式都不稳定。项目里使用 Pydantic 结构约束:
返回值不是字符串,而是一个 Pydantic 对象:
这一步是 LangChain 在项目中非常关键的价值:让 LLM 的输出进入可校验、可分支、可记录的工程世界 同样的方式也用于查询变体生成:

5.4 Prompt Profile:不是一个 Prompt 走天下

项目文件:
  • qa_core/prompts/profiles.py
  • qa_core/prompts/selector.py
项目没有把所有问题都塞进同一个 Prompt,而是按意图和风险类别选择不同模板:
可以这样理解:
  • Prompt 不是一段固定文案,而是回答策略配置
  • system_template 控制助手身份、边界、风险口径。
  • user_template 注入历史、检索上下文、用户问题。
  • reason 进入调试信息,帮助解释为什么选择这个模板。

5.5 最终答案:ChatOpenAI.stream() 推给前端

项目文件:qa_core/pipeline/steps.py
项目主流程会把每个 chunk 转成 WebSocket token 事件:
注意:这里不是 LangChain 替我们完成整个 RAG。LangChain 只负责模型流式调用;FAQ 直出、文档检索、上下文筛选、引用补强、写历史这些仍由项目代码控制。

6. 离线入库链路中的 LangChain

在线问答依赖的是“可检索的知识库”。这个知识库来自离线入库链路:

6.1 Document:入库链路的统一数据结构

LangChain 的 Document 很简单,只有两个核心字段:
本项目的约定:
  • page_content 存正文,用于 embedding、BM25 和最终上下文。
  • metadata 存来源、场景、权限、知识库版本、文件名、行号等治理字段。
需要记住:RAG 入库不是只存文本,还要存 metadata。没有 metadata,就无法做来源引用、权限过滤、版本隔离和质量追踪。

6.2 Document Loader:不同文件格式统一成 Document

项目文件:qa_core/indexing/document_loaders.py 项目用注册表模式管理 Loader,而不是在主流程里写一堆 if/elif
重点不是背 Loader 名称,而是理解模式:
文件格式不同,但进入后续入库链路之前,都要统一成 list[Document]

6.3 Text Splitter:为什么默认使用 RecursiveCharacterTextSplitter

项目文件:qa_core/indexing/chunking.py
为什么用它作为默认方案:
  • 它快、稳定、便宜,不依赖 embedding 模型。
  • chunk 大小可控,适合向量库检索。
  • 对制度、流程、手册、FAQ、Markdown 这类企业资料足够可靠。
  • 参数容易解释,适合学习、排查和生产调优。
SemanticChunker 不是不能用,但不适合作为默认切分器。它更适合无明显结构、话题自然漂移的长文,例如访谈、会议纪要、研究报告。企业 RAG 的主链路更需要稳定、可控、可复现。

6.4 MarkdownHeaderTextSplitter:先保留章节结构

项目中 Markdown 文件会先按标题拆分,再进入递归切分:
这样做的好处是:标题层级会进入 metadata,后续回答可以显示更清晰的来源,例如:

6.5 父子块策略:检索要精确,回答要完整

一句话解释:
子块负责“找得准”,父块负责“答得完整”。
更深入的 chunk size、overlap 和质量检查放到 附录 G:文档切分策略 和第 16 讲展开。本讲只建立在 LangChain 生态中的位置感。

6.6 VectorStore:把 Document 写入 Milvus

项目文件:qa_core/retrieval/store.py
这里先只讲抽象:
  • embedding_function 生成 dense 向量。
  • builtin_function=bm25_function() 让 Milvus 服务端生成 sparse 向量。
  • add_documents() 写入 Document
  • similarity_search_with_score() 检索并返回 Document + score
下一讲再进入 Milvus 自身:Collection、Schema、Index、Load、Insert、Search。

7. Runnable 和 LCEL 只作为统一接口理解

7.1 Runnable 的意义

Runnable 是 LangChain 的统一调用协议。无论 Prompt、Model、Parser,核心调用都收敛到三个方法:

7.2 LCEL 是线性链路语法,不是项目主流程

LCEL 可以把 Prompt、Model、Parser 串成线性管道:
它适合简单线性任务:
但本项目的 RAG 主流程不是一条直线: 所以本项目选择显式编排,而不是高层 Chain:
可以这样总结:
LCEL 很适合教“组件怎么串起来”,但企业 RAG 主链路有大量分支、阈值、提前退出和诊断信息,所以本项目不用一条 LCEL 链包到底。

8. 自研与生态的分工

8.1 交给 LangChain 的部分

8.2 项目自己实现的部分

这就是本项目的工程取舍:底层组件用生态,业务编排自己掌控

9. 学习路径建议

为了避免把 LangChain 学成零散组件,建议按下面顺序学习,而不是按组件名孤立学习: 配套 demo 可以按这个顺序练习:

10. 常见误区

10.1 误区一:用了 LangChain 就应该用 RetrievalQA

不对。RetrievalQA 适合快速 demo,但企业级 RAG 需要:
  • FAQ 标准答案直出
  • 意图识别
  • 追问改写
  • 多场景 source 过滤
  • 知识库版本隔离
  • 风险 Prompt Profile
  • 引用来源补强
  • Trace 和诊断面板
这些都很难塞进一个黑盒 Chain 里。

10.2 误区二:LCEL 越多越工程化

LCEL 适合表达线性链路,但不是所有流程都应该写成管道。复杂业务分支用显式函数更清楚,也更容易调试。

10.3 误区三:SemanticChunker 一定比 RecursiveCharacterTextSplitter 更好

不一定。企业 RAG 里的切分要可控、稳定、便宜、可复现。RecursiveCharacterTextSplitter 更适合作为默认方案;SemanticChunker 可以作为特殊文档的增强策略,而不是主链路默认切分器。

10.4 误区四:VectorStore 就是 Milvus

不是。VectorStore 是 LangChain 的抽象,Milvus 是具体后端。本项目选择 Milvus,是因为需要服务端 BM25、混合检索、多集合、多版本和企业级部署能力。

11. 本讲实践闭环


12. 重点掌握


13. 本讲小结

  • LangChain 在本项目里不是“一键 RAG 框架”,而是一组工程适配器。
  • 在线问答链路中主要用到 ChatOpenAI、Message、结构化输出、Prompt Profile 和流式生成。
  • 离线入库链路中主要用到 Document、Loader、Splitter 和 Milvus VectorStore。
  • Runnable 统一了 invoke / stream / batch,LCEL 统一了简单线性链路写法。
  • 本项目没有使用高层 Chain 包办 RAG,是因为企业级链路有分支、阈值、直出、过滤、追问、版本、引用和诊断。
  • 先看项目主线,再看组件 API,会更容易建立整体感。
下一讲Milvus 索引机制与基本操作 — 进入向量库底层:Collection、Schema、Index、Insert、Search,以及 LangChain Milvus 封装隐藏了哪些操作。