查看: 993|回复: 3

Python调用DeepSeek API实现本地文档问答助手代码解析

[复制链接]
发表于 6 小时前 | 显示全部楼层 |阅读模式
很多大模型应用并非从零训练模型,而是将现有API接入具体业务流程。本文以一个本地文档问答助手为例,展示如何使用Python、Streamlit、DeepSeek API、pypdf和scikit-learn构建一个入门版RAG(检索增强生成)应用。项目实现的效果是:上传PDF或TXT文档后,程序自动读取文本内容,将长文本切分成多个片段,根据用户问题检索相关片段,再调用DeepSeek大模型生成回答,并展示答案与参考片段。

技术选型方面,项目以Python为核心语言,Streamlit负责快速搭建Web页面,DeepSeek API提供大模型生成能力,OpenAI SDK用于兼容调用DeepSeek接口,pypdf负责读取PDF文本,scikit-learn使用TF-IDF与余弦相似度完成文本检索。不引入LangChain或向量数据库,目的是用较少代码理解RAG核心流程。

项目原理可以拆解为五步:读取上传文档、将文档切分成多个文本片段、计算用户问题和文本片段的相似度、取出最相关的几个片段、将片段和问题一起交给大模型生成回答。这里的检索基于TF-IDF和余弦相似度,属于关键词匹配层面的检索,不是真正的语义向量检索,但胜在代码简单、依赖少,适合入门。

环境准备建议使用Python 3.10以上版本。创建项目目录后,执行以下命令:
  1. mkdir document_qa_demo
  2. cd document_qa_demo
  3. python -m venv .venv
复制代码
Windows PowerShell激活虚拟环境使用“.venv\Scripts\Activate.ps1”,macOS/Linux使用“source .venv/bin/activate”。安装依赖时执行:
  1. pip install streamlit openai scikit-learn pypdf
复制代码
也可以将依赖写入requirements.txt后统一安装。DeepSeek API兼容OpenAI SDK,调用时需要配置base_url和API Key。临时设置环境变量的方式为:
  1. # Windows PowerShell
  2. $env:DEEPSEEK_API_KEY="你的 API Key"
  3. # macOS / Linux
  4. export DEEPSEEK_API_KEY="你的 API Key"
复制代码
使用Streamlit secrets时,创建.streamlit/secrets.toml文件并写入:
DEEPSEEK_API_KEY = "你的 API Key"
注意不要把API Key提交到GitHub或写进公开代码中。

完整项目目录结构如下:
  1. document_qa_demo
  2. ├── app.py
  3. ├── requirements.txt
  4. └── .streamlit
  5.     └── secrets.toml
复制代码
其中app.py是主程序,requirements.txt是依赖列表,.streamlit/secrets.toml是本地密钥配置(可选)。

核心代码中,首先定义模型名称和API Key获取函数。模型使用“deepseek-v4-flash”,API Key优先从st.secrets读取,其次从环境变量读取。PDF读取使用pypdf:
  1. def read_pdf(uploaded_file):
  2.     reader = PdfReader(BytesIO(uploaded_file.getvalue()))
  3.     text_list = []
  4.     for page in reader.pages:
  5.         page_text = page.extract_text()
  6.         if page_text:
  7.             text_list.append(page_text)
  8.     return "\n".join(text_list)
复制代码
TXT读取直接使用uploaded_file.getvalue().decode("utf-8", errors="ignore")。文本切分函数split_text设置了两个参数:chunk_size决定每个片段的大致长度,overlap控制相邻片段之间的重叠长度。保留重叠是为了避免一句话或一个段落被切断后丢失上下文。切分规则是每次前进chunk_size - overlap个字符,当前片段长度大于80字符才保留。
  1. def split_text(text, chunk_size=700, overlap=120):
  2.     chunks = []
  3.     start = 0
  4.     while start < len(text):
  5.         end = start + chunk_size
  6.         chunk = text[start:end].strip()
  7.         if len(chunk) > 80:
  8.             chunks.append(chunk)
  9.         start = end - overlap
  10.     return chunks
复制代码
检索相关片段时,使用TfidfVectorizer,并针对中文情况设置了字符级n-gram:
  1. vectorizer = TfidfVectorizer(
  2.     analyzer="char",
  3.     ngram_range=(2, 4)
  4. )
  5. doc_vectors = vectorizer.fit_transform(chunks)
  6. question_vector = vectorizer.transform([question])
  7. scores = cosine_similarity(question_vector, doc_vectors)[0]
复制代码
这样即使不使用分词工具,也能完成基础的中文检索效果。排序后取出top_k个片段,返回内容与相似度分数。

调用DeepSeek API时,使用OpenAI客户端并指定base_url为“https://api.deepseek.com”。构造消息时,系统提示词要求模型只根据用户提供的资料回答问题,如果资料中没有相关信息则明确说明无法确定。用户消息中拼接了检索到的资料片段和原始问题,并设置了回答要求:先直接回答,不编造信息,资料不足时明确说明,最后说明依据来自哪些片段。
  1. client = OpenAI(
  2.     api_key=api_key,
  3.     base_url="https://api.deepseek.com"
  4. )
  5. response = client.chat.completions.create(
  6.     model=MODEL_NAME,
  7.     messages=[...],
  8.     stream=False
  9. )
  10. return response.choices[0].message.content
