用纯Python构建一个类似ChatGPT的Web应用程序使用Reflex
“使用Reflex利用纯Python构建类似ChatGPT的Web应用程序”
用OpenAI的API和纯Python实现一行部署构建聊天Web应用

在过去的几个月里,我一直在尝试各种令人惊叹的新LLM聊天机器人,包括Llama 2、GPT-4、Falcon 40B和Claude 2。有一个问题一直困扰着我,那就是如何构建一个可以调用所有这些优秀的LLM作为API的自己的聊天机器人UI?
虽然有无数的选项可以构建漂亮的用户界面,但作为一个机器学习工程师,我对Javascript或任何前端语言都没有经验。我正在寻找一种只使用我目前所知的语言Python来构建我的Web应用的方式!
我决定使用一个相当新的开源框架叫做Reflex,它让我可以纯粹使用Python构建我的后端和前端。
免责声明:我在Reflex担任创始工程师的工作,为开源框架做出了贡献。
在本教程中,我们将介绍如何使用纯Python从头开始构建一个完整的人工智能聊天应用 — 您也可以在这个Github仓库中找到所有的代码。
您将学习以下内容:
- 安装
reflex
并设置开发环境。 - 创建组件来定义和样式化您的用户界面。
- 使用状态为您的应用添加交互性。
- 通过一行命令部署您的应用与他人共享。
设置项目
我们将从创建一个新项目和设置开发环境开始。首先,创建一个新目录用于存放您的项目,并导航到该目录。
~ $ mkdir chatapp~ $ cd chatapp
接下来,我们将为项目创建一个虚拟环境。在这个例子中,我们将使用venv来创建我们的虚拟环境。
chatapp $ python3 -m venv .venv$ source .venv/bin/activate
现在,我们将安装Reflex并创建一个新项目。这将在我们的项目目录中创建一个新的目录结构。
chatapp $ pip install reflexchatapp $ reflex init────────────────────────────────── 初始化chatapp ──────────────────────────────────成功:已初始化chatappchatapp $ lsassets chatapp rxconfig.py .venv
您可以运行模板应用程序以确保一切正常工作。
chatapp $ reflex run─────────────────────────────────── 启动Reflex App ──────────────────────────────────编译: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 1/1 0:00:00─────────────────────────────────────── App正在运行 ───────────────────────────────────────应用正在运行于:http://localhost:3000
您应该在http://localhost:3000看到您的应用正在运行。
Reflex还启动了处理所有状态管理和与前端通信的后端服务器。您可以通过导航到http://localhost:8000/ping来测试后端服务器是否正在运行。
现在我们已经设置好了项目,让我们构建我们的应用!
基本前端
让我们从定义我们的聊天应用的前端开始。在Reflex中,前端可以分解为独立的、可重用的组件。详见组件文档以了解更多信息。
显示问题和答案
我们将修改chatapp/chatapp.py
文件中的index
函数,以返回一个显示单个问题和答案的组件。

# chatapp.pyimport reflex as rxdef index() -> rx.Component: return rx.container( rx.box( "Reflex是什么?", # 用户的问题在右边。 text_align="right", ), rx.box( "一种用纯Python构建Web应用的方法!", # 答案在左边。 text_align="left", ), )# 向应用添加状态和页面。app = rx.App()app.add_page(index)app.compile()
组件可以嵌套在一起以创建复杂的布局。在这里,我们创建了一个包含两个盒子(问题和答案)的父容器。
我们还对组件添加了一些基本样式。组件接受关键字参数,称为props,用于修改组件的外观和功能。我们使用了text_align
prop来将文本左对齐或右对齐。
重用组件
现在我们有了一个显示单个问题和答案的组件,我们可以重用它来显示多个问题和答案。我们将该组件移动到一个单独的函数question_answer
中,并从index
函数中调用它。

def qa(question: str, answer: str) -> rx.Component: return rx.box( rx.box(question, text_align="right"), rx.box(answer, text_align="left"), margin_y="1em", )def chat() -> rx.Component: qa_pairs = [ ( "Reflex是什么?", "一种用纯Python构建Web应用的方法!", ), ( "我能用它做什么?", "从简单的网站到复杂的Web应用都可以!", ), ] return rx.box( *[ qa(question, answer) for question, answer in qa_pairs ] )def index() -> rx.Component: return rx.container(chat())
聊天输入
现在我们希望用户能输入一个问题。为此,我们将使用input组件让用户添加文本,并使用button组件提交问题。

def action_bar() -> rx.Component: return rx.hstack( rx.input(placeholder="提问"), rx.button("提问"), )def index() -> rx.Component: return rx.container( chat(), action_bar(), )
样式
让我们为应用添加一些样式。关于样式的更多信息可以在样式文档中找到。为了保持代码的整洁,我们将样式移动到一个单独的文件chatapp/style.py
中。
# style.py# 问题和答案的通用样式.shadow = "rgba(0, 0, 0, 0.15) 0px 2px 8px"chat_margin = "20%"message_style = dict( padding="1em", border_radius="5px", margin_y="0.5em", box_shadow=shadow, max_width="30em", display="inline-block",)# 为问题和答案设置特定的样式.question_style = message_style | dict( bg="#F5EFFE", margin_left=chat_margin)answer_style = message_style | dict( bg="#DEEAFD", margin_right=chat_margin)# 操作栏的样式.input_style = dict( border_width="1px", padding="1em", box_shadow=shadow)button_style = dict(bg="#CEFFEE", box_shadow=shadow)
我们将在 chatapp.py
中导入样式,并在组件中使用它们。此时,应用程序应该是这样的:

