RAG项目

0 阅读5分钟

给自己的文档搭建一个问答机器人

分三步走:

第一步:安装依赖,处理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("文档中提到了哪些关键概念?")

image.png

这个代码做了

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

跑起来可以试着问两个有关联的问题,比如:

image.png

多轮对话RAG跑通了

第二个问题--你说‘它解决了什么问题’,没有提模型名字,但是AI知道“它是指Anomaly-OneVision,这就是多轮对话在起作用。

回顾一下加了什么:

history = []  # 对话历史

# 每次提问时:系统提示 + 历史 + 当前问题
messages = [SystemMessage(...)] + history + [HumanMessage(question)]

# 回答后存入历史
history.append(HumanMessage(content=question))
history.append(AIMessage(content=reply))

本质还是之前学的message机制--把历史对话一起发给AI,他就能“记住”上下文