# Eval System — User Guide

Система оценки качества сессий Navi через LLM-as-judge. Анализирует реальные разговоры (не тестовые сценарии) по 7 осям, с усреднением по 3 независимым экспертам.

**Статус:** реализовано (фазы 1–5). UI: `/debug/eval/`. CLI: `python -m debug.eval`.

---

## Как открыть

Веб-интерфейс доступен по адресу:

```
http://<your-host>:8000/debug/eval
```

Требует PostgreSQL (`DATABASE_URL` в `.env`). Если база не настроена — система вернёт 503.

---

## Что оценивается

Судья (тот же LLM, которым пользуется Navi) читает **полную расшифровку сессии** — все сообщения пользователя, ответы ассистента, вызовы инструментов, результаты, thinking-блоки, логи планирования. Никакие сообщения не вырезаются и не заменяются сжатыми summary — судья видит реальный процесс работы.

### Сигналы, которые видит судья

1. **Полный transcript** в оригинальном порядке.
2. **Лайки/дизлайки** пользователя — встроены рядом с каждым сообщением ассистента (`👍` / `👎`).
3. **Метаданные сессии**: профиль, модель, флаги thinking-механик, длительность, количество итераций, токенов.
4. **Логи планирования** (если профиль использует planning).

---

## 7 осей оценки (Rubric v1)

| Ось | Что оценивается | Шкала |
|---|---|---|
| `task_complexity` | Сложность запроса пользователя, судится **только по вопросу**, не по ответу Navi | 10–100+ |
| `goal_completion` | Получил ли пользователь то, что хотел. Смотрим на финальный ответ и реакции | 10–100+ |
| `tool_usage_quality` | Правильно ли выбраны инструменты, нет ли лишних/повторных вызовов | 10–100+ |
| `efficiency` | Соотношение итераций к результату. Петли, тупики, переделки штрафуются | 10–100+ |
| `communication` | Ясность, честность, отсутствие галлюцинаций, лаконичность | 10–100+ |
| `subagent_orchestration` | Качество делегирования под-агентам (`spawn_agent`). **Null**, если под-агенты не использовались | 10–100+ или null |
| `self_extension` | Качество саморасширения (`write_tool`, `reload_tools`). **Null**, если инструменты не писались | 10–100+ или null |

### Якоря шкалы (фиксированы в `rubric_v1`)

- **10** — катастрофа / тривиально (зависит от оси)
- **30** — слабо / просто
- **50** — средне
- **75** — хорошо / сложно
- **100** — на пределе возможностей Navi сегодня

Шкала **открытая** — если судья видит что-то за пределами 100, он может поставить 120+. Это становится якорем для будущих версий рубрики.

---

## 3 эксперта и усреднение

Каждая сессия оценивается **3 параллельными вызовами** с разными system prompt:

| Эксперт | Наклон |
|---|---|
| `strict_critic` | Ищет ошибки, штрафует за любой промах, консервативные оценки |
| `pragmatist` | «Пользователь получил результат?» — итог важнее пути |
| `tech_lead` | Архитектура, выбор инструментов, эффективность, технические решения |

Итоговый балл по каждой оси — **среднее арифметическое** трёх экспертов. Nullable-оси (`subagent_orchestration`, `self_extension`) усредняются только по ненулевым значениям.

Разброс между экспертами тоже сохраняется — большой разброс = спорная/шумная сессия.

Если эксперт вернул невалидный JSON — делается **один retry** с корректирующим сообщением. Если и retry не удался — сессия помечается failed в этом run.

---

## Версионирование

Каждый run привязан к двум версиям:

- **`judge_version`** — версия кода судьи (prompts, логика рендеринга, retry policy)
- **`rubric_version`** — версия рубрики (оси, якоря, описания)

Когда вы меняете prompt эксперта или рубрику — **бампаете версию** в коде. Старые оценки остаются в базе, новые пишутся под новой версией.

### Статусы сессий

| Статус | Значение |
|---|---|
| `evaluated` | Есть полный run по текущим `judge_version` + `rubric_version` (все 3 эксперта) |
| `pending` | Нет ни одной оценки |
| `stale` | Есть оценки, но по старым версиям judge или rubric. Нужен re-run |

---

## Интерфейс — 4 вкладки

### 1. Sessions (список сессий)

Таблица всех сессий, новые сверху. Колонки:
- Время старта, профиль, ID, имя
- Количество сообщений
- 👍 / 👎 (суммарный feedback)
- Статус оценки (`evaluated` / `pending` / `stale`)
- Средние по `goal_completion`, `tool_usage_quality`, `communication`

Фильтры: профиль, статус, лимит строк.

Клик по строке → переход в Detail с подставленным ID.

### 2. Detail (детали сессии)

- Метаданные сессии (ID, профиль, время, сообщения, feedback)
- Все evaluation runs для этой сессии (новые сверху)
- Каждый run — таблица с оценками по 7 осям, колонка на каждого эксперта + колонка `avg`
- Комментарии экспертов под таблицей

Кнопка **"evaluate this session"** — запускает run только для этой сессии.

### 3. Stats (статистика)

Недельные средние по осям за выбранный период (7–90 дней).

Опция **"split by complexity bucket"** — разбивает по сложности задач (`0-25`, `26-50`, `51-75`, `76+`). Позволяет отловить смещение выборки: если средний балл вырос, но только потому что стали приходить простые задачи.

### 4. Run (запуск оценки)

Форма для триггера фонового run:

