agentx-python 0.8.22__tar.gz → 0.8.23__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.22 → agentx_python-0.8.23}/PKG-INFO +8 -1
- {agentx_python-0.8.22 → agentx_python-0.8.23}/README.md +7 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/client.py +21 -4
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/evaluation_settings.py +14 -1
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/models.py +4 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/reporting.py +1 -1
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/results.py +10 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/runner.py +72 -5
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/integrations/_traced_call.py +5 -1
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/integrations/autogen.py +4 -1
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/integrations/crewai.py +4 -1
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/integrations/langchain.py +4 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/integrations/llamaindex.py +6 -1
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/monitor/judge_scorers.py +13 -4
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/monitor/models.py +7 -5
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/tracing/ingest_client.py +17 -4
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/tracing/tracer.py +2 -2
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/version.py +2 -2
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx_python.egg-info/PKG-INFO +8 -1
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_docs_match_sdk.py +15 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_judge_scorers.py +33 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_selfhost_analysis_fallback.py +21 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/LICENSE +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/__init__.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/agentx.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/cli.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/__init__.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/_term.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/adapters/__init__.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/adapters/http_endpoint.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/adapters/precomputed.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/adapters/raw.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/datasets.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/prompts.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/tool_schemas.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/evaluations/tracing.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/exceptions.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/export.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/feedback.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/integrations/__init__.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/integrations/anthropic.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/integrations/databricks.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/integrations/google_adk.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/integrations/google_genai.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/integrations/litellm.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/integrations/moveworks.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/integrations/openai.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/integrations/openai_agents.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/monitor/__init__.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/monitor/agents.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/monitor/client.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/monitor/improvement_groups.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/monitor/online_evaluators.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/monitor/patterns.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/monitor/profile.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/monitor/review_queue.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/monitor/rules.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/monitor/scorer_groups.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/monitor/scorers.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/monitor/sessions.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/monitor/signals.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/outcomes.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/projects.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/py.typed +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/resources/__init__.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/resources/agent.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/resources/conversation.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/resources/workforce.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/testing.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/traces.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/tracing/__init__.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/tracing/ci_types.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/tracing/eval_scope.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/tracing/framework_detect.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx/util.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx_python.egg-info/SOURCES.txt +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx_python.egg-info/dependency_links.txt +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx_python.egg-info/entry_points.txt +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx_python.egg-info/not-zip-safe +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx_python.egg-info/requires.txt +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/agentx_python.egg-info/top_level.txt +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/setup.cfg +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/setup.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_cli_launcher.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_deep_dive_fixes.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_error_taxonomy.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_eval_scope.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_framework_detect.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_integration.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_integrations.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_multi_judge.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_pairwise.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_review_queue.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_runner_features.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_selfhost_compat.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_span_tree.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_testing.py +0 -0
- {agentx_python-0.8.22 → agentx_python-0.8.23}/tests/test_wire_models.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentx-python
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.23
|
|
4
4
|
Summary: Official Python SDK for AgentX (https://www.agentx.so/)
|
|
5
5
|
Home-page: https://github.com/AgentX-ai/AgentX-python
|
|
6
6
|
Author: Robin Wang and AgentX Team
|
|
@@ -255,6 +255,13 @@ extra:
|
|
|
255
255
|
| LlamaIndex | `pip install "agentx-python[llamaindex]"` | `AgentXLlamaIndexHandler` |
|
|
256
256
|
| AutoGen | `pip install "agentx-python[autogen]"` | `AgentXAutoGenObserver` |
|
|
257
257
|
|
|
258
|
+
> **Warning: pick ONE instrumentation layer per LLM call.** Do not combine
|
|
259
|
+
> `AgentXCallbackHandler` (or any framework integration) with a patched provider client
|
|
260
|
+
> (`patch_openai_client`, `patch_anthropic_client`, `patch_genai_client`) on the same code
|
|
261
|
+
> path. A patched call that runs outside an active span emits its own root trace, so every
|
|
262
|
+
> LLM call the framework already traces gets a duplicate trace - and its cost is counted
|
|
263
|
+
> twice.
|
|
264
|
+
|
|
258
265
|
Two more platforms are covered by **pull importers** rather than in-process hooks, each with its
|
|
259
266
|
own CLI: `agentx-moveworks` (Moveworks Data API sync, no extra needed) and `agentx-databricks`
|
|
260
267
|
(`pip install "agentx-python[databricks]"`, MLflow/Databricks trace sync).
|
|
@@ -190,6 +190,13 @@ extra:
|
|
|
190
190
|
| LlamaIndex | `pip install "agentx-python[llamaindex]"` | `AgentXLlamaIndexHandler` |
|
|
191
191
|
| AutoGen | `pip install "agentx-python[autogen]"` | `AgentXAutoGenObserver` |
|
|
192
192
|
|
|
193
|
+
> **Warning: pick ONE instrumentation layer per LLM call.** Do not combine
|
|
194
|
+
> `AgentXCallbackHandler` (or any framework integration) with a patched provider client
|
|
195
|
+
> (`patch_openai_client`, `patch_anthropic_client`, `patch_genai_client`) on the same code
|
|
196
|
+
> path. A patched call that runs outside an active span emits its own root trace, so every
|
|
197
|
+
> LLM call the framework already traces gets a duplicate trace - and its cost is counted
|
|
198
|
+
> twice.
|
|
199
|
+
|
|
193
200
|
Two more platforms are covered by **pull importers** rather than in-process hooks, each with its
|
|
194
201
|
own CLI: `agentx-moveworks` (Moveworks Data API sync, no extra needed) and `agentx-databricks`
|
|
195
202
|
(`pip install "agentx-python[databricks]"`, MLflow/Databricks trace sync).
|
|
@@ -221,7 +221,16 @@ class EvaluationsClient:
|
|
|
221
221
|
the Sovereignty & Portability Index. Pass ``provider`` (e.g. "Google")
|
|
222
222
|
to filter."""
|
|
223
223
|
params = {"provider": provider} if provider else None
|
|
224
|
-
|
|
224
|
+
try:
|
|
225
|
+
data = self._request("GET", "/models", params=params)
|
|
226
|
+
except AgentXEvaluationsError as exc:
|
|
227
|
+
if exc.status_code == 404:
|
|
228
|
+
raise AgentXEvaluationsError(
|
|
229
|
+
"list_models is hosted-only; on self-host pass any model id your judge "
|
|
230
|
+
"key can reach, or use client.monitor.* portability models",
|
|
231
|
+
status_code=404,
|
|
232
|
+
) from exc
|
|
233
|
+
raise
|
|
225
234
|
items = data if isinstance(data, list) else data.get("models", [])
|
|
226
235
|
return [ModelInfo(**m) for m in items]
|
|
227
236
|
|
|
@@ -546,12 +555,20 @@ class EvaluationsClient:
|
|
|
546
555
|
) -> bool:
|
|
547
556
|
"""Return True if ``exc`` is the 404 that means "this engine is self-host".
|
|
548
557
|
|
|
549
|
-
Only a 404 qualifies. Anything else - auth, validation, a 500, a dead
|
|
550
|
-
is a real failure on a route that does exist, and must propagate rather
|
|
551
|
-
retried against a different endpoint that would mask it.
|
|
558
|
+
Only a route-level 404 qualifies. Anything else - auth, validation, a 500, a dead
|
|
559
|
+
connection - is a real failure on a route that does exist, and must propagate rather
|
|
560
|
+
than be retried against a different endpoint that would mask it.
|
|
561
|
+
|
|
562
|
+
A resource 404 does not qualify either: the engine's SDK router answers these routes
|
|
563
|
+
with bodies naming the missing resource ("Run not found" / "No analysis found for
|
|
564
|
+
this run"), so latching on one would permanently reroute every later analysis call
|
|
565
|
+
to the dashboard router because a caller once passed a wrong run id.
|
|
552
566
|
"""
|
|
553
567
|
if exc.status_code != 404:
|
|
554
568
|
return False
|
|
569
|
+
body = str(exc)
|
|
570
|
+
if "Run not found" in body or "No analysis found for this run" in body:
|
|
571
|
+
return False
|
|
555
572
|
if self._analysis_on_dashboard_router is None:
|
|
556
573
|
logger.info(
|
|
557
574
|
"%s is not served from %s; using the dashboard router at %s "
|
|
@@ -74,8 +74,21 @@ class EvaluationSettingsBuilder:
|
|
|
74
74
|
# Sandboxed JS scorers run per result alongside the judge - each entry is
|
|
75
75
|
# {"name": ..., "enabled": True, "code": "..."} where the code is a JS function body
|
|
76
76
|
# receiving (input, output, expected, toolCalls) and returning {score, reasoning}.
|
|
77
|
+
# Normalized the same way DatasetBuilder does: id defaulted, name optional (the
|
|
78
|
+
# engine defaults it), enabled default True - raw pass-through sent entries the
|
|
79
|
+
# engine's shape validation rejects.
|
|
77
80
|
if code_scorers:
|
|
78
|
-
|
|
81
|
+
import uuid as _uuid
|
|
82
|
+
|
|
83
|
+
self._payload["codeScorers"] = [
|
|
84
|
+
{
|
|
85
|
+
"id": scorer.get("id") or _uuid.uuid4().hex[:12],
|
|
86
|
+
"name": scorer.get("name"),
|
|
87
|
+
"code": scorer["code"],
|
|
88
|
+
"enabled": scorer.get("enabled", True),
|
|
89
|
+
}
|
|
90
|
+
for scorer in code_scorers
|
|
91
|
+
]
|
|
79
92
|
|
|
80
93
|
def publish(self) -> EvaluationSettings:
|
|
81
94
|
logger.info("Publishing evaluation settings '%s'", self._payload["name"])
|
|
@@ -51,6 +51,10 @@ class TestCase(BaseModel):
|
|
|
51
51
|
expected_knowledge_base: Optional[List[str]] = Field(default=None, alias="expectedKnowledgeBase")
|
|
52
52
|
expected_delegations: Optional[List[str]] = Field(default=None, alias="expectedDelegations")
|
|
53
53
|
judge_guideline: Optional[str] = Field(default=None, alias="judgeGuideline")
|
|
54
|
+
# Engine-side trajectory match (e.g. {"tools": ["search"], "mode": "in_order"}) and the
|
|
55
|
+
# expected retrieval context for RAG grading - carried so import_dataset round-trips them.
|
|
56
|
+
expected_trajectory: Optional[Dict[str, Any]] = Field(default=None, alias="expectedTrajectory")
|
|
57
|
+
expected_retrieval_context: Optional[Any] = Field(default=None, alias="expectedRetrievalContext")
|
|
54
58
|
smoke_test: Optional[SmokeTestSettings] = Field(default=None, alias="smokeTest")
|
|
55
59
|
# Named subsets this case belongs to (e.g. ["smoke"], ["full", "regression"]).
|
|
56
60
|
# ``run(dataset_id, split="smoke")`` runs only cases tagged with that split.
|
|
@@ -170,7 +170,7 @@ def print_report(report: Report) -> None:
|
|
|
170
170
|
|
|
171
171
|
# --- Low-scoring cases ---
|
|
172
172
|
if report.low_scoring_cases:
|
|
173
|
-
_section("Low-scoring Cases (rating
|
|
173
|
+
_section("Low-scoring Cases (rating <= 5)")
|
|
174
174
|
for case in report.low_scoring_cases[:5]:
|
|
175
175
|
q = (case.get("query") or case.get("questionText", ""))[:80]
|
|
176
176
|
rating = case.get("rating", "?")
|
|
@@ -87,6 +87,16 @@ def normalize_result(
|
|
|
87
87
|
else:
|
|
88
88
|
output = {"text": str(raw)} if raw is not None else {"text": ""}
|
|
89
89
|
|
|
90
|
+
if error is None and (
|
|
91
|
+
output is None
|
|
92
|
+
or (set(output) <= {"text"} and not str(output.get("text") or "").strip())
|
|
93
|
+
):
|
|
94
|
+
# An empty output with no error would fail the engine's row validation and silently
|
|
95
|
+
# vanish from the run - store it as an explicit failed row instead.
|
|
96
|
+
error = ResultError(type="EmptyOutput", message="Agent returned no output")
|
|
97
|
+
if output is None:
|
|
98
|
+
output = {"text": ""}
|
|
99
|
+
|
|
90
100
|
has_timings = (
|
|
91
101
|
latency_ms is not None or input_tokens is not None or output_tokens is not None
|
|
92
102
|
)
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
import sys
|
|
5
6
|
import time
|
|
6
7
|
|
|
7
8
|
import requests
|
|
@@ -307,9 +308,23 @@ class EvaluationRunContext:
|
|
|
307
308
|
# generator happens to be garbage-collected.
|
|
308
309
|
if results_iter is not None:
|
|
309
310
|
results_iter.close()
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
# Flush the trailing partial batch HERE, not after the try: a mid-run exception
|
|
312
|
+
# (agent crash, Ctrl-C) used to discard up to max_batch - 1 already-paid-for
|
|
313
|
+
# results still waiting in it.
|
|
314
|
+
if batch:
|
|
315
|
+
propagating = sys.exc_info()[1]
|
|
316
|
+
try:
|
|
317
|
+
self._flush_batch(batch)
|
|
318
|
+
except Exception as flush_exc:
|
|
319
|
+
if propagating is None:
|
|
320
|
+
raise
|
|
321
|
+
# An exception is already propagating out of the loop - a flush failure
|
|
322
|
+
# here must not mask it.
|
|
323
|
+
logger.error(
|
|
324
|
+
"Trailing batch flush failed while handling %r: %s",
|
|
325
|
+
propagating,
|
|
326
|
+
flush_exc,
|
|
327
|
+
)
|
|
313
328
|
|
|
314
329
|
return self
|
|
315
330
|
|
|
@@ -326,6 +341,15 @@ class EvaluationRunContext:
|
|
|
326
341
|
_say(
|
|
327
342
|
f" {green('✓')} Scored {resp.accepted} result{'s' if resp.accepted != 1 else ''}"
|
|
328
343
|
)
|
|
344
|
+
if resp.failed_validation > 0:
|
|
345
|
+
# The engine accepts the batch but silently drops rows that fail its
|
|
346
|
+
# validation (typically empty output and no error) - say so, or those
|
|
347
|
+
# cases just vanish from the report.
|
|
348
|
+
_say(
|
|
349
|
+
f" {yellow('!')} {resp.failed_validation} result"
|
|
350
|
+
f"{'s' if resp.failed_validation != 1 else ''} failed validation "
|
|
351
|
+
"(empty output and no error) and did not get stored"
|
|
352
|
+
)
|
|
329
353
|
logger.info(
|
|
330
354
|
"Batch %s: accepted=%d duplicates=%d failed=%d",
|
|
331
355
|
batch_id[:8],
|
|
@@ -415,8 +439,11 @@ class EvaluationRunContext:
|
|
|
415
439
|
``no_regression=True`` fails it when the average dropped more than ``tolerance``
|
|
416
440
|
(default 0.5, judge scores are noisy) below the dataset's previous completed run.
|
|
417
441
|
At least one check is required. On a multi-judge run, ``scorer`` (an additional
|
|
418
|
-
scorer's id or name, e.g. ``scorer="Safety"``) gates that scorer's own average
|
|
419
|
-
instead of the primary's - "fail if Safety is low even when the average looks fine".
|
|
442
|
+
judge scorer's id or name, e.g. ``scorer="Safety"``) gates that scorer's own average
|
|
443
|
+
instead of the primary's - "fail if Safety is low even when the average looks fine".
|
|
444
|
+
Only judge scorers resolve here: deterministic scorer-group members (pattern/code
|
|
445
|
+
kinds) have no per-run judge average, so naming one is rejected by the engine.
|
|
446
|
+
Prints a CI-log-friendly verdict and returns a
|
|
420
447
|
:class:`GateResult` - the caller decides the exit code::
|
|
421
448
|
|
|
422
449
|
report = client.evaluations.run(...).execute(my_agent).finalize()
|
|
@@ -469,6 +496,16 @@ class EvaluationRunContext:
|
|
|
469
496
|
"""Number of submitted results that have received a rating so far."""
|
|
470
497
|
return self._live_stats.rated_count if self._live_stats else 0
|
|
471
498
|
|
|
499
|
+
@property
|
|
500
|
+
def skipped_count(self) -> int:
|
|
501
|
+
"""Number of submitted results the judge could not score."""
|
|
502
|
+
return self._live_stats.skipped_count if self._live_stats else 0
|
|
503
|
+
|
|
504
|
+
@property
|
|
505
|
+
def failed_count(self) -> int:
|
|
506
|
+
"""Number of submitted results that carried an error."""
|
|
507
|
+
return self._live_stats.failed_count if self._live_stats else 0
|
|
508
|
+
|
|
472
509
|
@property
|
|
473
510
|
def average_rating(self) -> Optional[float]:
|
|
474
511
|
"""Live average rating across all results scored so far. Populated as
|
|
@@ -639,6 +676,36 @@ class EvaluationsRunner:
|
|
|
639
676
|
script execution)."""
|
|
640
677
|
return self._client.get_analysis_status(run_id)
|
|
641
678
|
|
|
679
|
+
# Run-lifecycle calls by id - the standalone forms of what run()/execute()/finalize()/
|
|
680
|
+
# analyze() drive for you, for scripts operating on a run created elsewhere.
|
|
681
|
+
|
|
682
|
+
def init_run(self, dataset_id: str, subject, **kwargs):
|
|
683
|
+
"""Create a run row without executing anything - the standalone form of :meth:`run`.
|
|
684
|
+
Accepts the same kwargs as ``EvaluationsClient.init_run``."""
|
|
685
|
+
return self._client.init_run(dataset_id, subject, **kwargs)
|
|
686
|
+
|
|
687
|
+
def append_results(self, run_id: str, batch_id: str, results: list):
|
|
688
|
+
"""Submit one batch of results to a run by id (scored synchronously server-side)."""
|
|
689
|
+
return self._client.append_results(run_id, batch_id, results)
|
|
690
|
+
|
|
691
|
+
def finalize_run(self, run_id: str) -> dict:
|
|
692
|
+
"""Mark a run completed by id - the standalone form of
|
|
693
|
+
``EvaluationRunContext.finalize()``."""
|
|
694
|
+
return self._client.finalize_run(run_id)
|
|
695
|
+
|
|
696
|
+
def analyze_run(self, run_id: str, **kwargs) -> dict:
|
|
697
|
+
"""Start the LLM analysis of a finalized run by id; poll
|
|
698
|
+
:meth:`get_analysis_status`, then :meth:`get_report`."""
|
|
699
|
+
return self._client.analyze_run(run_id, **kwargs)
|
|
700
|
+
|
|
701
|
+
def get_report(self, run_id: str):
|
|
702
|
+
"""The analyzed report for a run by id, once analysis has finished."""
|
|
703
|
+
return self._client.get_report(run_id)
|
|
704
|
+
|
|
705
|
+
def get_submitted_keys(self, run_id: str) -> list:
|
|
706
|
+
"""Idempotency keys a run has already accepted - what execute() uses to resume."""
|
|
707
|
+
return self._client.get_submitted_keys(run_id)
|
|
708
|
+
|
|
642
709
|
def gate_run(
|
|
643
710
|
self,
|
|
644
711
|
run_id: str,
|
|
@@ -153,7 +153,11 @@ def finish_llm_call(
|
|
|
153
153
|
)
|
|
154
154
|
return
|
|
155
155
|
|
|
156
|
-
span
|
|
156
|
+
# A patched provider call outside any active span becomes its own root trace - it is a bare
|
|
157
|
+
# model call, so stamp it "llm" rather than leaving the kind unset.
|
|
158
|
+
span = tracer.trace(
|
|
159
|
+
name, metadata=metadata, framework=framework, model=model, session_id=session_id, span_kind="llm"
|
|
160
|
+
)
|
|
157
161
|
span.__enter__()
|
|
158
162
|
span._start = start_t
|
|
159
163
|
span.input = input_repr
|
|
@@ -107,7 +107,10 @@ class AgentXAutoGenObserver:
|
|
|
107
107
|
# explicit return/break/continue there would silently swallow any exception
|
|
108
108
|
# propagating from agent_or_team.run() above (see crewai.py's kickoff() for the same
|
|
109
109
|
# hazard spelled out in full).
|
|
110
|
-
|
|
110
|
+
# span_kind="agent": the root of a standalone team/agent run is the agent run itself.
|
|
111
|
+
with self._tracer.trace(
|
|
112
|
+
self._name, metadata=self._metadata, session_id=self._session_id, span_kind="agent"
|
|
113
|
+
) as span:
|
|
111
114
|
span._start = start_t
|
|
112
115
|
if error:
|
|
113
116
|
span.set_error(error)
|
|
@@ -93,7 +93,10 @@ class AgentXCrewObserver:
|
|
|
93
93
|
# `return` here (this whole method body runs inside the try's `finally`) - an
|
|
94
94
|
# explicit return/break/continue in a finally block silently swallows any exception
|
|
95
95
|
# propagating from crew.kickoff() above.
|
|
96
|
-
|
|
96
|
+
# span_kind="agent": the root of a standalone crew kickoff is the agent run itself.
|
|
97
|
+
with self._tracer.trace(
|
|
98
|
+
self._name, metadata=self._metadata, session_id=self._session_id, span_kind="agent"
|
|
99
|
+
) as span:
|
|
97
100
|
span._start = start
|
|
98
101
|
if error:
|
|
99
102
|
span.set_error(error)
|
|
@@ -595,6 +595,9 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
595
595
|
else self._metadata
|
|
596
596
|
),
|
|
597
597
|
session_id=self._session_id,
|
|
598
|
+
# The root of a standalone chain/agent invocation is the agent run itself,
|
|
599
|
+
# not one of its llm/tool/retrieval children.
|
|
600
|
+
span_kind="agent",
|
|
598
601
|
) as span:
|
|
599
602
|
# __enter__ just set _start to "now" - overridden to the chain's real start time,
|
|
600
603
|
# see llamaindex.py's _send_trace for the identical fix and full rationale.
|
|
@@ -655,6 +658,7 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
655
658
|
else self._metadata
|
|
656
659
|
),
|
|
657
660
|
session_id=self._session_id,
|
|
661
|
+
span_kind="agent",
|
|
658
662
|
) as span:
|
|
659
663
|
span._start = state["start"]
|
|
660
664
|
span.set_error(str(error))
|
|
@@ -359,7 +359,12 @@ class AgentXLlamaIndexHandler(BaseCallbackHandler):
|
|
|
359
359
|
# tool_calls loop reads latency_ms for duration and the timestamps for position, so each
|
|
360
360
|
# tool call lands correctly in the tree panel instead of defaulting to offset 0.
|
|
361
361
|
with self._tracer.trace(
|
|
362
|
-
self._name,
|
|
362
|
+
self._name,
|
|
363
|
+
metadata=self._metadata,
|
|
364
|
+
session_id=self._session_id,
|
|
365
|
+
framework="llamaindex",
|
|
366
|
+
# The root of a standalone query/agent invocation is the agent run itself.
|
|
367
|
+
span_kind="agent",
|
|
363
368
|
) as span:
|
|
364
369
|
# __enter__ just set _start to "now" - overridden to the query's real start time so
|
|
365
370
|
# __exit__'s latency_ms reflects the actual run, not the few microseconds between this
|
|
@@ -116,6 +116,7 @@ class JudgeScorersClient:
|
|
|
116
116
|
judge_prompt: Optional[str] = None,
|
|
117
117
|
judge_model: Optional[str] = None,
|
|
118
118
|
tool_context: Optional[str] = None,
|
|
119
|
+
requires_expected: Optional[bool] = None,
|
|
119
120
|
# Offline profile (dataset-run grading)
|
|
120
121
|
number_of_requests: int = 1,
|
|
121
122
|
vector_similarity: bool = False,
|
|
@@ -135,13 +136,17 @@ class JudgeScorersClient:
|
|
|
135
136
|
alert_threshold: Optional[float] = 5,
|
|
136
137
|
severity: str = "medium",
|
|
137
138
|
agent_ids: Optional[List[str]] = None,
|
|
138
|
-
idle_seconds: int =
|
|
139
|
+
idle_seconds: Optional[int] = None,
|
|
139
140
|
) -> "JudgeScorerBuilder":
|
|
140
141
|
"""Snake_case builder with ``.publish()``, the unified successor of
|
|
141
142
|
``client.evaluations.settings.builder(...)`` - same offline fields (plus ``thresholds``,
|
|
142
143
|
``tool_context``) and, new here, the online profile in the same call. The scorer the
|
|
143
144
|
builder publishes is one entity: its ``.id`` is what ``client.evaluations.run(...,
|
|
144
145
|
scorer_id=...)`` takes, and its live profile is what online scoring keys on."""
|
|
146
|
+
if idle_seconds is not None and scope != "session":
|
|
147
|
+
# idleSeconds only applies to session scope - with trace scope the engine ignores
|
|
148
|
+
# it, so an explicit value here would be silently inert.
|
|
149
|
+
raise ValueError("idle_seconds requires scope='session'")
|
|
145
150
|
judge: Dict[str, Any] = {}
|
|
146
151
|
for key, value in (
|
|
147
152
|
("acceptanceCriteria", acceptance_criteria),
|
|
@@ -150,6 +155,7 @@ class JudgeScorersClient:
|
|
|
150
155
|
("judgePrompt", judge_prompt),
|
|
151
156
|
("judgeModel", judge_model),
|
|
152
157
|
("toolContext", tool_context),
|
|
158
|
+
("requiresExpected", requires_expected),
|
|
153
159
|
):
|
|
154
160
|
if value is not None:
|
|
155
161
|
judge[key] = value
|
|
@@ -178,7 +184,7 @@ class JudgeScorersClient:
|
|
|
178
184
|
"scope": scope,
|
|
179
185
|
"alertThreshold": alert_threshold,
|
|
180
186
|
"severity": severity,
|
|
181
|
-
"idleSeconds": idle_seconds,
|
|
187
|
+
"idleSeconds": idle_seconds if idle_seconds is not None else 120,
|
|
182
188
|
}
|
|
183
189
|
if agent_ids:
|
|
184
190
|
online["scopeMode"] = "selected"
|
|
@@ -329,7 +335,9 @@ class JudgeScorersClient:
|
|
|
329
335
|
|
|
330
336
|
def ratings(self, scorer_id: str, window: str = "7d") -> "List[OnlineEvaluatorRatingPoint]":
|
|
331
337
|
"""Bucketed average-rating-over-time for this scorer's live checks - same typed points
|
|
332
|
-
the legacy online_evaluators client returns, so scripts migrate without shape changes.
|
|
338
|
+
the legacy online_evaluators client returns, so scripts migrate without shape changes.
|
|
339
|
+
``window`` accepts "24h", "7d", or "30d" only (unlike :meth:`calibration`, which also
|
|
340
|
+
takes "rubric")."""
|
|
333
341
|
from agentx.monitor.models import OnlineEvaluatorRatingPoint
|
|
334
342
|
|
|
335
343
|
data = self._request("GET", f"/online-evaluators/{self._profile_id(scorer_id)}/ratings?window={window}")
|
|
@@ -337,7 +345,8 @@ class JudgeScorersClient:
|
|
|
337
345
|
|
|
338
346
|
def events(self, scorer_id: str, window: str = "7d") -> "List[OnlineEvaluatorEvent]":
|
|
339
347
|
"""Individually scored traces behind the ratings series, worst-rated first - typed, same
|
|
340
|
-
as the legacy online_evaluators client."""
|
|
348
|
+
as the legacy online_evaluators client. ``window`` accepts "24h", "7d", or "30d" only
|
|
349
|
+
(unlike :meth:`calibration`, which also takes "rubric")."""
|
|
341
350
|
from agentx.monitor.models import OnlineEvaluatorEvent
|
|
342
351
|
|
|
343
352
|
data = self._request("GET", f"/online-evaluators/{self._profile_id(scorer_id)}/events?window={window}")
|
|
@@ -10,13 +10,14 @@ class MonitorPattern(BaseModel):
|
|
|
10
10
|
``pattern_ids`` entry in ``tracer.trace(..., monitor=True, pattern_ids=[...])``.
|
|
11
11
|
|
|
12
12
|
A "failure" pattern (the default) raises a signal to triage; a "proper" pattern logs a
|
|
13
|
-
healthy tally instead.
|
|
14
|
-
meaningful at a time, selected by ``detector_kind``.
|
|
13
|
+
healthy tally instead.
|
|
15
14
|
|
|
16
15
|
On self-host the engine stores a pattern as a list of ``conditions`` (each with its own
|
|
17
|
-
detector kind and match settings)
|
|
18
|
-
``
|
|
19
|
-
|
|
16
|
+
detector kind and match settings) and ``conditions`` is the only truth: the wire always
|
|
17
|
+
carries ``includeTerms``/``excludeTerms`` as ``[]`` and omits ``regex``/``semanticPrompt``
|
|
18
|
+
entirely (they are legacy display fields kept for wire compatibility), so the flat
|
|
19
|
+
``include_terms``/``exclude_terms``/``regex``/``semantic_prompt`` attributes here stay
|
|
20
|
+
empty/None - read the match settings from ``conditions``.
|
|
20
21
|
"""
|
|
21
22
|
|
|
22
23
|
id: str = Field(alias="_id")
|
|
@@ -41,6 +42,7 @@ class MonitorPattern(BaseModel):
|
|
|
41
42
|
agent_ids: List[str] = Field(default_factory=list, alias="agentIds")
|
|
42
43
|
|
|
43
44
|
class Config:
|
|
45
|
+
populate_by_name = True
|
|
44
46
|
extra = "ignore"
|
|
45
47
|
|
|
46
48
|
|
|
@@ -179,7 +179,9 @@ class IngestClient:
|
|
|
179
179
|
resp = self._session.post(self._endpoint, json=payload, timeout=10)
|
|
180
180
|
except requests.RequestException as exc:
|
|
181
181
|
self._warn_delivery(f"{exc.__class__.__name__}: {exc}")
|
|
182
|
-
|
|
182
|
+
# WARNING, not debug: the sync caller explicitly asked for a trace_id back, so
|
|
183
|
+
# a dropped trace here silently becomes trace_id None downstream.
|
|
184
|
+
logger.warning("agentx sync trace send failed (%s) - trace dropped, no trace_id", exc)
|
|
183
185
|
return None
|
|
184
186
|
if resp.status_code in (429, 503) and attempt < 2:
|
|
185
187
|
retry_after = resp.headers.get("Retry-After")
|
|
@@ -193,7 +195,13 @@ class IngestClient:
|
|
|
193
195
|
continue
|
|
194
196
|
if not resp.ok:
|
|
195
197
|
self._warn_delivery(f"HTTP {resp.status_code}", status=resp.status_code)
|
|
196
|
-
|
|
198
|
+
# WARNING, not debug: the sync caller explicitly asked for a trace_id back, so
|
|
199
|
+
# a dropped trace here silently becomes trace_id None downstream.
|
|
200
|
+
logger.warning(
|
|
201
|
+
"agentx sync trace send failed (HTTP %d: %s) - trace dropped, no trace_id",
|
|
202
|
+
resp.status_code,
|
|
203
|
+
resp.text[:200],
|
|
204
|
+
)
|
|
197
205
|
return None
|
|
198
206
|
try:
|
|
199
207
|
return resp.json().get("trace_id")
|
|
@@ -211,11 +219,16 @@ class IngestClient:
|
|
|
211
219
|
resp = self._session.post(self._endpoint, json=payload, timeout=10)
|
|
212
220
|
except requests.RequestException as exc:
|
|
213
221
|
self._warn_delivery(f"{exc.__class__.__name__}: {exc}")
|
|
214
|
-
|
|
222
|
+
# WARNING, not debug - same reasoning as send_trace_sync.
|
|
223
|
+
logger.warning("agentx sync trace send failed (%s) - trace dropped, no trace_id", exc)
|
|
215
224
|
return None
|
|
216
225
|
if not resp.ok:
|
|
217
226
|
self._warn_delivery(f"HTTP {resp.status_code}", status=resp.status_code)
|
|
218
|
-
logger.
|
|
227
|
+
logger.warning(
|
|
228
|
+
"agentx sync trace send failed (HTTP %d: %s) - trace dropped, no trace_id",
|
|
229
|
+
resp.status_code,
|
|
230
|
+
resp.text[:200],
|
|
231
|
+
)
|
|
219
232
|
return None
|
|
220
233
|
try:
|
|
221
234
|
body = resp.json()
|
|
@@ -1283,8 +1283,8 @@ class Tracer:
|
|
|
1283
1283
|
(available as ``span.trace_id`` once that `with` block exits).
|
|
1284
1284
|
dataset_id: EvaluationSettings ID to score against.
|
|
1285
1285
|
question_index: Optional index into the dataset's questions array.
|
|
1286
|
-
|
|
1287
|
-
|
|
1286
|
+
Hosted-only: the self-host engine ignores it and
|
|
1287
|
+
scores the trace's own input/output as-is.
|
|
1288
1288
|
|
|
1289
1289
|
Returns:
|
|
1290
1290
|
Dict with keys: ``run_id``, ``trace_id``, ``rating``,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
VERSION = "0.8.
|
|
1
|
+
VERSION = "0.8.23"
|
|
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.24"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentx-python
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.23
|
|
4
4
|
Summary: Official Python SDK for AgentX (https://www.agentx.so/)
|
|
5
5
|
Home-page: https://github.com/AgentX-ai/AgentX-python
|
|
6
6
|
Author: Robin Wang and AgentX Team
|
|
@@ -255,6 +255,13 @@ extra:
|
|
|
255
255
|
| LlamaIndex | `pip install "agentx-python[llamaindex]"` | `AgentXLlamaIndexHandler` |
|
|
256
256
|
| AutoGen | `pip install "agentx-python[autogen]"` | `AgentXAutoGenObserver` |
|
|
257
257
|
|
|
258
|
+
> **Warning: pick ONE instrumentation layer per LLM call.** Do not combine
|
|
259
|
+
> `AgentXCallbackHandler` (or any framework integration) with a patched provider client
|
|
260
|
+
> (`patch_openai_client`, `patch_anthropic_client`, `patch_genai_client`) on the same code
|
|
261
|
+
> path. A patched call that runs outside an active span emits its own root trace, so every
|
|
262
|
+
> LLM call the framework already traces gets a duplicate trace - and its cost is counted
|
|
263
|
+
> twice.
|
|
264
|
+
|
|
258
265
|
Two more platforms are covered by **pull importers** rather than in-process hooks, each with its
|
|
259
266
|
own CLI: `agentx-moveworks` (Moveworks Data API sync, no extra needed) and `agentx-databricks`
|
|
260
267
|
(`pip install "agentx-python[databricks]"`, MLflow/Databricks trace sync).
|
|
@@ -95,6 +95,21 @@ def test_documented_judge_scorer_methods_exist():
|
|
|
95
95
|
assert not missing, "documented but not on JudgeScorersClient: " + ", ".join(missing)
|
|
96
96
|
|
|
97
97
|
|
|
98
|
+
def test_run_lifecycle_calls_documented_on_client_evaluations_exist():
|
|
99
|
+
"""The mintlify API reference shows client.evaluations.<method>(...) for the whole run
|
|
100
|
+
lifecycle - these delegate to the private EvaluationsClient, and every one must exist on
|
|
101
|
+
the public runner or the documented snippets raise AttributeError."""
|
|
102
|
+
for method in (
|
|
103
|
+
"init_run",
|
|
104
|
+
"append_results",
|
|
105
|
+
"finalize_run",
|
|
106
|
+
"analyze_run",
|
|
107
|
+
"get_report",
|
|
108
|
+
"get_submitted_keys",
|
|
109
|
+
):
|
|
110
|
+
assert hasattr(EvaluationsRunner, method), f"client.evaluations.{method} is documented but missing"
|
|
111
|
+
|
|
112
|
+
|
|
98
113
|
def test_documented_builder_keywords_are_real_parameters():
|
|
99
114
|
keywords = [
|
|
100
115
|
(doc, keyword)
|
|
@@ -214,6 +214,39 @@ def test_judge_scorers_builder_matches_legacy_builder_ergonomics(monkeypatch):
|
|
|
214
214
|
assert payload["online"]["agentIds"] == ["support-agent"]
|
|
215
215
|
|
|
216
216
|
|
|
217
|
+
def test_builder_rejects_idle_seconds_without_session_scope():
|
|
218
|
+
"""idleSeconds only applies to session scope - the engine silently ignores it with trace
|
|
219
|
+
scope, so an explicit idle_seconds without scope="session" is a hard error, not an inert
|
|
220
|
+
wire field."""
|
|
221
|
+
client = JudgeScorersClient(api_key="agtx_local_test", base_url="http://localhost:1")
|
|
222
|
+
with pytest.raises(ValueError, match="idle_seconds requires scope='session'"):
|
|
223
|
+
client.builder("Support quality", live=True, idle_seconds=300)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def test_builder_sends_idle_seconds_and_requires_expected(monkeypatch):
|
|
227
|
+
"""With scope="session", an explicit idle_seconds reaches the wire, and requires_expected
|
|
228
|
+
lands in the judge section as requiresExpected."""
|
|
229
|
+
client = JudgeScorersClient(api_key="agtx_local_test", base_url="http://localhost:1")
|
|
230
|
+
captured = {}
|
|
231
|
+
|
|
232
|
+
def fake_request(method, path, **kwargs):
|
|
233
|
+
captured["payload"] = kwargs.get("json")
|
|
234
|
+
return {"judgeScorer": {"_id": "s1", "name": "Support quality", "judge": {}, "offline": {}, "online": None}}
|
|
235
|
+
|
|
236
|
+
monkeypatch.setattr(client, "_request", fake_request)
|
|
237
|
+
client.builder(
|
|
238
|
+
"Support quality",
|
|
239
|
+
requires_expected=True,
|
|
240
|
+
live=True,
|
|
241
|
+
scope="session",
|
|
242
|
+
idle_seconds=300,
|
|
243
|
+
).publish()
|
|
244
|
+
payload = captured["payload"]
|
|
245
|
+
assert payload["judge"]["requiresExpected"] is True
|
|
246
|
+
assert payload["online"]["scope"] == "session"
|
|
247
|
+
assert payload["online"]["idleSeconds"] == 300
|
|
248
|
+
|
|
249
|
+
|
|
217
250
|
def test_from_env_honors_selfhost_base_url_conventions(monkeypatch):
|
|
218
251
|
"""from_env silently targeting the hosted default while the shell exports the self-host
|
|
219
252
|
conventions (AGENTX_SELFHOST_BASE_URL / BASE_URL) produced confusing auth errors - it now
|
|
@@ -271,6 +271,27 @@ def test_failures_that_are_not_404_propagate_untouched(status):
|
|
|
271
271
|
assert client._analysis_on_dashboard_router is None
|
|
272
272
|
|
|
273
273
|
|
|
274
|
+
@pytest.mark.parametrize("body", [{"error": "Run not found"}, {"error": "No analysis found for this run. POST /runs/:runId/analyze first."}])
|
|
275
|
+
def test_resource_404s_do_not_latch_the_dashboard_fallback(body):
|
|
276
|
+
"""A 404 whose body names the missing resource comes from a route that EXISTS - the SDK
|
|
277
|
+
router answered it. It must propagate as-is and must not permanently reroute every later
|
|
278
|
+
analysis call to the dashboard router."""
|
|
279
|
+
client, session = make_client(
|
|
280
|
+
{
|
|
281
|
+
("GET", f"{SDK_ROOT}/runs/{RUN}/analyze-status"): FakeResponse(404, body),
|
|
282
|
+
# Present, and must not be reached.
|
|
283
|
+
("GET", f"{API_ROOT}/evaluate/analyze/{RUN}/status"): FakeResponse(200, STATUS_BODY),
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
with pytest.raises(AgentXEvaluationsError) as caught:
|
|
288
|
+
client.get_analysis_status(RUN)
|
|
289
|
+
|
|
290
|
+
assert caught.value.status_code == 404
|
|
291
|
+
assert not [u for u in session.urls() if "/evaluate/" in u], "masked a resource 404"
|
|
292
|
+
assert client._analysis_on_dashboard_router is None
|
|
293
|
+
|
|
294
|
+
|
|
274
295
|
def test_auth_errors_are_not_mistaken_for_a_missing_route():
|
|
275
296
|
client, session = make_client(
|
|
276
297
|
{("GET", f"{SDK_ROOT}/runs/{RUN}/analyze-status"): FakeResponse(401, {"e": "nope"})}
|
|
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
|