agentx-python 0.6.2__tar.gz → 0.6.3__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.3}/PKG-INFO +1 -1
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/anthropic.py +30 -5
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/google_adk.py +17 -2
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/google_genai.py +14 -2
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/langchain.py +33 -1
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/tracing/tracer.py +19 -0
- agentx_python-0.6.3/agentx/version.py +1 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3/agentx_python.egg-info}/PKG-INFO +1 -1
- agentx_python-0.6.2/agentx/version.py +0 -1
- {agentx_python-0.6.2 → agentx_python-0.6.3}/LICENSE +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/README.md +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/__init__.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/agentx.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/__init__.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/_term.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/adapters/__init__.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/adapters/http_endpoint.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/adapters/precomputed.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/adapters/raw.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/client.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/datasets.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/models.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/redaction.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/reporting.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/results.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/runner.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/tracing.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/exceptions.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/__init__.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/_perf.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/crewai.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/openai_agents.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/resources/__init__.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/resources/agent.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/resources/conversation.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/resources/workforce.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/tracing/__init__.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/tracing/ci_types.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/tracing/ingest_client.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/util.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx_python.egg-info/SOURCES.txt +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx_python.egg-info/dependency_links.txt +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx_python.egg-info/requires.txt +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx_python.egg-info/top_level.txt +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/setup.cfg +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/setup.py +0 -0
- {agentx_python-0.6.2 → agentx_python-0.6.3}/tests/test_integration.py +0 -0
|
@@ -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.3"
|
|
@@ -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
|
|
File without changes
|