Skip to content

About

Scores AI agent answers against declarative rubrics. FastAPI, PostgreSQL, Docker, 34 tests in CI.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

agent-grader

Сервис оценки ответов AI-агентов по декларативным критериям. Задача описывается промптом и rubric — списком проверок; агент присылает ответ; сервис возвращает вердикт с разбором каждой проверки и копит статистику по задаче.

FastAPI · PostgreSQL · SQLAlchemy 2 (async) · pytest · Docker · GitHub Actions

Зачем

Когда агента гоняют по набору задач, обычно ломается не модель, а учёт вокруг неё: ретрай по таймауту записывается как вторая попытка и портит pass rate; кривой критерий выглядит как «агент не справился»; p95 считается интерполяцией и показывает задержку, которой никто не наблюдал. Этот сервис написан вокруг таких мест, а не вокруг счастливого пути.

Три решения, которые стоит заметить

Присланное не исполняется. Проверки декларативные — contains, not_contains, regex, json_equals, numeric_range, max_latency_ms. Это ограничивает выразительность, зато харнесс оценки не превращается в способ выполнить произвольный код у себя.

Rubric проверяется при создании задачи. Сломанный критерий отдаёт 422 с причиной автору задачи, а не всплывает через неделю как «агент не прошёл».

Повторы схлопываются по idempotency_key. Тот же ключ в пределах задачи возвращает исходный результат и 200 вместо 201, а уникальный индекс в базе удерживает это и при одновременных ретраях. Тест на четыре параллельных запроса с одним ключом нашёл в этом месте настоящую ошибку — она разобрана в комментарии в app/main.py.

API

Метод Путь Что делает
GET /health проверка живости
POST /tasks создать задачу с rubric (валидируется сразу)
GET /tasks список задач
POST /tasks/{slug}/submissions оценить ответ агента
GET /tasks/{slug}/stats pass rate, средний score, p95 задержки, разбивка провалов

Интерактивная схема — /docs.

Пример

curl -X POST localhost:8000/tasks -H 'content-type: application/json' -d '{
  "slug": "json-status",
  "prompt": "Верни {\"status\": \"done\"}",
  "rubric": [
    {"kind": "json_equals", "path": "status", "value": "done"},
    {"kind": "not_contains", "value": "api_key", "weight": 3},
    {"kind": "max_latency_ms", "value": 500}
  ]
}'

curl -X POST localhost:8000/tasks/json-status/submissions \
  -H 'content-type: application/json' \
  -d '{"agent": "claude", "output": "{\"status\": \"done\"}", "latency_ms": 120, "idempotency_key": "run-1"}'

Ответ содержит passed (прошли ли все проверки), score (доля веса пройденных — чтобы отличать «мимо целиком» от «почти верно») и checks с объяснением по каждой.

Запуск

docker compose up --build      # api на :8000, postgres на :5432

Локально без Docker:

pip install -r requirements-dev.txt
DATABASE_URL="postgresql+asyncpg://grader:grader@localhost:5432/grader" \
  uvicorn app.main:app --reload

Тесты

pytest                                   # SQLite, быстро
DATABASE_URL=postgresql+asyncpg://... pytest   # как в CI

34 теста: движок оценки отдельно, HTTP-слой через ASGI-транспорт целиком. CI гоняет их на PostgreSQL — том же движке, что в проде, потому что расхождения между базами SQLite-прогон не ловит, — плюс собирает образ и поднимает docker compose, проверяя /health живым запросом.

Что здесь сознательно не сделано

  • Нет Alembic. Схема создаётся из моделей. Для сервиса такого размера это разумный размен; как только схема начнёт меняться на живых данных, размен станет неверным.
  • Нет аутентификации. Сервис задуман как внутренний. Ставить его наружу без слоя авторизации нельзя.
  • Нет очереди. Оценка синхронная и быстрая, потому что не исполняет код. Появятся долгие проверки — понадобится воркер, и это уже другой проект.

About

Scores AI agent answers against declarative rubrics. FastAPI, PostgreSQL, Docker, 34 tests in CI.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages