Дата: 5 октября 2025 г.
Оригинальный файл app.py был модифицирован для поддержки BYOK (Bring Your Own API Key). Теперь при каждом запуске приложения пользователь должен ввести свой OpenAI API ключ.
Изменено:
def __init__(self, api_key: Optional[str] = None):Что делает:
- Теперь принимает
api_keyкак опциональный параметр - Если ключ передан - использует его
- Если нет - пытается взять из
.envфайла (обратная совместимость)
Преимущества:
- Динамическое использование API ключа
- Обратная совместимость с
.env - Гибкость для разных сценариев использования
Добавлена новая функция:
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
Изменено:
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 создаётся динамически для каждой сессии.
Добавлен новый блок:
# Если 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()блокирует дальнейшее выполнение
Изменено:
# Инициализация 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динамически (не кэшируется) - Проверяет успешность инициализации
- Предлагает сбросить ключ при ошибке
Добавлено в 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 - Перезагружает страницу, возвращая к форме ввода
Запуск: 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() - перезагрузка страницы
Перезагрузка страницы
↓
init_session_state()
↓
st.session_state.api_key уже есть ("sk-...")
↓
Проверка: if st.session_state.api_key
↓
chat_manager = get_chat_manager()
↓
ChatManager(api_key="sk-...")
↓
Приложение работает нормально
Нажатие "🗑️ Очистить ключ"
↓
st.session_state.api_key = ""
↓
st.rerun()
↓
Возврат к форме ввода ключа
-
Хранение в памяти
- API ключ хранится только в
st.session_state - НЕ записывается в файлы
- НЕ сохраняется в базе данных
- НЕ логируется
- API ключ хранится только в
-
Скрытие при вводе
- Поле ввода имеет тип
password - Символы отображаются как
•••
- Поле ввода имеет тип
-
Автоматическое удаление
- Ключ удаляется при закрытии браузера
- Ключ удаляется при обновлении страницы (F5)
- Можно очистить вручную кнопкой
-
Обратная совместимость
- Если ключ есть в
.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()
# Работа с приложениемПреимущества:
- ✅ Ключ вводится при каждом запуске
- ✅ Хранится только в памяти
- ✅ Каждый пользователь использует свой ключ
- ✅ Легко деплоить
- ✅ Безопасность
cd "e:/My_Projects/Ai assistant/AI Tutor"
streamlit run app.py- Откроется форма ввода
- Вставьте ваш OpenAI API ключ (начинается с
sk-) - Нажмите "💾 Сохранить и начать работу"
- Появится уведомление: "✅ Ключ сохранён"
- Используйте все функции как обычно
- В sidebar отображается: "✅ API ключ активен"
- Для смены ключа нажмите "🗑️ Очистить ключ"
- Просто закройте вкладку браузера
- Ключ автоматически удалится из памяти
- При следующем запуске нужно ввести ключ заново
-
src/chat_manager.py- Добавлен параметр
api_keyв__init__() - Изменён
_create_llm()для использованияself.api_key
- Добавлен параметр
-
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Сохранена обратная совместимость:
- Если передан
api_keyвChatManager()- используется он - Если не передан - берётся из
.env(как раньше)
# Работает с BYOK
manager = ChatManager(api_key="sk-...")
# Работает с .env (обратная совместимость)
manager = ChatManager() # Берёт из .envУбран декоратор @st.cache_resource:
ChatManagerсоздаётся для каждой сессии- Это необходимо для динамического API ключа
- Производительность: незначительное влияние (инициализация быстрая)
При обновлении страницы (F5):
st.session_stateсбрасывается- Ключ теряется
- Нужно ввести заново
Это сделано намеренно для безопасности!
-
Изменённых файлов: 2
src/chat_manager.pyapp.py
-
Добавлено строк: ~80
- Форма ввода ключа: ~50 строк
- Инициализация состояния: ~10 строк
- Функция
get_chat_manager(): ~10 строк - Кнопка очистки: ~10 строк
-
Изменено строк: ~15
ChatManager.__init__()ChatManager._create_llm()
- Показывается форма ввода ключа
- Поле ввода скрывает символы (type="password")
- Кнопка "Сохранить" работает
- После сохранения - перезагрузка страницы
- Уведомление "✅ Ключ сохранён"
- ChatManager инициализируется с ключом
- Все функции чата работают
- В sidebar отображается "✅ API ключ активен"
- Можно создавать новые чаты
- Можно отправлять сообщения
- Кнопка "🗑️ Очистить ключ" работает
- Ключ удаляется из
session_state - Возврат к форме ввода
- Можно ввести новый ключ
- При F5 ключ теряется
- Показывается форма ввода
- История чатов сохраняется (в storage)
- BYOK функционал - ключ вводится при каждом запуске
- Безопасность - ключ хранится только в памяти
- Обратная совместимость -
.envвсё ещё работает - Простота - минимальные изменения в коде
- UX - понятная форма ввода с инструкцией
Приложение полностью готово к работе:
streamlit run app.pyПри каждом запуске будет запрашиваться API ключ! ✅
Версия: 2.0.0 (с BYOK)
Дата: 5 октября 2025 г.
Статус: ✅ Полностью готово и протестировано