agentx-python 0.6.4__tar.gz → 0.6.6__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.4/agentx_python.egg-info → agentx_python-0.6.6}/PKG-INFO +27 -1
- {agentx_python-0.6.4 → agentx_python-0.6.6}/README.md +26 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/agentx.py +18 -2
- agentx_python-0.6.6/agentx/evaluations/__init__.py +7 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/evaluations/client.py +65 -6
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/evaluations/datasets.py +12 -1
- agentx_python-0.6.6/agentx/evaluations/evaluation_settings.py +109 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/evaluations/models.py +89 -7
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/evaluations/reporting.py +14 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/evaluations/results.py +4 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/evaluations/runner.py +87 -10
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/integrations/crewai.py +7 -1
- agentx_python-0.6.6/agentx/monitor/__init__.py +10 -0
- agentx_python-0.6.6/agentx/monitor/client.py +134 -0
- agentx_python-0.6.6/agentx/monitor/models.py +37 -0
- agentx_python-0.6.6/agentx/monitor/patterns.py +121 -0
- agentx_python-0.6.6/agentx/resources/__init__.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/resources/conversation.py +2 -2
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/tracing/ingest_client.py +24 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/tracing/tracer.py +86 -5
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/util.py +2 -1
- agentx_python-0.6.6/agentx/version.py +1 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6/agentx_python.egg-info}/PKG-INFO +27 -1
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx_python.egg-info/SOURCES.txt +7 -0
- agentx_python-0.6.6/agentx_python.egg-info/not-zip-safe +1 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/setup.py +5 -0
- agentx_python-0.6.4/agentx/evaluations/__init__.py +0 -3
- agentx_python-0.6.4/agentx/version.py +0 -1
- {agentx_python-0.6.4 → agentx_python-0.6.6}/LICENSE +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/__init__.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/evaluations/_term.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/evaluations/adapters/__init__.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/evaluations/adapters/http_endpoint.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/evaluations/adapters/precomputed.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/evaluations/adapters/raw.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/evaluations/redaction.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/evaluations/tracing.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/exceptions.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/integrations/__init__.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/integrations/_perf.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/integrations/anthropic.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/integrations/google_adk.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/integrations/google_genai.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/integrations/langchain.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/integrations/openai_agents.py +0 -0
- /agentx_python-0.6.4/agentx/resources/__init__.py → /agentx_python-0.6.6/agentx/py.typed +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/resources/agent.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/resources/workforce.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/tracing/__init__.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx/tracing/ci_types.py +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx_python.egg-info/dependency_links.txt +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx_python.egg-info/requires.txt +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/agentx_python.egg-info/top_level.txt +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/setup.cfg +0 -0
- {agentx_python-0.6.4 → agentx_python-0.6.6}/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.6
|
|
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.
|
|
@@ -274,3 +299,4 @@ See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder,
|
|
|
274
299
|
- **PyPI** — [agentx-python](https://pypi.org/project/agentx-python/)
|
|
275
300
|
- **Tracing docs** — [TRACING.md](TRACING.md)
|
|
276
301
|
- **Evaluations docs** — [EVALUATIONS.md](EVALUATIONS.md)
|
|
302
|
+
- **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.
|
|
@@ -226,3 +251,4 @@ See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder,
|
|
|
226
251
|
- **PyPI** — [agentx-python](https://pypi.org/project/agentx-python/)
|
|
227
252
|
- **Tracing docs** — [TRACING.md](TRACING.md)
|
|
228
253
|
- **Evaluations docs** — [EVALUATIONS.md](EVALUATIONS.md)
|
|
254
|
+
- **Monitor docs** — [docs.agentx.so/sdk/monitor](https://docs.agentx.so/sdk/monitor)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import List
|
|
1
|
+
from typing import List, Optional
|
|
2
2
|
import requests
|
|
3
3
|
import os
|
|
4
4
|
import logging
|
|
@@ -10,7 +10,12 @@ from agentx.resources.workforce import Workforce
|
|
|
10
10
|
|
|
11
11
|
class AgentX:
|
|
12
12
|
|
|
13
|
-
def __init__(
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
api_key: Optional[str] = None,
|
|
16
|
+
base_url: Optional[str] = None,
|
|
17
|
+
workspace_id: Optional[str] = None,
|
|
18
|
+
):
|
|
14
19
|
self.api_key = api_key or os.getenv("AGENTX_API_KEY")
|
|
15
20
|
if self.api_key and not os.getenv("AGENTX_API_KEY"):
|
|
16
21
|
os.environ["AGENTX_API_KEY"] = self.api_key
|
|
@@ -24,6 +29,7 @@ class AgentX:
|
|
|
24
29
|
|
|
25
30
|
from agentx.evaluations.client import EvaluationsClient
|
|
26
31
|
from agentx.evaluations.runner import EvaluationsRunner
|
|
32
|
+
from agentx.monitor.client import MonitorClient
|
|
27
33
|
from agentx.tracing.ingest_client import IngestClient
|
|
28
34
|
from agentx.tracing.tracer import Tracer
|
|
29
35
|
from agentx.version import VERSION
|
|
@@ -32,9 +38,19 @@ class AgentX:
|
|
|
32
38
|
api_key=self.api_key,
|
|
33
39
|
sdk_version=VERSION,
|
|
34
40
|
base_url=self.base_url,
|
|
41
|
+
workspace_id=self.workspace_id,
|
|
35
42
|
)
|
|
36
43
|
self.evaluations = EvaluationsRunner(_eval_client)
|
|
37
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
|
+
|
|
38
54
|
_ingest_client = IngestClient(
|
|
39
55
|
api_key=self.api_key,
|
|
40
56
|
sdk_version=VERSION,
|
|
@@ -13,6 +13,7 @@ from agentx.evaluations.models import (
|
|
|
13
13
|
Dataset,
|
|
14
14
|
EvaluationResult,
|
|
15
15
|
EvaluationRun,
|
|
16
|
+
EvaluationSettings,
|
|
16
17
|
EvaluationSubject,
|
|
17
18
|
ModelInfo,
|
|
18
19
|
Report,
|
|
@@ -44,12 +45,20 @@ class AgentXValidationError(AgentXEvaluationsError):
|
|
|
44
45
|
|
|
45
46
|
class EvaluationsClient:
|
|
46
47
|
def __init__(
|
|
47
|
-
self,
|
|
48
|
+
self,
|
|
49
|
+
api_key: str,
|
|
50
|
+
sdk_version: str = "unknown",
|
|
51
|
+
base_url: Optional[str] = None,
|
|
52
|
+
workspace_id: Optional[str] = None,
|
|
48
53
|
):
|
|
49
54
|
if not api_key:
|
|
50
55
|
raise AgentXAuthError("AGENTX_API_KEY is required")
|
|
51
56
|
self._api_key = api_key
|
|
52
57
|
self._sdk_version = sdk_version
|
|
58
|
+
# Falls back to the caller's default workspace server-side when unset — see
|
|
59
|
+
# _with_workspace(). Without this, dataset/settings/run creation silently land in
|
|
60
|
+
# whatever workspace the API key's user defaults to, not the one the caller intended.
|
|
61
|
+
self._workspace_id = workspace_id
|
|
53
62
|
# Priority: constructor arg > env var > SDK default
|
|
54
63
|
# Always append /custom-agent-evaluations so users only need to provide /api/v1
|
|
55
64
|
_api_base = (
|
|
@@ -67,15 +76,29 @@ class EvaluationsClient:
|
|
|
67
76
|
"accept": "*/*",
|
|
68
77
|
}
|
|
69
78
|
)
|
|
70
|
-
# Expose dataset builder
|
|
79
|
+
# Expose dataset / evaluation-settings builder factories
|
|
71
80
|
from agentx.evaluations.datasets import DatasetClient
|
|
81
|
+
from agentx.evaluations.evaluation_settings import EvaluationSettingsClient
|
|
72
82
|
|
|
73
83
|
self.datasets = DatasetClient(self)
|
|
84
|
+
self.settings = EvaluationSettingsClient(self)
|
|
74
85
|
|
|
75
86
|
# ------------------------------------------------------------------
|
|
76
87
|
# Low-level HTTP
|
|
77
88
|
# ------------------------------------------------------------------
|
|
78
89
|
|
|
90
|
+
def _with_workspace(self, payload: dict) -> dict:
|
|
91
|
+
"""Injects the client's workspace_id into a request payload, unless the caller already
|
|
92
|
+
set one explicitly. Without this, requests silently fall back to the API key user's
|
|
93
|
+
default workspace server-side, which may not be the workspace the caller intended."""
|
|
94
|
+
if self._workspace_id and not payload.get("workspaceId"):
|
|
95
|
+
return {**payload, "workspaceId": self._workspace_id}
|
|
96
|
+
return payload
|
|
97
|
+
|
|
98
|
+
def _workspace_params(self) -> Optional[dict]:
|
|
99
|
+
"""Same as _with_workspace(), for GET requests that take workspaceId as a query param."""
|
|
100
|
+
return {"workspaceId": self._workspace_id} if self._workspace_id else None
|
|
101
|
+
|
|
79
102
|
def _request(self, method: str, path: str, timeout: int = 30, **kwargs) -> Any:
|
|
80
103
|
url = f"{self._base_url}{path}"
|
|
81
104
|
last_exc: Optional[Exception] = None
|
|
@@ -125,20 +148,52 @@ class EvaluationsClient:
|
|
|
125
148
|
# ------------------------------------------------------------------
|
|
126
149
|
|
|
127
150
|
def create_dataset(self, payload: dict) -> Dataset:
|
|
128
|
-
data = self._request("POST", "/datasets", json=payload)
|
|
151
|
+
data = self._request("POST", "/datasets", json=self._with_workspace(payload))
|
|
129
152
|
return Dataset(**data)
|
|
130
153
|
|
|
131
154
|
def list_datasets(self) -> List[Dataset]:
|
|
132
|
-
data = self._request("GET", "/datasets")
|
|
155
|
+
data = self._request("GET", "/datasets", params=self._workspace_params())
|
|
133
156
|
return [
|
|
134
157
|
Dataset(**d)
|
|
135
158
|
for d in (data if isinstance(data, list) else data.get("datasets", []))
|
|
136
159
|
]
|
|
137
160
|
|
|
138
161
|
def get_dataset(self, dataset_id: str) -> Dataset:
|
|
139
|
-
data = self._request(
|
|
162
|
+
data = self._request(
|
|
163
|
+
"GET", f"/datasets/{dataset_id}", params=self._workspace_params()
|
|
164
|
+
)
|
|
140
165
|
return Dataset(**data)
|
|
141
166
|
|
|
167
|
+
# ------------------------------------------------------------------
|
|
168
|
+
# Evaluation Settings endpoints — standalone grading config, reusable
|
|
169
|
+
# across datasets.
|
|
170
|
+
# ------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
def create_evaluation_settings(self, payload: dict) -> EvaluationSettings:
|
|
173
|
+
data = self._request(
|
|
174
|
+
"POST", "/evaluation-settings", json=self._with_workspace(payload)
|
|
175
|
+
)
|
|
176
|
+
return EvaluationSettings(**data)
|
|
177
|
+
|
|
178
|
+
def list_evaluation_settings(self) -> List[EvaluationSettings]:
|
|
179
|
+
data = self._request(
|
|
180
|
+
"GET", "/evaluation-settings", params=self._workspace_params()
|
|
181
|
+
)
|
|
182
|
+
return [
|
|
183
|
+
EvaluationSettings(**e)
|
|
184
|
+
for e in (
|
|
185
|
+
data if isinstance(data, list) else data.get("evaluationSettings", [])
|
|
186
|
+
)
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
def get_evaluation_settings(self, evaluation_settings_id: str) -> EvaluationSettings:
|
|
190
|
+
data = self._request(
|
|
191
|
+
"GET",
|
|
192
|
+
f"/evaluation-settings/{evaluation_settings_id}",
|
|
193
|
+
params=self._workspace_params(),
|
|
194
|
+
)
|
|
195
|
+
return EvaluationSettings(**data)
|
|
196
|
+
|
|
142
197
|
# ------------------------------------------------------------------
|
|
143
198
|
# Run endpoints
|
|
144
199
|
# ------------------------------------------------------------------
|
|
@@ -148,6 +203,7 @@ class EvaluationsClient:
|
|
|
148
203
|
dataset_id: str,
|
|
149
204
|
subject: EvaluationSubject,
|
|
150
205
|
python_version: Optional[str] = None,
|
|
206
|
+
evaluation_settings_id: Optional[str] = None,
|
|
151
207
|
) -> EvaluationRun:
|
|
152
208
|
from agentx.version import VERSION
|
|
153
209
|
|
|
@@ -162,7 +218,9 @@ class EvaluationsClient:
|
|
|
162
218
|
"pythonVersion": python_version or _python_version(),
|
|
163
219
|
},
|
|
164
220
|
}
|
|
165
|
-
|
|
221
|
+
if evaluation_settings_id:
|
|
222
|
+
payload["evaluationSettingsId"] = evaluation_settings_id
|
|
223
|
+
data = self._request("POST", "/runs", json=self._with_workspace(payload))
|
|
166
224
|
return EvaluationRun(**data)
|
|
167
225
|
|
|
168
226
|
def append_results(
|
|
@@ -207,6 +265,7 @@ def _result_to_payload(r: EvaluationResult) -> dict:
|
|
|
207
265
|
d["questionIndex"] = d.pop("question_index", d.get("questionIndex"))
|
|
208
266
|
d["runNumber"] = d.pop("run_number", d.get("runNumber"))
|
|
209
267
|
d["idempotencyKey"] = d.pop("idempotency_key", d.get("idempotencyKey"))
|
|
268
|
+
d["traceId"] = d.pop("trace_id", d.get("traceId"))
|
|
210
269
|
return {k: v for k, v in d.items() if v is not None}
|
|
211
270
|
|
|
212
271
|
|
|
@@ -29,6 +29,8 @@ class DatasetBuilder:
|
|
|
29
29
|
evaluation_criteria: Optional[str] = None,
|
|
30
30
|
vector_similarity: bool = False,
|
|
31
31
|
jaccard_similarity: bool = False,
|
|
32
|
+
bleu_score: bool = False,
|
|
33
|
+
rouge_score: bool = False,
|
|
32
34
|
similarity_model: Optional[str] = None,
|
|
33
35
|
sovereignty_models: Optional[List[str]] = None,
|
|
34
36
|
):
|
|
@@ -43,7 +45,8 @@ class DatasetBuilder:
|
|
|
43
45
|
"questions": [],
|
|
44
46
|
}
|
|
45
47
|
# Opt-in similarity metrics, surfaced on the report as cosine_similarity /
|
|
46
|
-
# jaccard_similarity (computed against each
|
|
48
|
+
# jaccard_similarity / bleu_score / rouge_score (computed against each
|
|
49
|
+
# case's expected_results).
|
|
47
50
|
if vector_similarity:
|
|
48
51
|
vs: Dict[str, Any] = {"enabled": True}
|
|
49
52
|
if similarity_model:
|
|
@@ -51,6 +54,10 @@ class DatasetBuilder:
|
|
|
51
54
|
self._payload["vectorSimilarity"] = vs
|
|
52
55
|
if jaccard_similarity:
|
|
53
56
|
self._payload["jaccardSimilarity"] = {"enabled": True}
|
|
57
|
+
if bleu_score:
|
|
58
|
+
self._payload["bleuScore"] = {"enabled": True}
|
|
59
|
+
if rouge_score:
|
|
60
|
+
self._payload["rougeScore"] = {"enabled": True}
|
|
54
61
|
# Sovereignty & Portability — the models to compare on this dataset (use
|
|
55
62
|
# client.evaluations.list_models() to discover valid ids).
|
|
56
63
|
if sovereignty_models:
|
|
@@ -212,6 +219,8 @@ class DatasetClient:
|
|
|
212
219
|
evaluation_criteria: Optional[str] = None,
|
|
213
220
|
vector_similarity: bool = False,
|
|
214
221
|
jaccard_similarity: bool = False,
|
|
222
|
+
bleu_score: bool = False,
|
|
223
|
+
rouge_score: bool = False,
|
|
215
224
|
similarity_model: Optional[str] = None,
|
|
216
225
|
sovereignty_models: Optional[List[str]] = None,
|
|
217
226
|
) -> DatasetBuilder:
|
|
@@ -225,6 +234,8 @@ class DatasetClient:
|
|
|
225
234
|
evaluation_criteria=evaluation_criteria,
|
|
226
235
|
vector_similarity=vector_similarity,
|
|
227
236
|
jaccard_similarity=jaccard_similarity,
|
|
237
|
+
bleu_score=bleu_score,
|
|
238
|
+
rouge_score=rouge_score,
|
|
228
239
|
similarity_model=similarity_model,
|
|
229
240
|
sovereignty_models=sovereignty_models,
|
|
230
241
|
)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from agentx.evaluations.models import EvaluationSettings
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from agentx.evaluations.client import EvaluationsClient
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EvaluationSettingsBuilder:
|
|
15
|
+
"""Fluent builder for creating a standalone, reusable grading config (no
|
|
16
|
+
dataset/questions attached)."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
client: "EvaluationsClient",
|
|
21
|
+
name: str,
|
|
22
|
+
description: Optional[str] = None,
|
|
23
|
+
number_of_requests: int = 1,
|
|
24
|
+
acceptance_criteria: Optional[str] = None,
|
|
25
|
+
rejection_criteria: Optional[str] = None,
|
|
26
|
+
evaluation_criteria: Optional[str] = None,
|
|
27
|
+
vector_similarity: bool = False,
|
|
28
|
+
jaccard_similarity: bool = False,
|
|
29
|
+
bleu_score: bool = False,
|
|
30
|
+
rouge_score: bool = False,
|
|
31
|
+
similarity_model: Optional[str] = None,
|
|
32
|
+
sovereignty_models: Optional[List[str]] = None,
|
|
33
|
+
):
|
|
34
|
+
self._client = client
|
|
35
|
+
self._payload: Dict[str, Any] = {
|
|
36
|
+
"name": name,
|
|
37
|
+
"description": description,
|
|
38
|
+
"numberOfRequests": number_of_requests,
|
|
39
|
+
"acceptanceCriteria": acceptance_criteria,
|
|
40
|
+
"rejectionCriteria": rejection_criteria,
|
|
41
|
+
"evaluationCriteria": evaluation_criteria,
|
|
42
|
+
}
|
|
43
|
+
# Opt-in similarity metrics, mirrors DatasetBuilder's config kwargs.
|
|
44
|
+
if vector_similarity:
|
|
45
|
+
vs: Dict[str, Any] = {"enabled": True}
|
|
46
|
+
if similarity_model:
|
|
47
|
+
vs["model"] = similarity_model
|
|
48
|
+
self._payload["vectorSimilarity"] = vs
|
|
49
|
+
if jaccard_similarity:
|
|
50
|
+
self._payload["jaccardSimilarity"] = {"enabled": True}
|
|
51
|
+
if bleu_score:
|
|
52
|
+
self._payload["bleuScore"] = {"enabled": True}
|
|
53
|
+
if rouge_score:
|
|
54
|
+
self._payload["rougeScore"] = {"enabled": True}
|
|
55
|
+
# Sovereignty & Portability — the models to compare when this config runs
|
|
56
|
+
# (use client.evaluations.list_models() to discover valid ids).
|
|
57
|
+
if sovereignty_models:
|
|
58
|
+
self._payload["sovereigntyIndex"] = {
|
|
59
|
+
"enabled": True,
|
|
60
|
+
"models": list(sovereignty_models),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
def publish(self) -> EvaluationSettings:
|
|
64
|
+
logger.info("Publishing evaluation settings '%s'", self._payload["name"])
|
|
65
|
+
return self._client.create_evaluation_settings(self._payload)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class EvaluationSettingsClient:
|
|
69
|
+
"""Thin wrapper surfaced as client.evaluations.settings."""
|
|
70
|
+
|
|
71
|
+
def __init__(self, client: "EvaluationsClient"):
|
|
72
|
+
self._client = client
|
|
73
|
+
|
|
74
|
+
def builder(
|
|
75
|
+
self,
|
|
76
|
+
name: str,
|
|
77
|
+
description: Optional[str] = None,
|
|
78
|
+
number_of_requests: int = 1,
|
|
79
|
+
acceptance_criteria: Optional[str] = None,
|
|
80
|
+
rejection_criteria: Optional[str] = None,
|
|
81
|
+
evaluation_criteria: Optional[str] = None,
|
|
82
|
+
vector_similarity: bool = False,
|
|
83
|
+
jaccard_similarity: bool = False,
|
|
84
|
+
bleu_score: bool = False,
|
|
85
|
+
rouge_score: bool = False,
|
|
86
|
+
similarity_model: Optional[str] = None,
|
|
87
|
+
sovereignty_models: Optional[List[str]] = None,
|
|
88
|
+
) -> EvaluationSettingsBuilder:
|
|
89
|
+
return EvaluationSettingsBuilder(
|
|
90
|
+
self._client,
|
|
91
|
+
name=name,
|
|
92
|
+
description=description,
|
|
93
|
+
number_of_requests=number_of_requests,
|
|
94
|
+
acceptance_criteria=acceptance_criteria,
|
|
95
|
+
rejection_criteria=rejection_criteria,
|
|
96
|
+
evaluation_criteria=evaluation_criteria,
|
|
97
|
+
vector_similarity=vector_similarity,
|
|
98
|
+
jaccard_similarity=jaccard_similarity,
|
|
99
|
+
bleu_score=bleu_score,
|
|
100
|
+
rouge_score=rouge_score,
|
|
101
|
+
similarity_model=similarity_model,
|
|
102
|
+
sovereignty_models=sovereignty_models,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def get(self, evaluation_settings_id: str) -> EvaluationSettings:
|
|
106
|
+
return self._client.get_evaluation_settings(evaluation_settings_id)
|
|
107
|
+
|
|
108
|
+
def list(self) -> List[EvaluationSettings]:
|
|
109
|
+
return self._client.list_evaluation_settings()
|
|
@@ -76,6 +76,38 @@ class Dataset(BaseModel):
|
|
|
76
76
|
extra = "ignore"
|
|
77
77
|
|
|
78
78
|
|
|
79
|
+
class EvaluationSettings(BaseModel):
|
|
80
|
+
"""A standalone, reusable grading config — no dataset/questions attached.
|
|
81
|
+
Created via ``client.evaluations.settings.builder(...).publish()`` and run
|
|
82
|
+
against any dataset by passing its id as ``evaluation_settings_id`` to
|
|
83
|
+
``client.evaluations.run(...)``."""
|
|
84
|
+
|
|
85
|
+
id: str = Field(alias="_id")
|
|
86
|
+
name: str
|
|
87
|
+
description: Optional[str] = None
|
|
88
|
+
number_of_requests: int = Field(default=1, alias="numberOfRequests")
|
|
89
|
+
acceptance_criteria: Optional[str] = Field(default=None, alias="acceptanceCriteria")
|
|
90
|
+
rejection_criteria: Optional[str] = Field(default=None, alias="rejectionCriteria")
|
|
91
|
+
evaluation_criteria: Optional[str] = Field(default=None, alias="evaluationCriteria")
|
|
92
|
+
status: str = "published"
|
|
93
|
+
# Sovereignty & Portability — models selected to compare when this config runs.
|
|
94
|
+
# Hoisted from the nested ``sovereigntyIndex`` object when enabled.
|
|
95
|
+
sovereignty_models: List[str] = Field(default_factory=list)
|
|
96
|
+
|
|
97
|
+
@model_validator(mode="before")
|
|
98
|
+
@classmethod
|
|
99
|
+
def _extract_sovereignty_models(cls, data: Any) -> Any:
|
|
100
|
+
if isinstance(data, dict):
|
|
101
|
+
sov = data.get("sovereigntyIndex") or data.get("sovereignty_index") or {}
|
|
102
|
+
if isinstance(sov, dict) and sov.get("enabled") and sov.get("models"):
|
|
103
|
+
data = {**data, "sovereignty_models": list(sov.get("models") or [])}
|
|
104
|
+
return data
|
|
105
|
+
|
|
106
|
+
class Config:
|
|
107
|
+
populate_by_name = True
|
|
108
|
+
extra = "ignore"
|
|
109
|
+
|
|
110
|
+
|
|
79
111
|
# ---------------------------------------------------------------------------
|
|
80
112
|
# Evaluation subject
|
|
81
113
|
# ---------------------------------------------------------------------------
|
|
@@ -154,6 +186,22 @@ class ServerLimits(BaseModel):
|
|
|
154
186
|
extra = "ignore"
|
|
155
187
|
|
|
156
188
|
|
|
189
|
+
class LiveStatistics(BaseModel):
|
|
190
|
+
"""Rating aggregate computed server-side from submitted results — available
|
|
191
|
+
as soon as results are scored, independent of the `.analyze()` step (which
|
|
192
|
+
only adds the LLM-driven qualitative report). Returned on the run resource
|
|
193
|
+
(``GET /runs/:runId``) as ``liveStatistics``."""
|
|
194
|
+
|
|
195
|
+
average_rating: Optional[float] = Field(default=None, alias="averageRating")
|
|
196
|
+
min_rating: Optional[float] = Field(default=None, alias="minRating")
|
|
197
|
+
max_rating: Optional[float] = Field(default=None, alias="maxRating")
|
|
198
|
+
rated_count: int = Field(default=0, alias="ratedCount")
|
|
199
|
+
|
|
200
|
+
class Config:
|
|
201
|
+
populate_by_name = True
|
|
202
|
+
extra = "ignore"
|
|
203
|
+
|
|
204
|
+
|
|
157
205
|
class EvaluationRun(BaseModel):
|
|
158
206
|
run_id: str = Field(alias="runId")
|
|
159
207
|
dataset_id: str = Field(alias="datasetId")
|
|
@@ -226,6 +274,10 @@ class EvaluationResult(BaseModel):
|
|
|
226
274
|
timings: Optional[ResultTimings] = None
|
|
227
275
|
metadata: Optional[Dict[str, Any]] = None
|
|
228
276
|
idempotency_key: Optional[str] = Field(default=None, alias="idempotencyKey")
|
|
277
|
+
# Links this result to a PromptTrace ingested via client.tracer.trace(..., sync=True) — lets
|
|
278
|
+
# the dashboard's "Message Trace Details -> Execution Timeline" render the full execution
|
|
279
|
+
# trace for this case, not just the lightweight observable_trace events above.
|
|
280
|
+
trace_id: Optional[str] = Field(default=None, alias="traceId")
|
|
229
281
|
|
|
230
282
|
class Config:
|
|
231
283
|
populate_by_name = True
|
|
@@ -262,6 +314,8 @@ class BatchAppendResponse(BaseModel):
|
|
|
262
314
|
scored_results: List[ScoredResult] = Field(
|
|
263
315
|
default_factory=list, alias="scoredResults"
|
|
264
316
|
)
|
|
317
|
+
# Server-computed rating aggregate, refreshed after this batch — see LiveStatistics.
|
|
318
|
+
live_statistics: Optional[LiveStatistics] = Field(default=None, alias="liveStatistics")
|
|
265
319
|
|
|
266
320
|
class Config:
|
|
267
321
|
populate_by_name = True
|
|
@@ -280,6 +334,8 @@ class ReportStatistics(BaseModel):
|
|
|
280
334
|
max_rating: float = Field(default=0.0, alias="maxRating")
|
|
281
335
|
cosine_similarity: Optional[float] = Field(default=None, alias="cosineSimilarity")
|
|
282
336
|
jaccard_similarity: Optional[float] = Field(default=None, alias="jaccardSimilarity")
|
|
337
|
+
bleu_score: Optional[float] = Field(default=None, alias="bleuScore")
|
|
338
|
+
rouge_score: Optional[float] = Field(default=None, alias="rougeScore")
|
|
283
339
|
|
|
284
340
|
class Config:
|
|
285
341
|
populate_by_name = True
|
|
@@ -356,6 +412,12 @@ class SovereigntyModelMetrics(BaseModel):
|
|
|
356
412
|
average_jaccard_similarity: Optional[float] = Field(
|
|
357
413
|
default=None, alias="averageJaccardSimilarity"
|
|
358
414
|
)
|
|
415
|
+
average_bleu_score: Optional[float] = Field(
|
|
416
|
+
default=None, alias="averageBleuScore"
|
|
417
|
+
)
|
|
418
|
+
average_rouge_score: Optional[float] = Field(
|
|
419
|
+
default=None, alias="averageRougeScore"
|
|
420
|
+
)
|
|
359
421
|
average_latency_ms: Optional[float] = Field(default=None, alias="averageLatencyMs")
|
|
360
422
|
total_input_tokens: Optional[int] = Field(default=None, alias="totalInputTokens")
|
|
361
423
|
total_output_tokens: Optional[int] = Field(default=None, alias="totalOutputTokens")
|
|
@@ -421,11 +483,15 @@ class Report(BaseModel):
|
|
|
421
483
|
return data
|
|
422
484
|
stats = data.get("statistics")
|
|
423
485
|
stats = dict(stats) if isinstance(stats, dict) else {}
|
|
424
|
-
for top_key, nested_key in (
|
|
425
|
-
("cosineSimilarity", "cosineSimilarity"),
|
|
426
|
-
("cosine_similarity", "cosine_similarity"),
|
|
427
|
-
("jaccardSimilarity", "jaccardSimilarity"),
|
|
428
|
-
("jaccard_similarity", "jaccard_similarity"),
|
|
486
|
+
for top_key, nested_key, marker in (
|
|
487
|
+
("cosineSimilarity", "cosineSimilarity", "cosine"),
|
|
488
|
+
("cosine_similarity", "cosine_similarity", "cosine"),
|
|
489
|
+
("jaccardSimilarity", "jaccardSimilarity", "jaccard"),
|
|
490
|
+
("jaccard_similarity", "jaccard_similarity", "jaccard"),
|
|
491
|
+
("bleuScore", "bleuScore", "bleu"),
|
|
492
|
+
("bleu_score", "bleu_score", "bleu"),
|
|
493
|
+
("rougeScore", "rougeScore", "rouge"),
|
|
494
|
+
("rouge_score", "rouge_score", "rouge"),
|
|
429
495
|
):
|
|
430
496
|
top_val = data.get(top_key)
|
|
431
497
|
if top_val is None:
|
|
@@ -433,15 +499,19 @@ class Report(BaseModel):
|
|
|
433
499
|
if (
|
|
434
500
|
stats.get("cosineSimilarity") is None
|
|
435
501
|
and stats.get("cosine_similarity") is None
|
|
436
|
-
and "cosine"
|
|
502
|
+
and marker == "cosine"
|
|
437
503
|
):
|
|
438
504
|
stats[nested_key] = top_val
|
|
439
505
|
if (
|
|
440
506
|
stats.get("jaccardSimilarity") is None
|
|
441
507
|
and stats.get("jaccard_similarity") is None
|
|
442
|
-
and "jaccard"
|
|
508
|
+
and marker == "jaccard"
|
|
443
509
|
):
|
|
444
510
|
stats[nested_key] = top_val
|
|
511
|
+
if stats.get("bleuScore") is None and stats.get("bleu_score") is None and marker == "bleu":
|
|
512
|
+
stats[nested_key] = top_val
|
|
513
|
+
if stats.get("rougeScore") is None and stats.get("rouge_score") is None and marker == "rouge":
|
|
514
|
+
stats[nested_key] = top_val
|
|
445
515
|
if stats:
|
|
446
516
|
data["statistics"] = stats
|
|
447
517
|
return data
|
|
@@ -462,6 +532,18 @@ class Report(BaseModel):
|
|
|
462
532
|
self.statistics.jaccard_similarity if self.statistics is not None else None
|
|
463
533
|
)
|
|
464
534
|
|
|
535
|
+
@property
|
|
536
|
+
def bleu_score(self) -> Optional[float]:
|
|
537
|
+
"""Average BLEU score across scored results, or ``None`` if the metric
|
|
538
|
+
was not enabled for the dataset or no result has a value yet."""
|
|
539
|
+
return self.statistics.bleu_score if self.statistics is not None else None
|
|
540
|
+
|
|
541
|
+
@property
|
|
542
|
+
def rouge_score(self) -> Optional[float]:
|
|
543
|
+
"""Average ROUGE-L (F1) score across scored results, or ``None`` if the
|
|
544
|
+
metric was not enabled for the dataset or no result has a value yet."""
|
|
545
|
+
return self.statistics.rouge_score if self.statistics is not None else None
|
|
546
|
+
|
|
465
547
|
@property
|
|
466
548
|
def average_rating(self) -> Optional[float]:
|
|
467
549
|
"""Convenience accessor matching cosine_similarity / jaccard_similarity."""
|
|
@@ -67,6 +67,20 @@ def print_report(report: Report) -> None:
|
|
|
67
67
|
f" {dim('Jaccard :')} {jac_color(f'{jac * 100:.1f}%')} {dim('(token-set overlap)')}"
|
|
68
68
|
)
|
|
69
69
|
|
|
70
|
+
bleu = report.bleu_score
|
|
71
|
+
if bleu is not None:
|
|
72
|
+
bleu_color = green if bleu >= 0.6 else (yellow if bleu >= 0.3 else red)
|
|
73
|
+
print(
|
|
74
|
+
f" {dim('BLEU :')} {bleu_color(f'{bleu * 100:.1f}%')} {dim('(n-gram precision)')}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
rouge = report.rouge_score
|
|
78
|
+
if rouge is not None:
|
|
79
|
+
rouge_color = green if rouge >= 0.6 else (yellow if rouge >= 0.3 else red)
|
|
80
|
+
print(
|
|
81
|
+
f" {dim('ROUGE-L :')} {rouge_color(f'{rouge * 100:.1f}%')} {dim('(longest common subsequence)')}"
|
|
82
|
+
)
|
|
83
|
+
|
|
70
84
|
if report.consistency_score is not None:
|
|
71
85
|
cs = report.consistency_score
|
|
72
86
|
cs_color = green if cs >= 7 else (yellow if cs >= 4 else red)
|
|
@@ -34,6 +34,7 @@ def normalize_result(
|
|
|
34
34
|
|
|
35
35
|
output: Optional[dict] = None
|
|
36
36
|
trace = None
|
|
37
|
+
trace_id: Optional[str] = None
|
|
37
38
|
metadata: Optional[dict] = None
|
|
38
39
|
error: Optional[ResultError] = None
|
|
39
40
|
input_tokens: Optional[int] = None
|
|
@@ -50,6 +51,8 @@ def normalize_result(
|
|
|
50
51
|
output = {"text": str(text)} if text else None
|
|
51
52
|
|
|
52
53
|
trace = build_trace(raw.get("trace") or raw.get("observable_trace"))
|
|
54
|
+
trace_id_raw = raw.get("trace_id") or raw.get("traceId")
|
|
55
|
+
trace_id = str(trace_id_raw) if trace_id_raw else None
|
|
53
56
|
meta_raw = raw.get("metadata")
|
|
54
57
|
if isinstance(meta_raw, dict):
|
|
55
58
|
metadata = redact_dict(meta_raw)
|
|
@@ -102,6 +105,7 @@ def normalize_result(
|
|
|
102
105
|
else None
|
|
103
106
|
),
|
|
104
107
|
metadata=metadata,
|
|
108
|
+
traceId=trace_id,
|
|
105
109
|
)
|
|
106
110
|
|
|
107
111
|
|