agentx-python 0.6.34__py3-none-any.whl → 0.6.36__py3-none-any.whl

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/agentx.py CHANGED
@@ -20,10 +20,12 @@ class AgentX:
20
20
  if self.api_key and not os.getenv("AGENTX_API_KEY"):
21
21
  os.environ["AGENTX_API_KEY"] = self.api_key
22
22
 
23
- # base_url overrides AGENTX_API_BASE_URL env var (and the SDK default)
23
+ # base_url overrides AGENTX_API_BASE_URL env var (and the SDK default). It is
24
+ # deliberately NOT written back into os.environ: the constructor used to do that, which
25
+ # made the last-constructed client silently re-point every other client in the process
26
+ # (deep-dive round 3, bug #1). Each sub-client below receives this value explicitly and
27
+ # captures it at construction instead.
24
28
  self.base_url = base_url or os.getenv("AGENTX_API_BASE_URL")
25
- if self.base_url:
26
- os.environ["AGENTX_API_BASE_URL"] = self.base_url
27
29
 
28
30
  self.workspace_id = workspace_id or os.getenv("AGENTX_WORKSPACE_ID")
29
31
 
@@ -58,29 +60,29 @@ class AgentX:
58
60
 
59
61
  # Report real, after-the-fact outcomes ("the ticket got reopened") against traces - the
60
62
  # ground truth behind the dashboard's Judge Calibration card. Self-host only.
61
- self.outcomes = OutcomesClient(api_key=self.api_key)
63
+ self.outcomes = OutcomesClient(api_key=self.api_key, base_url=self.base_url)
62
64
 
63
65
  from agentx.projects import ProjectsClient
64
66
 
65
67
  # Project CRUD (self-host): isolated tenants with their own API keys (P1.1).
66
- self.projects = ProjectsClient(api_key=self.api_key)
68
+ self.projects = ProjectsClient(api_key=self.api_key, base_url=self.base_url)
67
69
 
68
70
  from agentx.traces import TracesClient
69
71
 
70
72
  # The read side of tracing: trace-by-id detail and paginated listing (P1.2).
71
- self.traces = TracesClient(api_key=self.api_key)
73
+ self.traces = TracesClient(api_key=self.api_key, base_url=self.base_url)
72
74
 
73
75
  from agentx.export import ExportClient
74
76
 
75
77
  # Bulk NDJSON egress for backup/migration (P2.1): manifest, per-entity streaming, and
76
78
  # directory dumps. Self-host only.
77
- self.export = ExportClient(api_key=self.api_key)
79
+ self.export = ExportClient(api_key=self.api_key, base_url=self.base_url)
78
80
 
79
81
  from agentx.feedback import FeedbackClient
80
82
 
81
83
  # Forward end-user votes ("up"/"down") on traced responses - a "down" raises a signal
82
84
  # directly, and every vote feeds Judge Calibration alongside outcomes. Self-host only.
83
- self.feedback = FeedbackClient(api_key=self.api_key)
85
+ self.feedback = FeedbackClient(api_key=self.api_key, base_url=self.base_url)
84
86
 
85
87
  _ingest_client = IngestClient(
86
88
  api_key=self.api_key,
@@ -92,13 +94,23 @@ class AgentX:
92
94
 
93
95
  @classmethod
94
96
  def from_env(cls) -> "AgentX":
95
- """Create an AgentX client using AGENTX_API_KEY (and optionally AGENTX_API_BASE_URL) from the environment."""
96
- return cls()
97
+ """Create an AgentX client from the environment: AGENTX_API_KEY plus, for the base URL,
98
+ the first of AGENTX_API_BASE_URL / AGENTX_SELFHOST_BASE_URL / BASE_URL that is set.
99
+ The fallbacks match the conventions the self-host samples and .env files already use,
100
+ so from_env works wherever an explicit AgentX(base_url=...) would."""
101
+ import os
102
+
103
+ base_url = (
104
+ os.getenv("AGENTX_API_BASE_URL")
105
+ or os.getenv("AGENTX_SELFHOST_BASE_URL")
106
+ or os.getenv("BASE_URL")
107
+ )
108
+ return cls(base_url=base_url) if base_url else cls()
97
109
 
98
110
  def get_agent(self, id: str) -> Agent:
99
- url = f"{api_base()}/access/agents/{id}"
111
+ url = f"{self.base_url or api_base()}/access/agents/{id}"
100
112
  # Make a GET request to the AgentX API
101
- response = requests.get(url, headers=get_headers())
113
+ response = requests.get(url, headers=get_headers(self.api_key))
102
114
  # Check if response was successful
103
115
  if response.status_code == 200:
104
116
  return Agent(**response.json())
@@ -106,9 +118,9 @@ class AgentX:
106
118
  raise Exception(f"Failed to retrieve agent: {response.reason}")
107
119
 
108
120
  def list_agents(self) -> List[Agent]:
109
- url = f"{api_base()}/access/agents"
121
+ url = f"{self.base_url or api_base()}/access/agents"
110
122
  # Make a GET request to the AgentX API
111
- response = requests.get(url, headers=get_headers())
123
+ response = requests.get(url, headers=get_headers(self.api_key))
112
124
  # Check if response was successful
113
125
  if response.status_code == 200:
114
126
  return [Agent(**agent) for agent in response.json()]
@@ -146,7 +158,7 @@ class AgentX:
146
158
  """
147
159
  from agentx.exceptions import AgentXAPIError, AgentXAuthError, AgentXConnectionError
148
160
 
149
- base = api_base()
161
+ base = (self.base_url or api_base()).rstrip("/")
150
162
  # /monitor/patterns: the cheapest key-authenticated endpoint that exists on both the
151
163
  # hosted API and the self-host engine's SDK-facing router.
152
164
  url = f"{base}/monitor/patterns"
@@ -173,8 +185,8 @@ class AgentX:
173
185
 
174
186
  def get_profile(self):
175
187
  """Get the current user's profile information."""
176
- url = f"{api_base()}/access/getProfile"
177
- response = requests.get(url, headers=get_headers())
188
+ url = f"{self.base_url or api_base()}/access/getProfile"
189
+ response = requests.get(url, headers=get_headers(self.api_key))
178
190
  if response.status_code == 200:
179
191
  return response.json()
180
192
  else:
@@ -59,6 +59,15 @@ class AgentXValidationError(AgentXEvaluationsError):
59
59
  pass
60
60
 
61
61
 
62
+ def _resolve_scorer_id(scorer_id: Optional[str], evaluation_settings_id: Optional[str]) -> Optional[str]:
63
+ """One grader, two spellings: ``scorer_id`` is the post-consolidation name for what the wire
64
+ still calls ``evaluationSettingsId`` (the ids are identical by design). Both kwargs are
65
+ accepted everywhere a run picks its grader; passing both with different values is a bug."""
66
+ if scorer_id and evaluation_settings_id and scorer_id != evaluation_settings_id:
67
+ raise ValueError("Pass either scorer_id or evaluation_settings_id (they are the same id), not two different ids")
68
+ return scorer_id or evaluation_settings_id
69
+
70
+
62
71
  class EvaluationsClient:
63
72
  def __init__(
64
73
  self,
@@ -100,11 +109,21 @@ class EvaluationsClient:
100
109
  from agentx.evaluations.prompts import PromptClient
101
110
 
102
111
  self.datasets = DatasetClient(self)
103
- self.settings = EvaluationSettingsClient(self)
112
+ # Legacy view of an LLM Judge Scorer's offline profile - constructed lazily so its
113
+ # DeprecationWarning fires on first USE, not for every client that never touches it.
114
+ self._settings: "EvaluationSettingsClient | None" = None
104
115
  self.prompts = PromptClient(self)
105
116
  from agentx.evaluations.tool_schemas import ToolSchemaClient
106
117
  self.tool_schemas = ToolSchemaClient(self)
107
118
 
119
+ @property
120
+ def settings(self) -> "EvaluationSettingsClient":
121
+ if self._settings is None:
122
+ from agentx.evaluations.evaluation_settings import EvaluationSettingsClient
123
+
124
+ self._settings = EvaluationSettingsClient(self)
125
+ return self._settings
126
+
108
127
  # ------------------------------------------------------------------
109
128
  # Low-level HTTP
110
129
  # ------------------------------------------------------------------
@@ -275,10 +294,16 @@ class EvaluationsClient:
275
294
  dataset_id: str,
276
295
  subject: EvaluationSubject,
277
296
  python_version: Optional[str] = None,
297
+ scorer_id: Optional[str] = None,
278
298
  evaluation_settings_id: Optional[str] = None,
279
299
  ) -> EvaluationRun:
300
+ """``scorer_id`` names the LLM Judge Scorer grading this run (its id doubles as the
301
+ wire's ``evaluationSettingsId``). ``evaluation_settings_id`` is the pre-consolidation
302
+ alias and keeps working."""
280
303
  from agentx.version import VERSION
281
304
 
305
+ grader_id = _resolve_scorer_id(scorer_id, evaluation_settings_id)
306
+
282
307
  payload = {
283
308
  "datasetId": dataset_id,
284
309
  "evaluationSubject": subject.model_dump(by_alias=True, exclude_none=True),
@@ -290,8 +315,8 @@ class EvaluationsClient:
290
315
  "pythonVersion": python_version or _python_version(),
291
316
  },
292
317
  }
293
- if evaluation_settings_id:
294
- payload["evaluationSettingsId"] = evaluation_settings_id
318
+ if grader_id:
319
+ payload["evaluationSettingsId"] = grader_id
295
320
  data = self._request("POST", "/runs", json=self._with_workspace(payload))
296
321
  return EvaluationRun(**data)
297
322
 
@@ -290,6 +290,7 @@ class DatasetClient:
290
290
  rouge_score: bool = False,
291
291
  similarity_model: Optional[str] = None,
292
292
  sovereignty_models: Optional[List[str]] = None,
293
+ code_scorers: Optional[List[Dict[str, Any]]] = None,
293
294
  ) -> DatasetBuilder:
294
295
  return DatasetBuilder(
295
296
  self._client,
@@ -307,6 +308,9 @@ class DatasetClient:
307
308
  rouge_score=rouge_score,
308
309
  similarity_model=similarity_model,
309
310
  sovereignty_models=sovereignty_models,
311
+ # Was documented (evaluation/code-scorers.mdx) but not forwarded - DatasetBuilder
312
+ # itself always accepted it. Fixed with the judge-scorer unification.
313
+ code_scorers=code_scorers,
310
314
  )
311
315
 
312
316
  def from_csv(self, path: str, name: str, **kwargs) -> DatasetBuilder:
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
+ import warnings
4
5
  from typing import Any, Dict, List, Optional, TYPE_CHECKING
5
6
 
6
7
  from agentx.evaluations.models import EvaluationSettings
@@ -81,10 +82,25 @@ class EvaluationSettingsBuilder:
81
82
 
82
83
 
83
84
  class EvaluationSettingsClient:
84
- """Thin wrapper surfaced as client.evaluations.settings."""
85
+ """Thin wrapper surfaced as ``client.evaluations.settings``.
86
+
87
+ Note: an evaluation-settings record is the judge rubric + OFFLINE profile of an
88
+ **LLM Judge Scorer** - the unified entity at ``client.monitor.judge_scorers``, which also
89
+ carries the optional online (live-traffic) profile. This client keeps working unchanged;
90
+ prefer ``judge_scorers`` for new code so both profiles live in one place -
91
+ ``client.monitor.judge_scorers.builder(...)`` has the same snake_case ergonomics as the
92
+ builder below, plus tool_context, thresholds, and the live profile in one call."""
85
93
 
86
94
  def __init__(self, client: "EvaluationsClient"):
87
95
  self._client = client
96
+ # Soft deprecation: hidden by default (DeprecationWarning), visible under -W or pytest.
97
+ warnings.warn(
98
+ "client.evaluations.settings is the legacy view of an LLM Judge Scorer's offline "
99
+ "profile; prefer client.monitor.judge_scorers, which manages the judge rubric, "
100
+ "offline profile, and online (live-traffic) profile as one entity.",
101
+ DeprecationWarning,
102
+ stacklevel=3,
103
+ )
88
104
 
