agentx-python 0.6.2__tar.gz → 0.6.4__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.2/agentx_python.egg-info → agentx_python-0.6.4}/PKG-INFO +63 -4
- {agentx_python-0.6.2 → agentx_python-0.6.4}/README.md +62 -3
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/integrations/anthropic.py +30 -5
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/integrations/google_adk.py +17 -2
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/integrations/google_genai.py +14 -2
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/integrations/langchain.py +33 -1
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/tracing/tracer.py +19 -0
- agentx_python-0.6.4/agentx/version.py +1 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4/agentx_python.egg-info}/PKG-INFO +63 -4
- agentx_python-0.6.2/agentx/version.py +0 -1
- {agentx_python-0.6.2 → agentx_python-0.6.4}/LICENSE +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/__init__.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/agentx.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/evaluations/__init__.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/evaluations/_term.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/evaluations/adapters/__init__.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/evaluations/adapters/http_endpoint.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/evaluations/adapters/precomputed.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/evaluations/adapters/raw.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/evaluations/client.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/evaluations/datasets.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/evaluations/models.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/evaluations/redaction.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/evaluations/reporting.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/evaluations/results.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/evaluations/runner.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/evaluations/tracing.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/exceptions.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/integrations/__init__.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/integrations/_perf.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/integrations/crewai.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/integrations/openai_agents.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/resources/__init__.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/resources/agent.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/resources/conversation.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/resources/workforce.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/tracing/__init__.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/tracing/ci_types.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/tracing/ingest_client.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx/util.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx_python.egg-info/SOURCES.txt +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx_python.egg-info/dependency_links.txt +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx_python.egg-info/requires.txt +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/agentx_python.egg-info/top_level.txt +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/setup.cfg +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/setup.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.4}/tests/test_integration.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentx-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
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
|
|
@@ -49,8 +49,12 @@ Dynamic: summary
|
|
|
49
49
|

