ExecChat / README.md
guilsyTrue's picture
Out-of-scope reply when RAG has no context
0a9953f verified
|
Raw
History Blame Contribute Delete
20.5 kB
---
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