agentx-python 0.8.24__py3-none-any.whl → 0.8.26__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 +16 -12
- agentx/integrations/__init__.py +1 -0
- agentx/integrations/crewai.py +2 -2
- agentx/integrations/nvidia_nim.py +70 -0
- agentx/integrations/openai.py +5 -1
- agentx/monitor/__init__.py +9 -0
- agentx/monitor/_transport.py +48 -0
- agentx/monitor/client.py +5 -3
- agentx/monitor/improvement_groups.py +16 -4
- agentx/monitor/judge_scorers.py +33 -6
- agentx/monitor/patterns.py +97 -5
- agentx/monitor/rules.py +15 -2
- agentx/monitor/scorer_groups.py +30 -6
- agentx/monitor/scorers.py +18 -6
- agentx/resources/agent.py +28 -9
- agentx/resources/conversation.py +31 -10
- agentx/resources/workforce.py +34 -9
- agentx/tracing/tracer.py +6 -4
- agentx/util.py +9 -1
- agentx/version.py +2 -2
- {agentx_python-0.8.24.dist-info → agentx_python-0.8.26.dist-info}/METADATA +7 -2
- {agentx_python-0.8.24.dist-info → agentx_python-0.8.26.dist-info}/RECORD +26 -24
- {agentx_python-0.8.24.dist-info → agentx_python-0.8.26.dist-info}/WHEEL +0 -0
- {agentx_python-0.8.24.dist-info → agentx_python-0.8.26.dist-info}/entry_points.txt +0 -0
- {agentx_python-0.8.24.dist-info → agentx_python-0.8.26.dist-info}/licenses/LICENSE +0 -0
- {agentx_python-0.8.24.dist-info → agentx_python-0.8.26.dist-info}/top_level.txt +0 -0
agentx/agentx.py
CHANGED
|
@@ -20,8 +20,8 @@ class AgentX:
|
|
|
20
20
|
# The api_key is NOT written back into os.environ (it used to be): every sub-client
|
|
21
21
|
# below receives it explicitly, and mutating process-global state from a constructor
|
|
22
22
|
# re-pointed unrelated code - the same leak the base_url write below had (deep-dive
|
|
23
|
-
# round 3, bug #1).
|
|
24
|
-
#
|
|
23
|
+
# round 3, bug #1). Flows that still read the env (bare get_headers()) now require
|
|
24
|
+
# the caller to set AGENTX_API_KEY themselves.
|
|
25
25
|
self.api_key = api_key or os.getenv("AGENTX_API_KEY")
|
|
26
26
|
|
|
27
27
|
# base_url overrides AGENTX_API_BASE_URL env var (and the SDK default). It is
|
|
@@ -147,7 +147,7 @@ class AgentX:
|
|
|
147
147
|
response = requests.get(url, headers=get_headers(self.api_key))
|
|
148
148
|
# Check if response was successful
|
|
149
149
|
if response.status_code == 200:
|
|
150
|
-
return Agent(**response.json())
|
|
150
|
+
return Agent(**response.json())._bind(self.api_key, self.base_url)
|
|
151
151
|
else:
|
|
152
152
|
raise AgentXError(
|
|
153
153
|
f"Failed to retrieve agent: {response.reason}. This endpoint is "
|
|
@@ -165,22 +165,26 @@ class AgentX:
|
|
|
165
165
|
response = requests.get(url, headers=get_headers(self.api_key))
|
|
166
166
|
# Check if response was successful
|
|
167
167
|
if response.status_code == 200:
|
|
168
|
-
return [Agent(**agent) for agent in response.json()]
|
|
168
|
+
return [Agent(**agent)._bind(self.api_key, self.base_url) for agent in response.json()]
|
|
169
169
|
else:
|
|
170
170
|
raise AgentXError(
|
|
171
171
|
f"Failed to list agents: {response.reason}. This endpoint is "
|
|
172
172
|
"hosted-platform only - on self-host use client.monitor.agents.list()."
|
|
173
173
|
)
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
response
|
|
175
|
+
def list_workforces(self) -> List["Workforce"]:
|
|
176
|
+
"""List all workforces/teams, each bound to this client's credentials - including each
|
|
177
|
+
workforce's ``manager`` and ``agents``, so their calls authenticate the same way.
|
|
178
|
+
|
|
179
|
+
This used to be documented as a static call (``AgentX.list_workforces()``); that form
|
|
180
|
+
was broken (the old staticmethod body referenced ``self`` and raised NameError on any
|
|
181
|
+
non-empty response). Construct a client instead - ``AgentX().list_workforces()`` picks
|
|
182
|
+
up AGENTX_API_KEY / AGENTX_API_BASE_URL from the environment, which is what the static
|
|
183
|
+
form effectively did."""
|
|
184
|
+
url = f"{self.base_url or api_base()}/access/teams"
|
|
185
|
+
response = requests.get(url, headers=get_headers(self.api_key))
|
|
182
186
|
if response.status_code == 200:
|
|
183
|
-
return [Workforce(**workforce) for workforce in response.json()]
|
|
187
|
+
return [Workforce(**workforce)._bind(self.api_key, self.base_url) for workforce in response.json()]
|
|
184
188
|
else:
|
|
185
189
|
raise Exception(
|
|
186
190
|
f"Failed to list workforces: {response.status_code} - {response.reason}"
|
agentx/integrations/__init__.py
CHANGED
|
@@ -7,4 +7,5 @@
|
|
|
7
7
|
# from agentx.integrations.anthropic import patch_anthropic_client
|
|
8
8
|
# from agentx.integrations.google_adk import AgentXADKPlugin
|
|
9
9
|
# from agentx.integrations.google_genai import patch_genai_client
|
|
10
|
+
# from agentx.integrations.nvidia_nim import patch_nim_client
|
|
10
11
|
# from agentx.integrations.moveworks import MoveworksImporter # Data API pull sync, not in-process
|
agentx/integrations/crewai.py
CHANGED
|
@@ -144,14 +144,14 @@ class AgentXCrewObserver:
|
|
|
144
144
|
TaskFailedEvent,
|
|
145
145
|
TaskStartedEvent,
|
|
146
146
|
)
|
|
147
|
-
except ImportError
|
|
147
|
+
except Exception: # noqa: BLE001 - crewai import raises TypeError (PEP 604) on py3.9, not just ImportError
|
|
148
148
|
from crewai.utilities.events import crewai_event_bus
|
|
149
149
|
from crewai.utilities.events.task_events import (
|
|
150
150
|
TaskCompletedEvent,
|
|
151
151
|
TaskFailedEvent,
|
|
152
152
|
TaskStartedEvent,
|
|
153
153
|
)
|
|
154
|
-
except ImportError
|
|
154
|
+
except Exception: # noqa: BLE001 - crewai import raises TypeError (PEP 604) on py3.9, not just ImportError
|
|
155
155
|
if not _warned_no_event_bus:
|
|
156
156
|
_warned_no_event_bus = True
|
|
157
157
|
logger.warning(
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NVIDIA NIM integration for AgentX production tracing.
|
|
3
|
+
|
|
4
|
+
NIM (NVIDIA Inference Microservices) serves models behind an OpenAI-compatible
|
|
5
|
+
``/v1/chat/completions`` API, so the client you patch is the ordinary ``openai``
|
|
6
|
+
Python client pointed at a NIM endpoint - a local NIM container
|
|
7
|
+
(``http://localhost:8000/v1``) or NVIDIA's hosted API
|
|
8
|
+
(``https://integrate.api.nvidia.com/v1``). This module reuses the OpenAI patch
|
|
9
|
+
machinery verbatim and differs in exactly one way: traces are stamped
|
|
10
|
+
``framework="nvidia-nim"``, so NIM traffic gets its own row in Monitor's
|
|
11
|
+
Platforms chart and the framework filters instead of blending into "openai".
|
|
12
|
+
|
|
13
|
+
Usage::
|
|
14
|
+
|
|
15
|
+
from agentx.integrations.nvidia_nim import patch_nim_client
|
|
16
|
+
import openai
|
|
17
|
+
|
|
18
|
+
nim = openai.OpenAI(
|
|
19
|
+
base_url="http://localhost:8000/v1", # or https://integrate.api.nvidia.com/v1
|
|
20
|
+
api_key=os.environ.get("NVIDIA_API_KEY", "not-needed-for-local-nim"),
|
|
21
|
+
)
|
|
22
|
+
patch_nim_client(nim, agentx.tracer, name="nim-agent")
|
|
23
|
+
|
|
24
|
+
# All subsequent nim.chat.completions.create() calls are now traced.
|
|
25
|
+
|
|
26
|
+
Works with both ``openai.OpenAI`` and ``openai.AsyncOpenAI`` clients. Token
|
|
27
|
+
usage comes straight off the response's OpenAI-shaped ``usage`` block; NIM
|
|
28
|
+
reports no prompt-cache fields, so cache token counts stay unset.
|
|
29
|
+
|
|
30
|
+
Streaming calls (``stream=True``) are passed through untouched and are not
|
|
31
|
+
currently traced - same posture as ``patch_openai_client``, see its docstring.
|
|
32
|
+
|
|
33
|
+
Requires: ``pip install "agentx-python[nvidia-nim]"`` (installs the ``openai``
|
|
34
|
+
client package; there is no separate NIM SDK dependency).
|
|
35
|
+
"""
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from typing import Any, Dict, Optional
|
|
39
|
+
|
|
40
|
+
from agentx.tracing.tracer import Tracer
|
|
41
|
+
from agentx.integrations.openai import _patch_chat_completions_create
|
|
42
|
+
|
|
43
|
+
NIM_FRAMEWORK = "nvidia-nim"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def patch_nim_client(
|
|
47
|
+
client: Any,
|
|
48
|
+
tracer: Tracer,
|
|
49
|
+
name: str = "nim-agent",
|
|
50
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
51
|
+
session_id: Optional[str] = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Monkey-patch ``client.chat.completions.create`` on an OpenAI-compatible
|
|
55
|
+
client pointed at a NIM endpoint, sending a trace for every non-streaming
|
|
56
|
+
call with ``framework="nvidia-nim"``.
|
|
57
|
+
|
|
58
|
+
The original method is still called and its return value passed through
|
|
59
|
+
unchanged. Sync and async clients both work; ``stream=True`` calls pass
|
|
60
|
+
through untraced. Patching is idempotent - and because it shares the guard
|
|
61
|
+
with ``patch_openai_client``, whichever of the two patched a given client
|
|
62
|
+
first wins (patch each client with the integration that matches where its
|
|
63
|
+
``base_url`` actually points).
|
|
64
|
+
"""
|
|
65
|
+
chat = getattr(client, "chat", None)
|
|
66
|
+
completions = getattr(chat, "completions", None) if chat is not None else None
|
|
67
|
+
if completions is None:
|
|
68
|
+
raise ValueError("Provided client does not have a .chat.completions attribute")
|
|
69
|
+
|
|
70
|
+
_patch_chat_completions_create(completions, tracer, name, metadata, session_id, framework=NIM_FRAMEWORK)
|
agentx/integrations/openai.py
CHANGED
|
@@ -113,7 +113,11 @@ def _patch_chat_completions_create(
|
|
|
113
113
|
name: str,
|
|
114
114
|
metadata: Optional[Dict[str, Any]],
|
|
115
115
|
session_id: Optional[str],
|
|
116
|
+
framework: str = "openai",
|
|
116
117
|
) -> None:
|
|
118
|
+
# `framework` exists for OpenAI-compatible endpoints served by other vendors
|
|
119
|
+
# (agentx.integrations.nvidia_nim stamps "nvidia-nim" through here) - the request/response
|
|
120
|
+
# shapes are identical, so they share this machinery instead of duplicating it.
|
|
117
121
|
original = completions_resource.create
|
|
118
122
|
if getattr(original, "_agentx_patched", False):
|
|
119
123
|
return # already patched
|
|
@@ -148,7 +152,7 @@ def _patch_chat_completions_create(
|
|
|
148
152
|
finish_llm_call(
|
|
149
153
|
tracer,
|
|
150
154
|
name=name,
|
|
151
|
-
framework=
|
|
155
|
+
framework=framework,
|
|
152
156
|
metadata=metadata,
|
|
153
157
|
session_id=session_id,
|
|
154
158
|
start_t=start_t,
|
agentx/monitor/__init__.py
CHANGED
|
@@ -10,6 +10,9 @@ from agentx.monitor.judge_scorers import (
|
|
|
10
10
|
from agentx.monitor.models import MonitorPattern, MonitorProfile, MonitorSignal, SignalOccurrence
|
|
11
11
|
from agentx.monitor.patterns import MonitorPatternBuilder, MonitorPatternClient
|
|
12
12
|
from agentx.monitor.profile import MonitorProfileClient
|
|
13
|
+
from agentx.monitor.review_queue import ReviewQueueClient, ReviewQueueItem
|
|
14
|
+
from agentx.monitor.rules import MonitorRule, MonitorRulesClient
|
|
15
|
+
from agentx.monitor.scorers import AgentXScorersError, ScorersClient
|
|
13
16
|
from agentx.monitor.scorer_groups import AgentXScorerGroupsError, ScorerGroup, ScorerGroupsClient
|
|
14
17
|
from agentx.monitor.sessions import MonitorSessionClient
|
|
15
18
|
from agentx.monitor.signals import MonitorSignalClient
|
|
@@ -19,6 +22,7 @@ __all__ = [
|
|
|
19
22
|
"AgentXJudgeScorersError",
|
|
20
23
|
"AgentXMonitorError",
|
|
21
24
|
"AgentXScorerGroupsError",
|
|
25
|
+
"AgentXScorersError",
|
|
22
26
|
"ImprovementGroupsClient",
|
|
23
27
|
"JudgeScorer",
|
|
24
28
|
"JudgeScorerBuilder",
|
|
@@ -30,10 +34,15 @@ __all__ = [
|
|
|
30
34
|
"MonitorPatternClient",
|
|
31
35
|
"MonitorProfile",
|
|
32
36
|
"MonitorProfileClient",
|
|
37
|
+
"MonitorRule",
|
|
38
|
+
"MonitorRulesClient",
|
|
33
39
|
"MonitorSessionClient",
|
|
34
40
|
"MonitorSignal",
|
|
35
41
|
"MonitorSignalClient",
|
|
42
|
+
"ReviewQueueClient",
|
|
43
|
+
"ReviewQueueItem",
|
|
36
44
|
"ScorerGroup",
|
|
45
|
+
"ScorersClient",
|
|
37
46
|
"ScorerGroupsClient",
|
|
38
47
|
"SignalOccurrence",
|
|
39
48
|
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Shared HTTP transport for the monitor sub-clients that own their ``_request`` (scorers,
|
|
2
|
+
judge_scorers, scorer_groups, improvement_groups): one retry schedule mirroring
|
|
3
|
+
``MonitorClient._request``, so ``retry=False`` means the same thing everywhere the client.py
|
|
4
|
+
comment promises it ("retry=False for ANY non-idempotent write")."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import time
|
|
10
|
+
from typing import Any, Optional
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
# Same schedule as MonitorClient._request (agentx/monitor/client.py).
|
|
17
|
+
_RETRYABLE_STATUS = {429, 500, 502, 503, 504}
|
|
18
|
+
_RETRY_BACKOFF = [1.0, 2.0, 4.0]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def request_with_retries(
|
|
22
|
+
method: str, url: str, *, retry: bool = True, **kwargs: Any
|
|
23
|
+
) -> requests.Response:
|
|
24
|
+
"""``requests.request`` with MonitorClient's transport posture: when ``retry`` is true,
|
|
25
|
+
connection errors and retryable statuses (429/5xx) walk the backoff schedule; the last
|
|
26
|
+
response (whatever its status) is returned for the caller's own error taxonomy.
|
|
27
|
+
|
|
28
|
+
``retry=False`` is single-shot - for non-idempotent writes (creates, deletes) and
|
|
29
|
+
judge-billing POSTs, where a client-side timeout must not fire the same work twice.
|
|
30
|
+
Transport errors keep their ``requests`` exception type (callers guard on
|
|
31
|
+
``requests.Timeout`` for judge-billing endpoints)."""
|
|
32
|
+
schedule = [0.0] + _RETRY_BACKOFF if retry else [0.0]
|
|
33
|
+
last_exc: Optional[Exception] = None
|
|
34
|
+
for attempt, wait in enumerate(schedule):
|
|
35
|
+
if wait:
|
|
36
|
+
time.sleep(wait)
|
|
37
|
+
try:
|
|
38
|
+
resp = requests.request(method, url, **kwargs)
|
|
39
|
+
except requests.RequestException as e:
|
|
40
|
+
last_exc = e
|
|
41
|
+
logger.debug("Request error (attempt %d): %s", attempt + 1, e)
|
|
42
|
+
continue
|
|
43
|
+
if retry and resp.status_code in _RETRYABLE_STATUS and attempt < len(schedule) - 1:
|
|
44
|
+
logger.debug("Retryable status %d (attempt %d)", resp.status_code, attempt + 1)
|
|
45
|
+
continue
|
|
46
|
+
return resp
|
|
47
|
+
assert last_exc is not None # every non-raising path returned above
|
|
48
|
+
raise last_exc
|
agentx/monitor/client.py
CHANGED
|
@@ -205,9 +205,11 @@ class MonitorClient:
|
|
|
205
205
|
def _request(
|
|
206
206
|
self, method: str, path: str, timeout: int = 30, base: Optional[str] = None, retry: bool = True, **kwargs
|
|
207
207
|
) -> Any:
|
|
208
|
-
# retry=False for non-idempotent
|
|
209
|
-
#
|
|
210
|
-
#
|
|
208
|
+
# retry=False for ANY non-idempotent write - duplicating creates, deletes (a lost
|
|
209
|
+
# response + retry turns success into a spurious 404), and judge-spending POSTs
|
|
210
|
+
# (sweep, coherence, portability, tuning: a client-side timeout must not fire the
|
|
211
|
+
# same LLM-billing work twice while the first invocation still runs server-side).
|
|
212
|
+
# The judge list is the example set, not the rule. Same precedent as
|
|
211
213
|
# EvaluationsClient._request / analyze_run.
|
|
212
214
|
url = f"{base or self._base_url}{path}"
|
|
213
215
|
last_exc: Optional[Exception] = None
|
|
@@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional
|
|
|
4
4
|
|
|
5
5
|
import requests
|
|
6
6
|
|
|
7
|
+
from agentx.monitor._transport import request_with_retries
|
|
7
8
|
from agentx.util import api_base, get_headers
|
|
8
9
|
from agentx.exceptions import AgentXError, AgentXAuthError, AgentXValidationError
|
|
9
10
|
|
|
@@ -45,7 +46,9 @@ class ImprovementGroupsClient:
|
|
|
45
46
|
self._workspace_id = workspace_id
|
|
46
47
|
self._base_url = (base_url or api_base()).rstrip("/")
|
|
47
48
|
|
|
48
|
-
def _request(
|
|
49
|
+
def _request(
|
|
50
|
+
self, method: str, path: str, json: Any = None, timeout: int = 120, retry: bool = True
|
|
51
|
+
) -> Any:
|
|
49
52
|
params = None
|
|
50
53
|
if self._workspace_id:
|
|
51
54
|
# Mirrors MonitorClient._workspace_params/_with_workspace: GETs (and DELETEs)
|
|
@@ -57,9 +60,12 @@ class ImprovementGroupsClient:
|
|
|
57
60
|
json = {**json, "workspaceId": self._workspace_id}
|
|
58
61
|
else:
|
|
59
62
|
params = {"workspaceId": self._workspace_id}
|
|
60
|
-
|
|
63
|
+
# retry=False for ANY non-idempotent write (member deletes, the report-generating
|
|
64
|
+
# POST) - MonitorClient._request's posture, via the shared monitor transport.
|
|
65
|
+
resp = request_with_retries(
|
|
61
66
|
method,
|
|
62
67
|
f"{self._base_url}/agent-monitoring{path}",
|
|
68
|
+
retry=retry,
|
|
63
69
|
headers={**get_headers(self._api_key), "Content-Type": "application/json"},
|
|
64
70
|
json=json,
|
|
65
71
|
params=params,
|
|
@@ -92,7 +98,9 @@ class ImprovementGroupsClient:
|
|
|
92
98
|
|
|
93
99
|
def remove_member(self, group_id: str, member_id: str) -> None:
|
|
94
100
|
"""Prune a member before spending the group (a confirm that turned out uninteresting)."""
|
|
95
|
-
|
|
101
|
+
# retry=False: a lost response + transport retry would turn a successful delete
|
|
102
|
+
# into a spurious 404.
|
|
103
|
+
self._request("DELETE", f"/improvement-groups/{group_id}/members/{member_id}", retry=False)
|
|
96
104
|
|
|
97
105
|
def generate_report(self, group_id: str, model: Optional[str] = None) -> Dict[str, Any]:
|
|
98
106
|
"""Spend the group: one real LLM call clustering the confirmed failures into issues
|
|
@@ -101,7 +109,11 @@ class ImprovementGroupsClient:
|
|
|
101
109
|
payload: Dict[str, Any] = {}
|
|
102
110
|
if model is not None:
|
|
103
111
|
payload["model"] = model
|
|
104
|
-
|
|
112
|
+
# retry=False: spends the group (real LLM billing) - a client-side timeout must not
|
|
113
|
+
# fire the same generation twice while the first still runs server-side.
|
|
114
|
+
return self._request(
|
|
115
|
+
"POST", f"/improvement-groups/{group_id}/report", json=payload, timeout=300, retry=False
|
|
116
|
+
)["report"]
|
|
105
117
|
|
|
106
118
|
def list_reports(self) -> List[Dict[str, Any]]:
|
|
107
119
|
return self._request("GET", "/improvement-reports").get("improvementReports", [])
|
agentx/monitor/judge_scorers.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Optional
|
|
|
5
5
|
|
|
6
6
|
import requests
|
|
7
7
|
|
|
8
|
+
from agentx.monitor._transport import request_with_retries
|
|
8
9
|
from agentx.util import api_base, get_headers
|
|
9
10
|
from agentx.exceptions import AgentXError, AgentXAuthError, AgentXValidationError
|
|
10
11
|
|
|
@@ -97,7 +98,9 @@ class JudgeScorersClient:
|
|
|
97
98
|
# Captured once at construction so two clients with different bases can coexist.
|
|
98
99
|
self._base_url = (base_url or api_base()).rstrip("/")
|
|
99
100
|
|
|
100
|
-
def _request(
|
|
101
|
+
def _request(
|
|
102
|
+
self, method: str, path: str, json: Any = None, timeout: int = 60, retry: bool = True
|
|
103
|
+
) -> Any:
|
|
101
104
|
params = None
|
|
102
105
|
if self._workspace_id:
|
|
103
106
|
# Mirrors MonitorClient._workspace_params/_with_workspace: GETs (and DELETEs)
|
|
@@ -109,9 +112,13 @@ class JudgeScorersClient:
|
|
|
109
112
|
json = {**json, "workspaceId": self._workspace_id}
|
|
110
113
|
else:
|
|
111
114
|
params = {"workspaceId": self._workspace_id}
|
|
112
|
-
|
|
115
|
+
# retry=False for ANY non-idempotent write (creates, deletes) and judge-spending POST
|
|
116
|
+
# (tune/validate/publish) - MonitorClient._request's posture, via the shared monitor
|
|
117
|
+
# transport.
|
|
118
|
+
resp = request_with_retries(
|
|
113
119
|
method,
|
|
114
120
|
f"{self._base_url}/agent-monitoring{path}",
|
|
121
|
+
retry=retry,
|
|
115
122
|
headers={**get_headers(self._api_key), "Content-Type": "application/json"},
|
|
116
123
|
json=json,
|
|
117
124
|
params=params,
|
|
@@ -263,7 +270,10 @@ class JudgeScorersClient:
|
|
|
263
270
|
payload["offline"] = offline
|
|
264
271
|
if online is not None:
|
|
265
272
|
payload["online"] = online
|
|
266
|
-
|
|
273
|
+
# Server-side create: a timeout + transport retry would create the scorer twice.
|
|
274
|
+
return JudgeScorer(
|
|
275
|
+
self._request("POST", "/judge-scorers", json=payload, retry=False)["judgeScorer"]
|
|
276
|
+
)
|
|
267
277
|
|
|
268
278
|
def get(self, scorer_id: str) -> JudgeScorer:
|
|
269
279
|
return JudgeScorer(self._request("GET", f"/judge-scorers/{scorer_id}")["judgeScorer"])
|
|
@@ -300,7 +310,9 @@ class JudgeScorersClient:
|
|
|
300
310
|
def delete(self, scorer_id: str) -> None:
|
|
301
311
|
"""Delete the scorer: rubric, version history, and online profile together.
|
|
302
312
|
Irreversible; refused for the built-in Session Baseline Judge."""
|
|
303
|
-
|
|
313
|
+
# retry=False: a lost response + transport retry would turn a successful delete
|
|
314
|
+
# into a spurious 404.
|
|
315
|
+
self._request("DELETE", f"/judge-scorers/{scorer_id}", retry=False)
|
|
304
316
|
|
|
305
317
|
# ------------------------------------------------------------------
|
|
306
318
|
# Online-profile pass-throughs (calibration / tuning / ratings / events)
|
|
@@ -334,8 +346,14 @@ class JudgeScorersClient:
|
|
|
334
346
|
def tune(self, scorer_id: str, window: str = "7d") -> dict:
|
|
335
347
|
"""Propose a rewrite of the rubric from calibration disagreements (LLM call, slow).
|
|
336
348
|
``window`` accepts the same values as :meth:`calibration`, including "rubric"."""
|
|
349
|
+
# retry=False (judge-spending POST, MonitorClient.propose_online_evaluator_tuning's
|
|
350
|
+
# posture): a client-side timeout must not fire the same LLM-billing work twice.
|
|
337
351
|
data = self._request(
|
|
338
|
-
"POST",
|
|
352
|
+
"POST",
|
|
353
|
+
f"/online-evaluators/{self._profile_id(scorer_id)}/tune",
|
|
354
|
+
json={"window": window},
|
|
355
|
+
timeout=300,
|
|
356
|
+
retry=False,
|
|
339
357
|
)
|
|
340
358
|
# The wire wraps the proposal ({"proposal": {...}}); unwrap like the legacy client so
|
|
341
359
|
# proposal["reasoning"] / the criteria fields are directly addressable.
|
|
@@ -344,11 +362,13 @@ class JudgeScorersClient:
|
|
|
344
362
|
def validate_tuning(self, scorer_id: str, criteria: Dict[str, Any], window: str = "7d") -> dict:
|
|
345
363
|
"""Re-judge the disagreement + control cases with candidate criteria (LLM calls, slow)."""
|
|
346
364
|
# The wire takes the criteria fields at the TOP level of the body, not nested.
|
|
365
|
+
# retry=False (judge-spending POST) - same posture as tune() above.
|
|
347
366
|
return self._request(
|
|
348
367
|
"POST",
|
|
349
368
|
f"/online-evaluators/{self._profile_id(scorer_id)}/tune/validate",
|
|
350
369
|
json={**criteria, "window": window},
|
|
351
370
|
timeout=600,
|
|
371
|
+
retry=False,
|
|
352
372
|
)
|
|
353
373
|
|
|
354
374
|
def publish_tuning(
|
|
@@ -382,7 +402,14 @@ class JudgeScorersClient:
|
|
|
382
402
|
payload["validation"] = validation_payload
|
|
383
403
|
if force:
|
|
384
404
|
payload["force"] = True
|
|
385
|
-
|
|
405
|
+
# retry=False: a non-idempotent write (each publish appends a rubric version) -
|
|
406
|
+
# MonitorClient.publish_online_evaluator_tuning's posture.
|
|
407
|
+
return self._request(
|
|
408
|
+
"POST",
|
|
409
|
+
f"/online-evaluators/{self._profile_id(scorer_id)}/tune/publish",
|
|
410
|
+
json=payload,
|
|
411
|
+
retry=False,
|
|
412
|
+
)
|
|
386
413
|
|
|
387
414
|
def ratings(self, scorer_id: str, window: str = "7d") -> "List[OnlineEvaluatorRatingPoint]":
|
|
388
415
|
"""Bucketed average-rating-over-time for this scorer's live checks - same typed points
|
agentx/monitor/patterns.py
CHANGED
|
@@ -137,16 +137,108 @@ class MonitorPatternClient:
|
|
|
137
137
|
retry=False,
|
|
138
138
|
)
|
|
139
139
|
|
|
140
|
+
# snake_case -> wire camelCase, same posture as rules.update: the engine reads only the
|
|
141
|
+
# camelCase key and silently keeps the stored value for anything it does not recognize -
|
|
142
|
+
# update(sample_rate=0.05) used to 200 with the rate unchanged.
|
|
143
|
+
_UPDATE_ALIASES = {
|
|
144
|
+
"sample_rate": "sampleRate",
|
|
145
|
+
"scope_mode": "scopeMode",
|
|
146
|
+
"agent_ids": "agentIds",
|
|
147
|
+
"detector_kind": "detectorKind",
|
|
148
|
+
"include_terms": "includeTerms",
|
|
149
|
+
"exclude_terms": "excludeTerms",
|
|
150
|
+
"regex": "regex",
|
|
151
|
+
"semantic_prompt": "semanticPrompt",
|
|
152
|
+
"match_mode": "matchMode",
|
|
153
|
+
"match_target": "matchTarget",
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# The engine's PUT rebuilds the pattern's WHOLE conditions array (legacyPayloadToConditions:
|
|
157
|
+
# "a full replace, not a sparse patch") exactly when the body carries one of these - its own
|
|
158
|
+
# sentConditionFields set, minus "conditions". Bodies without any of them keep the stored
|
|
159
|
+
# conditions untouched.
|
|
160
|
+
_TRIGGER_FIELDS = ("includeTerms", "regex", "semanticPrompt")
|
|
161
|
+
|
|
162
|
+
# Sent alone, these look sparse but cannot land: without a trigger field the engine never
|
|
163
|
+
# rebuilds conditions (the values are silently ignored). For excludeTerms/matchMode there is
|
|
164
|
+
# also nothing to merge them over client-side - the wire GET reports display-only
|
|
165
|
+
# placeholders (includeTerms/excludeTerms always [], matchMode always "any"; conditions is
|
|
166
|
+
# the only truth). matchTarget IS reported faithfully, but the engine only reads it during a
|
|
167
|
+
# rebuild, so alone it is the same silent no-op.
|
|
168
|
+
_UNMERGEABLE_ALONE = ("excludeTerms", "matchMode", "matchTarget")
|
|
169
|
+
|
|
140
170
|
def update(self, pattern_id: str, **fields: Any) -> MonitorPattern:
|
|
141
|
-
"""Update a pattern
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
171
|
+
"""Update a pattern and return the updated :class:`MonitorPattern`. Accepts snake_case
|
|
172
|
+
kwargs (``sample_rate=0.05``) or the wire's camelCase; an unrecognized snake_case key
|
|
173
|
+
raises instead of silently changing nothing.
|
|
174
|
+
|
|
175
|
+
The real contract on self-host: the stored truth is the pattern's ``conditions`` array,
|
|
176
|
+
and the flat fields the wire GET returns are display-only placeholders (``includeTerms``/
|
|
177
|
+
``excludeTerms`` always ``[]``, ``matchMode`` always ``"any"``, ``regex``/
|
|
178
|
+
``semanticPrompt`` omitted). The engine's PUT rebuilds the WHOLE conditions array
|
|
179
|
+
whenever the body carries ``include_terms``, ``regex``, or ``semantic_prompt`` (or an
|
|
180
|
+
explicit ``conditions`` list, which wins outright). Consequences:
|
|
181
|
+
|
|
182
|
+
- ``update(pid, regex=...)`` (or include_terms/semantic_prompt) is a full detector
|
|
183
|
+
rewrite; the detector kind follows the trigger field actually sent (``regex`` ->
|
|
184
|
+
``"regex"``, ``semantic_prompt`` -> ``"semantic"``, ``include_terms`` ->
|
|
185
|
+
``"contains"``), so cross-kind updates work, and this client back-fills only
|
|
186
|
+
``matchTarget`` from the stored pattern so the rebuild keeps its target. It never
|
|
187
|
+
back-fills ``includeTerms``/``excludeTerms``/``matchMode`` - the GET values are
|
|
188
|
+
placeholders, and copying them in would destroy real conditions.
|
|
189
|
+
- ``exclude_terms=``, ``match_mode=``, or ``match_target=`` alone raises ValueError:
|
|
190
|
+
the engine silently ignores them without a rebuild. Pass ``conditions=[...]`` (built
|
|
191
|
+
from ``get(pattern_id).conditions``) instead, or pass them alongside the
|
|
192
|
+
include_terms/regex/semantic_prompt they should be rebuilt with.
|
|
193
|
+
- Everything else stays a sparse metadata edit that leaves the stored conditions
|
|
194
|
+
untouched."""
|
|
195
|
+
payload: Dict[str, Any] = {}
|
|
196
|
+
for key, value in fields.items():
|
|
197
|
+
wire_key = self._UPDATE_ALIASES.get(key, key)
|
|
198
|
+
if "_" in wire_key:
|
|
199
|
+
raise ValueError(
|
|
200
|
+
f"Unknown pattern field {key!r} - the engine reads camelCase keys and would "
|
|
201
|
+
"silently ignore this (see MonitorPattern for the field names)."
|
|
202
|
+
)
|
|
203
|
+
payload[wire_key] = value
|
|
204
|
+
sends_conditions = "conditions" in payload
|
|
205
|
+
triggered = any(k in payload for k in self._TRIGGER_FIELDS)
|
|
206
|
+
if not sends_conditions and not triggered:
|
|
207
|
+
offending = [k for k in self._UNMERGEABLE_ALONE if k in payload]
|
|
208
|
+
if offending:
|
|
209
|
+
raise ValueError(
|
|
210
|
+
f"{' and '.join(offending)} cannot be updated on their own: the engine only "
|
|
211
|
+
"rebuilds a pattern's conditions when includeTerms/regex/semanticPrompt is "
|
|
212
|
+
"sent (alone they are silently ignored), and the wire GET returns display-"
|
|
213
|
+
"only placeholders (includeTerms/excludeTerms always [], matchMode always "
|
|
214
|
+
"'any'), so there is no stored value to merge them over. Pass "
|
|
215
|
+
"conditions=[...] built from get(pattern_id).conditions instead, or send "
|
|
216
|
+
"them alongside the trigger field they should be rebuilt with."
|
|
217
|
+
)
|
|
218
|
+
if triggered and not sends_conditions:
|
|
219
|
+
stored = self.get(pattern_id)
|
|
220
|
+
# The rebuild's detector kind follows the trigger field actually sent - back-filling
|
|
221
|
+
# the STORED kind 400s a kind change (regex= on a "contains" pattern) and silently
|
|
222
|
+
# corrupts the inverse (include_terms= on a regex pattern would write phrase
|
|
223
|
+
# conditions while keeping detectorKind "regex").
|
|
224
|
+
if "regex" in payload:
|
|
225
|
+
inferred_kind = "regex"
|
|
226
|
+
elif "semanticPrompt" in payload:
|
|
227
|
+
inferred_kind = "semantic"
|
|
228
|
+
else:
|
|
229
|
+
inferred_kind = "contains"
|
|
230
|
+
payload.setdefault("detectorKind", inferred_kind)
|
|
231
|
+
# matchTarget is the one field the wire reports faithfully - it rides along so the
|
|
232
|
+
# server-side full-replace rebuild keeps the stored target. NEVER includeTerms/
|
|
233
|
+
# excludeTerms/matchMode - see _UNMERGEABLE_ALONE's comment.
|
|
234
|
+
payload.setdefault("matchTarget", stored.match_target)
|
|
145
235
|
data = self._client._request(
|
|
146
236
|
"PUT",
|
|
147
237
|
f"/agent-monitoring/patterns/{pattern_id}",
|
|
148
238
|
base=self._client._api_root(),
|
|
149
|
-
json=
|
|
239
|
+
json=payload,
|
|
240
|
+
# An idempotent merge server-side (same payload, same result) - but the read-
|
|
241
|
+
# merge-write above is not atomic, so keep the single-shot posture.
|
|
150
242
|
retry=False,
|
|
151
243
|
)
|
|
152
244
|
return MonitorPattern(**data["pattern"])
|
agentx/monitor/rules.py
CHANGED
|
@@ -70,9 +70,22 @@ class MonitorRulesClient:
|
|
|
70
70
|
|
|
71
71
|
def update(self, rule_id: str, **fields: Any) -> MonitorRule:
|
|
72
72
|
"""Sparse update. snake_case keys are mapped to the wire (``sample_rate`` ->
|
|
73
|
-
``sampleRate``, ``action_config`` -> ``actionConfig``)
|
|
73
|
+
``sampleRate``, ``action_config`` -> ``actionConfig``); an unrecognized snake_case
|
|
74
|
+
key raises instead of 200ing with the rule unchanged (the engine reads only camelCase
|
|
75
|
+
and silently keeps the stored value for keys it does not know)."""
|
|
74
76
|
aliases = {"sample_rate": "sampleRate", "action_config": "actionConfig"}
|
|
75
|
-
payload
|
|
77
|
+
payload: Dict[str, Any] = {}
|
|
78
|
+
for key, value in fields.items():
|
|
79
|
+
wire_key = aliases.get(key, key)
|
|
80
|
+
if "_" in wire_key:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
f"Unknown rule field {key!r} - the engine reads camelCase keys and would "
|
|
83
|
+
"silently ignore this (see MonitorRule for the field names)."
|
|
84
|
+
)
|
|
85
|
+
payload[wire_key] = value
|
|
86
|
+
# PUT /rules/:id is an idempotent full-body merge (same payload, same result), so the
|
|
87
|
+
# transport's default retry is safe - and skipping it just drops legitimate edits on a
|
|
88
|
+
# transient failure.
|
|
76
89
|
data = self._request("PUT", f"/agent-monitoring/rules/{rule_id}", json=payload)
|
|
77
90
|
return MonitorRule(data.get("rule", data))
|
|
78
91
|
|
agentx/monitor/scorer_groups.py
CHANGED
|
@@ -10,6 +10,7 @@ from typing import Any, Dict, List, Optional
|
|
|
10
10
|
import requests
|
|
11
11
|
|
|
12
12
|
from agentx.exceptions import AgentXError, AgentXAuthError, AgentXValidationError
|
|
13
|
+
from agentx.monitor._transport import request_with_retries
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class AgentXScorerGroupsError(AgentXError):
|
|
@@ -49,7 +50,9 @@ class ScorerGroupsClient:
|
|
|
49
50
|
self._workspace_id = workspace_id
|
|
50
51
|
self._base = base_url.rstrip("/") + "/agent-monitoring/scorer-groups"
|
|
51
52
|
|
|
52
|
-
def _request(
|
|
53
|
+
def _request(
|
|
54
|
+
self, method: str, url: str, json: Optional[Dict[str, Any]] = None, retry: bool = True
|
|
55
|
+
) -> Any:
|
|
53
56
|
params = None
|
|
54
57
|
if self._workspace_id:
|
|
55
58
|
# Mirrors MonitorClient._workspace_params/_with_workspace: GETs (and DELETEs)
|
|
@@ -61,9 +64,12 @@ class ScorerGroupsClient:
|
|
|
61
64
|
json = {**json, "workspaceId": self._workspace_id}
|
|
62
65
|
else:
|
|
63
66
|
params = {"workspaceId": self._workspace_id}
|
|
64
|
-
|
|
67
|
+
# retry=False for ANY non-idempotent write (creates, deletes) -
|
|
68
|
+
# MonitorClient._request's posture, via the shared monitor transport.
|
|
69
|
+
response = request_with_retries(
|
|
65
70
|
method,
|
|
66
71
|
url,
|
|
72
|
+
retry=retry,
|
|
67
73
|
headers={"x-api-key": self._api_key, "content-type": "application/json"},
|
|
68
74
|
json=json,
|
|
69
75
|
params=params,
|
|
@@ -106,15 +112,33 @@ class ScorerGroupsClient:
|
|
|
106
112
|
payload["description"] = description
|
|
107
113
|
if online is not None:
|
|
108
114
|
payload["online"] = online
|
|
109
|
-
|
|
115
|
+
# Server-side create: a timeout + transport retry would create the group twice.
|
|
116
|
+
return ScorerGroup(
|
|
117
|
+
self._request("POST", self._base, json=payload, retry=False)["scorerGroup"]
|
|
118
|
+
)
|
|
110
119
|
|
|
111
120
|
def update(self, group_id: str, **fields: Any) -> ScorerGroup:
|
|
112
|
-
"""Sparse update - pass any of name/description/members/online
|
|
113
|
-
live scoring
|
|
121
|
+
"""Sparse update - pass any of name/description/members/online. ``online`` itself may be
|
|
122
|
+
partial: ``online={"enabled": False}`` pauses live scoring, the engine merges the patch
|
|
123
|
+
over the stored profile. ``online=None`` detaches live scoring entirely. A partial
|
|
124
|
+
``online=`` patch on a group with NO stored live profile is rejected by the engine
|
|
125
|
+
(400) - send the full profile the first time (the shape ``create`` documents)."""
|
|
126
|
+
# Same guard patterns.update/rules.update carry: the engine's schema strips keys it
|
|
127
|
+
# does not recognize, so a snake_case key would 200 with the group unchanged.
|
|
128
|
+
for key in fields:
|
|
129
|
+
if "_" in key:
|
|
130
|
+
first, *rest = key.split("_")
|
|
131
|
+
camel = first + "".join(part.capitalize() for part in rest)
|
|
132
|
+
raise ValueError(
|
|
133
|
+
f"Unknown scorer group field {key!r} - the engine reads camelCase keys and "
|
|
134
|
+
f"would silently ignore this; send {camel!r} instead."
|
|
135
|
+
)
|
|
114
136
|
return ScorerGroup(self._request("PUT", f"{self._base}/{group_id}", json=fields)["scorerGroup"])
|
|
115
137
|
|
|
116
138
|
def delete(self, group_id: str) -> None:
|
|
117
|
-
|
|
139
|
+
# retry=False: a lost response + transport retry would turn a successful delete
|
|
140
|
+
# into a spurious 404.
|
|
141
|
+
self._request("DELETE", f"{self._base}/{group_id}", retry=False)
|
|
118
142
|
|
|
119
143
|
def ratings(self, group_id: str, window: str = "7d") -> Dict[str, Any]:
|
|
120
144
|
"""Live score history for a group - ``{"window", "points": [{ts, averageRating, count}]}``,
|
agentx/monitor/scorers.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Optional, Sequence
|
|
|
5
5
|
|
|
6
6
|
import requests
|
|
7
7
|
|
|
8
|
+
from agentx.monitor._transport import request_with_retries
|
|
8
9
|
from agentx.util import api_base, get_headers
|
|
9
10
|
from agentx.exceptions import AgentXError, AgentXAuthError, AgentXValidationError
|
|
10
11
|
|
|
@@ -52,7 +53,9 @@ class ScorersClient:
|
|
|
52
53
|
# Captured once at construction (deep-dive round 3, bug #1).
|
|
53
54
|
self._base_url = (base_url or api_base()).rstrip("/")
|
|
54
55
|
|
|
55
|
-
def _request(
|
|
56
|
+
def _request(
|
|
57
|
+
self, method: str, path: str, json: Any = None, params: Any = None, retry: bool = True
|
|
58
|
+
) -> Any:
|
|
56
59
|
if self._workspace_id:
|
|
57
60
|
# Mirrors MonitorClient._workspace_params/_with_workspace: GETs (and DELETEs)
|
|
58
61
|
# carry workspaceId as a query param, write bodies carry it as a field.
|
|
@@ -63,9 +66,12 @@ class ScorersClient:
|
|
|
63
66
|
json = {**json, "workspaceId": self._workspace_id}
|
|
64
67
|
elif not (params or {}).get("workspaceId"):
|
|
65
68
|
params = {**(params or {}), "workspaceId": self._workspace_id}
|
|
66
|
-
|
|
69
|
+
# retry=False for ANY non-idempotent write (creates, deletes, dry-run executions) -
|
|
70
|
+
# MonitorClient._request's posture, via the shared monitor transport.
|
|
71
|
+
resp = request_with_retries(
|
|
67
72
|
method,
|
|
68
73
|
f"{self._base_url}/agent-monitoring{path}",
|
|
74
|
+
retry=retry,
|
|
69
75
|
headers={**get_headers(self._api_key), "Content-Type": "application/json"},
|
|
70
76
|
json=json,
|
|
71
77
|
params=params,
|
|
@@ -147,7 +153,8 @@ class ScorersClient:
|
|
|
147
153
|
``None`` to skip; a score below ``alert_below`` raises a signal."""
|
|
148
154
|
if language not in ("python", "javascript"):
|
|
149
155
|
raise AgentXScorersError('language must be "python" or "javascript"')
|
|
150
|
-
|
|
156
|
+
# Server-side create: a timeout + transport retry would deploy the scorer twice.
|
|
157
|
+
return self._request("POST", "/custom-evaluators", retry=False, json={
|
|
151
158
|
"name": name,
|
|
152
159
|
"kind": "code",
|
|
153
160
|
"language": language,
|
|
@@ -173,7 +180,8 @@ class ScorersClient:
|
|
|
173
180
|
agent_ids: Optional[Sequence[str]] = None,
|
|
174
181
|
) -> Dict[str, Any]:
|
|
175
182
|
"""Register an external scorer endpoint (POSTed the v2 payload per sampled trace)."""
|
|
176
|
-
|
|
183
|
+
# Server-side create: a timeout + transport retry would register the scorer twice.
|
|
184
|
+
return self._request("POST", "/custom-evaluators", retry=False, json={
|
|
177
185
|
"name": name,
|
|
178
186
|
"url": url,
|
|
179
187
|
"sampleRate": sample_rate,
|
|
@@ -191,7 +199,9 @@ class ScorersClient:
|
|
|
191
199
|
return self._request("PUT", f"/custom-evaluators/{scorer_id}", json=wire)["evaluator"]
|
|
192
200
|
|
|
193
201
|
def delete(self, scorer_id: str) -> None:
|
|
194
|
-
|
|
202
|
+
# retry=False: a lost response + transport retry would turn a successful delete
|
|
203
|
+
# into a spurious 404.
|
|
204
|
+
self._request("DELETE", f"/custom-evaluators/{scorer_id}", retry=False)
|
|
195
205
|
|
|
196
206
|
def events(self, scorer_id: str, window: str = "24h") -> List[Dict[str, Any]]:
|
|
197
207
|
"""The scorer's per-check history (score, matched, justification, trace ids)."""
|
|
@@ -201,7 +211,9 @@ class ScorersClient:
|
|
|
201
211
|
"""Execute a scorer against the built-in sample without persisting: pass either
|
|
202
212
|
``url=...`` (external) or ``kind="code", language=..., script=...`` (code)."""
|
|
203
213
|
wire = {_SNAKE_TO_WIRE.get(k, k): v for k, v in payload.items()}
|
|
204
|
-
|
|
214
|
+
# Executes real scorer work (and, for external, hits the user's endpoint) - a
|
|
215
|
+
# client-side timeout must not fire it twice.
|
|
216
|
+
return self._request("POST", "/custom-evaluators/dry-run", json=wire, retry=False)
|
|
205
217
|
|
|
206
218
|
|
|
207
219
|
_SNAKE_TO_WIRE = {
|
agentx/resources/agent.py
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
from typing import Optional, List
|
|
2
|
-
from pydantic import BaseModel, Field
|
|
2
|
+
from pydantic import BaseModel, PrivateAttr, Field
|
|
3
3
|
import requests
|
|
4
4
|
import os
|
|
5
5
|
import logging
|
|
6
|
-
from dataclasses import dataclass
|
|
7
6
|
from agentx.util import get_headers, api_base
|
|
8
7
|
from .conversation import Conversation
|
|
9
8
|
|
|
10
9
|
|
|
11
|
-
@dataclass
|
|
12
10
|
class Agent(BaseModel):
|
|
13
11
|
id: str = Field(alias="_id")
|
|
14
12
|
name: str
|
|
@@ -16,16 +14,35 @@ class Agent(BaseModel):
|
|
|
16
14
|
createdAt: Optional[str] = None
|
|
17
15
|
updatedAt: Optional[str] = None
|
|
18
16
|
|
|
17
|
+
|
|
18
|
+
# Hosted credentials threaded from the constructing AgentX client. The module-level
|
|
19
|
+
# api_base()/get_headers() read only env vars, and the client deliberately stopped
|
|
20
|
+
# writing its constructor args into os.environ - without binding, a client created with
|
|
21
|
+
# api_key=/base_url= issued these calls unauthenticated against the default host.
|
|
22
|
+
_api_key: Optional[str] = PrivateAttr(default=None)
|
|
23
|
+
_base_url: Optional[str] = PrivateAttr(default=None)
|
|
24
|
+
|
|
25
|
+
def _bind(self, api_key: Optional[str], base_url: Optional[str]) -> "Agent":
|
|
26
|
+
self._api_key = api_key
|
|
27
|
+
self._base_url = base_url
|
|
28
|
+
return self
|
|
29
|
+
|
|
30
|
+
def _api_base(self) -> str:
|
|
31
|
+
return self._base_url or api_base()
|
|
32
|
+
|
|
33
|
+
def _headers(self):
|
|
34
|
+
return get_headers(self._api_key)
|
|
35
|
+
|
|
19
36
|
def __init__(self, **data):
|
|
20
37
|
super().__init__(**data)
|
|
21
38
|
|
|
22
39
|
def new_conversation(self) -> Conversation:
|
|
23
|
-
url = f"{
|
|
24
|
-
response = requests.post(url, headers=
|
|
40
|
+
url = f"{self._api_base()}/access/agents/{self.id}/conversations/new"
|
|
41
|
+
response = requests.post(url, headers=self._headers(), json={"type": "chat"})
|
|
25
42
|
if response.status_code == 200:
|
|
26
43
|
data = response.json()
|
|
27
44
|
data["agent_id"] = self.id
|
|
28
|
-
return Conversation(**data)
|
|
45
|
+
return Conversation(**data)._bind(self._api_key, self._base_url)
|
|
29
46
|
else:
|
|
30
47
|
raise Exception(f"Failed to create conversation: {response.reason}")
|
|
31
48
|
|
|
@@ -37,8 +54,8 @@ class Agent(BaseModel):
|
|
|
37
54
|
)
|
|
38
55
|
|
|
39
56
|
def list_conversations(self) -> List[Conversation]:
|
|
40
|
-
url = f"{
|
|
41
|
-
response = requests.get(url, headers=
|
|
57
|
+
url = f"{self._api_base()}/access/agents/{self.id}/conversations"
|
|
58
|
+
response = requests.get(url, headers=self._headers())
|
|
42
59
|
if response.status_code == 200:
|
|
43
60
|
return [
|
|
44
61
|
Conversation(
|
|
@@ -49,7 +66,9 @@ class Agent(BaseModel):
|
|
|
49
66
|
agents=conv_res.get("bots"),
|
|
50
67
|
createdAt=conv_res.get("createdAt"),
|
|
51
68
|
updatedAt=conv_res.get("updatedAt"),
|
|
52
|
-
|
|
69
|
+
# Bound to this agent's own credentials (threaded in via Agent._bind) so
|
|
70
|
+
# the conversation's calls authenticate the same way this listing did.
|
|
71
|
+
)._bind(self._api_key, self._base_url)
|
|
53
72
|
for conv_res in response.json()
|
|
54
73
|
]
|
|
55
74
|
else:
|
agentx/resources/conversation.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import requests
|
|
3
3
|
from typing import Optional, List, Any, Iterator
|
|
4
|
-
from pydantic import BaseModel, Field
|
|
4
|
+
from pydantic import BaseModel, PrivateAttr, Field
|
|
5
5
|
from agentx.util import get_headers, api_base
|
|
6
6
|
|
|
7
7
|
|
|
@@ -42,28 +42,49 @@ class Conversation(BaseModel):
|
|
|
42
42
|
populate_by_name = True
|
|
43
43
|
extra = "ignore"
|
|
44
44
|
|
|
45
|
+
|
|
46
|
+
# Hosted credentials threaded from the constructing AgentX client. The module-level
|
|
47
|
+
# api_base()/get_headers() read only env vars, and the client deliberately stopped
|
|
48
|
+
# writing its constructor args into os.environ - without binding, a client created with
|
|
49
|
+
# api_key=/base_url= issued these calls unauthenticated against the default host.
|
|
50
|
+
_api_key: Optional[str] = PrivateAttr(default=None)
|
|
51
|
+
_base_url: Optional[str] = PrivateAttr(default=None)
|
|
52
|
+
|
|
53
|
+
def _bind(self, api_key: Optional[str], base_url: Optional[str]) -> "Conversation":
|
|
54
|
+
self._api_key = api_key
|
|
55
|
+
self._base_url = base_url
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
def _api_base(self) -> str:
|
|
59
|
+
return self._base_url or api_base()
|
|
60
|
+
|
|
61
|
+
def _headers(self):
|
|
62
|
+
return get_headers(self._api_key)
|
|
63
|
+
|
|
45
64
|
def __init__(self, **data):
|
|
46
65
|
super().__init__(**data)
|
|
47
66
|
|
|
48
67
|
def new_conversation(self) -> "Conversation":
|
|
49
|
-
url = f"{
|
|
68
|
+
url = f"{self._api_base()}/access/agents/{self.agent_id}/conversations/new"
|
|
50
69
|
response = requests.post(
|
|
51
70
|
url,
|
|
52
|
-
headers=
|
|
71
|
+
headers=self._headers(),
|
|
53
72
|
json={"type": "chat"},
|
|
54
73
|
)
|
|
55
74
|
if response.status_code == 200:
|
|
56
75
|
new_conv = response.json()
|
|
57
76
|
new_conv["agent_id"] = self.agent_id
|
|
58
|
-
|
|
77
|
+
# Bound to this conversation's own credentials - unbound, the sibling
|
|
78
|
+
# conversation fell back to env credentials against the default host.
|
|
79
|
+
return Conversation(**new_conv)._bind(self._api_key, self._base_url)
|
|
59
80
|
else:
|
|
60
81
|
raise Exception(
|
|
61
82
|
f"Failed to create new conversation: {response.status_code} - {response.reason}"
|
|
62
83
|
)
|
|
63
84
|
|
|
64
85
|
def list_messages(self) -> List[Message]:
|
|
65
|
-
url = f"{
|
|
66
|
-
response = requests.get(url, headers=
|
|
86
|
+
url = f"{self._api_base()}/access/agents/{self.agent_id}/conversations/{self.id}"
|
|
87
|
+
response = requests.get(url, headers=self._headers())
|
|
67
88
|
if response.status_code == 200:
|
|
68
89
|
res = response.json()
|
|
69
90
|
if res.get("messages"):
|
|
@@ -83,18 +104,18 @@ class Conversation(BaseModel):
|
|
|
83
104
|
)
|
|
84
105
|
|
|
85
106
|
def chat(self, message: str, context: Optional[int] = None):
|
|
86
|
-
url = f"{
|
|
107
|
+
url = f"{self._api_base()}/access/conversations/{self.id}/message"
|
|
87
108
|
response = requests.post(
|
|
88
109
|
url,
|
|
89
|
-
headers=
|
|
110
|
+
headers=self._headers(),
|
|
90
111
|
json={"message": message, "context": context},
|
|
91
112
|
)
|
|
92
113
|
return response.json()
|
|
93
114
|
|
|
94
115
|
def chat_stream(self, message: str, context: Optional[int] = None) -> Iterator[ChatResponse]:
|
|
95
|
-
url = f"{
|
|
116
|
+
url = f"{self._api_base()}/access/conversations/{self.id}/jsonmessagesse"
|
|
96
117
|
response = requests.post(
|
|
97
|
-
url, headers=
|
|
118
|
+
url, headers=self._headers(), json={"message": message, "context": context}
|
|
98
119
|
)
|
|
99
120
|
result = ""
|
|
100
121
|
if response.status_code == 200:
|
agentx/resources/workforce.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from typing import Optional, List, Dict, Any, Iterator
|
|
2
|
-
from pydantic import BaseModel, Field
|
|
2
|
+
from pydantic import BaseModel, PrivateAttr, Field
|
|
3
3
|
import requests
|
|
4
4
|
import os
|
|
5
5
|
import json
|
|
@@ -46,19 +46,44 @@ class Workforce(BaseModel):
|
|
|
46
46
|
populate_by_name = True
|
|
47
47
|
extra = "ignore"
|
|
48
48
|
|
|
49
|
+
|
|
50
|
+
# Hosted credentials threaded from the constructing AgentX client. The module-level
|
|
51
|
+
# api_base()/get_headers() read only env vars, and the client deliberately stopped
|
|
52
|
+
# writing its constructor args into os.environ - without binding, a client created with
|
|
53
|
+
# api_key=/base_url= issued these calls unauthenticated against the default host.
|
|
54
|
+
_api_key: Optional[str] = PrivateAttr(default=None)
|
|
55
|
+
_base_url: Optional[str] = PrivateAttr(default=None)
|
|
56
|
+
|
|
57
|
+
def _bind(self, api_key: Optional[str], base_url: Optional[str]) -> "Workforce":
|
|
58
|
+
self._api_key = api_key
|
|
59
|
+
self._base_url = base_url
|
|
60
|
+
# The nested Agent objects issue their own calls (new_conversation,
|
|
61
|
+
# list_conversations) - left unbound they silently fall back to env credentials
|
|
62
|
+
# against the default host, the exact leak _bind exists to close.
|
|
63
|
+
self.manager._bind(api_key, base_url)
|
|
64
|
+
for agent in self.agents:
|
|
65
|
+
agent._bind(api_key, base_url)
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
def _api_base(self) -> str:
|
|
69
|
+
return self._base_url or api_base()
|
|
70
|
+
|
|
71
|
+
def _headers(self):
|
|
72
|
+
return get_headers(self._api_key)
|
|
73
|
+
|
|
49
74
|
def new_conversation(self) -> Conversation:
|
|
50
75
|
"""Create a new conversation for this workforce."""
|
|
51
|
-
url = f"{
|
|
76
|
+
url = f"{self._api_base()}/access/teams/{self.id}/conversations/new"
|
|
52
77
|
response = requests.post(
|
|
53
78
|
url,
|
|
54
|
-
headers=
|
|
79
|
+
headers=self._headers(),
|
|
55
80
|
json={"type": "chat"},
|
|
56
81
|
)
|
|
57
82
|
if response.status_code == 200:
|
|
58
83
|
conv_data = response.json()
|
|
59
84
|
# Set the agent_id to the manager's ID since this is a workforce conversation
|
|
60
85
|
conv_data["agent_id"] = self.manager.id
|
|
61
|
-
return Conversation(**conv_data)
|
|
86
|
+
return Conversation(**conv_data)._bind(self._api_key, self._base_url)
|
|
62
87
|
else:
|
|
63
88
|
raise Exception(
|
|
64
89
|
f"Failed to create new conversation: {response.status_code} - {response.reason}"
|
|
@@ -66,14 +91,14 @@ class Workforce(BaseModel):
|
|
|
66
91
|
|
|
67
92
|
def list_conversations(self) -> List[Conversation]:
|
|
68
93
|
"""List all conversations for this workforce."""
|
|
69
|
-
url = f"{
|
|
70
|
-
response = requests.get(url, headers=
|
|
94
|
+
url = f"{self._api_base()}/access/teams/{self.id}/conversations"
|
|
95
|
+
response = requests.get(url, headers=self._headers())
|
|
71
96
|
if response.status_code == 200:
|
|
72
97
|
conversations = []
|
|
73
98
|
for conv_data in response.json():
|
|
74
99
|
# Set the agent_id to the manager's ID since this is a workforce conversation
|
|
75
100
|
conv_data["agent_id"] = self.manager.id
|
|
76
|
-
conversations.append(Conversation(**conv_data))
|
|
101
|
+
conversations.append(Conversation(**conv_data)._bind(self._api_key, self._base_url))
|
|
77
102
|
return conversations
|
|
78
103
|
else:
|
|
79
104
|
raise Exception(
|
|
@@ -85,10 +110,10 @@ class Workforce(BaseModel):
|
|
|
85
110
|
) -> Iterator[ChatResponse]:
|
|
86
111
|
"""Send a message to a team conversation and stream the response."""
|
|
87
112
|
url = (
|
|
88
|
-
f"{
|
|
113
|
+
f"{self._api_base()}/access/teams/conversations/{conversation_id}/jsonmessagesse"
|
|
89
114
|
)
|
|
90
115
|
response = requests.post(
|
|
91
|
-
url, headers=
|
|
116
|
+
url, headers=self._headers(), json={"message": message, "context": context}
|
|
92
117
|
)
|
|
93
118
|
result = ""
|
|
94
119
|
if response.status_code == 200:
|
agentx/tracing/tracer.py
CHANGED
|
@@ -931,9 +931,11 @@ class Tracer:
|
|
|
931
931
|
Deliberately NOT a retrieval: retrieval spans feed the RAG judges' ``{context}``
|
|
932
932
|
(knowledge grounding), while memory is recalled state - see the engine's spanKind.ts.
|
|
933
933
|
|
|
934
|
-
With no active span the record is DROPPED (
|
|
935
|
-
|
|
936
|
-
|
|
934
|
+
With no active span the record is DROPPED (warns once per process, then logs at
|
|
935
|
+
debug), not queued. There are two pending queues (tool calls -> ``tool_calls``,
|
|
936
|
+
retrievals -> ``retrieval_steps``) and neither fits: ``retrieval_steps`` feeds the
|
|
937
|
+
engine's RAG ``{context}`` extraction, which recalled state must never reach, and no
|
|
938
|
+
memory-shaped queue has been built yet. Wrap the call
|
|
937
939
|
in ``tracer.trace()`` to keep it - or, on a worker thread, wrap the worker body in
|
|
938
940
|
``tracer.use_span(span)`` - a bare thread starts with an empty span stack.
|
|
939
941
|
(``record_tool_call``/``record_retrieval`` queue instead - see their docstrings.)
|
|
@@ -980,7 +982,7 @@ class Tracer:
|
|
|
980
982
|
with tracer.trace_memory("user prefs", operation="read", query=user_id) as m:
|
|
981
983
|
m.output = memory.search(user_id, question)
|
|
982
984
|
|
|
983
|
-
With no active span the record is DROPPED (
|
|
985
|
+
With no active span the record is DROPPED (warns once per process, then logs at debug), not queued - see
|
|
984
986
|
:meth:`record_memory`. An exception escaping the block records the operation as
|
|
985
987
|
failed (error set, output ``ERROR: ...``) and then propagates unchanged - same
|
|
986
988
|
posture as :meth:`trace_tool_call`.
|
agentx/util.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from typing import Optional
|
|
3
3
|
|
|
4
|
+
from agentx.exceptions import AgentXAuthError
|
|
5
|
+
|
|
4
6
|
_DEFAULT_API_BASE = "https://api.agentx.so/api/v1"
|
|
5
7
|
|
|
6
8
|
_EVALUATIONS_SUFFIX = "/custom-agent-evaluations"
|
|
@@ -26,4 +28,10 @@ def api_base() -> str:
|
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
def get_headers(api_key: Optional[str] = None):
|
|
29
|
-
|
|
31
|
+
key = api_key or os.getenv("AGENTX_API_KEY")
|
|
32
|
+
if not key:
|
|
33
|
+
# A None header serializes as the literal string "None" (or drops), turning a config
|
|
34
|
+
# mistake into an opaque 401 from the server - fail loud at the call site instead,
|
|
35
|
+
# with the SDK's canonical auth error so `except agentx.AgentXAuthError` catches it.
|
|
36
|
+
raise AgentXAuthError("No API key: pass api_key= or set AGENTX_API_KEY")
|
|
37
|
+
return {"accept": "*/*", "x-api-key": key}
|
agentx/version.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
VERSION = "0.8.
|
|
1
|
+
VERSION = "0.8.26"
|
|
2
2
|
|
|
3
3
|
# The AgentX-trace-eval release this SDK version is tested against - what `agentx-trace-eval`
|
|
4
4
|
# installs and converges to (see agentx/cli.py). Bump together with VERSION when releasing, so
|
|
5
5
|
# every published SDK names a known-good engine+dashboard pair. Users can override with
|
|
6
6
|
# AGENTX_TRACE_EVAL_VERSION=<tag|latest>.
|
|
7
|
-
ENGINE_VERSION = "v0.3.
|
|
7
|
+
ENGINE_VERSION = "v0.3.29"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentx-python
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.26
|
|
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
|
|
@@ -23,6 +23,8 @@ Provides-Extra: openai-agents
|
|
|
23
23
|
Requires-Dist: openai-agents>=0.0.3; extra == "openai-agents"
|
|
24
24
|
Provides-Extra: openai
|
|
25
25
|
Requires-Dist: openai>=1.0.0; extra == "openai"
|
|
26
|
+
Provides-Extra: nvidia-nim
|
|
27
|
+
Requires-Dist: openai>=1.0.0; extra == "nvidia-nim"
|
|
26
28
|
Provides-Extra: anthropic
|
|
27
29
|
Requires-Dist: anthropic>=0.25.0; extra == "anthropic"
|
|
28
30
|
Provides-Extra: google-adk
|
|
@@ -108,7 +110,9 @@ Also see [SDK Developer Docs](https://developers.agentx.so), [API Reference Docs
|
|
|
108
110
|
pip install --upgrade agentx-python
|
|
109
111
|
```
|
|
110
112
|
|
|
111
|
-
Requires Python 3.9 or newer.
|
|
113
|
+
Requires Python 3.9 or newer for the core SDK. Some integration extras have higher floors set
|
|
114
|
+
by their upstream packages - `[crewai]`, `[autogen]`, and `[databricks]` need Python 3.10+ (`[all]`
|
|
115
|
+
therefore does too); the core tracer and every REST surface stay 3.9-compatible.
|
|
112
116
|
|
|
113
117
|
#### Run the self-host governance suite locally
|
|
114
118
|
|
|
@@ -248,6 +252,7 @@ extra:
|
|
|
248
252
|
| CrewAI | `pip install "agentx-python[crewai]"` | `AgentXCrewObserver` |
|
|
249
253
|
| OpenAI Agents SDK | `pip install "agentx-python[openai-agents]"` | `AgentXTracingProcessor` |
|
|
250
254
|
| OpenAI (raw client) | `pip install "agentx-python[openai]"` | `patch_openai_client` |
|
|
255
|
+
| NVIDIA NIM | `pip install "agentx-python[nvidia-nim]"` | `patch_nim_client` |
|
|
251
256
|
| Anthropic | `pip install "agentx-python[anthropic]"` | `patch_anthropic_client` |
|
|
252
257
|
| Google ADK | `pip install "agentx-python[google-adk]"` | `AgentXADKPlugin` |
|
|
253
258
|
| Google GenAI (Gemini) | `pip install "agentx-python[google-genai]"` | `patch_genai_client` |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
agentx/__init__.py,sha256=oIxNK1s1Gv7S5gKzhX0NC571TBvinzfn-OWevLMME_g,902
|
|
2
|
-
agentx/agentx.py,sha256=
|
|
2
|
+
agentx/agentx.py,sha256=IgFIQAHJoo-FyqMCGY6RdRMmHAnjtBXKTcXWlyDkeE8,12040
|
|
3
3
|
agentx/cli.py,sha256=yawLQLSeZ7KIl7ukgmly-lrYbx9GgKcaI9M4xamiMPw,10468
|
|
4
4
|
agentx/exceptions.py,sha256=jATpl8mdgdBpRPOLXu8ltdjF6s_V0a5mWdW0K5mDg68,2115
|
|
5
5
|
agentx/export.py,sha256=nvFRn_d-9W_cHCEsh6rwOrYzVMJosfw493DG8Gi2Ca8,4309
|
|
@@ -9,8 +9,8 @@ agentx/projects.py,sha256=5s8_ib5HV5kTlPCCm7VsbSYsPP9JaARsA8VjYNR7m6o,2584
|
|
|
9
9
|
agentx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
agentx/testing.py,sha256=0shZEid_vhJgBegpO_loUq6APYDxQrUgD3jvcRdIDV8,7372
|
|
11
11
|
agentx/traces.py,sha256=Jh07n3GLS1SGRsDoLDTUaAbuMEdvjqxCCMA9gUcSZ0M,2331
|
|
12
|
-
agentx/util.py,sha256=
|
|
13
|
-
agentx/version.py,sha256=
|
|
12
|
+
agentx/util.py,sha256=ivCuFQ5AGUw9fR9fJy5HrSp3lLwwngsDwppnvyZpq9E,1471
|
|
13
|
+
agentx/version.py,sha256=mHuhKZSPSp8Xp5Pq7q93wgdcMtBUM_OBt-nWG4Qoh6g,366
|
|
14
14
|
agentx/evaluations/__init__.py,sha256=Erv7RGFlRxGTG4rVb2uHhCqLLEX_iAWKqkZKzK6CumE,262
|
|
15
15
|
agentx/evaluations/_term.py,sha256=WFpiNzdgDBeJJ-Gg-6X7TwwxllobuE4OqFUTuDQvS3Y,2529
|
|
16
16
|
agentx/evaluations/client.py,sha256=7c35tS8zM3fuvFfVDQk5etTTEFiIykmPjuJxkuFyFTY,38013
|
|
@@ -27,11 +27,11 @@ agentx/evaluations/adapters/__init__.py,sha256=fK8Hx75usbiY03XUSmnLnSrwBXip2CQs2
|
|
|
27
27
|
agentx/evaluations/adapters/http_endpoint.py,sha256=-Gika9lBcXjtbnyB-uNR6nYEVJhWEPtDazi9ixLvJtg,2059
|
|
28
28
|
agentx/evaluations/adapters/precomputed.py,sha256=vxnQELmpiU2NkQATfiVjG7v1ZmmkrEA70Y11ijfYtHw,1208
|
|
29
29
|
agentx/evaluations/adapters/raw.py,sha256=FkDq_mdf21-xt5EHnGyIGcS6VZxkEkFcmM9VTd-T5sA,1130
|
|
30
|
-
agentx/integrations/__init__.py,sha256=
|
|
30
|
+
agentx/integrations/__init__.py,sha256=p-YHLIudYxQj1bBj77NiVPsP0VQHemX5E59phkI3Y-Q,711
|
|
31
31
|
agentx/integrations/_traced_call.py,sha256=OuFmSLw5vjI54gpdte7-zOt7gld7faQmvhuoh_3OPlo,6861
|
|
32
32
|
agentx/integrations/anthropic.py,sha256=z6o9cC9rBzxxbF1UBiMr-kjEgDUO-BCuPOFoqGN3qF4,10451
|
|
33
33
|
agentx/integrations/autogen.py,sha256=V1kvTjxQqOG9KsYd4dztaRYH7zTknW0_fqzKjIQG5dk,9507
|
|
34
|
-
agentx/integrations/crewai.py,sha256=
|
|
34
|
+
agentx/integrations/crewai.py,sha256=SCObRsrS40P8jOGoI_T5oW4IYuoBW5WjBNuc7zUBU70,14346
|
|
35
35
|
agentx/integrations/databricks.py,sha256=vKXnur-LWMTzctFuIKte2N826sydXVlpi1qFXo2dfio,18044
|
|
36
36
|
agentx/integrations/google_adk.py,sha256=AHJFpt8ZKHuURqKQ9xVVvFqIm16R4bfRSJtIuWuL110,19213
|
|
37
37
|
agentx/integrations/google_genai.py,sha256=2patIdgyjEEW70FLP7ng4hcnFY-p0a1jPi1Pof28KBo,13272
|
|
@@ -39,36 +39,38 @@ agentx/integrations/langchain.py,sha256=YDeEFT2irhoEvydwt3QjbmTpk_AZZbYPS9euOR18
|
|
|
39
39
|
agentx/integrations/litellm.py,sha256=eW8iCaNq0feRbZma8iOvmJ28WiAO7ZaRmlpPjHAtcvo,7072
|
|
40
40
|
agentx/integrations/llamaindex.py,sha256=ruzMiC2TC3Xhub4xoXKPHjsvzV1sQ3QqhjkAdMxUUi4,18001
|
|
41
41
|
agentx/integrations/moveworks.py,sha256=IyBswLE5LwMSp1VbIvG5izYV5BvXmv4atZelnrDn8SE,25670
|
|
42
|
-
agentx/integrations/
|
|
42
|
+
agentx/integrations/nvidia_nim.py,sha256=bvS6ebEnQZT1DVlIjQQ2RKLI4uPPTdlMKINgDwj0Z-o,2961
|
|
43
|
+
agentx/integrations/openai.py,sha256=edJL-HXTO7w-vvIZGQChMu0AnPCY5z0duplIi8z2gXE,6936
|
|
43
44
|
agentx/integrations/openai_agents.py,sha256=KSOQ55eRPFxxkqm7p4Dsz6HcJ_9uUEwlyywPI8dwbvY,14852
|
|
44
|
-
agentx/monitor/__init__.py,sha256=
|
|
45
|
+
agentx/monitor/__init__.py,sha256=Rkk-Xv1ymXwDFbx8fetmcOzXfSU1bvsazI_-x5Ati5g,1715
|
|
46
|
+
agentx/monitor/_transport.py,sha256=ge0pmdvFdUtCJRkTLvAgjuTQVA8YOCYGQNoyRuselRk,2077
|
|
45
47
|
agentx/monitor/agents.py,sha256=KhsHVhkqa6V0e0lPcuo04fh44_qSaJOaGcD6U6qXSFo,1622
|
|
46
|
-
agentx/monitor/client.py,sha256=
|
|
47
|
-
agentx/monitor/improvement_groups.py,sha256=
|
|
48
|
-
agentx/monitor/judge_scorers.py,sha256=
|
|
48
|
+
agentx/monitor/client.py,sha256=JoKp3AmFtQ4UDOsNH5nWEkTjvQtBrrihObGaPVMtBYo,27270
|
|
49
|
+
agentx/monitor/improvement_groups.py,sha256=MrXexaBLPNzZHrqZjiCPfmwa-DSr-RHuFdP73SKrMJM,6143
|
|
50
|
+
agentx/monitor/judge_scorers.py,sha256=Xyoafo9ljeq5RpLe5HxvsL0Y0FebFQ-2CxYz8-E1Wio,21025
|
|
49
51
|
agentx/monitor/models.py,sha256=mNBQDXdVthGYHUyEeRgpFdKVOC9uMtdUgBlLfep1E14,9298
|
|
50
52
|
agentx/monitor/online_evaluators.py,sha256=YWnsBmlIbYvj4LCaX4guwx4HS5y3oEzCQfo6ae9VH_w,8693
|
|
51
|
-
agentx/monitor/patterns.py,sha256=
|
|
53
|
+
agentx/monitor/patterns.py,sha256=mHYqsigHXKkgUKYjHjj-ENXjqJZh9xpe73jdFgtsXcM,11758
|
|
52
54
|
agentx/monitor/profile.py,sha256=2ZT0HwTm47lcd_KKpAMfi5SU4vKQLj4C1IRTd5SELOY,3210
|
|
53
55
|
agentx/monitor/review_queue.py,sha256=i6Dp8ezTkGva0DBRhLOLgBqFychVFdig73jIpS6wo_g,4104
|
|
54
|
-
agentx/monitor/rules.py,sha256=
|
|
55
|
-
agentx/monitor/scorer_groups.py,sha256=
|
|
56
|
-
agentx/monitor/scorers.py,sha256=
|
|
56
|
+
agentx/monitor/rules.py,sha256=u5ezzMt8-YQHwH-XJLg4YAAox88iO0VTaC6R4ZdcGUw,4132
|
|
57
|
+
agentx/monitor/scorer_groups.py,sha256=uEtRvGpn2ho3O4AhkHyt_u1h0G5MS1fbUJCk_Pn4T4U,7019
|
|
58
|
+
agentx/monitor/scorers.py,sha256=NCag_CwUKOiDsmriKqKsoEhy6DBS7H0AVW5w8Ma7Nps,10432
|
|
57
59
|
agentx/monitor/sessions.py,sha256=bJOp-yBgnKczq2J8jsPSrstlViOkdGGN7ixH3oOYb7Q,2472
|
|
58
60
|
agentx/monitor/signals.py,sha256=Ld11lW2lbg8NHBUHdgoY6iwCvJDK-eg4ge4qQ9y7nGY,1432
|
|
59
61
|
agentx/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
|
-
agentx/resources/agent.py,sha256=
|
|
61
|
-
agentx/resources/conversation.py,sha256=
|
|
62
|
-
agentx/resources/workforce.py,sha256=
|
|
62
|
+
agentx/resources/agent.py,sha256=1WH6v6uMueqOI0TSFPTWQ_Ql3SRcq-uB7MkduPJC2qs,3033
|
|
63
|
+
agentx/resources/conversation.py,sha256=fPqvsmKW7VsHt3RDA__70I_FL6Imwm_ssmU3ObTVhec,5496
|
|
64
|
+
agentx/resources/workforce.py,sha256=cB9baw2ilf7Grw_xz1W0T3LENxo8HhrOjDwwppOrAv8,5424
|
|
63
65
|
agentx/tracing/__init__.py,sha256=l2mIRILE-wWrCD0fekgrIOQUEvtDObzdrkgpQUtXroU,408
|
|
64
66
|
agentx/tracing/ci_types.py,sha256=b-W2LowRhNBVUdaoMPgd4bnKQy9AxhfiikftRwGPAqU,1778
|
|
65
67
|
agentx/tracing/eval_scope.py,sha256=ElMbPxpqpQBVuaUnHNlw9RIQu71oyOpd0yR55bDH8eI,2144
|
|
66
68
|
agentx/tracing/framework_detect.py,sha256=uV4O7Th-4_2UkdooAyaWCOIA0jwLJbblFQ_ucFDjeJM,2638
|
|
67
69
|
agentx/tracing/ingest_client.py,sha256=teukTPckFWPg5RhpbGbVDBejBzPIPXUO3_DJcuVh2uc,22660
|
|
68
|
-
agentx/tracing/tracer.py,sha256=
|
|
69
|
-
agentx_python-0.8.
|
|
70
|
-
agentx_python-0.8.
|
|
71
|
-
agentx_python-0.8.
|
|
72
|
-
agentx_python-0.8.
|
|
73
|
-
agentx_python-0.8.
|
|
74
|
-
agentx_python-0.8.
|
|
70
|
+
agentx/tracing/tracer.py,sha256=NS0bs5HbDDK3l0Z95KVKV3xCNJT7LINS0EoPFM-wzIk,68543
|
|
71
|
+
agentx_python-0.8.26.dist-info/licenses/LICENSE,sha256=gZVsM-nLsE8vlaY6NXXsVoo6IlCClxkToAWmhgT3y_s,10762
|
|
72
|
+
agentx_python-0.8.26.dist-info/METADATA,sha256=JH0SY8e_dBGzmK92xd_Cas62qpPweH0BoBLbDmE7xPA,22986
|
|
73
|
+
agentx_python-0.8.26.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
74
|
+
agentx_python-0.8.26.dist-info/entry_points.txt,sha256=rQqF1JTY3T1yfviBU1r8PU-5mBdfOk6P4bZ1L4BskIM,172
|
|
75
|
+
agentx_python-0.8.26.dist-info/top_level.txt,sha256=s-q-HB9Gb_QdrZNacSeQyF_c25gQooMy7DlxzgLOHPk,7
|
|
76
|
+
agentx_python-0.8.26.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|