89
105
  def builder(
90
106
  self,
@@ -94,7 +94,7 @@ class Dataset(BaseModel):
94
94
 
95
95
  class EvaluationSettings(BaseModel):
96
96
  """A standalone, reusable grading config - no dataset/questions attached.
97
- Created via ``client.evaluations.settings.builder(...).publish()`` and run
97
+ Created via ``client.monitor.judge_scorers.builder(...).publish()`` (or the legacy ``client.evaluations.settings.builder``) and run
98
98
  against any dataset by passing its id as ``evaluation_settings_id`` to
99
99
  ``client.evaluations.run(...)``."""
100
100
 
@@ -421,10 +421,15 @@ class EvaluationsRunner:
421
421
  def __init__(self, client: EvaluationsClient):
422
422
  self._client = client
423
423
  self.datasets = client.datasets
424
- self.settings = client.settings
425
424
  self.prompts = client.prompts
426
425
  self.tool_schemas = client.tool_schemas
427
426
 
427
+ @property
428
+ def settings(self):
429
+ # Deferred: touching client.settings eagerly here would fire its legacy-surface
430
+ # DeprecationWarning for every runner, used or not.
431
+ return self._client.settings
432
+
428
433
  def list_models(self, provider: Optional[str] = None) -> List[ModelInfo]:
429
434
  """List the LLM models AgentX supports - the same set selectable for
430
435
  the Sovereignty & Portability Index. Pass ``provider`` (e.g. "Google")
@@ -482,24 +487,24 @@ class EvaluationsRunner:
482
487
  self,
483
488
  dataset_id: str,
484
489
  subject: Union[Dict[str, Any], EvaluationSubject],
490
+ scorer_id: Optional[str] = None,
485
491
  evaluation_settings_id: Optional[str] = None,
486
492
  ) -> EvaluationRunContext:
487
- """Start a run of ``dataset_id`` against ``subject``. Pass
488
- ``evaluation_settings_id`` to grade against a standalone, reusable
489
- config (created via ``client.evaluations.settings.builder(...)``)
490
- instead of the dataset's own default config."""
493
+ """Start a run of ``dataset_id`` against ``subject``. Pass ``scorer_id`` (an LLM Judge
494
+ Scorer's id, e.g. from ``client.monitor.judge_scorers``) to grade with a specific
495
+ scorer instead of the dataset's default. ``evaluation_settings_id`` is the
496
+ pre-consolidation alias for the same id and keeps working."""
497
+ from agentx.evaluations.client import _resolve_scorer_id
498
+
491
499
  if isinstance(subject, dict):
492
500
  subject = EvaluationSubject(**subject)
493
501
 
502
+ grader_id = _resolve_scorer_id(scorer_id, evaluation_settings_id)
494
503
  dataset = self._client.get_dataset(dataset_id)
495
504
  evaluation_settings = (
496
- self._client.get_evaluation_settings(evaluation_settings_id)
497
- if evaluation_settings_id
498
- else None
499
- )
500
- run = self._client.init_run(
501
- dataset_id, subject, evaluation_settings_id=evaluation_settings_id
505
+ self._client.get_evaluation_settings(grader_id) if grader_id else None
502
506
  )
507
+ run = self._client.init_run(dataset_id, subject, scorer_id=grader_id)
503
508
  logger.info(
504
509
  "Started evaluation run %s on dataset %s (%d case(s), %d repetition(s))",
505
510
  run.run_id,
agentx/export.py CHANGED
@@ -36,13 +36,15 @@ class ExportClient:
36
36
  (``pg_dump`` / SQLite file copy). There is deliberately no blind row-import endpoint.
37
37
  """
38
38
 
39
- def __init__(self, api_key: Optional[str] = None):
39
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
40
40
  self._api_key = api_key
41
+ # Captured once at construction (deep-dive round 3, bug #1).
42
+ self._base_url = (base_url or api_base()).rstrip("/")
41
43
 
42
44
  def manifest(self) -> List[Dict[str, Any]]:
43
45
  """The exportable entities with live row counts: ``[{entity, rows, path}, ...]``."""
44
46
  resp = requests.get(
45
- f"{api_base()}/export", headers=get_headers(self._api_key), timeout=30
47
+ f"{self._base_url}/export", headers=get_headers(self._api_key), timeout=30
46
48
  )
47
49
  if resp.status_code >= 400:
48
50
  raise AgentXExportError(f"Export manifest failed ({resp.status_code}): {resp.text[:200]}")
@@ -54,7 +56,7 @@ class ExportClient:
54
56
  timestamp column, e.g. ``createdAt`` for traces, ``lastSeenAt`` for signals)."""
55
57
  params = {"since": since} if since else None
56
58
  resp = requests.get(
57
- f"{api_base()}/export/{entity}",
59
+ f"{self._base_url}/export/{entity}",
58
60
  headers=get_headers(self._api_key),
59
61
  params=params,
60
62
  stream=True,
agentx/feedback.py CHANGED
@@ -26,8 +26,10 @@ class FeedbackClient:
26
26
  reopened", reported by a workflow); feedback is a human vote with up/down semantics.
27
27
  """
28
28
 
29
- def __init__(self, api_key: Optional[str] = None):
29
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
30
30
  self._api_key = api_key
31
+ # Captured once at construction (deep-dive round 3, bug #1).
32
+ self._base_url = (base_url or api_base()).rstrip("/")
31
33
 
32
34
  def report(
33
35
  self,
@@ -61,7 +63,7 @@ class FeedbackClient:
61
63
  payload["endUserId"] = end_user_id
62
64
 
63
65
  resp = requests.post(
64
- f"{api_base()}/feedback",
66
+ f"{self._base_url}/feedback",
65
67
  headers={**get_headers(self._api_key), "Content-Type": "application/json"},
66
68
  json=payload,
67
69
  timeout=10,
@@ -79,7 +81,7 @@ class FeedbackClient:
79
81
  """All votes recorded on one trace, oldest first (``GET /feedback/trace/:traceId``) -
80
82
  the same rows the dashboard's trace dialog shows as up/down chips."""
81
83
  resp = requests.get(
82
- f"{api_base()}/feedback/trace/{trace_id}",
84
+ f"{self._base_url}/feedback/trace/{trace_id}",
83
85
  headers=get_headers(self._api_key),
84
86
  timeout=10,
85
87
  )
agentx/monitor/client.py CHANGED
@@ -85,9 +85,18 @@ class MonitorClient:
85
85
  from agentx.monitor.scorers import ScorersClient
86
86
  # Scorers-catalog administration as code: template enable/disable, code/external scorer
87
87
  # CRUD and dry runs - full parity with the dashboard's Scorers page (P1.3).
88
- self.scorers = ScorersClient(api_key=api_key)
88
+ # Handed this client's own resolved API root, never the process-global default, so a
89
+ # second AgentX() with a different base_url can't re-point it (deep-dive bug #1).
90
+ self.scorers = ScorersClient(api_key=api_key, base_url=self._api_root())
91
+ from agentx.monitor.judge_scorers import JudgeScorersClient
92
+ # The unified LLM Judge Scorer (rubric + offline/online profiles in one entity) - the
93
+ # surface that matches the product; evaluations.settings and online_evaluators below
94
+ # remain as its profile-level views.
95
+ self.judge_scorers = JudgeScorersClient(api_key=api_key, base_url=self._api_root())
89
96
  self.profile = MonitorProfileClient(self)
90
- self.online_evaluators = MonitorOnlineEvaluatorClient(self)
97
+ # Legacy view of an LLM Judge Scorer's online profile - constructed lazily so its
98
+ # DeprecationWarning fires on first USE, not for every client that never touches it.
99
+ self._online_evaluators: "MonitorOnlineEvaluatorClient | None" = None
91
100
  from agentx.monitor.sessions import MonitorSessionClient
92
101
  from agentx.monitor.agents import MonitorAgentClient
93
102
  self.sessions = MonitorSessionClient(self)
@@ -171,6 +180,14 @@ class MonitorClient:
171
180
  data = self._request("POST", "/online-evaluators", json=self._with_workspace(payload))
172
181
  return MonitorOnlineEvaluator(**data["evaluator"])
173
182
 
183
+ @property
184
+ def online_evaluators(self) -> "MonitorOnlineEvaluatorClient":
185
+ if self._online_evaluators is None:
186
+ from agentx.monitor.online_evaluators import MonitorOnlineEvaluatorClient
187
+
188
+ self._online_evaluators = MonitorOnlineEvaluatorClient(self)
189
+ return self._online_evaluators
190
+
174
191
  def list_online_evaluators(self) -> List[MonitorOnlineEvaluator]:
175
192
  data = self._request("GET", "/online-evaluators", params=self._workspace_params())
176
193
  return [MonitorOnlineEvaluator(**e) for e in data.get("evaluators", [])]
@@ -0,0 +1,334 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ import requests
7
+
8
+ from agentx.util import api_base, get_headers
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ _SENTINEL: Any = object()
13
+
14
+
15
+ class AgentXJudgeScorersError(Exception):
16
+ pass
17
+
18
+
19
+ class JudgeScorer(dict):
20
+ """Wire object for the unified LLM Judge Scorer (dict subclass so unknown fields
21
+ round-trip). Convenience properties expose the pieces scripts reach for most."""
22
+
23
+ @property
24
+ def id(self) -> str:
25
+ return self["_id"]
26
+
27
+ @property
28
+ def name(self) -> str:
29
+ return self["name"]
30
+
31
+ @property
32
+ def judge(self) -> Dict[str, Any]:
33
+ return self.get("judge", {})
34
+
35
+ @property
36
+ def offline(self) -> Dict[str, Any]:
37
+ return self.get("offline", {})
38
+
39
+ @property
40
+ def online(self) -> Optional[Dict[str, Any]]:
41
+ return self.get("online")
42
+
43
+ @property
44
+ def online_profile_id(self) -> Optional[str]:
45
+ online = self.online
46
+ return online.get("profileId") if online else None
47
+
48
+
49
+ class JudgeScorersClient:
50
+ """Surfaced as ``client.monitor.judge_scorers``: the unified LLM Judge Scorer.
51
+
52
+ One scorer = one judge rubric (``judge``: acceptance/rejection/evaluation criteria, judge
53
+ prompt, judge model) + two setting profiles:
54
+
55
+ - ``offline`` - how dataset runs grade with it (repetitions, similarity metrics, code
56
+ scorers, default/status). The scorer's ``id`` is exactly what
57
+ ``client.evaluations.run(dataset_id, scorer_id=scorer.id)`` takes.
58
+ - ``online`` - whether/how it scores live traffic (enabled, sample rate, scope,
59
+ alert threshold). ``None`` means offline-only.
60
+
61
+ Strictly one online profile per scorer. This supersedes the split between
62
+ ``client.evaluations.settings`` (the offline half) and ``client.monitor.online_evaluators``
63
+ (the online half) - both keep working, but this is the surface that matches the product.
64
+
65
+ Example::
66
+
67
+ scorer = client.monitor.judge_scorers.create(
68
+ "Support quality",
69
+ judge={"acceptanceCriteria": "Concrete, correct, cites the policy."},
70
+ online={"enabled": True, "sampleRate": 0.2, "alertThreshold": 6},
71
+ )
72
+ client.evaluations.run(dataset_id, subject, scorer_id=scorer.id)
73
+ cal = client.monitor.judge_scorers.calibration(scorer.id)
74
+ """
75
+
76
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
77
+ self._api_key = api_key
78
+ # Captured once at construction so two clients with different bases can coexist.
79
+ self._base_url = (base_url or api_base()).rstrip("/")
80
+
81
+ def _request(self, method: str, path: str, json: Any = None, timeout: int = 60) -> Any:
82
+ resp = requests.request(
83
+ method,
84
+ f"{self._base_url}/agent-monitoring{path}",
85
+ headers={**get_headers(self._api_key), "Content-Type": "application/json"},
86
+ json=json,
87
+ timeout=timeout,
88
+ )
89
+ if resp.status_code >= 400:
90
+ try:
91
+ detail = resp.json().get("error", resp.reason)
92
+ except ValueError:
93
+ detail = resp.reason
94
+ raise AgentXJudgeScorersError(f"Judge scorer request failed ({resp.status_code}): {detail}")
95
+ return resp.json() if resp.text else {}
96
+
97
+ # ------------------------------------------------------------------
98
+ # CRUD
99
+ # ------------------------------------------------------------------
100
+
101
+ def builder(
102
+ self,
103
+ name: str,
104
+ description: Optional[str] = None,
105
+ *,
106
+ # Judge rubric (shared by every surface that runs this scorer)
107
+ acceptance_criteria: Optional[str] = None,
108
+ rejection_criteria: Optional[str] = None,
109
+ evaluation_criteria: Optional[str] = None,
110
+ judge_prompt: Optional[str] = None,
111
+ judge_model: Optional[str] = None,
112
+ tool_context: Optional[str] = None,
113
+ # Offline profile (dataset-run grading)
114
+ number_of_requests: int = 1,
115
+ vector_similarity: bool = False,
116
+ jaccard_similarity: bool = False,
117
+ bleu_score: bool = False,
118
+ rouge_score: bool = False,
119
+ similarity_model: Optional[str] = None,
120
+ sovereignty_models: Optional[List[str]] = None,
121
+ thresholds: Optional[Dict[str, Any]] = None,
122
+ code_scorers: Optional[List[Dict[str, Any]]] = None,
123
+ is_default: bool = False,
124
+ # Online profile (live traffic). live=True enables scoring immediately - every check is
125
+ # a real judge call on your own key.
126
+ live: bool = False,
127
+ sample_rate: float = 0.1,
128
+ scope: str = "trace",
129
+ alert_threshold: Optional[float] = 5,
130
+ severity: str = "medium",
131
+ agent_ids: Optional[List[str]] = None,
132
+ idle_seconds: int = 120,
133
+ ) -> "JudgeScorerBuilder":
134
+ """Snake_case builder with ``.publish()``, the unified successor of
135
+ ``client.evaluations.settings.builder(...)`` - same offline fields (plus ``thresholds``,
136
+ ``tool_context``) and, new here, the online profile in the same call. The scorer the
137
+ builder publishes is one entity: its ``.id`` is what ``client.evaluations.run(...,
138
+ scorer_id=...)`` takes, and its live profile is what online scoring keys on."""
139
+ judge: Dict[str, Any] = {}
140
+ for key, value in (
141
+ ("acceptanceCriteria", acceptance_criteria),
142
+ ("rejectionCriteria", rejection_criteria),
143
+ ("evaluationCriteria", evaluation_criteria),
144
+ ("judgePrompt", judge_prompt),
145
+ ("judgeModel", judge_model),
146
+ ("toolContext", tool_context),
147
+ ):
148
+ if value is not None:
149
+ judge[key] = value
150
+ offline: Dict[str, Any] = {"numberOfRequests": number_of_requests, "isDefault": is_default}
151
+ if vector_similarity:
152
+ offline["vectorSimilarity"] = (
153
+ {"enabled": True, "model": similarity_model} if similarity_model else {"enabled": True}
154
+ )
155
+ if jaccard_similarity:
156
+ offline["jaccardSimilarity"] = {"enabled": True}
157
+ if bleu_score:
158
+ offline["bleuScore"] = {"enabled": True}
159
+ if rouge_score:
160
+ offline["rougeScore"] = {"enabled": True}
161
+ if sovereignty_models is not None:
162
+ offline["sovereigntyIndex"] = {"enabled": bool(sovereignty_models), "models": sovereignty_models}
163
+ if thresholds is not None:
164
+ offline["thresholds"] = thresholds
165
+ if code_scorers is not None:
166
+ offline["codeScorers"] = code_scorers
167
+ online: Optional[Dict[str, Any]] = None
168
+ if live:
169
+ online = {
170
+ "enabled": True,
171
+ "sampleRate": sample_rate,
172
+ "scope": scope,
173
+ "alertThreshold": alert_threshold,
174
+ "severity": severity,
175
+ "idleSeconds": idle_seconds,
176
+ }
177
+ if agent_ids:
178
+ online["scopeMode"] = "selected"
179
+ online["agentIds"] = agent_ids
180
+ return JudgeScorerBuilder(self, name=name, description=description, judge=judge, offline=offline, online=online)
181
+
182
+ def create(
183
+ self,
184
+ name: str,
185
+ *,
186
+ description: Optional[str] = None,
187
+ judge: Optional[Dict[str, Any]] = None,
188
+ offline: Optional[Dict[str, Any]] = None,
189
+ online: Optional[Dict[str, Any]] = None,
190
+ ) -> JudgeScorer:
191
+ """Create a scorer. ``online=None`` (default) creates it offline-only; pass e.g.
192
+ ``online={"enabled": True, "sampleRate": 0.1, "alertThreshold": 5}`` to score live
193
+ traffic from the start. Section dicts use the wire's camelCase keys."""
194
+ payload: Dict[str, Any] = {"name": name}
195
+ if description is not None:
196
+ payload["description"] = description
197
+ if judge is not None:
198
+ payload["judge"] = judge
199
+ if offline is not None:
200
+ payload["offline"] = offline
201
+ if online is not None:
202
+ payload["online"] = online
203
+ return JudgeScorer(self._request("POST", "/judge-scorers", json=payload)["judgeScorer"])
204
+
205
+ def get(self, scorer_id: str) -> JudgeScorer:
206
+ return JudgeScorer(self._request("GET", f"/judge-scorers/{scorer_id}")["judgeScorer"])
207
+
208
+ def list(self) -> List[JudgeScorer]:
209
+ return [JudgeScorer(s) for s in self._request("GET", "/judge-scorers").get("judgeScorers", [])]
210
+
211
+ def update(
212
+ self,
213
+ scorer_id: str,
214
+ *,
215
+ name: Optional[str] = None,
216
+ description: Optional[str] = None,
217
+ judge: Optional[Dict[str, Any]] = None,
218
+ offline: Optional[Dict[str, Any]] = None,
219
+ online: Any = _SENTINEL,
220
+ ) -> JudgeScorer:
221
+ """Sparse update: only the sections you pass change. ``online={...}`` upserts the
222
+ online profile (this is how an offline-only scorer goes live); ``online=None``
223
+ detaches it (refused for the built-in Session Baseline Judge)."""
224
+ payload: Dict[str, Any] = {}
225
+ if name is not None:
226
+ payload["name"] = name
227
+ if description is not None:
228
+ payload["description"] = description
229
+ if judge is not None:
230
+ payload["judge"] = judge
231
+ if offline is not None:
232
+ payload["offline"] = offline
233
+ if online is not _SENTINEL:
234
+ payload["online"] = online
235
+ return JudgeScorer(self._request("PUT", f"/judge-scorers/{scorer_id}", json=payload)["judgeScorer"])
236
+
237
+ def delete(self, scorer_id: str) -> None:
238
+ """Delete the scorer: rubric, version history, and online profile together.
239
+ Irreversible; refused for the built-in Session Baseline Judge."""
240
+ self._request("DELETE", f"/judge-scorers/{scorer_id}")
241
+
242
+ # ------------------------------------------------------------------
243
+ # Online-profile pass-throughs (calibration / tuning / ratings / events)
244
+ # These endpoints key on the online profile's id, resolved here so callers only ever
245
+ # handle the scorer id.
246
+ # ------------------------------------------------------------------
247
+
248
+ def _profile_id(self, scorer_id: str) -> str:
249
+ scorer = self.get(scorer_id)
250
+ profile_id = scorer.online_profile_id
251
+ if not profile_id:
252
+ raise AgentXJudgeScorersError(
253
+ f"Judge scorer {scorer_id!r} has no online profile - calibration/tuning/ratings "
254
+ "cover live-traffic scoring. Give it one first: "
255
+ "update(scorer_id, online={'enabled': True})."
256
+ )
257
+ return profile_id
258
+
259
+ def calibration(self, scorer_id: str, window: str = "7d") -> dict:
260
+ """How this scorer's verdicts compare against recorded ground truth (triage
261
+ corrections, outcomes, end-user votes) over the window."""
262
+ return self._request("GET", f"/online-evaluators/{self._profile_id(scorer_id)}/calibration?window={window}")
263
+
264
+ def tune(self, scorer_id: str, window: str = "7d") -> dict:
265
+ """Propose a rewrite of the rubric from calibration disagreements (LLM call, slow)."""
266
+ data = self._request(
267
+ "POST", f"/online-evaluators/{self._profile_id(scorer_id)}/tune", json={"window": window}, timeout=300
268
+ )
269
+ # The wire wraps the proposal ({"proposal": {...}}); unwrap like the legacy client so
270
+ # proposal["reasoning"] / the criteria fields are directly addressable.
271
+ return data.get("proposal", data) if isinstance(data, dict) else data
272
+
273
+ def validate_tuning(self, scorer_id: str, criteria: Dict[str, Any], window: str = "7d") -> dict:
274
+ """Re-judge the disagreement + control cases with candidate criteria (LLM calls, slow)."""
275
+ # The wire takes the criteria fields at the TOP level of the body, not nested.
276
+ return self._request(
277
+ "POST",
278
+ f"/online-evaluators/{self._profile_id(scorer_id)}/tune/validate",
279
+ json={**criteria, "window": window},
280
+ timeout=600,
281
+ )
282
+
283
+ def publish_tuning(self, scorer_id: str, criteria: Dict[str, Any]) -> dict:
284
+ """Write tuned criteria onto the scorer's rubric - it applies everywhere the scorer is
285
+ used: online scoring, offline dataset runs, and the playground."""
286
+ return self._request(
287
+ "POST", f"/online-evaluators/{self._profile_id(scorer_id)}/tune/publish", json=dict(criteria)
288
+ )
289
+
290
+ def ratings(self, scorer_id: str, window: str = "7d") -> "List[OnlineEvaluatorRatingPoint]":
291
+ """Bucketed average-rating-over-time for this scorer's live checks - same typed points
292
+ the legacy online_evaluators client returns, so scripts migrate without shape changes."""
293
+ from agentx.monitor.models import OnlineEvaluatorRatingPoint
294
+
295
+ data = self._request("GET", f"/online-evaluators/{self._profile_id(scorer_id)}/ratings?window={window}")
296
+ return [OnlineEvaluatorRatingPoint(**p) for p in data.get("points", [])]
297
+
298
+ def events(self, scorer_id: str, window: str = "7d") -> "List[OnlineEvaluatorEvent]":
299
+ """Individually scored traces behind the ratings series, worst-rated first - typed, same
300
+ as the legacy online_evaluators client."""
301
+ from agentx.monitor.models import OnlineEvaluatorEvent
302
+
303
+ data = self._request("GET", f"/online-evaluators/{self._profile_id(scorer_id)}/events?window={window}")
304
+ return [OnlineEvaluatorEvent(**e) for e in data.get("events", [])]
305
+
306
+
307
+ class JudgeScorerBuilder:
308
+ """Assembled by :meth:`JudgeScorersClient.builder`; ``.publish()`` creates the scorer."""
309
+
310
+ def __init__(
311
+ self,
312
+ client: "JudgeScorersClient",
313
+ *,
314
+ name: str,
315
+ description: Optional[str],
316
+ judge: Dict[str, Any],
317
+ offline: Dict[str, Any],
318
+ online: Optional[Dict[str, Any]],
319
+ ):
320
+ self._client = client
321
+ self._name = name
322
+ self._description = description
323
+ self._judge = judge
324
+ self._offline = offline
325
+ self._online = online
326
+
327
+ def publish(self) -> JudgeScorer:
328
+ return self._client.create(
329
+ self._name,
330
+ description=self._description,
331
+ judge=self._judge or None,
332
+ offline=self._offline or None,
333
+ online=self._online,
334
+ )
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
+ import warnings
4
5
  from typing import Any, Dict, List, Optional, TYPE_CHECKING
5
6
 
6
7
  from agentx.monitor.models import MonitorOnlineEvaluator, OnlineEvaluatorRatingPoint, OnlineEvaluatorEvent
@@ -17,7 +18,12 @@ class MonitorOnlineEvaluatorBuilder:
17
18
  ``evaluation_settings_id`` must reference an existing Evaluator config (criteria, judge
18
19
  prompt, judge model), the same config datasets/Evaluate runs use, see
19
20
  ``client.evaluations.settings.builder(...)``.
20
- """
21
+
22
+ Note: an online evaluator is the ONLINE profile of an **LLM Judge Scorer** - the unified
23
+ entity at ``client.monitor.judge_scorers``. Strictly one profile per config since the
24
+ unification: binding a config that is already another evaluator's profile transparently
25
+ binds a fresh copy of it instead (the response's ``evaluationSettingsId`` is the copy).
26
+ This surface keeps working unchanged; prefer ``judge_scorers`` for new code."""
21
27
 
22
28
  def __init__(
23
29
  self,
@@ -60,10 +66,23 @@ class MonitorOnlineEvaluatorBuilder:
60
66
 
61
67
 
62
68
  class MonitorOnlineEvaluatorClient:
63
- """Thin wrapper surfaced as ``client.monitor.online_evaluators``."""
69
+ """Thin wrapper surfaced as ``client.monitor.online_evaluators``.
70
+
71
+ Note: an online evaluator is the ONLINE (live-traffic) profile of an **LLM Judge Scorer** -
72
+ the unified entity at ``client.monitor.judge_scorers``, which also carries the judge rubric
73
+ and the offline profile. This client keeps working unchanged; prefer ``judge_scorers`` for
74
+ new code so both profiles live in one place."""
64
75
 
65
76
  def __init__(self, client: "MonitorClient"):
66
77
  self._client = client
78
+ # Soft deprecation: hidden by default (DeprecationWarning), visible under -W or pytest.
79
+ warnings.warn(
80
+ "client.monitor.online_evaluators is the legacy view of an LLM Judge Scorer's "
81
+ "online profile; prefer client.monitor.judge_scorers, which manages the judge "
82
+ "rubric, offline profile, and online profile as one entity.",
83
+ DeprecationWarning,
84
+ stacklevel=3,
85
+ )
67
86
 
68
87
  def builder(
69
88
  self,
agentx/monitor/scorers.py CHANGED
@@ -33,13 +33,15 @@ class ScorersClient:
33
33
  wire.
34
34
  """
35
35
 
36
- def __init__(self, api_key: Optional[str] = None):
36
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
37
37
  self._api_key = api_key
38
+ # Captured once at construction (deep-dive round 3, bug #1).
39
+ self._base_url = (base_url or api_base()).rstrip("/")
38
40
 
39
41
  def _request(self, method: str, path: str, json: Any = None, params: Any = None) -> Any:
40
42
  resp = requests.request(
41
43
  method,
42
- f"{api_base()}/agent-monitoring{path}",
44
+ f"{self._base_url}/agent-monitoring{path}",
43
45
  headers={**get_headers(self._api_key), "Content-Type": "application/json"},
44
46
  json=json,
45
47
  params=params,
agentx/outcomes.py CHANGED
@@ -26,8 +26,10 @@ class OutcomesClient:
26
26
  tracker's webhook, a CRM workflow), and this method is the SDK's way to be that caller.
27
27
  """
28
28
 
29
- def __init__(self, api_key: Optional[str] = None):
29
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
30
30
  self._api_key = api_key
31
+ # Captured once at construction (deep-dive round 3, bug #1).
32
+ self._base_url = (base_url or api_base()).rstrip("/")
31
33
 
32
34
  def report(
33
35
  self,
@@ -69,7 +71,7 @@ class OutcomesClient:
69
71
  payload["reportedBy"] = reported_by
70
72
 
71
73
  resp = requests.post(
72
- f"{api_base()}/outcomes",
74
+ f"{self._base_url}/outcomes",
73
75
  headers={**get_headers(self._api_key), "Content-Type": "application/json"},
74
76
  json=payload,
75
77
  timeout=10,
agentx/projects.py CHANGED
@@ -25,13 +25,16 @@ class ProjectsClient:
25
25
  users; this client covers the default self-host (auth-disabled) mode.
26
26
  """
27
27
 
28
- def __init__(self, api_key: Optional[str] = None):
28
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
29
29
  self._api_key = api_key
30
+ # Captured once at construction so two clients with different bases can coexist
31
+ # (deep-dive round 3, bug #1) - never read from process-global state per call.
32
+ self._base_url = (base_url or api_base()).rstrip("/")
30
33
 
31
34
  def _request(self, method: str, path: str, json: Any = None) -> Any:
32
35
  resp = requests.request(
33
36
  method,
34
- f"{api_base()}{path}",
37
+ f"{self._base_url}{path}",
35
38
  headers={**get_headers(self._api_key), "Content-Type": "application/json"},
36
39
  json=json,
37
40
  timeout=15,
agentx/traces.py CHANGED
@@ -24,12 +24,14 @@ class TracesClient:
24
24
  span-tree read.
25
25
  """
26
26
 
27
- def __init__(self, api_key: Optional[str] = None):
27
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
28
28
  self._api_key = api_key
29
+ # Captured once at construction (deep-dive round 3, bug #1).
30
+ self._base_url = (base_url or api_base()).rstrip("/")
29
31
 
30
32
  def _request(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
31
33
  resp = requests.get(
32
- f"{api_base()}{path}",
34
+ f"{self._base_url}{path}",
33
35
  headers=get_headers(self._api_key),
34
36
  params=params or {},
35
37
  timeout=15,
@@ -72,6 +72,11 @@ class IngestClient:
72
72
  # failures stay at DEBUG to avoid log spam. client.ping() is the fail-fast startup check.
73
73
  self._delivery_warning_emitted = False
74
74
 
75
+ # Dropped-trace accounting (deep-dive round 3, bug #5): fire-and-forget may drop traces
76
+ # (queue overflow, retries exhausted while the engine is down), but it must never drop
77
+ # them SILENTLY. First drop warns, then every 50th, with the cumulative count.
78
+ self._dropped = 0
79
+
75
80
  self._queue: queue.Queue[Optional[Dict[str, Any]]] = queue.Queue(maxsize=_QUEUE_MAX)
76
81
  self._worker = threading.Thread(target=self._drain, daemon=True, name="agentx-ingest")
77
82
  self._worker.start()
@@ -90,11 +95,44 @@ class IngestClient:
90
95
  try:
91
96
  self._queue.put_nowait(payload)
92
97
  except queue.Full:
93
- logger.debug("agentx ingest queue full - trace dropped")
98
+ self._record_drop("queue full")
99
+
100
+ def flush(self, timeout: float = 5.0) -> bool:
101
+ """Block until all queued traces have been sent, or until ``timeout`` seconds elapse.
94
102
 
95
- def flush(self, timeout: float = 5.0) -> None:
96
- """Block until all queued traces have been sent (or timeout elapses)."""
97
- self._queue.join()
103
+ Returns ``True`` when the queue fully drained, ``False`` on deadline - undelivered
104
+ traces stay queued and keep sending in the background. The old implementation called
105
+ ``queue.join()``, which has no deadline at all: with the engine down, each queued item
106
+ burned the full retry backoff and a ``flush(timeout=3)`` measurably blocked for over
107
+ two minutes (deep-dive round 3, bug #5). The timeout is now a real wall-clock bound.
108
+ """
109
+ deadline = time.time() + timeout
110
+ with self._queue.all_tasks_done:
111
+ while self._queue.unfinished_tasks:
112
+ remaining = deadline - time.time()
113
+ if remaining <= 0:
114
+ logger.warning(
115
+ "agentx flush(%.1fs) timed out with %d trace(s) still undelivered - "
116
+ "they remain queued and keep sending in the background",
117
+ timeout,
118
+ self._queue.unfinished_tasks,
119
+ )
120
+ return False
121
+ self._queue.all_tasks_done.wait(remaining)
122
+ return True
123
+
124
+ def _record_drop(self, reason: str) -> None:
125
+ self._dropped += 1
126
+ if self._dropped == 1 or self._dropped % 50 == 0:
127
+ logger.warning(
128
+ "agentx dropped a trace (%s) - %d dropped total this process. "
129
+ "Tracing is fire-and-forget: traces that cannot be delivered are not persisted "
130
+ "locally. Use client.ping() at startup to fail fast on a bad endpoint.",
131
+ reason,
132
+ self._dropped,
133
+ )
134
+ else:
135
+ logger.debug("agentx dropped a trace (%s), %d total", reason, self._dropped)
98
136
 
99
137
  def send_trace_sync(self, payload: Dict[str, Any]) -> Optional[str]:
100
138
  """
@@ -372,7 +410,9 @@ class IngestClient:
372
410
  try:
373
411
  self._send(payload)
374
412
  except Exception as exc:
375
- logger.debug("agentx ingest send error: %s", exc)
413
+ # Anything _send didn't classify still cost us this payload - count it as a
414
+ # drop rather than whispering at DEBUG (bug #5's silent half).
415
+ self._record_drop(f"unexpected error: {exc}")
376
416
  finally:
377
417
  self._queue.task_done()
378
418
 
@@ -397,4 +437,4 @@ class IngestClient:
397
437
  return
398
438
 
399
439
  self._warn_delivery(str(last_exc))
400
- logger.debug("agentx ingest failed after retries: %s", last_exc)
440
+ self._record_drop(f"retries exhausted: {last_exc}")
agentx/tracing/tracer.py CHANGED
@@ -890,9 +890,11 @@ class Tracer:
890
890
  agent_id=agent_id,
891
891
  )
892
892
 
893
- def flush(self, timeout: float = 5.0) -> None:
894
- """Block until all queued traces have been delivered."""
895
- self._client.flush(timeout)
893
+ def flush(self, timeout: float = 5.0) -> bool:
894
+ """Block until all queued traces have been delivered, or until ``timeout`` seconds
895
+ elapse. Returns ``True`` when everything drained, ``False`` on deadline (a warning is
896
+ logged and undelivered traces keep sending in the background)."""
897
+ return self._client.flush(timeout)
896
898
 
897
899
  # ------------------------------------------------------------------
898
900
  # CI/CD evaluation
agentx/version.py CHANGED
@@ -1 +1 @@
1
- VERSION = "0.6.34"
1
+ VERSION = "0.6.36"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentx-python
3
- Version: 0.6.34
3
+ Version: 0.6.36
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
@@ -1,25 +1,25 @@
1
1
  agentx/__init__.py,sha256=4iMiGLNU4S-I-WyX0Z7l0BFUY_krVJ3seMQPqyO-ReA,602
2
- agentx/agentx.py,sha256=xNtG_g51_QKn1fRSAXYC18mYsxLwnvajoSSiwxYDtmI,7858
2
+ agentx/agentx.py,sha256=WuzVQtXSPqBhRwKxkUlQ8VG2cwvGpbMcti4n6aTNPwA,8816
3
3
  agentx/cli.py,sha256=ueKYPBhW8qd63nvQBQeZ4Mgz9NwEVaM0KH2WvLQ0bGs,6097
4
4
  agentx/exceptions.py,sha256=2tDLdZjriFBQbjgzyIcah12RazpbujhqZSRbSXgAml4,1377
5
- agentx/export.py,sha256=Pvz-0vXUHnKx7_KPrr5kzXMsMdTk1LMX-4CB3k0AgMI,4094
6
- agentx/feedback.py,sha256=ZNM89mfKtjkbDZefbGWuej-pqSwlnjAnIS6oQ-Tw09M,3472
7
- agentx/outcomes.py,sha256=wD03oNUuFv56_l1ebOSTbWJ6XqSU7_QXnWzMZ5G3RDU,3295
8
- agentx/projects.py,sha256=er9OdY-IISB5Xn16Auolmh2lnKDF6E4M4nPAvR7NZbM,2267
5
+ agentx/export.py,sha256=FNT00e-j9AQRkNDksUwgHJXgr8-XVPscEH4xUqJHmfM,4265
6
+ agentx/feedback.py,sha256=hMPU3ivUHvhOXOH7gvT4Jy1yztVmoIMOw1MbUNRs5Gk,3643
7
+ agentx/outcomes.py,sha256=yCA6PrO5BrGufEjkzYWTK5wusAOt6w0gSKkH-5x1Fs4,3462
8
+ agentx/projects.py,sha256=JN-nhesELPjEb1P0m9F4-Qw-0eGR68kCtBN5zNhArt0,2540
9
9
  agentx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- agentx/traces.py,sha256=9QfTgGlwHkqPMY7lkF-LVk1SqcTWFtsuliu2ChDi6cE,2120
10
+ agentx/traces.py,sha256=sz9gxutlDKNgf0fdCkyzKsGwK5eMAJuIEl3NIwGrsuo,2287
11
11
  agentx/util.py,sha256=lt2Kpg4Fj8whrSuAhLEahcPEIYRVpK9RCC5y9ShdTJs,703
12
- agentx/version.py,sha256=No3CH7gRh0Bs49mgkkl6NBX4xs2x7o3PFlV4yDIt7Ho,19
12
+ agentx/version.py,sha256=Vjh1dxR-kRQ3D_9qSk_EH1XFCc8RMkJ2gALg30ZZ0ns,19
13
13
  agentx/evaluations/__init__.py,sha256=Erv7RGFlRxGTG4rVb2uHhCqLLEX_iAWKqkZKzK6CumE,262
14
14
  agentx/evaluations/_term.py,sha256=WFpiNzdgDBeJJ-Gg-6X7TwwxllobuE4OqFUTuDQvS3Y,2529
15
- agentx/evaluations/client.py,sha256=GoYWV0v_4AJRwdwohyfGrICe2QNi6Q2WjS1sgaJQ16g,25837
16
- agentx/evaluations/datasets.py,sha256=cLJ0syj-e3uvCGJbBDQvYibNPVTIoh09_Z0XVqsYMgY,14007
17
- agentx/evaluations/evaluation_settings.py,sha256=SrCUZ9gObXLemJ8HLx9WHaUv18d9HANAEIDez_xHR8E,5273
18
- agentx/evaluations/models.py,sha256=FG0JqNDB_CzuPvXbD9Ky-tbjhrYGizY1xyoAEGzWja0,28675
15
+ agentx/evaluations/client.py,sha256=Vo3pvD37f6Z8VgUm3DR0FzSmnO3qYcokGHmiTc8RzwU,27273
16
+ agentx/evaluations/datasets.py,sha256=7OYj5pXpJenViAhPe7Sr_7Sf9KWd7b1OhV-dPL2tso8,14283
17
+ agentx/evaluations/evaluation_settings.py,sha256=geRydHqKpxwkxBVfMIOqkB6NlStyOZrYwZnMHluDwvA,6276
18
+ agentx/evaluations/models.py,sha256=iXM1XReT1PLxjVjgdfMuomFOSAwFhHAY4qBxNjiKw88,28732
19
19
  agentx/evaluations/prompts.py,sha256=8xyvMpAl3mD5xuSZozuaohdnCBWe5Q02RldwiPLUO20,3493
20
20
  agentx/evaluations/reporting.py,sha256=GtNnL-1eNEQrSqd0yrHi5mGqC9_u0kUAN8Mbjdhw6yY,6389
21
21
  agentx/evaluations/results.py,sha256=w7TRUik4eXIqrznXvlF_L9hnObnHvgdRKvihfN1XeAU,4692
22
- agentx/evaluations/runner.py,sha256=lVpdR4oIYQ7MCTT7Zn8Ono9Mrb7XCQZ5eMDEZr7njc8,26234
22
+ agentx/evaluations/runner.py,sha256=wXBUzYvdS_7dlcnIlJQXSWfjGdC0BfWiu4_Ki1rLYtg,26567
23
23
  agentx/evaluations/tool_schemas.py,sha256=ZyrnSOnx9xlSAj4swfDey9ZLS2ly-g21DByTwRAGwNA,2323
24
24
  agentx/evaluations/tracing.py,sha256=MSJD9bzfInMi7E70mc8mWUc0fx5DLkOgUzRru2mNQoc,1825
25
25
  agentx/evaluations/adapters/__init__.py,sha256=fK8Hx75usbiY03XUSmnLnSrwBXip2CQs24YZcdEgj6w,287
@@ -42,12 +42,13 @@ agentx/integrations/openai.py,sha256=1KLs-hJaeW2emHPwNkmC3zcGksL3zd87uWbcLGRW704
42
42
  agentx/integrations/openai_agents.py,sha256=tCa3kPxqux5LnNNDQ6zA05OybQrjkc_LeE08YuNPJEs,12742
43
43
  agentx/monitor/__init__.py,sha256=MLiASSvQnCy2jqVNRTgPWFhUSf8lGYRQeEgEVVhE5Tg,574
44
44
  agentx/monitor/agents.py,sha256=8Xk4jWmNTvdtjFixHiLmw4jj96uqIXWIT6KW8zXoWrc,1006
45
- agentx/monitor/client.py,sha256=phdBAzQSsw8TiaFMPQJ1jPdKGxZdywxZA7G4h7P7t3o,15535
45
+ agentx/monitor/client.py,sha256=BXaTDAsKJf_3uwrhZMKn4UqAz5Xr0qap-MH9a-CR7d8,16655
46
+ agentx/monitor/judge_scorers.py,sha256=40Xq60QASLW9z2QT2dSTmZhILGeBTfFGLrog5yeessM,14261
46
47
  agentx/monitor/models.py,sha256=yTC3WTdTziHMkdeAXlTro-4YiSbBUHMgf9kNZHGfPDA,8470
47
- agentx/monitor/online_evaluators.py,sha256=gfGMgJLIWjPUpYK068fxv2oZerHi3o0_4AOjxoVQhak,7176
48
+ agentx/monitor/online_evaluators.py,sha256=tTXqRgOb6ah39c5nWMEVXLxEUc8Tqu9E9SFdcHy0ExM,8418
48
49
  agentx/monitor/patterns.py,sha256=ruBqLoA2T2KQuqRbbn1kWkIEkiOal3SvyPl9opGyhzI,4185
49
50
  agentx/monitor/profile.py,sha256=uGk-4bXT2vmnEQiY5u6rWhAfzZchHBXL74VD3piVHvU,3102
50
- agentx/monitor/scorers.py,sha256=67QWvy6EDW17II9hgfZY47moISuAXt2-hTRHdbHDD2I,7169
51
+ agentx/monitor/scorers.py,sha256=huIZBszrfLKI8I7VV6NFpW6piRsj9HI03rXUwWs2tgs,7336
51
52
  agentx/monitor/sessions.py,sha256=gePcL284m3UITxQAtHxwrKFl6bRjmRib1QK6JbZeS9s,815
52
53
  agentx/monitor/signals.py,sha256=Ld11lW2lbg8NHBUHdgoY6iwCvJDK-eg4ge4qQ9y7nGY,1432
53
54
  agentx/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -56,11 +57,11 @@ agentx/resources/conversation.py,sha256=94pOdM6Pmj67RhikZVBNGkT3V-WKK_ZAD7s27p5-
56
57
  agentx/resources/workforce.py,sha256=lfGVkoV9lcOp-lZScjTvwRarJMkhzfMfLm4syJDujGc,4168
57
58
  agentx/tracing/__init__.py,sha256=l2mIRILE-wWrCD0fekgrIOQUEvtDObzdrkgpQUtXroU,408
58
59
  agentx/tracing/ci_types.py,sha256=zFVcZGvc1qbVDdOlEPQ1i5R6terqJHxCzFF4ZjouxxI,1423
59
- agentx/tracing/ingest_client.py,sha256=TuKQAVeKi35Nv8oMGIHxw-vR8Lg_4NtpzBQHouxljFk,15517
60
- agentx/tracing/tracer.py,sha256=zv5iNXyHXSZ8QW5yBe-Pk3kauLhaFAeQii3YDZchQwE,51366
61
- agentx_python-0.6.34.dist-info/licenses/LICENSE,sha256=gZVsM-nLsE8vlaY6NXXsVoo6IlCClxkToAWmhgT3y_s,10762
62
- agentx_python-0.6.34.dist-info/METADATA,sha256=P4C9ErZLr2n6Mblzw7j_DwT7Lwa861c_7SwYnreToAA,20133
63
- agentx_python-0.6.34.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
64
- agentx_python-0.6.34.dist-info/entry_points.txt,sha256=rQqF1JTY3T1yfviBU1r8PU-5mBdfOk6P4bZ1L4BskIM,172
65
- agentx_python-0.6.34.dist-info/top_level.txt,sha256=s-q-HB9Gb_QdrZNacSeQyF_c25gQooMy7DlxzgLOHPk,7
66
- agentx_python-0.6.34.dist-info/RECORD,,
60
+ agentx/tracing/ingest_client.py,sha256=5X6RyxtMq1XYgOzsMd3b2f2qpmYpsMnhKm7NB3oup00,17664
61
+ agentx/tracing/tracer.py,sha256=6qLTrV4Fz_ONitlBMKiayRbeoV1dpkTwskTDP5oVt4I,51567
62
+ agentx_python-0.6.36.dist-info/licenses/LICENSE,sha256=gZVsM-nLsE8vlaY6NXXsVoo6IlCClxkToAWmhgT3y_s,10762
63
+ agentx_python-0.6.36.dist-info/METADATA,sha256=lqDEHXO2-wFeK4cx6hGiU1hI-cE_4t92idDVwvza8q8,20133
64
+ agentx_python-0.6.36.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
65
+ agentx_python-0.6.36.dist-info/entry_points.txt,sha256=rQqF1JTY3T1yfviBU1r8PU-5mBdfOk6P4bZ1L4BskIM,172
66
+ agentx_python-0.6.36.dist-info/top_level.txt,sha256=s-q-HB9Gb_QdrZNacSeQyF_c25gQooMy7DlxzgLOHPk,7
67
+ agentx_python-0.6.36.dist-info/RECORD,,