跟着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 同样需要配置你的个人密钥

  • 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 参数。

  • # 这里我们将参数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 正是帮助解决这个问题!它们捆绑了从用户输入到完全格式化的提示的所有逻辑。这可以非常简单地开始 - 例如,生成上述字符串的提示就是:

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

  • 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 类型的输出解析为了字符串

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

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

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

    • 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

    • # 定义 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

    • 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

    • 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("请你自我介绍一下自己!")
      
    • 构建检索问答链

    • 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 效果

    • 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"])
      
    • 大模型自己回答的效果

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

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

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

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

    • 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 库。

    • 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 生成的响应

    • 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)
      
    • 返回计算机的终端以运行该应用程序

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

    • # 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函数返回调用不带有历史记录的检索问答链后的结果

    • 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:带历史记录的检索问答模式

    • 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,并且可以从世界各地访问!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/758863.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

图书管理系统(附源码)

前言:前面一起和小伙伴们学习了较为完整的Java语法体系,那么本篇将运用这些知识连串在一起实现图书管理系统。 目录 一、总体设计 二、书籍与书架 书籍(Book) 书架(Booklist) 三、对图书的相关操作 I…

java将html转成图片

java 将html转成图片 1.导入jar2.代码3.展示结果4.注意事项 最近有一个需求需要根据指定的样式生成图片&#xff0c;使用java原生技术有些麻烦&#xff0c;所以上网搜了下案例&#xff0c;最后发现最好用的还是html2image&#xff0c;这里进行简单总结下。 1.导入jar <!-- 用…

metasfresh开源ERP系统Windows开发环境配置参考

目录 概述 开发环境 配置过程 后端启动 前端启动 登陆系统 其他 概述 Compiere闭源之后衍生出了Admpiere等若干开源的产品&#xff0c;metasfresh就是其中之一&#xff0c;metasfresh截至发稿时在GitHub上已有64000多次的修改提交&#xff0c;而且仍在维护中&#xff0…

Python应用开发——30天学习Streamlit Python包进行APP的构建(12)

st.checkbox 显示复选框部件。 Function signature[source] st.checkbox(label, valueFalse, keyNone, helpNone, on_changeNone, argsNone, kwargsNone, *, disabledFalse, label_visibility"visible") Returns (bool) Whether or not the checkbox is checked. …

Sentinel解决雪崩问题

我们或多或少都对雪崩问题有点了解&#xff0c;在微服务系统中&#xff0c;各个微服务互相调用&#xff0c;关系错综复杂&#xff0c;如果其中一个微服务挂了或者处理消息的速度大幅下降&#xff0c;需要被处理的消息越积越多&#xff0c;那么影响的不仅仅是本微服务的功能&…

算法入门(上)

什么是算法&#xff1f; 算法&#xff08;Algorithm&#xff09;是解决特定问题求解步骤的描述&#xff0c;在计算机中表现为指令的有限序列&#xff0c;并且每条指令表示一个或多个操作。 给定一个问题&#xff0c;能够解决这个问题的算法是有很多种的。算式中的问题是千奇百怪…

C语言单链表的算法之插入节点

一&#xff1a;访问各个节点中的数据 &#xff08;1&#xff09;访问链表中的各个节点的有效数据&#xff0c;这个访问必须注意不能使用p、p1、p2&#xff0c;而只能使用phead &#xff08;2&#xff09;只能用头指针不能用各个节点自己的指针。因为在实际当中我们保存链表的时…

后端之路第三站(Mybatis)——XML文件操作sql

一、XML映射文件是啥 前面我们学过了在Mapper接口用注解的方式来操作sql语句 那么XML映射文件就另一种操作sql语句的方法 为什么还要有这么个玩意&#xff1f; 我简单说就是&#xff1a;如果有的sql特别复杂的话&#xff0c;比如需要【动态sql】的话&#xff0c;就得用到XM…

数据可视化期末总结

期末考试重点&#xff08;世界上最没意义的事情&#xff09; 选择 p8 数据可视化的标准&#xff1a; 实用、完整、真实、艺术、交互&#xff08;性&#xff09; p21 色彩三属性 色相、饱和度、亮度 p23 视觉通道的类型&#xff1a; 记得色调是定性 p39 散点图&#xff08;二维…

GIT-LFS使用

