1.完整代码

由于使用的是通义,所以代码改造了下,因为openAI需要钱

import streamlit as st
import tempfile
import os

from langchain_classic.memory import ConversationBufferMemory
from langchain_community.chat_message_histories import StreamlitChatMessageHistory
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_core.prompts import PromptTemplate
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_classic.agents import AgentExecutor, create_tool_calling_agent
from langchain_community.callbacks.streamlit import StreamlitCallbackHandler
from langchain_openai import ChatOpenAI
from langchain_community.chat_models import ChatTongyi
from langchain_core.tools import tool, create_retriever_tool
from dotenv import load_dotenv
load_dotenv()

# 设置Streamlit应用的页面标题和布局
st.set_page_config(page_title="文档问答", layout="wide")
# 设置应用的标题
st.title("📚 文档问答系统")

# 上传txt文件,允许上传多个文件
uploaded_files = st.sidebar.file_uploader(
    label="上传txt文件", type=["txt"], accept_multiple_files=True
)

# 如果没有上传文件,提示用户上传文件并停止运行
if not uploaded_files:
    st.info("请先上传按TXT文档。")
    st.stop()

@st.cache_resource(ttl="1h")
def configure_retriever(uploaded_files):
    # 读取上传的文档,并写入一个临时目录
    docs = []
    temp_dir = tempfile.TemporaryDirectory(dir=r"D:\蒋辉\AI大模型")
    for file in uploaded_files:
        temp_filepath = os.path.join(temp_dir.name, file.name)
        with open(temp_filepath, "wb") as f:
            f.write(file.getvalue())
        # 使用TextLoader加载文本文件
        loader = TextLoader(temp_filepath, encoding="utf-8")
        docs.extend(loader.load())
    # 进行文档分割
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
    splits = text_splitter.split_documents(docs)
    # 使用向量模型生成文档的向量表示
    embeddings = DashScopeEmbeddings(
        model="text-embedding-v3",
        dashscope_api_key="sk-秘钥"
    )
    vectordb = Chroma.from_documents(splits, embeddings)

    # 创建文档检索器
    retriever = vectordb.as_retriever()
    return retriever

# 配置检索器
retriever = configure_retriever(uploaded_files)

# 如果session_state中没有消息记录或用户点击了清空聊天记录按钮,则初始化消息记录
if "messages" not in st.session_state or st.sidebar.button("清空聊天记录"):
    st.session_state["messages"] = [{"role": "assistant", "content": "您好,我是文档问答助手"}]

# 加载历史聊天记录
for msg in st.session_state.messages:
    st.chat_message(msg["role"]).write(msg["content"])

# 创建用于文档检索的工具
retrieval_tool = create_retriever_tool(
    retriever,
    name="文档检索",
    description="用于检索用户提出的问题,并基于检索到的文档内容进行回复.",
)

tools = [retrieval_tool]

# 创建聊天消息历史记录
msgs = StreamlitChatMessageHistory()

# 创建对话缓冲区内存
memory = ConversationBufferMemory(
    chat_memory=msgs, return_messages=True, memory_key="chat_history", output_key="output"
)

# 指令模板
instructions = """您是一个设计用于查询文档来回答问题的代理。
您可以使用文档检索工具,并基于检索内容来回答问题
您可能不查询文档就知道答案,但是您仍然应该查询文档来获得答案。
如果您从文档中找不到任何信息用于回答问题,则只需返回“抱歉,这个问题我还不知道。”作为答案。
"""

# 基础提示模板 - 修改这里!
base_prompt_template = """
{instructions}

TOOLS:
------
You have access to the following tools:

{tools}

To use a tool, please use the following format:
Thought: Do I need to use a tool? Yes
Action: The action to take, should be one of [{tool_names}]
Action Input: {input}
Observation: the result of the action

When you have a response to say to the Human, or if you do not need to use a tool, you MUST use the format:

Thought: Do I need to use a tool? No
Final Answer: [your response here]


Begin!

Previous conversation history:
{chat_history}

New input: {input}
{agent_scratchpad}"""

# 创建基础提示模板 - 修改这里!
# 需要传递tools和tool_names
tool_names = ", ".join([tool.name for tool in tools])
tool_descriptions = "\n".join([f"{tool.name}: {tool.description}" for tool in tools])

