
释放本地抹布的力量:Deepseek-r1 和 Ollama 实现终极 PDF 问题解答!
深度搜索RAG工具
Source: canva
深度搜索模型现在正在整合到各种工具和程序中。
这让用户暂时摆脱了开放AI和其他顶级大型语言模型的束缚。
我们已经看到RAG过程很多次,大多数情况下我们看到人们使用开放AI进行大型语言模型的用途。
我想,为什么不尝试使用深度搜索R1制作一个简单而强大的RAG工具,全部在本地完成呢?
这个项目到底是关于什么的?
你是否曾经想过仅仅问一个PDF一个问题?
就像你在与一个人聊天一样——并得到一个明确、切合实际的答案?
这就是RAG或检索增强生成的全部内容。
简单来说,它提取你PDF中最重要的部分,并将其展示给语言模型,然后模型仅使用这些部分来回答你的问题。
听起来不错吧?
现在让我们来看看如何在本地运行DeepSeek-R1,使用Ollama,这样一切都保留在你的计算机上。
为什么RAG很棒
我知道我们大多数人已经知道RAG是什么。但我假设我们并不知道。
假设你有一个装满事实的PDF或者一个课程的大纲。
你当然可以自己阅读,但有时候你只想输入,
“嘿,第三周涵盖了哪些主题?”
并得到一个快速的回复。
这就是RAG。基本上,它聚焦于相关文本并迅速生成答案。
很棒,对吧?
项目计划
我们将建立一个 Streamlit 应用程序,它可以:
- 让您选择一个 PDF。
- 将其切分成可管理的部分(块)。
- 将每个部分转换为计算机可以快速搜索的内容(嵌入)。
- 将这些嵌入存储在内存中。
- 通过相同的过程处理您的问题,找到匹配的块,并给模型提供它所需的确切信息以进行回答。
我们在本地完成所有这些操作,因此您的 PDF 保持在您的机器上。
让我们来看看代码
Don’t sweat it if code isn’t your forte — I’ll keep it chill.
导入必要组件
import streamlit as st
from langchain_ollama import OllamaEmbeddings, OllamaLLM
from langchain_community.document_loaders import PDFPlumberLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_core.prompts import ChatPromptTemplate
这里发生了什么:
Streamlit: 我们简单易用的网页界面。
PDFPlumber加载器: 读取PDF文本。
递归字符文本分割器: 切分大文本。
Ollama嵌入 / Ollama大型语言模型: 连接到我们的深度搜索R1模型。
内存向量存储: 存储您的PDF的精美向量版本。
聊天提示模板: 帮助我们塑造与模型的对话方式。
2. 一些基本设置
PDF_STORAGE = "document_store/pdfs/"
EMBEDDING_MODEL_NAME = "deepseek-r1:1.5b"
我们在告诉我们的代码:
将PDF放在
document_store/pdfs/
中。
使用名为
deepseek-r1:1.5b
的深度搜索R1模型进行嵌入和生成答案。
3. 启动我们的本地模型 + 向量存储
embeddings = OllamaEmbeddings(model=EMBEDDING_MODEL_NAME)
vector_db = InMemoryVectorStore(embeddings)
llm = OllamaLLM(model=EMBEDDING_MODEL_NAME)
vector_db
是我们跟踪这些向量的地方,以便进行搜索。llm
是我们的语言模型(深度搜索R1),实际生成文本。
4. 便捷助手函数
def save_uploaded_file(uploaded_file):
file_path = PDF_STORAGE + uploaded_file.name
with open(file_path, "wb") as f:
f.write(uploaded_file.getbuffer())
return file_path
这个函数只是将PDF写入我们的本地文件夹。
这里没有什么复杂的。
def load_pdf(file_path):
loader = PDFPlumberLoader(file_path)
return loader.load()
这个函数打开PDF并提取其文本。
def split_into_chunks(docs):
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
return splitter.split_documents(docs)
在这里,我们将您的文档拆分为更小的块。
重叠200个字符可以保持重要细节不被拆分到不同的块中。
def index_chunks(chunks):
vector_db.add_documents(chunks)
我们将这些块转换为向量并存储,以便稍后可以快速查找。
def find_related_docs(query):
return vector_db.similarity_search(query)
当您提出问题时,这个函数会找出哪些块与您的查询匹配。
def generate_answer(user_query, related_docs):
context = "\\n\\n".join([d.page_content for d in related_docs])
prompt_template = """
You are a research assistant. Use the context below to answer the user's question.
Context: {context}
Question: {question}
Answer:
"""
chat_prompt = ChatPromptTemplate.from_template(prompt_template)
chain = chat_prompt | llm
final_answer = chain.invoke({
"context": context,
"question": user_query
})
return final_answer
最后,一旦我们知道哪些块是重要的,我们就将该文本输入模型。
模型会处理这些内容并返回一个整洁的答案,引用您的PDF。
Streamlit Magic
现在是网络界面。
这是一个最小的示例:
st.title("深度搜索RAG演示")
st.write("使用深度搜索R1和Ollama询问有关您的PDF的问题!")
uploaded_file = st.file_uploader("上传PDF", type=["pdf"])
if uploaded_file is not None:
path_to_pdf = save_uploaded_file(uploaded_file)
docs = load_pdf(path_to_pdf)
chunks = split_into_chunks(docs)
index_chunks(chunks)
st.success("PDF索引成功!在下面输入您的问题。")
user_query = st.text_input("您的问题")
if user_query:
with st.spinner("正在搜索和生成答案..."):
related_docs = find_related_docs(user_query)
answer = generate_answer(user_query, related_docs)
st.write(answer)
这是流程:
您上传一个PDF。
代码将其分割成块并构建一个向量数据库。
您输入一个问题,我们将其与这些块进行比较并找到最佳匹配。
我们将这些匹配结果提供给模型,然后模型给出答案。
通过以下命令运行它:
streamlit run my_rag_app.py
然后观看您的浏览器打开这个甜美的小问答工具。
处理问题
缺少包?
仔细检查您是否安装了 requirements.txt
中的所有内容(如 pdfplumber
、streamlit
、langchain
等)。
未找到 DeepSeek-R1?
确保运行 ollama pull deepseek-r1:1.5b
,以便它在您的机器上。
调整块:
如果 1,000 个字符太大或太小,请尝试调整 chunk_size
和 chunk_overlap
。
所以这就是全部:一个本地的、私有的、参考您 PDF 中确切文本的问答系统。
在您看到它的实际运行后,您可能会想知道您是如何在没有它的情况下度过的。
您甚至可以添加聊天历史或多个 PDF,以使其更加花哨。
但现在基本功能都在这里。