0.前言 目前git仓库有很多很大的文件需要管理&#xff0c;但是直接上传&#xff0c;每次clone的文件太大&#xff0c;所有准备使用git-lfs解决。 1、下载和安装 Git LFS 1.1、直接下载二进制包&#xff1a; Releases git-lfs/git-lfs GitHub 安装 Git LFS sudo rpm -ivh…

Leica Cyclone 3DR2024 一款功能强大的点云建模软件下载License获取

Leica Cyclone 3DR 2024 是一款功能强大的点云建模软件&#xff0c;使用旨在为用户提供全面的点云管理、自动化的点云分析&#xff0c;结合强大的建模&#xff0c;在一个直观友好的环境中&#xff0c;专注的完成挑战&#xff0c;提高生产力&#xff0c;轻松创建并交付专业的成果…

杨幂跨界学术圈:内容营销专家刘鑫炜带你了解核心期刊的学术奥秘

近日&#xff0c;知名艺人杨幂在权威期刊《中国广播电视学刊》上发表了一篇名为《浅谈影视剧中演员创作习惯——以电视剧<哈尔滨一九四四>为例》的学术论文&#xff0c;此举在学术界和娱乐圈均引起了广泛关注。该期刊不仅享有极高的声誉&#xff0c;还同时被北大中文核心…

Data-Driven Reinforcement Learning for Robotic Manipulation

意思是 不同的任务以及机器人都有单独的数据和模型 未来需要整合 一个大的数据集包含所有的 然后训练一个大模型 以后具体的任务只需要针对这个模型进行微调 challenge bootstrapping with large data 2 3 4 高清图补充

【C++】using namespace std 到底什么意思

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文作为 JohnKi 的学习笔记&#xff0c;引用了部分大佬的案例 &#x1f4e2;未来很长&a…

【SGX系列教程】(二)第一个 SGX 程序: HelloWorld,linux下运行

文章目录 0. SGX基础原理分析一.准备工作1.1 前提条件1.2 SGX IDE1.3 基本原理 二.程序设计2.1 目录结构2.2 源码设计2.2.1 Encalve/Enclave.edl:Enclave Description Language2.2.2 Enclave/Enclave.lds: Enclave linker script2.2.3 Enclave/Enclave.config.xml: Enclave 配置…

ctfshow-web入门-命令执行(web59-web65)

目录 1、web59 2、web60 3、web61 4、web62 5、web63 6、web64 7、web65 都是使用 highlight_file 或者 show_source 1、web59 直接用上一题的 payload&#xff1a; cshow_source(flag.php); 拿到 flag&#xff1a;ctfshow{9e058a62-f37d-425e-9696-43387b0b3629} 2、w…

MathType7.6专业数学公式编辑器!与Word、PPT等常用软件无缝对接。

MathType&#xff0c;一款专业的数学公式编辑器&#xff0c;以其强大的功能和友好的用户界面&#xff0c;在科研、教学等领域广受欢迎。它支持丰富的数学符号和公式模板&#xff0c;满足不同用户的需求。同时&#xff0c;MathType还提供了多种输出格式&#xff0c;方便与其他文…

3ds Max导出fbx贴图问题简单记录

1.前言 工作中发现3ds Max导出的fbx在其它软件&#xff08;Autodesk viewer&#xff0c;blender&#xff0c;navisworks&#xff0c;FBXReview等&#xff09;中丢失了部分贴图&#xff0c;但导出的fbx用3ds Max打开却正常显示。 fbx格式使用范围较广&#xff0c;很多常见的三…

如何用Go语言,实现基于宏系统的解释器?

目录 一、Go语言介绍二、什么是宏系统三、什么是解释器四、如何用Go语言实现一个基于宏系统的解释器&#xff1f; 一、Go语言介绍 Go语言&#xff0c;又称为Golang&#xff0c;是一种由谷歌公司开发并开源的编程语言。Go语言的设计目标是提高程序员的生产力&#xff0c;同时具…

树莓派开发之文件传输

文章目录 一、简介使用U盘传输文件使用SD卡传输文件使用Xftp 7传输文件 二、 总结 一、简介 在树莓派开发中经常会用到文件传输&#xff0c;下面介绍几种树莓派文件传输的几种方法。 使用U盘传输文件 &#xff08;1&#xff09;复制所需传输文件到U盘 &#xff08;2&#…