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.
Files changed (47) hide show
  1. {agentx_python-0.6.2/agentx_python.egg-info → agentx_python-0.6.3}/PKG-INFO +1 -1
  2. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/anthropic.py +30 -5
  3. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/google_adk.py +17 -2
  4. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/google_genai.py +14 -2
  5. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/langchain.py +33 -1
  6. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/tracing/tracer.py +19 -0
  7. agentx_python-0.6.3/agentx/version.py +1 -0
  8. {agentx_python-0.6.2 → agentx_python-0.6.3/agentx_python.egg-info}/PKG-INFO +1 -1
  9. agentx_python-0.6.2/agentx/version.py +0 -1
  10. {agentx_python-0.6.2 → agentx_python-0.6.3}/LICENSE +0 -0
  11. {agentx_python-0.6.2 → agentx_python-0.6.3}/README.md +0 -0
  12. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/__init__.py +0 -0
  13. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/agentx.py +0 -0
  14. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/__init__.py +0 -0
  15. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/_term.py +0 -0
  16. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/adapters/__init__.py +0 -0
  17. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/adapters/http_endpoint.py +0 -0
  18. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/adapters/precomputed.py +0 -0
  19. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/adapters/raw.py +0 -0
  20. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/client.py +0 -0
  21. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/datasets.py +0 -0
  22. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/models.py +0 -0
  23. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/redaction.py +0 -0
  24. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/reporting.py +0 -0
  25. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/results.py +0 -0
  26. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/runner.py +0 -0
  27. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/evaluations/tracing.py +0 -0
  28. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/exceptions.py +0 -0
  29. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/__init__.py +0 -0
  30. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/_perf.py +0 -0
  31. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/crewai.py +0 -0
  32. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/integrations/openai_agents.py +0 -0
  33. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/resources/__init__.py +0 -0
  34. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/resources/agent.py +0 -0
  35. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/resources/conversation.py +0 -0
  36. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/resources/workforce.py +0 -0
  37. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/tracing/__init__.py +0 -0
  38. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/tracing/ci_types.py +0 -0
  39. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/tracing/ingest_client.py +0 -0
  40. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx/util.py +0 -0
  41. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx_python.egg-info/SOURCES.txt +0 -0
  42. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx_python.egg-info/dependency_links.txt +0 -0
  43. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx_python.egg-info/requires.txt +0 -0
  44. {agentx_python-0.6.2 → agentx_python-0.6.3}/agentx_python.egg-info/top_level.txt +0 -0
  45. {agentx_python-0.6.2 → agentx_python-0.6.3}/setup.cfg +0 -0
  46. {agentx_python-0.6.2 → agentx_python-0.6.3}/setup.py +0 -0
  47. {agentx_python-0.6.2 → agentx_python-0.6.3}/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.2
3
+ Version: 0.6.3
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
@@ -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
- try:
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.content[0].text if final.content else None
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
- """Extract plain text from a google.genai types.Content object."""
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
- return " ".join(texts) if texts else None
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
- """Pull the generated text out of a GenerateContentResponse."""
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
- return "\n".join(texts) if texts else None
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentx-python
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: Official Python SDK for AgentX (https://www.agentx.so/)
5
5
  Home-page: https://github.com/AgentX-ai/AgentX-python
6
6
  Author: Robin Wang and AgentX Team
@@ -1 +0,0 @@
1
- VERSION = "0.6.2"
File without changes
File without changes
File without changes
File without changes