一、RAG问答系统总览
上篇完成了知识库构建(离线),下篇聚焦在线推理链路:
text
用户问题 → 向量检索(Top-K)→ 上下文注入Prompt → LLM生成 → 返回
-
核心问答逻辑(检索 + Prompt构建 + LLM调用)
-
基于Streamlit的Web交互界面
二、核心问答实现
2.1 加载向量库
python
db = FAISS.load_local("../faiss/wuliu", embeddings, allow_dangerous_deserialization=True)
注意 :allow_dangerous_deserialization=True 是LangChain的安全机制,因为pickle反序列化可能存在风险,在可信环境中可开启。
2.2 语义检索:similarity_search
python
docs = db.similarity_search(question, k=2)
-
将用户问题通过同一Embedding模型向量化
-
FAISS计算余弦相似度,返回Top-K最相关文档块
-
k=2控制召回数量:过少则上下文不足,过多则Prompt超长
2.3 上下文构建:get_related_content
设计细节:将检索到的多个文档块合并为一个字符串,作为LLM的上下文。换行符清理保证文本整洁,避免干扰模型理解。
python
def get_related_content(related_docs):
related_content = []
for doc in related_docs:
related_content.append(doc.page_content.replace("\n\n", "\n"))
return "\n".join(related_content)
设计细节:将检索到的多个文档块合并为一个字符串,作为LLM的上下文。换行符清理保证文本整洁,避免干扰模型理解。
2.4 Prompt工程:模板设计
模板设计要点:
python
PROMPT_TEMPLATE = """
基于以下已知信息,简洁和专业的来回答用户的问题。不允许在答案中添加编造成分。
已知内容:
{context}
问题:
{question}"""
模板设计要点:
-
角色约束:要求"简洁和专业",控制回答风格
-
知识边界:明确"不允许添加编造成分",限制幻觉
-
结构化 :
{context}和{question}占位符清晰分离
2.5 LLM调用:ChatOpenAI兼容接口
设计亮点:
python
llm = ChatOpenAI(
model="qwen3.7-max",
api_key=os.getenv("QWEN_API_KEY"),
base_url=os.getenv("QWEN_BASE_URL")
)
设计亮点:
-
使用
ChatOpenAI统一接口,兼容OpenAI、Qwen、DeepSeek等多种模型 -
通过
base_url灵活切换API端点 -
环境变量管理敏感信息(
dotenv加载)
三、WebUI实现
3.1 会话式检索链:ConversationalRetrievalChain
这个Chain做了什么?
python
from langchain_classic.chains import ConversationalRetrievalChain
chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=db.as_retriever()
)
这个Chain做了什么?
-
将用户问题 + 历史聊天记录合并成一个"新问题"
-
用新问题检索向量库
-
将检索结果 + 历史上下文一起送入LLM生成回答
相比基础RAG,它支持多轮对话,能在上下文基础上理解指代(如"那它什么时候到?"中的"它")。
3.2 Streamlit交互界面
Streamlit优势:
python
st.set_page_config(page_title="物流行业信息咨询系统", layout="wide")
st.title("物流行业信息咨询系统")
Streamlit优势:
-
纯Python构建WebUI,无需前端知识
-
热重载,修改代码即刷新
-
st.chat_message提供类ChatGPT的对话气泡样式
3.3 会话状态管理
python
if "messages" not in st.session_state:
st.session_state.messages = []
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
st.session_state 核心作用:
-
跨用户交互保持数据(类似内存中的全局状态)
-
存储
messages列表实现聊天历史持久化(单会话内)
3.4 流式输出模拟
python
full_response = ""
for chunk in assistant_response.split():
full_response += chunk + " "
message_placeholder.markdown(full_response + "▌")
message_placeholder.markdown(full_response)
实现思路:
-
按空格切分回答,逐个词追加显示
-
配合
▌光标闪烁效果,模拟流式生成体验 -
虽然不是真正的SSE流式,但用户体验显著提升
3.5 聊天历史维护
python
chat_history.append((prompt, result["answer"]))
ConversationalRetrievalChain 需要 chat_history 参数,格式为 List[Tuple[str, str]],分别存储用户问题和AI回答。这保证了多轮对话的上下文连贯性。
四、完整请求链路图
用户输入 "我的快递从哪发?"
│
▼
┌─────────────────────────┐
│ 1. st.chat_input 接收 │
└─────────┬───────────────┘
▼
┌─────────────────────────┐
│ 2. ConversationalRetrieval │
│ Chain 处理 │
│ - 合并历史上下文 │
│ - 检索FAISS向量库 │
│ - 构建最终Prompt │
└─────────┬───────────────┘
▼
┌─────────────────────────┐
│ 3. Qwen3.7-max 推理 │
└─────────┬───────────────┘
▼
┌─────────────────────────┐
│ 4. 逐词渲染 + 会话保存 │
└─────────────────────────┘
五、生产环境优化建议
| 优化方向 | 具体措施 |
|---|---|
| 检索质量 | 增加 k 值(如5),结合 MMR 算法提升多样性 |
| 响应速度 | FAISS索引升级为IVF系列(倒排索引),Embedding模型使用GPU加速 |
| 多轮对话 | 增加对话摘要节点,防止历史过长导致Prompt超限 |
| 并发处理 | Streamlit部署时使用 --server.enableXsrfProtection false,配合Nginx负载均衡 |
| 私有化部署 | 全部使用Ollama本地模型(qwen2.5:7b + bge-m3),实现完全离线 |
六、项目演示效果
基于Streamlit构建的Web界面,用户输入"我的快递出发地是哪?预计几天到达?",系统会:
-
从FAISS检索相关文档块
-
将检索结果注入Prompt
-
Qwen模型生成专业回答
-
界面逐词展示最终结果
总结(下篇 + 整体回顾)
下篇我们完成了:
-
向量检索与上下文构建的工程实现
-
Prompt模板设计思路
-
ConversationalRetrievalChain的多轮对话能力
-
Streamlit WebUI的全流程交互设计
整体而言,这个物流RAG系统是一个标准的"离线索引 + 在线检索生成"架构,核心亮点包括:
-
完全本地化部署(BGE-M3 + Qwen + FAISS)
-
多轮对话支持
-
开箱即用的WebUI
这套代码结构同样适用于金融、医疗、法律、教育等领域的私有知识问答场景,只需替换PDF文档和调整分块参数即可快速迁移。
两篇博客均提供了完整的技术剖析与工程实践参考,希望对你的RAG学习之旅有所帮助!