agentx-python 0.8.19__tar.gz → 0.8.21__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.19/agentx_python.egg-info → agentx_python-0.8.21}/PKG-INFO +1 -1
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/__init__.py +4 -5
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/client.py +53 -20
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/datasets.py +16 -3
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/evaluation_settings.py +2 -1
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/models.py +4 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/prompts.py +3 -3
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/reporting.py +4 -2
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/runner.py +13 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/integrations/anthropic.py +1 -1
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/integrations/autogen.py +3 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/integrations/crewai.py +21 -3
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/integrations/google_adk.py +6 -2
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/integrations/langchain.py +38 -21
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/integrations/llamaindex.py +43 -5
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/integrations/openai_agents.py +17 -1
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/monitor/client.py +20 -10
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/monitor/models.py +7 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/monitor/profile.py +1 -1
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/monitor/scorer_groups.py +2 -1
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/monitor/scorers.py +9 -2
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/monitor/sessions.py +17 -1
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/tracing/ci_types.py +17 -11
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/tracing/ingest_client.py +48 -21
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/tracing/tracer.py +80 -24
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/version.py +2 -2
- {agentx_python-0.8.19 → agentx_python-0.8.21/agentx_python.egg-info}/PKG-INFO +1 -1
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_runner_features.py +16 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_selfhost_analysis_fallback.py +25 -1
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_span_tree.py +93 -5
- {agentx_python-0.8.19 → agentx_python-0.8.21}/LICENSE +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/README.md +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/agentx.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/cli.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/__init__.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/_term.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/adapters/__init__.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/adapters/http_endpoint.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/adapters/precomputed.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/adapters/raw.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/results.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/tool_schemas.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/evaluations/tracing.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/exceptions.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/export.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/feedback.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/integrations/__init__.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/integrations/_traced_call.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/integrations/databricks.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/integrations/google_genai.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/integrations/litellm.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/integrations/moveworks.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/integrations/openai.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/monitor/__init__.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/monitor/agents.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/monitor/improvement_groups.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/monitor/judge_scorers.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/monitor/online_evaluators.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/monitor/patterns.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/monitor/review_queue.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/monitor/rules.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/monitor/signals.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/outcomes.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/projects.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/py.typed +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/resources/__init__.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/resources/agent.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/resources/conversation.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/resources/workforce.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/testing.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/traces.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/tracing/__init__.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/tracing/eval_scope.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/tracing/framework_detect.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx/util.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx_python.egg-info/SOURCES.txt +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx_python.egg-info/dependency_links.txt +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx_python.egg-info/entry_points.txt +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx_python.egg-info/not-zip-safe +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx_python.egg-info/requires.txt +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/agentx_python.egg-info/top_level.txt +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/setup.cfg +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/setup.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_cli_launcher.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_deep_dive_fixes.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_docs_match_sdk.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_eval_scope.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_framework_detect.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_integration.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_integrations.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_judge_scorers.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_multi_judge.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_pairwise.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_review_queue.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_selfhost_compat.py +0 -0
- {agentx_python-0.8.19 → agentx_python-0.8.21}/tests/test_testing.py +0 -0
|
@@ -12,11 +12,10 @@ from agentx.exceptions import (
|
|
|
12
12
|
CIGateFailure,
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
-
logging.basicConfig
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
)
|
|
15
|
+
# Library logging hygiene: a library must never call logging.basicConfig - it hijacks the
|
|
16
|
+
# host application's root logger (format AND level) and turns the app's own later basicConfig
|
|
17
|
+
# into a no-op. Consumers opt into our logs with logging.getLogger("agentx").setLevel(...).
|
|
18
|
+
logging.getLogger("agentx").addHandler(logging.NullHandler())
|
|
20
19
|
|
|
21
20
|
__all__ = [
|
|
22
21
|
"AgentX",
|
|
@@ -207,6 +207,12 @@ class EvaluationsClient:
|
|
|
207
207
|
return resp.json()
|
|
208
208
|
except Exception:
|
|
209
209
|
return resp.text
|
|
210
|
+
# A timeout keeps its type: runner._flush_batch catches requests.Timeout specifically
|
|
211
|
+
# (the engine may still be scoring the batch - a blind retry double-bills every judge
|
|
212
|
+
# call), and wrapping it in AgentXEvaluationsError here made that guard unreachable.
|
|
213
|
+
# Applies to retry=False calls too, where the single attempt lands straight here.
|
|
214
|
+
if isinstance(last_exc, requests.Timeout):
|
|
215
|
+
raise last_exc
|
|
210
216
|
raise AgentXEvaluationsError(f"Request failed after retries: {last_exc}")
|
|
211
217
|
|
|
212
218
|
# ------------------------------------------------------------------
|
|
@@ -227,7 +233,9 @@ class EvaluationsClient:
|
|
|
227
233
|
# ------------------------------------------------------------------
|
|
228
234
|
|
|
229
235
|
def create_dataset(self, payload: dict) -> Dataset:
|
|
230
|
-
|
|
236
|
+
# Server-side write: a timeout after the dataset row was created would be
|
|
237
|
+
# retried into a duplicate dataset, so no transport retry.
|
|
238
|
+
data = self._request("POST", "/datasets", json=self._with_workspace(payload), retry=False)
|
|
231
239
|
return Dataset(**data)
|
|
232
240
|
|
|
233
241
|
def delete_dataset(self, dataset_id: str) -> None:
|
|
@@ -255,8 +263,9 @@ class EvaluationsClient:
|
|
|
255
263
|
# ------------------------------------------------------------------
|
|
256
264
|
|
|
257
265
|
def create_evaluation_settings(self, payload: dict) -> EvaluationSettings:
|
|
266
|
+
# Server-side write - no transport retry (see init_run's comment).
|
|
258
267
|
data = self._request(
|
|
259
|
-
"POST", "/evaluation-settings", json=self._with_workspace(payload)
|
|
268
|
+
"POST", "/evaluation-settings", json=self._with_workspace(payload), retry=False
|
|
260
269
|
)
|
|
261
270
|
return EvaluationSettings(**data)
|
|
262
271
|
|
|
@@ -281,12 +290,14 @@ class EvaluationsClient:
|
|
|
281
290
|
|
|
282
291
|
# ------------------------------------------------------------------
|
|
283
292
|
# Prompt registry endpoints - see agentx.evaluations.prompts.PromptClient for the concept
|
|
284
|
-
# (the external-agent analog to native autotune).
|
|
285
|
-
#
|
|
293
|
+
# (the external-agent analog to native autotune). propose_prompt never publishes;
|
|
294
|
+
# publish_prompt_version below IS the explicit approval step - call it only after a human
|
|
295
|
+
# reviewed the proposal.
|
|
286
296
|
# ------------------------------------------------------------------
|
|
287
297
|
|
|
288
298
|
def create_prompt(self, payload: dict) -> Prompt:
|
|
289
|
-
|
|
299
|
+
# Server-side write - no transport retry (see init_run's comment).
|
|
300
|
+
data = self._request("POST", "/prompts", json=self._with_workspace(payload), retry=False)
|
|
290
301
|
return Prompt(**data)
|
|
291
302
|
|
|
292
303
|
def list_prompts(self) -> List[Prompt]:
|
|
@@ -322,7 +333,9 @@ class EvaluationsClient:
|
|
|
322
333
|
alias and keeps working. ``split`` records the named case subset this run covers.
|
|
323
334
|
``additional_scorer_ids`` (self-host): extra judge scorers that each pass their own
|
|
324
335
|
verdict on every result from the same single agent execution - verdicts land in each
|
|
325
|
-
result row's ``judgeScorerResults`` and the run's ``scorerBreakdown``.
|
|
336
|
+
result row's ``judgeScorerResults`` and the run's ``scorerBreakdown``. When
|
|
337
|
+
``scorer_group_id`` is set, the engine nulls ``additionalScorerIds`` on the run too -
|
|
338
|
+
the group is the whole grading story, not a layer on top of extra scorers."""
|
|
326
339
|
from agentx.version import VERSION
|
|
327
340
|
|
|
328
341
|
grader_id = _resolve_scorer_id(scorer_id, evaluation_settings_id)
|
|
@@ -343,12 +356,15 @@ class EvaluationsClient:
|
|
|
343
356
|
if additional_scorer_ids:
|
|
344
357
|
payload["additionalScorerIds"] = additional_scorer_ids
|
|
345
358
|
# 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
|
|
359
|
+
# column and member verdicts land per row. Mutually exclusive with scorer_id (group
|
|
360
|
+
# wins), and the engine also nulls additionalScorerIds when a group grades the run.
|
|
347
361
|
if scorer_group_id:
|
|
348
362
|
payload["scorerGroupId"] = scorer_group_id
|
|
349
363
|
if split:
|
|
350
364
|
payload["split"] = split
|
|
351
|
-
|
|
365
|
+
# Server-side write: a timeout after the run row was created would be
|
|
366
|
+
# retried into a duplicate run, so no transport retry.
|
|
367
|
+
data = self._request("POST", "/runs", json=self._with_workspace(payload), retry=False)
|
|
352
368
|
return EvaluationRun(**data)
|
|
353
369
|
|
|
354
370
|
def append_results(
|
|
@@ -406,7 +422,10 @@ class EvaluationsClient:
|
|
|
406
422
|
# scorer's own per-result verdicts. Unknown names are a hard 400 from the engine.
|
|
407
423
|
if scorer:
|
|
408
424
|
params["scorer"] = scorer
|
|
409
|
-
|
|
425
|
+
# record=True is a server-side write despite the GET verb (it persists a gate-history
|
|
426
|
+
# row): a timeout after the row was stored would be retried into a duplicate verdict,
|
|
427
|
+
# so no transport retry - same precedent as init_run.
|
|
428
|
+
return self._request("GET", f"/runs/{run_id}/gate", params=params, retry=not record)
|
|
410
429
|
|
|
411
430
|
def analyze_run(
|
|
412
431
|
self,
|
|
@@ -432,8 +451,12 @@ class EvaluationsClient:
|
|
|
432
451
|
|
|
433
452
|
if not self._analysis_on_dashboard_router:
|
|
434
453
|
try:
|
|
454
|
+
# The self-host route runs the analysis SYNCHRONOUSLY (engine
|
|
455
|
+
# routes/evaluations.ts) - a short timeout with retries re-billed the whole
|
|
456
|
+
# multi-judge analysis up to 4x while the first was still running. Full
|
|
457
|
+
# analysis timeout, no transport retry.
|
|
435
458
|
return self._request(
|
|
436
|
-
"POST", f"/runs/{run_id}/analyze", json=payload, timeout=
|
|
459
|
+
"POST", f"/runs/{run_id}/analyze", json=payload, timeout=1800, retry=False
|
|
437
460
|
)
|
|
438
461
|
except AgentXEvaluationsError as exc:
|
|
439
462
|
if not self._note_missing_analysis_route(exc, "analyze"):
|
|
@@ -554,13 +577,17 @@ class EvaluationsClient:
|
|
|
554
577
|
if isinstance(dataset_id, dict): # populated reference, not a bare id
|
|
555
578
|
dataset_id = dataset_id.get("_id") or dataset_id.get("id")
|
|
556
579
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
580
|
+
# Built as one merged dict (explicit keys last, so they win) - passing the
|
|
581
|
+
# explicit keys as keyword arguments alongside **body raises "got multiple
|
|
582
|
+
# values" whenever the analysis body itself carries runId/datasetId/status/
|
|
583
|
+
# statistics.
|
|
584
|
+
return Report(**{
|
|
562
585
|
**body,
|
|
563
|
-
|
|
586
|
+
"runId": run_id,
|
|
587
|
+
"datasetId": dataset_id or "",
|
|
588
|
+
"status": envelope.get("status") or "completed",
|
|
589
|
+
"statistics": envelope.get("statistics"),
|
|
590
|
+
})
|
|
564
591
|
|
|
565
592
|
# ------------------------------------------------------------------
|
|
566
593
|
# Prompt improvement loop (examples -> propose -> publish). These ride the engine's
|
|
@@ -589,8 +616,11 @@ class EvaluationsClient:
|
|
|
589
616
|
payload["reasoning"] = reasoning
|
|
590
617
|
if based_on_version is not None:
|
|
591
618
|
payload["basedOnVersion"] = based_on_version
|
|
619
|
+
# Server-side write: a timeout after the version was stored would be
|
|
620
|
+
# retried into a duplicate version, so no transport retry.
|
|
592
621
|
return self._request(
|
|
593
|
-
"POST", f"/evaluate/prompts/{prompt_id}/versions", base=self._api_root, json=payload
|
|
622
|
+
"POST", f"/evaluate/prompts/{prompt_id}/versions", base=self._api_root, json=payload,
|
|
623
|
+
retry=False,
|
|
594
624
|
)
|
|
595
625
|
|
|
596
626
|
# ------------------------------------------------------------------
|
|
@@ -628,7 +658,7 @@ class EvaluationsClient:
|
|
|
628
658
|
payload["judgeModel"] = judge_model
|
|
629
659
|
if both_orders:
|
|
630
660
|
payload["bothOrders"] = True
|
|
631
|
-
response = self._request("POST", "/evaluate/runs/pairwise", json=payload, base=self._api_root)
|
|
661
|
+
response = self._request("POST", "/evaluate/runs/pairwise", json=payload, base=self._api_root, timeout=900, retry=False,)
|
|
632
662
|
return PairwiseComparison(**response["comparison"])
|
|
633
663
|
|
|
634
664
|
def get_pairwise(self, batch_id: str) -> PairwiseComparison:
|
|
@@ -662,7 +692,8 @@ class EvaluationsClient:
|
|
|
662
692
|
payload: dict = {"name": name, "definition": definition}
|
|
663
693
|
if description is not None:
|
|
664
694
|
payload["description"] = description
|
|
665
|
-
|
|
695
|
+
# Server-side write - no transport retry (see init_run's comment).
|
|
696
|
+
return self._request("POST", "/evaluate/tool-schemas", base=self._api_root, json=payload, retry=False)
|
|
666
697
|
|
|
667
698
|
def get_tool_schema_examples(self, tool_schema_id: str, window: Optional[str] = None) -> dict:
|
|
668
699
|
params = {"window": window} if window else None
|
|
@@ -686,8 +717,10 @@ class EvaluationsClient:
|
|
|
686
717
|
payload["reasoning"] = reasoning
|
|
687
718
|
if based_on_version is not None:
|
|
688
719
|
payload["basedOnVersion"] = based_on_version
|
|
720
|
+
# Server-side write - no transport retry (see init_run's comment).
|
|
689
721
|
return self._request(
|
|
690
|
-
"POST", f"/evaluate/tool-schemas/{tool_schema_id}/versions", base=self._api_root, json=payload
|
|
722
|
+
"POST", f"/evaluate/tool-schemas/{tool_schema_id}/versions", base=self._api_root, json=payload,
|
|
723
|
+
retry=False,
|
|
691
724
|
)
|
|
692
725
|
|
|
693
726
|
# ------------------------------------------------------------------
|
|
@@ -16,7 +16,13 @@ _REQUIRED_CSV_COLS = {"query"}
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class DatasetBuilder:
|
|
19
|
-
"""Fluent builder for creating a Custom Agent Evaluations dataset.
|
|
19
|
+
"""Fluent builder for creating a Custom Agent Evaluations dataset.
|
|
20
|
+
|
|
21
|
+
``judge_prompt``/``judge_model`` are LLM-as-judge overrides for the dataset's grading
|
|
22
|
+
config. NOTE (self-host): the engine's dataset-create route currently ignores both -
|
|
23
|
+
set them on a judge scorer / the evaluation settings instead. ``sovereignty_models``
|
|
24
|
+
is accepted on the wire but not acted on by the self-host engine.
|
|
25
|
+
"""
|
|
20
26
|
|
|
21
27
|
def __init__(
|
|
22
28
|
self,
|
|
@@ -50,6 +56,8 @@ class DatasetBuilder:
|
|
|
50
56
|
# LLM-as-judge overrides for this dataset's own grading config. Omit either to keep the
|
|
51
57
|
# server default (raw prompt template / gpt-5.6-luna, see EVALUATIONS.md). judge_model
|
|
52
58
|
# must be one of client.evaluations.list_models() (OpenAI or Anthropic).
|
|
59
|
+
# Self-host: the dataset-create route currently IGNORES judgePrompt/judgeModel - set
|
|
60
|
+
# them on a judge scorer / the evaluation settings instead (see class docstring).
|
|
53
61
|
if judge_prompt is not None:
|
|
54
62
|
self._payload["judgePrompt"] = judge_prompt
|
|
55
63
|
if judge_model is not None:
|
|
@@ -73,7 +81,8 @@ class DatasetBuilder:
|
|
|
73
81
|
self._payload["codeScorers"] = [
|
|
74
82
|
{
|
|
75
83
|
"id": scorer.get("id") or _uuid.uuid4().hex[:12],
|
|
76
|
-
|
|
84
|
+
# Name may be omitted - the engine defaults it, so don't KeyError here.
|
|
85
|
+
"name": scorer.get("name"),
|
|
77
86
|
"code": scorer["code"],
|
|
78
87
|
"enabled": scorer.get("enabled", True),
|
|
79
88
|
}
|
|
@@ -84,7 +93,8 @@ class DatasetBuilder:
|
|
|
84
93
|
if rouge_score:
|
|
85
94
|
self._payload["rougeScore"] = {"enabled": True}
|
|
86
95
|
# Sovereignty & Portability - the models to compare on this dataset (use
|
|
87
|
-
# client.evaluations.list_models() to discover valid ids).
|
|
96
|
+
# client.evaluations.list_models() to discover valid ids). Self-host: accepted on
|
|
97
|
+
# the wire but not acted on by the engine (see class docstring).
|
|
88
98
|
if sovereignty_models:
|
|
89
99
|
self._payload["sovereigntyIndex"] = {
|
|
90
100
|
"enabled": True,
|
|
@@ -354,10 +364,13 @@ class DatasetClient:
|
|
|
354
364
|
"acceptanceCriteria",
|
|
355
365
|
"rejectionCriteria",
|
|
356
366
|
"evaluationCriteria",
|
|
367
|
+
"judgePrompt",
|
|
368
|
+
"judgeModel",
|
|
357
369
|
"vectorSimilarity",
|
|
358
370
|
"jaccardSimilarity",
|
|
359
371
|
"bleuScore",
|
|
360
372
|
"rougeScore",
|
|
373
|
+
"sovereigntyIndex",
|
|
361
374
|
"codeScorers",
|
|
362
375
|
):
|
|
363
376
|
if wire.get(key) is not None:
|
|
@@ -64,7 +64,8 @@ class EvaluationSettingsBuilder:
|
|
|
64
64
|
if rouge_score:
|
|
65
65
|
self._payload["rougeScore"] = {"enabled": True}
|
|
66
66
|
# Sovereignty & Portability - the models to compare when this config runs
|
|
67
|
-
# (use client.evaluations.list_models() to discover valid ids).
|
|
67
|
+
# (use client.evaluations.list_models() to discover valid ids). Self-host: accepted
|
|
68
|
+
# on the wire but not acted on by the engine (same caveat as DatasetBuilder's).
|
|
68
69
|
if sovereignty_models:
|
|
69
70
|
self._payload["sovereigntyIndex"] = {
|
|
70
71
|
"enabled": True,
|
|
@@ -248,6 +248,10 @@ class LiveStatistics(BaseModel):
|
|
|
248
248
|
min_rating: Optional[float] = Field(default=None, alias="minRating")
|
|
249
249
|
max_rating: Optional[float] = Field(default=None, alias="maxRating")
|
|
250
250
|
rated_count: int = Field(default=0, alias="ratedCount")
|
|
251
|
+
# Rows the judge could not score / rows submitted with an error - the difference between
|
|
252
|
+
# "everything rated 8" and "half the run never got a verdict".
|
|
253
|
+
skipped_count: int = Field(default=0, alias="skippedCount")
|
|
254
|
+
failed_count: int = Field(default=0, alias="failedCount")
|
|
251
255
|
|
|
252
256
|
class Config:
|
|
253
257
|
populate_by_name = True
|
|
@@ -17,9 +17,9 @@ class PromptClient:
|
|
|
17
17
|
existing version-comparison view (``client.evaluations`` run comparisons on a dataset) can
|
|
18
18
|
tell you which published version actually scored higher.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
``propose()`` never publishes anything; ``publish_version()`` IS the explicit approval
|
|
21
|
+
step - call it only after a human reviewed the proposal, since a published version is
|
|
22
|
+
what your running agent pulls as its live prompt.
|
|
23
23
|
|
|
24
24
|
Example::
|
|
25
25
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
3
5
|
from agentx.evaluations.models import Report
|
|
4
6
|
from agentx.evaluations._term import (
|
|
5
7
|
bold,
|
|
@@ -21,14 +23,14 @@ _RATING_ICONS = {"high": "●", "medium": "◑", "low": "○"}
|
|
|
21
23
|
_PRI_COLORS = {"high": red, "medium": yellow, "low": dim}
|
|
22
24
|
|
|
23
25
|
|
|
24
|
-
def _rating_badge(rating: str
|
|
26
|
+
def _rating_badge(rating: Optional[str]) -> str:
|
|
25
27
|
icon = _RATING_ICONS.get(rating or "", "·")
|
|
26
28
|
color = _RATING_COLORS.get(rating or "", dim)
|
|
27
29
|
label = (rating or "").upper()
|
|
28
30
|
return color(f"{icon} {label}") if label else dim(icon)
|
|
29
31
|
|
|
30
32
|
|
|
31
|
-
def _section(title: str, rating: str
|
|
33
|
+
def _section(title: str, rating: Optional[str] = None) -> None:
|
|
32
34
|
badge = f" {_rating_badge(rating)}" if rating else ""
|
|
33
35
|
print(f"\n{bold(title)}{badge}")
|
|
34
36
|
print(dim(_THIN))
|
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
import time
|
|
6
|
+
|
|
7
|
+
import requests
|
|
6
8
|
import uuid
|
|
7
9
|
from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Union
|
|
8
10
|
|
|
@@ -304,6 +306,12 @@ class EvaluationRunContext:
|
|
|
304
306
|
resp.failed_validation,
|
|
305
307
|
)
|
|
306
308
|
return
|
|
309
|
+
except requests.Timeout as exc:
|
|
310
|
+
# A read timeout means the engine may STILL be scoring this batch - a
|
|
311
|
+
# retry re-POSTs it and double-bills every judge call (idempotency keys
|
|
312
|
+
# protect rows already inserted, not judge work mid-flight). Fail loud.
|
|
313
|
+
last_exc = exc
|
|
314
|
+
break
|
|
307
315
|
except Exception as exc:
|
|
308
316
|
last_exc = exc
|
|
309
317
|
if attempt == 1:
|
|
@@ -334,6 +342,10 @@ class EvaluationRunContext:
|
|
|
334
342
|
# ------------------------------------------------------------------
|
|
335
343
|
|
|
336
344
|
def finalize(self) -> "EvaluationRunContext":
|
|
345
|
+
"""Mark the run completed server-side. A failed finalize is raised, not swallowed
|
|
346
|
+
(same fail-loud posture as _flush_batch): it leaves the run in_progress - a state a
|
|
347
|
+
CI pipeline MUST treat as a failure, since gates and baselines only consider
|
|
348
|
+
completed runs."""
|
|
337
349
|
_say()
|
|
338
350
|
with Spinner("Finalizing - submitting results"):
|
|
339
351
|
try:
|
|
@@ -345,6 +357,7 @@ class EvaluationRunContext:
|
|
|
345
357
|
except Exception as exc:
|
|
346
358
|
_say(f" {red('✗')} Finalize failed: {dim(str(exc))}")
|
|
347
359
|
logger.error("Finalize failed: %s", exc)
|
|
360
|
+
raise
|
|
348
361
|
return self
|
|
349
362
|
|
|
350
363
|
def gate(
|
|
@@ -167,6 +167,9 @@ class AgentXAutoGenObserver:
|
|
|
167
167
|
"end_time": end_t,
|
|
168
168
|
"input": pending["input"] if pending else None,
|
|
169
169
|
"output": f"ERROR: {output}" if is_error else (str(output) if output is not None else None),
|
|
170
|
+
# The engine's failure test is success === false; without
|
|
171
|
+
# this a failed tool call would read as passing.
|
|
172
|
+
"success": not is_error,
|
|
170
173
|
})
|
|
171
174
|
continue
|
|
172
175
|
|
|
@@ -127,6 +127,16 @@ class AgentXCrewObserver:
|
|
|
127
127
|
except ImportError:
|
|
128
128
|
return task_timings, lambda: None
|
|
129
129
|
|
|
130
|
+
# Double-instrumentation guard (bus-keyed latch, the same idea as the
|
|
131
|
+
# other integrations' _agentx_patched flag): the event bus is a global
|
|
132
|
+
# singleton, so a notebook re-run or an overlapping kickoff that
|
|
133
|
+
# already has AgentX listeners registered would otherwise get a second
|
|
134
|
+
# set and duplicate every task span. When already attached, this
|
|
135
|
+
# kickoff just falls back to the evenly-divided timing approximation.
|
|
136
|
+
if getattr(crewai_event_bus, "_agentx_attached", False):
|
|
137
|
+
return task_timings, lambda: None
|
|
138
|
+
crewai_event_bus._agentx_attached = True
|
|
139
|
+
|
|
130
140
|
def on_task_started(source: Any, event: Any) -> None:
|
|
131
141
|
task_id = getattr(event, "task_id", None)
|
|
132
142
|
if task_id is None:
|
|
@@ -154,9 +164,14 @@ class AgentXCrewObserver:
|
|
|
154
164
|
crewai_event_bus.on(TaskFailedEvent)(on_task_failed)
|
|
155
165
|
|
|
156
166
|
def unregister() -> None:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
167
|
+
try:
|
|
168
|
+
crewai_event_bus.off(TaskStartedEvent, on_task_started)
|
|
169
|
+
crewai_event_bus.off(TaskCompletedEvent, on_task_completed)
|
|
170
|
+
crewai_event_bus.off(TaskFailedEvent, on_task_failed)
|
|
171
|
+
finally:
|
|
172
|
+
# Clear the latch even if .off() raises, so a later kickoff
|
|
173
|
+
# can re-attach instead of being locked out forever.
|
|
174
|
+
crewai_event_bus._agentx_attached = False
|
|
160
175
|
|
|
161
176
|
return task_timings, unregister
|
|
162
177
|
|
|
@@ -197,6 +212,9 @@ class AgentXCrewObserver:
|
|
|
197
212
|
"end_time": end,
|
|
198
213
|
"input": description,
|
|
199
214
|
"output": output_text,
|
|
215
|
+
# A CrewAI task is an agent turn, not a model call - without this,
|
|
216
|
+
# _merge_child_run's default stamped every task span "llm".
|
|
217
|
+
"kind": "agent",
|
|
200
218
|
})
|
|
201
219
|
if description is not None or task_output_text is not None:
|
|
202
220
|
tool_calls.append({"name": name, "input": description, "output": task_output_text})
|
|
@@ -231,6 +231,7 @@ class AgentXADKPlugin(BasePlugin):
|
|
|
231
231
|
model=call_start.get("model") if call_start else None,
|
|
232
232
|
input_tokens=call_input_tokens,
|
|
233
233
|
output_tokens=call_output_tokens,
|
|
234
|
+
span_kind="llm",
|
|
234
235
|
)
|
|
235
236
|
|
|
236
237
|
async def on_model_error_callback(
|
|
@@ -261,6 +262,7 @@ class AgentXADKPlugin(BasePlugin):
|
|
|
261
262
|
output=f"ERROR: {error}",
|
|
262
263
|
model=call_start.get("model") if call_start else None,
|
|
263
264
|
error=str(error),
|
|
265
|
+
span_kind="llm",
|
|
264
266
|
)
|
|
265
267
|
|
|
266
268
|
# ------------------------------------------------------------------
|
|
@@ -290,7 +292,8 @@ class AgentXADKPlugin(BasePlugin):
|
|
|
290
292
|
tool_input = _safe_serialize(tool_args)
|
|
291
293
|
tool_output = str(result) if result is not None else None
|
|
292
294
|
state["root_span"].child_span(
|
|
293
|
-
tool_name, start_time=start_t, end_time=end_t, input=tool_input, output=tool_output
|
|
295
|
+
tool_name, start_time=start_t, end_time=end_t, input=tool_input, output=tool_output,
|
|
296
|
+
span_kind="tool",
|
|
294
297
|
)
|
|
295
298
|
|
|
296
299
|
async def on_tool_error_callback(
|
|
@@ -311,5 +314,6 @@ class AgentXADKPlugin(BasePlugin):
|
|
|
311
314
|
tool_input = _safe_serialize(tool_args)
|
|
312
315
|
tool_output = f"ERROR: {error}"
|
|
313
316
|
state["root_span"].child_span(
|
|
314
|
-
tool_name, start_time=start_t, end_time=end_t, input=tool_input, output=tool_output, error=str(error)
|
|
317
|
+
tool_name, start_time=start_t, end_time=end_t, input=tool_input, output=tool_output, error=str(error),
|
|
318
|
+
span_kind="tool",
|
|
315
319
|
)
|
|
@@ -277,6 +277,10 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
277
277
|
become child spans, and each LLM call / tool call / retrieval becomes a span parented under
|
|
278
278
|
the node that ran it - so the engine's Execution Timeline shows the actual graph trajectory
|
|
279
279
|
(which nodes ran, in what order, and what each did), not a flat step list.
|
|
280
|
+
|
|
281
|
+
Retriever runs are stamped ``retrieval`` - LangChain cannot distinguish memory-backed
|
|
282
|
+
retrievers, so a Mem0/Zep-style store exposed as a retriever classifies as retrieval too.
|
|
283
|
+
Use ``tracer.trace_memory`` for lookups that should classify as memory.
|
|
280
284
|
"""
|
|
281
285
|
|
|
282
286
|
def __init__(
|
|
@@ -339,6 +343,16 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
339
343
|
self._retrieval_starts.pop(run_id, None)
|
|
340
344
|
self._parents.pop(run_id, None)
|
|
341
345
|
|
|
346
|
+
# Pre-run retrieval steps waiting for a top-level chain that never came
|
|
347
|
+
# (e.g. retriever.invoke() called but agent.invoke() aborted before
|
|
348
|
+
# on_chain_start). Each step carries its own start_time, so drop the
|
|
349
|
+
# pre-cutoff ones just like the run_id-keyed structures above.
|
|
350
|
+
with self._state_lock:
|
|
351
|
+
if self._pending_retrieval_steps:
|
|
352
|
+
self._pending_retrieval_steps[:] = [
|
|
353
|
+
step for step in self._pending_retrieval_steps if step.get("start_time", 0) >= cutoff
|
|
354
|
+
]
|
|
355
|
+
|
|
342
356
|
# ------------------------------------------------------------------
|
|
343
357
|
# Chain lifecycle
|
|
344
358
|
# ------------------------------------------------------------------
|
|
@@ -359,8 +373,9 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
359
373
|
self._prune_stale_entries()
|
|
360
374
|
# Consume any retrieval steps that ran before this chain started
|
|
361
375
|
# (pre-run RAG: retriever.invoke() called before agent.invoke())
|
|
362
|
-
|
|
363
|
-
|
|
376
|
+
with self._state_lock:
|
|
377
|
+
pending = self._pending_retrieval_steps[:]
|
|
378
|
+
self._pending_retrieval_steps.clear()
|
|
364
379
|
self._runs[run_id] = {
|
|
365
380
|
"start": time.time(),
|
|
366
381
|
"input": _extract_input(inputs),
|
|
@@ -670,7 +685,7 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
670
685
|
"input": _extract_llm_input(prompts=prompts, messages=messages),
|
|
671
686
|
}
|
|
672
687
|
top = self._find_top_ancestor(parent_run_id)
|
|
673
|
-
if top and not self._runs[top].get("model") and model:
|
|
688
|
+
if top and top in self._runs and not self._runs[top].get("model") and model:
|
|
674
689
|
self._runs[top]["model"] = model
|
|
675
690
|
|
|
676
691
|
def on_llm_start(
|
|
@@ -882,15 +897,16 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
882
897
|
step["output"] = "\n\n---\n\n".join(contents)
|
|
883
898
|
|
|
884
899
|
top = self._find_top_ancestor(parent_run_id)
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
900
|
+
with self._state_lock:
|
|
901
|
+
if top and top in self._runs:
|
|
902
|
+
# Retriever ran inside an active chain - attach directly
|
|
903
|
+
retrievals = self._runs[top]["retrieval_steps"]
|
|
904
|
+
step["name"] = f"Retrieval {len(retrievals) + 1}"
|
|
905
|
+
retrievals.append(step)
|
|
906
|
+
else:
|
|
907
|
+
# Retriever ran before the chain started (pre-run RAG pattern)
|
|
908
|
+
step["name"] = f"Retrieval {len(self._pending_retrieval_steps) + 1}"
|
|
909
|
+
self._pending_retrieval_steps.append(step)
|
|
894
910
|
|
|
895
911
|
def on_retriever_error(
|
|
896
912
|
self,
|
|
@@ -918,15 +934,16 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
918
934
|
step["query"] = query
|
|
919
935
|
|
|
920
936
|
top = self._find_top_ancestor(parent_run_id)
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
937
|
+
with self._state_lock:
|
|
938
|
+
if top and top in self._runs:
|
|
939
|
+
# Retriever ran inside an active chain - attach directly
|
|
940
|
+
retrievals = self._runs[top]["retrieval_steps"]
|
|
941
|
+
step["name"] = f"Retrieval {len(retrievals) + 1}"
|
|
942
|
+
retrievals.append(step)
|
|
943
|
+
else:
|
|
944
|
+
# Retriever ran before the chain started (pre-run RAG pattern)
|
|
945
|
+
step["name"] = f"Retrieval {len(self._pending_retrieval_steps) + 1}"
|
|
946
|
+
self._pending_retrieval_steps.append(step)
|
|
930
947
|
|
|
931
948
|
# ------------------------------------------------------------------
|
|
932
949
|
# Helpers
|