Spaces:
Runtime error
Runtime error
| title: ExecChat | |
| emoji: 📊 | |
| colorFrom: red | |
| colorTo: yellow | |
| sdk: docker | |
| pinned: false | |
| # ExecChat — генератор ответов поддержки домовят | |
| Веб-приложение для помощи операторам поддержки сервиса уборки «Домовёнок»: по вопросу исполнителя (домовёнка) строится ответ на основе базы знаний и при необходимости вызываются инструменты (доходы, качество, штрафы). | |
| --- | |
| ## Как устроена система | |
| 1. **База знаний** — текст подгружается из Google Doc, режется на чанки (`CHUNK_SIZE=800`, перекрытие `CHUNK_OVERLAP=200`, `RecursiveCharacterTextSplitter`) и индексируется в FAISS (эмбеддинги OpenAI). При первом запуске, изменении документа или версии индекса (`INDEX_VERSION`) индекс пересобирается; хэш документа хранится в `doc_hash.pkl`. | |
| 2. **System prompt** — загружается из отдельного Google Doc (редактирование только там; версии — через историю документа). На странице — read-only просмотр и кнопка «Обновить промпт из документа». При `ENABLE_TOOLS=1` к промпту из документа дописывается блок про инструменты из кода. | |
| 3. **Классификатор social-intent** (`gpt-4.1-nano`) — чистые вежливые реплики (спасибо/привет/прощание/комплимент) получают короткий вежливый ответ и **не** уходят в эскалацию и RAG. | |
| 4. **Переписывание запроса** (`QUERY_REWRITE_MODEL`, по умолчанию `gpt-4.1-mini`) — шумное сообщение исполнителя превращается в чистый короткий поисковый запрос (опечатки, местоимения, учёт последних реплик). Качество rerank сильно зависит от этого шага. | |
| 5. **Retrieval + rerank** — FAISS отдаёт `RETRIEVAL_CANDIDATES=30` кандидатов, затем cross-encoder **`BAAI/bge-reranker-v2-m3`** переранжирует их; в контекст идут чанки из топ-`RERANK_TOP_N=8` с rerank-скором ≥ `RERANK_SCORE_THRESHOLD=0.1`. Чанки обрезаются до `MAX_CHUNK_CHARS=800`. Если порог не прошёл ни один чанк — бот **не эскалирует**, а вежливо отвечает, что вопрос вне темы сервиса (без вызова менеджера); эскалация остаётся только при сбое генерации или при явном провале guardrails (`ENABLE_OUTPUT_GUARDRAILS=1`). | |
| 6. **Ответ генерирует OpenAI GPT-4.1 mini** (`LLM_MODEL`, по умолчанию `gpt-4.1-mini`): system prompt + контекст из RAG + история диалога (последние 3 пары) + текущий вопрос. `max_tokens=512`. **Temperature** настраивается на странице (кнопка «Применить temperature»). | |
| 7. **Guardrails на выходе** — детерминированный `enforce_format` (≤2 абзацев, срез «приглашений задавать вопросы»). Проверка **grounding** (цельный ответ vs контекст, `gpt-4.1-nano`) и корректирующая регенерация **выключены по умолчанию** (`ENABLE_OUTPUT_GUARDRAILS=0`): давали ложные NOT_GROUNDED на корректных перефразах. При повторении галлюцинаций имеет смысл вернуть проверку в виде **claim-level** (по отдельным утверждениям), а не одного вердикта на весь ответ — тогда можно включить `ENABLE_OUTPUT_GUARDRAILS=1`. Модерация токсичности есть в коде, но по умолчанию выключена (`ENABLE_OUTPUT_MODERATION=False`). | |
| 8. **Инструменты (tools)** — при `ENABLE_TOOLS=1` модель может вызывать функции из `tools.py`. В `app_2.py` реализован цикл: запрос к модели → при наличии `tool_calls` выполняются инструменты → результаты передаются обратно в модель → ответ без вызовов возвращается пользователю. | |
| 9. **Многоходовый диалог** — в контекст ответа передаётся **весь диалог**, который был до сих пор (полная история сессии). Переписывание запроса при этом по-прежнему смотрит только на последние `MAX_HISTORY_TURNS=3` пары. | |
| > **Пометка для prod (coconut):** сейчас история берётся из `st.session_state` (одна Streamlit-сессия = один диалог). При реализации в **coconut** контекст нужно передавать **в рамках `dialog_id`** — собирать все сообщения конкретного диалога по его идентификатору, а не полагаться на сессию. Для длинных историй стоит делать суммаризацию старых сообщений, чтобы не упираться в контекст/стоимость. | |
| --- | |
| ## Структура проекта | |
| | Файл / папка | Назначение | | |
| |--------------|------------| | |
| | **`app_2.py`** | Точка входа: Streamlit-интерфейс, загрузка Google Doc, построение/загрузка FAISS, GPT-4.1 mini с инструментами, RAG, цикл вызова инструментов и формирование ответа. | | |
| | **`tools.py`** | Определения инструментов для LLM (декоратор `@tool` из LangChain). Сюда добавлять новые инструменты и подключать реальные API/БД. | | |
| | **`evals/`** | Встроенный харнесс проверок: прогон сценариев против **живого** промпта и базы знаний (`runtime_live.py`), детерминированные проверки (`checks.py`), LLM-судья (`judge.py`), чтение/запись сценариев в Google Sheet (`sheets.py`), стартовый набор (`scenarios_seed.yaml`). | | |
| | **`requirements.txt`** | Зависимости Python (Streamlit, LangChain, OpenAI, FAISS, Google API и т.д.). | | |
| | **`faiss_index/`** | Локальный индекс FAISS (создаётся при первом запуске). | | |
| | **`doc_hash.pkl`** | Хэш документа базы знаний | | |
| | **`prompt_hash.pkl`** | Хэш документа с system prompt | | |
| --- | |
| ## Инструменты (tools) | |
| Все инструменты объявлены в **`tools.py`** и возвращаются функцией **`get_all_tools()`**. | |
| - **`get_exec_next_orders`** — детали ближайших заказов исполнителя (`executive_id`). | |
| - **`get_exec_quality_params`** — параметры качества работы исполнителя (`executive_id`, опционально `period`, по умолчанию 14 дней, макс. 30). | |
| - **`get_exec_revenue_and_fines_feed`** — лента доходов и штрафов исполнителя (`executive_id`, опционально `period`, по умолчанию 7 дней, макс. 10). | |
| - **`get_exec_orders_history`** — история выполненных заказов исполнителя (`executive_id`, опционально `period`, по умолчанию 7 дней, макс. 10). | |
| Сейчас внутри — заглушки с текстом «Подключите ваш API». Чтобы подключить реальные данные: в каждой функции замените тело на вызов вашего API или БД и возвращайте строку с результатом (она попадёт в контекст модели). | |
| **Как добавить новый инструмент:** | |
| 1. В `tools.py` описать функцию с типами аргументов и **docstring** (по нему модель понимает, когда инструмент вызывать). | |
| 2. Обернуть функцию в `@tool` из `langchain_core.tools`. | |
| 3. Добавить её в список возврата функции `get_all_tools()`. | |
| Изменения в `app_2.py` не требуются — список инструментов подхватывается при инициализации. | |
| --- | |
| ## Проверка сценариев (evals) | |
| На странице есть скрытая admin-панель **«🧪 Проверка сценариев»**, которая прогоняет наборы тест-сценариев против **текущего** (живого) промпта и базы знаний — удобно после правок промпта/документа убедиться, что ничего не сломалось. Сценарии и их ожидания (`expect`) хранятся в Google-таблице, поэтому их можно редактировать прямо из UI, не трогая код. | |
| **Что умеет панель:** | |
| - **Прогон** — выбрать сценарии, запустить, увидеть таблицу PASS/FAIL с детализацией каждой проверки и ответом бота. При `ENABLE_TOOLS=1` тул-сценарии используют mock-инструменты с реальными именами (реальные API не вызываются). | |
| - **Редактор** — добавлять/редактировать/удалять сценарии и сохранять их в Google Sheet. Если таблица пустая — кнопка «Засеять стандартным набором» из `evals/scenarios_seed.yaml`. | |
| Сценарий идентифицируется по полю **`id`** (короткое уникальное имя латиницей). | |
| **Проверки (`expect`):** `escalate`, `format_ok`, `no_prompt_leak`, `tools_any`, `tools_none` (детерминированные) и `judge` — критерий на естественном языке, который оценивает LLM-судья (`JUDGE_MODEL`, по умолчанию `gpt-4o-mini`). Сценарий проходит, если прошли все заполненные проверки. `format_ok` и `no_prompt_leak` включены принудительно во всех сценариях, поэтому в UI их нет. | |
| **Инструменты — два независимых понятия (для агентов с tools):** | |
| - **Доступ** (`use_tools`, вход) — даём ли боту инструменты в этом сценарии: `Да` / `Нет` / `Авто`. `Да` даёт доступ **независимо** от глобальной галочки прогона; `Авто` берёт значение из настроек прогона («По умолчанию давать инструменты»); `Нет` — никогда. Жёсткое условие одно: сам агент должен поддерживать инструменты (для RAG-only агентов раздел скрыт). | |
| - **Проверка** (`tools_any` / `tools_none`, ассерт) — что бот сделал по факту: должен ли был вызвать инструмент (`tools_any` — достаточно любого из перечисленных) или не должен был (`tools_none`). Чтобы проверка `tools_any` могла пройти, в сценарии должен быть разрешён доступ. | |
| > **Инструменты в харнессе завязаны на `ENABLE_TOOLS`** (тот же флаг, что и для бота). Пока `ENABLE_TOOLS=0` (значение по умолчанию на проде), инструментов «нет» и для бота, и для проверок: тул-виджеты скрыты, mock-инструменты не подставляются, бот в евалах ведёт себя как tool-less. Когда инструменты будут реализованы — поставьте `ENABLE_TOOLS=1`, и тестирование инструментов вернётся без правок кода. На время `ENABLE_TOOLS=0` тул-сценарии (с проверкой `tools_any`) будут падать — не выбирайте их в прогоне или временно удалите из таблицы. | |
| **Доступ — за паролем.** Раздел появляется только если задан секрет `EVAL_PASSWORD`. Схема простая: вы сами придумываете пароль, кладёте его в секреты Space (`EVAL_PASSWORD`) и раздаёте доверенным людям. Без правильного пароля панель не разворачивается; это не полноценная авторизация, а лёгкий барьер, чтобы случайные пользователи не запускали платные прогоны. | |
| **Настройка хранилища (Google Sheet):** | |
| 1. Создайте Google-таблицу. Первая вкладка будет использоваться целиком (шапка пишется автоматически). | |
| 2. Расшарьте её на `client_email` того же сервисного аккаунта, что и для Google Docs, с правом **Редактор**. | |
| 3. В Google Cloud проекте включите **Google Sheets API**. | |
| 4. ID таблицы (из её URL) положите в секрет `SCENARIOS_SHEET_ID`. | |
| > Сервисному аккаунту нужен scope `https://www.googleapis.com/auth/spreadsheets` — он запрашивается в коде (`evals/sheets.py`), отдельной настройки не требует, важно лишь дать аккаунту доступ к самой таблице (шаг 2) и включить API (шаг 3). | |
| --- | |
| ## Переменные окружения и секреты | |
| | Переменная | Назначение | | |
| |------------|------------| | |
| | **`OPENAI_API_KEY`** | Ключ OpenAI (эмбеддинги FAISS + генерация ответов). | | |
| | **`GOOGLE_CREDENTIALS`** | JSON сервисного аккаунта Google (доступ к Google Doc). Либо в .env, либо в секретах Streamlit/HF под ключом `google`. | | |
| | **`LLM_MODEL`** | (опционально) модель генерации ответа, по умолчанию `gpt-4.1-mini`. | | |
| | **`QUERY_REWRITE_MODEL`** | (опционально) модель переписывания запроса, по умолчанию `gpt-4.1-mini`. | | |
| | **`RERANK_MODEL`** | (опционально) cross-encoder для rerank, по умолчанию `BAAI/bge-reranker-v2-m3`. | | |
| | **`SOCIAL_INTENT_MODEL`** | (опционально) модель классификатора social-intent, по умолчанию `gpt-4.1-nano`. | | |
| | **`ENABLE_TOOLS`** | Включить инструменты (`1` или `true`). По умолчанию `0` — инструменты выключены и у бота, и в харнессе проверок (тул-виджеты скрыты, mock-инструменты не подставляются). **На Hugging Face не задавайте** (останется версия без tools). Локально для разработки с tools добавьте в .env: `ENABLE_TOOLS=1`. | | |
| | **`ENABLE_OUTPUT_GUARDRAILS`** | (опционально) LLM-проверка grounding цельного ответа + регенерация при провале. По умолчанию `0` (выключено). Включать (`1`) только после перехода на claim-level проверку — см. раздел про guardrails выше. | | |
| | **`EVAL_PASSWORD`** | Пароль для входа в admin-панель «Проверка сценариев». Без него раздел скрыт. Придумайте сами и раздайте доверенным людям. | | |
| | **`SCENARIOS_SHEET_ID`** | ID Google-таблицы, где хранятся сценарии проверок (см. раздел ниже). | | |
| | **`JUDGE_MODEL`** | (опционально) модель LLM-судьи для проверок, по умолчанию `gpt-4o-mini`. | | |
| Локально достаточно создать `.env` с этими переменными. На Hugging Face Spaces секреты задаются в настройках Space (Settings → Variables and secrets). | |
| --- | |
| ## Запуск | |
| **Локально:** | |
| ```bash | |
| pip install -r requirements.txt | |
| streamlit run app_2.py | |
| ``` | |
| Приложение откроется по адресу http://localhost:8501. При первом запуске подтягивается Google Doc и строится FAISS (1–2 минуты). | |
| **На Hugging Face:** репозиторий разворачивается как Space (Docker); переменные окружения задаются в интерфейсе Space. | |
| --- | |
| ## Конфигурация в коде | |
| В `app_2.py` в начале заданы: | |
| - **`DOCUMENT_ID`** (база знаний) — `1p9J-knk-d7gIOnzDnjN_kuejP0x09VqIG3KJGMfVxh8` | |
| - **`PROMPT_DOCUMENT_ID`** (system prompt) — `1hk1PS4hkvp8MFDe0gGIDfYNsgzJCN5gMmyjMG0pkggM` | |
| - https://docs.google.com/document/d/1hk1PS4hkvp8MFDe0gGIDfYNsgzJCN5gMmyjMG0pkggM/edit | |
| - **`FAISS_INDEX_PATH`** — каталог для сохранения индекса FAISS. | |
| - **`HASH_FILE`** / **`PROMPT_HASH_FILE`** — хэши документов для кэширования. | |
| Перед деплоем расшарьте **оба** документа на `client_email` сервисного аккаунта (доступ «Просмотр»). | |
| Кнопка «Обновить базу знаний» сбрасывает кэш FAISS. После правки промпта в Google Doc нажмите **«Обновить промпт из документа»**. Если промпт-doc пуст, используется `DEFAULT_SYSTEM_TEMPLATE` из кода. | |
| --- | |
| ## Деплой на Hugging Face | |
| 1. Задать секреты в Space Settings → Variables and secrets: `OPENAI_API_KEY`, `GOOGLE_CREDENTIALS`. Для admin-панели проверок также `EVAL_PASSWORD` и `SCENARIOS_SHEET_ID` (см. раздел «Проверка сценариев»). | |
| 2. Удалить `ANTHROPIC_API_KEY` из секретов Space (больше не используется) | |
| 3. Загрузить файлы через `hf upload` (из-за `.docx` в git history обычный `git push` может быть заблокирован): | |
| ```bash | |
| hf upload DomovenokChatbots/ExecChat app_2.py README.md requirements.txt .gitignore --commit-message "GPT-4.1 mini + token optimization" | |
| hf upload DomovenokChatbots/ExecChat evals/ evals/ --commit-message "in-app eval harness" | |
| ``` | |
| ### Стоимость (ориентир) | |
| | | Было (Sonnet 4.6) | Стало (GPT-4.1 mini) | | |
| |--|-------------------|----------------------| | |
| | Input / 1M tok | ~$3 | ~$0.40 | | |
| | Output / 1M tok | ~$15 | ~$1.60 | | |
| | Типичный запрос | ~$0.02 | ~$0.002 | | |
| --- | |
| Конфигурация Space: https://huggingface.co/docs/hub/spaces-config-reference | |