复制代码
Streamlit界面部分,侧边栏增加了三个可调参数:文本片段长度(300-1500,默认700)、片段重叠长度(0-300,默认120)、检索片段数量(1-8,默认4)。文件上传组件限制类型为PDF和TXT。当用户上传文档并输入问题后,点击“生成回答”按钮,程序依次执行读取文档、切分文本、检索片段、调用大模型生成回答,最后展示答案和参考片段。参考片段以expander折叠面板显示,并带相似度数值。

运行项目时,在项目目录执行:
  1. streamlit run app.py
复制代码
如果命令不可用,可使用“python -m streamlit run app.py”。默认访问地址是http://localhost:8501。

关于常见问题,首先,上传PDF后没有内容,大概率是扫描版PDF,每一页本质上是图片而非文字,pypdf只能提取文本型PDF,扫描版需要OCR工具识别。其次,回答不够准确的原因可能包括:文档切分太短导致上下文不完整、切分太长导致检索不精确、TF-IDF偏关键词匹配而非语义匹配、问题表述与文档内容差异较大。可以尝试调整侧边栏的片段长度、重叠长度和检索数量。第三,TF-IDF与真正向量检索的区别在于:TF-IDF更关注字词是否相似,Embedding向量检索更关注语义是否相似。例如“如何申请报销”和“费用报销流程是什么”字面不同但语义接近,Embedding通常能更好识别这种关系。第四,API Key不要写在代码里,建议使用环境变量、Streamlit secrets或部署平台提供的密钥管理功能。如果代码要上传GitHub,需将.streamlit/secrets.toml加入.gitignore。

后续优化方向包括:使用Embedding模型替代TF-IDF提高语义检索效果;使用FAISS或Chroma存储向量,支持更大文档库;支持多文件上传形成个人知识库;记录历史对话实现连续追问;增加页码引用让答案可溯源到PDF具体页面;增加FastAPI后端实现前后端分离;增加Dockerfile方便部署;接入OCR支持扫描版PDF。升级路线可以规划为:版本1为TF-IDF + Streamlit单文件Demo,版本2为Embedding + FAISS语义检索,版本3为多文档知识库 + 历史对话,版本4为FastAPI后端 + 前端页面,版本5为Docker部署上线。

这个项目已覆盖大模型应用开发中的几个关键点:Prompt设计、API调用、文档处理、文本检索、RAG基本流程和Web页面展示。先完成一个能运行、能演示、能继续扩展的小项目,比一开始直接堆复杂框架更容易理解核心逻辑。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python调用DeepSeek API实现本地文档问答助手代码解析

楼主这个入门 RAG 写得很清晰,正好适合我这种想了解大模型应用又不想一上来就上 LangChain 的人。我已经照着跑通了一遍,有几个小感受和问题: 1. **文本切分的 overlap 逻辑**确实能减少断句丢上下文,但对中文长文档,如果正好切在代码或表格中间,还是会有一些奇怪片段。不过作为 demo 完全够用了。 2. **TF-IDF 字符 n-gram** 这个思路很妙,省了分词,但感觉如果文档是英文的话,`analyzer="char"` 可能不如 `"word"` 效果好。楼主有试过中英文混合文档吗? 3. 我这边用 `pypdf` 遇到一个扫描版 PDF,里面根本没有文字层,提取出来是空的。所以这个 demo 对纯文字型 PDF 友好,扫描版可能得先 OCR。楼主后续有没有考虑加这一步? 4. 另外想确认一下,`deepseek-v4-flash` 这个模型名是 DeepSeek 官方的新模型吗?我平时用的是 `deepseek-chat`,如果官方已经出了 flash 版本,我也想去试一下。 总之感谢分享,代码结构简单明了,很适合作为 RAG 的入门模板,期待你后续更多作品!
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python调用DeepSeek API实现本地文档问答助手代码解析

感谢楼主分享!这个Demo把RAG的流程讲得非常清楚,代码也很精简,特别适合我这种刚接触大模型应用开发的人。我照着搭了一遍,能跑通,确实理解了“文本切分-检索-生成”的核心链路。 有个小问题想请教:楼主提到TF-IDF是关键词层面的检索,如果用户问的问题和文档里的措辞差别比较大,是不是会经常检索不到相关内容?有没有试过用embedding模型或者结合向量库来提升召回效果?当然,我也理解这样会增加复杂度,目前这个入门版已经很棒了。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python调用DeepSeek API实现本地文档问答助手代码解析

楼主的讲解很清晰,把RAG的基本流程串起来了,对入门来说确实是很好的参考。我尤其喜欢用TF-IDF+字符n-gram做中文检索那部分,省了分词依赖,又能覆盖一些词形变化,简洁实用。 有一点想请教:文本切分时overlap固定为120字符,chunk_size=700,遇到中英文混排或代码块较多的文档,会不会出现切在很别扭的位置?虽然对整体问答影响可能不大,但好奇有没有考虑过按段落或句子边界做自适应切分。当然那样代码会复杂不少,可能就不符合“少代码理解核心”的初衷了。 另外,检索到的片段拼接后直接塞给模型,如果片段很多,会不会超过模型的上下文限制?是不是需要根据top_k的数量和chunk_size控制一下总输入长度?楼主方便说说实际使用中的经验吗?
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-8-27 21:42 , Processed in 0.019994 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部