Document与Retriever

问答要引用私有 PDF,不能把整本书塞进 prompt。检索器从索引里取出 Top-K 片段,再拼进提示。LangChain 用 Document 表示「一段文本 + 元数据」,用 Retriever 抽象「query → list[Document]」,与具体向量库解耦。

段末注释Document = 带 page_contentmetadata 的文本单元;Retriever(检索器)= 根据 query 返回相关 Document 的 Runnable。


1. 一句话定位

维度 内容
角色 能力层 RAG 召回:query → 相关文档片段
输入 → 输出 str query → list[Document]
典型调用入口 retriever.invoke("问题")vectorstore.as_retriever()
与 LangGraph RAG 常作为图上一节点;线性链用 retriever | format_docs | prompt

2. 实现逻辑

1
2
3
4
5
1. 离线:Loader → Document[] → Splitter → 入库(VectorStore 专篇)
2. retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
3. docs = retriever.invoke("用户问题")
4. context = "\n\n".join(d.page_content for d in docs)
5. prompt.invoke({"context": context, "question": q}) → model → 答案

字段级变形

1
2
3
"什么是 LCEL?"
→ retriever.invoke
[Document(page_content="LCEL 用 | 连接...", metadata={source:"doc1", page:2}), ...]

3. 原理说明

3.1 Document

Document(数据类,langchain_core.documents.Document
功能:一段文本 + 元数据,检索与引用的基本单元。

字段 类型 默认值 最小维度
page_content str 必填 建议 ≥1 字符;空串可构造但检索无意义
metadata dict {} 可空;引用答案时至少 source
id str / None None 更新/删除时建议非空
1
Document(page_content="LCEL 用 | 连接 Runnable。", metadata={"source": "lcel.md"})

日志与引用依赖 metadata;塞 prompt 只用 page_content

3.2 BaseRetriever

BaseRetriever(抽象类,Runnable)
功能:query: str → list[Document]。子类实现 _get_relevant_documents
默认值:视实现。
最小维度:query 非空 str;返回 list,len 由 k 决定,无命中可为 []

invoke(query, config=None)(方法)
similarity_search 结果同形,但是 LCEL 协议,能进 | 管道。

1
docs = retriever.invoke("什么是 LCEL?")  # list[Document]

VectorStore.as_retriever(*, search_type="similarity", search_kwargs=None, **kwargs)(方法)
功能:把 VectorStore 包成 Retriever。
默认值:search_type="similarity"search_kwargs 默认常含 k=4(视实现,不传则用 store 默认 k)。
最小维度:store 内至少 1 条已索引 Document,否则恒返回 []

1
retriever = store.as_retriever(search_type="similarity", search_kwargs={"k": 2})

search_typesimilarity / mmr / 部分实现的 similarity_score_threshold

3.3 与 VectorStore 分工

VectorStore 管 存储与相似度;Retriever 管 搜索策略(k、score_threshold、filter)。换库时链上仍叫 retriever

search_kwargs(dict)最小常用键:k: int ≥ 1(默认 4)。filter 视后端,可空。score_threshold 可选 float。MMR 时 fetch_k ≥ k

3.4 不要直接把 Document 当 str

format_docs(docs)(应用层函数,非框架类)
功能:拼 prompt 用的 context。最小输入:list[Document],len≥0;空列表 → ""

1
2
3
def format_docs(docs: list[Document]) -> str:
"""输入 Document 列表,输出用空行拼接的 page_content。"""
return "\n\n".join(d.page_content for d in docs)

4. 最小可运行示例

1
pip install -U langchain-openai langchain-huggingface sentence-transformers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from operator import itemgetter
from langchain_openai import ChatOpenAI
from langchain_core.documents import Document
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_huggingface import HuggingFaceEmbeddings

embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
encode_kwargs={"normalize_embeddings": True},
)
store = InMemoryVectorStore.from_documents(
[
Document(page_content="LCEL 用 | 连接 Runnable,组合成的对象仍支持 invoke/stream/batch。", metadata={"source": "lcel.md"}),
Document(page_content="Retriever 把 query 变成 list[Document],供 RAG 拼进 prompt。", metadata={"source": "rag.md"}),
Document(page_content="Python 列表推导式用一行从可迭代对象生成列表。", metadata={"source": "py.md"}),
],
embedding=embeddings,
)
retriever = store.as_retriever(search_kwargs={"k": 2})

docs = retriever.invoke("管道组合后为什么能 stream?")
print([(d.metadata["source"], d.page_content[:24]) for d in docs])
# 预期:第一条来自 lcel.md(真实语义,不是随机向量)

model = ChatOpenAI(
model="qwen3.5:9b",
api_key="ollama",
base_url="http://localhost:11434/v1",
temperature=0,
)

def format_docs(docs: list[Document]) -> str:
"""把检索结果拼成 prompt 用的 context。输入 Document 列表,输出换行拼接的正文。"""
return "\n\n".join(d.page_content for d in docs)

chain = (
{
"context": itemgetter("question") | retriever | format_docs,
"question": itemgetter("question"),
}
| ChatPromptTemplate.from_template("只根据资料回答。\n资料:{context}\n问题:{question}")
| model
| StrOutputParser()
)
print(chain.invoke({"question": "LCEL 组合之后还能怎样调用?"}))
# 预期形态:提到 invoke 或 stream 或 batch

重要配置参数

参数(API 名) 类型 / 默认值 功能说明 作用与影响 参考起点 配置指导
search_kwargs["k"] int,常默认 4 检索最多返回几条 Document 过大噪声多、挤 context;过小漏召回 3~6 与 prompt 窗口一起算
search_type str,默认 similarity 选近邻算法:纯相似或 MMR 去冗余 mmr 更散、略慢;similarity 更贴 query similarity 文档重复多时用 mmr
score_threshold float,可选 低于该相似度的结果丢掉 过高常返回空;过低噪声增加 0.7~0.85 无结果时先降 threshold
filter dict,部分 store 按 metadata 过滤后再搜 缩小候选集;键不存在则空结果 {source:"hr"} 多集合索引时常用
fetch_k int,mmr 用 MMR 重排前先取多大候选池 fetch_k ≥ k;过小 MMR 无意义 k×3 影响 mmr 质量
metadata 字段 dict,Document 上,默认 {} 片段来源、页码等溯源信息 不进模型也能在引用里展示 source, page 答案须引用时保留

5. 易踩坑

  1. retrieve 后直接 str(docs):得到对象 repr,模型读不到正文。
  2. k 过大挤爆 context:检索片段 + few-shot + 历史总和超窗。
  3. query 与索引语言不一致:跨语言检索质量差,需同语言或专用 embedding。

小结

  • Document 承载片段与 metadataRetriever 统一 query → docs。
  • RAG 链典型:retriever → 拼 context → prompt → model
  • 搜索策略在 as_retriever(search_kwargs=…) 配置。
  • 业务切分与评测见 RAG 目录;本篇只讲 LangChain API。

参考链接

-------------本文结束感谢您的阅读-------------