Skip to content

Commit c72abbb

Browse files
authored
Merge pull request #44 from AgentX-ai/feat/group-score-with-session
group scorer with session
2 parents 1f35976 + 2747e76 commit c72abbb

10 files changed

Lines changed: 50 additions & 19 deletions

File tree

‎EVALUATIONS.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ Score strictly: any missing policy detail is a failing response.""",
432432
```
433433

434434
- `judge_prompt` is a raw template. `{input}`, `{output}`, and `{expected}` are substituted in; everything else (chain of thought, capabilities/references, criteria, per-question `judge_guideline`, delegation notes) is appended automatically after it, so a custom prompt can restructure the grading philosophy without ever losing that context. Omit it to keep the default rubric.
435-
- `judge_model` accepts any OpenAI or Anthropic model id (`client.evaluations.list_models(provider="Anthropic")` to discover valid ones). Omit it to keep the default (`gpt-5.5`).
435+
- `judge_model` accepts any OpenAI or Anthropic model id (`client.evaluations.list_models(provider="Anthropic")` to discover valid ones). Omit it to keep the engine default (`gpt-5.6-luna`).
436436
- `list_models()` is **hosted platform only**: it calls the hosted API's `/custom-agent-evaluations/models` registry, which the self-host engine does not serve (404, surfaced as `AgentXEvaluationsError`). On self-host, pass any model id your engine's judge keys can reach.
437437

438438
---
@@ -902,7 +902,7 @@ This run + gate flow is the **self-host CI path**. The separate CI-runs API in [
902902
report = client.evaluations.run(...).execute(my_agent).finalize().analyze(
903903
mode="auto", # "auto" | "sync" | "batch"
904904
quality_mode="quality_first", # "quality_first" | "balanced"
905-
judges=["gpt-5.5", "claude-opus-4-8"], # 1-3 model ids; omit for a single gpt-5.5 judge
905+
judges=["gpt-5.6-luna", "claude-opus-4-8"], # 1-3 model ids; omit for the platform default judge
906906
)
907907

908908
report.summary # str | None, overall narrative summary
@@ -933,7 +933,7 @@ This is separate from, and available even without, the numeric `average_rating`/
933933
|---|---|---|
934934
| `mode` | `"auto"` (default), `"sync"`, `"batch"` | How item scoring executes server-side; `"auto"` picks based on run size |
935935
| `quality_mode` | `"quality_first"`, `"balanced"` | `"quality_first"` runs a second judge on every item; `"balanced"` samples based on risk |
936-
| `judges` | 1-3 model ids | Which LLM(s) score each response. The first always runs; a second confirms, a third only breaks a tie between the first two. Defaults to a single judge, `["gpt-5.5"]`, if omitted. |
936+
| `judges` | 1-3 model ids | Which LLM(s) score each response. The first always runs; a second confirms, a third only breaks a tie between the first two. Omit to score with a single judge, the engine's platform default model. |
937937
| `poll_interval` | seconds, default `5.0` | How often to check job status while waiting |
938938
| `timeout` | seconds, default `1800.0` | Give up waiting after this long (the job keeps running server-side; call `get_report()` later to check on it) |
939939

‎agentx/evaluations/datasets.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def __init__(
4848
"questions": [],
4949
}
5050
# LLM-as-judge overrides for this dataset's own grading config. Omit either to keep the
51-
# server default (raw prompt template / OpenAI gpt-5.5, see EVALUATIONS.md). judge_model
51+
# server default (raw prompt template / gpt-5.6-luna, see EVALUATIONS.md). judge_model
5252
# must be one of client.evaluations.list_models() (OpenAI or Anthropic).
5353
if judge_prompt is not None:
5454
self._payload["judgePrompt"] = judge_prompt

‎agentx/evaluations/evaluation_settings.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def __init__(
4545
"evaluationCriteria": evaluation_criteria,
4646
}
4747
# LLM-as-judge overrides. Omit either to keep the server default (raw prompt template /
48-
# OpenAI gpt-5.5, see EVALUATIONS.md). judge_model must be one of
48+
# gpt-5.6-luna, see EVALUATIONS.md). judge_model must be one of
4949
# client.evaluations.list_models() (OpenAI or Anthropic).
5050
if judge_prompt is not None:
5151
self._payload["judgePrompt"] = judge_prompt

‎agentx/evaluations/models.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ class EvaluationSettings(BaseModel):
113113
evaluation_criteria: Optional[str] = Field(default=None, alias="evaluationCriteria")
114114
# Custom code scorers on this scorer's offline profile - [{ id, name, code, enabled }].
115115
code_scorers: Optional[List[Dict[str, Any]]] = Field(default=None, alias="codeScorers")
116-
# LLM-as-judge overrides. None means "use the server default" (raw prompt template / OpenAI
117-
# gpt-5.5). See client.evaluations.settings.builder(judge_prompt=..., judge_model=...).
116+
# LLM-as-judge overrides. None means "use the server default" (raw prompt template /
117+
# gpt-5.6-luna). See client.evaluations.settings.builder(judge_prompt=..., judge_model=...).
118118
judge_prompt: Optional[str] = Field(default=None, alias="judgePrompt")
119119
judge_model: Optional[str] = Field(default=None, alias="judgeModel")
120120
status: str = "published"

‎agentx/evaluations/runner.py‎

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,6 @@ def _say(*args, **kwargs) -> None:
6363
"l4_final_reduce": "writing final report",
6464
}
6565

66-
_DEFAULT_JUDGE_MODEL = "gpt-5.5"
67-
68-
6966
class GateResult:
7067
"""Wire result of the CI gate (GET /runs/:id/gate) with attribute access for the fields a
7168
CI script actually branches on."""
@@ -456,16 +453,16 @@ def analyze(
456453
Args:
457454
mode: "auto" (default), "sync", or "batch" - how item scoring executes server-side.
458455
quality_mode: "quality_first" or "balanced" - how many items get a second/third judge.
459-
judges: 1-3 model ids, e.g. ``["gpt-5.5", "claude-opus-4-8"]``. Defaults to a single
460-
judge, ``["gpt-5.5"]``, rather than the dashboard's 3-judge default - SDK runs are
461-
typically lighter-weight, quick-start evaluations.
456+
judges: 1-3 model ids from ``client.evaluations.list_models()``, e.g.
457+
``["gpt-5.6-luna", "claude-opus-4-8"]``. Omit to let the engine score with its
458+
platform default model (a single judge, rather than the dashboard's 3-judge
459+
default - SDK runs are typically lighter-weight, quick-start evaluations).
462460
poll_interval: seconds between status checks while waiting.
463461
timeout: give up waiting after this many seconds (the job keeps running server-side;
464462
call ``get_report()`` later to check on it).
465463
"""
466464
if judges is not None and not (1 <= len(judges) <= 3):
467465
raise ValueError("judges must contain 1-3 model ids")
468-
resolved_judges = judges if judges is not None else [_DEFAULT_JUDGE_MODEL]
469466

