本项目用于学习一个最小可运行的 LangChain RAG 系统:
- 使用
PyPDFLoader读取 PDF - 使用
RecursiveCharacterTextSplitter切分文档 - 使用 DashScope embedding 生成向量
- 使用 Milvus 存储和检索向量
- 使用 LangChain
Retriever+ Prompt + LLM 组织问答
当前推荐学习分支:
experiment/langchain-milvus-retriever
这条分支的重点是:
- 用
langchain-milvus理解 LangChain 风格的向量库接入 - 用
VectorStore/Retriever理解标准 RAG 链路 - 在保留 Milvus 的同时,观察 LangChain 封装层到底包装了什么
阅读和运行本项目时,应重点理解这些问题:
- RAG 与直接调用大模型的区别是什么
- 为什么长 PDF 需要切分成 chunk
chunk_size与chunk_overlap会如何影响检索效果- 什么是 embedding,什么是向量维度
- Milvus 中
collection、schema、index、search各自是什么 - LangChain 中
VectorStore与Retriever各自负责什么 langchain-milvus与原生pymilvus的代码组织差异是什么
项目的主链路如下:
PDF
-> Loader
-> Documents
-> Text Splitter
-> Chunks
-> Embedding
-> Milvus
-> Retriever
-> Prompt
-> LLM
-> Answer
可以再拆成两阶段:
把论文变成可检索知识。
把用户问题转成 query,检索相关 chunk,再交给 LLM 组织回答。
rag_app/
app.py
config.py
ingestion.py
milvus_store.py
chatbot.py
prompts.py
dashscope_embeddings.py
建议阅读顺序:
app.pyconfig.pyingestion.pymilvus_store.pychatbot.pyprompts.pydashscope_embeddings.py
项目入口,暴露两个命令:
ingest-paperschat
作用是接收命令并调用业务模块,不承担核心逻辑。
统一读取 .env 配置,整理为 RagSettings。
重点配置包括:
EMBEDDING_MODELEMBEDDING_DIMMILVUS_URIMILVUS_TOKENMILVUS_DBMILVUS_COLLECTIONTOP_K
建库主流程:
load_documents
-> split_documents
-> add_documents
学习重点:
- LangChain
Document的结构 - chunk 切分的必要性
- chunk overlap 的作用
将 Milvus 包装为 LangChain 可使用的 VectorStore。
学习重点:
- Milvus 本体负责 collection、schema、index、search
- LangChain 将其抽象为
add_documents()、similarity_search()、as_retriever() langchain-milvus并不改变 Milvus 原理,只是统一了调用接口
问答主链,关键结构是:
self.retriever = self.vector_store.as_retriever(
search_kwargs={"k": self.settings.k}
)
documents = self.retriever.invoke(question)这体现了典型 RAG 分工:
VectorStore:存储与搜索能力Retriever:根据问题找证据LLM:阅读证据并组织回答
定义提示词与回答规则。
重点不是文风,而是行为约束:
- 基于检索证据回答
- 不确定时明确说明
- 多子问题分项回答
- 尽量附带来源
对 DashScope embedding 做项目级封装。
主要处理:
- 向量维度控制
- batch 限制
- 实际返回维度校验
LangChain Document 通常包含:
page_contentmetadata
这表示 RAG 处理的不是纯文本,而是“文本 + 来源信息”。
长文档不能直接整体做一个向量,因此需要切成更小的 chunk。
切分的原因包括:
- 让检索更精确
- 降低上下文冗余
- 让问题与局部内容匹配
embedding 是把文本映射成向量。
Milvus 不理解自然语言,但可以比较向量之间的相似度。
向量维度是 schema 的一部分。
如果 collection 的向量字段按某个维度建好,后续写入的向量长度必须一致。
VectorStore 是 LangChain 对“向量存储 + 相似度搜索”的统一抽象。
在本项目中,底层实现是 Milvus。
Retriever 是专门负责“根据问题找相关文档”的组件。
典型链路:
question -> retriever -> documents -> llm -> answer
python -m venv .venv
.\.venv\Scripts\activate
pip install -r requirements.txtDASHSCOPE_API_KEY=...
BASE_URL=...
MILVUS_URI=...
MILVUS_TOKEN=...
MILVUS_DB=paper_rag
MILVUS_COLLECTION=paper_chunks
EMBEDDING_MODEL=text-embedding-v4
EMBEDDING_DIM=1024说明:
MILVUS_COLLECTION决定写入哪张 collectionEMBEDDING_DIM必须与实际输出向量长度一致- 测试不同维度时,建议使用新的 collection 名称
python -m rag_app.app --help
python -m rag_app.app ingest-papers 2310.11511v1.pdf
python -m rag_app.app chatpython -m rag_app.app ingest-papers 2310.11511v1.pdf
python -m rag_app.app chat目标:
- 知道一个最小 RAG 项目怎样运行
重点看这几行:
vector_store.add_documents(chunks)self.retriever = self.vector_store.as_retriever(...)documents = self.retriever.invoke(question)目标:
- 理解 LangChain 风格 RAG 的核心接口
优先调整这些参数:
CHUNK_SIZECHUNK_OVERLAPTOP_KEMBEDDING_DIMMILVUS_COLLECTION
观察:
- 检索结果是否更集中
- 回答是否更具体
- 是否更容易丢失上下文
langchain-milvus 内部混用了 MilvusClient 与 ORM alias 机制。
在某些版本组合下,可能出现:
ConnectionNotExistException: should create connection first.
这类问题通常不是凭证错误,而是连接管理衔接问题。
当代码写成:
vector_store.add_documents(chunks)底层仍然会发生很多事:
- 文本与 metadata 提取
- embedding 调用
- collection 检查或初始化
- 写入与索引操作
优点是代码短,缺点是出错时不透明。
链路涉及:
langchainlangchain-corelangchain-communitylangchain-milvuspymilvus
层数越多,兼容性风险越高。
vector_store = get_vector_store(embeddings, settings)
vector_store.add_documents(chunks)texts = [chunk.page_content for chunk in chunks]
vectors = embeddings.embed_documents(texts)
rows = []
for chunk, vector in zip(chunks, vectors, strict=True):
rows.append(
{
"text": chunk.page_content,
"source": chunk.metadata.get("source", "unknown"),
"page": chunk.metadata.get("page", -1),
"vector": vector,
}
)
collection.insert(rows)
collection.flush()langchain-milvus 在这里包装掉了:
- 从
Document提取文本和 metadata - embedding 调用衔接
- 写入结构转换
- 插入流程封装
retriever = vector_store.as_retriever(search_kwargs={"k": 4})
documents = retriever.invoke(question)query_vector = embedding.embed_query(question)
results = collection.search(
data=[query_vector],
anns_field="vector",
param={"metric_type": "COSINE", "params": {}},
limit=4,
output_fields=["text", "source", "page"],
)并手动转换为:
documents = []
for result in results[0]:
entity = result.entity
documents.append(
Document(
page_content=entity.get("text"),
metadata={
"source": entity.get("source", "unknown"),
"page": entity.get("page", "?"),
},
)
)langchain-milvus 在这里包装掉了:
- query embedding 与搜索衔接
- 搜索结果解析
- 结果转
Document Retriever接口抽象
langchain-milvus 包装的是接入细节,不是 RAG 原理。
它帮助简化:
- 文档转换
- 向量写入衔接
- 检索结果转换
- retriever 接口
它不会替代这些基础问题:
- 向量维度一致性
- chunk 设计
- top-k 选择
- embedding 质量
- 依赖兼容性
DashScope embedding 有两条常见调用路径:
参数名:
dimensions
参数名:
dimension
本项目当前走的是 dashscope.TextEmbedding.call(...) 这条路径,因此必须使用:
dimension=768如果误写成:
dimensions=768可能会被静默忽略,并回退到模型默认维度。
可以继续在这个项目上做这些扩展:
- 给 README 每章补思考题
- 给项目增加
tests/ - 增加检索结果可视化输出
- 比较不同 chunk 参数的实验结果
- 比较
langchain-milvus与原生pymilvus的维护成本