Build a basic LLM chat app
Introduction
像GPT这样的大型语言模型的出现,极大地简化了开发基于聊天的应用程序的难度。Streamlit提供了几个聊天元素,使您能够为对话代理或聊天机器人构建图形用户界面(GUIs)。利用会话状态与这些元素相结合,您可以使用纯Python代码构建从基本聊天机器人到更高级的、类似ChatGPT的体验。
在本教程中,我们将首先介绍Streamlit的聊天元素,st.chat_message和st.chat_input。然后我们将继续构建三个不同的应用程序,每个应用程序都展示了逐渐增加的复杂性和功能:
- 首先,我们将构建一个镜像你输入的机器人,以感受聊天元素及其工作原理。我们还将介绍会话状态以及如何使用它来存储聊天历史记录。本节将作为教程其余部分的基础。
- 接下来,您将学习如何使用流式传输构建一个简单的聊天机器人GUI。
- 最后,我们将构建一个类似ChatGPT的应用程序,利用会话状态来记住对话上下文,全部代码不到50行。
这里是我们将在本教程中构建的带有流式传输功能的LLM驱动的聊天机器人GUI的预览:
尝试上面的演示,感受一下我们将在本教程中构建的内容。需要注意的几点:
- 屏幕底部有一个始终可见的聊天输入框。它包含一些占位符文本。您可以输入消息并按Enter键或点击运行按钮来发送它。
- 当你输入消息时,它会作为聊天消息显示在上面的容器中。容器是可滚动的,因此你可以向上滚动查看之前的消息。你的消息左侧会显示一个默认的头像。
- 助手的响应会流式传输到前端,并使用不同的默认头像显示。
在我们开始构建之前,让我们仔细看看我们将使用的聊天元素。
Chat elements
Streamlit 提供了多个命令来帮助您构建对话式应用程序。这些聊天元素设计为可以相互配合使用,但您也可以单独使用它们。
st.chat_message 允许你在应用中插入一个聊天消息容器,以便显示来自用户或应用的消息。聊天容器可以包含其他Streamlit元素,包括图表、表格、文本等。st.chat_input 允许你显示一个聊天输入小部件,以便用户可以输入消息。
要了解API的概述,请查看由Streamlit的高级开发者倡导者Chanin Nantasenamat(@dataprofessor)制作的视频教程。
st.chat_message
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
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_message和st.chat_input来构建一个镜像或回显你输入的机器人。
Build a bot that mirrors your 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的完整代码和结果:
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})
虽然上面的例子非常简单,但它是构建更复杂对话应用程序的良好起点。请注意机器人如何立即响应您的输入。在下一节中,我们将添加一个延迟来模拟机器人在响应之前“思考”的过程。
Build a simple chatbot GUI with streaming
在本节中,我们将构建一个简单的聊天机器人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})
到目前为止唯一的区别是我们更改了应用程序的标题,并添加了对random和time的导入。我们将使用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的完整代码和结果:
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的应用程序。
Build a ChatGPT-like app
现在你已经了解了Streamlit聊天元素的基础知识,让我们对其进行一些调整,以构建我们自己的类似ChatGPT的应用程序。你需要安装OpenAI Python库并获取一个API密钥来继续操作。
Install dependencies
首先,让我们安装本节所需的依赖项:
pip install openai streamlit
Add OpenAI API key to Streamlit secrets
接下来,让我们将我们的OpenAI API密钥添加到Streamlit secrets中。我们通过在项目目录中创建.streamlit/secrets.toml文件并向其中添加以下行来完成此操作:
# .streamlit/secrets.toml
OPENAI_API_KEY = "YOUR_API_KEY"
Write the app
现在让我们编写应用程序。我们将使用与之前相同的代码,但我们将用对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调用中,我们传递了在会话状态中硬编码的模型名称,并将聊天历史作为消息列表传递。我们还传递了聊天历史中每条消息的role和content。最后,OpenAI返回了一系列响应(分成令牌块),我们遍历并显示每个块。
将所有内容整合在一起,以下是我们类似ChatGPT应用程序的完整代码和结果:
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应用程序以获取灵感。🎈
还有问题吗?
我们的 论坛 充满了有用的信息和Streamlit专家。