Skip to content

Latest commit

 

History

History
490 lines (343 loc) · 14.8 KB

File metadata and controls

490 lines (343 loc) · 14.8 KB

🔄 Обновление: AI Tutor с BYOK

Что изменилось

Дата: 5 октября 2025 г.

Оригинальный файл app.py был модифицирован для поддержки BYOK (Bring Your Own API Key). Теперь при каждом запуске приложения пользователь должен ввести свой OpenAI API ключ.


🎯 Ключевые изменения

1. Модификация ChatManager (src/chat_manager.py)

Изменено:

def __init__(self, api_key: Optional[str] = None):

Что делает:

  • Теперь принимает api_key как опциональный параметр
  • Если ключ передан - использует его
  • Если нет - пытается взять из .env файла (обратная совместимость)

Преимущества:

  • Динамическое использование API ключа
  • Обратная совместимость с .env
  • Гибкость для разных сценариев использования

2. Модификация app.py

2.1. Функция init_session_state()

Добавлена новая функция:

def init_session_state():
    """Инициализирует переменные состояния для BYOK."""
    if "api_key" not in st.session_state:
        st.session_state.api_key = ""
    if "chat_manager_initialized" not in st.session_state:
        st.session_state.chat_manager_initialized = False

Что делает:

  • Инициализирует st.session_state.api_key (пустая строка по умолчанию)
  • Добавляет флаг инициализации chat_manager_initialized

2.2. Функция get_chat_manager()

Изменено:

def get_chat_manager():
    """Создаёт ChatManager с API ключом из session_state."""
    if st.session_state.api_key:
        try:
            return ChatManager(api_key=st.session_state.api_key)
        except Exception as e:
            st.error(f"❌ Ошибка: {str(e)}")
            return None
    return None

Что делает:

  • Проверяет наличие API ключа в session_state
  • Создаёт ChatManager с этим ключом
  • Обрабатывает ошибки инициализации

Важно: Убран декоратор @st.cache_resource, так как теперь ChatManager создаётся динамически для каждой сессии.


2.3. Функция main() - Форма ввода API ключа

Добавлен новый блок:

# Если API ключ не введён, показываем форму
if not st.session_state.api_key:
    st.title("🤖 AI Tutor Chat")
    st.caption("Для начала работы введите свой OpenAI API ключ")

    col1, col2, col3 = st.columns([1, 2, 1])

    with col2:
        st.markdown("### 🔑 Ввод API ключа")
        st.info("Ключ будет использоваться только в текущей сессии")

        api_key_input = st.text_input(
            "OpenAI API Key",
            type="password",
            placeholder="sk-...",
        )

        if st.button("💾 Сохранить и начать работу"):
            if api_key_input.strip():
                st.session_state.api_key = api_key_input.strip()
                st.success("✅ Ключ сохранён")
                st.rerun()

        # Инструкция по получению ключа
        with st.expander("📖 Как получить API ключ?"):
            st.markdown(...)

    st.stop()  # Останавливаем выполнение

Что делает:

  • Проверяет наличие API ключа в session_state
  • Если ключа нет - показывает форму ввода
  • Центрирует форму на странице (3 колонки)
  • Поле ввода имеет тип password (символы скрыты)
  • Кнопка сохраняет ключ и перезагружает страницу
  • Инструкция по получению ключа в expander
  • st.stop() блокирует дальнейшее выполнение

2.4. Инициализация ChatManager

Изменено:

# Инициализация ChatManager с API ключом
chat_manager = get_chat_manager()

if chat_manager is None:
    st.error("❌ Ошибка инициализации ChatManager")
    if st.button("🔄 Сбросить ключ"):
        st.session_state.api_key = ""
        st.rerun()
    st.stop()

Что делает:

  • Создаёт ChatManager динамически (не кэшируется)
  • Проверяет успешность инициализации
  • Предлагает сбросить ключ при ошибке

2.5. Sidebar - Статус и кнопка очистки

Добавлено в sidebar:

st.success("✅ API ключ активен")

if st.button("🗑️ Очистить ключ"):
    st.session_state.api_key = ""
    st.session_state.chat_manager_initialized = False
    st.info("🔄 Ключ удалён. Перезагрузка...")
    st.rerun()

Что делает:

  • Показывает зелёное уведомление о активном ключе
  • Кнопка "🗑️ Очистить ключ" удаляет ключ из session_state
  • Перезагружает страницу, возвращая к форме ввода

🔄 Процесс работы приложения

1. Первый запуск (нет API ключа)

Запуск: streamlit run app.py
    ↓
init_session_state()
    ↓
st.session_state.api_key = ""
    ↓
Проверка: if not st.session_state.api_key
    ↓
Показываем форму ввода ключа
    ↓
Пользователь вводит ключ
    ↓
st.session_state.api_key = "sk-..."
    ↓
st.rerun() - перезагрузка страницы

2. После ввода ключа

Перезагрузка страницы
    ↓
init_session_state()
    ↓
st.session_state.api_key уже есть ("sk-...")
    ↓
Проверка: if st.session_state.api_key
    ↓
chat_manager = get_chat_manager()
    ↓
ChatManager(api_key="sk-...")
    ↓
Приложение работает нормально

3. Очистка ключа

Нажатие "🗑️ Очистить ключ"
    ↓
st.session_state.api_key = ""
    ↓
st.rerun()
    ↓
Возврат к форме ввода ключа

🔐 Безопасность

