Build a basic LLM chat app

像GPT这样的大型语言模型的出现,极大地简化了开发基于聊天的应用程序的难度。Streamlit提供了几个聊天元素,使您能够为对话代理或聊天机器人构建图形用户界面(GUIs)。利用会话状态与这些元素相结合,您可以使用纯Python代码构建从基本聊天机器人到更高级的、类似ChatGPT的体验。

在本教程中,我们将首先介绍Streamlit的聊天元素,st.chat_messagest.chat_input。然后我们将继续构建三个不同的应用程序,每个应用程序都展示了逐渐增加的复杂性和功能:

  1. 首先,我们将构建一个镜像你输入的机器人,以感受聊天元素及其工作原理。我们还将介绍会话状态以及如何使用它来存储聊天历史记录。本节将作为教程其余部分的基础。
  2. 接下来,您将学习如何使用流式传输构建一个简单的聊天机器人GUI
  3. 最后,我们将构建一个类似ChatGPT的应用程序,利用会话状态来记住对话上下文,全部代码不到50行。

这里是我们将在本教程中构建的带有流式传输功能的LLM驱动的聊天机器人GUI的预览:

尝试上面的演示,感受一下我们将在本教程中构建的内容。需要注意的几点:

  • 屏幕底部有一个始终可见的聊天输入框。它包含一些占位符文本。您可以输入消息并按Enter键或点击运行按钮来发送它。
  • 当你输入消息时,它会作为聊天消息显示在上面的容器中。容器是可滚动的,因此你可以向上滚动查看之前的消息。你的消息左侧会显示一个默认的头像。
  • 助手的响应会流式传输到前端,并使用不同的默认头像显示。

在我们开始构建之前,让我们仔细看看我们将使用的聊天元素。

Streamlit 提供了多个命令来帮助您构建对话式应用程序。这些聊天元素设计为可以相互配合使用,但您也可以单独使用它们。

st.chat_message 允许你在应用中插入一个聊天消息容器,以便显示来自用户或应用的消息。聊天容器可以包含其他Streamlit元素,包括图表、表格、文本等。st.chat_input 允许你显示一个聊天输入小部件,以便用户可以输入消息。

要了解API的概述,请查看由Streamlit的高级开发者倡导者Chanin Nantasenamat(@dataprofessor)制作的视频教程。

st.chat_message 允许你在应用中插入一个多元素的聊天消息容器。返回的容器可以包含任何Streamlit元素,包括图表、表格、文本等。要向返回的容器中添加元素,你可以使用with语法。

st.chat_message的第一个参数是消息作者的name,可以是"user""assistant",以启用预设的样式和头像,如上方的演示所示。你也可以传入一个自定义字符串作为作者名称。目前,名称不会在用户界面中显示,仅设置为可访问性标签。出于可访问性的原因,不应使用空字符串。

这是一个如何使用st.chat_message显示欢迎信息的最小示例:

import streamlit as st with st.chat_message("user"): st.write("Hello 👋")

请注意,由于我们传入了"user"作为作者名称,消息显示时使用了默认的头像和样式。你也可以传入"assistant"作为作者名称以使用不同的默认头像和样式,或者传入自定义名称和头像。更多详情请参见API参考

import streamlit as st import numpy as np with st.chat_message("assistant"): st.write("Hello human") st.bar_chart(np.random.randn(30, 3))

虽然我们在上面的例子中使用了首选的with符号,你也可以直接在返回的对象中调用方法。下面的例子与上面的例子是等价的:

import streamlit as st import numpy as np message = st.chat_message("assistant") message.write("Hello human") message.bar_chart(np.random.randn(30, 3))

到目前为止,我们已经显示了预定义的消息。但如果我们想根据用户输入显示消息呢?

st.chat_input 允许你显示一个聊天输入小部件,以便用户可以输入消息。返回值是用户的输入,如果用户尚未发送消息,则返回值为 None。你还可以传递一个默认提示以显示在输入小部件中。以下是如何使用 st.chat_input 显示聊天输入小部件并显示用户输入的示例:

import streamlit as st prompt = st.chat_input("Say something") if prompt: st.write(f"User has sent the following prompt: {prompt}")

非常简单,对吧?现在让我们结合st.chat_messagest.chat_input来构建一个镜像或回显你输入的机器人。

在本节中,我们将构建一个镜像或回显你输入的机器人。更具体地说,机器人将用相同的消息响应你的输入。我们将使用st.chat_message来显示用户的输入,并使用st.chat_input来接受用户输入。我们还将使用session state来存储聊天历史记录,以便我们可以在聊天消息容器中显示它。

首先,让我们考虑一下构建我们的机器人所需的不同组件:

  • 两个聊天消息容器,分别用于显示用户和机器人的消息。
  • 一个聊天输入小部件,用户可以在其中输入消息。
  • 一种存储聊天历史的方式,以便我们可以在聊天消息容器中显示它。我们可以使用一个列表来存储消息,并在每次用户或机器人发送消息时追加到列表中。列表中的每个条目将是一个字典,包含以下键:role(消息的作者)和content(消息内容)。