# chatapp.pyimport reflex as rxfrom chatapp import styledef qa(question: str, answer: str) -> rx.Component: return rx.box( rx.box( rx.text(question, style=style.question_style), text_align="right", ), rx.box( rx.text(answer, style=style.answer_style), text_align="left", ), margin_y="1em", )def chat() -> rx.Component: qa_pairs = [ ( "Reflex是什么?", "用纯Python构建 Web 应用的方法!", ), ( "我能用它做什么?", "从简单的网站到复杂的 Web 应用,什么都可以!", ), ] return rx.box( *[ qa(question, answer) for question, answer in qa_pairs ] )def action_bar() -> rx.Component: return rx.hstack( rx.input( placeholder="提问问题", style=style.input_style, ), rx.button("提问", style=style.button_style), )def index() -> rx.Component: return rx.container( chat(), action_bar(), )app = rx.App()app.add_page(index)app.compile()
应用程序看起来不错,但现在还没有太多用处!现在,让我们添加一些功能。
状态
现在,通过添加状态,让聊天应用程序变得互动起来。状态是我们定义应用程序中可以发生变化的所有变量和可以修改它们的所有函数的地方。您可以在状态文档中了解更多关于状态的信息。
定义状态
我们将在 chatapp
目录下创建一个名为 state.py
的新文件。我们的状态将跟踪当前提问的问题以及聊天历史记录。我们还将定义一个事件处理器 answer
,它将处理当前问题并将答案添加到聊天历史记录中。
# state.pyimport reflex as rxclass State(rx.State): # 当前提问的问题。 question: str # 将聊天历史记录作为 (问题, 答案) 元组列表进行跟踪。 chat_history: list[tuple[str, str]] def answer(self): # 我们的聊天机器人现在不太聪明... answer = "我不知道!" self.chat_history.append((self.question, answer))
将状态绑定到组件
现在我们可以在 chatapp.py
中导入状态,并在我们的前端组件中引用它。我们将修改 chat
组件,以使用状态而不是当前固定的问题和答案。

# chatapp.pyfrom chatapp.state import State...def chat() -> rx.Component: return rx.box( rx.foreach( State.chat_history, lambda messages: qa(messages[0], messages[1]), ) )...def action_bar() -> rx.Component: return rx.hstack( rx.input( placeholder="提问问题", on_change=State.set_question, style=style.input_style, ), rx.button( "提问", on_click=State.answer, style=style.button_style, ), )
普通的 Python for
循环无法用于迭代状态变量,因为这些值可能会发生变化,并且在编译时未知。相反,我们使用foreach组件来迭代聊天历史记录。
我们还将输入的on_change
事件绑定到set_question
事件处理程序上,当用户在输入框中输入时,它将更新question
状态变量。我们将按钮的on_click
事件绑定到answer
事件处理程序上,它将处理问题并将答案添加到聊天历史记录中。set_question
事件处理程序是一个隐式定义的内置事件处理程序。每个基本变量都有一个。在事件文档的Setters部分了解更多信息。
清理输入
目前,用户点击按钮后输入框不会清空。我们可以通过将输入框的值绑定到question
变量上,使用value=State.question
来修复这个问题,并在运行answer
事件处理程序时清空它,使用self.question = ''
。
# chatapp.pydef action_bar() -> rx.Component: return rx.hstack( rx.input( value=State.question, placeholder="提问", on_change=State.set_question, style=style.input_style, ), rx.button( "提问", on_click=State.answer, style=style.button_style, ), )
# state.pydef answer(self): # 我们的聊天机器人现在不太聪明...... answer = "我不知道!" self.chat_history.append((self.question, answer)) self.question = ""
流式文本
通常,当事件处理程序返回时,状态更新会发送到前端。但是,我们希望以生成的方式从聊天机器人中获取文本。我们可以通过从事件处理程序中使用yield来实现这一点。有关更多信息,请参见事件yield文档。
# state.pyimport asyncio...async def answer(self): # 我们的聊天机器人现在不太聪明...... answer = "我不知道!" self.chat_history.append((self.question, "")) # 清空问题输入。 self.question = "" # 在继续之前,在此处产生yield以清除前端输入。 yield for i in range(len(answer)): # 暂停以显示流式效果。 await asyncio.sleep(0.1) # 逐个字母添加到输出中。 self.chat_history[-1] = ( self.chat_history[-1][0], answer[: i + 1], ) yield
使用API
我们将使用OpenAI的API为我们的聊天机器人增加一些智能性。我们需要修改我们的事件处理程序以向API发送请求。
# state.pyimport osimport openaiopenai.api_key = os.environ["OPENAI_API_KEY"]...def answer(self): # 我们的聊天机器人现在有点本事了! session = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[ {"role": "user", "content": self.question} ], stop=None, temperature=0.7, stream=True, ) # 随着聊天机器人的回应,添加到答案中。 answer = "" self.chat_history.append((self.question, answer)) # 清空问题输入。 self.question = "" # 在继续之前,在此处产生yield以清除前端输入。 yield for item in session: if hasattr(item.choices[0].delta, "content"): answer += item.choices[0].delta.content self.chat_history[-1] = ( self.chat_history[-1][0], answer, ) yield
最后,我们有了自己的AI聊天机器人!
结论
按照本教程的步骤,我们成功地使用OpenAI的API密钥在Python中创建了我们的聊天应用程序。
要运行此应用程序,我们只需运行以下简单命令:
$ reflex run
要部署它,以便与其他用户共享,我们可以运行以下命令:
$ reflex deploy
我希望本教程能激发您构建自己的基于LLM的应用程序。我非常期待看到您构建的结果,请在社交媒体或评论中与我联系。
如果您有问题,请在下方评论或在Twitter上向我发送私信:@tgotsman12 或在LinkedIn上联系我。在社交媒体上分享您的应用创作并标记我,我很乐意提供反馈或帮助转发!