在日常工作与学习中,我们经常需要处理大量 PDF 文档 ------ 学术论文、行业报告、书籍资料、企业手册等。传统阅读方式效率低下,尤其是面对数百页的长文档时,快速定位关键信息、精准解答疑问变得十分困难。
随着大语言模型(LLM)与检索增强生成(RAG)技术的快速发展,智能 PDF 问答工具应运而生。它能让 AI "读懂" PDF 内容,支持自然语言提问并给出精准答案,大幅提升信息获取效率。本文将基于 Streamlit、LangChain 与阿里千问大模型,手把手带你从零搭建一款功能完整的 AI 智能 PDF 问答工具,全程实战,代码可直接复用。
一、技术选型:核心框架与工具解析
在动手开发前,先明确技术栈选型,确保工具适配性与开发效率。本项目核心技术组合为 Streamlit + LangChain + 阿里千问大模型 + FAISS 向量库,各组件作用如下:
1.1 Streamlit:快速搭建 Web 交互界面
Streamlit 是一款轻量级 Python Web 框架,专为数据科学与 AI 应用设计,无需前端基础,几行代码即可搭建美观的 Web 界面。相比 Flask、Django,它开发效率极高,支持实时交互、文件上传、会话状态管理等功能,完美适配本项目的 "文件上传 - 提问 - 回答" 交互流程。
1.2 LangChain:大模型应用开发核心框架
LangChain 是连接大语言模型与外部数据的核心框架,提供文档加载、文本分割、向量存储、检索问答、对话记忆等模块化组件,能快速构建 RAG 系统。它的核心价值是让大模型 "学会使用外部知识",避免回答幻觉,同时支持对话历史记忆,实现连续问答。
1.3 阿里千问大模型:高性能中文 LLM
本项目选用阿里千问(qwen3.5-plus)作为核心大模型,通过 DashScope(阿里灵积)API 调用。千问模型在中文理解、文本生成、逻辑推理方面表现优异,支持长文本上下文,且 API 调用稳定、性价比高,适合中文场景下的 PDF 问答需求。
1.4 FAISS:轻量级高效向量数据库
FAISS 是 Meta 开源的向量数据库,专为高维向量检索优化,支持快速存储与查询文本向量。本项目用它存储 PDF 文本块的向量数据,用户提问时,先检索与问题最相关的文本片段,再交给大模型生成答案,确保回答基于 PDF 原文,准确可靠。
二、项目整体架构:从 PDF 到答案的全流程
在编写代码前,先梳理项目整体架构,明确数据流转与核心模块分工,避免开发过程中逻辑混乱。本项目架构可分为5 大核心模块,流程清晰、层层递进:
- Web 交互层(Streamlit):负责用户交互,包括 API 密钥输入、PDF 文件上传、问题输入、答案展示、历史对话记录管理,是用户与系统的直接接口。
- 会话状态管理层:基于 Streamlit 的 session_state,存储对话记忆(ConversationBufferMemory)与聊天历史,确保多轮问答时上下文连贯,不会丢失对话信息。
- 文档处理模块(LangChain) :接收上传的 PDF 文件,完成加载→文本分割→向量生成→向量存储全流程,将非结构化 PDF 转化为可检索的向量数据。
- 检索问答模块(LangChain):用户提问后,先通过 FAISS 检索相关文本片段,再将 "问题 + 相关上下文 + 对话历史" 传入千问大模型,生成精准答案。
- 大模型调用层(DashScope):封装千问大模型与嵌入模型的 API 调用,负责与阿里灵积平台通信,处理请求与响应,确保模型调用稳定。
简单来说,整个流程就是:用户上传 PDF→系统解析并向量化→用户提问→系统检索相关内容→大模型生成答案→展示结果并记忆对话。下面进入实战开发环节,分步实现各模块功能。
三、环境准备与依赖安装
3.1 环境要求
- Python 3.9+(建议 3.10,兼容性最佳)
- 操作系统:Windows/macOS/Linux 均可
- 网络:可访问阿里灵积 DashScope API(需注册账号获取 API 密钥)
3.2 安装核心依赖包
创建项目文件夹,在终端执行以下命令,安装项目所需依赖:
bash
pip install streamlit langchain langchain-openai langchain-community langchain-text-splitters pypdf faiss-cpu python-dotenv
依赖包说明:
- streamlit:Web 界面框架
- langchain:核心 RAG 框架
- langchain-openai:适配 OpenAI 接口(千问兼容此接口)
- langchain-community:社区组件(PDF 加载、向量库等)
- langchain-text-splitters:文本分割工具
- pypdf:PDF 文件解析
- faiss-cpu:CPU 版 FAISS 向量库
- python-dotenv:环境变量管理
3.3 获取阿里灵积 API 密钥
- 访问阿里灵积控制台:https://bailian.console.aliyun.com/
- 注册 / 登录账号,进入 "API-KEY 管理"
- 创建 API 密钥,保存备用(后续代码需使用)
四、分步实现核心功能(代码实战)
项目共包含 2 个核心代码文件:main.py(Web 界面与交互逻辑)、scripts.py(PDF 处理与问答核心逻辑),以及 1 个测试用 PDF 文件(卢浮宫.pdf)。下面分步编写代码,详解关键逻辑。
4.1 编写 scripts.py:核心问答逻辑封装
scripts.py是项目的 "大脑",负责 PDF 加载、文本分割、向量生成、检索问答与大模型调用。代码如下:
python
import os
from langchain.chains import ConversationalRetrievalChain
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.embeddings import DashScopeEmbeddings
def qa_agent(openai_api_key, memory, uploaded_file, question):
# 1. 初始化千问大模型(兼容OpenAI接口)
model = ChatOpenAI(
model="qwen3.5-plus", # 千问3.5增强版
openai_api_key=os.getenv("DASHSCOPE_API_KEY"), # DashScope API密钥
openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1" # 千问API地址
)
# 2. 保存上传的PDF为临时文件
file_content = uploaded_file.read()
temp_file_path = "temp.pdf" # 临时PDF路径
with open(temp_file_path, "wb") as temp_file:
temp_file.write(file_content)
# 3. 加载PDF并分割文本
loader = PyPDFLoader(temp_file_path) # PDF加载器
docs = loader.load() # 加载PDF所有页面
# 文本分割器:按中文语义分割,块大小500,重叠50(保证上下文连贯)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n", "。", "!", "?", ",", "、", ""] # 中文分隔符
)
texts = text_splitter.split_documents(docs) # 分割后的文本块
# 4. 初始化嵌入模型(千问文本嵌入模型)
embeddings_model = DashScopeEmbeddings(
model="text-embedding-v2", # 文本嵌入模型
dashscope_api_key="替换为你的API密钥"
)
# 5. 生成向量并存储到FAISS
db = FAISS.from_documents(texts, embeddings_model) # 向量库
retriever = db.as_retriever() # 检索器
# 6. 创建检索问答链(支持对话记忆)
qa = ConversationalRetrievalChain.from_llm(
llm=model,
retriever=retriever,
memory=memory # 绑定对话记忆
)
# 7. 执行问答并返回结果
response = qa.invoke({"chat_history": memory, "question": question})
return response
关键逻辑详解:
- 大模型初始化 :千问模型兼容 OpenAI 接口,通过
openai_api_base指定千问 API 地址,无需额外适配,降低开发成本。 - PDF 临时存储:Streamlit 上传的文件是字节流,需保存为临时 PDF 文件,供 PyPDFLoader 加载。
- 中文文本分割 :使用
RecursiveCharacterTextSplitter,设置中文分隔符(句号、逗号等),确保分割后的文本块语义完整,避免断句混乱。 - 嵌入模型选择 :选用千问
text-embedding-v2嵌入模型,与大模型同源,中文向量匹配度更高,检索结果更精准。 - 对话记忆绑定 :通过
ConversationalRetrievalChain绑定memory,自动记录对话历史,支持多轮连续问答。
4.2 编写 main.py:Web 界面与交互逻辑
main.py是项目的 "脸面",基于 Streamlit 搭建 Web 界面,处理用户输入、文件上传、结果展示与历史对话管理。代码如下:
python
import streamlit as st
from langchain.memory import ConversationBufferMemory
from scripts import qa_agent
# 1. 初始化会话状态(对话记忆+聊天历史)
if "memory" not in st.session_state:
st.session_state["memory"] = ConversationBufferMemory(
return_messages=True,
memory_key="chat_history",
output_key="answer"
)
if "chat_history" not in st.session_state:
st.session_state["chat_history"] = []
# 2. 页面配置(标题、图标、布局)
st.set_page_config(
page_title="AI智能PDF问答工具",
page_icon="📑",
layout="centered"
)
# 3. 主标题
st.title("📑 AI智能PDF问答工具")
# 4. API密钥输入区域
st.subheader("🔑 API设置")
api_col1, api_col2 = st.columns([3, 1])
with api_col1:
openai_api_key = st.text_input(
"请输入OpenAI API密钥",
type="password",
placeholder="sk-..."
)
with api_col2:
st.markdown("[获取API密钥](阿里云百炼的api_key的网址)")
st.divider()
# 5. 文件上传区域
st.subheader("📁 文件上传")
uploaded_file = st.file_uploader(
"点击或拖拽PDF文件到此处",
type="pdf",
help="支持PDF格式文件"
)
# 6. 问题输入区域
st.subheader("❓ 提问")
question = st.text_input(
"请输入您的问题...",
disabled=not uploaded_file, # 未上传PDF时禁用输入
placeholder="例如:请总结PDF的主要内容..."
)
# 7. 核心处理逻辑(上传文件+输入问题后触发)
if uploaded_file and question:
if not openai_api_key:
st.warning("⚠️ 请先输入OpenAI API密钥")
else:
with st.spinner("🤖 AI正在思考中,请稍等..."):
try:
# 调用scripts.py中的问答函数
response = qa_agent(
openai_api_key,
st.session_state["memory"],
uploaded_file,
question
)
# 展示答案
st.subheader("📝 答案")
st.write(response["answer"])
# 更新会话历史
st.session_state["chat_history"] = response["chat_history"]
except Exception as e:
st.error(f"❌ 处理过程中发生错误: {str(e)}")
# 8. 历史对话记录展示
if st.session_state["chat_history"]:
st.divider()
with st.expander("🗣️ 历史对话记录", expanded=False):
st.write("---")
# 遍历对话历史(用户问题+AI答案成对展示)
for i in range(0, len(st.session_state["chat_history"]), 2):
if i + 1 < len(st.session_state["chat_history"]):
human_msg = st.session_state["chat_history"][i]
ai_msg = st.session_state["chat_history"][i + 1]
st.markdown(f"**👤 你**: {human_msg.content}")
st.markdown(f"**🤖 AI**: {ai_msg.content}")
if i < len(st.session_state["chat_history"]) - 2:
st.write("---")
# 清空对话历史按钮
if st.button("🔄 清空对话历史"):
st.session_state["memory"] = ConversationBufferMemory(
return_messages=True,
memory_key="chat_history",
output_key="answer"
)
st.session_state["chat_history"] = []
st.success("✅ 对话历史已清空")
st.experimental_rerun()
# 9. 使用说明
st.divider()
st.subheader("ℹ️ 使用说明")
st.write("1. 输入OpenAI API密钥")
st.write("2. 上传PDF文件")
st.write("3. 输入您的问题")
st.write("4. 查看AI的回答")
关键逻辑详解:
- 会话状态初始化 :Streamlit 的
session_state用于跨交互保存数据,初始化memory(对话记忆对象)与chat_history(对话历史列表),确保页面刷新后对话不丢失。 - 交互体验优化 :未上传 PDF 时禁用问题输入框,避免无效操作;添加加载动画(
st.spinner),提升等待体验;错误时展示具体异常信息,便于排查问题。 - 历史对话管理 :用
expander折叠历史对话,界面更简洁;支持一键清空对话历史,重置记忆对象,满足多文档问答需求。
4.3 测试用 PDF 文件:卢浮宫.pdf
为方便测试,准备一份简单的 PDF 文档(卢浮宫介绍),内容包含卢浮宫的历史、位置、开放时间等信息。上传后可提问:"卢浮宫的开放时间是什么?""卢浮宫的建筑始建于哪一年?" 等,验证问答准确性。
五、项目运行与功能测试
5.1 启动项目
在终端进入项目文件夹,执行以下命令启动 Streamlit 应用:
bash
streamlit run main.py
启动成功后,浏览器会自动打开 Web 界面(默认地址:http://localhost:8501)。
5.2 功能测试步骤
- 输入 API 密钥 :在 "API 设置" 区域输入你的 DashScope API 密钥(注意:密钥以
sk-开头)。 - 上传 PDF 文件:点击 "文件上传" 区域,选择 "卢浮宫.pdf" 上传,上传成功后可看到文件名。
- 输入问题并提问 :在 "提问" 区域输入问题,例如:
- 问题 1:卢浮宫的建筑始建于哪一年?
- 问题 2:卢浮宫现在的开放时间是怎样的?
- 问题 3:请总结卢浮宫的主要信息。
- 查看答案:等待几秒后,AI 会基于 PDF 内容生成精准答案,展示在 "答案" 区域。
- 多轮对话测试:继续输入关联问题,例如:"卢浮宫的参观费用是什么时候开始收取的?",验证对话记忆是否生效,上下文是否连贯。
- 历史对话查看与清空:展开 "历史对话记录",可查看所有问答记录;点击 "清空对话历史",可重置对话,重新开始。
5.3 测试结果示例
以 "卢浮宫的开放时间是什么?" 为例,AI 生成的答案如下:
卢浮宫除周二公休和特殊假日外,通常向游客全面开放参观。在 1855 年至 1922 年期间,除周一外全天向公众免费开放;1922 年开始收费。1824 年起,公众可在星期日和节假日参观,其他日子仅对艺术家和外国游客开放。
答案完全基于 PDF 原文,准确无误,无幻觉信息,验证了系统的可靠性。
六、核心技术深度解析:RAG 如何让 AI 读懂 PDF?
本项目的核心是检索增强生成(RAG)技术,它解决了大模型 "知识过时""幻觉回答""无法处理长文档" 三大痛点。下面深度解析 RAG 的核心原理,帮助你理解系统背后的逻辑。
6.1 RAG 的核心流程:分 "离线处理" 与 "在线问答" 两阶段
阶段 1:离线文档处理(上传 PDF 时执行)
- 文档加载:用 PyPDFLoader 读取 PDF 内容,提取所有文本。
- 文本分割:将长文本分割为 500 字左右的短文本块(Chunk),避免超出大模型上下文限制,同时保证语义完整。
- 向量生成 :用嵌入模型(text-embedding-v2)将每个文本块转化为高维向量(1024 维),向量能精准表征文本语义,语义相似的文本向量距离更近。
- 向量存储:将所有文本块的向量与原文存储到 FAISS 向量库,建立索引,便于快速检索。
阶段 2:在线问答(用户提问时执行)
- 问题向量化:用户提问后,用同样的嵌入模型将问题转化为向量。
- 相似向量检索 :在 FAISS 向量库中,检索与问题向量距离最近的 Top-k 文本块(默认 k=3),这些文本块就是与问题最相关的上下文。
- Prompt 构建:将 "用户问题 + 检索到的相关上下文 + 对话历史" 拼接成 Prompt,传入大模型。
- 答案生成:大模型基于 Prompt 中的上下文,生成精准、可靠的答案,确保答案完全基于 PDF 原文,避免幻觉。
6.2 对话记忆原理:如何实现多轮连贯问答?
本项目使用 LangChain 的ConversationBufferMemory实现对话记忆,其核心原理是:自动记录每一轮的 "用户问题 + AI 答案",并在后续问答时,将历史对话自动拼接到 Prompt 中,让大模型能理解上下文,实现连贯问答。
例如:
- 第一轮问题:卢浮宫始建于哪一年?
- 第一轮答案:1190 年左右。
- 第二轮问题:现在的开放时间呢?
- Prompt 会自动拼接:"历史对话:用户:卢浮宫始建于哪一年?AI:1190 年左右。用户:现在的开放时间呢?",大模型据此生成答案。
七、总结与感悟
本文基于 Streamlit、LangChain 与阿里千问大模型,从零搭建了一款功能完整的 AI 智能 PDF 问答工具,实现了 PDF 上传、文本解析、向量检索、智能问答、对话记忆等核心功能。整个开发过程无需复杂的前端与算法基础,基于模块化框架快速落地,充分体现了大模型时代 "低代码开发 AI 应用" 的优势。
通过本项目,我们不仅掌握了 Streamlit Web 开发、LangChain RAG 系统搭建、大模型 API 调用等核心技能,更深入理解了检索增强生成(RAG)技术的核心原理 ------让 AI 学会使用外部知识,是解决大模型痛点的关键。
在实际应用中,这款工具可广泛用于学术研究(论文精读)、职场办公(报告分析)、学习备考(资料梳理)等场景,大幅提升信息获取效率。同时,项目的优化与扩展方向也为后续迭代提供了清晰思路,可根据实际需求持续升级,打造更强大、更易用的智能文档问答工具。
最后,随着大模型技术的不断发展,AI 应用的开发门槛会持续降低,未来会有更多基于 RAG 技术的智能工具落地,赋能各行各业,让信息获取更高效、更智能。