import streamlit as st st.title("Echo Bot") # Initialize chat history if "messages" not in st.session_state: st.session_state.messages = [] # Display chat messages from history on app rerun for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"])

在上面的代码片段中,我们为应用程序添加了一个标题,并使用了一个for循环来遍历聊天历史记录,并在聊天消息容器中显示每条消息(包括作者角色和消息内容)。我们还添加了一个检查,以查看messages键是否在st.session_state中。如果不存在,我们将其初始化为一个空列表。这是因为我们稍后会向列表中添加消息,我们不希望每次应用程序重新运行时都覆盖该列表。

现在让我们使用st.chat_input接受用户输入,在聊天消息容器中显示用户的消息,并将其添加到聊天历史记录中。

# React to user input if prompt := st.chat_input("What is up?"): # Display user message in chat message container with st.chat_message("user"): st.markdown(prompt) # Add user message to chat history st.session_state.messages.append({"role": "user", "content": prompt})

我们使用了:=操作符将用户的输入赋值给prompt变量,并在同一行检查它是否为None。如果用户发送了消息,我们会在聊天消息容器中显示该消息,并将其附加到聊天历史记录中。

剩下的就是在if块中添加聊天机器人的响应。我们将使用与之前相同的逻辑,在聊天消息容器中显示机器人的响应(即用户的提示),并将其添加到历史记录中。

response = f"Echo: {prompt}" # Display assistant response in chat message container with st.chat_message("assistant"): st.markdown(response) # Add assistant response to chat history st.session_state.messages.append({"role": "assistant", "content": response})

将所有内容整合在一起,这是我们简单聊天机器人GUI的完整代码和结果:

View full codeexpand_more
import streamlit as st st.title("Echo Bot") # Initialize chat history if "messages" not in st.session_state: st.session_state.messages = [] # Display chat messages from history on app rerun for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"]) # React to user input if prompt := st.chat_input("What is up?"): # Display user message in chat message container st.chat_message("user").markdown(prompt) # Add user message to chat history st.session_state.messages.append({"role": "user", "content": prompt}) response = f"Echo: {prompt}" # Display assistant response in chat message container with st.chat_message("assistant"): st.markdown(response) # Add assistant response to chat history st.session_state.messages.append({"role": "assistant", "content": response})

虽然上面的例子非常简单,但它是构建更复杂对话应用程序的良好起点。请注意机器人如何立即响应您的输入。在下一节中,我们将添加一个延迟来模拟机器人在响应之前“思考”的过程。

在本节中,我们将构建一个简单的聊天机器人GUI,它会从预定义的响应列表中随机选择一条消息来响应用户输入。在下一节中,我们将使用OpenAI将这个简单的玩具示例转换为类似ChatGPT的体验。

就像之前一样,我们仍然需要相同的组件来构建我们的聊天机器人。两个聊天消息容器分别用于显示用户和机器人的消息。一个聊天输入小部件,以便用户可以输入消息。以及一种存储聊天历史记录的方式,以便我们可以在聊天消息容器中显示它。

让我们从前面的部分复制代码,并对其进行一些调整。

import streamlit as st import random import time st.title("Simple chat") # Initialize chat history if "messages" not in st.session_state: st.session_state.messages = [] # Display chat messages from history on app rerun for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"]) # Accept user input if prompt := st.chat_input("What is up?"): # Display user message in chat message container with st.chat_message("user"): st.markdown(prompt) # Add user message to chat history st.session_state.messages.append({"role": "user", "content": prompt})

到目前为止唯一的区别是我们更改了应用程序的标题,并添加了对randomtime的导入。我们将使用random从响应列表中随机选择一个响应,并使用time添加延迟以模拟聊天机器人在响应前的“思考”过程。

剩下的就是在if块中添加聊天机器人的响应。我们将使用一个响应列表,并随机选择一个来显示。我们还将添加一个延迟来模拟聊天机器人在响应之前“思考”(或流式传输其响应)。让我们为此创建一个辅助函数,并将其插入到应用程序的顶部。

# Streamed response emulator def response_generator(): response = random.choice( [ "Hello there! How can I assist you today?", "Hi, human! Is there anything I can help you with?", "Do you need help?", ] ) for word in response.split(): yield word + " " time.sleep(0.05)

回到在我们的聊天界面中编写响应,我们将使用st.write_stream以打字机效果写出流式响应。

# Display assistant response in chat message container with st.chat_message("assistant"): response = st.write_stream(response_generator()) # Add assistant response to chat history st.session_state.messages.append({"role": "assistant", "content": response})

在上面,我们添加了一个占位符来显示聊天机器人的响应。我们还添加了一个for循环来遍历响应并逐个单词显示。我们在每个单词之间添加了0.05秒的延迟,以模拟聊天机器人在响应前的“思考”。最后,我们将聊天机器人的响应附加到聊天历史中。正如你可能已经猜到的,这是一个简单的流式实现。我们将在下一节中看到如何使用OpenAI实现流式处理。

