引言
在企业数字化转型浪潮中,大量非结构化文档(合同、报告、手册、邮件)成为沉睡的数据资产。传统关键词搜索效率低、语义理解弱,而大语言模型(LLM)虽然知识渊博,却无法直接访问企业内部文档,且存在幻觉问题。将LLM与文档检索结合的检索增强生成(RAG)技术,成为智能文档处理的核心范式。
本文将带你从零实现一个基于LLM的文档智能处理系统,它能自动加载多种格式的本地文档,将内容向量化存储,并用自然语言提问获得精准答案。我们将使用LangChain作为编排框架,Chroma作为向量数据库,OpenAI提供大模型能力,最终构建一个在命令行即可交互的文档问答机器人。所有代码完整可运行,注释详尽,可直接复现。
核心概念:RAG 为什么是文档智能的基石
RAG(Retrieval-Augmented Generation) 的工作流程分为三步:
- 文档索引(Indexing):将文档分割成合适大小的块,使用嵌入模型将每个块转换为向量,存入向量数据库。
- 检索(Retrieval):用户提问时,同样将问题向量化,在向量库中搜索最相似的Top-K个文档块。
- 生成(Generation):将检索到的文档块作为上下文,连同问题一起送入LLM,生成准确的回答。
这样做的好处显而易见:LLM能基于企业私有数据回答问题,且答案可追溯来源,大幅降低幻觉。LangChain提供了即插即用的组件,能轻松串联整个流程。
实战:构建可运行的文档智能问答系统
下面是完整的项目实现。我们将创建一个Python脚本,支持pdf、docx、txt格式,使用OpenAI的gpt-3.5-turbo和text-embedding-ada-002模型。运行前请确保已安装以下依赖:
bash
pip install langchain langchain-openai chromadb pypdf docx2txt
同时需要设置环境变量OPENAI_API_KEY。
完整代码
创建文件doc_qa.py,代码如下:
python
import os
import sys
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
from langchain.document_loaders import PyPDFLoader, Docx2txtLoader, TextLoader
from langchain.schema import Document
# -------------------- 1. 加载多种格式文档 --------------------
def load_documents(file_path: str) -> list[Document]:
"""
根据文件扩展名选择合适的加载器,返回Document对象列表。
支持:pdf, docx, txt
"""
ext = os.path.splitext(file_path)[1].lower()
if ext == ".pdf":
loader = PyPDFLoader(file_path)
elif ext == ".docx":
loader = Docx2txtLoader(file_path)
elif ext == ".txt":
loader = TextLoader(file_path, encoding="utf-8")
else:
raise ValueError(f"不支持的文件格式: {ext}")
return loader.load()
# -------------------- 2. 文档分割 --------------------
def split_documents(documents: list[Document], chunk_size=500, chunk_overlap=50) -> list[Document]:
"""
使用递归字符分割器,保持语义连贯性。
chunk_size: 每块最大字符数
chunk_overlap: 相邻块重叠字符数,防止关键信息被截断
"""
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] # 中文优先
)
return splitter.split_documents(documents)
# -------------------- 3. 建立向量存储 --------------------
def create_vector_store(docs: list[Document], persist_dir: str = "doc_vectordb"):
"""
使用OpenAI嵌入模型将文档块向量化,存入Chroma本地持久化目录。
若目录已存在,则直接加载,避免重复计算。
"""
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
if os.path.exists(persist_dir) and os.listdir(persist_dir):
print("检测到已有向量库,直接加载...")
vectordb = Chroma(persist_directory=persist_dir, embedding_function=embeddings)
else:
print("正在向量化文档并存储...")
vectordb = Chroma.from_documents(
documents=docs,
embedding=embeddings,
persist_directory=persist_dir
)
return vectordb
# -------------------- 4. 构建问答链 --------------------
def build_qa_chain(vectordb):
"""
创建一个RetrievalQA链,使用GPT-3.5-turbo生成答案。
检索器使用向量相似度搜索,返回4个最相关文档块。
"""
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
retriever = vectordb.as_retriever(search_kwargs={"k": 4})
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # 将检索到的文档直接拼接作为上下文
retriever=retriever,
return_source_documents=True # 返回来源文档,便于验证
)
return qa_chain
# -------------------- 主程序 --------------------
def main():
if len(sys.argv) < 2:
print("用法: python doc_qa.py <文档路径>")
sys.exit(1)
file_path = sys.argv[1]
if not os.path.exists(file_path):
print(f"文件不存在: {file_path}")
sys.exit(1)
# 1. 加载文档
print(f"正在加载文档: {file_path}")
raw_docs = load_documents(file_path)
print(f"文档加载完毕,页数/段落数: {len(raw_docs)}")
# 2. 分割文档
docs = split_documents(raw_docs)
print(f"分割为 {len(docs)} 个文本块")
# 3. 建立向量库(持久化到 doc_vectordb 目录)
vectordb = create_vector_store(docs)
# 4. 构建问答链
qa = build_qa_chain(vectordb)
# 5. 交互式问答
print("\n文档问答系统已就绪,输入问题(exit 退出,clr 清空历史)")
while True:
query = input("\n用户: ").strip()
if query.lower() in ["exit", "quit"]:
break
if query.lower() == "clr":
vectordb.delete_collection()
vectordb = create_vector_store(docs)
qa = build_qa_chain(vectordb)
print("向量库已重置")
continue
if not query:
continue
try:
result = qa.invoke({"query": query})
answer = result["result"]
sources = result["source_documents"]
print(f"助手: {answer}")
# 显示引用的文档片段(截取前80字符)
if sources:
print("-" * 50)
for i, doc in enumerate(sources):
source = doc.metadata.get("source", "未知来源")
snippet = doc.page_content.replace("\n", " ")[:80]
print(f" 来源{i+1}: {source} -> {snippet}...")
except Exception as e:
print(f"出错了: {e}")
if __name__ == "__main__":
main()
使用说明
- 准备一份PDF/Word/TXT文件,例如
企业年报.pdf。 - 在终端执行:
bash python doc_qa.py 企业年报.pdf - 系统会加载文档、分割、向量化并持久化到
doc_vectordb目录。再次运行时直接加载,无需重复计算。 - 输入问题,如"去年的总营收是多少?",助手将给出基于文档的精准回答,并打印引用的片段和来源页码。
代码要点解析
- 多格式加载器 :通过判断扩展名选用
PyPDFLoader、Docx2txtLoader、TextLoader,实现统一接口。 - 中文友好分割 :
RecursiveCharacterTextSplitter的分隔符列表中包含中文标点(。!?;,),优先在句末截断,保持语义完整。 - 持久化向量库:Chroma支持本地磁盘存储,避免每次运行都重新嵌入,节省Token开销。通过检查目录是否存在来决定新建还是加载。
- 来源追溯 :
return_source_documents=True返回源头文档,便于验证答案是否忠于原文。 - 清空历史功能 :输入
clr可删除集合并重建,方便更换文档或重置。
运行截图展示(文字描述):加载文档后,向量化耗时约数秒(取决于文档大小),问答过程实时返回,来源信息清晰可读。
常见问题与注意事项
1. 处理超大文档或批量文档怎么办?
本示例加载单个文件,但load_documents可以轻松扩展为遍历文件夹,将所有文档合并后统一分割索引。对于巨大文档(如数百页),可适当调大chunk_size(如1000),减少块数量,同时增大k(检索数量)以获取更全面的上下文。若文档数量过多导致向量库过大,可考虑使用Chroma的元数据过滤或更换为FAISS等索引。
2. 中文文档效果不佳如何优化?
- 分割时使用中文标点优先分隔,我们已配置。
- 嵌入模型选择:
text-embedding-ada-002在多语言表现良好,但若完全中文场景,可尝试BAAI的bge-large-zh等国产模型,需替换嵌入层。 - LLM本身的理解能力:GPT-3.5对中文支持尚可,复杂长文建议使用GPT-4或通义千问等。
3. 成本控制(API费用)
- 嵌入成本:文档每1千tokens约0.0001,向量化10万字约0.01。持久化向量库是核心省钱策略。
- 对话成本:每次问答会将问题+检索到的上下文拼接发给LLM,上下文越多费用越高。可通过调整
k值和chunk_size平衡成本与效果。 - 免费替代:可切换为Ollama本地模型(如llama2、qwen),只需更换Embeddings和LLM对象,LangChain社区有对应封装。
4. 多轮对话支持
当前实现是一问一答,无历史记忆。如需多轮对话,可引入ConversationalRetrievalChain,并使用带记忆的聊天模型,LangChain提供了ConversationBufferMemory等组件。修改时注意上下文长度控制。
5. 实时更新索引
当源文档改动时,需重新索引。可在代码中增加文件哈希校验,只在文件变化时重建向量库;或利用Chroma的增量添加功能,删除旧文档ID再添加新文档。
6. 安全与权限
本系统运行在本地,但向量库和LLM调用可能涉及敏感数据上传OpenAI。企业内部部署建议使用私有化大模型(例如通过FastChat部署的Vicuna)和本地嵌入服务,避免数据外泄。此外,对用户提问也应进行适当过滤。
总结
本文从零搭建了一个基于大语言模型的文档智能处理系统,涵盖了文档加载、智能分割、向量化存储、检索增强生成的全流程。通过LangChain的强大抽象,我们用不到150行代码就实现了支持多种格式、持久化、可追溯的文档问答机器人。这个系统不仅是一个Demo,更可以作为企业知识库、合同审查、档案管理等场景的原型基础。
扩展方向包括:支持更多文档类型(HTML、Markdown)、引入OCR处理扫描件、结合结构数据实现表格问答、部署为Web服务等。RAG技术的边界正在不断被拓展,但核心始终是:让大模型扎根于真实、可靠的数据。希望这篇实战能帮助你快速上手,在业务中释放文档的价值。
参考资料
-
LangChain官方文档:https://python.langchain.com
-
Chroma使用指南:https://docs.trychroma.com
-
RAG论文:Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks