【大模型Wiki】04 · LangChain 与 RAG
LangChain 与 RAG
整理自博客园旧笔记《langchain教程》(2024-05-11),2026-09 更新(含 LangGraph 时代的变化)。
本文目标是把 LangChain 这套「开发 LLM 应用的框架」讲透,并重点拆解当前最落地的应用场景——RAG(检索增强生成) 的底层原理与工程细节。阅读完本文,你应该能回答:LangChain 到底解决了什么问题、一条链(Chain)由哪些抽象组成、RAG 为什么会「检索不准就答不对」、以及生产环境下要把 RAG 做对需要做哪些额外工作。
一、LangChain 是什么
LangChain 是开发 LLM 应用的框架,覆盖应用生命周期:开发(组件与 LCEL)、生产化(LangSmith 观测评估)、部署(LangServe 把链转 REST API)。
它出现的背景是:直接调 openai.chat.completions.create 很快就能跑通 demo,但要把它变成一个「能检索私有文档、能记上下文、能调工具、能监控成本」的生产应用,需要大量胶水代码。LangChain 的价值就是把这些常见模式(提示模板、链、检索、记忆、Agent)抽象成可组合、可替换的组件。
生态组成:
langchain-core:基础抽象与 LangChain 表达式语言(LCEL)langchain-community/ 合作伙伴包(langchain-openai 等):第三方集成langchain:链、Agent、检索策略langgraph:把步骤建模为图中的节点/边,构建健壮的有状态多参与者应用(2026 年做 Agent 的主推方式)langsmith:调试、测试、评估、监控平台
经验提示:现在安装时普遍采用「按需装包」策略,而不是
pip install langchain一把梭。例如只用到 OpenAI 就装langchain-openai,只用到 Chroma 就装langchain-chroma。这样能显著减小依赖体积、避免版本冲突。langchain主包本身更多是一层「高级链与检索策略」的合集。
# 常见的最小依赖组合
pip install langchain-core langchain-openai langchain-chroma langchain-text-splitters
1.1、LCEL:声明式链组合
| 管道符把组件串起来,天然支持流式、并行、批处理:
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
prompt = ChatPromptTemplate.from_template("讲一个关于{topic}的冷笑话")
chain = prompt | ChatOpenAI(model="gpt-4o-mini") | StrOutputParser()
chain.invoke({"topic": "冰激凌"})
LCEL 的设计哲学:所有组件都实现同一个 Runnable 接口(invoke / stream / batch / ainvoke 等),因此任意两个实现了该接口的对象都能用 | 拼接,并且拼接结果仍然是一个 Runnable。这意味着你写出来的「提示 + 模型 + 解析器」小链,和生产里「检索器 + 重排 + 提示 + 模型 + 解析器 + 缓存」的大链,用的是同一套组合语法,无需改写。
| 背后做的事,等价于:
# 下面两行语义一致
result = chain.invoke({"topic": "冰激凌"})
result = StrOutputParser().invoke(
ChatOpenAI(model="gpt-4o-mini").invoke(
prompt.invoke({"topic": "冰激凌"})
)
)
LCEL 还带来几个生产级能力,无需额外代码:
- 流式(stream):一旦第一个组件开始产出,结果就能逐块返回,对应聊天界面的「打字机」。
- 并行(RunnableParallel):多条分支同时执行,例如「一边检索文档、一边把问题原样透传」。
- 批处理(batch):一次给多个输入,框架自动调度并发。
- 异步(ainvoke/astream):与 asyncio 生态无缝衔接。
Runnable 标准接口(invoke/stream/batch)统一了所有组件:
| 组件 | 输入 | 输出 |
|---|---|---|
| Prompt | Dictionary | PromptValue |
| ChatModel | 消息列表 / PromptValue | ChatMessage |
| OutputParser | LLM 输出 | 解析后对象 |
| Retriever | 字符串 | List[Document] |
# LCEL 并行:同时准备 context 与 question
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
setup = RunnableParallel(
{"context": retriever | format_docs, "question": RunnablePassthrough()}
)
二、RAG:检索增强生成
LLM 的知识冻结在训练数据截止点,且不知道私有数据。RAG = 检索相关知识 → 注入提示 → 生成回答,是知识库问答、企业知识管理的标准方案。
为什么不直接把文档全文塞进 prompt?三个现实约束:
- 上下文窗口有限:再大的窗口也装不下整个公司 wiki / 法律合同全集。
- 成本与延迟:token 越多,单次调用越贵越慢。
- 注意力稀释:无关长文本会干扰模型,反而更容易答错(所谓「lost in the middle」现象)。
RAG 的思路是「现用现取」:只在回答某个具体问题时,把最相关的几段文本塞进 prompt。这就把「模型需要知道全部知识」降级为「模型需要会在正确的时候检索到正确片段」。
2.1、两个阶段
索引(离线):
Load 加载 → Split 切块 → Embed 向量化 → Store 存入向量库
检索与生成(在线):
用户提问 → Retriever 检索相关块 → 拼进 Prompt → LLM 生成答案
两个阶段解耦,意味着索引可以周期性重建(比如每天夜里),而检索与生成每次请求都跑。很多工程 bug 的根源,就是混淆了这两个阶段:在离线阶段用错了切块参数,导致在线阶段怎么调提示都救不回来。
2.2、最小可运行示例
from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
# 1-3. 索引:加载 → 切块 → 向量库
docs = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/").load()
splits = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200).split_documents(docs)
vectorstore = Chroma.from_documents(splits, OpenAIEmbeddings())
retriever = vectorstore.as_retriever(k=4)
def format_docs(ds):
return "\n\n".join(d.page_content for d in ds)
prompt = ChatPromptTemplate.from_template(
"仅根据以下上下文回答问题:\n{context}\n\n问题:{question}")
# 4-5. 检索 + 生成(LCEL 组合)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt | ChatOpenAI(model="gpt-4o-mini") | StrOutputParser()
)
print(rag_chain.invoke("什么是任务分解?"))
这段代码已经是一个能用的 RAG 了:它加载网页、切块、向量化、建库、检索、拼提示、生成。但「能用」不等于「好用」——下面 2.4~2.8 会解释要让它真正可靠,还差哪些关键设计。
2.3、工程要点(2026 年经验)
| 环节 | 常用方案 |
|---|---|
| 切块 | RecursiveCharacterTextSplitter(按语义边界,块 500-1000 + overlap) |
| 向量库 | Chroma(原型)、Milvus/Qdrant/pgvector(生产) |
| Embedding | OpenAI text-embedding-3、BGE-M3(中文开源首选) |
| 检索优化 | 混合检索(BM25+向量)、重排序(bge-reranker)、父子块 |
| 评估 | RAGAS:检索命中率 + 答案忠实度/相关性 |
典型失败模式:切坏了(答案跨块)、检索不准(该用重排)、模型幻觉(提示里强制"仅依据上下文")。
2.4、切块(Chunking)策略对比
切块是 RAG 质量的「地基」。切得太大 → 噪声多、检索不精准;切得太小 → 语义被切碎、一个知识点散落在多处。常见策略:
| 策略 | 做法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 固定长度 | 按字符/ token 数硬切 | 简单、可控 | 可能切断句子/段落 | 快速原型 |
| 递归字符切分 | 按 \n\n、\n、空格等优先级逐级切 |
尽量保留语义边界 | 仍可能切到代码/表格 | 通用默认首选 |
| 按分隔符(Markdown/代码) | 识别标题、函数、JSON 字段 | 结构保持好 | 依赖格式规范 | 文档/代码库 |
| 语义切分 | 用 embedding 找语义断点 | 块内高度连贯 | 慢、依赖模型 | 长文精读 |
| 父子块(Parent-Child) | 小块用于检索、大块用于喂模型 | 兼顾精准与完整 | 实现略复杂 | 生产推荐 |
递归字符切分(RecursiveCharacterTextSplitter) 为什么是默认?因为它按一组分隔符 [".\n\n", "\n\n", "\n", "。", ",", " ", ""] 依次尝试,优先在段落、再在句子边界切断,最大限度避免把一句话劈成两半。chunk_overlap 的作用是让相邻块首尾重叠一小段,防止「答案恰好跨在两块边界、两块都只含半句」的情况——这正是 2.3 提到的「切坏了」陷阱。
父子块(Parent-Child) 模式值得单拎出来说:把文档先切成大块(如 2000 字)作为「父」,再把大块细切成小块(如 400 字)作为「子」。检索时只用小块算相似度(更精准),但真正喂给 LLM 的是小块对应的「父块」(更完整)。这样既不会因为块太大而稀释相似度,也不会因为块太小而丢失上下文。在 LangChain 里可用 Ingest / 手动映射实现,或用 langchain 的 ParentDocumentRetriever。
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain_chroma import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 父块:大;子块:小。子块用于检索,父块用于生成。
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400, chunk_overlap=50)
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000, chunk_overlap=200)
retriever = ParentDocumentRetriever(
vectorstore=Chroma(embedding_function=OpenAIEmbeddings()),
docstore=InMemoryStore(), # 存父块原文
child_splitter=child_splitter,
parent_splitter=parent_splitter,
)
2.5、Embedding 与相似度计算
「检索」本质是:把用户问题变成一个向量,去向量库里找离它最近的几个文档块向量。这里有两件事:
1)文本 → 向量(Embedding)
Embedding 模型把一段文本压缩成一个高维浮点数数组(如 1536 维)。语义相近的文本,向量在空间中距离也近。中文场景强烈建议用 BGE-M3 / bge-large-zh 这类在中文语料上训练过的模型,直接用 OpenAI 英文模型处理中文,相似度往往偏低、检索命中率下降。
2)相似度度量
最常用三种:
- 余弦相似度(Cosine):看两个向量的「方向」是否一致,与长度无关。最常用,因为它关心语义而非文本长短。
- 点积(Dot Product):在未归一化向量上等价于「长度 × 余弦」,长度越长得分越高,需先归一化。
- 欧氏距离(L2):看空间距离,距离越小越相似。某些 rerank 前的初筛会用。
数学上,余弦相似度 = (A·B) / (|A| × |B|)。OpenAI text-embedding-3 等模型输出已归一化,此时余弦相似度与点积数值一致,可直接用点积加速。
import numpy as np
def cosine(a, b):
a, b = np.array(a), np.array(b)
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
# 问题向量与每个文档块向量算余弦,取 Top-k
scores = [cosine(query_vec, doc_vec) for doc_vec in doc_vectors]
top_idx = np.argsort(scores)[-4:][::-1] # 取相似度最高的 4 个
坑点:同一个向量库里必须用同一个 embedding 模型索引和查询,否则维度/语义空间不一致,相似度失去意义。换模型要全量重建索引。
2.6、向量库选型:FAISS / Milvus / Chroma
向量库负责「存向量 + 高效近邻搜索(ANN)」。选型看规模与部署形态:
| 向量库 | 形态 | 优点 | 局限 | 适用 |
|---|---|---|---|---|
| FAISS | 本地内存库(Meta) | 极快、零服务依赖 | 纯内存、无持久化/无并发服务 | 实验、单机脚本 |
| Chroma | 嵌入式/轻服务 | 上手最快、Python 友好 | 大规模下运维能力弱 | 原型、中小项目 |
| Milvus | 分布式服务 | 十亿级、云原生、性能好 | 部署重、运维成本高 | 企业生产 |
| Qdrant | 独立服务(Rust) | 性能好、过滤灵活 | 需单独部署 | 生产 |
| pgvector | Postgres 扩展 | 复用现有 PG、事务一致 | 超大规模性能一般 | 已有 PG 的团队 |
# FAISS:最轻量,适合 demo
from langchain_community.vectorstores import FAISS
vs = FAISS.from_documents(splits, OpenAIEmbeddings())
vs.save_local("faiss_index") # 落到磁盘
vs = FAISS.load_local("faiss_index", OpenAIEmbeddings())
# Chroma:一行起本地库
from langchain_chroma import Chroma
vs = Chroma.from_documents(splits, OpenAIEmbeddings(), persist_directory="./chroma")
经验法则:先用 Chroma 把链路跑通,再按数据量与并发升级到 Milvus/Qdrant/pgvector。不要一上来就上分布式 Milvus,部署成本会拖慢验证速度。
2.7、重排(Rerank)
向量检索的「召回」和「精排」是两件事。向量检索擅长「广撒网」快速找回一批候选(如 20 条),但它本质是「语义方向相似」,未必是「真的相关」——比如问题问「如何退款」,向量可能同时召回「退款政策」和「退款案例分享」,后者并不该排第一。
Rerank 模型(如 bge-reranker-large、Cohere Rerank)做一次「交叉编码(cross-encoder)」:把「问题 + 候选文档」拼在一起过一遍模型,直接输出相关性分数。它比向量检索准,但慢且贵,所以标准做法是:
向量召回 Top-20 → Rerank 精排 → 取 Top-4 喂给 LLM
from langchain_community.document_transformers import (
LongContextReorder, # 另有专用 rerank 集成
)
# 以 sentence-transformers 的 FlagEmbedding 为例的通用思路:
from FlagEmbedding import FlagReranker
reranker = FlagReranker("BAAI/bge-reranker-large", use_fp16=True)
pairs = [(query, d.page_content) for d in candidates]
scores = reranker.compute_score(pairs)
ranked = [d for _, d in sorted(zip(scores, candidates), reverse=True)]
实践:加一层 rerank,往往是「检索不准」类问题性价比最高的修复手段,通常能让答案相关性肉眼可见地提升。
2.8、混合检索(Hybrid Search)
纯向量检索对精确关键词(专有名词、型号、报错码、ID)不敏感——「CUDA error 802」这种字符串,向量检索可能召回一堆无关的 CUDA 文档。这时候关键词检索(BM25 / 全文检索) 反而更准。
混合检索 = 向量检索 ∪ 关键词检索,再融合结果:
- 向量检索负责「语义相似」
- BM25 负责「字面命中」
- 用 RRF(Reciprocal Rank Fusion) 等算法把两套排名合并:
score = Σ 1/(k + rank_i)
LangChain 可通过 EnsembleRetriever 实现:
from langchain_community.retrievers import BM25Retriever, EnsembleRetriever
bm25 = BM25Retriever.from_documents(splits, k=4)
vec = vectorstore.as_retriever(search_kwargs={"k": 4})
hybrid = EnsembleRetriever(retrievers=[bm25, vec], weights=[0.5, 0.5])
docs = hybrid.invoke("CUDA error 802 怎么解决?")
生产 RAG 的「标准答案」往往是:混合检索召回 + rerank 精排 + 父子块。三者叠加,能解决绝大多数「答非所问」。
三、LangChain 核心抽象全景
把前面零散提到的组件系统梳理一遍。LangChain 的核心抽象可以归为六类:Model / Prompt / Chain / Retriever / Memory / Agent。理解这六个,就理解了 LangChain 的骨架。
3.1、Model(模型层)
分两类:
- ChatModel(聊天模型):以「消息列表」为接口,支持 system/user/assistant 角色,是主流。
- LLM(文本补全):以「纯文本」为接口,正在被 ChatModel 取代。
from langchain_openai import ChatOpenAI
# temperature 控制随机性:0=稳定可复现,1=更有创意
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, max_tokens=1024)
msg = llm.invoke([{"role": "user", "content": "用一句话解释 RPC"}])
print(msg.content)
常见参数:model(模型名)、temperature(0~2,越高越随机)、max_tokens(输出上限)、top_p、stop(停止符)、stream(流式)。
3.2、Prompt(提示模板)
把「固定的指令 + 可变的用户输入」参数化,避免每次手工拼字符串。详见 04 篇 1.1 与 05 篇 4.1 的模板写法。核心价值是可复用、与模型无关、便于版本管理。
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你是{role},请用专业且简洁的语气回答。"),
("human", "{question}"),
])
print(prompt.format_messages(role="资深后端工程师", question="什么是幂等?"))
3.3、Chain(链)
链 = 若干 Runnable 的顺序组合(通常用 |)。最经典的「提示 → 模型 → 解析」三件套就是一条链。链可以嵌套:一条链的输出是另一条链的输入。
chain = prompt | llm | StrOutputParser()
# 链本身也是 Runnable,可以继续被 | 进别的组件
LangChain 还提供一批开箱即用的高级链构造器(详见 05 篇 4.2 表),如 create_stuff_documents_chain(把文档塞进提示)、create_retrieval_chain(检索 + 生成)、create_history_aware_retriever(结合历史改写查询)。
3.4、Retriever(检索器)
输入一个字符串(查询),输出 List[Document]。它是 RAG 的「检索」那一半,解耦了「用什么库、怎么搜」的实现细节。任何能返回文档的函数都能包成 Retriever。
from langchain_chroma import Chroma
retriever = Chroma.from_documents(splits, OpenAIEmbeddings()).as_retriever(
search_type="similarity", # 或 "mmr"(最大边际相关,多样性更好)
search_kwargs={"k": 4},
)
docs = retriever.invoke("RAG 怎么减少幻觉?")
3.5、Memory(记忆)
让多轮对话「记住」上下文。本质上就是一个按会话 ID 存/取消息列表的组件,底层可以是内存、Redis、Postgres。
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
# 用 Redis 按 session_id 存历史,适合多用户生产环境
history = RedisChatMessageHistory(session_id="user-123", url="redis://localhost:6379")
history.add_user_message("我是做电商的")
history.add_ai_message("好的,有什么可以帮你?")
配合 RunnableWithMessageHistory 可自动把历史注入链:
from langchain_core.runnables.history import RunnableWithMessageHistory
chain_with_history = RunnableWithMessageHistory(
runnable=chain,
get_session_history=lambda sid: RedisChatMessageHistory(sid, url="redis://localhost:6379"),
input_messages_key="question",
history_messages_key="history",
)
3.6、Agent(智能体)
Agent = 模型「自己决定调哪个工具、传什么参数」,执行后得到结果再决定下一步,直到能回答。它把 LLM 从「只会说话」升级为「能干活」。详见第六节与 08 篇。
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.tools import tool
@tool
def calculator(expr: str) -> str:
"""计算数学表达式,如 '2**10'。"""
return str(eval(expr))
agent = create_tool_calling_agent(llm, [calculator], prompt)
executor = AgentExecutor(agent=agent, tools=[calculator], verbose=True)
六大抽象的关系一句话总结:Prompt 组织输入 → Model 推理 → Chain 组合流程 → Retriever 取外部知识 → Memory 维持状态 → Agent 自主决策调工具。
四、信息提取与结构化输出
用 Pydantic 定义 schema,with_structured_output 强制模型按 schema 返回:
from pydantic import BaseModel, Field
from typing import Optional
class Person(BaseModel):
"""人员信息。"""
name: Optional[str] = Field(default=None, description="姓名")
hair_color: Optional[str] = Field(default=None, description="发色,未知则留空")
runnable = prompt | llm.with_structured_output(schema=Person)
runnable.invoke({"text": "Alan Smith is 6 feet tall and has blond hair."})
# Person(name='Alan Smith', hair_color='blond', ...)
最佳实践:字段描述写清楚(会发给 LLM);字段用 Optional 允许模型"不知道"而不是编造。
结构化输出底层有两种机制:
- 工具/函数调用:模型按 schema 输出参数,最稳,绝大多数现代模型支持。
- JSON 模式 /
response_format:强制输出合法 JSON,schema 作为提示一部分传入。
当字段很多、约束很复杂时,建议给模型 1~2 个 few-shot 示例(通过
MessagesPlaceholder("examples")),能显著提升抽取准确率,尤其是对日期、单位这类易错字段。
五、聊天机器人与记忆
核心组件:ChatModel + 提示模板(MessagesPlaceholder 注入历史)+ 聊天历史存储 + 可选 Retriever:
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
MessagesPlaceholder(variable_name="messages"), # 历史注入点
])
chain = prompt | chat
生产环境中历史存 Redis/Postgres(RunnableWithMessageHistory 自动管理);接入 RAG 的写法是先检索、再 create_stuff_documents_chain 把文档"填"进 prompt。
一个容易踩的坑:历史会无限增长。每轮都把全部历史塞进 prompt,迟早撑爆上下文、推高成本。常见处理:
- 滑动窗口:只保留最近 N 轮。
- 摘要压缩:定期用 LLM 把旧历史压成一段摘要。
- 检索式记忆:把历史也入库,按需检索相关片段(AutoGen/MemGPT 思路)。
# 摘要式记忆示意:用 LLM 把旧消息压成 summary 再继续
from langchain_core.prompts import ChatPromptTemplate
summary_prompt = ChatPromptTemplate.from_template(
"把以下对话压缩成一段摘要,保留关键事实:\n{history}")
六、从 Chain 到 Agent(LangGraph)
旧版 AgentExecutor 已被 LangGraph 取代:把"模型决策 → 工具执行 → 状态更新"建模为图,获得循环、分支、人工介入(interrupt)、持久化检查点等能力。典型图:
START → agent(决定调用哪个工具) ─► tools(执行) ─┐
▲ │
└──── 有工具结果,回到 agent ◄──────┘
│ (模型认为完成)
▼
END
这与通用 Agent 循环(ReAct)同构,原理详见 08 篇;需要团队协作/子任务拆分时用 LangGraph 的 multi-agent 模式。
LangGraph 相对 AgentExecutor 的关键改进:
- 状态(State)显式化:用 TypedDict / Pydantic 定义全局状态,节点读写它,便于调试。
- 持久化检查点(checkpoint):对话可中途断、可恢复,支持「人类在环(human-in-the-loop)」——在关键节点
interrupt()等人工确认。 - 可控循环:不再是无脑最大步数,而是图结构明确「何时该停」。
from langgraph.graph import StateGraph, START, END
from typing import TypedDict, Annotated
import operator
class State(TypedDict):
messages: Annotated[list, operator.add] # 消息累加
def agent(state): ...
def tools(state): ...
g = StateGraph(State)
g.add_node("agent", agent)
g.add_node("tools", tools)
g.add_edge(START, "agent")
g.add_edge("tools", "agent")
g.add_edge("agent", END)
app = g.compile()
七、实践细节与踩坑
把 RAG / LangChain 落地时最高频的坑,列成清单:
- 切块切坏了:答案跨块、被 overlap 之外的边界切断。→ 用递归切分 + 合理 overlap;关键文档上父子块。
- embedding 模型不一致:索引用 A、查询用 B。→ 全量重建;把模型名写进配置。
- 检索不准就怪模型:其实该加 rerank / 混合检索。→ 先调检索,再调提示。
- 提示没约束:模型拿检索到的内容自由发挥。→ 明确「仅依据上下文,不知道就说不知道」。
- 历史无限膨胀:多轮后上下文溢出。→ 滑动窗口 / 摘要 / 检索式记忆。
- 把离线索引当成在线逻辑:改了切块参数只重建部分。→ 索引与查询参数用同一份配置。
- 没评估就上线:不知道检索命中率多少。→ 用 RAGAS 测「检索命中 + 答案忠实度」。
- 依赖未隔离:
langchain全家桶版本打架。→ 按需装包 + 锁版本(requirements.txt/pyproject.toml)。
调试手段:本地先用 verbose=True 看链内部每一步输入输出;生产用 LangSmith 追踪每一次请求的全链路(哪一步慢、哪个检索块无关、花了多少 token)。
八、小结
- LangChain 的价值在于把「提示/模型/链/检索/记忆/Agent」抽象成可组合的
Runnable,让你用同一套语法从 demo 扩展到生产。 - RAG 不是「装个向量库」就完事:切块策略、embedding 一致性、向量库选型、rerank 精排、混合检索,每一环都直接决定回答质量。生产标配是「混合检索 + rerank + 父子块」。
- 结构化输出用 Pydantic +
with_structured_output,是做信息抽取的捷径;记得字段加 Optional 让模型可「拒答」。 - 记忆要管理长度,否则上下文会溢出;Agent 已全面转向 LangGraph,获得状态化、可持久化、可人工介入的能力。
- 工程上始终把「离线索引」与「在线检索」的参数统一,并引入评估(RAGAS)+ 观测(LangSmith),否则你永远不知道为什么答错了。
九、参考
- https://python.langchain.com/docs/introduction/
- LangGraph:https://langchain-ai.github.io/langgraph/
- RAGAS 评估:https://docs.ragas.io
- BGE 系列 embedding/rerank:https://github.com/FlagOpen/FlagEmbedding

浙公网安备 33010602011771号