《Python + Streamlit + DeepSeek API 实现一个本地文档问答助手》
Python Streamlit DeepSeek API 实现一个本地文档问答助手本文会从 0 到 1 实现一个可以运行的大模型文档问答小项目上传 PDF 或 TXT 文档输入问题后程序会先从文档中检索相关片段再调用大模型生成回答。摘要很多大模型应用并不是从零训练模型而是把已有模型接入到具体业务流程中。本文以“本地文档问答助手”为例使用 Python、Streamlit、DeepSeek API、pypdf 和 scikit-learn 实现一个入门版 RAG 应用。项目完成后可以实现上传 PDF / TXT 文档自动读取文档内容将长文本切分成多个片段根据用户问题检索相关内容调用 DeepSeek 大模型生成回答展示答案和参考片段本文尽量不依赖复杂框架先把完整流程跑通适合用来理解大模型应用开发中的 RAG 基本思路。目录一、项目效果二、技术选型三、项目原理四、环境准备五、项目目录六、完整代码七、运行项目八、核心代码解析九、常见问题十、后续优化方向十一、总结一、项目效果运行后会得到一个本地 Web 页面页面中包含两个主要输入区域文档上传区域支持上传 PDF 或 TXT问题输入区域输入想从文档中查询的问题使用流程如下上传文档 ↓ 输入问题 ↓ 点击“生成回答” ↓ 系统检索文档片段 ↓ 大模型基于检索内容生成回答 ↓ 页面展示回答和参考片段例如上传一份技术文档后可以提问这份文档主要讲了什么也可以提问文档中提到了哪些关键步骤相比普通聊天机器人这个项目的重点在于模型回答时会参考用户上传的文档内容而不是完全依赖模型自身知识。二、技术选型本项目使用的技术如下技术作用Python核心开发语言Streamlit快速搭建 Web 页面DeepSeek API调用大模型生成回答OpenAI SDK使用兼容 OpenAI 格式的接口调用 DeepSeekpypdf读取 PDF 文本scikit-learn使用 TF-IDF 和余弦相似度做文本检索这里没有直接使用 LangChain、LlamaIndex 或向量数据库主要是为了先用较少代码理解 RAG 的核心流程。后续可以在这个版本基础上继续升级。三、项目原理这个项目可以看作一个简化版 RAG也就是检索增强生成。普通大模型问答流程是用户问题 → 大模型 → 回答本文实现的流程是用户问题 → 检索文档相关片段 → 大模型基于片段回答 → 展示答案完整流程可以拆成 5 步1. 读取上传文档 2. 将文档切分成多个文本片段 3. 计算用户问题和文本片段的相似度 4. 取出最相关的几个片段 5. 将片段和问题一起交给大模型生成回答这里的“检索”使用 TF-IDF cosine similarity 实现。它不是最强的语义检索方案但非常适合入门因为代码简单、依赖少、方便理解。四、环境准备建议使用 Python 3.10 或以上版本。1. 创建项目目录mkdirdocument_qa_democddocument_qa_demo2. 创建虚拟环境python-mvenv .venvWindows PowerShell 激活虚拟环境.venv\Scripts\Activate.ps1macOS / Linux 激活虚拟环境source.venv/bin/activate3. 安装依赖pipinstallstreamlit openai scikit-learn pypdf也可以新建requirements.txtstreamlit openai scikit-learn pypdf然后执行pipinstall-rrequirements.txt4. 配置 DeepSeek API KeyDeepSeek API 兼容 OpenAI SDK调用时需要配置base_url和 API Key。Windows PowerShell 临时设置$env:DEEPSEEK_API_KEY你的 API KeymacOS / Linux 临时设置exportDEEPSEEK_API_KEY你的 API Key如果使用 Streamlit 的 secrets也可以创建文件.streamlit/secrets.toml写入DEEPSEEK_API_KEY 你的 API Key注意不要把自己的 API Key 上传到 GitHub也不要直接写进公开文章的代码里。五、项目目录最终目录结构如下document_qa_demo ├── app.py ├── requirements.txt └── .streamlit └── secrets.toml其中app.py项目主程序requirements.txt依赖列表.streamlit/secrets.toml本地密钥配置可选六、完整代码新建app.py写入下面代码importosfromioimportBytesIOimportstreamlitasstfromopenaiimportOpenAIfrompypdfimportPdfReaderfromsklearn.feature_extraction.textimportTfidfVectorizerfromsklearn.metrics.pairwiseimportcosine_similarity MODEL_NAMEdeepseek-v4-flashdefget_api_key():ifDEEPSEEK_API_KEYinst.secrets:returnst.secrets[DEEPSEEK_API_KEY]returnos.getenv(DEEPSEEK_API_KEY)defread_pdf(uploaded_file):readerPdfReader(BytesIO(uploaded_file.getvalue()))text_list[]forpageinreader.pages:page_textpage.extract_text()ifpage_text:text_list.append(page_text)return\n.join(text_list)defread_txt(uploaded_file):returnuploaded_file.getvalue().decode(utf-8,errorsignore)defsplit_text(text,chunk_size700,overlap120):chunks[]start0whilestartlen(text):endstartchunk_size chunktext[start:end].strip()iflen(chunk)80:chunks.append(chunk)startend-overlapreturnchunksdefretrieve_chunks(question,chunks,top_k4):ifnotchunks:return[]vectorizerTfidfVectorizer(analyzerchar,ngram_range(2,4))doc_vectorsvectorizer.fit_transform(chunks)question_vectorvectorizer.transform([question])scorescosine_similarity(question_vector,doc_vectors)[0]ranked_indexesscores.argsort()[::-1][:top_k]results[]forindexinranked_indexes:results.append({content:chunks[index],score:float(scores[index])})returnresultsdefask_llm(api_key,question,retrieved_chunks):context\n\n.join([f资料片段{index1}\n{item[content]}forindex,iteminenumerate(retrieved_chunks)])clientOpenAI(api_keyapi_key,base_urlhttps://api.deepseek.com)responseclient.chat.completions.create(modelMODEL_NAME,messages[{role:system,content:(你是一个严谨的文档问答助手。请只根据用户提供的资料回答问题。如果资料中没有相关信息请明确说明无法从当前资料中确定。)},{role:user,content:f 请根据下面的资料回答用户问题。 【资料】{context}【用户问题】{question}【回答要求】 1. 先直接回答问题 2. 不要编造资料中没有的信息 3. 如果资料不足请明确说明 4. 最后简单说明依据来自哪些资料片段 }],streamFalse)returnresponse.choices[0].message.content st.set_page_config(page_title本地文档问答助手,layoutwide)st.title(本地文档问答助手)st.caption(上传 PDF 或 TXT 文档输入问题后系统会检索相关片段并调用大模型生成回答。)api_keyget_api_key()ifnotapi_key:st.warning(请先设置 DEEPSEEK_API_KEY。可以使用环境变量也可以使用 .streamlit/secrets.toml。)st.stop()withst.sidebar:st.header(参数设置)chunk_sizest.slider(文本片段长度,min_value300,max_value1500,value700,step100)overlapst.slider(片段重叠长度,min_value0,max_value300,value120,step20)top_kst.slider(检索片段数量,min_value1,max_value8,value4,step1)uploaded_filest.file_uploader(上传文档,type[pdf,txt])questionst.text_input(请输入你的问题,placeholder例如这份文档的核心内容是什么)ifuploaded_file:st.info(f当前文件{uploaded_file.name})ifuploaded_fileandquestion:ifst.button(生成回答,typeprimary):withst.spinner(正在读取文档...):ifuploaded_file.name.lower().endswith(.pdf):textread_pdf(uploaded_file)else:textread_txt(uploaded_file)ifnottext.strip():st.error(没有读取到有效文本。可能是扫描版 PDF或者文档内容为空。)st.stop()withst.spinner(正在切分文本并检索相关内容...):chunkssplit_text(text,chunk_sizechunk_size,overlapoverlap)retrieved_chunksretrieve_chunks(question,chunks,top_ktop_k)ifnotretrieved_chunks:st.error(没有检索到可用文本片段。)st.stop()withst.spinner(正在调用大模型生成回答...):answerask_llm(api_key,question,retrieved_chunks)st.subheader(回答)st.write(answer)st.subheader(参考片段)forindex,iteminenumerate(retrieved_chunks,start1):withst.expander(f参考片段{index}相似度{item[score]:.4f}):st.write(item[content])else:st.write(请先上传文档并输入问题。)七、运行项目在项目目录下执行streamlit run app.py如果命令不可用可以使用python-mstreamlit run app.py正常情况下浏览器会自动打开本地页面地址通常是http://localhost:8501如果页面没有自动打开也可以手动复制终端里的地址到浏览器访问。八、核心代码解析1. 使用 Streamlit 上传文件uploaded_filest.file_uploader(上传文档,type[pdf,txt])这里限制上传类型为 PDF 和 TXT。Streamlit 会把上传的文件包装成一个类似文件对象的UploadedFile后续可以直接读取内容。2. 读取 PDF 文本readerPdfReader(BytesIO(uploaded_file.getvalue()))pypdf可以读取普通 PDF 中的文本。如果 PDF 是扫描图片可能提取不到文字这种情况需要额外接入 OCR。3. 文本切分chunkssplit_text(text,chunk_sizechunk_size,overlapoverlap)长文档不能直接全部塞给大模型所以需要切成多个片段。这里设置了两个参数chunk_size每个片段的大致长度overlap相邻片段之间的重叠长度保留重叠的原因是避免一句话或一个段落被切断后丢失上下文。4. 检索相关片段scorescosine_similarity(question_vector,doc_vectors)[0]这里使用 TF-IDF 将文本转换成特征向量再用余弦相似度计算问题和文档片段的相关程度。相似度越高说明该片段越可能和问题相关。本文为了适配中文使用了字符级 n-gramanalyzerchar,ngram_range(2,4)这样即使没有分词工具也能完成一个基础检索效果。5. 调用 DeepSeek APIclientOpenAI(api_keyapi_key,base_urlhttps://api.deepseek.com)DeepSeek API 兼容 OpenAI SDK所以可以通过OpenAI客户端调用。本文使用的模型是MODEL_NAMEdeepseek-v4-flash生成回答时将检索到的资料片段和用户问题一起发送给模型responseclient.chat.completions.create(modelMODEL_NAME,messages[...],streamFalse)这样模型就会优先根据上传文档中的内容进行回答。九、常见问题1. 为什么上传 PDF 后没有内容可能原因是 PDF 是扫描版也就是每一页本质上是图片而不是可复制的文字。pypdf只能提取文本型 PDF。扫描版 PDF 需要使用 OCR 工具识别文字。2. 为什么回答看起来不够准确可能有几个原因文档切分太短导致上下文不完整文档切分太长导致检索不精确TF-IDF 更偏关键词匹配不是真正的语义向量检索问题表述和文档内容差异较大可以尝试调整侧边栏中的文本片段长度、片段重叠长度和检索片段数量。3. TF-IDF 和真正的向量检索有什么区别TF-IDF 更像关键词检索适合入门和小规模 Demo。真正的 RAG 项目通常会使用 Embedding 模型把文本转换成语义向量然后存入 FAISS、Chroma、Milvus 或 pgvector 等向量数据库中。简单理解TF-IDF更关注字词是否相似 Embedding更关注语义是否相似例如“如何申请报销”和“费用报销流程是什么”字面上不完全一样但语义接近。Embedding 检索通常更容易识别这种相似关系。4. API Key 应该怎么保存不要直接写在代码里建议使用环境变量.streamlit/secrets.toml部署平台提供的密钥管理功能如果代码要上传 GitHub记得把.streamlit/secrets.toml加入.gitignore。十、后续优化方向当前项目是入门版本可以继续从以下方向优化使用 Embedding 模型替代 TF-IDF提高语义检索效果。使用 FAISS 或 Chroma 存储向量支持更大的文档库。支持多文件上传实现个人知识库。记录历史对话让用户可以连续追问。增加页码引用让答案能追溯到 PDF 的具体页面。增加 FastAPI 后端将前端和后端分离。增加 Dockerfile方便部署和演示。接入 OCR支持扫描版 PDF。如果继续升级可以把项目路线设计成版本 1TF-IDF Streamlit 单文件 Demo 版本 2Embedding FAISS 语义检索 版本 3多文档知识库 历史对话 版本 4FastAPI 后端 前端页面 版本 5Docker 部署 项目上线这样既能逐步理解技术原理也能把项目迭代过程记录下来。十一、总结本文实现了一个可以本地运行的大模型文档问答助手核心流程包括文档上传 → 文本读取 → 文本切分 → 相关片段检索 → 大模型生成回答 → 展示参考片段这个项目虽然不复杂但已经覆盖了大模型应用开发中的几个关键点Prompt 设计API 调用文档处理文本检索RAG 基本流程Web 页面展示对于入门大模型应用开发来说先完成这样一个能运行、能演示、能继续扩展的小项目比一开始直接堆复杂框架更容易理解核心逻辑。参考资料DeepSeek API 文档https://api-docs.deepseek.com/Streamlit 运行应用文档https://docs.streamlit.io/develop/concepts/architecture/run-your-appStreamlit 文件上传组件文档https://docs.streamlit.io/develop/api-reference/widgets/st.file_uploaderscikit-learn TfidfVectorizer 文档https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.htmlscikit-learn cosine_similarity 文档https://scikit-learn.org/stable/modules/generated/sklearn.metrics.pairwise.cosine_similarity.htmlpypdf 文档https://pypdf.readthedocs.io/

相关新闻

破局数据孤岛:制造业基于 Trino 与统一元数据构建灵活数据资产底座

破局数据孤岛:制造业基于 Trino 与统一元数据构建灵活数据资产底座

在智能制造转型的深水区,制造企业正面临严峻的“数据烟囱”困境。ERP中的订单数据、MES里的生产工序、IoT平台的海量传感器时序数据以及质检系统记录,往往分散在数十种异构系统中。传统ETL模式不仅耗时费力,还极易产生数据副本与一致性风险。…

2026/7/6 2:39:14阅读更多 →
临界分词的存在性与最优性:从统计临界态到神经语言模型的双语实证检验

临界分词的存在性与最优性:从统计临界态到神经语言模型的双语实证检验

一项关于"自然语言分词是否存在内禀临界点,以及该点是否最优"的可证伪研究。 含 n-gram 统计分析(中/英)与线性 SSM 语言模型(FRSMASH v3.6,~8M 参数)双语验证。摘要 本文把"临界分词"…

2026/7/6 2:39:14阅读更多 →
硬件数据流的真实路径

硬件数据流的真实路径

SpaceMouse 数据不经过 Actor, 也不经过 FrankaEnv。它在 Wrapper 层被消费。实际数据流(每一步)如下:数据交互逻辑如下:数据路径说明SpaceMouse → 机器人SpaceMouse → 共享内存 → Wrapper → FrankaEnv → HTTP → 机器人不经…

2026/7/6 2:39:14阅读更多 →
OPENSSL生成非对称加密公私钥

OPENSSL生成非对称加密公私钥

本文内生成的文件均为密钥,不涉及证书的内容,密钥与证书的关系,以及各位客官所需要的究竟是密钥还是证书请自行查阅不同格式的密钥,使用时也会有所不同,因此需要明确自己要用的是什么编码和格式的密钥生成RSA公私钥生成…

2026/7/6 3:44:20阅读更多 →
风控模型岗,怎么靠副业日入过千?

风控模型岗,怎么靠副业日入过千?

做风控模型的,白天调参数、挖特征、找需求用户做营销、防逾期,晚上还要盯着线上指标。工资涨得慢,KPI 压得紧,很多人都在想: 能不能靠自己的技术,搞点外水? 今天这篇文章,不吹牛,不讲玄学,只说真实可落地的路子。 文章目录 一、为什么风控模型岗适合搞副业? 二、日入…

2026/7/6 3:44:20阅读更多 →
NHibernate实例分享:Northwind Mapping

NHibernate实例分享:Northwind Mapping

本节内容 说明代码参考资料 说明 NHibernate的强大之处在于其映射,我们在设计Domain中,经常为各种各样的映射感到头痛,为此我把微软SQL Server2000自带的Northwind数据库使用NHibernate映射了一下,需要的请下载其代码&#xff…

2026/7/6 3:44:20阅读更多 →
程序员的光荣与梦想——论侠客梦的延续与幻灭

程序员的光荣与梦想——论侠客梦的延续与幻灭

这不是很奇怪么?所有行业的初学者都可以被称作“菜鸟”,但是只有电脑高手(特别是程序牛人)被称作“大侠”。这到底是巧合呢,还是另有原因?今天1-2-3吃饱了撑着没事干,跟大家一起YY下这个问题。 …

2026/7/6 3:44:20阅读更多 →
真实项目中的四重奏式特征筛选:数据质量、统计相关、多变量稳定与业务终审

真实项目中的四重奏式特征筛选:数据质量、统计相关、多变量稳定与业务终审

1. 这不是又一篇“调个sklearn就完事”的 Feature Selection 教程你点开这篇,大概率刚学完 Pandas 和 Scikit-learn 的基础 API,正对着一个真实数据集发愁:列有 47 个,其中 3 个是 ID 字段、5 个是时间戳拆出来的冗余特征、2 个明…

2026/7/6 3:44:20阅读更多 →
2026年7月药房集采助行器5大排行榜

2026年7月药房集采助行器5大排行榜

进入2026年下半年,药房集采渠道的助行器市场竞争愈发激烈。随着人口老龄化加速,以及社区康复需求的持续释放,越来越多的药房、社区门诊和养老机构将助行器纳入常规采购清单。然而,面对市场上从几十元到上千元不等的产品&#xff0…

2026/7/6 3:39:19阅读更多 →
从GitHub安全案例解析常见漏洞与防护实践

从GitHub安全案例解析常见漏洞与防护实践

1. 项目概述:从GitHub Trending看安全实战 最近在GitHub Trending上看到一个项目,叫 skills4/skills ,它因为一些安全漏洞案例被大家讨论。这其实是一个挺典型的场景:一个旨在展示或教授某种技能的仓库,本身却成了安…

2026/7/5 0:01:08阅读更多 →
MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

# MLT 2026启示:因果推理与概率建模驱动下一代LLM应用## 一、背景与挑战:从“黑箱预测”到“可信推理”2026年6月,第7届机器学习与趋势国际会议(MLT 2026)将在悉尼召开。会议议程中,“因果与可解释机器学习…

2026/7/6 2:48:33阅读更多 →
通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

1. 项目概述与漏洞背景最近在梳理一些历史OA系统的安全风险时,通达OA v11.6版本中的一个老漏洞又进入了我的视线。这个漏洞位于/general/bi_design/appcenter/report_bi.func.php文件中,是一个典型的SQL注入点。虽然这个漏洞的利用方式看起来并不复杂&am…

2026/7/6 0:10:35阅读更多 →
Seraphine:基于LCU API的英雄联盟智能游戏助手技术解析与应用指南

Seraphine:基于LCU API的英雄联盟智能游戏助手技术解析与应用指南

Seraphine:基于LCU API的英雄联盟智能游戏助手技术解析与应用指南 【免费下载链接】Seraphine 英雄联盟战绩查询工具 项目地址: https://gitcode.com/gh_mirrors/se/Seraphine 技术架构先行:官方接口的合规应用 你是否曾在BP阶段手忙脚乱&#x…

2026/7/6 0:03:39阅读更多 →
多协议远程连接管理工具mRemoteNG:告别混乱,统一你的远程桌面管理

多协议远程连接管理工具mRemoteNG:告别混乱,统一你的远程桌面管理

多协议远程连接管理工具mRemoteNG:告别混乱,统一你的远程桌面管理 【免费下载链接】mRemoteNG mRemoteNG is the next generation of mRemote, open source, tabbed, multi-protocol, remote connections manager. 项目地址: https://gitcode.com/gh_m…

2026/7/6 0:03:39阅读更多 →
COUNT(DISTINCT) 与 GROUP BY 去重统计:5 亿数据量下的性能实测与选型指南

COUNT(DISTINCT) 与 GROUP BY 去重统计:5 亿数据量下的性能实测与选型指南

COUNT(DISTINCT) 与 GROUP BY 去重统计:5 亿数据量下的性能实测与选型指南在数据分析和处理领域,去重统计是最基础也是最频繁使用的操作之一。当数据量达到亿级规模时,不同的去重统计方法在性能上可能产生天壤之别。本文将基于 5 亿行数据的实…

2026/7/6 0:03:39阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/5 1:30:27阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/5 3:48:10阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/5 3:48:09阅读更多 →