【AI大模型应用开发】【项目实战】8.物流行业信息咨询RAG系统
1. 背景介绍
2023年以来,随着ChatGPT的火爆,使得LLM成为研究和应用的热点,但是市面上大部分LLM都存在一 个共同的问题: 模型都是基于过去的经验数据进行训练完成,无法获取最新的知识,以及各企业私有的知识,因此很多企业为了处理私有的知识,主要借助一下两种手段来实现:
- 利用企业私有知识, 基于开源大模型进行微调
- 利用企业私有知识,基于大模型搭建RAG系统
本次项目以"某物流行业"为例,基于物流信息构建RAG系统,测试问答效果
注意:除物流场景外,使用者可以自由切换其他行业类型知识,实现本地知识库问答的效果
2. 项目基本原理
RAG原理

项目流程

项目原理
主要功能:
- 基于本地知识库的问答:系统可以根据用户的提问,在本地的知识库中进行搜索,并返回相关的答案
- 多模型支持: 项目支持使用不同的语言模型(Embeding模型:做检索, LLMs: 大模型出结果),可以根据需求选择合适的模型进行使用(进行微调)
- 离线私有化: 可以将该问答系统部署在本地环境中,确保数据的安全性和隐私性
3. 项目流程

4. 环境配置

5. 代码实现
代码目录如下:
物流信息.pdf信息如下:
5.1 本地知识库构建
目的: 将本地pdf文档信息进行抽取,然后进行分块,最后Embedding存储向量数据库中
代码local_db.py如下:
代码实现了一个基于 LangChain 框架的 RAG(检索增强生成)基础流程,主要功能是将本地的 PDF 文档解析、切分、向量化,并最终存入本地的 FAISS 向量数据库中
from langchain_community.document_loaders import PyMuPDFLoader # 用于加载和读取本地的 PDF 文件
from langchain.text_splitter import RecursiveCharacterTextSplitter # 用于将长文本按规则递归切分成较小的文本块(chunks)
from langchain_community.embeddings import OllamaEmbeddings # 用于调用本地 Ollama 服务中的嵌入模型,将文本转换为向量
from langchain_community.vectorstores import FAISS # Meta 开源的本地向量数据库,用于存储向量并进行相似度检索
def get_vector():
# 第一步:加载文档
loader = PyMuPDFLoader("物流信息.pdf")
# 将文本转成 Document 对象
# 调用 load() 方法将 PDF 内容转化为 LangChain 的 Document 对象列表(通常一页对应一个 Document)
data = loader.load()
print(f'data-->{data}')
print(f'len(data):{len(data)}')
# # 第二步:切分文本
# 初始化文本分割器 RecursiveCharacterTextSplitter,设置了两个关键参数:
# chunk_size=50:每个文本块的最大长度为 50 个字符。
# chunk_overlap=20:相邻文本块之间有 20 个字符的重叠,以保证上下文语义的连贯性
text_splitter = RecursiveCharacterTextSplitter(chunk_size=50, chunk_overlap=20)
# 切割加载的 document
# 调用 split_documents(data) 对加载的文档进行切分,并打印切分后的文本块数量和具体内容
split_docs = text_splitter.split_documents(data)
print("split_docs size:", len(split_docs))
print(split_docs)
#
# # 第三步:初始化 hugginFace 的 embeddings 对象
# 实例化 OllamaEmbeddings,指定使用 "mxbai-embed-large" 这个嵌入模型。该模型负责将文本转化为计算机可理解的高维向量表示
embeddings = OllamaEmbeddings(model="mxbai-embed-large")
#
#
# # 第四步:将 document通过embeddings对象计算得到向量信息并永久存入FAISS向量数据库,并构建 FAISS 向量索引,用于后续匹配查询
db = FAISS.from_documents(split_docs, embeddings)
db.save_local("./faiss/wuliu")
if __name__ == '__main__':
result = get_vector()
执行一下, 如果报错:
raise ImportError( ImportError: `PyMuPDF` package not found, please install it with `pip install pymupdf`则安装pip install pymupdf即可
如果报 没有发现 mxbai-embed-large模型错误, 则使用ollama安装即可: ollama pull mxbai-embed-large
显示结果如下:
data-->[Document(metadata={'source': '物流信息.pdf', 'file_path': '物流信息.pdf', 'page': 0, 'total_pages': 1, 'format': 'PDF 1.3', 'title': '物流信息', 'author': '', 'subject': '', 'keywords': '', 'creator': 'typora', 'producer': 'macOS 版本10.16(版号21E230) Quartz PDFContext', 'creationDate': "D:20240604062031Z00'00'", 'modDate': "D:20240604062031Z00'00'", 'trapped': ''}, page_content='物流公司:速达物流 公司总部:北京市 业务范围:国际快递、仓储管理\n货物追踪:\n货物编号:ABC123456\n发货⽇期:2023-01-15\n当前位置:上海分拨中⼼\n预计到达⽇期:2023-01-20\n运输⽅式:\n运输公司:快运通\n运输⽅式:陆运\n出发地:⼴州\n⽬的地:重庆\n预计运输时间:3天\n仓储信息:\n仓库名称:东⽅仓储中⼼\n仓库位置:深圳市\n存储货物类型:电⼦产品\n存储条件:常温仓储\n当前库存量:1000件\n')]
len(data):1
split_docs size: 6
[Document(metadata={'source': '物流信息.pdf', 'file_path': '物流信息.pdf', 'page': 0, 'total_pages': 1, 'format': 'PDF 1.3', 'title': '物流信息', 'author': '', 'subject': '', 'keywords': '', 'creator': 'typora', 'producer': 'macOS 版本10.16(版号21E230) Quartz PDFContext', 'creationDate': "D:20240604062031Z00'00'", 'modDate': "D:20240604062031Z00'00'", 'trapped': ''}, page_content='物流公司:速达物流 公司总部:北京市 业务范围:国际快递、仓储管理\n货物追踪:'), Document(metadata={'source': '物流信息.pdf', 'file_path': '物流信息.pdf', 'page': 0, 'total_pages': 1, 'format': 'PDF 1.3', 'title': '物流信息', 'author': '', 'subject': '', 'keywords': '', 'creator': 'typora', 'producer': 'macOS 版本10.16(版号21E230) Quartz PDFContext', 'creationDate': "D:20240604062031Z00'00'", 'modDate': "D:20240604062031Z00'00'", 'trapped': ''}, page_content='货物追踪:\n货物编号:ABC123456\n发货⽇期:2023-01-15\n当前位置:上海分拨中⼼'), Document(metadata={'source': '物流信息.pdf', 'file_path': '物流信息.pdf', 'page': 0, 'total_pages': 1, 'format': 'PDF 1.3', 'title': '物流信息', 'author': '', 'subject': '', 'keywords': '', 'creator': 'typora', 'producer': 'macOS 版本10.16(版号21E230) Quartz PDFContext', 'creationDate': "D:20240604062031Z00'00'", 'modDate': "D:20240604062031Z00'00'", 'trapped': ''}, page_content='当前位置:上海分拨中⼼\n预计到达⽇期:2023-01-20\n运输⽅式:\n运输公司:快运通'), Document(metadata={'source': '物流信息.pdf', 'file_path': '物流信息.pdf', 'page': 0, 'total_pages': 1, 'format': 'PDF 1.3', 'title': '物流信息', 'author': '', 'subject': '', 'keywords': '', 'creator': 'typora', 'producer': 'macOS 版本10.16(版号21E230) Quartz PDFContext', 'creationDate': "D:20240604062031Z00'00'", 'modDate': "D:20240604062031Z00'00'", 'trapped': ''}, page_content='运输⽅式:\n运输公司:快运通\n运输⽅式:陆运\n出发地:⼴州\n⽬的地:重庆\n预计运输时间:3天'), Document(metadata={'source': '物流信息.pdf', 'file_path': '物流信息.pdf', 'page': 0, 'total_pages': 1, 'format': 'PDF 1.3', 'title': '物流信息', 'author': '', 'subject': '', 'keywords': '', 'creator': 'typora', 'producer': 'macOS 版本10.16(版号21E230) Quartz PDFContext', 'creationDate': "D:20240604062031Z00'00'", 'modDate': "D:20240604062031Z00'00'", 'trapped': ''}, page_content='⽬的地:重庆\n预计运输时间:3天\n仓储信息:\n仓库名称:东⽅仓储中⼼\n仓库位置:深圳市'), Document(metadata={'source': '物流信息.pdf', 'file_path': '物流信息.pdf', 'page': 0, 'total_pages': 1, 'format': 'PDF 1.3', 'title': '物流信息', 'author': '', 'subject': '', 'keywords': '', 'creator': 'typora', 'producer': 'macOS 版本10.16(版号21E230) Quartz PDFContext', 'creationDate': "D:20240604062031Z00'00'", 'modDate': "D:20240604062031Z00'00'", 'trapped': ''}, page_content='仓库位置:深圳市\n存储货物类型:电⼦产品\n存储条件:常温仓储\n当前库存量:1000件')]
优化建议:
代码中设置的chunk_size=50相对较小,这可能会导致切分出的文本块丢失完整的上下文语义,进而影响后续检索的准确度。对于中文文档,通常建议将chunk_size设置在 500 到 800 之间,并将chunk_overlap设置在 50 到 100 之间,这样能在检索精度和上下文完整性之间取得更好的平衡
5.2 构建本地问答RAG系统
目的: 基于本地实现RAG系统的检索问答
具体local_qa. py代码:代码实现了一个基于 LangChain 框架的 RAG(检索增强生成)问答流程的后半部分:从本地加载向量库、检索相关文档、构建 Prompt 并调用大模型生成答案
# coding:utf-8
# 导入必备的工具包
import time
from local_db import * # 从自定义模块 local_db 导入了 FAISS
from langchain import PromptTemplate
from langchain_community.llms import Ollama
# 加载FAISS向量库
# 初始化了 OllamaEmbeddings 嵌入模型(注意:这里指定了 mxbai-embed-large作为嵌入模型,
# 通常嵌入模型应该是专门的 embedding 模型如 mxbai-embed-large
embeddings = OllamaEmbeddings(model="mxbai-embed-large", temperature=0)
# 使用 FAISS.load_local 加载了之前保存的向量库,
# 并开启了 allow_dangerous_deserialization=True(这是新版 LangChain 加载本地 FAISS 必须的参数
db = FAISS.load_local("faiss/wuliu", embeddings, allow_dangerous_deserialization=True)
# db = FAISS.load_local("faiss/wuliu", embeddings)
start_time = time.time()
def get_related_content(related_docs):
"""
文档检索与内容提取
:param related_docs: 接收检索到的文档列表 related_doc
:return:
"""
# print(f'related_docs--》{related_docs}')
related_content = []
for doc in related_docs: # 遍历文档,提取 page_content 并用 \n 替换掉 \n\n(为了压缩 Prompt 长度)
# print(f'doc.page_content--》{doc.page_content}')
related_content.append(doc.page_content.replace("\n\n", "\n"))
# print(f'related_content列表状态--》{related_content}')
# 将所有相关文档的内容拼接成一个完整的字符串返回
return "\n".join(related_content)
def define_prompt():
# 硬编码了一个测试问题:'我的快递出发地是哪?预计几天的时间到达?'
question = '我的快递出发地是哪?预计几天的时间到达?'
# 调用 db.similarity_search(question, k=2) 从向量库中检索出与问题最相似的 2 个文本块
docs = db.similarity_search(question, k=2)
#
related_content = get_related_content(docs)
# print('*' * 80)
# print(f'related_content字符串状态-->{related_content}')
# print('*'*80)
# 定义了 Prompt 模板,要求模型“基于已知信息回答,不允许编造”
PROMPT_TEMPLATE = """
基于以下已知信息,简洁和专业的来回答用户的问题。不允许在答案中添加编造成分。
已知内容:
{context}
问题:
{question}"""
prompt = PromptTemplate(
input_variables=["context", "question"],
template=PROMPT_TEMPLATE, )
my_pmt = prompt.format(context=related_content, question=question)
return my_pmt
def qa():
model = Ollama(model="qwen2.5:7b")
# print(f'model-->{model}')
my_pmt = define_prompt()
print(f'my_pmt--》{my_pmt}')
result = model.invoke(my_pmt)
return result
if __name__ == '__main__':
result = qa()
print(result)
end_time = time.time()
print(end_time - start_time)
优化建议:
- 取消注释并获取流式/完整响应: 将
qa()中的result = model.invoke(my_pmt)取消注释,并确保函数有return result。- Embedding 模型与 LLM 模型区分: 代码中
OllamaEmbeddings(model="qwen2.5:7b")可能是个笔误。qwen2.5:7b是对话模型,不是嵌入模型。建议改回你之前构建向量库时使用的mxbai-embed-large,否则加载向量库时可能会因为向量维度不匹配而报错。- 使用 LangChain 的 RAG Chain 简化代码: 手动拼接 Prompt 和检索文档比较繁琐,可以使用 LangChain 的
create_retrieval_chain自动完成“检索 -> 填充上下文 -> 生成答案”的全流程
5.3 构建web界面实现 RAG检索
代码实现了一个基于 Streamlit 和 LangChain 的交互式 RAG 问答界面,并使用了
ConversationalRetrievalChain来支持多轮对话(记忆历史)web_qa.py代码如下:
# 从自定义模块 local_qa 中导入所有必要的组件(如大模型实例、向量数据库 db 等)
from local_qa import *
# 从 LangChain 导入 ConversationalRetrievalChain,该链专门用于处理带有历史上下文的检索问答
# ConversationalRetrievalChain作用:自动保存历史对话信息
from langchain.chains import ConversationalRetrievalChain
# 导入 Streamlit 库,用于快速构建 Web 交互界面
import streamlit as st
# 设置标题
st.set_page_config(page_title="物流行业信息咨询系统")
st.title("物流行业信息咨询RAG系统")
# 初始化全局变量, 用于存储对话历史
# 注意:在 Streamlit 中,每次页面交互都会重新运行整个脚本,普通全局变量无法跨次交互保留状态
chat_history = []
# 定义检索链函数
def new_retrival():
"""
创建基于 ConversationalRetrievalChain 的问答链
该链会自动处理:检索相关文档 -> 将文档和历史对话作为上下文 -> 传给大模型生成答案
"""
chain = ConversationalRetrievalChain.from_llm(
llm=Ollama(model="qwen2.5:7b"), # 使用本地大模型
retriever=db.as_retriever() # 基于本地数据库的检索器
)
return chain
# 主逻辑
def main():
"""
Streamlit 主页面的交互逻辑, 负责界面渲染和状态管理
"""
# print(f'st.session_state-->{st.session_state}')
# 初始化会话状态
# 初始化 Streamlit 的会话状态(Session State)
# session_state 是跨页面刷新/交互保留数据的唯一正确方式
if "messages" not in st.session_state:
st.session_state.messages = [] # 用于保存聊天记录, 用于在页面上渲染聊天气泡
# print(f'st.session_state-->{st.session_state}')
# 展示历史聊天记录, 实现界面的消息回显
for message in st.session_state.messages:
# print(f'message["role"]-->{message["role"]}')
# 根据消息角色(user 或 assistant)创建对应的聊天气泡
with st.chat_message(message["role"]):
st.markdown(message["content"]) # 将消息内容以 Markdown 格式渲染显示
# 接受用户输入
# prompt = st.chat_input("请输入你的问题:")
if prompt := st.chat_input("请输入你的问题:"):
# 保存用户消息到会话状态
print(f'prompt--》{prompt}')
# 将用户的输入追加到会话状态的消息列表中
st.session_state.messages.append({"role": "user", "content": prompt})
# 显示用户输入: 立即在界面上显示用户的输入内容
with st.chat_message("user"):
st.markdown(prompt)
# 调用模型获取回答: 在界面上创建助手的回复气泡
with st.chat_message("assistant"):
# 创建一个空的占位符,通常用于后续实现流式输出(打字机效果)
message_placeholder = st.empty()
full_response = ""
# 调用检索链获取答案: 每次提问时重新创建检索链
chain = new_retrival()
# 调用检索链,传入当前问题和对话历史,获取包含答案的字典结果
result = chain.invoke({"question": prompt, "chat_history": chat_history})
print(f'result--->{result}')
# 将当前的问答对追加到全局的 chat_history 列表中,以维持多轮对话记忆
chat_history.append((prompt, result["answer"])) # 更新聊天历史
# 提取大模型生成的最终答案
assistant_response = result["answer"]
# 将答案渲染到占位符中
message_placeholder.markdown(assistant_response)
# 保存回答到会话状态
st.session_state.messages.append({"role": "assistant", "content": assistant_response})
# 运行主逻辑
if __name__ == "__main__":
main()
在终端运行 streamlit run .\web_qa.py 即可,如果报错:
streamlit : 无法将“streamlit”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
所在位置 行:1 字符: 1
+ streamlit run .\web_qa.py
+ ~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (streamlit:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
说明没有安装streamlit插件, 使用命令 pip install streamlit 安装即可
其他大模型使用:
from PyPDF2 import PdfReader # 读取pdf文件
from langchain.text_splitter import CharacterTextSplitter # 文本分割器
from langchain.vectorstores import FAISS # 向量库
from langchain.llms import QianfanLLMEndpoint # 千帆大模型平台库
import streamlit as st # 搭建web界面
from langchain.chains import ConversationalRetrievalChain # 对话检索链
from config import * # config文件配置API_key等参数
import os
from langchain.embeddings import QianfanEmbeddingsEndpoint
# 使用国外OPENAI的模型,需要导入API-KEY(需要科学上网)
# os.environ[ "OPENAI_API_KEY" ] = OPENAI_AK
# 使用国内百度千帆平台的模型,需要导入API-KEY和SERECT-KEY
os.environ['QIANFAN_AK'] = QIANFAN_AK
os.environ['QIANFAN_SK'] = QIANFAN_SK
llm = QianfanLLMEndpoint()
# 设置web页面:比如标题、描述功能
st.title("《农学院--物流行业信息咨询》")
st.write("请上传一个关于物流信息的pdf文档.")
# 设置上传pdf文件的功能
uploaded_file = st.file_uploader("选择一个pdf文档", type="pdf")
print('uploaded_file', uploaded_file)
if uploaded_file:
# 读取pdf文件
doc_reader = PdfReader(uploaded_file)
print(f'doc_reader--》{doc_reader}')
# 从pdf中提取文档
raw_text = ""
for i, page in enumerate(doc_reader.pages):
print(f'page-->{page}')
text = page.extract_text()
print(f'text-->{text}')
if text:
raw_text += text
#将文本切分成小的模块
print(raw_text)
print('*'*80)
text_splitter = CharacterTextSplitter(separator=' ', chunk_size=10, chunk_overlap=1)
texts = text_splitter.split_text(raw_text)
print(f'texts-->{texts}')
print(f'len(texts)-->{len(texts[0])}')
print('*'*80)
# embeddings模型
# EMBEDDING_MODEL = "/Users/ligang/PycharmProjects/llm/langchain_apply/Knowledge_QA/moka-ai/m3e-base"
# embeddings = HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL)
embeddings = QianfanEmbeddingsEndpoint()
# print(embeddings.embed_query("你好吗"))
# 创建文档搜索
docsearch = FAISS.from_texts(texts, embeddings)
# 创建对话链
qa = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=docsearch.as_retriever(),
return_source_documents=True, )
# 初始化聊天记录列表
chat_history = []
# 获取用户的查询
query = st.text_input("请给出你的问题")
#添加一个生成按钮
generate_button = st.button("生成答案")
if generate_button and query:
with st.spinner("答案生成中..."):
# 将问题以及历史对话记录传入对话链获得模型输出结果
result = qa({"question": query, "chat_history": chat_history})
answer = result["answer"]
source_documents = result['source_documents']
#将答案和source_documents合并为单个响应(输出)
response = { "answer": answer, "source_documents": source_documents
}
st.write("response:", response)
5.4 结果展示
终端运行 streamlit run .\web_qa.py:

5.5 整体代码如下
(1).local_db.py
from langchain_community.document_loaders import PyMuPDFLoader # 用于加载和读取本地的 PDF 文件
from langchain.text_splitter import RecursiveCharacterTextSplitter # 用于将长文本按规则递归切分成较小的文本块(chunks)
from langchain_community.embeddings import OllamaEmbeddings # 用于调用本地 Ollama 服务中的嵌入模型,将文本转换为向量
from langchain_community.vectorstores import FAISS # Meta 开源的本地向量数据库,用于存储向量并进行相似度检索
def get_vector():
# 第一步:加载文档
loader = PyMuPDFLoader("物流信息.pdf")
# 将文本转成 Document 对象
# 调用 load() 方法将 PDF 内容转化为 LangChain 的 Document 对象列表(通常一页对应一个 Document)
data = loader.load()
print(f'data-->{data}')
print(f'len(data):{len(data)}')
# # 第二步:切分文本
# 初始化文本分割器 RecursiveCharacterTextSplitter,设置了两个关键参数:
# chunk_size=50:每个文本块的最大长度为 50 个字符。
# chunk_overlap=20:相邻文本块之间有 20 个字符的重叠,以保证上下文语义的连贯性
text_splitter = RecursiveCharacterTextSplitter(chunk_size=50, chunk_overlap=20)
# 切割加载的 document
# 调用 split_documents(data) 对加载的文档进行切分,并打印切分后的文本块数量和具体内容
split_docs = text_splitter.split_documents(data)
print("split_docs size:", len(split_docs))
print(split_docs)
#
# # 第三步:初始化 hugginFace 的 embeddings 对象
# 实例化 OllamaEmbeddings,指定使用 "qwen2.5:7b" 这个嵌入模型。该模型负责将文本转化为计算机可理解的高维向量表示
embeddings = OllamaEmbeddings(model="mxbai-embed-large")
#
#
# # 第四步:将 document通过embeddings对象计算得到向量信息并永久存入FAISS向量数据库,并构建 FAISS 向量索引,用于后续匹配查询
db = FAISS.from_documents(split_docs, embeddings)
db.save_local("./faiss/wuliu")
if __name__ == '__main__':
result = get_vector()
(2).local_qa.py
# coding:utf-8
# 导入必备的工具包
import time
from local_db import * # 从自定义模块 local_db 导入了 FAISS
from langchain_core.prompts import PromptTemplate
from langchain_community.llms import Ollama
# 加载FAISS向量库
# 初始化了 OllamaEmbeddings 嵌入模型(注意:这里指定了 mxbai-embed-large 作为嵌入模型,
# 通常嵌入模型应该是专门的 embedding 模型如 mxbai-embed-large
embeddings = OllamaEmbeddings(model="mxbai-embed-large", temperature=0)
# 使用 FAISS.load_local 加载了之前保存的向量库,
# 并开启了 allow_dangerous_deserialization=True(这是新版 LangChain 加载本地 FAISS 必须的参数
db = FAISS.load_local("faiss/wuliu", embeddings, allow_dangerous_deserialization=True)
# db = FAISS.load_local("faiss/wuliu", embeddings)
start_time = time.time()
def get_related_content(related_docs):
"""
文档检索与内容提取
:param related_docs: 接收检索到的文档列表 related_doc
:return:
"""
# print(f'related_docs--》{related_docs}')
related_content = []
for doc in related_docs: # 遍历文档,提取 page_content 并用 \n 替换掉 \n\n(为了压缩 Prompt 长度)
# print(f'doc.page_content--》{doc.page_content}')
related_content.append(doc.page_content.replace("\n\n", "\n"))
# print(f'related_content列表状态--》{related_content}')
# 将所有相关文档的内容拼接成一个完整的字符串返回
return "\n".join(related_content)
def define_prompt():
# 硬编码了一个测试问题:'我的快递出发地是哪?预计几天的时间到达?'
question = '我的快递出发地是哪?预计几天的时间到达?'
# 调用 db.similarity_search(question, k=2) 从向量库中检索出与问题最相似的 2 个文本块
docs = db.similarity_search(question, k=2)
#
related_content = get_related_content(docs)
# print('*' * 80)
# print(f'related_content字符串状态-->{related_content}')
# print('*'*80)
# 定义了 Prompt 模板,要求模型“基于已知信息回答,不允许编造”
PROMPT_TEMPLATE = """
基于以下已知信息,简洁和专业的来回答用户的问题。不允许在答案中添加编造成分。
已知内容:
{context}
问题:
{question}"""
prompt = PromptTemplate(
input_variables=["context", "question"],
template=PROMPT_TEMPLATE, )
my_pmt = prompt.format(context=related_content, question=question)
return my_pmt
def qa():
model = Ollama(model="qwen2.5:7b")
# print(f'model-->{model}')
my_pmt = define_prompt()
print(f'my_pmt--》{my_pmt}')
result = model.invoke(my_pmt)
return result
if __name__ == '__main__':
result = qa()
print(result)
end_time = time.time()
print(end_time - start_time)
(3).web_qa.py
# 从自定义模块 local_qa 中导入所有必要的组件(如大模型实例、向量数据库 db 等)
from local_qa import *
# 从 LangChain 导入 ConversationalRetrievalChain,该链专门用于处理带有历史上下文的检索问答
# ConversationalRetrievalChain作用:自动保存历史对话信息
from langchain.chains import ConversationalRetrievalChain
# 导入 Streamlit 库,用于快速构建 Web 交互界面
import streamlit as st
# 设置标题
st.set_page_config(page_title="物流行业信息咨询系统")
st.title("物流行业信息咨询RAG系统")
# 初始化全局变量, 用于存储对话历史
# 注意:在 Streamlit 中,每次页面交互都会重新运行整个脚本,普通全局变量无法跨次交互保留状态
chat_history = []
# 定义检索链函数
def new_retrival():
"""
创建基于 ConversationalRetrievalChain 的问答链
该链会自动处理:检索相关文档 -> 将文档和历史对话作为上下文 -> 传给大模型生成答案
"""
chain = ConversationalRetrievalChain.from_llm(
llm=Ollama(model="qwen2.5:7b"), # 使用本地大模型
retriever=db.as_retriever() # 基于本地数据库的检索器
)
return chain
# 主逻辑
def main():
"""
Streamlit 主页面的交互逻辑, 负责界面渲染和状态管理
"""
# print(f'st.session_state-->{st.session_state}')
# 初始化会话状态
# 初始化 Streamlit 的会话状态(Session State)
# session_state 是跨页面刷新/交互保留数据的唯一正确方式
if "messages" not in st.session_state:
st.session_state.messages = [] # 用于保存聊天记录, 用于在页面上渲染聊天气泡
# print(f'st.session_state-->{st.session_state}')
# 展示历史聊天记录, 实现界面的消息回显
for message in st.session_state.messages:
# print(f'message["role"]-->{message["role"]}')
# 根据消息角色(user 或 assistant)创建对应的聊天气泡
with st.chat_message(message["role"]):
st.markdown(message["content"]) # 将消息内容以 Markdown 格式渲染显示
# 接受用户输入
# prompt = st.chat_input("请输入你的问题:")
if prompt := st.chat_input("请输入你的问题:"):
# 保存用户消息到会话状态
print(f'prompt--》{prompt}')
# 将用户的输入追加到会话状态的消息列表中
st.session_state.messages.append({"role": "user", "content": prompt})
# 显示用户输入: 立即在界面上显示用户的输入内容
with st.chat_message("user"):
st.markdown(prompt)
# 调用模型获取回答: 在界面上创建助手的回复气泡
with st.chat_message("assistant"):
# 创建一个空的占位符,通常用于后续实现流式输出(打字机效果)
message_placeholder = st.empty()
full_response = ""
# 调用检索链获取答案: 每次提问时重新创建检索链
chain = new_retrival()
# 调用检索链,传入当前问题和对话历史,获取包含答案的字典结果
result = chain.invoke({"question": prompt, "chat_history": chat_history})
print(f'result--->{result}')
# 将当前的问答对追加到全局的 chat_history 列表中,以维持多轮对话记忆
chat_history.append((prompt, result["answer"])) # 更新聊天历史
# 提取大模型生成的最终答案
assistant_response = result["answer"]
# 将答案渲染到占位符中
message_placeholder.markdown(assistant_response)
# 保存回答到会话状态
st.session_state.messages.append({"role": "assistant", "content": assistant_response})
# 运行主逻辑
if __name__ == "__main__":
main()
github地址: 物流行业RAG,制作不易,请Star
更多推荐






所有评论(0)