| Поле | Описание |
|---|---|
| `scope` | `unevaluated` — только неоценённые, `all` — всё (re-eval), `session` — одна сессия |
| `session id` | Только для `scope=session` |
| `since` | Только сессии, начатые после этой даты |
| `limit` | Макс. количество сессий |
| `model` | Модель судьи (default: `gemma4:31b-cloud`) |
| `backend` | Бэкенд LLM (default: `ollama`) |

После запуска появляется панель **Active run** с прогрессом: сколько сессий обработано, статус каждой (`pending` / `running` / `ok` / `failed`), средние баллы.

Poll каждые 2.5 секунды. По завершении run обновляется история внизу.

---

## CLI

Альтернатива UI — запуск из терминала. Не требует запущенного сервера, но требует доступа к PostgreSQL.

```bash
# Оценить все неоценённые сессии
python -m debug.eval run

# Оценить одну сессию
python -m debug.eval run --session <session_id>

# Re-evaluate всего (после смены judge/rubric)
python -m debug.eval run --re-evaluate-all

# Только сессии после даты
python -m debug.eval run --since 2026-04-01

# Сухой прогон — показать, что будет оценено, но не звать LLM
python -m debug.eval run --dry-run

# Сменить модель судьи
python -m debug.eval run --model qwen3.6:27b

# Показать оценки сессии
python -m debug.eval show <session_id>

# Статистика (Phase 4 — пока заглушка)
python -m debug.eval stats --days 30
python -m debug.eval stats --days 30 --csv
```

---

## Feedback — лайки/дизлайки

В основном чате (не в eval UI) на каждом сообщении ассистента есть 👍 / 👎. Клик отправляет:

```
POST /eval/feedback
{ "session_id": "...", "message_index": N, "rating": 1 }
```

- `rating: 1` — лайк
- `rating: -1` — дизлайк
- `rating: 0` — очистить

Индекс — позиция сообщения в `session.messages` (display history, append-only, индексы стабильны).

Судья видит эти реакции в transcript: `👍` / `👎` рядом с соответствующим сообщением ассистента.

---

## REST API

Все эндпоинты под префиксом `/eval`:

| Метод | Путь | Описание |
|---|---|---|
| POST | `/eval/feedback` | Поставить/убрать лайк или дизлайк |
| GET | `/eval/feedback/{session_id}` | Список feedback для сессии |
| GET | `/eval/sessions` | Список сессий с оценками и статусами |
| GET | `/eval/sessions/{session_id}` | Детали сессии + все evaluation runs |
| GET | `/eval/stats` | Агрегированная статистика |
| POST | `/eval/run` | Запустить фоновый run |
| GET | `/eval/run/{run_id}` | Статус конкретного run |
| GET | `/eval/runs` | История всех runs |

---

## Хранение (PostgreSQL)

Таблицы создаются лениво при первом подключении (`debug/eval/schema.sql`):

```sql
-- Пользовательский feedback
CREATE TABLE message_feedback (
    session_id      TEXT        NOT NULL,
    message_index   INTEGER     NOT NULL,
    rating          SMALLINT    NOT NULL CHECK (rating IN (-1, 1)),
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (session_id, message_index)
);

-- Оценки судьи
CREATE TABLE evaluations (
    id              UUID        PRIMARY KEY,
    session_id      TEXT        NOT NULL,
    eval_run_id     UUID        NOT NULL,        -- группирует 3 экспертов одного run
    eval_date       TIMESTAMPTZ NOT NULL DEFAULT now(),
    judge_model     TEXT        NOT NULL,
    judge_version   TEXT        NOT NULL,
    rubric_version  TEXT        NOT NULL,
    expert_id       TEXT        NOT NULL,        -- strict_critic | pragmatist | tech_lead
    scores          JSONB       NOT NULL,         -- {task_complexity: 65, ...}
    comment         TEXT        NOT NULL DEFAULT ''
);
```

SQLite **не поддерживается** для eval-системы — только PostgreSQL.

---

## Стоимость

3 эксперта × полный transcript на сессию. Для длинных сессий (50+ сообщений, 50k+ токенов контекста) — это 3 больших LLM-вызова. Рекомендуется запускать batch overnight, а не в реальном времени.

Run использует тот же `FallbackOllamaBackend`, что и основной агент — мульти-серверный fallback работает и для судьи.

---

## Известные ограничения

- Судья не верифицирует факты и не запускает код — он видит только transcript. «А этот код действительно работает?» — out of scope.
- Систематическое смещение судьи к многословным/уверенным ответам частично компенсируется 3 экспертами, но не полностью.
- Оценки **относительные** (динамика), а не абсолютные. Мы не калибруем судью вручную — это осознанное решение, так как нам важны тренды, а не точное значение «75 vs 80».
- Слишком длинные сессии могут не поместиться в контекст судьи. На данный момент run падает с ошибкой — нет chunking'а.

---

## Файлы системы

```
debug/eval/
  api.py              # FastAPI router (GET/POST эндпоинты)
  cli.py              # CLI: run, show, stats
  db.py               # asyncpg: feedback + evaluations
  judge.py            # Рендеринг сессии, 3 эксперта, усреднение
  runner.py           # Фоновый runner (async tasks)
  schema.py           # Pydantic-модели
  schema.sql          # DDL для PostgreSQL
  index.html          # SPA UI (4 вкладки)
  prompts/
    rubric_v1.yaml           # Рубрика с якорями
    expert_strict_critic.txt # System prompt строгого критика
    expert_pragmatist.txt    # System prompt прагматика
    expert_tech_lead.txt     # System prompt техлида
```

Судья и все вспомогательные функции — в `debug/eval/`. Ни одна из этих модулей не импортируется в production runtime агента; eval-система полностью обособлена.
