Создание потокового чат-бота
Логика состояния use_chat упрощает создание диалогового пользовательского интерфейса для вашего приложения чат-бота. Он обеспечивает потоковую передачу сообщений чата от вашего провайдера искусственного интеллекта, управляет состоянием чата и автоматически обновляет пользовательский интерфейс по мере поступления новых сообщений.
Подводя итог, настройка потокового чата предоставляет следующие возможности:
- Потоковая передача сообщений: все сообщения от поставщика ИИ передаются в пользовательский интерфейс чата в режиме реального времени.
- Управляемые состояния: ваш класс состояния управляет значениями ввода, сообщениями, статусом, ошибкой и многим другим.
- Бесшовная интеграция: легко интегрируйте искусственный интеллект чата в любой дизайн или макет с минимальными усилиями.
В этом руководстве вы узнаете, как реализовать приложение чат-бота с потоковой передачей сообщений в реальном времени. Ознакомьтесь с нашим chatbot with tools guide, чтобы узнать, как использовать инструменты в вашем чат-боте.
Пример
Поток запросов работает следующим образом:
- Пользователь отправляет сообщение, и
send_messageзапускает фоновую задачу серверной части. - Ваш обработчик событий вызывает провайдера и получает поток генератора в реальном времени.
- Состояние добавляет фрагменты к последнему сообщению по мере их поступления, что приводит к запуску обновлений пользовательского интерфейса.
import reflex as rx
class ChatState(rx.State):
messages: list[dict] = []
status: str = "ready"
async def send_message(self, text: str):
self.messages.append({"role": "user", "content": text})
self.status = "submitted"
yield
# Request to the streaming handler
async for chunk in get_stream_response(self.messages):
if self.status == "submitted":
self.status = "streaming"
self.messages.append({"role": "assistant", "content": ""})
self.messages[-1]["content"] += chunk
yield
self.status = "ready"
yield
def chat_interface() -> rx.Component:
return rx.fragment(
rx.foreach(
ChatState.messages,
lambda msg: render_message(msg)
),
ChatInput(
on_submit=lambda text: ChatState.send_message(text),
disabled=ChatState.status != "ready"
)
)
import openai
async def get_stream_response(messages: list[dict]):
client = openai.AsyncOpenAI()
response = await client.chat.completions.create(
model="gpt-4o",
messages=messages,
stream=True,
)
async for chunk in response:
content = chunk.choices[0].delta.content
if content is not None:
yield content
Сообщения пользовательского интерфейса имеют новое свойство
parts, которое содержит части сообщения. Мы рекомендуем отображать сообщения, используя свойствоpartsвместо свойстваcontent. Свойство частей поддерживает различные типы сообщений, включая текст, вызов инструмента и результат инструмента, а также обеспечивает более гибкие и сложные пользовательские интерфейсы чата.
В компоненте Page перехватчики потоковой передачи будут запрашивать конечную точку вашего провайдера AI всякий раз, когда пользователь отправляет сообщение с использованием send_message. Затем сообщения передаются обратно в режиме реального времени и отображаются в пользовательском интерфейсе чата.
Индивидуальный интерфейс
Вы также можете управлять состояниями сообщений чата с помощью кода, показывать статус и обновлять сообщения без активации пользователем.
Статус
Состояние управляет переменной status. Он имеет следующие возможные значения:
submitted: Сообщение отправлено в API, и мы ожидаем начала потока ответа.streaming: ответ активно поступает от API, получая фрагменты данных.ready: Полный ответ получен и обработан; можно отправить новое сообщение пользователя.error: во время запроса API произошла ошибка, препятствующая успешному завершению.
# Inside your layout component
rx.cond(
(ChatState.status == "submitted") | (ChatState.status == "streaming"),
rx.hstack(
rx.cond(ChatState.status == "submitted", Spinner()),
rx.button("Stop", on_click=ChatState.stop_stream)
)
)
Состояние ошибки
Аналогично, состояние error отражает объект ошибки, выданный во время запроса. Его можно использовать для отображения сообщения об ошибке, отключения кнопки отправки или отображения кнопки повтора:
Мы рекомендуем показывать пользователю общее сообщение об ошибке, например «Что-то пошло не так». Это хорошая практика, позволяющая избежать утечки информации с сервера.
# Inside your layout component
rx.cond(
ChatState.has_error,
rx.fragment(
rx.text("An error occurred."),
rx.button("Retry", on_click=ChatState.regenerate_last)
)
)
Отмена и регенерация
Также распространенным вариантом использования является прерывание ответного сообщения, пока оно все еще передается обратно от провайдера искусственного интеллекта. Вы можете сделать это, вызвав метод отмены, который прерывает цикл асинхронного генератора в вашем состоянии.
rx.button(
"Stop",
on_click=ChatState.stop_stream,
disabled=~((ChatState.status == "streaming") | (ChatState.status == "submitted"))
)
Справочник по API
конфигурация use_chat (параметры состояния)
Создает состояние помощника чата. Все значения настраиваются; значения по умолчанию запрашивают API потока и выполняют рендеринг со скоростью встроенного генератора.
| Опора | Тип | Описание |
|---|---|---|
transport |
ChatTransport |
Как сообщения достигают вашего маршрута API |
messages |
list[dict] |
Начальные сообщения для начала разговора |
on_finish |
Callable[[str], None] |
Запускается после завершения ответа помощника |
on_error |
Callable[[Exception], None] |
Запускается при сбое соединения с потоком |
throttle |
float |
Секунды на сон между выходами во время потоковой передачи |
Обратные вызовы событий
Состояние чата предоставляет дополнительные обратные вызовы событий, которые вы можете использовать для обработки различных этапов жизненного цикла чат-бота:
on_finish: вызывается после завершения ответа помощника. Событие включает в себя ответное сообщение, все сообщения и флаги прерывания, отключения и ошибок.on_error: вызывается, когда во время запроса выборки возникает ошибка.on_data: вызывается при получении части данных.
Эти обратные вызовы можно использовать для запуска дополнительных действий, таких как ведение журнала, аналитика или обновления пользовательского интерфейса.
class ConfiguredChatState(rx.State):
def on_finish(self, message: dict):
self.save_to_history(message)
def on_error(self, error: Exception):
logger.error(f"Chat stream failed: {error}")
Математика
Отображение математических функций находится в ритме потока и прокручивается, когда оно длится долго. Встроенная математика, такая как eiπ+1=0, движется по линии, не растягивая ее.
Дисплей
Квадратная формула, как блок:
Переполнение
Длинное расширение прокручивается внутри своего поля, а не разрывает столбец:
Утро в Музее высокой печати
Первое, что бросается в глаза, — это запах: машинного масла, бумажной пыли и столетия чернил, которые так и не высыхают до конца. Второе — это звук. Работающая мастерская высокой печати не может быть тихой, а в музее на Грир-стрит работают три печатных машины, потому что, как сказал мне доцент через минуту после моего приезда, «тихая машина — это всего лишь мебель».
Я пошел, потому что пишу программное обеспечение, которое упорядочивает текст на экранах, и я начал подозревать, что все трудности в моей работе были решены сто лет назад людьми со стальными инструментами и без возможности отмены. Через четыре часа я ушел с чернилами на рукаве и блокнотом, полным подтверждений.
Комната для набора текста
Шрифт живет в неглубоких деревянных ящиках, называемых футлярами, а расположение футляра само по себе является частью дизайна интерфейса: буквы, к которым вы чаще всего тянетесь, находятся ближе всего к вашей руке, в самых больших отделениях. Их никто не расставлял по алфавиту. Договоренность была определена по частоте, обсуждалась в течение поколения, а затем больше никогда не менялась, и это примерно история всех хороших дефолтов, которые я когда-либо выпускал.
Наборщик, работающий на полную мощность, набирает около двух тысяч знаков в час, читая рукопись одним глазом, а другим проверяя каждый выбор. Доцент, наборщик на пенсии по имени Рут Окафор, провела демонстрацию, ни разу не посмотрев вниз. Когда я спросил, сколько времени нужно, чтобы научиться этому, она ответила: «Рукам нужен год. Чтобы понять, когда линия неправильная, нужно десять».
Каждый эм пространства в этой комнате — физический объект. Вам нужно больше воздуха между двумя линиями, вы берете преимущество и возвращаете его обратно. Это делает ваше мнение о интервале очень честным.
Эта строчка что-то перестроила в моей голове. Именно из-за полосок свинца, которые принтеры втыкали между строками текста, мы до сих пор говорим leading. На моем экране интервал — это число, которое я могу изменить нажатием клавиши, поэтому я меняю его постоянно, небрежно. В магазине Руфи было ровно четыре ширины свинца, и все торговцы согласились на них, и книги за сто лет все равно вышли прекрасными. Стесненность не была препятствием для ремесла. Это было ремесло.
Что знает металл
Три вещи, на которых настаивает металл, о которых экраны позволяют нам забыть:
- Пространство материально. Пробелы между словами, отступы строк и поля – это объекты с шириной. Ничто не является «автоматическим». Кто-то выбрал все.
- Иерархия обходится дорого. Изменение размера означает переход к другому делу. Принтеры в первую очередь делали акцент на весе и пространстве, потому что размер был дорогостоящим шагом, и их страницы читались лучше из-за этого.
- Страница закончена до того, как она будет напечатана. Заблокированная погоня либо держится, либо нет. В этом есть удовлетворительная завершенность, которую мне никогда не давало ни одно развертывание.
Все это не ностальгия, или не только. Ограничениями были реальные затраты, и цифровой шрифт был прав, чтобы их устранить. Но удаление имеет эффект второго порядка: когда ничего не стоит дорого, ничто не заставляет принимать решения, и невынужденные решения отходят на второй план. Настройки принтеров по умолчанию сохранились, потому что изменить их было непросто. Нашим приходится выживать за счет дисциплины, которая является более слабым материалом.
Примечания к полям
Практические вещи, которые я записал, в том порядке, в котором я их написал:
- Рут устанавливает сплошной (без интерлиньяжа) только для строк короче алфавита. Все, что дольше, получает воздух. Наши правила относительно длины строки совпадают, что порадовало меня больше, чем следовало бы.
- «Домашний стиль» магазина умещается на карточке, приклеенной к дверце шкафа. Четыре отведения, два лица, три размера. Целая система проектирования, физически перечислимая.
- Ученики учатся распределению (убиранию шрифта) перед составлением. Вы изучаете систему, возвращая в нее вещи.
В музее открыта студия в первую субботу каждого месяца, и если вы попросите, вам дадут возможность указать свое имя. Моя вышла криво. Рут на мгновение взглянула на него и сказала, что это обычная ошибка новичков: я была так тщательно выбираю буквы, что забыла проверить пробелы. Я думал об этом всю неделю.
Музей прессы на Грир-стрит открыт с четверга по воскресенье. Если пойдешь, захвати с собой куртку; в композиторской комнате холодно из-за металла.
Журнал изменений
v2.4.0
18 июня 2026 г.
- Добавлено:
store.batch(fn)группирует несколько операций записи в одно уведомление. Слушатели наблюдают только конечное состояние. - Добавлено параметр
nameдля трассировки инструментов разработчика; анонимные магазины теперь отображаются какstore#3вместоundefined. - Изменено: селекторы запоминаются для каждого подписчика, что сокращает количество повторных рендерингов примерно вдвое в крупных магазинах.
- Исправлено: подписка во время уведомления больше не пропускает следующее обновление.
- Исправлено:
equalsсоблюдается при начальном чтенииuseStore, что соответствует документированному поведению.
Критические изменения
Устаревший псевдоним store.update() удален. Замените его на store.set(); подпись идентична:
- store.update((s) => ({ count: s.count + 1 }))
+ store.set((s) => ({ count: s.count + 1 }))
v2.3.1
30 мая 2026 г.
- Исправлено: гонка, при которой две синхронные записи в одном и том же такте могли отправлять уведомления в обратном порядке при одновременном рендеринге React.
- Документация: разъяснено, что хранилища необходимо вынимать из компонентов, используя правило ворса, чтобы уловить это.
v2.3.0
12 мая 2026 г.
- Добавлено: поддержка React Native;
useStoreбольше не касаетсяwindow. - Устарело:
store.update(), удалено в версии 2.4.0. Предупреждение консоли ссылается на примечание о миграции. - Производительность: учет подписок перенесен из массива в набор; отписаться теперь O(1).
Синхронизация платформы: неделя 27
3 июля 2026 г. · 25 мин · доступна запись
Решения
- Отправьте конечную точку потоковой передачи с флагом во вторник; полное развертывание с учетом задержки p95, удерживающей менее 800 мс в течение 48 часов.
- Используйте курсорную нумерацию страниц для ленты активности. Смещение остается только в таблицах администратора и ограничено страницей 500.
- Отложить миграцию очереди до Q3. Никто не мог назвать текущий сбой, который он исправляет.
Действия
- [x] Mia: конфигурация флага + переключатель уничтожения для конечной точки потоковой передачи
- [ ] Девон: информационная панель задержки с нарисованной линией 800 мс.
- [ ] Сэм: напишите кодировку курсора RFC, максимум одна страница
- [ ] Прия: закройте три устаревшие страницы Runbook до пятницы.
Обсуждение
- Потоковое развертывание
- Поведение повторной попытки при отключении по-прежнему определяется клиентом; сервер отправляет
retry-after, но никто его не читает. - Соглашение: SDK должен соблюдать его, приложения, которые извлекаются вручную, являются самостоятельными.
- Девон вызвался добавить его в журнал изменений SDK как призыв к «изменению поведения».
- Дежурная нагрузка
- Страницы упали на 40 % с момента дедупликации предупреждений. Два из оставшихся оповещений известны-зашумлены и никому не принадлежат.
- Прия берет оба; если их не удается исправить за час каждое, они удаляются.
«Если предупреждение сработало двенадцать раз и не было выполнено никаких действий, это не предупреждение, а заставка».
1. Create typeset.css
Copy the stylesheet into a typeset.css file next to your main CSS file, then import it:
@import "tailwindcss";
@import "./typeset.css";2. Add the fonts
Copy this code into your main Reflex file to load your selected Google Fonts.
3. Create your custom typeset
Copy the custom typeset class below into your global stylesheet, then wrap your content in it: