Newer
Older
navi-1 / docs / eval_system.md

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.

# Оценить все неоценённые сессии
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):

-- Пользовательский 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-система полностью обособлена.