ExecChat / judge.py
guilsyTrue's picture
Eval: out_of_scope is not escalation
7e2d640 verified
Raw
History Blame Contribute Delete
2.13 kB
"""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}"}