base_prompt = PromptTemplate(
    template=base_prompt_template,
    input_variables=["agent_scratchpad", "chat_history", "input"],
    partial_variables={
        "instructions": instructions,
        "tools": tool_descriptions,
        "tool_names": tool_names
    }
)

# 创建Llm
llm = ChatTongyi(model="qwen-plus")

# 创建react Agent
agent = create_tool_calling_agent(llm, tools, base_prompt)

# 创建Agent执行器
'''
AgentExecutor 的这几个参数作用如下:
agent=agent: 指定要使用的 Agent 实例,这里是通过 create_tool_calling_agent 创建的工具调用代理
tools=tools: 传入可用的工具列表,代理会根据需要调用这些工具(如文档检索工具)
memory=memory: 配置对话内存,用于保存对话历史,使代理能够记住之前的交互内容
verbose=True: 启用详细日志输出,方便调试和查看代理的思考过程
handle_parsing_errors=True: 启用解析错误处理机制,当 Agent 输出格式不正确时自动处理而不是抛出异常
'''
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    memory=memory,
    verbose=True,
    handle_parsing_errors=True
)

# 创建聊天输入框
user_query = st.chat_input(placeholder="请开始提问吧!")

# 如果有用户输入的查询
if user_query:
    # 添加用户消息到session_state
    st.session_state.messages.append({"role": "user", "content": user_query})

    # 显示用户消息
    st.chat_message("user").write(user_query)

    with st.chat_message("assistant"):
        # 创建streamlit回调处理器
        st_cb = StreamlitCallbackHandler(st.container())
        # agent执行过程日志回调显示在Streamlit Container
        config = {"callbacks": [st_cb]}

        try:
            # 执行Agent并获取响应
            response = agent_executor.invoke({"input": user_query}, config=config)
            # 添加助手消息到session_state
            st.session_state.messages.append({"role": "assistant", "content": response["output"]})

            # 显示助手响应
            st.write(response["output"])
        except Exception as e:
            st.error(f"处理问题时出错: {str(e)}")
            st.session_state.messages.append({"role": "assistant", "content": "抱歉,处理您的问题时出现了错误。"})

2.结果展示

控制台执行命令

streamlit run D:/jhproject/python_project/LLM/jh-langserve-app/app/RAG_问答系统.py

在这里插入图片描述

上传txt文件,目前demo支持txt,可以改造成支持多种文档格式,直接问AI就行,这里就不展示了

每次询问和程序员相关的,他会查询出对应结果,因为知识库来自上传的文档
在这里插入图片描述

如果询问奥特曼或者非知识库的内容,它答不上来
在这里插入图片描述

之前配置的监控也会看出,smith平台,如何配置请看LangSmith监控
在这里插入图片描述

3. RAG介绍

核心概念

RAG(Retrieval-Augmented Generation,检索增强生成)是一种将信息检索与大语言模型生成相结合的技术架构。它的核心理念是:
“先检索相关知识,再基于知识生成回答”。
在这里插入图片描述

RAG解决的核心问题

传统的LLM存在以下局限性:

  • 知识陈旧:训练数据有截止日期,无法获取最新信息

  • 事实错误:容易产生看似合理但事实错误的“幻觉”

  • 缺乏专业/私有知识:无法访问企业内部的私有数据

  • 透明度差:用户无法知道回答的依据来源

RAG通过引入外部知识库,完美解决了这些问题。

RAG vs 传统LLM vs 微调

方面传统LLM微调(Fine-tuning)RAG
知识更新无法更新更新成本高、周期长实时更新
事实准确性可能幻觉减少幻觉大幅减少幻觉
私有数据无法访问可学习私有知识可访问私有数据
可追溯性无来源无来源有明确来源
实施成本中等

4.文档问答

实现流程

一个 RAG 程序的 APP 主要有以下流程:

  • 1.用户在 RAG 客户端上传一个txt文件
  • 2.服务器端接收客户端文件,存储在服务端
  • 3.服务器端程序对文件进行读取
  • 4.对文件内容进行拆分,防止一次性塞给 Embedding 模型超 token 限制
  • 5.把 Embedding 后的内容存储在向量数据库,生成检索器
  • 6.程序准备就绪,允许用户进行提问
  • 7.用户提出问题,大模型调用检索器检索文档,把相关片段找出来后,组织后,回复用户。
    在这里插入图片描述
    在这里插入图片描述

更多推荐