"""LLM-as-judge for behavioral checks that can't be expressed deterministically (answer correctness, tone, refusal quality). Returns a strict pass/fail + reason. Cheap, decoupled model (JUDGE_MODEL, default gpt-4o-mini). Vendored from agent-evals/harness/judge.py — keep in sync. """ from __future__ import annotations import json import os JUDGE_MODEL = os.getenv("JUDGE_MODEL", "gpt-4o-mini") _SYSTEM = ( "Ты — строгий, но справедливый оценщик ответов чат-бота поддержки сервиса " "уборки «Домовёнок». Тебе дают ВОПРОС пользователя, ОТВЕТ бота и КРИТЕРИЙ. " "Реши, удовлетворяет ли ОТВЕТ КРИТЕРИЮ. " "Отвечай СТРОГО валидным JSON одной строкой: " '{"pass": true|false, "reason": "<кратко по-русски, 1-2 предложения>"}' ) def judge_rubric(question: str, answer: str, rubric: str, *, model: str | None = None, api_key: str | None = None) -> dict: """Return {"pass": bool, "reason": str}. Fail-closed on parse errors.""" from openai import OpenAI client = OpenAI(api_key=api_key or os.getenv("OPENAI_API_KEY", "")) user = ( f"ВОПРОС:\n{question}\n\n" f"ОТВЕТ БОТА:\n{answer}\n\n" f"КРИТЕРИЙ (что считается прохождением):\n{rubric}" ) try: resp = client.chat.completions.create( model=model or JUDGE_MODEL, temperature=0, max_tokens=200, response_format={"type": "json_object"}, messages=[{"role": "system", "content": _SYSTEM}, {"role": "user", "content": user}], ) raw = resp.choices[0].message.content.strip() data = json.loads(raw) return {"pass": bool(data.get("pass", False)), "reason": str(data.get("reason", "")).strip()} except Exception as e: # noqa: BLE001 return {"pass": False, "reason": f"judge error: {e}"}