跟着DW学习大语言模型-使用Streamlit构建一个RAG应用

  • 在大语言模型 (LLMs) 的应用中,我们面临众多挑战,包括领域知识的缺乏、信息的准确性问题以及生成的虚假内容。检索增强生成 (RAG) 通过引入外部知识库等额外信息源,为这些问题提供了有效的缓解策略。RAG 在那些需要不断更新知识的知识密集型场景或特定领域应用中尤为有效。与其他方法相比,RAG 的一大优势是无需针对特定任务重新训练大语言模型。RAG 系统的核心组成部分,包括检索、生成和增强三大环节

  • 检索增强生成(Retrieval Augmented Generation),简称 RAG。是通过自有垂域数据库检索相关信息,然后合并成为提示模板,给大模型生成漂亮的回答 。RAG是一个将输入与一组相关的支持文档结合起来的技术,这些文档通常来自于像维基百科这样的来源。这些文档被添加到输入提示中,一起送入文本生成器,从而产生最终的输出。RAG的这一机制特别适用于需要应对信息不断更新的场景,因为大语言模型(LLM)所依赖的参数知识本质上是静态的。通过RAG,语言模型可以不经过重新训练而直接访问最新信息,以便生成可靠的、基于检索的输出

  • RAG通过检索到的证据来提高LLM响应的准确性、可控性和相关性 ,这对于在快速变化的环境中解决问题尤其有价值,能有效减少错误信息生成和性能下降的问题。具体步骤如下:

    • 输入: 是指LLM系统需要回答的问题。如果不使用RAG,问题直接由LLM回答。
    • 索引: 使用RAG时,会先将相关文档分块,为这些块生成嵌入向量,并将它们索引到向量库中。在进行查询时,查询内容也会以相似的方式进行嵌入。
    • 检索: 通过比较查询内容与索引向量,找到相关的文档。
    • 生成: 将找到的相关文档与原始提示结合作为额外上下文,然后传递给模型进行回应生成,最终形成系统对用户的回答。
  • 初级 RAG 采用了一个传统过程,包括索引建立、文档检索和内容生成。简单来说,系统根据用户的输入查询相关文档,然后将这些文档和一个提示语结合起来,交给模型生成最终的回答。如果涉及到多轮对话,还可以将对话历史整合到提示语中。

    • 初级 RAG 的局限性包括低精确度(检索到的信息不够准确)和低召回率(有时候无法检索到所有相关的信息)。此外,有时候模型可能会接收到过时的信息,这正是 RAG 系统希望首先解决的问题之一。这可能会导致模型产生不基于事实的幻想性回答,从而影响回答的准确性和可靠性。
    • 当引入额外信息以增强回答时,还可能出现信息重复或冗余的问题。处理多个检索到的文档时,如何排列它们的优先级以及如何使生成的内容风格和语调一致也是需要考虑的挑战。我们还需要确保生成的任务不会过分依赖于这些额外信息,避免模型仅仅重复这些信息而缺乏创新。
  • 高级 RAG 解决了初级 RAG 面临的问题,尤其是在提高检索质量方面,包括优化检索前、检索时和检索后的各个过程。在检索前的准备阶段,我们通过优化数据的索引建立来提高数据质量,包括改善数据的细节度、优化索引结构、添加元数据、改进对齐方式以及混合检索方法。

    • 在检索阶段,我们可以通过改进嵌入模型来提高上下文片段的质量。例如,通过对嵌入模型进行微调,以提高检索的相关性,或者使用能够更好理解上下文的动态嵌入模型(如 OpenAI 的 embeddings-ada-02 模型)。在检索后的优化过程中,我们专注于解决上下文窗口限制和减少噪音或分散注意力的信息。常用的方法包括重新排列文档,以将更相关的内容放在提示的前后,或者重新计算查询与文档片段之间的语义相似度。此外,通过压缩提示信息也有助于解决这些问题。
  • 模块化 RAG,顾名思义,通过增强其功能模块来提升性能,例如加入相似性检索的搜索模块,以及在检索工具上进行精细调整。模块化 RAG 能够根据具体的任务需求,添加、替换或调整模块之间的工作流程,从而实现更高的多样性和灵活性。这种设计让模块化 RAG 不仅包括了朴素 RAG 和高级 RAG 这两种固定模式,还扩展了包括搜索、记忆、融合、路由、预测和任务适配等多种模块,以解决各种问题。随着 RAG 系统构建变得更加灵活,一系列优化技术相继被提出,用于进一步优化 RAG 流程,包括:

    • 混合式搜索探索: 结合了关键词搜索与语义搜索等多种搜索技术,以便检索到既相关又富含上下文的信息,特别适用于处理多样化的查询类型和信息需求。
    • 递归式检索与查询引擎: 通过从小的语义片段开始,逐步检索更大的内容块以丰富上下文的递归过程,有效平衡了检索效率与信息的丰富度。
    • StepBack-prompt 提示技术: 一种特殊的提示方法,能让大语言模型进行概念和原则的抽象化处理,从而引导更加深入的推理过程。当应用于 RAG 框架时,能够帮助模型超越具体事例,进行更广泛的推理。
    • 子查询策略: 采用树状查询或按序查询小块信息的不同策略,适用于多种场景。LlamaIndex 提供的子问题查询引擎允许将大的查询任务拆分成多个小问题,分别利用不同的数据源进行解答。
    • 假设性文档嵌入技术 (HyDE): 通过生成查询的假设性回答并嵌入,来检索与这个假设回答相似的文档,而不是直接使用查询本身,以此来优化检索效果。
  • 一句话总结:RAG(中文为检索增强生成) = 检索技术 + LLM 提示。例如,我们向 LLM 提问一个问题(answer),RAG 从各种数据源检索相关的信息,并将检索到的信息和问题(answer)注入到 LLM 提示中,LLM 最后给出答案。完整的RAG应用流程主要包含两个阶段:

    • 数据准备阶段:数据提取------>文本分割------>向量化(embedding)------>数据入库
    • 应用阶段:用户提问------>数据检索(召回)------>注入Prompt------>LLM生成答案
  • 数据准备一般是一个离线的过程,主要是将私域数据向量化后构建索引并存入数据库的过程。主要包括:数据提取、文本分割、向量化、数据入库等环节。

    • 数据提取:数据加载:包括多格式数据加载、不同数据源获取等,根据数据自身情况,将数据处理为同一个范式。数据处理:包括数据过滤、压缩、格式化等。元数据获取:提取数据中关键信息,例如文件名、Title、时间等 。
    • 文本分割:文本分割主要考虑两个因素:1)embedding模型的Tokens限制情况;2)语义完整性对整体的检索效果的影响。一些常见的文本分割方式如下:句分割:以"句"的粒度进行切分,保留一个句子的完整语义。常见切分符包括:句号、感叹号、问号、换行符等。固定长度分割:根据embedding模型的token长度限制,将文本分割为固定长度(例如256/512个tokens),这种切分方式会损失很多语义信息,一般通过在头尾增加一定冗余量来缓解。
    • 向量化(embedding):是一个将文本数据转化为向量矩阵的过程,该过程会直接影响到后续检索的效果。
    • **数据入库:**数据向量化后构建索引,并写入数据库的过程可以概述为数据入库过程,适用于RAG场景的数据库包括:FAISS、Chromadb、ES、milvus等。一般可以根据业务场景、硬件、性能需求等多因素综合考虑,选择合适的数据库。
  • 构建 RAG 应用

  • 将LLM 接入 LangChain,LangChain 为基于 LLM 开发自定义应用提供了高效的开发框架,便于开发者迅速地激发 LLM 的强大能力,搭建 LLM 应用。LangChain 也同样支持多种大模型,内置了 OpenAI、LLAMA 等大模型的调用接口。但是,LangChain 并没有内置所有大模型,它通过允许用户自定义 LLM 类型,来提供强大的可扩展性。LangChain 提供了对于多种大模型的封装,基于 LangChain 的接口可以便捷地调用 ChatGPT 并将其集合在以 LangChain 为基础框架搭建的个人应用中。基于 LangChain 接口调用 ChatGPT 同样需要配置你的个人密钥