470467
_say()
471468
with Spinner("Analyzing - AI is reviewing your results") as spinner:
@@ -474,7 +471,7 @@ def analyze(
474471
self._run.run_id,
475472
mode=mode,
476473
quality_mode=quality_mode,
477-
judges=resolved_judges,
474+
judges=judges,
478475
)
479476
deadline = time.monotonic() + timeout
480477
status = self._client.get_analysis_status(self._run.run_id)

‎agentx/monitor/client.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,24 @@ def list_session_spans(self, session_id: str) -> List[dict]:
429429
data = self._request("GET", f"/ingest/sessions/{session_id}/spans", base=self._api_root())
430430
return data.get("spans", []) if isinstance(data, dict) else data
431431

432+
def list_session_scores(self, session_id: str) -> List[dict]:
433+
"""Every session-level verdict on the session, newest first: session-scoped online
434+
evaluators (kind ``online-eval:<id>``), session-scoped scorer groups
435+
(``scorer-group:<id>``), and legacy coherence rows."""
436+
data = self._request(
437+
"GET", f"/agent-monitoring/sessions/{session_id}/scores", base=self._api_root()
438+
)
439+
return data.get("scores", []) if isinstance(data, dict) else data
440+
441+
def run_session_sweep(self) -> dict:
442+
"""Run the idle-session sweep once, now - the tick that scores quiet multi-turn
443+
sessions with every enabled session-scoped evaluator and scorer group. Production
444+
engines run this automatically every minute; the manual trigger exists for demos,
445+
tests, and backfills. Returns ``{"judged": n}``."""
446+
return self._request(
447+
"POST", "/agent-monitoring/session-sweep/run", base=self._api_root(), timeout=300
448+
)
449+
432450
# ------------------------------------------------------------------
433451
# Model portability (self-host): replay a trace's input against other models
434452
# ------------------------------------------------------------------

