检索增强生成 RAG
2026/5/14大约 16 分钟
检索增强生成 RAG
什么是 RAG
LLM 的局限性
大语言模型虽然能力强大,但存在三个根本性局限:
RAG(Retrieval-Augmented Generation,检索增强生成)的核心思路:在 LLM 回答问题之前,先从外部知识库中检索相关信息,然后让 LLM 基于检索到的事实来生成回答。
| 方式 | 原理 | 类比 |
|---|---|---|
| 纯 LLM | 模型凭记忆回答 | 开卷考试(但不一定对) |
| Fine-tuning | 重新训练模型 | 重新上课学习 |
| RAG | 检索资料后回答 | 开卷考试,先查资料再答题 |
RAG 工作流程
完整管道
RAG 分为两个阶段:索引阶段(离线)和查询阶段(在线)。
简单代码骨架
"""RAG 最简示例"""
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_community.vectorstores import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 1. 索引阶段:文档 → 分块 → 向量化 → 存储
docs = [...] # 文档列表
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(docs)
vectorstore = Chroma.from_documents(chunks, OpenAIEmbeddings(), collection_name="my_docs")
# 2. 查询阶段:检索 → 拼接 → 生成
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
prompt = ChatPromptTemplate.from_template("""基于以下上下文回答问题。如果上下文中没有相关信息,请说"我不确定"。
上下文:
{context}
问题:{question}""")
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| ChatOpenAI(model="gpt-4o-mini")
| StrOutputParser()
)
answer = rag_chain.invoke("什么是 RAG?")
print(answer)
文档加载器
常用加载器
LangChain 提供了大量文档加载器,用于从不同来源读取数据:
# ========== 文本文件 ==========
from langchain_community.document_loaders import TextLoader
loader = TextLoader("data/readme.txt", encoding="utf-8")
docs = loader.load()
# 每个 doc 有 page_content 和 metadata
# ========== PDF 文件 ==========
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("data/report.pdf")
pages = loader.load()
# 每个 PDF 页面对应一个 Document,metadata 包含页码信息
for page in pages:
print(f"第 {page.metadata['page']} 页,内容长度: {len(page.page_content)}")
# ========== CSV 文件 ==========
from langchain_community.document_loaders import CSVLoader
loader = CSVLoader(
file_path="data/products.csv",
content_columns=["name", "description"], # 只加载指定列
metadata_columns=["id", "category"], # 作为元数据
)
docs = loader.load()
# ========== Web 网页 ==========
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://python.langchain.com/docs/get_started/introduction")
docs = loader.load()
# ========== 目录批量加载 ==========
from langchain_community.document_loaders import DirectoryLoader
loader = DirectoryLoader(
path="data/docs/",
glob="**/*.md", # 匹配所有 .md 文件
loader_cls=TextLoader, # 使用 TextLoader
loader_kwargs={"encoding": "utf-8"},
show_progress=True, # 显示进度条
)
docs = loader.load()
print(f"共加载 {len(docs)} 个文档")
加载器对比
| 加载器 | 适用格式 | 安装依赖 | 特点 |
|---|---|---|---|
| TextLoader | .txt, .md | 内置 | 最简单,直接读取文本 |
| PyPDFLoader | pypdf | 按页分割,保留页码元数据 | |
| CSVLoader | .csv | 内置 | 按行加载,可指定列 |
| WebBaseLoader | 网页 | beautifulsoup4 | 抓取网页正文 |
| DirectoryLoader | 目录 | 内置 | 批量加载目录下文件 |
| UnstructuredLoader | 多种格式 | unstructured | 通用最强,支持 Word/PPT/HTML 等 |
提示
对于复杂格式(Word、PPT、Excel),推荐使用 UnstructuredLoader,安装命令:pip install unstructured
文本分割器
为什么需要分割
LLM 有上下文长度限制,不能把整本书塞给模型。需要把长文档切成合适大小的片段:
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个片段最大字符数
chunk_overlap=50, # 片段之间的重叠字符数
length_function=len, # 计算长度的函数
separators=["\n\n", "\n", "。", ",", " ", ""], # 分割优先级
)
chunks = text_splitter.split_documents(docs)
print(f"原文档数: {len(docs)}")
print(f"分割后片段数: {len(chunks)}")
print(f"第一个片段: {chunks[0].page_content[:100]}...")
分割策略详解
# ========== 递归字符分割(推荐,通用场景)==========
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n## ", "\n### ", "\n\n", "\n", "。", ""],
)
# ========== Token 分割(需要精确控制 Token 消耗)==========
from langchain_text_splitters import TokenTextSplitter
splitter = TokenTextSplitter(
chunk_size=500, # 每 500 个 Token 一段
chunk_overlap=50, # 重叠 50 个 Token
)
# ========== Markdown 分割(保留标题结构)==========
from langchain_text_splitters import MarkdownHeaderTextSplitter
md_splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=[
("#", "chapter"),
("##", "section"),
("###", "subsection"),
]
)
# 每个 Markdown 章节单独一个 Document,metadata 包含标题层级
# ========== 代码分割(按语法切分,不切断函数)==========
from langchain_text_splitters import Language, RecursiveCharacterTextSplitter
code_splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.PYTHON,
chunk_size=1000,
chunk_overlap=100,
)
python_code = """
class DataProcessor:
def __init__(self):
self.data = []
def process(self, item):
self.data.append(item)
return len(self.data)
def summary(self):
return f"Total: {len(self.data)} items"
"""
chunks = code_splitter.create_documents([python_code])
for chunk in chunks:
print(f"--- 片段 ---\n{chunk.page_content}\n")
分割参数选择
| 文档类型 | chunk_size | chunk_overlap | 说明 |
|---|---|---|---|
| FAQ/短文本 | 300-500 | 30-50 | 保持每条完整 |
| 技术文档 | 800-1200 | 100-200 | 保留段落完整性 |
| 长篇文章 | 1000-2000 | 200-400 | 上下文更丰富 |
| 代码文件 | 800-1500 | 50-100 | 不切断函数定义 |
注意
chunk_overlap 不宜过小,否则可能丢失上下文边界信息;也不宜过大,否则会产生大量冗余数据。建议设置为 chunk_size 的 10%-20%。
向量嵌入 Embedding
什么是向量嵌入
向量嵌入是将文本转换为高维数字向量的过程。语义相近的文本,向量距离也相近:
使用 Embedding 模型
from langchain_openai import OpenAIEmbeddings
# OpenAI Embedding(推荐)
embeddings = OpenAIEmbeddings(
model="text-embedding-3-small", # 最新模型,性价比高
)
# 单文本嵌入
vector = embeddings.embed_query("什么是 RAG?")
print(f"向量维度: {len(vector)}") # 1536
print(f"前5个值: {vector[:5]}")
# 批量嵌入
vectors = embeddings.embed_documents([
"Python 是一门编程语言",
"Java 也是一门编程语言",
"今天中午吃什么",
])
print(f"生成了 {len(vectors)} 个向量")
Embedding 模型对比
| 模型 | 提供商 | 维度 | 价格(/1M Token) | 中文支持 | 推荐场景 |
|---|---|---|---|---|---|
| text-embedding-3-small | OpenAI | 1536 | $0.02 | 好 | 通用推荐 |
| text-embedding-3-large | OpenAI | 3072 | $0.13 | 好 | 高精度需求 |
| bge-large-zh-v1.5 | BAAI | 1024 | 免费(本地) | 优秀 | 中文场景首选 |
| m3e-base | Moka | 768 | 免费(本地) | 优秀 | 轻量中文场景 |
# 使用本地免费 Embedding(适合中文场景)
from langchain_huggingface import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-large-zh-v1.5",
model_kwargs={"device": "cpu"}, # 使用 CPU
encode_kwargs={"normalize_embeddings": True}, # L2 归一化
)
vector = embeddings.embed_query("什么是检索增强生成")
print(f"向量维度: {len(vector)}") # 1024
向量数据库
Chroma 使用
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_core.documents import Document
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 创建文档
docs = [
Document(page_content="LangChain 是一个用于构建 LLM 应用的框架", metadata={"source": "intro.md"}),
Document(page_content="RAG 通过检索外部知识增强 LLM 的回答质量", metadata={"source": "rag.md"}),
Document(page_content="向量数据库用于存储和检索高维向量数据", metadata={"source": "vector.md"}),
Document(page_content="Agent 可以自主决策并调用外部工具完成任务", metadata={"source": "agent.md"}),
]
# ===== 创建向量库 =====
vectorstore = Chroma.from_documents(
documents=docs,
embedding=embeddings,
collection_name="my_knowledge_base",
persist_directory="./chroma_db", # 持久化目录
)
# ===== 相似度检索 =====
results = vectorstore.similarity_search(
query="如何让 LLM 使用外部工具?",
k=2, # 返回最相似的 2 个结果
)
for doc in results:
print(f"内容: {doc.page_content}")
print(f"来源: {doc.metadata['source']}")
print("---")
# ===== 相似度检索(带分数)=====
results_with_scores = vectorstore.similarity_search_with_score(
query="什么是 RAG",
k=3,
)
for doc, score in results_with_scores:
print(f"分数: {score:.4f} | 内容: {doc.page_content}")
# 分数越小越相似(L2 距离)
# ===== 加载已有向量库 =====
existing_store = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings,
collection_name="my_knowledge_base",
)
FAISS 使用
FAISS 是 Facebook 开源的高效向量检索库,适合大规模数据:
# 安装
pip install faiss-cpu # CPU 版本
# pip install faiss-gpu # GPU 版本(需要 CUDA)
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain_core.documents import Document
embeddings = OpenAIEmbeddings()
docs = [
Document(page_content="Python 支持面向对象编程"),
Document(page_content="Python 的列表推导式非常强大"),
Document(page_content="Java 是一种静态类型语言"),
]
# 创建 FAISS 索引
vectorstore = FAISS.from_documents(docs, embeddings)
# 检索
results = vectorstore.similarity_search("Python 的特性", k=2)
for doc in results:
print(doc.page_content)
# 保存和加载
vectorstore.save_local("faiss_index")
loaded_store = FAISS.load_local("faiss_index", embeddings, allow_dangerous_deserialization=True)
向量数据库对比
| 特性 | Chroma | FAISS | Pinecone | Milvus |
|---|---|---|---|---|
| 类型 | 本地/客户端 | 本地库 | 云服务 | 自部署/云 |
| 安装难度 | 简单 | 简单 | 注册即用 | 中等 |
| 适合规模 | 万级 | 百万级 | 亿级 | 亿级 |
| 持久化 | 支持 | 需手动 | 自动 | 自动 |
| 元数据过滤 | 支持 | 不支持 | 支持 | 支持 |
| 推荐场景 | 开发测试 | 本地高性能 | 生产免运维 | 企业级生产 |
检索器 Retriever
基本检索
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=OpenAIEmbeddings(),
)
# 方式一:相似度检索(默认)
retriever = vectorstore.as_retriever(
search_type="similarity", # 相似度搜索
search_kwargs={"k": 4}, # 返回 4 个结果
)
# 方式二:MMR 检索(最大边际相关性)
# 平衡相关性和多样性,避免返回内容重复的文档
mmr_retriever = vectorstore.as_retriever(
search_type="mmr",
search_kwargs={
"k": 4, # 最终返回 4 个结果
"fetch_k": 20, # 先检索 20 个候选
"lambda_mult": 0.5, # 多样性权重(0=最大多样性,1=最大相关性)
},
)
# 方式三:相似度分数阈值
threshold_retriever = vectorstore.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={
"k": 10,
"score_threshold": 0.7, # 只返回相似度 >= 0.7 的结果
},
)
# 使用检索器
docs = retriever.invoke("什么是 LangChain?")
for doc in docs:
print(doc.page_content[:80])
print(f"来源: {doc.metadata}")
print("---")
多查询检索器
当用户的问题表述不够精确时,可以用 LLM 生成多个变体查询来提升召回率:
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# 包装为多查询检索器
multi_retriever = MultiQueryRetriever.from_llm(
retriever=retriever,
llm=llm,
)
# 使用——会自动生成多个查询变体,合并检索结果
import logging
logging.basicConfig()
logging.getLogger("langchain.retrievers.multi_query").setLevel(logging.INFO)
docs = multi_retriever.invoke("RAG 的优势是什么")
# 日志会显示 LLM 生成的多个查询变体:
# 1. "RAG 相比其他方法有什么优点"
# 2. "检索增强生成的好处是什么"
# 3. "为什么使用 RAG 架构"
构建 RAG 链
完整 RAG 链
使用 LCEL 将检索和生成组合成一条完整的链:
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain_community.vectorstores import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader
# ===== 1. 索引阶段 =====
loader = TextLoader("data/knowledge_base.txt", encoding="utf-8")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=100)
chunks = splitter.split_documents(docs)
vectorstore = Chroma.from_documents(chunks, OpenAIEmbeddings())
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# ===== 2. 构建提示词模板 =====
prompt = ChatPromptTemplate.from_template("""你是一个专业的知识库助手。请基于以下参考资料回答用户的问题。
## 规则
1. 只根据参考资料中的信息回答
2. 如果参考资料中没有相关信息,明确说"根据现有资料无法回答"
3. 回答要准确、完整、有条理
## 参考资料
{context}
## 用户问题
{question}""")
# ===== 3. 组装 RAG 链 =====
def format_docs(docs):
"""将检索到的文档格式化为文本"""
formatted = []
for i, doc in enumerate(docs, 1):
source = doc.metadata.get("source", "未知来源")
formatted.append(f"[资料{i}] (来源: {source})\n{doc.page_content}")
return "\n\n".join(formatted)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
rag_chain = (
RunnableParallel(
context=retriever | format_docs,
question=RunnablePassthrough(),
)
| prompt
| llm
| StrOutputParser()
)
# ===== 4. 使用 =====
answer = rag_chain.invoke("LangChain 的核心组件有哪些?")
print(answer)
带来源引用的 RAG
让回答包含引用来源,增强可信度:
from langchain_core.runnables import RunnablePassthrough
from pydantic import BaseModel, Field
from typing import List
class SourceDocument(BaseModel):
content: str
source: str
class RAGResponse(BaseModel):
"""RAG 回答"""
answer: str = Field(description="回答内容")
sources: List[str] = Field(description="引用来源列表")
confidence: float = Field(description="置信度,0-1之间")
# 使用 with_structured_output 获取结构化回答
structured_llm = ChatOpenAI(
model="gpt-4o-mini", temperature=0
).with_structured_output(RAGResponse)
cite_prompt = ChatPromptTemplate.from_template("""基于以下参考资料回答问题。
参考资料:
{context}
问题:{question}
要求:
1. 回答必须基于参考资料
2. 列出引用了哪些来源
3. 评估回答的置信度""")
cite_chain = (
RunnableParallel(
context=retriever | format_docs,
question=RunnablePassthrough(),
)
| cite_prompt
| structured_llm
)
result = cite_chain.invoke("什么是向量数据库?")
print(f"回答: {result.answer}")
print(f"来源: {result.sources}")
print(f"置信度: {result.confidence}")
流式 RAG
流式输出让用户不用等待完整生成:
# 流式 RAG 链
stream_chain = (
RunnableParallel(
context=retriever | format_docs,
question=RunnablePassthrough(),
)
| prompt
| ChatOpenAI(model="gpt-4o-mini", temperature=0, streaming=True)
| StrOutputParser()
)
for chunk in stream_chain.stream("什么是 LangChain?"):
print(chunk, end="", flush=True)
RAG 优化策略
常见问题与优化方向
查询重写
对用户的原始问题进行改写,使其更适合检索:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# 查询重写链
rewrite_prompt = ChatPromptTemplate.from_template(
"请将以下用户问题改写为更适合在知识库中检索的查询语句。"
"只输出改写后的查询,不要解释。\n\n"
"原始问题:{question}\n\n改写后的查询:"
)
rewrite_chain = rewrite_prompt | ChatOpenAI(model="gpt-4o-mini", temperature=0) | StrOutputParser()
original = "怎么用那个跟数据库连起来的东西?"
rewritten = rewrite_chain.invoke({"question": original})
print(f"原始: {original}")
print(f"改写: {rewritten}")
# 输出:LangChain 如何连接数据库?SQLDatabase Chain 的使用方法
# 将重写后的查询传入 RAG 链
answer = rag_chain.invoke(rewritten)
实战:私有文档问答系统
完整示例
"""
doc_qa.py — 私有文档问答系统
功能:
1. 加载目录下的文档(txt/md/pdf)
2. 自动分割和向量化
3. 支持相似度检索和问答
4. 带来源引用
"""
import os
from pathlib import Path
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain_community.vectorstores import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import (
TextLoader,
PyPDFLoader,
DirectoryLoader,
)
class DocumentQA:
"""私有文档问答系统"""
def __init__(
self,
docs_dir: str = "./data/docs",
db_dir: str = "./chroma_db",
model: str = "gpt-4o-mini",
):
self.docs_dir = docs_dir
self.db_dir = db_dir
self.model = model
self.embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
self.llm = ChatOpenAI(model=model, temperature=0)
self.splitter = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=100,
separators=["\n## ", "\n### ", "\n\n", "\n", "。", ",", " "],
)
self.vectorstore = None
self.retriever = None
self.chain = None
def build_index(self):
"""构建文档索引"""
print(f"正在加载文档: {self.docs_dir}")
# 加载所有文档
all_docs = []
# 加载 Markdown/TXT 文件
if os.path.exists(self.docs_dir):
md_loader = DirectoryLoader(
self.docs_dir,
glob="**/*.{md,txt}",
loader_cls=TextLoader,
loader_kwargs={"encoding": "utf-8"},
show_progress=True,
)
all_docs.extend(md_loader.load())
# 加载 PDF 文件
pdf_loader = DirectoryLoader(
self.docs_dir,
glob="**/*.pdf",
loader_cls=PyPDFLoader,
show_progress=True,
)
all_docs.extend(pdf_loader.load())
if not all_docs:
print("未找到任何文档")
return
print(f"共加载 {len(all_docs)} 个文档")
# 分割文档
chunks = self.splitter.split_documents(all_docs)
print(f"分割为 {len(chunks)} 个片段")
# 创建向量库
self.vectorstore = Chroma.from_documents(
documents=chunks,
embedding=self.embeddings,
persist_directory=self.db_dir,
)
self.retriever = self.vectorstore.as_retriever(
search_type="mmr",
search_kwargs={"k": 4, "fetch_k": 10},
)
print("索引构建完成")
def load_index(self):
"""加载已有索引"""
self.vectorstore = Chroma(
persist_directory=self.db_dir,
embedding_function=self.embeddings,
)
self.retriever = self.vectorstore.as_retriever(
search_type="mmr",
search_kwargs={"k": 4, "fetch_k": 10},
)
def build_chain(self):
"""构建问答链"""
prompt = ChatPromptTemplate.from_template(
"你是一个专业的知识库助手。请基于以下参考资料准确回答问题。\n\n"
"规则:\n"
"1. 只根据参考资料中的信息回答\n"
"2. 如果资料中没有相关信息,说'根据现有资料无法回答'\n"
"3. 引用时标注来源\n\n"
"参考资料:\n{context}\n\n"
"问题:{question}"
)
def format_docs(docs):
return "\n\n".join(
f"[来源{i}: {doc.metadata.get('source', '未知')}]\n{doc.page_content}"
for i, doc in enumerate(docs, 1)
)
self.chain = (
RunnableParallel(
context=self.retriever | format_docs,
question=RunnablePassthrough(),
)
| prompt
| self.llm
| StrOutputParser()
)
def ask(self, question: str) -> str:
"""提问"""
if not self.chain:
self.build_chain()
return self.chain.invoke(question)
def ask_stream(self, question: str):
"""流式提问"""
if not self.chain:
self.build_chain()
for chunk in self.chain.stream(question):
yield chunk
def search(self, query: str, k: int = 5):
"""纯检索(不生成回答)"""
if not self.retriever:
self.load_index()
results = self.vectorstore.similarity_search_with_score(query, k=k)
for doc, score in results:
print(f"[相似度: {score:.4f}] {doc.metadata.get('source', '未知')}")
print(f" {doc.page_content[:100]}...")
print()
# 使用
if __name__ == "__main__":
qa = DocumentQA(docs_dir="./data/docs")
# 首次使用需要构建索引
qa.build_index()
# 问答
answer = qa.ask("LangChain 的核心模块有哪些?")
print(f"回答: {answer}")
# 流式问答
print("\n--- 流式输出 ---")
for chunk in qa.ask_stream("如何构建 RAG 应用?"):
print(chunk, end="", flush=True)
本章小结
核心要点
- RAG 本质:检索外部知识 → 注入提示词 → LLM 基于事实回答
- 文档加载:TextLoader、PyPDFLoader、DirectoryLoader 处理不同来源
- 文本分割:RecursiveCharacterTextSplitter 最通用,注意 chunk_size 和 overlap 的平衡
- 向量嵌入:OpenAI Embedding 通用,BGE 中文优秀,选择取决于场景和预算
- 向量数据库:Chroma 开发友好,FAISS 高性能,按规模选择
- 检索策略:similarity 基础、MMR 多样性、多查询提升召回
- LCEL 组装:
retriever | format_docs拼接上下文,组装完整 RAG 链 - 优化方向:查询重写、重排序、父子文档策略
RAG 技术栈速查
| 环节 | 推荐组件 | 备选方案 |
|---|---|---|
| 文档加载 | TextLoader / PyPDFLoader | UnstructuredLoader |
| 文本分割 | RecursiveCharacterTextSplitter | MarkdownHeaderTextSplitter |
| 向量嵌入 | text-embedding-3-small | BAAI/bge-large-zh-v1.5 |
| 向量存储 | Chroma | FAISS / Milvus |
| 检索策略 | MMR (diversity) | MultiQueryRetriever |
| 生成模型 | GPT-4o-mini | Qwen-Plus / Claude |