|
|
50
50
|
|
|
51
51
|
[](https://pypi.org/project/agentx-python/)
|
|
52
|
+
[](https://pypi.org/project/agentx-python/)
|
|
53
|
+
[](LICENSE)
|
|
52
54
|
|
|
53
|
-
The official Python SDK for **[AgentX](https://
|
|
55
|
+
The official Python SDK for **[AgentX](https://app.agentx.so/)** — build, chat with, orchestrate, and trace AI agents in a few lines of code.
|
|
56
|
+
|
|
57
|
+
Also see [SDK Developer Docs](https://developers.agentx.so), [API Reference Docs](https://docs.agentx.so/reference)
|
|
54
58
|
|
|
55
59
|
---
|
|
56
60
|
|
|
@@ -64,8 +68,9 @@ The official Python SDK for **[AgentX](https://www.agentx.so/)** — build, chat
|
|
|
64
68
|
- [List agents](#list-agents)
|
|
65
69
|
- [Start a conversation](#start-a-conversation)
|
|
66
70
|
- [Chat (streaming and non-streaming)](#chat-streaming-and-non-streaming)
|
|
67
|
-
- [Workforce (multi-agent orchestration)](#workforce-multi-agent-orchestration)
|
|
68
|
-
- [
|
|
71
|
+
- [Workforce (multi-agent orchestration)](#workforce-multi-agent-orchestration) — teams of agents with a designated manager
|
|
72
|
+
- [Production tracing](#production-tracing) — record live agent runs from any framework
|
|
73
|
+
- [Custom agent evaluations](#custom-agent-evaluations) — LLM-as-a-judge, cosine / Jaccard similarity
|
|
69
74
|
- [Links](#links)
|
|
70
75
|
|
|
71
76
|
---
|
|
@@ -78,6 +83,7 @@ The official Python SDK for **[AgentX](https://www.agentx.so/)** — build, chat
|
|
|
78
83
|
- **Batteries included** — voice (ASR/TTS), image generation, document/CSV/Excel/OCR, RAG with built-in re-ranking.
|
|
79
84
|
- **MCP support** — connect any Model Context Protocol server.
|
|
80
85
|
- **Multi-agent orchestration** — workforces of agents with a designated manager, across LLM vendors.
|
|
86
|
+
- **Production tracing** — one decorator or context manager records every agent run (input, output, latency, tool calls, token usage) into your workspace, for any framework.
|
|
81
87
|
- **Agent Evaluations** — score any agent (LangChain, CrewAI, OpenAI, Anthropic, HTTP, …) with LLM-as-a-judge ratings plus optional cosine and Jaccard similarity metrics.
|
|
82
88
|
- **A2A** — Each agent can be published with agent-to-agent protocol compatible.
|
|
83
89
|
|
|
@@ -187,6 +193,58 @@ for chunk in workforce.chat_stream(conversation.id, "How can you help me with th
|
|
|
187
193
|
|
|
188
194
|
---
|
|
189
195
|
|
|
196
|
+
## Production tracing
|
|
197
|
+
|
|
198
|
+
Record live agent runs into your workspace with a single decorator or context manager — no changes to your agent's logic. Traces appear in the **Live Traces** tab and can be evaluated against your test datasets with [`tracer.evaluate_trace()`](TRACING.md#tracerevaluate_trace).
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from agentx import AgentX
|
|
202
|
+
|
|
203
|
+
client = AgentX.from_env()
|
|
204
|
+
tracer = client.tracer
|
|
205
|
+
|
|
206
|
+
@tracer.trace("customer-support-agent", framework="langchain", model="gpt-4o")
|
|
207
|
+
def handle_query(query: str) -> str:
|
|
208
|
+
return chain.invoke(query)
|
|
209
|
+
|
|
210
|
+
# Every call is automatically traced: input, output, latency, tool calls, token usage
|
|
211
|
+
handle_query("How do I reset my password?")
|
|
212
|
+
tracer.flush(timeout=10) # ensure delivery before the process exits
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Prefer full control over what gets captured? Use the context manager instead:
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
with tracer.trace("rag-agent", framework="langchain") as span:
|
|
219
|
+
span.input = {"query": query, "user_id": user_id}
|
|
220
|
+
|
|
221
|
+
kb_result = search_knowledge_base(query)
|
|
222
|
+
span.add_tool_call("search_knowledge_base", input=query, output=kb_result, latency_ms=190)
|
|
223
|
+
|
|
224
|
+
span.output = llm.invoke(f"Context: {kb_result}\n\nQuery: {query}")
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Framework integrations
|
|
228
|
+
|
|
229
|
+
Each integration auto-captures LLM calls, tool calls, and token usage — install the matching extra:
|
|
230
|
+
|
|
231
|
+
| Framework | Install | Integration |
|
|
232
|
+
| --------------------- | -------------------------------------------- | ------------------------ |
|
|
233
|
+
| LangChain | `pip install "agentx-python[langchain]"` | `AgentXCallbackHandler` |
|
|
234
|
+
| CrewAI | `pip install "agentx-python[crewai]"` | `AgentXCrewObserver` |
|
|
235
|
+
| OpenAI Agents SDK | `pip install "agentx-python[openai-agents]"` | `AgentXTracingProcessor` |
|
|
236
|
+
| Anthropic | `pip install "agentx-python[anthropic]"` | `patch_anthropic_client` |
|
|
237
|
+
| Google ADK | `pip install "agentx-python[google-adk]"` | `AgentXADKPlugin` |
|
|
238
|
+
| Google GenAI (Gemini) | `pip install "agentx-python[google-genai]"` | `patch_genai_client` |
|
|
239
|
+
|
|
240
|
+
Or plain Python — wrap any function with `@tracer.trace(...)` and it just works, no framework required.
|
|
241
|
+
|
|
242
|
+
Running specialist agents in parallel with a `ThreadPoolExecutor`? Wrap each worker body in `tracer.use_span(span)` so their steps land on the parent trace instead of becoming independent traces — see [TRACING.md](TRACING.md) for the full pattern.
|
|
243
|
+
|
|
244
|
+
See **[TRACING.md](TRACING.md)** for the complete guide — session grouping, error handling, async support, and the full API reference.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
190
248
|
## Custom agent evaluations
|
|
191
249
|
|
|
192
250
|
Evaluate **any** AI agent — LangChain, CrewAI, AutoGen, LlamaIndex, OpenAI, Anthropic, HTTP endpoints, or plain Python — using AgentX as the scoring and reporting backend. Includes optional **cosine** and **Jaccard** similarity metrics alongside LLM-graded ratings.
|
|
@@ -214,4 +272,5 @@ See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder,
|
|
|
214
272
|
- **Dashboard** — [app.agentx.so](https://app.agentx.so)
|
|
215
273
|
- **Website** — [agentx.so](https://www.agentx.so/)
|
|
216
274
|
- **PyPI** — [agentx-python](https://pypi.org/project/agentx-python/)
|
|
275
|
+
- **Tracing docs** — [TRACING.md](TRACING.md)
|
|
217
276
|
- **Evaluations docs** — [EVALUATIONS.md](EVALUATIONS.md)
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/agentx-python/)
|
|
4
|
+
[](https://pypi.org/project/agentx-python/)
|
|
5
|
+
[](LICENSE)
|
|
4
6
|
|
|
5
|
-
The official Python SDK for **[AgentX](https://
|
|
7
|
+
The official Python SDK for **[AgentX](https://app.agentx.so/)** — build, chat with, orchestrate, and trace AI agents in a few lines of code.
|
|
8
|
+
|
|
9
|
+
Also see [SDK Developer Docs](https://developers.agentx.so), [API Reference Docs](https://docs.agentx.so/reference)
|
|
6
10
|
|
|
7
11
|
---
|
|
8
12
|
|
|
@@ -16,8 +20,9 @@ The official Python SDK for **[AgentX](https://www.agentx.so/)** — build, chat
|
|
|
16
20
|
- [List agents](#list-agents)
|
|
17
21
|
- [Start a conversation](#start-a-conversation)
|
|
18
22
|
- [Chat (streaming and non-streaming)](#chat-streaming-and-non-streaming)
|
|
19
|
-
- [Workforce (multi-agent orchestration)](#workforce-multi-agent-orchestration)
|
|
20
|
-
- [
|
|
23
|
+
- [Workforce (multi-agent orchestration)](#workforce-multi-agent-orchestration) — teams of agents with a designated manager
|
|
24
|
+
- [Production tracing](#production-tracing) — record live agent runs from any framework
|
|
25
|
+
- [Custom agent evaluations](#custom-agent-evaluations) — LLM-as-a-judge, cosine / Jaccard similarity
|
|
21
26
|
- [Links](#links)
|
|
22
27
|
|
|
23
28
|
---
|
|
@@ -30,6 +35,7 @@ The official Python SDK for **[AgentX](https://www.agentx.so/)** — build, chat
|
|
|
30
35
|
- **Batteries included** — voice (ASR/TTS), image generation, document/CSV/Excel/OCR, RAG with built-in re-ranking.
|
|
31
36
|
- **MCP support** — connect any Model Context Protocol server.
|
|
32
37
|
- **Multi-agent orchestration** — workforces of agents with a designated manager, across LLM vendors.
|
|
38
|
+
- **Production tracing** — one decorator or context manager records every agent run (input, output, latency, tool calls, token usage) into your workspace, for any framework.
|
|
33
39
|
- **Agent Evaluations** — score any agent (LangChain, CrewAI, OpenAI, Anthropic, HTTP, …) with LLM-as-a-judge ratings plus optional cosine and Jaccard similarity metrics.
|
|
34
40
|
- **A2A** — Each agent can be published with agent-to-agent protocol compatible.
|
|
35
41
|
|
|
@@ -139,6 +145,58 @@ for chunk in workforce.chat_stream(conversation.id, "How can you help me with th
|
|
|
139
145
|
|
|
140
146
|
---
|
|
141
147
|
|
|
148
|
+
## Production tracing
|
|
149
|
+
|
|
150
|
+
Record live agent runs into your workspace with a single decorator or context manager — no changes to your agent's logic. Traces appear in the **Live Traces** tab and can be evaluated against your test datasets with [`tracer.evaluate_trace()`](TRACING.md#tracerevaluate_trace).
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from agentx import AgentX
|
|
154
|
+
|
|
155
|
+
client = AgentX.from_env()
|
|
156
|
+
tracer = client.tracer
|
|
157
|
+
|
|
158
|
+
@tracer.trace("customer-support-agent", framework="langchain", model="gpt-4o")
|
|
159
|
+
def handle_query(query: str) -> str:
|
|
160
|
+
return chain.invoke(query)
|
|
161
|
+
|
|
162
|
+
# Every call is automatically traced: input, output, latency, tool calls, token usage
|
|
163
|
+
handle_query("How do I reset my password?")
|
|
164
|
+
tracer.flush(timeout=10) # ensure delivery before the process exits
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Prefer full control over what gets captured? Use the context manager instead:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
with tracer.trace("rag-agent", framework="langchain") as span:
|
|
171
|
+
span.input = {"query": query, "user_id": user_id}
|
|
172
|
+
|
|
173
|
+
kb_result = search_knowledge_base(query)
|
|
174
|
+
span.add_tool_call("search_knowledge_base", input=query, output=kb_result, latency_ms=190)
|
|
175
|
+
|
|
176
|
+
span.output = llm.invoke(f"Context: {kb_result}\n\nQuery: {query}")
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Framework integrations
|
|
180
|
+
|
|
181
|
+
Each integration auto-captures LLM calls, tool calls, and token usage — install the matching extra:
|
|
182
|
+
|
|
183
|
+
| Framework | Install | Integration |
|
|
184
|
+
| --------------------- | -------------------------------------------- | ------------------------ |
|
|
185
|
+
| LangChain | `pip install "agentx-python[langchain]"` | `AgentXCallbackHandler` |
|
|
186
|
+
| CrewAI | `pip install "agentx-python[crewai]"` | `AgentXCrewObserver` |
|
|
187
|
+
| OpenAI Agents SDK | `pip install "agentx-python[openai-agents]"` | `AgentXTracingProcessor` |
|
|
188
|
+
| Anthropic | `pip install "agentx-python[anthropic]"` | `patch_anthropic_client` |
|
|
189
|
+
| Google ADK | `pip install "agentx-python[google-adk]"` | `AgentXADKPlugin` |
|
|
190
|
+
| Google GenAI (Gemini) | `pip install "agentx-python[google-genai]"` | `patch_genai_client` |
|
|
191
|
+
|
|
192
|
+
Or plain Python — wrap any function with `@tracer.trace(...)` and it just works, no framework required.
|
|
193
|
+
|
|
194
|
+
Running specialist agents in parallel with a `ThreadPoolExecutor`? Wrap each worker body in `tracer.use_span(span)` so their steps land on the parent trace instead of becoming independent traces — see [TRACING.md](TRACING.md) for the full pattern.
|
|
195
|
+
|
|
196
|
+
See **[TRACING.md](TRACING.md)** for the complete guide — session grouping, error handling, async support, and the full API reference.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
142
200
|
## Custom agent evaluations
|
|
143
201
|
|
|
144
202
|
Evaluate **any** AI agent — LangChain, CrewAI, AutoGen, LlamaIndex, OpenAI, Anthropic, HTTP endpoints, or plain Python — using AgentX as the scoring and reporting backend. Includes optional **cosine** and **Jaccard** similarity metrics alongside LLM-graded ratings.
|
|
@@ -166,4 +224,5 @@ See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder,
|
|
|
166
224
|
- **Dashboard** — [app.agentx.so](https://app.agentx.so)
|
|
167
225
|
- **Website** — [agentx.so](https://www.agentx.so/)
|
|
168
226
|
- **PyPI** — [agentx-python](https://pypi.org/project/agentx-python/)
|
|
227
|
+
- **Tracing docs** — [TRACING.md](TRACING.md)
|
|
169
228
|
- **Evaluations docs** — [EVALUATIONS.md](EVALUATIONS.md)
|
|
@@ -22,6 +22,34 @@ from agentx.tracing.tracer import Tracer, _safe_serialize
|
|
|
22
22
|
from agentx.integrations._perf import build_performance_summary
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def _extract_output_text(response: Any) -> Optional[str]:
|
|
26
|
+
"""
|
|
27
|
+
Extract the assistant's text reply from a Messages API response, falling
|
|
28
|
+
back to a description of any tool_use blocks when the response is a pure
|
|
29
|
+
tool call with no accompanying text.
|
|
30
|
+
"""
|
|
31
|
+
content = getattr(response, "content", None) if response is not None else None
|
|
32
|
+
if not content:
|
|
33
|
+
return None
|
|
34
|
+
texts = []
|
|
35
|
+
tool_calls = []
|
|
36
|
+
for block in content:
|
|
37
|
+
block_type = getattr(block, "type", None)
|
|
38
|
+
if block_type == "text":
|
|
39
|
+
text = getattr(block, "text", None)
|
|
40
|
+
if text:
|
|
41
|
+
texts.append(text)
|
|
42
|
+
elif block_type == "tool_use":
|
|
43
|
+
name = getattr(block, "name", "unknown")
|
|
44
|
+
tool_input = getattr(block, "input", None)
|
|
45
|
+
tool_calls.append(f"{name}({tool_input})")
|
|
46
|
+
if texts:
|
|
47
|
+
return "\n".join(texts)
|
|
48
|
+
if tool_calls:
|
|
49
|
+
return "[tool call] " + ", ".join(tool_calls)
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
25
53
|
def patch_anthropic_client(
|
|
26
54
|
client: Any,
|
|
27
55
|
tracer: Tracer,
|
|
@@ -77,10 +105,7 @@ def _patch_create(
|
|
|
77
105
|
input_tokens = None
|
|
78
106
|
output_tokens = None
|
|
79
107
|
if response is not None:
|
|
80
|
-
|
|
81
|
-
output = response.content[0].text if response.content else None
|
|
82
|
-
except Exception:
|
|
83
|
-
output = str(response)
|
|
108
|
+
output = _extract_output_text(response)
|
|
84
109
|
try:
|
|
85
110
|
usage = getattr(response, "usage", None)
|
|
86
111
|
if usage is not None:
|
|
@@ -172,7 +197,7 @@ def _patch_stream(
|
|
|
172
197
|
output_tokens = None
|
|
173
198
|
try:
|
|
174
199
|
final = ctx.get_final_message()
|
|
175
|
-
output = final
|
|
200
|
+
output = _extract_output_text(final)
|
|
176
201
|
usage = getattr(final, "usage", None)
|
|
177
202
|
if usage is not None:
|
|
178
203
|
input_tokens = getattr(usage, "input_tokens", None)
|
|
@@ -36,7 +36,11 @@ except ImportError as exc: # pragma: no cover
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
def _content_to_text(content: Any) -> Optional[str]:
|
|
39
|
-
"""
|
|
39
|
+
"""
|
|
40
|
+
Extract plain text from a google.genai types.Content object, falling back
|
|
41
|
+
to a description of any function_call parts when there's no text (Gemini
|
|
42
|
+
function calling — the model responded with a pure tool call).
|
|
43
|
+
"""
|
|
40
44
|
if content is None:
|
|
41
45
|
return None
|
|
42
46
|
if isinstance(content, str):
|
|
@@ -45,11 +49,22 @@ def _content_to_text(content: Any) -> Optional[str]:
|
|
|
45
49
|
if not parts:
|
|
46
50
|
return None
|
|
47
51
|
texts = []
|
|
52
|
+
function_calls = []
|
|
48
53
|
for part in parts:
|
|
49
54
|
text = getattr(part, "text", None)
|
|
50
55
|
if text and isinstance(text, str):
|
|
51
56
|
texts.append(text)
|
|
52
|
-
|
|
57
|
+
continue
|
|
58
|
+
fc = getattr(part, "function_call", None)
|
|
59
|
+
if fc is not None:
|
|
60
|
+
name = getattr(fc, "name", "unknown")
|
|
61
|
+
args = getattr(fc, "args", None)
|
|
62
|
+
function_calls.append(f"{name}({args})")
|
|
63
|
+
if texts:
|
|
64
|
+
return " ".join(texts)
|
|
65
|
+
if function_calls:
|
|
66
|
+
return "[tool call] " + ", ".join(function_calls)
|
|
67
|
+
return None
|
|
53
68
|
|
|
54
69
|
|
|
55
70
|
def _contents_to_text(contents: Any) -> Optional[str]:
|
|
@@ -16,7 +16,7 @@ Requires: ``pip install "agentx-python[google-genai]"``
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
import time
|
|
19
|
-
from typing import Any, Dict, Optional
|
|
19
|
+
from typing import Any, Dict, List, Optional
|
|
20
20
|
|
|
21
21
|
from agentx.tracing.tracer import Tracer, _safe_serialize
|
|
22
22
|
from agentx.integrations._perf import build_performance_summary
|
|
@@ -49,13 +49,18 @@ def patch_genai_client(
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def _extract_response_text(response: Any) -> Optional[str]:
|
|
52
|
-
"""
|
|
52
|
+
"""
|
|
53
|
+
Pull the generated text out of a GenerateContentResponse, falling back to
|
|
54
|
+
a description of any function_call parts when the response is a pure
|
|
55
|
+
tool call with no text (Gemini function calling).
|
|
56
|
+
"""
|
|
53
57
|
# Convenience .text property (available on non-streaming responses)
|
|
54
58
|
text = getattr(response, "text", None)
|
|
55
59
|
if text and isinstance(text, str):
|
|
56
60
|
return text
|
|
57
61
|
# Fallback: walk candidates → content → parts
|
|
58
62
|
candidates = getattr(response, "candidates", None) or []
|
|
63
|
+
function_calls: List[str] = []
|
|
59
64
|
for candidate in candidates:
|
|
60
65
|
content = getattr(candidate, "content", None)
|
|
61
66
|
parts = getattr(content, "parts", None) or []
|
|
@@ -63,6 +68,13 @@ def _extract_response_text(response: Any) -> Optional[str]:
|
|
|
63
68
|
t = getattr(part, "text", None)
|
|
64
69
|
if t and isinstance(t, str):
|
|
65
70
|
return t
|
|
71
|
+
fc = getattr(part, "function_call", None)
|
|
72
|
+
if fc is not None:
|
|
73
|
+
name = getattr(fc, "name", "unknown")
|
|
74
|
+
args = getattr(fc, "args", None)
|
|
75
|
+
function_calls.append(f"{name}({args})")
|
|
76
|
+
if function_calls:
|
|
77
|
+
return "[tool call] " + ", ".join(function_calls)
|
|
66
78
|
return None
|
|
67
79
|
|
|
68
80
|
|
|
@@ -177,9 +177,30 @@ def _extract_llm_input(
|
|
|
177
177
|
return None
|
|
178
178
|
|
|
179
179
|
|
|
180
|
+
def _describe_tool_calls(message: Any) -> Optional[str]:
|
|
181
|
+
"""
|
|
182
|
+
Format an AIMessage's ``tool_calls`` as a readable fallback for ``output``
|
|
183
|
+
when the model responded with a pure tool call and no text content.
|
|
184
|
+
"""
|
|
185
|
+
tool_calls = getattr(message, "tool_calls", None) if message is not None else None
|
|
186
|
+
if not tool_calls:
|
|
187
|
+
return None
|
|
188
|
+
parts = []
|
|
189
|
+
for tc in tool_calls:
|
|
190
|
+
if isinstance(tc, dict):
|
|
191
|
+
name = tc.get("name") or "unknown"
|
|
192
|
+
args = tc.get("args")
|
|
193
|
+
else:
|
|
194
|
+
name = getattr(tc, "name", None) or "unknown"
|
|
195
|
+
args = getattr(tc, "args", None)
|
|
196
|
+
parts.append(f"{name}({args})" if args is not None else f"{name}()")
|
|
197
|
+
return "[tool call] " + ", ".join(parts)
|
|
198
|
+
|
|
199
|
+
|
|
180
200
|
def _extract_llm_output(response: "LLMResult") -> Optional[str]:
|
|
181
201
|
"""Flatten on_llm_end's ``LLMResult`` (chat or completion generations) into one string."""
|
|
182
202
|
texts: List[str] = []
|
|
203
|
+
tool_call_fallbacks: List[str] = []
|
|
183
204
|
for gen_list in getattr(response, "generations", None) or []:
|
|
184
205
|
for gen in gen_list or []:
|
|
185
206
|
message = getattr(gen, "message", None)
|
|
@@ -196,7 +217,18 @@ def _extract_llm_output(response: "LLMResult") -> Optional[str]:
|
|
|
196
217
|
text = getattr(gen, "text", None)
|
|
197
218
|
if text:
|
|
198
219
|
texts.append(text)
|
|
199
|
-
|
|
220
|
+
continue
|
|
221
|
+
# No text content — the model likely responded with a pure tool
|
|
222
|
+
# call instead of commentary. Fall back to describing it so the
|
|
223
|
+
# step's output isn't silently omitted.
|
|
224
|
+
described = _describe_tool_calls(message)
|
|
225
|
+
if described:
|
|
226
|
+
tool_call_fallbacks.append(described)
|
|
227
|
+
if texts:
|
|
228
|
+
return "\n".join(texts)
|
|
229
|
+
if tool_call_fallbacks:
|
|
230
|
+
return "\n".join(tool_call_fallbacks)
|
|
231
|
+
return None
|
|
200
232
|
|
|
201
233
|
|
|
202
234
|
class AgentXCallbackHandler(BaseCallbackHandler):
|
|
@@ -4,6 +4,7 @@ import asyncio
|
|
|
4
4
|
import concurrent.futures
|
|
5
5
|
import functools
|
|
6
6
|
import inspect
|
|
7
|
+
import re
|
|
7
8
|
import threading
|
|
8
9
|
import time
|
|
9
10
|
from contextlib import contextmanager
|
|
@@ -20,6 +21,11 @@ from agentx.integrations._perf import (
|
|
|
20
21
|
|
|
21
22
|
F = TypeVar("F", bound=Callable[..., Any])
|
|
22
23
|
|
|
24
|
+
# Matches the generic "LLM Call N" step names integrations generate (e.g.
|
|
25
|
+
# langchain.py numbers steps within one top-level chain run). Used by
|
|
26
|
+
# _TraceSpan._merge_child_run to renumber them on merge — see there.
|
|
27
|
+
_LLM_CALL_NAME_RE = re.compile(r"^LLM Call \d+$")
|
|
28
|
+
|
|
23
29
|
|
|
24
30
|
def _safe_serialize(value: Any, depth: int = 0) -> Any:
|
|
25
31
|
"""Best-effort conversion to a JSON-safe structure, truncated to avoid huge payloads."""
|
|
@@ -208,6 +214,19 @@ class _TraceSpan:
|
|
|
208
214
|
becoming its own independent trace.
|
|
209
215
|
"""
|
|
210
216
|
with self._merge_lock:
|
|
217
|
+
if execution_steps:
|
|
218
|
+
# Step names like "LLM Call N" are numbered locally within
|
|
219
|
+
# whatever sub-run produced them (e.g. one LangChain chain
|
|
220
|
+
# invocation numbers its own calls 1, 2, 3...). When several
|
|
221
|
+
# sub-runs merge into this span — e.g. a sequence of
|
|
222
|
+
# single-call specialist chains all folded into one
|
|
223
|
+
# orchestrator span via use_span() — renumber so the merged
|
|
224
|
+
# trace doesn't end up with several "LLM Call 1" entries.
|
|
225
|
+
next_n = len(self._execution_steps) + 1
|
|
226
|
+
for offset, step in enumerate(execution_steps):
|
|
227
|
+
name = step.get("name")
|
|
228
|
+
if isinstance(name, str) and _LLM_CALL_NAME_RE.match(name):
|
|
229
|
+
step["name"] = f"LLM Call {next_n + offset}"
|
|
211
230
|
self._execution_steps.extend(execution_steps or [])
|
|
212
231
|
self.tool_calls.extend(tool_calls or [])
|
|
213
232
|
self._retrieval_steps.extend(retrieval_steps or [])
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "0.6.4"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentx-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
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
|
|
@@ -49,8 +49,12 @@ Dynamic: summary
|
|
|
49
49
|

|
|
50
50
|
|
|
51
51
|
[](https://pypi.org/project/agentx-python/)
|
|
52
|
+
[](https://pypi.org/project/agentx-python/)
|
|
53
|
+
[](LICENSE)
|
|
52
54
|
|
|
53
|
-
The official Python SDK for **[AgentX](https://
|
|
55
|
+
The official Python SDK for **[AgentX](https://app.agentx.so/)** — build, chat with, orchestrate, and trace AI agents in a few lines of code.
|
|
56
|
+
|
|
57
|
+
Also see [SDK Developer Docs](https://developers.agentx.so), [API Reference Docs](https://docs.agentx.so/reference)
|
|
54
58
|
|
|
55
59
|
---
|
|
56
60
|
|
|
@@ -64,8 +68,9 @@ The official Python SDK for **[AgentX](https://www.agentx.so/)** — build, chat
|
|
|
64
68
|
- [List agents](#list-agents)
|
|
65
69
|
- [Start a conversation](#start-a-conversation)
|
|
66
70
|
- [Chat (streaming and non-streaming)](#chat-streaming-and-non-streaming)
|
|
67
|
-
- [Workforce (multi-agent orchestration)](#workforce-multi-agent-orchestration)
|
|
68
|
-
- [
|
|
71
|
+
- [Workforce (multi-agent orchestration)](#workforce-multi-agent-orchestration) — teams of agents with a designated manager
|
|
72
|
+
- [Production tracing](#production-tracing) — record live agent runs from any framework
|
|
73
|
+
- [Custom agent evaluations](#custom-agent-evaluations) — LLM-as-a-judge, cosine / Jaccard similarity
|
|
69
74
|
- [Links](#links)
|
|
70
75
|
|
|
71
76
|
---
|
|
@@ -78,6 +83,7 @@ The official Python SDK for **[AgentX](https://www.agentx.so/)** — build, chat
|
|
|
78
83
|
- **Batteries included** — voice (ASR/TTS), image generation, document/CSV/Excel/OCR, RAG with built-in re-ranking.
|
|
79
84
|
- **MCP support** — connect any Model Context Protocol server.
|
|
80
85
|
- **Multi-agent orchestration** — workforces of agents with a designated manager, across LLM vendors.
|
|
86
|
+
- **Production tracing** — one decorator or context manager records every agent run (input, output, latency, tool calls, token usage) into your workspace, for any framework.
|
|
81
87
|
- **Agent Evaluations** — score any agent (LangChain, CrewAI, OpenAI, Anthropic, HTTP, …) with LLM-as-a-judge ratings plus optional cosine and Jaccard similarity metrics.
|
|
82
88
|
- **A2A** — Each agent can be published with agent-to-agent protocol compatible.
|
|
83
89
|
|
|
@@ -187,6 +193,58 @@ for chunk in workforce.chat_stream(conversation.id, "How can you help me with th
|
|
|
187
193
|
|
|
188
194
|
---
|
|
189
195
|
|
|
196
|
+
## Production tracing
|
|
197
|
+
|
|
198
|
+
Record live agent runs into your workspace with a single decorator or context manager — no changes to your agent's logic. Traces appear in the **Live Traces** tab and can be evaluated against your test datasets with [`tracer.evaluate_trace()`](TRACING.md#tracerevaluate_trace).
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from agentx import AgentX
|
|
202
|
+
|
|
203
|
+
client = AgentX.from_env()
|
|
204
|
+
tracer = client.tracer
|
|
205
|
+
|
|
206
|
+
@tracer.trace("customer-support-agent", framework="langchain", model="gpt-4o")
|
|
207
|
+
def handle_query(query: str) -> str:
|
|
208
|
+
return chain.invoke(query)
|
|
209
|
+
|
|
210
|
+
# Every call is automatically traced: input, output, latency, tool calls, token usage
|
|
211
|
+
handle_query("How do I reset my password?")
|
|
212
|
+
tracer.flush(timeout=10) # ensure delivery before the process exits
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Prefer full control over what gets captured? Use the context manager instead:
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
with tracer.trace("rag-agent", framework="langchain") as span:
|
|
219
|
+
span.input = {"query": query, "user_id": user_id}
|
|
220
|
+
|
|
221
|
+
kb_result = search_knowledge_base(query)
|
|
222
|
+
span.add_tool_call("search_knowledge_base", input=query, output=kb_result, latency_ms=190)
|
|
223
|
+
|
|
224
|
+
span.output = llm.invoke(f"Context: {kb_result}\n\nQuery: {query}")
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Framework integrations
|
|
228
|
+
|
|
229
|
+
Each integration auto-captures LLM calls, tool calls, and token usage — install the matching extra:
|
|
230
|
+
|
|
231
|
+
| Framework | Install | Integration |
|
|
232
|
+
| --------------------- | -------------------------------------------- | ------------------------ |
|
|
233
|
+
| LangChain | `pip install "agentx-python[langchain]"` | `AgentXCallbackHandler` |
|
|
234
|
+
| CrewAI | `pip install "agentx-python[crewai]"` | `AgentXCrewObserver` |
|
|
235
|
+
| OpenAI Agents SDK | `pip install "agentx-python[openai-agents]"` | `AgentXTracingProcessor` |
|
|
236
|
+
| Anthropic | `pip install "agentx-python[anthropic]"` | `patch_anthropic_client` |
|
|
237
|
+
| Google ADK | `pip install "agentx-python[google-adk]"` | `AgentXADKPlugin` |
|
|
238
|
+
| Google GenAI (Gemini) | `pip install "agentx-python[google-genai]"` | `patch_genai_client` |
|
|
239
|
+
|
|
240
|
+
Or plain Python — wrap any function with `@tracer.trace(...)` and it just works, no framework required.
|
|
241
|
+
|
|
242
|
+
Running specialist agents in parallel with a `ThreadPoolExecutor`? Wrap each worker body in `tracer.use_span(span)` so their steps land on the parent trace instead of becoming independent traces — see [TRACING.md](TRACING.md) for the full pattern.
|
|
243
|
+
|
|
244
|
+
See **[TRACING.md](TRACING.md)** for the complete guide — session grouping, error handling, async support, and the full API reference.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
190
248
|
## Custom agent evaluations
|
|
191
249
|
|
|
192
250
|
Evaluate **any** AI agent — LangChain, CrewAI, AutoGen, LlamaIndex, OpenAI, Anthropic, HTTP endpoints, or plain Python — using AgentX as the scoring and reporting backend. Includes optional **cosine** and **Jaccard** similarity metrics alongside LLM-graded ratings.
|
|
@@ -214,4 +272,5 @@ See **[EVALUATIONS.md](EVALUATIONS.md)** for the full guide — dataset builder,
|
|
|
214
272
|
- **Dashboard** — [app.agentx.so](https://app.agentx.so)
|
|
215
273
|
- **Website** — [agentx.so](https://www.agentx.so/)
|
|
216
274
|
- **PyPI** — [agentx-python](https://pypi.org/project/agentx-python/)
|
|
275
|
+
- **Tracing docs** — [TRACING.md](TRACING.md)
|
|
217
276
|
- **Evaluations docs** — [EVALUATIONS.md](EVALUATIONS.md)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = "0.6.2"
|
|
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
|