‎agentx/monitor/scorer_groups.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ def create(
6565
online: Optional[Dict[str, Any]] = None,
6666
) -> ScorerGroup:
6767
"""``members``: [{"kind": "judge"|"pattern"|"custom", "refId": ..., "weight": 1, "gate": False}].
68-
``online``: {"enabled": True, "sampleRate": 0.1, "alertThreshold": 5, "severity": "medium"}
68+
``online``: {"enabled": True, "sampleRate": 0.1, "alertThreshold": 5, "severity": "medium"}.
69+
Add ``"scope": "session", "idleSeconds": 120`` to score whole multi-turn sessions once
70+
idle, instead of each sampled trace.
6971
or None for offline-only."""
7072
payload: Dict[str, Any] = {"name": name, "members": members}
7173
if description is not None:

‎agentx/monitor/sessions.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,15 @@ def coherence_check(self, session_id: str) -> dict:
1919
def spans(self, session_id: str) -> List[dict]:
2020
"""Every span in the session (roots and children), oldest first."""
2121
return self._client.list_session_spans(session_id)
22+
23+
def scores(self, session_id: str) -> List[dict]:
24+
"""Session-level verdicts, newest first. ``kind`` says who scored: a session-scoped
25+
online evaluator (``online-eval:<id>``) or a session-scoped scorer group
26+
(``scorer-group:<id>``)."""
27+
return self._client.list_session_scores(session_id)
28+
29+
def run_sweep(self) -> dict:
30+
"""Trigger the idle-session sweep once (normally automatic, every minute) - scores
31+
idle multi-turn sessions with every enabled session-scoped evaluator and scorer
32+
group. Returns ``{"judged": n}``."""
33+
return self._client.run_session_sweep()

‎tests/test_integrations.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,9 @@ def test_crewai_captures_real_per_task_timing_via_event_bus():
599599
durations are provably unequal — the old "divide latency evenly across
600600
tasks" approximation would have reported them as identical.
601601
"""
602-
crewai = pytest.importorskip("crewai")
602+
# exc_type=Exception: on older Pythons crewai can raise TypeError (PEP 604 syntax) at
603+
# import time, and a broken optional integration should skip this test, not fail it.
604+
crewai = pytest.importorskip("crewai", exc_type=Exception)
603605
from crewai.events.event_bus import crewai_event_bus
604606
from crewai.events.types.task_events import TaskCompletedEvent, TaskStartedEvent
605607
from crewai.tasks.task_output import TaskOutput

‎tests/test_selfhost_analysis_fallback.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,12 @@ def test_the_fallback_request_gets_the_long_analysis_timeout():
210210
{("POST", f"{API_ROOT}/evaluate/analyze/{RUN}"): FakeResponse(200, {"status": "completed"})}
211211
)
212212

213-
client.analyze_run(RUN, judges=["gpt-5.5"])
213+
client.analyze_run(RUN, judges=["gpt-5.6-luna"])
214214

215215
method, url, kwargs = session.calls[-1]
216216
assert url == f"{API_ROOT}/evaluate/analyze/{RUN}"
217217
assert kwargs["timeout"] > 60, "a synchronous judge pass needs more than the 30s default"
218-
assert kwargs["json"]["judges"] == [{"model": "gpt-5.5"}]
218+
assert kwargs["json"]["judges"] == [{"model": "gpt-5.6-luna"}]
219219

220220

221221
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)