python 复制代码
import os
import openai
from dotenv import load_dotenv, find_dotenv
# 读取本地/项目的环境变量。
# find_dotenv()寻找并定位.env文件的路径
# load_dotenv()读取该.env文件,并将其中的环境变量加载到当前的运行环境中 
# 如果你设置的是全局的环境变量,这行代码则没有任何作用。
_ = load_dotenv(find_dotenv())
# 获取环境变量 OPENAI_API_KEY
openai_api_key = os.environ['OPENAI_API_KEY']
  • 接下来你需要实例化一个 ChatOpenAI 类,可以在实例化时传入超参数来控制回答,例如 temperature 参数。

python 复制代码
# 这里我们将参数temperature设置为0.0,从而减少生成答案的随机性。
# 如果你想要每次得到不一样的有新意的答案,可以尝试调整该参数。
llm = ChatOpenAI(temperature=0.0)
llm = ChatOpenAI(temperature=0, openai_api_key="YOUR_API_KEY")
output = llm.invoke("请你自我介绍一下自己!")
  • 默认调用的是 ChatGPT-3.5 模型。另外,几种常用的超参数设置包括:

    • model_name:所要使用的模型,默认为 'gpt-3.5-turbo',参数设置与 OpenAI 原生接口参数设置一致。
    • temperature:温度系数,取值同原生接口。
    • openai_api_key:OpenAI API key,如果不使用环境变量设置 API Key,也可以在实例化时设置。
    • openai_proxy:设置代理,如果不使用环境变量设置代理,也可以在实例化时设置。
    • streaming:是否使用流式传输,即逐字输出模型回答,默认为 False,此处不赘述。
    • max_tokens:模型输出的最大 token 数,意义及取值同上。
  • 在我们开发大模型应用时,大多数情况下不会直接将用户的输入直接传递给 LLM。通常,他们会将用户输入添加到一个较大的文本中,称为提示模板,该文本提供有关当前特定任务的附加上下文。 PromptTemplates 正是帮助解决这个问题!它们捆绑了从用户输入到完全格式化的提示的所有逻辑。这可以非常简单地开始 - 例如,生成上述字符串的提示就是:

python 复制代码
from langchain_core.prompts import ChatPromptTemplate
# 这里我们要求模型对给定文本进行中文翻译
prompt = """请你将由三个反引号分割的文本翻译成英文!\
text: ```{text}```
"""
text = "我带着比身体重的行李,\
游入尼罗河底,\
经过几道闪电 看到一堆光圈,\
不确定是不是这里。\
"
prompt.format(text=text)
  • 我们知道聊天模型的接口是基于消息(message),而不是原始的文本。PromptTemplates 也可以用于产生消息列表,在这种样例中,prompt不仅包含了输入内容信息,也包含了每条message的信息(角色、在列表中的位置等)。通常情况下,一个 ChatPromptTemplate 是一个 ChatMessageTemplate 的列表。每个 ChatMessageTemplate 包含格式化该聊天消息的说明(其角色以及内容)。

python 复制代码
from langchain.prompts.chat import ChatPromptTemplate
template = "你是一个翻译助手,可以帮助我将 {input_language} 翻译成 {output_language}."
human_template = "{text}"
chat_prompt = ChatPromptTemplate.from_messages([
    ("system", template),
    ("human", human_template),
])
text = "我带着比身体重的行李,\
游入尼罗河底,\
经过几道闪电 看到一堆光圈,\
不确定是不是这里。\
"
messages  = chat_prompt.format_messages(input_language="中文", output_language="英文", text=text)
output  = llm.invoke(messages)
  • OutputParsers 将语言模型的原始输出转换为可以在下游使用的格式。 OutputParsers 有几种主要类型,包括:

    • 将 LLM 文本转换为结构化信息(例如 JSON)
    • 将 ChatMessage 转换为字符串
    • 将除消息之外的调用返回的额外信息(如 OpenAI 函数调用)转换为字符串
  • 最后,我们将模型输出传递给 output_parser,它是一个 BaseOutputParser,这意味着它接受字符串或 BaseMessage 作为输入 。 StrOutputParser 特别简单地将任何输入转换为字符串。通过输出解析器成功将 ChatMessage 类型的输出解析为了字符串

