agentx-python 0.8.15__tar.gz → 0.8.16__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentx_python-0.8.15 → agentx_python-0.8.16}/PKG-INFO +1 -1
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/client.py +18 -1
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/models.py +7 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/runner.py +20 -2
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/monitor/client.py +5 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/monitor/judge_scorers.py +5 -0
- agentx_python-0.8.16/agentx/monitor/scorer_groups.py +88 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/version.py +2 -2
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx_python.egg-info/PKG-INFO +1 -1
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx_python.egg-info/SOURCES.txt +2 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/tests/test_judge_scorers.py +31 -0
- agentx_python-0.8.16/tests/test_multi_judge.py +76 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/LICENSE +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/README.md +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/__init__.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/agentx.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/cli.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/__init__.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/_term.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/adapters/__init__.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/adapters/http_endpoint.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/adapters/precomputed.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/adapters/raw.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/datasets.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/evaluation_settings.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/prompts.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/reporting.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/results.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/tool_schemas.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/evaluations/tracing.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/exceptions.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/export.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/feedback.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/integrations/__init__.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/integrations/_traced_call.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/integrations/anthropic.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/integrations/autogen.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/integrations/crewai.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/integrations/databricks.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/integrations/google_adk.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/integrations/google_genai.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/integrations/langchain.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/integrations/litellm.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/integrations/llamaindex.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/integrations/moveworks.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/integrations/openai.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/integrations/openai_agents.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/monitor/__init__.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/monitor/agents.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/monitor/improvement_groups.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/monitor/models.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/monitor/online_evaluators.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/monitor/patterns.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/monitor/profile.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/monitor/review_queue.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/monitor/rules.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/monitor/scorers.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/monitor/sessions.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/monitor/signals.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/outcomes.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/projects.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/py.typed +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/resources/__init__.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/resources/agent.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/resources/conversation.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/resources/workforce.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/testing.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/traces.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/tracing/__init__.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/tracing/ci_types.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/tracing/eval_scope.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/tracing/framework_detect.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/tracing/ingest_client.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/tracing/tracer.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx/util.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx_python.egg-info/dependency_links.txt +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx_python.egg-info/entry_points.txt +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx_python.egg-info/not-zip-safe +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx_python.egg-info/requires.txt +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/agentx_python.egg-info/top_level.txt +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/setup.cfg +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/setup.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/tests/test_cli_launcher.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/tests/test_deep_dive_fixes.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/tests/test_docs_match_sdk.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/tests/test_eval_scope.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/tests/test_framework_detect.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/tests/test_integration.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/tests/test_integrations.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/tests/test_pairwise.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/tests/test_review_queue.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/tests/test_runner_features.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/tests/test_selfhost_analysis_fallback.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/tests/test_selfhost_compat.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/tests/test_span_tree.py +0 -0
- {agentx_python-0.8.15 → agentx_python-0.8.16}/tests/test_testing.py +0 -0
|
@@ -314,10 +314,15 @@ class EvaluationsClient:
|
|
|
314
314
|
scorer_id: Optional[str] = None,
|
|
315
315
|
evaluation_settings_id: Optional[str] = None,
|
|
316
316
|
split: Optional[str] = None,
|
|
317
|
+
additional_scorer_ids: Optional[List[str]] = None,
|
|
318
|
+
scorer_group_id: Optional[str] = None,
|
|
317
319
|
) -> EvaluationRun:
|
|
318
320
|
"""``scorer_id`` names the LLM Judge Scorer grading this run (its id doubles as the
|
|
319
321
|
wire's ``evaluationSettingsId``). ``evaluation_settings_id`` is the pre-consolidation
|
|
320
|
-
alias and keeps working. ``split`` records the named case subset this run covers.
|
|
322
|
+
alias and keeps working. ``split`` records the named case subset this run covers.
|
|
323
|
+
``additional_scorer_ids`` (self-host): extra judge scorers that each pass their own
|
|
324
|
+
verdict on every result from the same single agent execution - verdicts land in each
|
|
325
|
+
result row's ``judgeScorerResults`` and the run's ``scorerBreakdown``."""
|
|
321
326
|
from agentx.version import VERSION
|
|
322
327
|
|
|
323
328
|
grader_id = _resolve_scorer_id(scorer_id, evaluation_settings_id)
|
|
@@ -335,6 +340,12 @@ class EvaluationsClient:
|
|
|
335
340
|
}
|
|
336
341
|
if grader_id:
|
|
337
342
|
payload["evaluationSettingsId"] = grader_id
|
|
343
|
+
if additional_scorer_ids:
|
|
344
|
+
payload["additionalScorerIds"] = additional_scorer_ids
|
|
345
|
+
# Scorer group grading (self-host): the group's weighted 0-10 aggregate fills the rating
|
|
346
|
+
# column and member verdicts land per row. Mutually exclusive with scorer_id (group wins).
|
|
347
|
+
if scorer_group_id:
|
|
348
|
+
payload["scorerGroupId"] = scorer_group_id
|
|
338
349
|
if split:
|
|
339
350
|
payload["split"] = split
|
|
340
351
|
data = self._request("POST", "/runs", json=self._with_workspace(payload))
|
|
@@ -372,6 +383,7 @@ class EvaluationsClient:
|
|
|
372
383
|
tolerance: Optional[float] = None,
|
|
373
384
|
record: bool = True,
|
|
374
385
|
caller: Optional[str] = "sdk",
|
|
386
|
+
scorer: Optional[str] = None,
|
|
375
387
|
) -> Dict[str, Any]:
|
|
376
388
|
# CI gate (self-host): pass/fail a finalized run against an absolute rating floor and/or
|
|
377
389
|
# the dataset's previous completed run. Recorded into gate history by default (the
|
|
@@ -389,6 +401,11 @@ class EvaluationsClient:
|
|
|
389
401
|
params["record"] = "true"
|
|
390
402
|
if caller:
|
|
391
403
|
params["caller"] = caller
|
|
404
|
+
# Multi-judge runs (self-host): gate a named additional scorer (id or name, e.g.
|
|
405
|
+
# scorer="Safety") instead of the primary - failUnder/noRegression then use that
|
|
406
|
+
# scorer's own per-result verdicts. Unknown names are a hard 400 from the engine.
|
|
407
|
+
if scorer:
|
|
408
|
+
params["scorer"] = scorer
|
|
392
409
|
return self._request("GET", f"/runs/{run_id}/gate", params=params)
|
|
393
410
|
|
|
394
411
|
def analyze_run(
|
|
@@ -75,6 +75,9 @@ class Dataset(BaseModel):
|
|
|
75
75
|
rejection_criteria: Optional[str] = Field(default=None, alias="rejectionCriteria")
|
|
76
76
|
evaluation_criteria: Optional[str] = Field(default=None, alias="evaluationCriteria")
|
|
77
77
|
questions: List[DatasetQuestion] = Field(default_factory=list)
|
|
78
|
+
# Custom code scorers attached to this dataset - [{ id, name, code, enabled }]. Retrievable,
|
|
79
|
+
# so a fetched dataset round-trips them (import_dataset copies them to the new dataset).
|
|
80
|
+
code_scorers: Optional[List[Dict[str, Any]]] = Field(default=None, alias="codeScorers")
|
|
78
81
|
status: str = "published"
|
|
79
82
|
version_id: Optional[str] = Field(default=None, alias="versionId")
|
|
80
83
|
# Sovereignty & Portability - models selected to compare on this dataset.
|
|
@@ -108,6 +111,8 @@ class EvaluationSettings(BaseModel):
|
|
|
108
111
|
acceptance_criteria: Optional[str] = Field(default=None, alias="acceptanceCriteria")
|
|
109
112
|
rejection_criteria: Optional[str] = Field(default=None, alias="rejectionCriteria")
|
|
110
113
|
evaluation_criteria: Optional[str] = Field(default=None, alias="evaluationCriteria")
|
|
114
|
+
# Custom code scorers on this scorer's offline profile - [{ id, name, code, enabled }].
|
|
115
|
+
code_scorers: Optional[List[Dict[str, Any]]] = Field(default=None, alias="codeScorers")
|
|
111
116
|
# LLM-as-judge overrides. None means "use the server default" (raw prompt template / OpenAI
|
|
112
117
|
# gpt-5.5). See client.evaluations.settings.builder(judge_prompt=..., judge_model=...).
|
|
113
118
|
judge_prompt: Optional[str] = Field(default=None, alias="judgePrompt")
|
|
@@ -435,6 +440,8 @@ class RunResultRow(BaseModel):
|
|
|
435
440
|
bleu_score: Optional[float] = Field(default=None, alias="bleuScore")
|
|
436
441
|
rouge_score: Optional[float] = Field(default=None, alias="rougeScore")
|
|
437
442
|
code_scorer_results: Optional[List[Dict[str, Any]]] = Field(default=None, alias="codeScorerResults")
|
|
443
|
+
# Verdicts from the run's ADDITIONAL judge scorers: [{scorerId, name, rating, justification}].
|
|
444
|
+
judge_scorer_results: Optional[List[Dict[str, Any]]] = Field(default=None, alias="judgeScorerResults")
|
|
438
445
|
raw: Dict[str, Any] = Field(default_factory=dict)
|
|
439
446
|
|
|
440
447
|
class Config:
|
|
@@ -77,6 +77,9 @@ class GateResult:
|
|
|
77
77
|
self.baseline_average: Optional[float] = data.get("baselineAverage")
|
|
78
78
|
self.baseline_run_id: Optional[str] = data.get("baselineRunId")
|
|
79
79
|
self.checks: List[Dict[str, Any]] = data.get("checks", [])
|
|
80
|
+
# Multi-judge runs: {"id", "name"} of the additional scorer being gated when the gate
|
|
81
|
+
# ran with scorer=..., None when gating the primary.
|
|
82
|
+
self.gated_scorer: Optional[Dict[str, Any]] = data.get("gatedScorer")
|
|
80
83
|
|
|
81
84
|
@property
|
|
82
85
|
def exit_code(self) -> int:
|
|
@@ -354,13 +357,16 @@ class EvaluationRunContext:
|
|
|
354
357
|
no_regression: bool = False,
|
|
355
358
|
tolerance: Optional[float] = None,
|
|
356
359
|
caller: str = "sdk",
|
|
360
|
+
scorer: Optional[str] = None,
|
|
357
361
|
) -> "GateResult":
|
|
358
362
|
"""CI gate (self-host): pass/fail this finalized run so a CI job can block a merge.
|
|
359
363
|
|
|
360
364
|
``fail_under`` fails the gate when the run's average rating is below the floor;
|
|
361
365
|
``no_regression=True`` fails it when the average dropped more than ``tolerance``
|
|
362
366
|
(default 0.5, judge scores are noisy) below the dataset's previous completed run.
|
|
363
|
-
At least one check is required.
|
|
367
|
+
At least one check is required. On a multi-judge run, ``scorer`` (an additional
|
|
368
|
+
scorer's id or name, e.g. ``scorer="Safety"``) gates that scorer's own average
|
|
369
|
+
instead of the primary's - "fail if Safety is low even when the average looks fine". Prints a CI-log-friendly verdict and returns a
|
|
364
370
|
:class:`GateResult` - the caller decides the exit code::
|
|
365
371
|
|
|
366
372
|
report = client.evaluations.run(...).execute(my_agent).finalize()
|
|
@@ -374,6 +380,7 @@ class EvaluationRunContext:
|
|
|
374
380
|
no_regression=no_regression,
|
|
375
381
|
tolerance=tolerance,
|
|
376
382
|
caller=caller,
|
|
383
|
+
scorer=scorer,
|
|
377
384
|
)
|
|
378
385
|
result = GateResult(data)
|
|
379
386
|
_say()
|
|
@@ -589,6 +596,7 @@ class EvaluationsRunner:
|
|
|
589
596
|
tolerance: Optional[float] = None,
|
|
590
597
|
record: bool = True,
|
|
591
598
|
caller: Optional[str] = "sdk",
|
|
599
|
+
scorer: Optional[str] = None,
|
|
592
600
|
) -> GateResult:
|
|
593
601
|
"""CI-gate any finalized run by id - the standalone form of
|
|
594
602
|
``EvaluationRunContext.gate()``, for gating a run created elsewhere or
|
|
@@ -603,6 +611,7 @@ class EvaluationsRunner:
|
|
|
603
611
|
tolerance=tolerance,
|
|
604
612
|
record=record,
|
|
605
613
|
caller=caller,
|
|
614
|
+
scorer=scorer,
|
|
606
615
|
)
|
|
607
616
|
)
|
|
608
617
|
|
|
@@ -613,6 +622,8 @@ class EvaluationsRunner:
|
|
|
613
622
|
scorer_id: Optional[str] = None,
|
|
614
623
|
evaluation_settings_id: Optional[str] = None,
|
|
615
624
|
split: Optional[str] = None,
|
|
625
|
+
additional_scorer_ids: Optional[List[str]] = None,
|
|
626
|
+
scorer_group_id: Optional[str] = None,
|
|
616
627
|
) -> EvaluationRunContext:
|
|
617
628
|
"""Start a run of ``dataset_id`` against ``subject``. Pass ``scorer_id`` (an LLM Judge
|
|
618
629
|
Scorer's id, e.g. from ``client.monitor.judge_scorers``) to grade with a specific
|
|
@@ -632,7 +643,14 @@ class EvaluationsRunner:
|
|
|
632
643
|
evaluation_settings = (
|
|
633
644
|
self._client.get_evaluation_settings(grader_id) if grader_id else None
|
|
634
645
|
)
|
|
635
|
-
run = self._client.init_run(
|
|
646
|
+
run = self._client.init_run(
|
|
647
|
+
dataset_id,
|
|
648
|
+
subject,
|
|
649
|
+
scorer_id=grader_id,
|
|
650
|
+
split=split,
|
|
651
|
+
additional_scorer_ids=additional_scorer_ids,
|
|
652
|
+
scorer_group_id=scorer_group_id,
|
|
653
|
+
)
|
|
636
654
|
case_count = (
|
|
637
655
|
sum(1 for q in dataset.questions if split in (q.main_question.splits or []))
|
|
638
656
|
if split
|
|
@@ -147,6 +147,11 @@ class MonitorClient:
|
|
|
147
147
|
# surface that matches the product; evaluations.settings and online_evaluators below
|
|
148
148
|
# remain as its profile-level views.
|
|
149
149
|
self.judge_scorers = JudgeScorersClient(api_key=api_key, base_url=self._api_root())
|
|
150
|
+
from agentx.monitor.scorer_groups import ScorerGroupsClient
|
|
151
|
+
|
|
152
|
+
# Scorer groups: mixed-kind scorers composed into one 0-10 score (weights + must-pass
|
|
153
|
+
# gates) - a group grades dataset runs (scorer_group_id) and, when online, live traffic.
|
|
154
|
+
self.scorer_groups = ScorerGroupsClient(api_key=api_key, base_url=self._api_root())
|
|
150
155
|
from agentx.monitor.improvement_groups import ImprovementGroupsClient
|
|
151
156
|
|
|
152
157
|
# Auto-improve: confirmed production failures -> improvement report -> code fix (via
|
|
@@ -36,6 +36,11 @@ class JudgeScorer(dict):
|
|
|
36
36
|
def offline(self) -> Dict[str, Any]:
|
|
37
37
|
return self.get("offline", {})
|
|
38
38
|
|
|
39
|
+
@property
|
|
40
|
+
def code_scorers(self) -> List[Dict[str, Any]]:
|
|
41
|
+
"""Custom code scorers on the offline profile - [{ id, name, code, enabled }]."""
|
|
42
|
+
return list(self.offline.get("codeScorers") or [])
|
|
43
|
+
|
|
39
44
|
@property
|
|
40
45
|
def online(self) -> Optional[Dict[str, Any]]:
|
|
41
46
|
return self.get("online")
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Scorer groups (self-host): scorers of any kind - LLM judges, patterns, custom code/external
|
|
2
|
+
scorers - composed into ONE 0-10 score via per-member weights and optional must-pass gates.
|
|
3
|
+
Members are references: ``{"kind": "judge" | "pattern" | "custom", "refId": ..., "weight": ...,
|
|
4
|
+
"gate": ...}``. Grade a dataset run with a group by passing its id as ``scorer_group_id`` to
|
|
5
|
+
``client.evaluations.run(...)``; give it an ``online`` profile to score sampled live traffic and
|
|
6
|
+
raise Signals below the alert threshold."""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AgentXScorerGroupsError(Exception):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ScorerGroup(dict):
|
|
18
|
+
"""Wire object (dict subclass so unknown fields round-trip)."""
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def id(self) -> str:
|
|
22
|
+
return self["_id"]
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def name(self) -> str:
|
|
26
|
+
return self["name"]
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def members(self) -> List[Dict[str, Any]]:
|
|
30
|
+
return list(self.get("members") or [])
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def online(self) -> Optional[Dict[str, Any]]:
|
|
34
|
+
return self.get("online")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ScorerGroupsClient:
|
|
38
|
+
def __init__(self, api_key: str, base_url: str):
|
|
39
|
+
self._api_key = api_key
|
|
40
|
+
self._base = base_url.rstrip("/") + "/agent-monitoring/scorer-groups"
|
|
41
|
+
|
|
42
|
+
def _request(self, method: str, url: str, json: Optional[Dict[str, Any]] = None) -> Any:
|
|
43
|
+
response = requests.request(
|
|
44
|
+
method,
|
|
45
|
+
url,
|
|
46
|
+
headers={"x-api-key": self._api_key, "content-type": "application/json"},
|
|
47
|
+
json=json,
|
|
48
|
+
timeout=30,
|
|
49
|
+
)
|
|
50
|
+
if response.status_code >= 400:
|
|
51
|
+
raise AgentXScorerGroupsError(f"HTTP {response.status_code}: {response.text}")
|
|
52
|
+
return response.json()
|
|
53
|
+
|
|
54
|
+
def list(self) -> List[ScorerGroup]:
|
|
55
|
+
return [ScorerGroup(g) for g in self._request("GET", self._base).get("scorerGroups", [])]
|
|
56
|
+
|
|
57
|
+
def get(self, group_id: str) -> ScorerGroup:
|
|
58
|
+
return ScorerGroup(self._request("GET", f"{self._base}/{group_id}")["scorerGroup"])
|
|
59
|
+
|
|
60
|
+
def create(
|
|
61
|
+
self,
|
|
62
|
+
name: str,
|
|
63
|
+
members: List[Dict[str, Any]],
|
|
64
|
+
description: Optional[str] = None,
|
|
65
|
+
online: Optional[Dict[str, Any]] = None,
|
|
66
|
+
) -> ScorerGroup:
|
|
67
|
+
"""``members``: [{"kind": "judge"|"pattern"|"custom", "refId": ..., "weight": 1, "gate": False}].
|
|
68
|
+
``online``: {"enabled": True, "sampleRate": 0.1, "alertThreshold": 5, "severity": "medium"}
|
|
69
|
+
or None for offline-only."""
|
|
70
|
+
payload: Dict[str, Any] = {"name": name, "members": members}
|
|
71
|
+
if description is not None:
|
|
72
|
+
payload["description"] = description
|
|
73
|
+
if online is not None:
|
|
74
|
+
payload["online"] = online
|
|
75
|
+
return ScorerGroup(self._request("POST", self._base, json=payload)["scorerGroup"])
|
|
76
|
+
|
|
77
|
+
def update(self, group_id: str, **fields: Any) -> ScorerGroup:
|
|
78
|
+
"""Sparse update - pass any of name/description/members/online (online=None detaches
|
|
79
|
+
live scoring)."""
|
|
80
|
+
return ScorerGroup(self._request("PUT", f"{self._base}/{group_id}", json=fields)["scorerGroup"])
|
|
81
|
+
|
|
82
|
+
def delete(self, group_id: str) -> None:
|
|
83
|
+
self._request("DELETE", f"{self._base}/{group_id}")
|
|
84
|
+
|
|
85
|
+
def ratings(self, group_id: str, window: str = "7d") -> Dict[str, Any]:
|
|
86
|
+
"""Live score history for a group - ``{"window", "points": [{ts, averageRating, count}]}``,
|
|
87
|
+
the same shape online-evaluator ratings use. ``window``: "24h" | "7d" | "30d"."""
|
|
88
|
+
return self._request("GET", f"{self._base}/{group_id}/ratings?window={window}")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
VERSION = "0.8.
|
|
1
|
+
VERSION = "0.8.16"
|
|
2
2
|
|
|
3
3
|
# The AgentX-trace-eval release this SDK version is tested against - what `agentx-trace-eval`
|
|
4
4
|
# installs and converges to (see agentx/cli.py). Bump together with VERSION when releasing, so
|
|
5
5
|
# every published SDK names a known-good engine+dashboard pair. Users can override with
|
|
6
6
|
# AGENTX_TRACE_EVAL_VERSION=<tag|latest>.
|
|
7
|
-
ENGINE_VERSION = "v0.3.
|
|
7
|
+
ENGINE_VERSION = "v0.3.13"
|
|
@@ -55,6 +55,7 @@ agentx/monitor/patterns.py
|
|
|
55
55
|
agentx/monitor/profile.py
|
|
56
56
|
agentx/monitor/review_queue.py
|
|
57
57
|
agentx/monitor/rules.py
|
|
58
|
+
agentx/monitor/scorer_groups.py
|
|
58
59
|
agentx/monitor/scorers.py
|
|
59
60
|
agentx/monitor/sessions.py
|
|
60
61
|
agentx/monitor/signals.py
|
|
@@ -83,6 +84,7 @@ tests/test_framework_detect.py
|
|
|
83
84
|
tests/test_integration.py
|
|
84
85
|
tests/test_integrations.py
|
|
85
86
|
tests/test_judge_scorers.py
|
|
87
|
+
tests/test_multi_judge.py
|
|
86
88
|
tests/test_pairwise.py
|
|
87
89
|
tests/test_review_queue.py
|
|
88
90
|
tests/test_runner_features.py
|
|
@@ -268,3 +268,34 @@ def test_validate_and_publish_send_criteria_at_top_level(monkeypatch):
|
|
|
268
268
|
assert captured["validate"]["window"] == "24h"
|
|
269
269
|
assert "criteria" not in captured["validate"]
|
|
270
270
|
assert captured["publish"]["acceptanceCriteria"] == "a"
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def test_code_scorers_are_retrievable_from_the_wire_object():
|
|
274
|
+
"""The wire rows may lack ids (SDK-created scorers) - retrieval must hand them back as-is."""
|
|
275
|
+
from agentx.monitor.judge_scorers import JudgeScorer
|
|
276
|
+
|
|
277
|
+
scorer = JudgeScorer(
|
|
278
|
+
{
|
|
279
|
+
"_id": "s1",
|
|
280
|
+
"name": "Blend",
|
|
281
|
+
"offline": {"codeScorers": [{"name": "Final score", "code": "return 1;", "enabled": True}]},
|
|
282
|
+
}
|
|
283
|
+
)
|
|
284
|
+
assert scorer.code_scorers == [{"name": "Final score", "code": "return 1;", "enabled": True}]
|
|
285
|
+
# And an offline profile without any stays an empty list, not a KeyError.
|
|
286
|
+
assert JudgeScorer({"_id": "s2", "name": "Plain", "offline": {}}).code_scorers == []
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def test_dataset_model_round_trips_code_scorers():
|
|
290
|
+
"""extra="ignore" used to silently drop codeScorers on read - import_dataset lost them."""
|
|
291
|
+
from agentx.evaluations.models import Dataset
|
|
292
|
+
|
|
293
|
+
wire = {
|
|
294
|
+
"_id": "d1",
|
|
295
|
+
"name": "Guarded",
|
|
296
|
+
"questions": [],
|
|
297
|
+
"codeScorers": [{"id": "cs1", "name": "gate", "code": "return 0;", "enabled": True}],
|
|
298
|
+
}
|
|
299
|
+
parsed = Dataset(**wire)
|
|
300
|
+
assert parsed.code_scorers == wire["codeScorers"]
|
|
301
|
+
assert parsed.model_dump(by_alias=True)["codeScorers"] == wire["codeScorers"]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Wire-level tests for multi-judge dataset runs: additional_scorer_ids on run creation and
|
|
2
|
+
the named-scorer CI gate (gate_run(scorer=...)). The session/HTTP layer is monkeypatched; the
|
|
3
|
+
engine-side behavior is pinned by the engine's multiJudge.integration.test.ts."""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from agentx.evaluations.client import EvaluationsClient
|
|
10
|
+
from agentx.evaluations.models import EvaluationSubject
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FakeResponse:
|
|
14
|
+
def __init__(self, payload: Dict[str, Any], status_code: int = 200):
|
|
15
|
+
self._payload = payload
|
|
16
|
+
self.status_code = status_code
|
|
17
|
+
self.ok = status_code < 400
|
|
18
|
+
self.text = "x"
|
|
19
|
+
|
|
20
|
+
def json(self) -> Dict[str, Any]:
|
|
21
|
+
return self._payload
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture()
|
|
25
|
+
def recorded(monkeypatch):
|
|
26
|
+
calls: List[Dict[str, Any]] = []
|
|
27
|
+
|
|
28
|
+
def fake_request(method, url, timeout=None, **kwargs):
|
|
29
|
+
calls.append({"method": method, "url": url, **kwargs})
|
|
30
|
+
return FakeResponse(
|
|
31
|
+
{
|
|
32
|
+
"runId": "r1",
|
|
33
|
+
"datasetId": "ds1",
|
|
34
|
+
"status": "in_progress",
|
|
35
|
+
"passed": True,
|
|
36
|
+
"checks": [],
|
|
37
|
+
"gatedScorer": None,
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
client = EvaluationsClient(api_key="k", base_url="http://engine:4700/api/v1")
|
|
42
|
+
monkeypatch.setattr(client._session, "request", fake_request)
|
|
43
|
+
return client, calls
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_init_run_sends_additional_scorer_ids_camel_case(recorded):
|
|
47
|
+
client, calls = recorded
|
|
48
|
+
client.init_run(
|
|
49
|
+
"ds1",
|
|
50
|
+
EvaluationSubject(kind="custom_agent"),
|
|
51
|
+
scorer_id="primary",
|
|
52
|
+
additional_scorer_ids=["safety", "tone"],
|
|
53
|
+
)
|
|
54
|
+
payload = calls[0]["json"]
|
|
55
|
+
assert payload["evaluationSettingsId"] == "primary"
|
|
56
|
+
assert payload["additionalScorerIds"] == ["safety", "tone"]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_init_run_omits_the_key_when_no_additional_scorers(recorded):
|
|
60
|
+
client, calls = recorded
|
|
61
|
+
client.init_run("ds1", EvaluationSubject(kind="custom_agent"), scorer_id="primary")
|
|
62
|
+
assert "additionalScorerIds" not in calls[0]["json"]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_gate_run_forwards_the_named_scorer(recorded):
|
|
66
|
+
client, calls = recorded
|
|
67
|
+
client.gate_run("r1", fail_under=5, scorer="Safety")
|
|
68
|
+
params = calls[0]["params"]
|
|
69
|
+
assert params["failUnder"] == 5
|
|
70
|
+
assert params["scorer"] == "Safety"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_gate_run_leaves_scorer_off_for_primary_gates(recorded):
|
|
74
|
+
client, calls = recorded
|
|
75
|
+
client.gate_run("r1", fail_under=5)
|
|
76
|
+
assert "scorer" not in calls[0]["params"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|