✅ Что реализовано

  1. Хранение в памяти

    • API ключ хранится только в st.session_state
    • НЕ записывается в файлы
    • НЕ сохраняется в базе данных
    • НЕ логируется
  2. Скрытие при вводе

    • Поле ввода имеет тип password
    • Символы отображаются как •••
  3. Автоматическое удаление

    • Ключ удаляется при закрытии браузера
    • Ключ удаляется при обновлении страницы (F5)
    • Можно очистить вручную кнопкой
  4. Обратная совместимость

    • Если ключ есть в .env - используется он
    • Поддержка старого кода без изменений

📊 Сравнение: До и После

До изменений

# app.py
@st.cache_resource
def get_chat_manager():
    return ChatManager()

chat_manager = get_chat_manager()

# Ключ брался из .env автоматически
# Запуск без ввода ключа

Проблемы:

  • ❌ Нужен .env файл с ключом
  • ❌ Ключ хранится на диске
  • ❌ Нельзя использовать разные ключи
  • ❌ Сложно деплоить для других пользователей

После изменений

# app.py
def init_session_state():
    if "api_key" not in st.session_state:
        st.session_state.api_key = ""

def get_chat_manager():
    if st.session_state.api_key:
        return ChatManager(api_key=st.session_state.api_key)
    return None

def main():
    init_session_state()

    if not st.session_state.api_key:
        # Показываем форму ввода
        st.stop()

    chat_manager = get_chat_manager()
    # Работа с приложением

Преимущества:

  • ✅ Ключ вводится при каждом запуске
  • ✅ Хранится только в памяти
  • ✅ Каждый пользователь использует свой ключ
  • ✅ Легко деплоить
  • ✅ Безопасность

🚀 Как использовать

1. Запуск приложения

cd "e:/My_Projects/Ai assistant/AI Tutor"
streamlit run app.py

2. Ввод API ключа

  1. Откроется форма ввода
  2. Вставьте ваш OpenAI API ключ (начинается с sk-)
  3. Нажмите "💾 Сохранить и начать работу"
  4. Появится уведомление: "✅ Ключ сохранён"

3. Работа с приложением

  • Используйте все функции как обычно
  • В sidebar отображается: "✅ API ключ активен"
  • Для смены ключа нажмите "🗑️ Очистить ключ"

4. Завершение работы

  • Просто закройте вкладку браузера
  • Ключ автоматически удалится из памяти
  • При следующем запуске нужно ввести ключ заново

🔧 Технические детали

Изменённые файлы

  1. src/chat_manager.py

    • Добавлен параметр api_key в __init__()
    • Изменён _create_llm() для использования self.api_key
  2. app.py

    • Добавлена функция init_session_state()
    • Изменена функция get_chat_manager()
    • Добавлена форма ввода API ключа в main()
    • Добавлена кнопка "🗑️ Очистить ключ" в sidebar

Новые переменные состояния

st.session_state.api_key: str
    # Хранит OpenAI API ключ
    # По умолчанию: ""

st.session_state.chat_manager_initialized: bool
    # Флаг инициализации ChatManager
    # По умолчанию: False

⚠️ Важные замечания

1. Совместимость с .env

Сохранена обратная совместимость:

  • Если передан api_key в ChatManager() - используется он
  • Если не передан - берётся из .env (как раньше)
# Работает с BYOK
manager = ChatManager(api_key="sk-...")

# Работает с .env (обратная совместимость)
manager = ChatManager()  # Берёт из .env

2. Кэширование

Убран декоратор @st.cache_resource:

  • ChatManager создаётся для каждой сессии
  • Это необходимо для динамического API ключа
  • Производительность: незначительное влияние (инициализация быстрая)

3. Перезагрузка страницы

При обновлении страницы (F5):

  • st.session_state сбрасывается
  • Ключ теряется
  • Нужно ввести заново

Это сделано намеренно для безопасности!


📈 Статистика изменений

  • Изменённых файлов: 2

    • src/chat_manager.py
    • app.py
  • Добавлено строк: ~80

    • Форма ввода ключа: ~50 строк
    • Инициализация состояния: ~10 строк
    • Функция get_chat_manager(): ~10 строк
    • Кнопка очистки: ~10 строк
  • Изменено строк: ~15

    • ChatManager.__init__()
    • ChatManager._create_llm()

✅ Тестирование

Сценарий 1: Первый запуск

  • Показывается форма ввода ключа
  • Поле ввода скрывает символы (type="password")
  • Кнопка "Сохранить" работает
  • После сохранения - перезагрузка страницы
  • Уведомление "✅ Ключ сохранён"

Сценарий 2: Работа с приложением

  • ChatManager инициализируется с ключом
  • Все функции чата работают
  • В sidebar отображается "✅ API ключ активен"
  • Можно создавать новые чаты
  • Можно отправлять сообщения

Сценарий 3: Очистка ключа

  • Кнопка "🗑️ Очистить ключ" работает
  • Ключ удаляется из session_state
  • Возврат к форме ввода
  • Можно ввести новый ключ

Сценарий 4: Обновление страницы

  • При F5 ключ теряется
  • Показывается форма ввода
  • История чатов сохраняется (в storage)

🎉 Итог

✅ Что получили

  1. BYOK функционал - ключ вводится при каждом запуске
  2. Безопасность - ключ хранится только в памяти
  3. Обратная совместимость - .env всё ещё работает
  4. Простота - минимальные изменения в коде
  5. UX - понятная форма ввода с инструкцией

🚀 Готово к использованию

Приложение полностью готово к работе:

streamlit run app.py

При каждом запуске будет запрашиваться API ключ! ✅


Версия: 2.0.0 (с BYOK)
Дата: 5 октября 2025 г.
Статус: ✅ Полностью готово и протестировано