Привет, Хабр! 👋 Меня зовут Дима Косаревский, я инженер данных (DE), увлеченный Data Science и всем, что связано с этим направлением. Data Science позволяет извлекать ценную информацию из огромных объемов данных при помощи статистических и вычислительных методов.
В последнее время эти ИИ, вроде ChatGPT, врываются прямо во все сферы. И вот благодаря увлечению Data Science можно использовать этих ботов, чтобы помогать людям, да ещё и пообщаться с ними на разные темы. ChatGPT действительно впечатляет. Он не только общается на разные темы, но еще и стихи сочинять умеет. Здорово, правда? Вот один из примеров, которые я получил недавно:
Покоритель мира - ИИ,
ChatGPT сулит грани новой жизни.
Работу заберёт у многих,
Страх испытавших горечь пламени.
В ответственности вложена сила,
Судьбы людей на блюдечке держа.
ИИ слава без предела,
В бесконечности вразумляя нас.

Сайт ChatGPT иногда работает медленно, и для доступа к нему в некоторых странах, например в России, требуется VPN. Некоторые из моих друзей хотят протестировать ИИ без СМС и регистрации. Я решил эту проблему с помощью официального API ChatGPT и Streamlit. Давайте посмотрим как это сделать?
ИИ-ассистент в 5 шагов
Из этого туториала можно узнать:
Как использовать ChatGPT API
Как отобразить диалог с ботом в чате
Как преобразовать текст в речь (TTS)
Как сделать локализацию
Как собрать все вместе в приложении Streamlit
Хотите сразу к делу? Вот рабочее приложение и репозиторий с кодом.
Функции-помощники
И сразу код. Сначала напишем несколько вспомогательных функций:
def clear_chat() -> None: st.session_state.generated = [] st.session_state.past = [] st.session_state.messages = [] st.session_state.user_text = "" def show_text_input() -> None: st.text_area(label=st.session_state.locale.chat_placeholder, value=st.session_state.user_text, key="user_text") def show_chat_buttons() -> None: b0, b1, b2 = st.columns(3) with b0, b1, b2: b0.button(label=st.session_state.locale.chat_run_btn) b1.button(label=st.session_state.locale.chat_clear_btn, on_click=clear_chat) b2.download_button( label=st.session_state.locale.chat_save_btn, data="\n".join([str(d) for d in st.session_state.messages[1:]]), file_name="ai-talks-chat.json", mime="application/json", )
Эти функции позволяют очистить состояние cессии Streamlit и отобразить область ввода пользовательского текста и кнопки чата.
1. Как использовать ChatGPT API
Взаимодействие с API:
import streamlit as st import openai from typing import List def create_gpt_completion(ai_model: str, messages: List[dict]) -> dict: openai.api_key = st.secrets.api_credentials.api_key completion = openai.ChatCompletion.create( model=ai_model, messages=messages, ) return completion
Эта функция принимает на вход два аргумента: ai_model, модель GPT, и messages, список сообщений чата для поддержания контекста диалога. Указываем ключ API, используя функцию секретов Streamlit, и создаём экземпляр класса ChatCompletion, используя метод create, передавая модель и сообщения. Когда API отвечает, функция возвращает результат в виде словаря (json): следующего вида:
{ "choices": [ { "finish_reason": "stop", "index": 0, "message": { "content": "How can I help you?", "role": "assistant" } } ], "created": 1681080142, "id": "chatcmpl-73Y1mIfmDFWzuHILFQ8PG3bQcvOzU", "model": "gpt-4-0314", "object": "chat.completion", "usage": { "completion_tokens": 6, "prompt_tokens": 27, "total_tokens": 33 } }
В целом, эта функция обеспечивает простой способ взаимодействия с API OpenAI и создания чат-бота. Параметр messages можно использовать для хранения истории разговоров и обеспечения контекстуальности и согласованности ответов чат-бота.
2. Как отобразить диалог с ботом в чате
В этом шаге можно увидеть код для отображения разговора в чате с использованием модели GPT. Код состоит из трех основных функций: show_chat, show_gpt_conversation и show_conversation.
from streamlit_chat import message def show_chat(ai_content: str, user_text: str) -> None: if ai_content not in st.session_state.generated: # store the ai content st.session_state.past.append(user_text) st.session_state.generated.append(ai_content) if st.session_state.generated: for i in range(len(st.session_state.generated)): message(st.session_state.past[i], is_user=True, key=str(i) + "_user", avatar_style="micah") message("", key=str(i)) st.markdown(st.session_state.generated[i])
Тут на помощь приходит библиотека streamlit-chat. Она позволяет отображать чат с ботом в удобном формате.
Функция show_chat отвечает за отображение сообщений разговора между ИИ и пользователем. В качестве аргументов он принимает ai_content (ответ от ИИ) и user_text (введенный пользователем текст).
Сначала функция проверяет, отсутствует ли
ai_contentв спискеst.session_state.generated. Если это не так, пользовательский ввод и созданный ИИ контент добавляются в спискиst.session_state.pastиst.session_state.generatedсоответственно.Если в списке
st.session_state.generatedесть сообщения, функция будет перебирать список и отображать сообщения пользователя, за которыми следуют ответы, сгенерированные ИИ, с использованием функцииmessage.
def show_gpt_conversation() -> None: try: completion = create_gpt_completion(st.session_state.model, st.session_state.messages) ai_content = completion.get("choices")[0].get("message").get("content") st.session_state.messages.append({"role": "assistant", "content": ai_content}) if ai_content: show_chat(ai_content, st.session_state.user_text) st.divider() show_audio_player(ai_content) except InvalidRequestError as err: if err.code == "context_length_exceeded": st.session_state.messages.pop(1) if len(st.session_state.messages) == 1: st.session_state.user_text = "" show_conversation() else: st.error(err) except (OpenAIError, UnboundLocalError) as err: st.error(err)
Функция show_gpt_conversation управляет потоком генерации ответа ИИ и отображения разговора пользователю.
Сначала пытаемся вызвать функцию
create_gpt_completion, чтобы сгенерировать ответ ИИ с использованием модели GPT и пользовательского ввода.Затем ответ ИИ (
ai_content) добавляется в списокst.session_state.messages.Если контент, сгенерированный ИИ, не пустой, функция вызывает функцию
show_chat, чтобы отобразить сообщения разговора и обработать любые ошибки, если таковые имеются, с помощью блоков try-except.
def show_conversation() -> None: if st.session_state.messages: st.session_state.messages.append({"role": "user", "content": st.session_state.user_text}) else: ai_role = f"{st.session_state.locale.ai_role_prefix} {st.session_state.role}. {st.session_state.locale.ai_role_postfix}" # NOQA: E501 st.session_state.messages = [ {"role": "system", "content": ai_role}, {"role": "user", "content": st.session_state.user_text}, ] show_gpt_conversation()
Функция show_conversation отвечает за управление состоянием беседы и обновление списка сообщений.
Если в
st.session_state.messagesесть сообщения, ввод пользователя (st.session_state.user_text) добавляется к списку.Если сообщений нет, создается вводное сообщение ИИ с
ai_roleи добавляется в список, после чего следует ввод данных пользователем.Функция
show_gpt_conversationвызывается для обработки потока разговора и генерации ответов ИИ.
Разделение кода на эти функции позволяет легко настраивать поток общения между пользователем и ИИ и управлять им. Вспомогательные функции упрощают код, облегчая его чтение и поддержку.
3. Как конвертировать текст в речь (TTS)
В функции show_gpt_conversation можно заметить вызов функции show_audio_player, но что там под капотом?
Давайте посмотрим.
from io import BytesIO from gtts import gTTS, gTTSError def show_audio_player(ai_content: str) -> None: sound_file = BytesIO() try: tts = gTTS(text=ai_content, lang=st.session_state.locale.lang_code) tts.write_to_fp(sound_file) st.write(st.session_state.locale.stt_placeholder) st.audio(sound_file) except gTTSError as err: st.error(err)
Начнем с необходимых импортов:
BytesIOиз модуляioи предоставляет способ чтения и записи из байтового буфера.gTTSиgTTSErrorиз библиотеки преобразования текста в речь, которая, внезапно, преобразует текст в речь.
Теперь давайте посмотрим на функцию show_audio_player :
Функция show_audio_player принимает параметр ai_content, который представляет собой текст, который будет отображаться и воспроизводиться как звук. При запуске функции, создав объект BytesIO. Этот объект будет хранить аудиоданные в памяти, что упростит воспроизведение аудио позже.
Далее будем использовать блок try для обработки любых возможных ошибок при преобразовании текста в аудио. Внутри блока try создаём экземпляр объекта gTTS с заданным текстом и языком. ДалееgTTS преобразует входной текст на заданном языке в речь. Дальше происходит запись аудиоданных в буфер sound_file.
Наконец, воспроизведится преобразование текста в речь, используя метод st.audio Streamlit. Если во время процесса возникают какие-либо ошибки, легко отлавливаем их и выводим информацию об исключении с помощью метода st.error.
Функция show_audio_player готова. Она принимает строку в качестве входных данных, создает аудиофайл из текста, а затем воспроизводит его в приложении Streamlit.
4. Как сделать локализацию
Если есть необходимость или желание сделать локализацию для нескольких языков - это легко сделать с помощью примера кода ниже:
Код локализации ⤵️
from dataclasses import dataclass from typing import List @dataclass class Locale: ai_role_options: List[str] ai_role_prefix: str ai_role_postfix: str title: str language: str lang_code: str chat_placeholder: str chat_run_btn: str chat_clear_btn: str chat_save_btn: str select_placeholder1: str select_placeholder2: str select_placeholder3: str radio_placeholder: str radio_text1: str radio_text2: str stt_placeholder: str AI_ROLE_OPTIONS_EN = [ "helpful assistant", "code assistant", "code reviewer", "text improver", "cinema expert", "sports expert", ] AI_ROLE_OPTIONS_RU = [ "ассистент, который готов помочь", "ассистент программиста", "рецензент кода программиста", "эксперт по улучшению текста", "эксперт по кинематографу", "эксперт в области спорта", ] en = Locale( ai_role_options=AI_ROLE_OPTIONS_EN, ai_role_prefix="You are a female", ai_role_postfix="Answer as concisely as possible.", title="AI Talks", language="English", lang_code="en", chat_placeholder="Start Your Conversation With AI:", chat_run_btn="Ask", chat_clear_btn="Clear", chat_save_btn="Save", select_placeholder1="Select Model", select_placeholder2="Select Role", select_placeholder3="Create Role", radio_placeholder="Role Interaction", radio_text1="Select", radio_text2="Create", stt_placeholder="To Hear The Voice Of AI Press Play", ) ru = Locale( ai_role_options=AI_ROLE_OPTIONS_RU, ai_role_prefix="Вы девушка", ai_role_postfix="Отвечай максимально лаконично.", title="Разговорчики с ИИ", language="Russian", lang_code="ru", chat_placeholder="Начните Вашу Беседу с ИИ:", chat_run_btn="Спросить", chat_clear_btn="Очистить", chat_save_btn="Сохранить", select_placeholder1="Выберите Модель", select_placeholder2="Выберите Роль", select_placeholder3="Создайте Роль", radio_placeholder="Взаимодествие с Ролью", radio_text1="Выбрать", radio_text2="Создать", stt_placeholder="Чтобы Услышать ИИ Нажми Кнопку Проигрывателя", )
В этом ⤴️ коде показано, как создать простую систему локализации приложения с двумя вариантами языка — Английским и Русским. Основными компонентами кода являются:
Импортируются необходимые модули:
dataclassesиспользуются для создания структур классов данных, аtypingиспользуется для тайп-хинтов.Создаётся родительский класс данных
Locale, содержащий общий атрибутai_role_optionsдля списка возможных ролей ИИ для всех поддерживаемых языков.Определяется два дочерних класса данных,
EnLocaleиRuLocale, которые наследуются отLocaleи предоставляют фактические переводы для каждого фрагмента статического текста в приложении. Английские переводы предоставляются вEnLocale, а русские переводы — вRuLocale.Назначаются предопределённые роли ИИ для каждого языка с помощью
AI_ROLE_OPTIONS_ENиAI_ROLE_OPTIONS_RU- это нам пригодится позже.Создаются экземпляры каждого дочернего класса данных,
enдля английского языка иruдля русского языка, с соответствующими списками ролей ИИ.
При реализации локализации в приложении можно использовать соответствующий экземпляр (en или ru) в зависимости от выбранного языка, чтобы отображались правильные переводы для всех меток, сообщений и другого текста. Используя этот пример, можно легко выполнить локализацию для своего языка или даже для нескольких языков.
5. Как объединить все это в приложении Streamlit
Это просто, давайте посмотрим на код. Создать основную логику приложения можно следующим образом:
Код приложения ⤵️
from streamlit_option_menu import option_menu from src.utils.lang import en, ru from src.utils.conversation import show_chat_buttons, show_text_input, show_conversation import streamlit as st # GENERAL SETTINGS --- PAGE_TITLE: str = "AI Talks" PAGE_ICON: str = "🤖" LANG_EN: str = "En" LANG_RU: str = "Ru" AI_MODEL_OPTIONS: list[str] = [ "gpt-3.5-turbo", "gpt-4", "gpt-4-32k", ] st.set_page_config(page_title=PAGE_TITLE, page_icon=PAGE_ICON) selected_lang = option_menu( menu_title=None, options=[LANG_EN, LANG_RU, ], icons=["globe2", "translate"], menu_icon="cast", default_index=0, orientation="horizontal", ) # Storing The Context if "locale" not in st.session_state: st.session_state.locale = en if "generated" not in st.session_state: st.session_state.generated = [] if "past" not in st.session_state: st.session_state.past = [] if "messages" not in st.session_state: st.session_state.messages = [] if "user_text" not in st.session_state: st.session_state.user_text = "" def main() -> None: c1, c2 = st.columns(2) with c1, c2: c1.selectbox(label=st.session_state.locale.select_placeholder1, key="model", options=AI_MODEL_OPTIONS) role_kind = c1.radio( label=st.session_state.locale.radio_placeholder, options=(st.session_state.locale.radio_text1, st.session_state.locale.radio_text2), horizontal=True, ) match role_kind: case st.session_state.locale.radio_text1: c2.selectbox(label=st.session_state.locale.select_placeholder2, key="role", options=st.session_state.locale.ai_role_options) case st.session_state.locale.radio_text2: c2.text_input(label=st.session_state.locale.select_placeholder3, key="role") if st.session_state.user_text: show_conversation() st.session_state.user_text = "" show_text_input() show_chat_buttons() if __name__ == "__main__": match selected_lang: case "En": st.session_state.locale = en case "Ru": st.session_state.locale = ru case _: st.session_state.locale = en st.markdown(f"<h1 style='text-align: center;'>{st.session_state.locale.title}</h1>", unsafe_allow_html=True) main()
Этот код настраивает наше приложение с интерфейсом чата для взаимодействия с различными моделями ИИ.
Импортирует необходимые библиотеки и модули.
Определяет общие настройки, такие как заголовок страницы, иконку для страницы браузера и параметры языка.
Настраивает конфигурацию страницы Streamlit с указанными параметрами.
Создаёт горизонтальное меню выбора языка (английский или русский).
Инициализирует значения состояния сеанса для хранения контекста разговора и пользовательского ввода.
Определяет функцию
main, которая содержит следующие элементы:Выбор модели ИИ и переключения между типами ролей, а также выбор предопределённых или создание собственных ролей.
Отображение истории разговоров с
show_conversation, если пользователь ввел текст.Отображение поля ввода для пользователя, чтобы ввести свое сообщение с помощью
show_text_input.Отображение ряда кнопок чата с помощью
show_chat_buttons, чтобы пользователи могли управлять чатом и отправлять сообщения.
Выполняет основную функцию
mainи отображает выбранный язык и название приложения. Отображение компонентов интерфейса чата на веб-странице.
Демо:

Подведение итогов
Спасибо за уделённое время, надеюсь моя статья была полезна! Теперь Вы можете создать своего ИИ-помощника или использовать AI Talks из любой страны без регистрации и СМС. VPN тоже не нужен.
На момент написания поста доступны модели gpt-3.5-turbo и gpt-4, но не удивляйтесь, если gpt-4 будет отключен в продакшене из‑за высокой нагрузки.
Если у вас есть вопросы, задавайте их в комментариях ниже или в Streamlit Discord app-sharing-gallery. Также можно задавать вопросы в Telegram в AI Talks Chat или смотреть за обновлениями приложения на канале AI Talks.
Блог пост на английском можно почитать на веб-сайте Streamlit. Можно посмотреть видео в TikTok. Репозиторий с кодом ожидающий ваших issue, pr и звёзд 🙂. Успехов! 🎈