python 复制代码
from langchain_core.output_parsers import StrOutputParser
output_parser = StrOutputParser()
output_parser.invoke(output)
  • 现在可以将所有这些组合成一条链。该链将获取输入变量,将这些变量传递给提示模板以创建提示,将提示传递给语言模型,然后通过(可选)输出解析器传递输出。接下来我们将使用LCEL这种语法去快速实现一条链(chain)。

python 复制代码
chain = chat_prompt | llm | output_parser
chain.invoke({"input_language":"中文", "output_language":"英文","text": text})
  • 构建检索问答链

    • 首先,我们加载已经构建的向量数据库。注意,此处你需要使用和构建时相同的 Emedding。从环境变量中加载你的 API_KEY

    python 复制代码
    import sys
    sys.path.append("../C3 搭建知识库") # 将父目录放入系统路径中
    # 使用智谱 Embedding API,注意,需要将上一章实现的封装代码下载到本地
    from zhipuai_embedding import ZhipuAIEmbeddings
    from langchain.vectorstores.chroma import Chroma
    from dotenv import load_dotenv, find_dotenv
    import os
    _ = load_dotenv(find_dotenv())    # read local .env file
    zhipuai_api_key = os.environ['ZHIPUAI_API_KEY']
    • 加载向量数据库,其中包含了 .../.../data_base/knowledge_db 下多个文档的 Embedding

    python 复制代码
    # 定义 Embeddings
    embedding = ZhipuAIEmbeddings()
    # 向量数据库持久化路径
    persist_directory = '../C3 搭建知识库/data_base/vector_db/chroma'
    # 加载数据库
    vectordb = Chroma(
        persist_directory=persist_directory,  # 允许我们将persist_directory目录保存到磁盘上
        embedding_function=embedding
    )
    print(f"向量库中存储的数量:{vectordb._collection.count()}")
    • 可以测试一下加载的向量数据库,使用一个问题 query 进行向量检索。如下代码会在向量数据库中根据相似性进行检索,返回前 k 个最相似的文档。使用相似性搜索前,请确保你已安装了 OpenAI 开源的快速分词工具 tiktoken 包:pip install tiktoken

    python 复制代码
    question = "什么是prompt engineering?"
    docs = vectordb.similarity_search(question,k=3)
    print(f"检索到的内容数:{len(docs)}")
    for i, doc in enumerate(docs):
        print(f"检索到的第{i}个内容: \n {doc.page_content}", end="\n-----------------------------------------------------\n")
    • 调用 OpenAI 的 API 创建一个 LLM

    python 复制代码
    import os 
    OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
    from langchain_openai import ChatOpenAI
    llm = ChatOpenAI(model_name = "gpt-3.5-turbo", temperature = 0)
    llm.invoke("请你自我介绍一下自己!")
    • 构建检索问答链

    python 复制代码
    from langchain.prompts import PromptTemplate
    template = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答
    案。最多使用三句话。尽量使答案简明扼要。总是在回答的最后说"谢谢你的提问!"。
    {context}
    问题: {question}
    """
    QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"], template=template)
    from langchain.chains import RetrievalQA
    
    qa_chain = RetrievalQA.from_chain_type(llm,
                                           retriever=vectordb.as_retriever(),
                                           return_source_documents=True,
                                           chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})
    • 创建检索 QA 链的方法 RetrievalQA.from_chain_type() 有如下参数:

      • llm:指定使用的 LLM
      • 指定 chain type : RetrievalQA.from_chain_type(chain_type="map_reduce"),也可以利用load_qa_chain()方法指定chain type。
      • 自定义 prompt :通过在RetrievalQA.from_chain_type()方法中,指定chain_type_kwargs参数,而该参数:chain_type_kwargs = {"prompt": PROMPT}
      • 返回源文档:通过RetrievalQA.from_chain_type()方法中指定:return_source_documents=True参数;也可以使用RetrievalQAWithSourceChain()方法,返回源文档的引用(坐标或者叫主键、索引)
    • 基于召回结果和 query 结合起来构建的 prompt 效果

    python 复制代码
    question_1 = "什么是南瓜书?"
    question_2 = "王阳明是谁?"
    result = qa_chain({"query": question_1})
    print("大模型+知识库后回答 question_1 的结果:")
    print(result["result"])
    result = qa_chain({"query": question_2})
    print("大模型+知识库后回答 question_2 的结果:")
    print(result["result"])
    • 大模型自己回答的效果

    python 复制代码
    prompt_template = """请回答下列问题:{}""".format(question_1)
    ### 基于大模型的问答
    llm.predict(prompt_template)
    prompt_template = """请回答下列问题:{}""".format(question_2)
    ### 基于大模型的问答
    llm.predict(prompt_template)
    • 通过以上两个问题,我们发现 LLM 对于一些近几年的知识以及非常识性的专业问题,回答的并不是很好。而加上我们的本地知识,就可以帮助 LLM 做出更好的回答。另外,也有助于缓解大模型的"幻觉"问题。

    • 现在我们已经实现了通过上传本地知识文档,然后将他们保存到向量知识库,通过将查询问题与向量知识库的召回结果进行结合输入到 LLM 中,我们就得到了一个相比于直接让 LLM 回答要好得多的结果。在与语言模型交互时,你可能已经注意到一个关键问题 - 它们并不记得你之前的交流内容 。这在我们构建一些应用程序(如聊天机器人)的时候,带来了很大的挑战,使得对话似乎缺乏真正的连续性。将介绍 LangChain 中的储存模块,即如何将先前的对话嵌入到语言模型中的,使其具有连续对话的能力。我们将使用 ConversationBufferMemory ,它保存聊天消息历史记录的列表,这些历史记录将在回答问题时与问题一起传递给聊天机器人,从而将它们添加到上下文中。

    python 复制代码
    from langchain.memory import ConversationBufferMemory
    memory = ConversationBufferMemory(
        memory_key="chat_history",  # 与 prompt 的输入变量保持一致。
        return_messages=True  # 将以消息列表的形式返回聊天记录,而不是单个字符串
    )
    • 对话检索链(ConversationalRetrievalChain)在检索 QA 链的基础上,增加了处理对话历史的能力。它的工作流程是:

      • 将之前的对话与新问题合并生成一个完整的查询语句。
      • 在向量数据库中搜索该查询的相关文档。
      • 获取结果后,存储所有答案到对话记忆区。
      • 用户可在 UI 中查看完整的对话流程。
    • 这种链式方式将新问题放在之前对话的语境中进行检索,可以处理依赖历史信息的查询。并保留所有信 息在对话记忆中,方便追踪。使用上一节中的向量数据库和 LLM !首先提出一个无历史对话的问题"我可以学习到关于提示工程的知识吗?"

    python 复制代码
    from langchain.chains import ConversationalRetrievalChain
    retriever=vectordb.as_retriever()
    qa = ConversationalRetrievalChain.from_llm(
        llm,
        retriever=retriever,
        memory=memory
    )
    question = "我可以学习到关于提示工程的知识吗?"
    result = qa({"question": question})
    print(result['answer'])
    question = "为什么这门课需要教这方面的知识?"
    result = qa({"question": question})
    print(result['answer'])
  • 对知识库和LLM已经有了基本的理解,现在是时候将它们巧妙地融合并打造成一个富有视觉效果的界面了。这样的界面不仅对操作更加便捷,还能便于与他人分享。Streamlit 是一种快速便捷的方法,可以直接在 Python 中通过友好的 Web 界面演示机器学习模型。Streamlit 可以通过 Python 接口程序快速实现这一目标,而无需编写任何前端、网页或 JavaScript 代码。

  • Streamlit 是一个用于快速创建数据应用程序的开源 Python 库。它的设计目标是让数据科学家能够轻松地将数据分析和机器学习模型转化为具有交互性的 Web 应用程序,而无需深入了解 Web 开发。和常规 Web 框架,如 Flask/Django 的不同之处在于,它不需要你去编写任何客户端代码(HTML/CSS/JS),只需要编写普通的 Python 模块,就可以在很短的时间内创建美观并具备高度交互性的界面,从而快速生成数据分析或者机器学习的结果;另一方面,和那些只能通过拖拽生成的工具也不同的是,你仍然具有对代码的完整控制权。Streamlit 提供了一组简单而强大的基础模块,用于构建数据应用程序:

    • st.write():这是最基本的模块之一,用于在应用程序中呈现文本、图像、表格等内容。
    • st.title()、st.header()、st.subheader():这些模块用于添加标题、子标题和分组标题,以组织应用程序的布局。
    • st.text()、st.markdown():用于添加文本内容,支持 Markdown 语法。
    • st.image():用于添加图像到应用程序中。
    • st.dataframe():用于呈现 Pandas 数据框。
    • st.table():用于呈现简单的数据表格。
    • st.pyplot()、st.altair_chart()、st.plotly_chart():用于呈现 Matplotlib、Altair 或 Plotly 绘制的图表。
    • st.selectbox()、st.multiselect()、st.slider()、st.text_input():用于添加交互式小部件,允许用户在应用程序中进行选择、输入或滑动操作。
    • st.button()、st.checkbox()、st.radio():用于添加按钮、复选框和单选按钮,以触发特定的操作。
  • 这些基础模块使得通过 Streamlit 能够轻松地构建交互式数据应用程序,并且在使用时可以根据需要进行组合和定制,更多内容请查看官方文档

  • 首先,创建一个新的 Python 文件并将其保存 streamlit_app.py在工作目录的根目录中,导入必要的 Python 库。

    python 复制代码
    import streamlit as st
    from langchain_openai import ChatOpenAI
    st.title('动手学大模型应用开发')
    openai_api_key = st.sidebar.text_input('OpenAI API Key', type='password')
    • 定义一个函数,使用用户密钥对 OpenAI API 进行身份验证、发送提示并获取 AI 生成的响应。该函数接受用户的提示作为参数,并使用st.info来在蓝色框中显示 AI 生成的响应

    python 复制代码
    def generate_response(input_text):
        llm = ChatOpenAI(temperature=0.7, openai_api_key=openai_api_key)
        st.info(llm(input_text))
    # 最后,使用st.form()创建一个文本框(st.text_area())供用户输入。当用户单击Submit时,generate-response()将使用用户的输入作为参数来调用该函数
    with st.form('my_form'):
        text = st.text_area('Enter text:', 'What are the three key pieces of advice for learning how to code?')
        submitted = st.form_submit_button('Submit')
        if not openai_api_key.startswith('sk-'):
            st.warning('Please enter your OpenAI API key!', icon='⚠')
        if submitted and openai_api_key.startswith('sk-'):
            generate_response(text)
    • 返回计算机的终端以运行该应用程序

    shell 复制代码
    streamlit run streamlit_app.py
    • 但是当前只能进行单轮对话,我们对上述做些修改,通过使用 st.session_state 来存储对话历史,可以在用户与应用程序交互时保留整个对话的上下文。

    python 复制代码
    # Streamlit 应用程序界面
    def main():
        st.title('动手学大模型应用开发')
        openai_api_key = st.sidebar.text_input('OpenAI API Key', type='password')
        # 用于跟踪对话历史
        if 'messages' not in st.session_state:
            st.session_state.messages = []
        messages = st.container(height=300)
        if prompt := st.chat_input("Say something"):
            # 将用户输入添加到对话历史中
            st.session_state.messages.append({"role": "user", "text": prompt})
            # 调用 respond 函数获取回答
            answer = generate_response(prompt, openai_api_key)
            # 检查回答是否为 None
            if answer is not None:
                # 将LLM的回答添加到对话历史中
                st.session_state.messages.append({"role": "assistant", "text": answer})
            # 显示整个对话历史
            for message in st.session_state.messages:
                if message["role"] == "user":
                    messages.chat_message("user").write(message["text"])
                elif message["role"] == "assistant":
                    messages.chat_message("assistant").write(message["text"])   
  • 添加检索问答

    • 先将2.构建检索问答链部分的代码进行封装:get_vectordb函数返回C3部分持久化后的向量知识库; get_chat_qa_chain函数返回调用带有历史记录的检索问答链后的结果; get_qa_chain函数返回调用不带有历史记录的检索问答链后的结果

    python 复制代码
    def get_vectordb():
        # 定义 Embeddings
        embedding = ZhipuAIEmbeddings()
        # 向量数据库持久化路径
        persist_directory = '../C3 搭建知识库/data_base/vector_db/chroma'
        # 加载数据库
        vectordb = Chroma(
            persist_directory=persist_directory,  # 允许我们将persist_directory目录保存到磁盘上
            embedding_function=embedding
        )
        return vectordb
    #带有历史记录的问答链
    def get_chat_qa_chain(question:str,openai_api_key:str):
        vectordb = get_vectordb()
        llm = ChatOpenAI(model_name = "gpt-3.5-turbo", temperature = 0,openai_api_key = openai_api_key)
        memory = ConversationBufferMemory(
            memory_key="chat_history",  # 与 prompt 的输入变量保持一致。
            return_messages=True  # 将以消息列表的形式返回聊天记录,而不是单个字符串
        )
        retriever=vectordb.as_retriever()
        qa = ConversationalRetrievalChain.from_llm(
            llm,
            retriever=retriever,
            memory=memory
        )
        result = qa({"question": question})
        return result['answer']
    #不带历史记录的问答链
    def get_qa_chain(question:str,openai_api_key:str):
        vectordb = get_vectordb()
        llm = ChatOpenAI(model_name = "gpt-3.5-turbo", temperature = 0,openai_api_key = openai_api_key)
        template = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答
            案。最多使用三句话。尽量使答案简明扼要。总是在回答的最后说"谢谢你的提问!"。
            {context}
            问题: {question}
            """
        QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],
                                     template=template)
        qa_chain = RetrievalQA.from_chain_type(llm,
                                           retriever=vectordb.as_retriever(),
                                           return_source_documents=True,
                                           chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})
        result = qa_chain({"query": question})
        return result["result"]
    • 然后,添加一个单选按钮部件st.radio,选择进行问答的模式:None:不使用检索问答的普通模式; qa_chain:不带历史记录的检索问答模式; chat_qa_chain:带历史记录的检索问答模式

    python 复制代码
    selected_method = st.radio(
            "你想选择哪种模式进行对话?",
            ["None", "qa_chain", "chat_qa_chain"],
            captions = ["不使用检索问答的普通模式", "不带历史记录的检索问答模式", "带历史记录的检索问答模式"])
    • 进入页面,首先先输入OPEN_API_KEY(默认),然后点击单选按钮选择进行问答的模式,最后在输入框输入你的问题,按下回车即可!完整代码参考streamlit_app.py
  • 要将应用程序部署到 Streamlit Cloud,请执行以下步骤:

    • 为应用程序创建 GitHub 存储库。您的存储库应包含两个文件:
      • your-repository/
        ├── streamlit_app.py
        └── requirements.txt
    • 转到 Streamlit Community Cloud,单击工作区中的New app按钮,然后指定存储库、分支和主文件路径。或者,您可以通过选择自定义子域来自定义应用程序的 URL
    • 点击Deploy!按钮,应用程序现在将部署到 Streamlit Community Cloud,并且可以从世界各地访问!
相关推荐
IT古董24 分钟前
【漫话机器学习系列】017.大O算法(Big-O Notation)
人工智能·机器学习
凯哥是个大帅比25 分钟前
人工智能ACA(五)--深度学习基础
人工智能·深度学习
Code哈哈笑27 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
m0_7482329244 分钟前
DALL-M:基于大语言模型的上下文感知临床数据增强方法 ,补充
人工智能·语言模型·自然语言处理
szxinmai主板定制专家1 小时前
【国产NI替代】基于FPGA的32通道(24bits)高精度终端采集核心板卡
大数据·人工智能·fpga开发
海棠AI实验室1 小时前
AI的进阶之路:从机器学习到深度学习的演变(三)
人工智能·深度学习·机器学习
机器懒得学习1 小时前
基于YOLOv5的智能水域监测系统:从目标检测到自动报告生成
人工智能·yolo·目标检测
QQ同步助手1 小时前
如何正确使用人工智能:开启智慧学习与创新之旅
人工智能·学习·百度
AIGC大时代1 小时前
如何使用ChatGPT辅助文献综述,以及如何进行优化?一篇说清楚
人工智能·深度学习·chatgpt·prompt·aigc
流浪的小新1 小时前
【AI】人工智能、LLM学习资源汇总
人工智能·学习