Привет, Хабр! ???? Меня зовут Дима Косаревский, я инженер данных (DE), увлеченный Data Science и всем, что связано с этим направлением. Data Science позволяет извлекать ценную информацию из огромных объемов данных при помощи статистических и вычислительных методов.

В последнее время эти ИИ, вроде ChatGPT, врываются прямо во все сферы. И вот благодаря увлечению Data Science можно использовать этих ботов, чтобы помогать людям, да ещё и пообщаться с ними на разные темы. ChatGPT действительно впечатляет. Он не только общается на разные темы, но еще и стихи сочинять умеет. Здорово, правда? Вот один из примеров, которые я получил недавно:

Покоритель мира - ИИ,

ChatGPT сулит грани новой жизни.

Работу заберёт у многих,

Страх испытавших горечь пламени.

В ответственности вложена сила,

Судьбы людей на блюдечке держа.

ИИ слава без предела,

В бесконечности вразумляя нас.

AI can do my job
AI can do my job

Сайт ChatGPT иногда работает медленно, и для доступа к нему в некоторых странах, например в России, требуется VPN. Некоторые из моих друзей хотят протестировать ИИ без СМС и регистрации. Я решил эту проблему с помощью официального API ChatGPT и Streamlit. Давайте посмотрим как это сделать?

ИИ-ассистент в 5 шагов

Из этого туториала можно узнать:

  1. Как использовать ChatGPT API

  2. Как отобразить диалог с ботом в чате

  3. Как преобразовать текст в речь (TTS)

  4. Как сделать локализацию

  5. Как собрать все вместе в приложении 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 (введенный пользователем текст).

  1. Сначала функция проверяет, отсутствует ли ai_content в списке st.session_state.generated. Если это не так, пользовательский ввод и созданный ИИ контент добавляются в списки st.session_state.past и st.session_state.generated соответственно.

  2. Если в списке 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 управляет потоком генерации ответа ИИ и отображения разговора пользователю.

  1. Сначала пытаемся вызвать функцию create_gpt_completion, чтобы сгенерировать ответ ИИ с использованием модели GPT и пользовательского ввода.

  2. Затем ответ ИИ (ai_content) добавляется в список st.session_state.messages .

  3. Если контент, сгенерированный ИИ, не пустой, функция вызывает функцию 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 отвечает за управление состоянием беседы и обновление списка сообщений.

  1. Если в st.session_state.messages есть сообщения, ввод пользователя (st.session_state.user_text) добавляется к списку.

  2. Если сообщений нет, создается вводное сообщение ИИ с ai_role и добавляется в список, после чего следует ввод данных пользователем.

  3. Функция 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="Чтобы Услышать ИИ Нажми Кнопку Проигрывателя",
)

В этом ⤴️ коде показано, как создать простую систему локализации приложения с двумя вариантами языка — Английским и Русским. Основными компонентами кода являются:

  1. Импортируются необходимые модули: dataclasses используются для создания структур классов данных, а typing используется для тайп-хинтов.

  2. Создаётся родительский класс данных Locale, содержащий общий атрибут ai_role_options для списка возможных ролей ИИ для всех поддерживаемых языков.

  3. Определяется два дочерних класса данных, EnLocale и RuLocale, которые наследуются от Locale и предоставляют фактические переводы для каждого фрагмента статического текста в приложении. Английские переводы предоставляются в EnLocale, а русские переводы — в RuLocale.

  4. Назначаются предопределённые роли ИИ для каждого языка с помощью AI_ROLE_OPTIONS_EN и AI_ROLE_OPTIONS_RU - это нам пригодится позже.

  5. Создаются экземпляры каждого дочернего класса данных, 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()

Этот код настраивает наше приложение с интерфейсом чата для взаимодействия с различными моделями ИИ.

  1. Импортирует необходимые библиотеки и модули.

  2. Определяет общие настройки, такие как заголовок страницы, иконку для страницы браузера и параметры языка.

  3. Настраивает конфигурацию страницы Streamlit с указанными параметрами.

  4. Создаёт горизонтальное меню выбора языка (английский или русский).

  5. Инициализирует значения состояния сеанса для хранения контекста разговора и пользовательского ввода.

  6. Определяет функцию main, которая содержит следующие элементы:

    • Выбор модели ИИ и переключения между типами ролей, а также выбор предопределённых или создание собственных ролей.

    • Отображение истории разговоров с show_conversation, если пользователь ввел текст.

    • Отображение поля ввода для пользователя, чтобы ввести свое сообщение с помощью show_text_input.

    • Отображение ряда кнопок чата с помощью show_chat_buttons, чтобы пользователи могли управлять чатом и отправлять сообщения.

  7. Выполняет основную функцию main и отображает выбранный язык и название приложения. Отображение компонентов интерфейса чата на веб-странице.

Демо:

AI Talks
AI Talks

Подведение итогов

Спасибо за уделённое время, надеюсь моя статья была полезна! Теперь Вы можете создать своего ИИ-помощника или использовать 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)


  1. devlind
    19.04.2023 09:19
    +2

    Спасибо за статью! Как раз думал на днях разобраться как решить именно такую задачу, а тут за меня уже всё сделали, останется только повторить. А можно как-то скармливать картинки чату таким образом?


    1. dKosarevsky Автор
      19.04.2023 09:19

      В текущей реализации картинки скармливать нельзя, но есть идеи по улучшению функционала, возможно и такое прикручу. Если будет желание - напишите и закидывайте PR на гитхаб =)


  1. nitro80
    19.04.2023 09:19

     использовать AI Talks из любой страны без регистрации и СМС. VPN тоже не нужен.

    Не получается, всё время ошибки... Похоже, слишком большая нагрузка


    1. dKosarevsky Автор
      19.04.2023 09:19

      А какие именно ошибки?


    1. dKosarevsky Автор
      19.04.2023 09:19
      +1

      Вижу, что не вывозит гугловый API для tts, там действительно 429 - Too many requests, а в остальном пока держимся))


  1. alefair
    19.04.2023 09:19

    У меня 4 чат не доступен, не смотря, на то, что купил аккаунт плюс (((

    А вообще, сделал тоже самое в качестве эксперимента 2 недели назад. Тот же стримлит, тот же чат, тот же апи. Вот только 4 чат недоступен... ????


    1. dKosarevsky Автор
      19.04.2023 09:19

      А заявку в вэйтлист подавали? https://openai.com/waitlist/gpt-4-api


      1. alefair
        19.04.2023 09:19

        Да, конечно. Но ещё не пришло((