给自己的文档搭建一个问答机器人
分三步走:
第一步:安装依赖,处理PDF
第二步:建立向量数据库,存文档
第三步:写问答逻辑,跑通完整RAG
先装依赖,在cmd(llm虚拟环境里)运行:
pip install langchain-community chromadb pypdf sentence-transformers
中途还爆了错误,发现是python3.14太新,torch和onnxruntime的DLL都不兼容,只能新建一个Python3.11的虚拟环境,3.11是目前AI库支持的最好环境(2026.4.22)
重新下载Python3.11并且创建激活3.11的虚拟环境。
代码 激活虚拟环境
D:\pyenv\llm311\Scripts\activate.bat
然后运行
python D:\projects\llm-demo\rag_demo.py
from langchain_openai import ChatOpenAI
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_core.messages import HumanMessage, SystemMessage
import chromadb
from chromadb.utils.embedding_functions import ONNXMiniLM_L6_V2
print("正在读取 PDF...")
loader = PyPDFLoader("D:/projects/llm-demo/doc.pdf")
pages = loader.load()
print(f"共读取 {len(pages)} 页")
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(pages)
print(f"切成 {len(chunks)} 个片段")
print("正在建立向量数据库...")
# 用 chromadb 内置的 ONNX 模型,不依赖 torch
ef = ONNXMiniLM_L6_V2()
client = chromadb.Client()
collection = client.create_collection("docs", embedding_function=ef)
texts = [c.page_content for c in chunks]
ids = [str(i) for i in range(len(chunks))]
collection.add(documents=texts, ids=ids)
print("向量数据库建立完成 ✅")
llm = ChatOpenAI(
api_key="your api key",
base_url="https://api.deepseek.com",
model="deepseek-chat"
)
def ask(question):
results = collection.query(query_texts=[question], n_results=3)
context = "\n\n".join(results["documents"][0])
print(f"\n用户:{question}")
prompt = "你是文档问答助手,只根据以下文档内容回答,没有相关信息就说【文档中未提及】。\n\n文档内容:\n" + context
messages = [
SystemMessage(content=prompt),
HumanMessage(content=question)
]
response = llm.invoke(messages)
print(f"AI:{response.content}")
ask("这篇文档的主要内容是什么?")
ask("文档中提到了哪些关键概念?")
这个代码做了
PDF文件
↓ PyPDFLoader 读取
5页原文
↓ RecursiveCharacterTextSplitter 切块
18个片段
↓ ONNX embedding 模型转成向量
存入 Chroma 向量数据库
↓ 用户提问 → 搜索最相关的3个片段
相关内容 + 问题 → 发给 DeepSeek
↓ 准确回答 ✅ 这就是完整的RAG流程。
设想一个场景,假设有100篇论文,然后想问AI里面的内容。
直接把100页全发给AI?不行,太长了,而且每次都要发而且很贵。
RAG就是解决这个问题的。
RAG的核心思路:先搜索,再回答
分两个阶段:
阶段一:建库(只做一次)
论文PDF
→ 切成小块(你的代码切成了18块)
→ 每块转成一串数字(向量)
→ 存进数据库
阶段二:问答(每次提问时)
你的问题
→ 也转成一串数字(向量)
→ 在数据库里找"数字最相近"的几块
→ 把这几块原文 + 你的问题一起发给AI
→ AI只看这几块内容来回答
向量是什么意思? 这是RAG最关键的概念,文字没法直接比较相似度,但是数字可以,向量就是把文字的意思转成一串数字
"如何检测安全漏洞" → [0.2, 0.8, 0.1, 0.9, ...]
"漏洞扫描的方法" → [0.2, 0.7, 0.1, 0.8, ...] ← 数字很接近
"今天天气怎么样" → [0.9, 0.1, 0.6, 0.2, ...] ← 数字差很远
意思相近的句子,转出来的数字就相近。这样就能找到语义最相关的片段,而不是关键词匹配。
对应的代码是:
# 切块 → 把论文切成18个小片段
chunks = splitter.split_documents(pages)
# 转向量+存库 → 18个片段全部变成数字存进去
collection.add(documents=texts, ids=ids)
# 搜索 → 找和问题最相近的3块
results = collection.query(query_texts=[question], n_results=3)
# 回答 → 把这3块原文给AI看,让它回答
context = "\n\n".join(results["documents"][0])
一句话总结:RAG=先用“语义搜索”找到相关片段,再让AI基于片段回答,而不是让AI凭空猜测
下一步把RAG升级——加上多轮会话,让它记住你问过的问题。
现在的RAG有一个问题,你问“文档里面的模型叫什么” AI回答了
但是你再问:“它有什么优点”,AI并不知道“它”指什么,因为它不记得上一句。
加上多轮会话之后,AI就能记住上下文了。
把rag_demo.py改成下列:
from langchain_openai import ChatOpenAI
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
import chromadb
from chromadb.utils.embedding_functions import ONNXMiniLM_L6_V2
# ── 1. 读取并切块 PDF ─────────────────────────────────────────
print("正在读取 PDF...")
loader = PyPDFLoader("D:/projects/llm-demo/doc.pdf")
pages = loader.load()
chunks = RecursiveCharacterTextSplitter(
chunk_size=500, chunk_overlap=50
).split_documents(pages)
print(f"共 {len(pages)} 页,切成 {len(chunks)} 个片段")
# ── 2. 建向量数据库 ───────────────────────────────────────────
print("正在建立向量数据库...")
ef = ONNXMiniLM_L6_V2()
client = chromadb.Client()
collection = client.create_collection("docs", embedding_function=ef)
collection.add(
documents=[c.page_content for c in chunks],
ids=[str(i) for i in range(len(chunks))]
)
print("完成 ✅\n")
# ── 3. 初始化模型 ─────────────────────────────────────────────
llm = ChatOpenAI(
api_key="your-api-key-here",
base_url="https://api.deepseek.com",
model="deepseek-chat"
)
# ── 4. 多轮对话循环 ───────────────────────────────────────────
print("开始对话,输入 exit 退出\n")
history = [] # 存储对话历史
while True:
question = input("你:")
if question == "exit":
break
# 搜索相关片段
results = collection.query(query_texts=[question], n_results=3)
context = "\n\n".join(results["documents"][0])
# 构建消息:系统提示 + 历史对话 + 当前问题
messages = [
SystemMessage(content="你是文档问答助手,优先根据以下文档内容回答,没有相关信息就说【文档中未提及】。\n\n文档内容:\n" + context)
]
messages += history # 加入历史对话
messages.append(HumanMessage(content=question))
# 调用AI
response = llm.invoke(messages)
reply = response.content
print(f"AI:{reply}\n")
# 把这轮对话存入历史
history.append(HumanMessage(content=question))
history.append(AIMessage(content=reply))
python D:\projects\llm-demo\rag_demo.py
跑起来可以试着问两个有关联的问题,比如:
多轮对话RAG跑通了
第二个问题--你说‘它解决了什么问题’,没有提模型名字,但是AI知道“它是指Anomaly-OneVision,这就是多轮对话在起作用。
回顾一下加了什么:
history = [] # 对话历史
# 每次提问时:系统提示 + 历史 + 当前问题
messages = [SystemMessage(...)] + history + [HumanMessage(question)]
# 回答后存入历史
history.append(HumanMessage(content=question))
history.append(AIMessage(content=reply))
本质还是之前学的message机制--把历史对话一起发给AI,他就能“记住”上下文