agentx-python 0.6.5__tar.gz → 0.6.7__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.6.5 → agentx_python-0.6.7}/PKG-INFO +32 -1
- {agentx_python-0.6.5 → agentx_python-0.6.7}/README.md +31 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/agentx.py +10 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/evaluations/_term.py +6 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/evaluations/client.py +23 -2
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/evaluations/models.py +63 -5
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/evaluations/runner.py +70 -4
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/integrations/crewai.py +7 -1
- agentx_python-0.6.7/agentx/monitor/__init__.py +10 -0
- agentx_python-0.6.7/agentx/monitor/client.py +134 -0
- agentx_python-0.6.7/agentx/monitor/models.py +37 -0
- agentx_python-0.6.7/agentx/monitor/patterns.py +121 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/tracing/tracer.py +30 -0
- agentx_python-0.6.7/agentx/version.py +1 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx_python.egg-info/PKG-INFO +32 -1
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx_python.egg-info/SOURCES.txt +4 -0
- agentx_python-0.6.5/agentx/version.py +0 -1
- {agentx_python-0.6.5 → agentx_python-0.6.7}/LICENSE +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/__init__.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/evaluations/__init__.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/evaluations/adapters/__init__.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/evaluations/adapters/http_endpoint.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/evaluations/adapters/precomputed.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/evaluations/adapters/raw.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/evaluations/datasets.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/evaluations/evaluation_settings.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/evaluations/redaction.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/evaluations/reporting.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/evaluations/results.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/evaluations/tracing.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/exceptions.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/integrations/__init__.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/integrations/_perf.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/integrations/anthropic.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/integrations/google_adk.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/integrations/google_genai.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/integrations/langchain.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/integrations/openai_agents.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/py.typed +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/resources/__init__.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/resources/agent.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/resources/conversation.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/resources/workforce.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/tracing/__init__.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/tracing/ci_types.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/tracing/ingest_client.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx/util.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx_python.egg-info/dependency_links.txt +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx_python.egg-info/not-zip-safe +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx_python.egg-info/requires.txt +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/agentx_python.egg-info/top_level.txt +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/setup.cfg +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/setup.py +0 -0
- {agentx_python-0.6.5 → agentx_python-0.6.7}/tests/test_integration.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentx-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.7
|
|
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
|
|
@@ -70,6 +70,7 @@ Also see [SDK Developer Docs](https://developers.agentx.so), [API Reference Docs
|
|
|
70
70
|
- [Chat (streaming and non-streaming)](#chat-streaming-and-non-streaming)
|
|
71
71
|
- [Workforce (multi-agent orchestration)](#workforce-multi-agent-orchestration) — teams of agents with a designated manager
|
|
72
72
|
- [Production tracing](#production-tracing) — record live agent runs from any framework
|
|
73
|
+
- [Monitor](#monitor) — automatic production monitoring, patterns and signals
|
|
73
74
|
- [Custom agent evaluations](#custom-agent-evaluations) — LLM-as-a-judge, cosine / Jaccard similarity
|
|
74
75
|
- [Links](#links)
|
|
75
76
|
|
|
@@ -245,6 +246,30 @@ See **[TRACING.md](TRACING.md)** for the complete guide — session grouping, er
|
|
|
245
246
|
|
|
246
247
|
---
|
|
247
248
|
|
|
249
|
+
## Monitor
|
|
250
|
+
|
|
251
|
+
Automatic production monitoring: check traces against detection patterns and get back triage-ready signals. A **pattern** is a first-class SDK resource with a real id, just like a `Dataset` or `EvaluationSettings`: create one once, then reference it by id at trace time.
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
pattern = client.monitor.patterns.builder(
|
|
255
|
+
name="Promises a refund",
|
|
256
|
+
detector_kind="semantic",
|
|
257
|
+
semantic_prompt="The response promises a refund.",
|
|
258
|
+
severity="high",
|
|
259
|
+
).publish()
|
|
260
|
+
|
|
261
|
+
with client.tracer.trace("support-agent", monitor=True, pattern_ids=[pattern.id]) as span:
|
|
262
|
+
span.output = call_llm(query)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`monitor=True` checks the trace immediately, no dashboard setup required. `pattern_ids` restricts detection to exactly those patterns; omit it to run the full default sweep instead (built-in checks like empty response, trace error, and latency regression, plus every pattern enabled for the workspace).
|
|
266
|
+
|
|
267
|
+
This works independently of the dashboard's per-agent monitoring toggle (Governance > Observe > Agents), which still auto-checks every trace from an agent once enabled there, with no code changes needed either way.
|
|
268
|
+
|
|
269
|
+
See **[TRACING.md](TRACING.md)** for the complete Monitor guide.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
248
273
|
## Custom agent evaluations
|
|
249
274
|
|
|
250
275
|
Evaluate **any** AI agent — LangChain, CrewAI, AutoGen, LlamaIndex, OpenAI, Anthropic, HTTP endpoints, or plain Python — using AgentX as the scoring and reporting backend. Includes optional **cosine** and **Jaccard** similarity metrics alongside LLM-graded ratings.
|
|
@@ -261,8 +286,13 @@ report = (
|
|
|
261
286
|
print(report.average_rating) # LLM-graded score, 0–10
|
|
262
287
|
print(report.cosine_similarity) # embedding cosine, 0–1 (None if not enabled)
|
|
263
288
|
print(report.jaccard_similarity) # token-set overlap, 0–1 (None if not enabled)
|
|
289
|
+
|
|
290
|
+
print(report.summary) # AI-generated narrative from .analyze()
|
|
291
|
+
print(report.recommendations) # list of prioritized, actionable fixes
|
|
264
292
|
```
|
|
265
293
|
|
|
294
|
+
`.analyze()` also generates a full qualitative report (strengths, weaknesses, instruction adherence, reasoning quality, and recommendations), running the same durable, multi-judge pipeline as the dashboard's "Analyze" button. `analyze(mode=..., quality_mode=..., judges=[...])` controls how items are scored and by which models. See [AI analysis report](EVALUATIONS.md#ai-analysis-report) in the full guide for the complete field and parameter reference.
|
|
295
|
+
|
|
266
296
|
See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder, framework adapters, similarity metrics, and the complete API reference.
|
|
267
297
|
|
|
268
298
|
---
|
|
@@ -274,3 +304,4 @@ See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder,
|
|
|
274
304
|
- **PyPI** — [agentx-python](https://pypi.org/project/agentx-python/)
|
|
275
305
|
- **Tracing docs** — [TRACING.md](TRACING.md)
|
|
276
306
|
- **Evaluations docs** — [EVALUATIONS.md](EVALUATIONS.md)
|
|
307
|
+
- **Monitor docs** — [docs.agentx.so/sdk/monitor](https://docs.agentx.so/sdk/monitor)
|
|
@@ -22,6 +22,7 @@ Also see [SDK Developer Docs](https://developers.agentx.so), [API Reference Docs
|
|
|
22
22
|
- [Chat (streaming and non-streaming)](#chat-streaming-and-non-streaming)
|
|
23
23
|
- [Workforce (multi-agent orchestration)](#workforce-multi-agent-orchestration) — teams of agents with a designated manager
|
|
24
24
|
- [Production tracing](#production-tracing) — record live agent runs from any framework
|
|
25
|
+
- [Monitor](#monitor) — automatic production monitoring, patterns and signals
|
|
25
26
|
- [Custom agent evaluations](#custom-agent-evaluations) — LLM-as-a-judge, cosine / Jaccard similarity
|
|
26
27
|
- [Links](#links)
|
|
27
28
|
|
|
@@ -197,6 +198,30 @@ See **[TRACING.md](TRACING.md)** for the complete guide — session grouping, er
|
|
|
197
198
|
|
|
198
199
|
---
|
|
199
200
|
|
|
201
|
+
## Monitor
|
|
202
|
+
|
|
203
|
+
Automatic production monitoring: check traces against detection patterns and get back triage-ready signals. A **pattern** is a first-class SDK resource with a real id, just like a `Dataset` or `EvaluationSettings`: create one once, then reference it by id at trace time.
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
pattern = client.monitor.patterns.builder(
|
|
207
|
+
name="Promises a refund",
|
|
208
|
+
detector_kind="semantic",
|
|
209
|
+
semantic_prompt="The response promises a refund.",
|
|
210
|
+
severity="high",
|
|
211
|
+
).publish()
|
|
212
|
+
|
|
213
|
+
with client.tracer.trace("support-agent", monitor=True, pattern_ids=[pattern.id]) as span:
|
|
214
|
+
span.output = call_llm(query)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`monitor=True` checks the trace immediately, no dashboard setup required. `pattern_ids` restricts detection to exactly those patterns; omit it to run the full default sweep instead (built-in checks like empty response, trace error, and latency regression, plus every pattern enabled for the workspace).
|
|
218
|
+
|
|
219
|
+
This works independently of the dashboard's per-agent monitoring toggle (Governance > Observe > Agents), which still auto-checks every trace from an agent once enabled there, with no code changes needed either way.
|
|
220
|
+
|
|
221
|
+
See **[TRACING.md](TRACING.md)** for the complete Monitor guide.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
200
225
|
## Custom agent evaluations
|
|
201
226
|
|
|
202
227
|
Evaluate **any** AI agent — LangChain, CrewAI, AutoGen, LlamaIndex, OpenAI, Anthropic, HTTP endpoints, or plain Python — using AgentX as the scoring and reporting backend. Includes optional **cosine** and **Jaccard** similarity metrics alongside LLM-graded ratings.
|
|
@@ -213,8 +238,13 @@ report = (
|
|
|
213
238
|
print(report.average_rating) # LLM-graded score, 0–10
|
|
214
239
|
print(report.cosine_similarity) # embedding cosine, 0–1 (None if not enabled)
|
|
215
240
|
print(report.jaccard_similarity) # token-set overlap, 0–1 (None if not enabled)
|
|
241
|
+
|
|
242
|
+
print(report.summary) # AI-generated narrative from .analyze()
|
|
243
|
+
print(report.recommendations) # list of prioritized, actionable fixes
|
|
216
244
|
```
|
|
217
245
|
|
|
246
|
+
`.analyze()` also generates a full qualitative report (strengths, weaknesses, instruction adherence, reasoning quality, and recommendations), running the same durable, multi-judge pipeline as the dashboard's "Analyze" button. `analyze(mode=..., quality_mode=..., judges=[...])` controls how items are scored and by which models. See [AI analysis report](EVALUATIONS.md#ai-analysis-report) in the full guide for the complete field and parameter reference.
|
|
247
|
+
|
|
218
248
|
See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder, framework adapters, similarity metrics, and the complete API reference.
|
|
219
249
|
|
|
220
250
|
---
|
|
@@ -226,3 +256,4 @@ See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder,
|
|
|
226
256
|
- **PyPI** — [agentx-python](https://pypi.org/project/agentx-python/)
|
|
227
257
|
- **Tracing docs** — [TRACING.md](TRACING.md)
|
|
228
258
|
- **Evaluations docs** — [EVALUATIONS.md](EVALUATIONS.md)
|
|
259
|
+
- **Monitor docs** — [docs.agentx.so/sdk/monitor](https://docs.agentx.so/sdk/monitor)
|
|
@@ -29,6 +29,7 @@ class AgentX:
|
|
|
29
29
|
|
|
30
30
|
from agentx.evaluations.client import EvaluationsClient
|
|
31
31
|
from agentx.evaluations.runner import EvaluationsRunner
|
|
32
|
+
from agentx.monitor.client import MonitorClient
|
|
32
33
|
from agentx.tracing.ingest_client import IngestClient
|
|
33
34
|
from agentx.tracing.tracer import Tracer
|
|
34
35
|
from agentx.version import VERSION
|
|
@@ -41,6 +42,15 @@ class AgentX:
|
|
|
41
42
|
)
|
|
42
43
|
self.evaluations = EvaluationsRunner(_eval_client)
|
|
43
44
|
|
|
45
|
+
# Monitor: create/reuse patterns (client.monitor.patterns) that a trace can be checked
|
|
46
|
+
# against at send time via tracer.trace(..., monitor=True, pattern_ids=[...]).
|
|
47
|
+
self.monitor = MonitorClient(
|
|
48
|
+
api_key=self.api_key,
|
|
49
|
+
sdk_version=VERSION,
|
|
50
|
+
base_url=self.base_url,
|
|
51
|
+
workspace_id=self.workspace_id,
|
|
52
|
+
)
|
|
53
|
+
|
|
44
54
|
_ingest_client = IngestClient(
|
|
45
55
|
api_key=self.api_key,
|
|
46
56
|
sdk_version=VERSION,
|
|
@@ -71,6 +71,12 @@ class Spinner:
|
|
|
71
71
|
self._thread.start()
|
|
72
72
|
return self
|
|
73
73
|
|
|
74
|
+
def update(self, message: str) -> None:
|
|
75
|
+
"""Change the displayed message while the spinner keeps running."""
|
|
76
|
+
self._message = message
|
|
77
|
+
if not _IS_TTY:
|
|
78
|
+
print(f" {message}...", flush=True)
|
|
79
|
+
|
|
74
80
|
def __exit__(self, *_) -> None:
|
|
75
81
|
if not _IS_TTY:
|
|
76
82
|
return
|
|
@@ -9,6 +9,7 @@ from typing import Any, Dict, List, Optional
|
|
|
9
9
|
import requests
|
|
10
10
|
|
|
11
11
|
from agentx.evaluations.models import (
|
|
12
|
+
AnalysisStatus,
|
|
12
13
|
BatchAppendResponse,
|
|
13
14
|
Dataset,
|
|
14
15
|
EvaluationResult,
|
|
@@ -238,8 +239,28 @@ class EvaluationsClient:
|
|
|
238
239
|
"POST", f"/runs/{run_id}/finalize", json={"status": "completed"}
|
|
239
240
|
)
|
|
240
241
|
|
|
241
|
-
def analyze_run(
|
|
242
|
-
|
|
242
|
+
def analyze_run(
|
|
243
|
+
self,
|
|
244
|
+
run_id: str,
|
|
245
|
+
mode: Optional[str] = None,
|
|
246
|
+
quality_mode: Optional[str] = None,
|
|
247
|
+
judges: Optional[List[str]] = None,
|
|
248
|
+
) -> Dict[str, Any]:
|
|
249
|
+
# Starts the durable analysis job and returns immediately (e.g. {"jobId": ..., "status":
|
|
250
|
+
# "pending"}); poll get_analysis_status() until it reaches a terminal status, then call
|
|
251
|
+
# get_report(). mode/quality_mode/judges mirror the dashboard's AnalyzeEvaluationRequest.
|
|
252
|
+
payload: Dict[str, Any] = {}
|
|
253
|
+
if mode is not None:
|
|
254
|
+
payload["mode"] = mode
|
|
255
|
+
if quality_mode is not None:
|
|
256
|
+
payload["qualityMode"] = quality_mode
|
|
257
|
+
if judges is not None:
|
|
258
|
+
payload["judges"] = [{"model": m} for m in judges]
|
|
259
|
+
return self._request("POST", f"/runs/{run_id}/analyze", json=payload, timeout=30)
|
|
260
|
+
|
|
261
|
+
def get_analysis_status(self, run_id: str) -> AnalysisStatus:
|
|
262
|
+
data = self._request("GET", f"/runs/{run_id}/analyze-status")
|
|
263
|
+
return AnalysisStatus(**data)
|
|
243
264
|
|
|
244
265
|
def get_run(self, run_id: str) -> Dict[str, Any]:
|
|
245
266
|
return self._request("GET", f"/runs/{run_id}")
|
|
@@ -438,11 +438,9 @@ class SovereigntyIndex(BaseModel):
|
|
|
438
438
|
extra = "ignore"
|
|
439
439
|
|
|
440
440
|
|
|
441
|
-
class
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
status: str = "completed"
|
|
445
|
-
statistics: Optional[ReportStatistics] = None
|
|
441
|
+
class AnalysisResult(BaseModel):
|
|
442
|
+
"""Shared qualitative-report fields, produced by ``client.evaluations.run(...).analyze()``."""
|
|
443
|
+
|
|
446
444
|
summary: Optional[str] = None
|
|
447
445
|
consistency_score: Optional[float] = Field(default=None, alias="consistencyScore")
|
|
448
446
|
instruction_adherence: Optional[ReportInstructionAdherence] = Field(
|
|
@@ -461,6 +459,17 @@ class Report(BaseModel):
|
|
|
461
459
|
weaknesses: List[str] = Field(default_factory=list)
|
|
462
460
|
overall_rating: Optional[str] = Field(default=None, alias="overallRating")
|
|
463
461
|
recommendations: List[ReportRecommendation] = Field(default_factory=list)
|
|
462
|
+
|
|
463
|
+
class Config:
|
|
464
|
+
populate_by_name = True
|
|
465
|
+
extra = "ignore"
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
class Report(AnalysisResult):
|
|
469
|
+
run_id: str = Field(alias="runId")
|
|
470
|
+
dataset_id: str = Field(alias="datasetId")
|
|
471
|
+
status: str = "completed"
|
|
472
|
+
statistics: Optional[ReportStatistics] = None
|
|
464
473
|
low_scoring_cases: List[Dict[str, Any]] = Field(
|
|
465
474
|
default_factory=list, alias="lowScoringCases"
|
|
466
475
|
)
|
|
@@ -548,3 +557,52 @@ class Report(BaseModel):
|
|
|
548
557
|
def average_rating(self) -> Optional[float]:
|
|
549
558
|
"""Convenience accessor matching cosine_similarity / jaccard_similarity."""
|
|
550
559
|
return self.statistics.average_rating if self.statistics is not None else None
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
class AnalysisLevelProgress(BaseModel):
|
|
563
|
+
total: int = 0
|
|
564
|
+
completed: int = 0
|
|
565
|
+
failed: int = 0
|
|
566
|
+
percentage: int = 0
|
|
567
|
+
|
|
568
|
+
class Config:
|
|
569
|
+
extra = "ignore"
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
class AnalysisProgress(BaseModel):
|
|
573
|
+
overall_percentage: int = Field(default=0, alias="overallPercentage")
|
|
574
|
+
current_level: Optional[str] = Field(default=None, alias="currentLevel")
|
|
575
|
+
levels: Dict[str, AnalysisLevelProgress] = Field(default_factory=dict)
|
|
576
|
+
|
|
577
|
+
class Config:
|
|
578
|
+
populate_by_name = True
|
|
579
|
+
extra = "ignore"
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
class AnalysisFailureReason(BaseModel):
|
|
583
|
+
code: str
|
|
584
|
+
message: str
|
|
585
|
+
retryable: bool = False
|
|
586
|
+
|
|
587
|
+
class Config:
|
|
588
|
+
extra = "ignore"
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
class AnalysisStatus(BaseModel):
|
|
592
|
+
"""Returned by ``client.evaluations.run(...).analyze()``'s polling loop
|
|
593
|
+
(``EvaluationsClient.get_analysis_status``). ``status`` is terminal once it's one of
|
|
594
|
+
"completed", "partially_failed", or "failed"."""
|
|
595
|
+
|
|
596
|
+
job_id: Optional[str] = Field(default=None, alias="jobId")
|
|
597
|
+
status: str = "not_started"
|
|
598
|
+
progress: AnalysisProgress = Field(default_factory=AnalysisProgress)
|
|
599
|
+
failure_reason: Optional[AnalysisFailureReason] = Field(default=None, alias="failureReason")
|
|
600
|
+
warnings: List[Dict[str, Any]] = Field(default_factory=list)
|
|
601
|
+
|
|
602
|
+
class Config:
|
|
603
|
+
populate_by_name = True
|
|
604
|
+
extra = "ignore"
|
|
605
|
+
|
|
606
|
+
@property
|
|
607
|
+
def is_terminal(self) -> bool:
|
|
608
|
+
return self.status in ("completed", "partially_failed", "failed")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import time
|
|
4
5
|
import uuid
|
|
5
6
|
from typing import Any, Callable, Dict, List, Optional, Set, Union
|
|
6
7
|
|
|
@@ -9,6 +10,7 @@ from agentx.evaluations.adapters.precomputed import PrecomputedAdapter
|
|
|
9
10
|
from agentx.evaluations.adapters.http_endpoint import HttpEndpointAdapter
|
|
10
11
|
from agentx.evaluations.client import EvaluationsClient
|
|
11
12
|
from agentx.evaluations.models import (
|
|
13
|
+
AnalysisStatus,
|
|
12
14
|
Dataset,
|
|
13
15
|
EvaluationCase,
|
|
14
16
|
EvaluationResult,
|
|
@@ -43,6 +45,15 @@ AdapterLike = Union[
|
|
|
43
45
|
HttpEndpointAdapter,
|
|
44
46
|
]
|
|
45
47
|
|
|
48
|
+
_ANALYSIS_LEVEL_LABELS = {
|
|
49
|
+
"l1_score": "scoring responses",
|
|
50
|
+
"l2_question_reduce": "reducing questions",
|
|
51
|
+
"l3_cluster_reduce": "reducing clusters",
|
|
52
|
+
"l4_final_reduce": "writing final report",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_DEFAULT_JUDGE_MODEL = "gpt-5.5"
|
|
56
|
+
|
|
46
57
|
|
|
47
58
|
class EvaluationRunContext:
|
|
48
59
|
"""
|
|
@@ -228,12 +239,61 @@ class EvaluationRunContext:
|
|
|
228
239
|
# Step 3: analyze + report
|
|
229
240
|
# ------------------------------------------------------------------
|
|
230
241
|
|
|
231
|
-
def analyze(
|
|
242
|
+
def analyze(
|
|
243
|
+
self,
|
|
244
|
+
mode: Optional[str] = None,
|
|
245
|
+
quality_mode: Optional[str] = None,
|
|
246
|
+
judges: Optional[List[str]] = None,
|
|
247
|
+
poll_interval: float = 5.0,
|
|
248
|
+
timeout: float = 1800.0,
|
|
249
|
+
) -> Report:
|
|
250
|
+
"""Generate the qualitative AI analysis report.
|
|
251
|
+
|
|
252
|
+
Runs the same durable, multi-stage pipeline as the dashboard's "Analyze" button: each
|
|
253
|
+
response is scored by 1-3 LLM judges (``judges``), then reduced into the final report.
|
|
254
|
+
This starts the job and polls until it finishes, which can take noticeably longer than a
|
|
255
|
+
single LLM call for larger runs.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
mode: "auto" (default), "sync", or "batch" - how item scoring executes server-side.
|
|
259
|
+
quality_mode: "quality_first" or "balanced" - how many items get a second/third judge.
|
|
260
|
+
judges: 1-3 model ids, e.g. ``["gpt-5.5", "claude-opus-4-8"]``. Defaults to a single
|
|
261
|
+
judge, ``["gpt-5.5"]``, rather than the dashboard's 3-judge default - SDK runs are
|
|
262
|
+
typically lighter-weight, quick-start evaluations.
|
|
263
|
+
poll_interval: seconds between status checks while waiting.
|
|
264
|
+
timeout: give up waiting after this many seconds (the job keeps running server-side;
|
|
265
|
+
call ``get_report()`` later to check on it).
|
|
266
|
+
"""
|
|
267
|
+
if judges is not None and not (1 <= len(judges) <= 3):
|
|
268
|
+
raise ValueError("judges must contain 1-3 model ids")
|
|
269
|
+
resolved_judges = judges if judges is not None else [_DEFAULT_JUDGE_MODEL]
|
|
270
|
+
|
|
232
271
|
print()
|
|
233
|
-
with Spinner("Analyzing — AI is reviewing your results"):
|
|
272
|
+
with Spinner("Analyzing — AI is reviewing your results") as spinner:
|
|
234
273
|
try:
|
|
235
|
-
self._client.analyze_run(
|
|
236
|
-
|
|
274
|
+
self._client.analyze_run(
|
|
275
|
+
self._run.run_id,
|
|
276
|
+
mode=mode,
|
|
277
|
+
quality_mode=quality_mode,
|
|
278
|
+
judges=resolved_judges,
|
|
279
|
+
)
|
|
280
|
+
deadline = time.monotonic() + timeout
|
|
281
|
+
status = self._client.get_analysis_status(self._run.run_id)
|
|
282
|
+
while not status.is_terminal and time.monotonic() < deadline:
|
|
283
|
+
level = _ANALYSIS_LEVEL_LABELS.get(status.progress.current_level, "")
|
|
284
|
+
spinner.update(
|
|
285
|
+
f"Analyzing, {level + ' ' if level else ''}{status.progress.overall_percentage}%"
|
|
286
|
+
)
|
|
287
|
+
time.sleep(poll_interval)
|
|
288
|
+
status = self._client.get_analysis_status(self._run.run_id)
|
|
289
|
+
|
|
290
|
+
if not status.is_terminal:
|
|
291
|
+
print(f" {yellow('!')} Still running after {int(timeout)}s, check the dashboard for status")
|
|
292
|
+
elif status.status == "failed":
|
|
293
|
+
reason = status.failure_reason.message if status.failure_reason else "unknown error"
|
|
294
|
+
print(f" {red('✗')} Analyze failed: {dim(reason)}")
|
|
295
|
+
else:
|
|
296
|
+
print(f" {green('✓')} Analysis complete")
|
|
237
297
|
except Exception as exc:
|
|
238
298
|
print(f" {red('✗')} Analyze failed: {dim(str(exc))}")
|
|
239
299
|
logger.warning("Analyze request failed: %s", exc)
|
|
@@ -280,6 +340,12 @@ class EvaluationsRunner:
|
|
|
280
340
|
against."""
|
|
281
341
|
return self._client.list_models(provider)
|
|
282
342
|
|
|
343
|
+
def get_analysis_status(self, run_id: str) -> AnalysisStatus:
|
|
344
|
+
"""Check on an in-progress ``.analyze()`` job by run id, without needing
|
|
345
|
+
the ``EvaluationRunContext`` that started it (e.g. from a separate
|
|
346
|
+
script execution)."""
|
|
347
|
+
return self._client.get_analysis_status(run_id)
|
|
348
|
+
|
|
283
349
|
def run(
|
|
284
350
|
self,
|
|
285
351
|
dataset_id: str,
|
|
@@ -121,8 +121,13 @@ class AgentXCrewObserver:
|
|
|
121
121
|
input: Optional[Any] = None,
|
|
122
122
|
metadata: Optional[Dict[str, Any]] = None,
|
|
123
123
|
session_id: Optional[str] = None,
|
|
124
|
+
sync: bool = False,
|
|
124
125
|
):
|
|
125
|
-
"""Return a context-manager span for manual kickoff wrapping.
|
|
126
|
+
"""Return a context-manager span for manual kickoff wrapping.
|
|
127
|
+
|
|
128
|
+
Pass ``sync=True`` to send synchronously so ``span.trace_id`` is populated once the
|
|
129
|
+
block exits — e.g. to attach the trace to an evaluation result. See ``Tracer.trace()``.
|
|
130
|
+
"""
|
|
126
131
|
from agentx.tracing.tracer import _TraceSpan
|
|
127
132
|
|
|
128
133
|
return _TraceSpan(
|
|
@@ -132,4 +137,5 @@ class AgentXCrewObserver:
|
|
|
132
137
|
metadata=metadata or self._metadata,
|
|
133
138
|
framework="crewai",
|
|
134
139
|
session_id=session_id or self._session_id,
|
|
140
|
+
sync=sync,
|
|
135
141
|
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from agentx.monitor.client import MonitorClient
|
|
2
|
+
from agentx.monitor.models import MonitorPattern
|
|
3
|
+
from agentx.monitor.patterns import MonitorPatternBuilder, MonitorPatternClient
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"MonitorClient",
|
|
7
|
+
"MonitorPattern",
|
|
8
|
+
"MonitorPatternBuilder",
|
|
9
|
+
"MonitorPatternClient",
|
|
10
|
+
]
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any, List, Optional
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
from agentx.monitor.models import MonitorPattern
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
from agentx.util import _DEFAULT_API_BASE as _UTIL_API_BASE
|
|
15
|
+
|
|
16
|
+
SDK_NAME = "agentx-python"
|
|
17
|
+
|
|
18
|
+
_RETRYABLE_STATUS = {429, 500, 502, 503, 504}
|
|
19
|
+
_MAX_RETRIES = 3
|
|
20
|
+
_RETRY_BACKOFF = [1.0, 2.0, 4.0]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AgentXMonitorError(Exception):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AgentXAuthError(AgentXMonitorError):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AgentXValidationError(AgentXMonitorError):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MonitorClient:
|
|
36
|
+
"""Low-level HTTP client for the Monitor API (``/monitor``). Accessed via
|
|
37
|
+
``client.monitor`` on the top-level :class:`agentx.AgentX` instance; most callers
|
|
38
|
+
should use ``client.monitor.patterns.builder(...)`` instead of this directly."""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
api_key: str,
|
|
43
|
+
sdk_version: str = "unknown",
|
|
44
|
+
base_url: Optional[str] = None,
|
|
45
|
+
workspace_id: Optional[str] = None,
|
|
46
|
+
):
|
|
47
|
+
if not api_key:
|
|
48
|
+
raise AgentXAuthError("AGENTX_API_KEY is required")
|
|
49
|
+
self._api_key = api_key
|
|
50
|
+
self._sdk_version = sdk_version
|
|
51
|
+
# Falls back to the caller's default workspace server-side when unset — mirrors
|
|
52
|
+
# EvaluationsClient. Without this, pattern creation silently lands in whatever
|
|
53
|
+
# workspace the API key's user defaults to, not the one the caller intended.
|
|
54
|
+
self._workspace_id = workspace_id
|
|
55
|
+
_api_base = (
|
|
56
|
+
base_url or os.getenv("AGENTX_API_BASE_URL", _UTIL_API_BASE)
|
|
57
|
+
).rstrip("/")
|
|
58
|
+
if not _api_base.endswith("/monitor"):
|
|
59
|
+
_api_base = f"{_api_base}/monitor"
|
|
60
|
+
self._base_url = _api_base
|
|
61
|
+
self._session = requests.Session()
|
|
62
|
+
self._session.headers.update(
|
|
63
|
+
{
|
|
64
|
+
"x-api-key": self._api_key,
|
|
65
|
+
"Content-Type": "application/json",
|
|
66
|
+
"User-Agent": f"{SDK_NAME}/{self._sdk_version}",
|
|
67
|
+
"accept": "*/*",
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
from agentx.monitor.patterns import MonitorPatternClient
|
|
72
|
+
|
|
73
|
+
self.patterns = MonitorPatternClient(self)
|
|
74
|
+
|
|
75
|
+
# ------------------------------------------------------------------
|
|
76
|
+
# Low-level HTTP
|
|
77
|
+
# ------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
def _with_workspace(self, payload: dict) -> dict:
|
|
80
|
+
if self._workspace_id and not payload.get("workspaceId"):
|
|
81
|
+
return {**payload, "workspaceId": self._workspace_id}
|
|
82
|
+
return payload
|
|
83
|
+
|
|
84
|
+
def _workspace_params(self) -> Optional[dict]:
|
|
85
|
+
return {"workspaceId": self._workspace_id} if self._workspace_id else None
|
|
86
|
+
|
|
87
|
+
def _request(self, method: str, path: str, timeout: int = 30, **kwargs) -> Any:
|
|
88
|
+
url = f"{self._base_url}{path}"
|
|
89
|
+
last_exc: Optional[Exception] = None
|
|
90
|
+
for attempt, wait in enumerate([0.0] + _RETRY_BACKOFF):
|
|
91
|
+
if wait:
|
|
92
|
+
time.sleep(wait)
|
|
93
|
+
try:
|
|
94
|
+
resp = self._session.request(method, url, timeout=timeout, **kwargs)
|
|
95
|
+
except requests.RequestException as e:
|
|
96
|
+
last_exc = e
|
|
97
|
+
logger.debug("Request error (attempt %d): %s", attempt + 1, e)
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
if resp.status_code == 401:
|
|
101
|
+
raise AgentXAuthError("Invalid or missing API key")
|
|
102
|
+
if resp.status_code == 422:
|
|
103
|
+
raise AgentXValidationError(resp.text)
|
|
104
|
+
if resp.status_code in _RETRYABLE_STATUS and attempt < _MAX_RETRIES - 1:
|
|
105
|
+
logger.debug(
|
|
106
|
+
"Retryable status %d (attempt %d)", resp.status_code, attempt + 1
|
|
107
|
+
)
|
|
108
|
+
last_exc = AgentXMonitorError(f"HTTP {resp.status_code}")
|
|
109
|
+
continue
|
|
110
|
+
if not resp.ok:
|
|
111
|
+
raise AgentXMonitorError(f"HTTP {resp.status_code}: {resp.text}")
|
|
112
|
+
try:
|
|
113
|
+
return resp.json()
|
|
114
|
+
except Exception:
|
|
115
|
+
return resp.text
|
|
116
|
+
raise AgentXMonitorError(f"Request failed after retries: {last_exc}")
|
|
117
|
+
|
|
118
|
+
# ------------------------------------------------------------------
|
|
119
|
+
# Pattern endpoints
|
|
120
|
+
# ------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
def create_pattern(self, payload: dict) -> MonitorPattern:
|
|
123
|
+
data = self._request("POST", "/patterns", json=self._with_workspace(payload))
|
|
124
|
+
return MonitorPattern(**data["pattern"])
|
|
125
|
+
|
|
126
|
+
def list_patterns(self) -> List[MonitorPattern]:
|
|
127
|
+
data = self._request("GET", "/patterns", params=self._workspace_params())
|
|
128
|
+
return [MonitorPattern(**p) for p in data.get("patterns", [])]
|
|
129
|
+
|
|
130
|
+
def get_pattern(self, pattern_id: str) -> MonitorPattern:
|
|
131
|
+
data = self._request(
|
|
132
|
+
"GET", f"/patterns/{pattern_id}", params=self._workspace_params()
|
|
133
|
+
)
|
|
134
|
+
return MonitorPattern(**data["pattern"])
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MonitorPattern(BaseModel):
|
|
8
|
+
"""A detection rule checked against production traces. Built via
|
|
9
|
+
``client.monitor.patterns.builder(...).publish()`` and referenced by id as a
|
|
10
|
+
``pattern_ids`` entry in ``tracer.trace(..., monitor=True, pattern_ids=[...])``.
|
|
11
|
+
|
|
12
|
+
A "failure" pattern (the default) raises a signal to triage; a "proper" pattern logs a
|
|
13
|
+
healthy tally instead. Only one of ``include_terms``/``regex``/``semantic_prompt`` is
|
|
14
|
+
meaningful at a time, selected by ``detector_kind``.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
id: str = Field(alias="_id")
|
|
18
|
+
key: str
|
|
19
|
+
name: str
|
|
20
|
+
description: Optional[str] = None
|
|
21
|
+
category: Optional[str] = None
|
|
22
|
+
detector_kind: str = Field(default="contains", alias="detectorKind")
|
|
23
|
+
match_target: List[str] = Field(default_factory=lambda: ["response"], alias="matchTarget")
|
|
24
|
+
match_mode: str = Field(default="any", alias="matchMode")
|
|
25
|
+
include_terms: List[str] = Field(default_factory=list, alias="includeTerms")
|
|
26
|
+
exclude_terms: List[str] = Field(default_factory=list, alias="excludeTerms")
|
|
27
|
+
regex: Optional[str] = None
|
|
28
|
+
semantic_prompt: Optional[str] = Field(default=None, alias="semanticPrompt")
|
|
29
|
+
severity: str = "medium"
|
|
30
|
+
polarity: str = "failure"
|
|
31
|
+
enabled: bool = True
|
|
32
|
+
sample_rate: float = Field(default=1.0, alias="sampleRate")
|
|
33
|
+
scope_mode: str = Field(default="all", alias="scopeMode")
|
|
34
|
+
agent_ids: List[str] = Field(default_factory=list, alias="agentIds")
|
|
35
|
+
|
|
36
|
+
class Config:
|
|
37
|
+
extra = "ignore"
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from agentx.monitor.models import MonitorPattern
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from agentx.monitor.client import MonitorClient
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MonitorPatternBuilder:
|
|
15
|
+
"""Fluent builder for creating a custom pattern. ``detector_kind`` selects which of
|
|
16
|
+
``include_terms``/``regex``/``semantic_prompt`` is used:
|
|
17
|
+
|
|
18
|
+
- ``"contains"`` (default): ``include_terms`` — a match if any (or all, with
|
|
19
|
+
``match_mode="all"``) phrase appears in the target text.
|
|
20
|
+
- ``"regex"``: ``regex`` — a single regular expression.
|
|
21
|
+
- ``"semantic"``: ``semantic_prompt`` — an LLM judges whether the response violates the
|
|
22
|
+
described rubric.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
client: "MonitorClient",
|
|
28
|
+
name: str,
|
|
29
|
+
description: Optional[str] = None,
|
|
30
|
+
category: Optional[str] = None,
|
|
31
|
+
detector_kind: str = "contains",
|
|
32
|
+
match_target: Optional[List[str]] = None,
|
|
33
|
+
match_mode: str = "any",
|
|
34
|
+
include_terms: Optional[List[str]] = None,
|
|
35
|
+
exclude_terms: Optional[List[str]] = None,
|
|
36
|
+
regex: Optional[str] = None,
|
|
37
|
+
semantic_prompt: Optional[str] = None,
|
|
38
|
+
severity: str = "medium",
|
|
39
|
+
polarity: str = "failure",
|
|
40
|
+
enabled: bool = True,
|
|
41
|
+
sample_rate: float = 1.0,
|
|
42
|
+
scope_mode: str = "all",
|
|
43
|
+
agent_ids: Optional[List[str]] = None,
|
|
44
|
+
):
|
|
45
|
+
self._client = client
|
|
46
|
+
self._payload: Dict[str, Any] = {
|
|
47
|
+
"name": name,
|
|
48
|
+
"description": description,
|
|
49
|
+
"category": category,
|
|
50
|
+
"detectorKind": detector_kind,
|
|
51
|
+
"matchTarget": match_target or ["response"],
|
|
52
|
+
"matchMode": match_mode,
|
|
53
|
+
"includeTerms": include_terms or [],
|
|
54
|
+
"excludeTerms": exclude_terms or [],
|
|
55
|
+
"regex": regex,
|
|
56
|
+
"semanticPrompt": semantic_prompt,
|
|
57
|
+
"severity": severity,
|
|
58
|
+
# A "failure" pattern (default) raises a signal to triage; a "proper" pattern logs
|
|
59
|
+
# a healthy tally instead.
|
|
60
|
+
"polarity": polarity,
|
|
61
|
+
"enabled": enabled,
|
|
62
|
+
"sampleRate": sample_rate,
|
|
63
|
+
"scopeMode": scope_mode,
|
|
64
|
+
"agentIds": agent_ids or [],
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
def publish(self) -> MonitorPattern:
|
|
68
|
+
logger.info("Publishing monitor pattern '%s'", self._payload["name"])
|
|
69
|
+
return self._client.create_pattern(self._payload)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class MonitorPatternClient:
|
|
73
|
+
"""Thin wrapper surfaced as ``client.monitor.patterns``."""
|
|
74
|
+
|
|
75
|
+
def __init__(self, client: "MonitorClient"):
|
|
76
|
+
self._client = client
|
|
77
|
+
|
|
78
|
+
def builder(
|
|
79
|
+
self,
|
|
80
|
+
name: str,
|
|
81
|
+
description: Optional[str] = None,
|
|
82
|
+
category: Optional[str] = None,
|
|
83
|
+
detector_kind: str = "contains",
|
|
84
|
+
match_target: Optional[List[str]] = None,
|
|
85
|
+
match_mode: str = "any",
|
|
86
|
+
include_terms: Optional[List[str]] = None,
|
|
87
|
+
exclude_terms: Optional[List[str]] = None,
|
|
88
|
+
regex: Optional[str] = None,
|
|
89
|
+
semantic_prompt: Optional[str] = None,
|
|
90
|
+
severity: str = "medium",
|
|
91
|
+
polarity: str = "failure",
|
|
92
|
+
enabled: bool = True,
|
|
93
|
+
sample_rate: float = 1.0,
|
|
94
|
+
scope_mode: str = "all",
|
|
95
|
+
agent_ids: Optional[List[str]] = None,
|
|
96
|
+
) -> MonitorPatternBuilder:
|
|
97
|
+
return MonitorPatternBuilder(
|
|
98
|
+
self._client,
|
|
99
|
+
name=name,
|
|
100
|
+
description=description,
|
|
101
|
+
category=category,
|
|
102
|
+
detector_kind=detector_kind,
|
|
103
|
+
match_target=match_target,
|
|
104
|
+
match_mode=match_mode,
|
|
105
|
+
include_terms=include_terms,
|
|
106
|
+
exclude_terms=exclude_terms,
|
|
107
|
+
regex=regex,
|
|
108
|
+
semantic_prompt=semantic_prompt,
|
|
109
|
+
severity=severity,
|
|
110
|
+
polarity=polarity,
|
|
111
|
+
enabled=enabled,
|
|
112
|
+
sample_rate=sample_rate,
|
|
113
|
+
scope_mode=scope_mode,
|
|
114
|
+
agent_ids=agent_ids,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def get(self, pattern_id: str) -> MonitorPattern:
|
|
118
|
+
return self._client.get_pattern(pattern_id)
|
|
119
|
+
|
|
120
|
+
def list(self) -> List[MonitorPattern]:
|
|
121
|
+
return self._client.list_patterns()
|
|
@@ -78,6 +78,8 @@ class _TraceSpan:
|
|
|
78
78
|
model: Optional[str] = None,
|
|
79
79
|
session_id: Optional[str] = None,
|
|
80
80
|
sync: bool = False,
|
|
81
|
+
monitor: bool = False,
|
|
82
|
+
pattern_ids: Optional[List[str]] = None,
|
|
81
83
|
) -> None:
|
|
82
84
|
self._tracer = tracer
|
|
83
85
|
self.name = name
|
|
@@ -91,6 +93,11 @@ class _TraceSpan:
|
|
|
91
93
|
# is populated by the time the `with` block exits — see Tracer.trace()'s sync param.
|
|
92
94
|
self._sync = sync
|
|
93
95
|
self._trace_id: Optional[str] = None
|
|
96
|
+
# Monitor: check this trace against patterns immediately on ingest, no dashboard profile
|
|
97
|
+
# required. pattern_ids (if given) fully defines what's checked — only those patterns run,
|
|
98
|
+
# the built-in checks are skipped. See Tracer.trace()'s monitor/pattern_ids params.
|
|
99
|
+
self._monitor = monitor
|
|
100
|
+
self._pattern_ids = pattern_ids
|
|
94
101
|
|
|
95
102
|
# Fields the caller can set while inside the context manager
|
|
96
103
|
self.output: Any = None
|
|
@@ -171,6 +178,8 @@ class _TraceSpan:
|
|
|
171
178
|
|
|
172
179
|
self._trace_id = self._tracer._send(
|
|
173
180
|
sync=self._sync,
|
|
181
|
+
monitor=self._monitor or None,
|
|
182
|
+
pattern_ids=self._pattern_ids,
|
|
174
183
|
name=self.name,
|
|
175
184
|
input=_safe_serialize(self.input) if self.input is not None else None,
|
|
176
185
|
output=_safe_serialize(self.output) if self.output is not None else None,
|
|
@@ -615,6 +624,8 @@ class Tracer:
|
|
|
615
624
|
model: Optional[str] = None,
|
|
616
625
|
session_id: Optional[str] = None,
|
|
617
626
|
sync: bool = False,
|
|
627
|
+
monitor: bool = False,
|
|
628
|
+
pattern_ids: Optional[List[str]] = None,
|
|
618
629
|
) -> _TraceSpan:
|
|
619
630
|
"""
|
|
620
631
|
Return a :class:`_TraceSpan` that works as both a decorator and a
|
|
@@ -629,6 +640,19 @@ class Tracer:
|
|
|
629
640
|
resp = call_llm(...)
|
|
630
641
|
span.output = resp
|
|
631
642
|
return {"output": resp, "trace_id": span.trace_id}
|
|
643
|
+
|
|
644
|
+
Pass ``monitor=True`` to check this trace against Monitor patterns immediately, with no
|
|
645
|
+
dashboard profile required. ``pattern_ids`` (ids from ``client.monitor.patterns.builder(
|
|
646
|
+
...).publish()``) restricts detection to exactly those patterns; omit it to run the full
|
|
647
|
+
default sweep (built-in checks plus every enabled workspace pattern)::
|
|
648
|
+
|
|
649
|
+
pattern = client.monitor.patterns.builder(
|
|
650
|
+
name="Promises a refund", detector_kind="semantic",
|
|
651
|
+
semantic_prompt="The response promises a refund.",
|
|
652
|
+
).publish()
|
|
653
|
+
|
|
654
|
+
with client.tracer.trace("support_agent_call", monitor=True, pattern_ids=[pattern.id]) as span:
|
|
655
|
+
span.output = call_llm(...)
|
|
632
656
|
"""
|
|
633
657
|
return _TraceSpan(
|
|
634
658
|
tracer=self,
|
|
@@ -639,6 +663,8 @@ class Tracer:
|
|
|
639
663
|
model=model,
|
|
640
664
|
session_id=session_id,
|
|
641
665
|
sync=sync,
|
|
666
|
+
monitor=monitor,
|
|
667
|
+
pattern_ids=pattern_ids,
|
|
642
668
|
)
|
|
643
669
|
|
|
644
670
|
def flush(self, timeout: float = 5.0) -> None:
|
|
@@ -862,6 +888,10 @@ class Tracer:
|
|
|
862
888
|
wire["input_tokens"] = payload["input_tokens"]
|
|
863
889
|
if "output_tokens" in payload:
|
|
864
890
|
wire["output_tokens"] = payload["output_tokens"]
|
|
891
|
+
if "monitor" in payload:
|
|
892
|
+
wire["monitor"] = payload["monitor"]
|
|
893
|
+
if "pattern_ids" in payload:
|
|
894
|
+
wire["pattern_ids"] = payload["pattern_ids"]
|
|
865
895
|
|
|
866
896
|
pending_retrievals, self._pending_retrievals = self._pending_retrievals, []
|
|
867
897
|
pending_tool_calls, self._pending_tool_calls = self._pending_tool_calls, []
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "0.6.7"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentx-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.7
|
|
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
|
|
@@ -70,6 +70,7 @@ Also see [SDK Developer Docs](https://developers.agentx.so), [API Reference Docs
|
|
|
70
70
|
- [Chat (streaming and non-streaming)](#chat-streaming-and-non-streaming)
|
|
71
71
|
- [Workforce (multi-agent orchestration)](#workforce-multi-agent-orchestration) — teams of agents with a designated manager
|
|
72
72
|
- [Production tracing](#production-tracing) — record live agent runs from any framework
|
|
73
|
+
- [Monitor](#monitor) — automatic production monitoring, patterns and signals
|
|
73
74
|
- [Custom agent evaluations](#custom-agent-evaluations) — LLM-as-a-judge, cosine / Jaccard similarity
|
|
74
75
|
- [Links](#links)
|
|
75
76
|
|
|
@@ -245,6 +246,30 @@ See **[TRACING.md](TRACING.md)** for the complete guide — session grouping, er
|
|
|
245
246
|
|
|
246
247
|
---
|
|
247
248
|
|
|
249
|
+
## Monitor
|
|
250
|
+
|
|
251
|
+
Automatic production monitoring: check traces against detection patterns and get back triage-ready signals. A **pattern** is a first-class SDK resource with a real id, just like a `Dataset` or `EvaluationSettings`: create one once, then reference it by id at trace time.
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
pattern = client.monitor.patterns.builder(
|
|
255
|
+
name="Promises a refund",
|
|
256
|
+
detector_kind="semantic",
|
|
257
|
+
semantic_prompt="The response promises a refund.",
|
|
258
|
+
severity="high",
|
|
259
|
+
).publish()
|
|
260
|
+
|
|
261
|
+
with client.tracer.trace("support-agent", monitor=True, pattern_ids=[pattern.id]) as span:
|
|
262
|
+
span.output = call_llm(query)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`monitor=True` checks the trace immediately, no dashboard setup required. `pattern_ids` restricts detection to exactly those patterns; omit it to run the full default sweep instead (built-in checks like empty response, trace error, and latency regression, plus every pattern enabled for the workspace).
|
|
266
|
+
|
|
267
|
+
This works independently of the dashboard's per-agent monitoring toggle (Governance > Observe > Agents), which still auto-checks every trace from an agent once enabled there, with no code changes needed either way.
|
|
268
|
+
|
|
269
|
+
See **[TRACING.md](TRACING.md)** for the complete Monitor guide.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
248
273
|
## Custom agent evaluations
|
|
249
274
|
|
|
250
275
|
Evaluate **any** AI agent — LangChain, CrewAI, AutoGen, LlamaIndex, OpenAI, Anthropic, HTTP endpoints, or plain Python — using AgentX as the scoring and reporting backend. Includes optional **cosine** and **Jaccard** similarity metrics alongside LLM-graded ratings.
|
|
@@ -261,8 +286,13 @@ report = (
|
|
|
261
286
|
print(report.average_rating) # LLM-graded score, 0–10
|
|
262
287
|
print(report.cosine_similarity) # embedding cosine, 0–1 (None if not enabled)
|
|
263
288
|
print(report.jaccard_similarity) # token-set overlap, 0–1 (None if not enabled)
|
|
289
|
+
|
|
290
|
+
print(report.summary) # AI-generated narrative from .analyze()
|
|
291
|
+
print(report.recommendations) # list of prioritized, actionable fixes
|
|
264
292
|
```
|
|
265
293
|
|
|
294
|
+
`.analyze()` also generates a full qualitative report (strengths, weaknesses, instruction adherence, reasoning quality, and recommendations), running the same durable, multi-judge pipeline as the dashboard's "Analyze" button. `analyze(mode=..., quality_mode=..., judges=[...])` controls how items are scored and by which models. See [AI analysis report](EVALUATIONS.md#ai-analysis-report) in the full guide for the complete field and parameter reference.
|
|
295
|
+
|
|
266
296
|
See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder, framework adapters, similarity metrics, and the complete API reference.
|
|
267
297
|
|
|
268
298
|
---
|
|
@@ -274,3 +304,4 @@ See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder,
|
|
|
274
304
|
- **PyPI** — [agentx-python](https://pypi.org/project/agentx-python/)
|
|
275
305
|
- **Tracing docs** — [TRACING.md](TRACING.md)
|
|
276
306
|
- **Evaluations docs** — [EVALUATIONS.md](EVALUATIONS.md)
|
|
307
|
+
- **Monitor docs** — [docs.agentx.so/sdk/monitor](https://docs.agentx.so/sdk/monitor)
|
|
@@ -30,6 +30,10 @@ agentx/integrations/google_adk.py
|
|
|
30
30
|
agentx/integrations/google_genai.py
|
|
31
31
|
agentx/integrations/langchain.py
|
|
32
32
|
agentx/integrations/openai_agents.py
|
|
33
|
+
agentx/monitor/__init__.py
|
|
34
|
+
agentx/monitor/client.py
|
|
35
|
+
agentx/monitor/models.py
|
|
36
|
+
agentx/monitor/patterns.py
|
|
33
37
|
agentx/resources/__init__.py
|
|
34
38
|
agentx/resources/agent.py
|
|
35
39
|
agentx/resources/conversation.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = "0.6.5"
|
|
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
|