agentx-python 0.6.14__tar.gz → 0.6.15__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentx_python-0.6.14/agentx_python.egg-info → agentx_python-0.6.15}/PKG-INFO +7 -2
- {agentx_python-0.6.14 → agentx_python-0.6.15}/README.md +6 -1
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/integrations/_traced_call.py +8 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/integrations/anthropic.py +25 -10
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/integrations/google_genai.py +12 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/integrations/litellm.py +11 -4
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/integrations/openai.py +15 -6
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/monitor/models.py +7 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/monitor/profile.py +8 -2
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/tracing/tracer.py +63 -0
- agentx_python-0.6.15/agentx/version.py +1 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15/agentx_python.egg-info}/PKG-INFO +7 -2
- agentx_python-0.6.14/agentx/version.py +0 -1
- {agentx_python-0.6.14 → agentx_python-0.6.15}/LICENSE +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/__init__.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/agentx.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/cli.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/__init__.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/_term.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/adapters/__init__.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/adapters/http_endpoint.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/adapters/precomputed.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/adapters/raw.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/client.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/datasets.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/evaluation_settings.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/models.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/prompts.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/redaction.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/reporting.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/results.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/runner.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/evaluations/tracing.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/exceptions.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/integrations/__init__.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/integrations/autogen.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/integrations/crewai.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/integrations/google_adk.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/integrations/langchain.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/integrations/llamaindex.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/integrations/openai_agents.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/monitor/__init__.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/monitor/client.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/monitor/online_evaluators.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/monitor/patterns.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/monitor/signals.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/py.typed +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/resources/__init__.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/resources/agent.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/resources/conversation.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/resources/workforce.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/tracing/__init__.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/tracing/ci_types.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/tracing/ingest_client.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx/util.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx_python.egg-info/SOURCES.txt +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx_python.egg-info/dependency_links.txt +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx_python.egg-info/entry_points.txt +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx_python.egg-info/not-zip-safe +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx_python.egg-info/requires.txt +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/agentx_python.egg-info/top_level.txt +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/setup.cfg +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/setup.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/tests/test_integration.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/tests/test_integrations.py +0 -0
- {agentx_python-0.6.14 → agentx_python-0.6.15}/tests/test_span_tree.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentx-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.15
|
|
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
|
|
@@ -228,7 +228,12 @@ with tracer.trace("rag-agent", framework="langchain") as span:
|
|
|
228
228
|
|
|
229
229
|
### Framework integrations
|
|
230
230
|
|
|
231
|
-
Each integration auto-captures LLM calls, tool calls, and token usage —
|
|
231
|
+
Each integration auto-captures LLM calls, tool calls, and token usage — including prompt-caching
|
|
232
|
+
token counts (Anthropic's cache write/read, OpenAI/LiteLLM's cached tokens, Google GenAI's cached
|
|
233
|
+
content), reported as their own `cache_read_tokens`/`cache_write_tokens` fields alongside the
|
|
234
|
+
regular totals, no extra config needed. Self-host's cost estimate prices these separately from a
|
|
235
|
+
regular input token when you've set optional cache rates on that model. Install the matching
|
|
236
|
+
extra:
|
|
232
237
|
|
|
233
238
|
| Framework | Install | Integration |
|
|
234
239
|
| --------------------- | -------------------------------------------- | ------------------------ |
|
|
@@ -166,7 +166,12 @@ with tracer.trace("rag-agent", framework="langchain") as span:
|
|
|
166
166
|
|
|
167
167
|
### Framework integrations
|
|
168
168
|
|
|
169
|
-
Each integration auto-captures LLM calls, tool calls, and token usage —
|
|
169
|
+
Each integration auto-captures LLM calls, tool calls, and token usage — including prompt-caching
|
|
170
|
+
token counts (Anthropic's cache write/read, OpenAI/LiteLLM's cached tokens, Google GenAI's cached
|
|
171
|
+
content), reported as their own `cache_read_tokens`/`cache_write_tokens` fields alongside the
|
|
172
|
+
regular totals, no extra config needed. Self-host's cost estimate prices these separately from a
|
|
173
|
+
regular input token when you've set optional cache rates on that model. Install the matching
|
|
174
|
+
extra:
|
|
170
175
|
|
|
171
176
|
| Framework | Install | Integration |
|
|
172
177
|
| --------------------- | -------------------------------------------- | ------------------------ |
|
|
@@ -77,6 +77,8 @@ def finish_llm_call(
|
|
|
77
77
|
input_tokens: Optional[int],
|
|
78
78
|
output_tokens: Optional[int],
|
|
79
79
|
error: Optional[str],
|
|
80
|
+
cache_read_tokens: Optional[int] = None,
|
|
81
|
+
cache_write_tokens: Optional[int] = None,
|
|
80
82
|
) -> None:
|
|
81
83
|
"""
|
|
82
84
|
Close out one raw-client LLM call — shared by the ``on_finish``/exit
|
|
@@ -107,6 +109,8 @@ def finish_llm_call(
|
|
|
107
109
|
model=model,
|
|
108
110
|
input_tokens=input_tokens,
|
|
109
111
|
output_tokens=output_tokens,
|
|
112
|
+
cache_read_tokens=cache_read_tokens,
|
|
113
|
+
cache_write_tokens=cache_write_tokens,
|
|
110
114
|
)
|
|
111
115
|
return
|
|
112
116
|
|
|
@@ -121,4 +125,8 @@ def finish_llm_call(
|
|
|
121
125
|
span._input_tokens = input_tokens
|
|
122
126
|
if output_tokens:
|
|
123
127
|
span._output_tokens = output_tokens
|
|
128
|
+
if cache_read_tokens:
|
|
129
|
+
span._cache_read_tokens = cache_read_tokens
|
|
130
|
+
if cache_write_tokens:
|
|
131
|
+
span._cache_write_tokens = cache_write_tokens
|
|
124
132
|
span.__exit__(None, None, None)
|
|
@@ -70,23 +70,26 @@ def _prepend_system(messages: Any, system: Any) -> Any:
|
|
|
70
70
|
return [system_entry, messages]
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
def _extract_usage_tokens(
|
|
73
|
+
def _extract_usage_tokens(
|
|
74
|
+
usage: Any,
|
|
75
|
+
) -> Tuple[Optional[int], Optional[int], Optional[int], Optional[int]]:
|
|
74
76
|
"""
|
|
75
|
-
Pull input/output token counts off a ``response.usage`` object
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
Pull input/output/cache token counts off a ``response.usage`` object.
|
|
78
|
+
``input_tokens`` stays the *total* (base + cache_creation + cache_read) —
|
|
79
|
+
still real input tokens for cost/context-window purposes — while
|
|
80
|
+
``cache_read``/``cache_write`` are reported alongside as the subset of
|
|
81
|
+
that total the provider actually billed at a different (cache) rate, so
|
|
82
|
+
the backend can price them separately instead of at the full input rate.
|
|
80
83
|
"""
|
|
81
84
|
if usage is None:
|
|
82
|
-
return None, None
|
|
85
|
+
return None, None, None, None
|
|
83
86
|
input_tokens = getattr(usage, "input_tokens", None)
|
|
84
87
|
output_tokens = getattr(usage, "output_tokens", None)
|
|
85
88
|
cache_creation = getattr(usage, "cache_creation_input_tokens", None)
|
|
86
89
|
cache_read = getattr(usage, "cache_read_input_tokens", None)
|
|
87
90
|
if cache_creation or cache_read:
|
|
88
91
|
input_tokens = (input_tokens or 0) + (cache_creation or 0) + (cache_read or 0)
|
|
89
|
-
return input_tokens, output_tokens
|
|
92
|
+
return input_tokens, output_tokens, cache_read, cache_creation
|
|
90
93
|
|
|
91
94
|
|
|
92
95
|
def patch_anthropic_client(
|
|
@@ -141,10 +144,14 @@ def _patch_create(
|
|
|
141
144
|
output = None
|
|
142
145
|
input_tokens = None
|
|
143
146
|
output_tokens = None
|
|
147
|
+
cache_read_tokens = None
|
|
148
|
+
cache_write_tokens = None
|
|
144
149
|
if response is not None:
|
|
145
150
|
output = _extract_output_text(response)
|
|
146
151
|
try:
|
|
147
|
-
input_tokens, output_tokens = _extract_usage_tokens(
|
|
152
|
+
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens = _extract_usage_tokens(
|
|
153
|
+
getattr(response, "usage", None)
|
|
154
|
+
)
|
|
148
155
|
except Exception:
|
|
149
156
|
pass
|
|
150
157
|
|
|
@@ -161,6 +168,8 @@ def _patch_create(
|
|
|
161
168
|
model=model,
|
|
162
169
|
input_tokens=input_tokens,
|
|
163
170
|
output_tokens=output_tokens,
|
|
171
|
+
cache_read_tokens=cache_read_tokens,
|
|
172
|
+
cache_write_tokens=cache_write_tokens,
|
|
164
173
|
error=error,
|
|
165
174
|
)
|
|
166
175
|
|
|
@@ -195,10 +204,14 @@ def _patch_stream(
|
|
|
195
204
|
output = None
|
|
196
205
|
input_tokens = None
|
|
197
206
|
output_tokens = None
|
|
207
|
+
cache_read_tokens = None
|
|
208
|
+
cache_write_tokens = None
|
|
198
209
|
if final_message is not None:
|
|
199
210
|
output = _extract_output_text(final_message)
|
|
200
211
|
try:
|
|
201
|
-
input_tokens, output_tokens = _extract_usage_tokens(
|
|
212
|
+
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens = _extract_usage_tokens(
|
|
213
|
+
getattr(final_message, "usage", None)
|
|
214
|
+
)
|
|
202
215
|
except Exception:
|
|
203
216
|
pass
|
|
204
217
|
finish_llm_call(
|
|
@@ -214,6 +227,8 @@ def _patch_stream(
|
|
|
214
227
|
model=model,
|
|
215
228
|
input_tokens=input_tokens,
|
|
216
229
|
output_tokens=output_tokens,
|
|
230
|
+
cache_read_tokens=cache_read_tokens,
|
|
231
|
+
cache_write_tokens=cache_write_tokens,
|
|
217
232
|
error=error,
|
|
218
233
|
)
|
|
219
234
|
|
|
@@ -112,11 +112,16 @@ def _patch_generate_content(
|
|
|
112
112
|
output = _extract_response_text(response) if response is not None else None
|
|
113
113
|
input_tokens = None
|
|
114
114
|
output_tokens = None
|
|
115
|
+
cache_read_tokens = None
|
|
115
116
|
if response is not None:
|
|
116
117
|
usage = getattr(response, "usage_metadata", None)
|
|
117
118
|
if usage is not None:
|
|
118
119
|
input_tokens = getattr(usage, "prompt_token_count", None)
|
|
119
120
|
output_tokens = getattr(usage, "candidates_token_count", None)
|
|
121
|
+
# prompt_token_count already includes this — a discount breakdown, same
|
|
122
|
+
# "total unchanged, cache portion reported alongside" posture as OpenAI's
|
|
123
|
+
# prompt_tokens_details.cached_tokens.
|
|
124
|
+
cache_read_tokens = getattr(usage, "cached_content_token_count", None)
|
|
120
125
|
finish_llm_call(
|
|
121
126
|
tracer,
|
|
122
127
|
name=name,
|
|
@@ -130,6 +135,7 @@ def _patch_generate_content(
|
|
|
130
135
|
model=str(model) if model else None,
|
|
131
136
|
input_tokens=input_tokens,
|
|
132
137
|
output_tokens=output_tokens,
|
|
138
|
+
cache_read_tokens=cache_read_tokens,
|
|
133
139
|
error=error,
|
|
134
140
|
)
|
|
135
141
|
|
|
@@ -195,9 +201,11 @@ def _patch_sync_generate_content_stream(
|
|
|
195
201
|
end_t = time.time()
|
|
196
202
|
input_tokens = None
|
|
197
203
|
output_tokens = None
|
|
204
|
+
cache_read_tokens = None
|
|
198
205
|
if last_usage_metadata is not None:
|
|
199
206
|
input_tokens = getattr(last_usage_metadata, "prompt_token_count", None)
|
|
200
207
|
output_tokens = getattr(last_usage_metadata, "candidates_token_count", None)
|
|
208
|
+
cache_read_tokens = getattr(last_usage_metadata, "cached_content_token_count", None)
|
|
201
209
|
output_repr = "".join(accumulated_text) or None
|
|
202
210
|
finish_llm_call(
|
|
203
211
|
tracer,
|
|
@@ -212,6 +220,7 @@ def _patch_sync_generate_content_stream(
|
|
|
212
220
|
model=model,
|
|
213
221
|
input_tokens=input_tokens,
|
|
214
222
|
output_tokens=output_tokens,
|
|
223
|
+
cache_read_tokens=cache_read_tokens,
|
|
215
224
|
error=error,
|
|
216
225
|
)
|
|
217
226
|
|
|
@@ -260,9 +269,11 @@ def _patch_async_generate_content_stream(
|
|
|
260
269
|
end_t = time.time()
|
|
261
270
|
input_tokens = None
|
|
262
271
|
output_tokens = None
|
|
272
|
+
cache_read_tokens = None
|
|
263
273
|
if last_usage_metadata is not None:
|
|
264
274
|
input_tokens = getattr(last_usage_metadata, "prompt_token_count", None)
|
|
265
275
|
output_tokens = getattr(last_usage_metadata, "candidates_token_count", None)
|
|
276
|
+
cache_read_tokens = getattr(last_usage_metadata, "cached_content_token_count", None)
|
|
266
277
|
output_repr = "".join(accumulated_text) or None
|
|
267
278
|
finish_llm_call(
|
|
268
279
|
tracer,
|
|
@@ -277,6 +288,7 @@ def _patch_async_generate_content_stream(
|
|
|
277
288
|
model=model,
|
|
278
289
|
input_tokens=input_tokens,
|
|
279
290
|
output_tokens=output_tokens,
|
|
291
|
+
cache_read_tokens=cache_read_tokens,
|
|
280
292
|
error=error,
|
|
281
293
|
)
|
|
282
294
|
|
|
@@ -56,11 +56,16 @@ def _extract_output_text(response: Any) -> Optional[str]:
|
|
|
56
56
|
return None
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
def _extract_usage_tokens(response: Any) -> Tuple[Optional[int], Optional[int]]:
|
|
59
|
+
def _extract_usage_tokens(response: Any) -> Tuple[Optional[int], Optional[int], Optional[int]]:
|
|
60
|
+
"""Same posture as agentx.integrations.openai's own _extract_usage_tokens — LiteLLM normalizes
|
|
61
|
+
every provider's response to an OpenAI-shaped ModelResponse, so prompt_tokens_details.cached_tokens
|
|
62
|
+
is the right field here too regardless of which underlying provider actually served the call."""
|
|
60
63
|
usage = getattr(response, "usage", None)
|
|
61
64
|
if usage is None:
|
|
62
|
-
return None, None
|
|
63
|
-
|
|
65
|
+
return None, None, None
|
|
66
|
+
details = getattr(usage, "prompt_tokens_details", None)
|
|
67
|
+
cached_tokens = getattr(details, "cached_tokens", None) if details is not None else None
|
|
68
|
+
return getattr(usage, "prompt_tokens", None), getattr(usage, "completion_tokens", None), cached_tokens
|
|
64
69
|
|
|
65
70
|
|
|
66
71
|
class AgentXLiteLLMLogger(CustomLogger):
|
|
@@ -96,9 +101,10 @@ class AgentXLiteLLMLogger(CustomLogger):
|
|
|
96
101
|
output = None
|
|
97
102
|
input_tokens = None
|
|
98
103
|
output_tokens = None
|
|
104
|
+
cache_read_tokens = None
|
|
99
105
|
if error is None and response_obj is not None:
|
|
100
106
|
output = _extract_output_text(response_obj)
|
|
101
|
-
input_tokens, output_tokens = _extract_usage_tokens(response_obj)
|
|
107
|
+
input_tokens, output_tokens, cache_read_tokens = _extract_usage_tokens(response_obj)
|
|
102
108
|
|
|
103
109
|
finish_llm_call(
|
|
104
110
|
self._tracer,
|
|
@@ -113,6 +119,7 @@ class AgentXLiteLLMLogger(CustomLogger):
|
|
|
113
119
|
model=model,
|
|
114
120
|
input_tokens=input_tokens,
|
|
115
121
|
output_tokens=output_tokens,
|
|
122
|
+
cache_read_tokens=cache_read_tokens,
|
|
116
123
|
error=error,
|
|
117
124
|
)
|
|
118
125
|
|
|
@@ -58,16 +58,21 @@ def _extract_output_text(response: Any) -> Optional[str]:
|
|
|
58
58
|
return None
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
def _extract_usage_tokens(usage: Any) -> Tuple[Optional[int], Optional[int]]:
|
|
61
|
+
def _extract_usage_tokens(usage: Any) -> Tuple[Optional[int], Optional[int], Optional[int]]:
|
|
62
62
|
"""
|
|
63
|
-
Pull input/output token counts off a ``response.usage`` object.
|
|
63
|
+
Pull input/output/cached token counts off a ``response.usage`` object.
|
|
64
64
|
``prompt_tokens`` already includes cached tokens (``prompt_tokens_details
|
|
65
65
|
.cached_tokens`` is a discount breakdown, not an addition), so — unlike
|
|
66
|
-
Anthropic's cache accounting — no extra folding is needed
|
|
66
|
+
Anthropic's cache accounting — no extra folding is needed for the input
|
|
67
|
+
total; ``cached_tokens`` is reported alongside it so the backend can
|
|
68
|
+
price that subset at its own (cheaper) cache rate instead of the full
|
|
69
|
+
input rate. OpenAI has no cache-*write* concept to report.
|
|
67
70
|
"""
|
|
68
71
|
if usage is None:
|
|
69
|
-
return None, None
|
|
70
|
-
|
|
72
|
+
return None, None, None
|
|
73
|
+
details = getattr(usage, "prompt_tokens_details", None)
|
|
74
|
+
cached_tokens = getattr(details, "cached_tokens", None) if details is not None else None
|
|
75
|
+
return getattr(usage, "prompt_tokens", None), getattr(usage, "completion_tokens", None), cached_tokens
|
|
71
76
|
|
|
72
77
|
|
|
73
78
|
def patch_openai_client(
|
|
@@ -129,10 +134,13 @@ def _patch_chat_completions_create(
|
|
|
129
134
|
output = None
|
|
130
135
|
input_tokens = None
|
|
131
136
|
output_tokens = None
|
|
137
|
+
cache_read_tokens = None
|
|
132
138
|
if response is not None:
|
|
133
139
|
output = _extract_output_text(response)
|
|
134
140
|
try:
|
|
135
|
-
input_tokens, output_tokens = _extract_usage_tokens(
|
|
141
|
+
input_tokens, output_tokens, cache_read_tokens = _extract_usage_tokens(
|
|
142
|
+
getattr(response, "usage", None)
|
|
143
|
+
)
|
|
136
144
|
except Exception:
|
|
137
145
|
pass
|
|
138
146
|
|
|
@@ -149,6 +157,7 @@ def _patch_chat_completions_create(
|
|
|
149
157
|
model=model,
|
|
150
158
|
input_tokens=input_tokens,
|
|
151
159
|
output_tokens=output_tokens,
|
|
160
|
+
cache_read_tokens=cache_read_tokens,
|
|
152
161
|
error=error,
|
|
153
162
|
)
|
|
154
163
|
|
|
@@ -117,6 +117,13 @@ class MonitorProfile(BaseModel):
|
|
|
117
117
|
approval policy gating autotune actions. See ``client.monitor.profile.get()/update()``.
|
|
118
118
|
``None`` from ``get()`` means this agent has never been configured and is running on
|
|
119
119
|
platform defaults (e.g. the built-in latency threshold defaults to 20000ms).
|
|
120
|
+
|
|
121
|
+
Self-host only: ``coverage_mode``/``sample_rate``/``retention_days``/``redaction_mode``, and
|
|
122
|
+
``threshold_overrides["latencyMs"]`` are project-level defaults now (set once for every agent
|
|
123
|
+
via the dashboard's Platform Settings screen), not real per-agent settings — this model and
|
|
124
|
+
``update()`` still accept/return them for wire compatibility, but a self-host engine no longer
|
|
125
|
+
reads the stored per-agent values for any behavior. ``enabled``/``failure_detection_enabled``/
|
|
126
|
+
``info_detection_enabled``/``channels`` remain real per-agent settings on self-host too.
|
|
120
127
|
"""
|
|
121
128
|
|
|
122
129
|
id: Optional[str] = Field(default=None, alias="_id")
|
|
@@ -47,9 +47,15 @@ class MonitorProfileClient:
|
|
|
47
47
|
"""Update (and enable, if not already) this agent's Monitor profile. Only fields passed
|
|
48
48
|
here are changed; everything else on the existing profile is left as is.
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Self-host only: ``coverage_mode``/``sample_rate``/``retention_days``/``redaction_mode``,
|
|
51
|
+
and ``threshold_overrides["latencyMs"]`` are project-level defaults now (see
|
|
52
|
+
``MonitorProfile``'s docstring) — set them via the dashboard's Platform Settings screen
|
|
53
|
+
instead, passing them here is accepted but has no effect. ``enabled``/
|
|
54
|
+
``failure_detection_enabled``/``info_detection_enabled``/``channels`` still work per-agent.
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
Example (hosted, or self-host's still-per-agent fields)::
|
|
57
|
+
|
|
58
|
+
client.monitor.profile.update("agent_123", info_detection_enabled=False)
|
|
53
59
|
"""
|
|
54
60
|
payload: Dict[str, Any] = {
|
|
55
61
|
"enabled": enabled,
|
|
@@ -70,6 +70,7 @@ class _TraceSpan:
|
|
|
70
70
|
sync: bool = False,
|
|
71
71
|
monitor: bool = False,
|
|
72
72
|
pattern_ids: Optional[List[str]] = None,
|
|
73
|
+
agent_id: Optional[str] = None,
|
|
73
74
|
) -> None:
|
|
74
75
|
self._tracer = tracer
|
|
75
76
|
self.name = name
|
|
@@ -79,6 +80,10 @@ class _TraceSpan:
|
|
|
79
80
|
self._framework = framework
|
|
80
81
|
self._model = model
|
|
81
82
|
self._session_id = session_id
|
|
83
|
+
# Disambiguator for when `name` alone isn't enough — pass an already-known agent id (e.g.
|
|
84
|
+
# from a prior GET /agents lookup) to pin this trace to that exact agent. None (the
|
|
85
|
+
# default) resolves from `name` alone server-side, one stable agent per distinct name.
|
|
86
|
+
self._agent_id = agent_id
|
|
82
87
|
# When True, __exit__ sends synchronously (blocking) instead of enqueueing, so trace_id
|
|
83
88
|
# is populated by the time the `with` block exits — see Tracer.trace()'s sync param.
|
|
84
89
|
self._sync = sync
|
|
@@ -113,6 +118,10 @@ class _TraceSpan:
|
|
|
113
118
|
self._captured_framework: Optional[str] = None
|
|
114
119
|
self._input_tokens: int = 0
|
|
115
120
|
self._output_tokens: int = 0
|
|
121
|
+
# Subsets of _input_tokens (not additional tokens) — a prompt-caching write/read, when the
|
|
122
|
+
# provider reports one. See _record_llm_call/child_span for where these get populated.
|
|
123
|
+
self._cache_read_tokens: int = 0
|
|
124
|
+
self._cache_write_tokens: int = 0
|
|
116
125
|
# Guards _merge_child_run — with Tracer.use_span(), multiple threads
|
|
117
126
|
# (e.g. a ThreadPoolExecutor) can merge into this span concurrently.
|
|
118
127
|
self._merge_lock = threading.Lock()
|
|
@@ -146,6 +155,7 @@ class _TraceSpan:
|
|
|
146
155
|
monitor=self._monitor or None,
|
|
147
156
|
pattern_ids=self._pattern_ids,
|
|
148
157
|
name=self.name,
|
|
158
|
+
agent_id=self._agent_id,
|
|
149
159
|
input=_safe_serialize(self.input) if self.input is not None else None,
|
|
150
160
|
output=_safe_serialize(self.output) if self.output is not None else None,
|
|
151
161
|
latency_ms=latency_ms,
|
|
@@ -157,6 +167,8 @@ class _TraceSpan:
|
|
|
157
167
|
session_id=self._session_id,
|
|
158
168
|
input_tokens=self._input_tokens or None,
|
|
159
169
|
output_tokens=self._output_tokens or None,
|
|
170
|
+
cache_read_tokens=self._cache_read_tokens or None,
|
|
171
|
+
cache_write_tokens=self._cache_write_tokens or None,
|
|
160
172
|
span_id=self._span_id,
|
|
161
173
|
parent_span_id=self._parent_span_id,
|
|
162
174
|
started_at_unix_nano=str(int(self._start * 1_000_000_000)) if self._start else None,
|
|
@@ -193,6 +205,8 @@ class _TraceSpan:
|
|
|
193
205
|
model: Optional[str] = None,
|
|
194
206
|
input_tokens: Optional[int] = None,
|
|
195
207
|
output_tokens: Optional[int] = None,
|
|
208
|
+
cache_read_tokens: Optional[int] = None,
|
|
209
|
+
cache_write_tokens: Optional[int] = None,
|
|
196
210
|
) -> None:
|
|
197
211
|
"""Record one LLM-call child span (e.g. one patched Anthropic call) under this span —
|
|
198
212
|
name left unset so _merge_child_run auto-numbers it "LLM Call N"."""
|
|
@@ -206,12 +220,16 @@ class _TraceSpan:
|
|
|
206
220
|
"output": output,
|
|
207
221
|
"inputTokenSize": input_tokens,
|
|
208
222
|
"outputTokenSize": output_tokens,
|
|
223
|
+
"cacheReadTokenSize": cache_read_tokens,
|
|
224
|
+
"cacheWriteTokenSize": cache_write_tokens,
|
|
209
225
|
}],
|
|
210
226
|
input=input,
|
|
211
227
|
output=output,
|
|
212
228
|
model=model,
|
|
213
229
|
input_tokens=input_tokens,
|
|
214
230
|
output_tokens=output_tokens,
|
|
231
|
+
cache_read_tokens=cache_read_tokens,
|
|
232
|
+
cache_write_tokens=cache_write_tokens,
|
|
215
233
|
)
|
|
216
234
|
|
|
217
235
|
def child_span(
|
|
@@ -227,6 +245,8 @@ class _TraceSpan:
|
|
|
227
245
|
framework: Optional[str] = None,
|
|
228
246
|
input_tokens: Optional[int] = None,
|
|
229
247
|
output_tokens: Optional[int] = None,
|
|
248
|
+
cache_read_tokens: Optional[int] = None,
|
|
249
|
+
cache_write_tokens: Optional[int] = None,
|
|
230
250
|
error: Optional[str] = None,
|
|
231
251
|
tool_calls: Optional[List[Dict[str, Any]]] = None,
|
|
232
252
|
) -> "_TraceSpan":
|
|
@@ -294,6 +314,10 @@ class _TraceSpan:
|
|
|
294
314
|
wire["input_tokens"] = input_tokens
|
|
295
315
|
if output_tokens:
|
|
296
316
|
wire["output_tokens"] = output_tokens
|
|
317
|
+
if cache_read_tokens:
|
|
318
|
+
wire["cache_read_tokens"] = cache_read_tokens
|
|
319
|
+
if cache_write_tokens:
|
|
320
|
+
wire["cache_write_tokens"] = cache_write_tokens
|
|
297
321
|
child._trace_id = self._tracer._dispatch(wire)
|
|
298
322
|
return child
|
|
299
323
|
|
|
@@ -309,6 +333,8 @@ class _TraceSpan:
|
|
|
309
333
|
framework: Optional[str] = None,
|
|
310
334
|
input_tokens: Optional[int] = None,
|
|
311
335
|
output_tokens: Optional[int] = None,
|
|
336
|
+
cache_read_tokens: Optional[int] = None,
|
|
337
|
+
cache_write_tokens: Optional[int] = None,
|
|
312
338
|
) -> None:
|
|
313
339
|
"""
|
|
314
340
|
Explode a whole auto-instrumented sub-run (e.g. one top-level LangChain
|
|
@@ -333,6 +359,8 @@ class _TraceSpan:
|
|
|
333
359
|
model=step.get("model"),
|
|
334
360
|
input_tokens=step.get("inputTokenSize"),
|
|
335
361
|
output_tokens=step.get("outputTokenSize"),
|
|
362
|
+
cache_read_tokens=step.get("cacheReadTokenSize"),
|
|
363
|
+
cache_write_tokens=step.get("cacheWriteTokenSize"),
|
|
336
364
|
)
|
|
337
365
|
for tc in tool_calls or []:
|
|
338
366
|
# Some callers' tool_calls dicts (e.g. langchain.py's, which sets these on the
|
|
@@ -348,6 +376,20 @@ class _TraceSpan:
|
|
|
348
376
|
input=tc.get("input"),
|
|
349
377
|
output=tc.get("output"),
|
|
350
378
|
)
|
|
379
|
+
# Also mirror onto this span's own flat tool_calls list, sent in this span's own
|
|
380
|
+
# wire payload on __exit__ (see tool_calls=self.tool_calls or None below). The
|
|
381
|
+
# child span above is only for the trace detail view's span tree; the engine's
|
|
382
|
+
# built-in "Tool failure" Monitor check reads *this* flat list specifically,
|
|
383
|
+
# looking for a `success: false` entry — a child span row has no such field, so
|
|
384
|
+
# without this a failed tool call recorded via a framework integration (e.g.
|
|
385
|
+
# langchain.py's on_tool_error) would silently never trip that check.
|
|
386
|
+
self.tool_calls.append({
|
|
387
|
+
"name": tc.get("name") or "Tool call",
|
|
388
|
+
"input": tc.get("input"),
|
|
389
|
+
"output": tc.get("output"),
|
|
390
|
+
"latency_ms": tc.get("latency_ms"),
|
|
391
|
+
"success": tc.get("success", True),
|
|
392
|
+
})
|
|
351
393
|
for step in retrieval_steps or []:
|
|
352
394
|
self._child_span_count += 1
|
|
353
395
|
self.child_span(
|
|
@@ -371,6 +413,10 @@ class _TraceSpan:
|
|
|
371
413
|
self._input_tokens += input_tokens
|
|
372
414
|
if output_tokens:
|
|
373
415
|
self._output_tokens += output_tokens
|
|
416
|
+
if cache_read_tokens:
|
|
417
|
+
self._cache_read_tokens += cache_read_tokens
|
|
418
|
+
if cache_write_tokens:
|
|
419
|
+
self._cache_write_tokens += cache_write_tokens
|
|
374
420
|
|
|
375
421
|
# ------------------------------------------------------------------
|
|
376
422
|
# Context manager helpers
|
|
@@ -675,6 +721,7 @@ class Tracer:
|
|
|
675
721
|
sync: bool = False,
|
|
676
722
|
monitor: bool = False,
|
|
677
723
|
pattern_ids: Optional[List[str]] = None,
|
|
724
|
+
agent_id: Optional[str] = None,
|
|
678
725
|
) -> _TraceSpan:
|
|
679
726
|
"""
|
|
680
727
|
Return a :class:`_TraceSpan` that works as both a decorator and a
|
|
@@ -713,6 +760,15 @@ class Tracer:
|
|
|
713
760
|
|
|
714
761
|
with client.tracer.trace("support_agent_call", monitor=True, pattern_ids=[pattern.id]) as span:
|
|
715
762
|
span.output = call_llm(...)
|
|
763
|
+
|
|
764
|
+
``agent_id`` disambiguates when ``name`` alone isn't enough — pass an already-known agent
|
|
765
|
+
id (e.g. one you've seen in the dashboard's Overview tab, or from a direct ``GET /agents``
|
|
766
|
+
call) to pin this trace to that exact agent instead of resolving by name. Omit it (the
|
|
767
|
+
default) and this resolves from ``name`` alone — one stable agent per distinct name,
|
|
768
|
+
created on first use::
|
|
769
|
+
|
|
770
|
+
with client.tracer.trace("support-agent", agent_id="ag_123", sync=True) as span:
|
|
771
|
+
span.output = call_llm(...)
|
|
716
772
|
"""
|
|
717
773
|
return _TraceSpan(
|
|
718
774
|
tracer=self,
|
|
@@ -725,6 +781,7 @@ class Tracer:
|
|
|
725
781
|
sync=sync,
|
|
726
782
|
monitor=monitor,
|
|
727
783
|
pattern_ids=pattern_ids,
|
|
784
|
+
agent_id=agent_id,
|
|
728
785
|
)
|
|
729
786
|
|
|
730
787
|
def flush(self, timeout: float = 5.0) -> None:
|
|
@@ -946,6 +1003,10 @@ class Tracer:
|
|
|
946
1003
|
wire["input_tokens"] = payload["input_tokens"]
|
|
947
1004
|
if "output_tokens" in payload:
|
|
948
1005
|
wire["output_tokens"] = payload["output_tokens"]
|
|
1006
|
+
if "cache_read_tokens" in payload:
|
|
1007
|
+
wire["cache_read_tokens"] = payload["cache_read_tokens"]
|
|
1008
|
+
if "cache_write_tokens" in payload:
|
|
1009
|
+
wire["cache_write_tokens"] = payload["cache_write_tokens"]
|
|
949
1010
|
if "monitor" in payload:
|
|
950
1011
|
wire["monitor"] = payload["monitor"]
|
|
951
1012
|
if "pattern_ids" in payload:
|
|
@@ -956,6 +1017,8 @@ class Tracer:
|
|
|
956
1017
|
wire["parent_span_id"] = payload["parent_span_id"]
|
|
957
1018
|
if "started_at_unix_nano" in payload:
|
|
958
1019
|
wire["started_at_unix_nano"] = payload["started_at_unix_nano"]
|
|
1020
|
+
if "agent_id" in payload:
|
|
1021
|
+
wire["agent_id"] = payload["agent_id"]
|
|
959
1022
|
|
|
960
1023
|
pending_tool_calls, self._pending_tool_calls = self._pending_tool_calls, []
|
|
961
1024
|
if pending_tool_calls:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "0.6.15"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentx-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.15
|
|
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
|
|
@@ -228,7 +228,12 @@ with tracer.trace("rag-agent", framework="langchain") as span:
|
|
|
228
228
|
|
|
229
229
|
### Framework integrations
|
|
230
230
|
|
|
231
|
-
Each integration auto-captures LLM calls, tool calls, and token usage —
|
|
231
|
+
Each integration auto-captures LLM calls, tool calls, and token usage — including prompt-caching
|
|
232
|
+
token counts (Anthropic's cache write/read, OpenAI/LiteLLM's cached tokens, Google GenAI's cached
|
|
233
|
+
content), reported as their own `cache_read_tokens`/`cache_write_tokens` fields alongside the
|
|
234
|
+
regular totals, no extra config needed. Self-host's cost estimate prices these separately from a
|
|
235
|
+
regular input token when you've set optional cache rates on that model. Install the matching
|
|
236
|
+
extra:
|
|
232
237
|
|
|
233
238
|
| Framework | Install | Integration |
|
|
234
239
|
| --------------------- | -------------------------------------------- | ------------------------ |
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = "0.6.14"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|