Привет, Хабр! ???? Меня зовут Дима Косаревский, я инженер данных (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 и звёзд ????. Успехов! ????
Комментарии (8)
nitro80
19.04.2023 09:19использовать AI Talks из любой страны без регистрации и СМС. VPN тоже не нужен.
Не получается, всё время ошибки... Похоже, слишком большая нагрузка
dKosarevsky Автор
19.04.2023 09:19+1Вижу, что не вывозит гугловый API для tts, там действительно 429 - Too many requests, а в остальном пока держимся))
alefair
19.04.2023 09:19У меня 4 чат не доступен, не смотря, на то, что купил аккаунт плюс (((
А вообще, сделал тоже самое в качестве эксперимента 2 недели назад. Тот же стримлит, тот же чат, тот же апи. Вот только 4 чат недоступен... ????
dKosarevsky Автор
19.04.2023 09:19А заявку в вэйтлист подавали? https://openai.com/waitlist/gpt-4-api
devlind
Спасибо за статью! Как раз думал на днях разобраться как решить именно такую задачу, а тут за меня уже всё сделали, останется только повторить. А можно как-то скармливать картинки чату таким образом?
dKosarevsky Автор
В текущей реализации картинки скармливать нельзя, но есть идеи по улучшению функционала, возможно и такое прикручу. Если будет желание - напишите и закидывайте PR на гитхаб =)