Loading

【大模型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?三个现实约束:

  1. 上下文窗口有限:再大的窗口也装不下整个公司 wiki / 法律合同全集。
  2. 成本与延迟:token 越多,单次调用越贵越慢。
  3. 注意力稀释:无关长文本会干扰模型,反而更容易答错(所谓「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,往往是「检索不准」类问题性价比最高的修复手段,通常能让答案相关性肉眼可见地提升。

纯向量检索对精确关键词(专有名词、型号、报错码、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,迟早撑爆上下文、推高成本。常见处理:

  1. 滑动窗口:只保留最近 N 轮。
  2. 摘要压缩:定期用 LLM 把旧历史压成一段摘要。
  3. 检索式记忆:把历史也入库,按需检索相关片段(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 落地时最高频的坑,列成清单:

  1. 切块切坏了:答案跨块、被 overlap 之外的边界切断。→ 用递归切分 + 合理 overlap;关键文档上父子块。
  2. embedding 模型不一致:索引用 A、查询用 B。→ 全量重建;把模型名写进配置。
  3. 检索不准就怪模型:其实该加 rerank / 混合检索。→ 先调检索,再调提示。
  4. 提示没约束:模型拿检索到的内容自由发挥。→ 明确「仅依据上下文,不知道就说不知道」。
  5. 历史无限膨胀:多轮后上下文溢出。→ 滑动窗口 / 摘要 / 检索式记忆。
  6. 把离线索引当成在线逻辑:改了切块参数只重建部分。→ 索引与查询参数用同一份配置。
  7. 没评估就上线:不知道检索命中率多少。→ 用 RAGAS 测「检索命中 + 答案忠实度」。
  8. 依赖未隔离: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),否则你永远不知道为什么答错了。

九、参考

posted @ 2026-09-19 05:42  Stellan7  阅读(10)  评论(0)    收藏  举报