将所有内容整合在一起,这是我们简单聊天机器人GUI的完整代码和结果:

View full codeexpand_more
import streamlit as st import random import time # Streamed response emulator def response_generator(): response = random.choice( [ "Hello there! How can I assist you today?", "Hi, human! Is there anything I can help you with?", "Do you need help?", ] ) for word in response.split(): yield word + " " time.sleep(0.05) st.title("Simple chat") # Initialize chat history if "messages" not in st.session_state: st.session_state.messages = [] # Display chat messages from history on app rerun for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"]) # Accept user input if prompt := st.chat_input("What is up?"): # Add user message to chat history st.session_state.messages.append({"role": "user", "content": prompt}) # Display user message in chat message container with st.chat_message("user"): st.markdown(prompt) # Display assistant response in chat message container with st.chat_message("assistant"): response = st.write_stream(response_generator()) # Add assistant response to chat history st.session_state.messages.append({"role": "assistant", "content": response})

尝试使用上面的演示,感受我们所构建的内容。这是一个非常简单的聊天机器人图形用户界面,但它包含了更复杂聊天机器人的所有组件。在下一节中,我们将看到如何使用OpenAI构建一个类似ChatGPT的应用程序。

现在你已经了解了Streamlit聊天元素的基础知识,让我们对其进行一些调整,以构建我们自己的类似ChatGPT的应用程序。你需要安装OpenAI Python库并获取一个API密钥来继续操作。

首先,让我们安装本节所需的依赖项:

pip install openai streamlit

接下来,让我们将我们的OpenAI API密钥添加到Streamlit secrets中。我们通过在项目目录中创建.streamlit/secrets.toml文件并向其中添加以下行来完成此操作:

# .streamlit/secrets.toml OPENAI_API_KEY = "YOUR_API_KEY"

现在让我们编写应用程序。我们将使用与之前相同的代码,但我们将用对OpenAI API的调用来替换响应列表。我们还将添加一些调整,使应用程序更像ChatGPT。

import streamlit as st from openai import OpenAI st.title("ChatGPT-like clone") # Set OpenAI API key from Streamlit secrets client = OpenAI(api_key=st.secrets["OPENAI_API_KEY"]) # Set a default model if "openai_model" not in st.session_state: st.session_state["openai_model"] = "gpt-3.5-turbo" # Initialize chat history if "messages" not in st.session_state: st.session_state.messages = [] # Display chat messages from history on app rerun for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"]) # Accept user input if prompt := st.chat_input("What is up?"): # Add user message to chat history st.session_state.messages.append({"role": "user", "content": prompt}) # Display user message in chat message container with st.chat_message("user"): st.markdown(prompt)

唯一的变化是我们向st.session_state添加了一个默认模型,并从Streamlit的secrets中设置了我们的OpenAI API密钥。这里变得有趣了。我们可以用OpenAI模型的响应替换我们的模拟流:

# Display assistant response in chat message container with st.chat_message("assistant"): stream = client.chat.completions.create( model=st.session_state["openai_model"], messages=[ {"role": m["role"], "content": m["content"]} for m in st.session_state.messages ], stream=True, ) response = st.write_stream(stream) st.session_state.messages.append({"role": "assistant", "content": response})

在上面,我们用对OpenAI().chat.completions.create的调用替换了响应列表。我们设置了stream=True以将响应流式传输到前端。在API调用中,我们传递了在会话状态中硬编码的模型名称,并将聊天历史作为消息列表传递。我们还传递了聊天历史中每条消息的rolecontent。最后,OpenAI返回了一系列响应(分成令牌块),我们遍历并显示每个块。

将所有内容整合在一起,以下是我们类似ChatGPT应用程序的完整代码和结果:

View full codeexpand_more
from openai import OpenAI import streamlit as st st.title("ChatGPT-like clone") client = OpenAI(api_key=st.secrets["OPENAI_API_KEY"]) if "openai_model" not in st.session_state: st.session_state["openai_model"] = "gpt-3.5-turbo" 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"]) if prompt := st.chat_input("What is up?"): st.session_state.messages.append({"role": "user", "content": prompt}) with st.chat_message("user"): st.markdown(prompt) with st.chat_message("assistant"): stream = client.chat.completions.create( model=st.session_state["openai_model"], messages=[ {"role": m["role"], "content": m["content"]} for m in st.session_state.messages ], stream=True, ) response = st.write_stream(stream) st.session_state.messages.append({"role": "assistant", "content": response})

恭喜!您已经用不到50行代码构建了自己的类似ChatGPT的应用程序。

我们非常期待看到您使用Streamlit的聊天元素构建的内容。尝试不同的模型并调整代码以构建您自己的对话应用程序。如果您构建了一些很酷的东西,请在论坛上告诉我们,或者查看一些其他的生成式AI应用程序以获取灵感。🎈

forum

还有问题吗?

我们的 论坛 充满了有用的信息和Streamlit专家。