Skip to content

[hackathon] add nullwatch-py Python SDK to community SDKs#2

Open
Viroslav wants to merge 1 commit intonullclaw:mainfrom
Viroslav:feat/python-sdk
Open

[hackathon] add nullwatch-py Python SDK to community SDKs#2
Viroslav wants to merge 1 commit intonullclaw:mainfrom
Viroslav:feat/python-sdk

Conversation

@Viroslav
Copy link
Copy Markdown

@Viroslav Viroslav commented May 5, 2026

Контекст

Этот PR подготовлен командой Автоматическая модерация методами ИИ в рамках WB × OpenSource Hackathon по треку nullclaw.

Команда:

  • Николай Иванов
  • Дмитрий Колесников

Что добавляет этот PR

nullwatch-py — Python SDK для nullwatch с нулевыми обязательными зависимостями в ядре.

У nullwatch уже есть хороший HTTP API и CLI, но нет SDK ни для одного языка — каждая интеграция требует ручного написания raw HTTP-запросов или в CLI. Этот SDK закрывает этот gap и добавляет два eval scorer-а для реальных классов ошибок агентов.

Почему это хороший вклад

  • Первый SDK для nullwatch в любом языке;
  • ядро клиента использует только stdlib (urllib, json, dataclasses) — никаких зависимостей по умолчанию;
  • RAGHallucinationScorer закрывает конкретный observability gap: агенты с RAG теперь могут автоматически проверять, заземлён ли ответ в извлечённом контексте, и сразу отправлять результат как nullwatch eval;
  • ToolCallScorer ловит распространённый класс ошибок агентов (выдуманные названия инструментов, опечатки в аргументах, неправильные типы) и формирует структурированные evals без ML-модели;
  • оба scorer-а возвращают объекты Eval и подключаются напрямую к существующему эндпоинту /v1/evals.

Состав

1. NullwatchClient — HTTP-клиент

  • ingest_span(span) / ingest_spans(spans) — POST в /v1/spans и /v1/spans/bulk
  • ingest_eval(eval_) — POST в /v1/evals
  • list_spans(), list_evals(), list_runs(), get_run() — GET с фильтрами
  • client.span(run_id, operation, ...) — контекст-менеджер: автоматически завершает span и отправляет его, при исключении ставит status="error"
  • is_alive() — проверка доступности сервера
  • режим raise_on_error=False для fire-and-forget инструментации

2. RAGHallucinationScorer

Использует LettuceDetect (lettucedect-large-modernbert-en-v1, ModernBERT token classifier, F1=79.2% на RAGTruth) для определения, какие части ответа LLM не подтверждены извлечённым контекстом.

Возвращает nullwatch Eval с:

  • eval_key = "rag_hallucination"
  • score — степень уверенности ответа (1.0 = полностью подтверждён контекстом)
  • verdict"pass" / "fail" на основе доли «галлюцинированных» символов
  • notes — конкретные span-ы с указанием уверенности модели
  • meta — структурированный словарь для downstream-анализа

Модель загружается при первом вызове, последующие вызовы переиспользуют детектор.

3. ToolCallScorer

Схемный валидатор tool call-ов, сгенерированных LLM. ML-модель не нужна.

Ловит:

  • несуществующие названия инструментов
  • отсутствующие обязательные аргументы
  • опечатки в именах аргументов (расстояние Левенштейна ≤ 2, с подсказкой правильного варианта)
  • несоответствие типов аргументов

Поддерживает одиночные tool call-ы и батчевую оценку целого тёрна. Score = доля валидных вызовов в тёрне.

4. BaseScorer

Абстрактный базовый класс для кастомных scorer-ов. Реализуй eval_key, scorer_name и score() — и любая логика оценки подключается к nullwatch.

Что сознательно не входит в этот PR

  • async-клиент (можно добавить поверх того же API)
  • retry / backoff
  • streaming spans
  • аутентификация (не требуется текущим API nullwatch)

Использование

from nullwatch import NullwatchClient
from nullwatch.scorers import RAGHallucinationScorer, ToolCallScorer

client = NullwatchClient()

# span с автоматическим таймером
with client.span("run-123", "llm.call", model="gpt-4o") as s:
    response = call_llm(prompt)
    s.input_tokens = response.usage.prompt_tokens
    s.output_tokens = response.usage.completion_tokens

# eval на галлюцинации в RAG
scorer = RAGHallucinationScorer()
eval_ = scorer.score("run-123", contexts=docs, question=q, answer=response.text)
client.ingest_eval(eval_)

# eval на валидность tool call-ов
tool_scorer = ToolCallScorer(tools=MY_TOOLS)
client.ingest_eval(tool_scorer.score("run-123", tool_calls=response.tool_calls))

Установка

pip install nullwatch-py          # только ядро клиента
pip install "nullwatch-py[rag]"   # с детекцией галлюцинаций в RAG

Валидация

make install
make lint   # ruff — 0 ошибок
make test   # 33 теста, внешние сервисы не нужны

Все тесты используют локальный mock HTTP-сервер — запущенный nullwatch для прогона не требуется.

Репозиторий

https://github.com/Viroslav/nullwatch-py

Текущий Статус

Готов к ревью.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant