agentx-python 0.4.13__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentx/integrations/__init__.py +2 -0
- agentx/integrations/google_adk.py +203 -0
- agentx/integrations/google_genai.py +152 -0
- agentx/integrations/langchain.py +156 -22
- agentx/integrations/openai_agents.py +141 -42
- agentx/version.py +1 -1
- {agentx_python-0.4.13.dist-info → agentx_python-0.5.1.dist-info}/METADATA +7 -1
- {agentx_python-0.4.13.dist-info → agentx_python-0.5.1.dist-info}/RECORD +11 -9
- {agentx_python-0.4.13.dist-info → agentx_python-0.5.1.dist-info}/WHEEL +0 -0
- {agentx_python-0.4.13.dist-info → agentx_python-0.5.1.dist-info}/licenses/LICENSE +0 -0
- {agentx_python-0.4.13.dist-info → agentx_python-0.5.1.dist-info}/top_level.txt +0 -0
agentx/integrations/__init__.py
CHANGED
|
@@ -5,3 +5,5 @@
|
|
|
5
5
|
# from agentx.integrations.crewai import AgentXCrewObserver
|
|
6
6
|
# from agentx.integrations.openai_agents import AgentXTracingProcessor
|
|
7
7
|
# from agentx.integrations.anthropic import patch_anthropic_client
|
|
8
|
+
# from agentx.integrations.google_adk import AgentXADKPlugin
|
|
9
|
+
# from agentx.integrations.google_genai import patch_genai_client
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Google ADK integration for AgentX production tracing.
|
|
3
|
+
|
|
4
|
+
Usage::
|
|
5
|
+
|
|
6
|
+
from agentx.integrations.google_adk import AgentXADKPlugin
|
|
7
|
+
from google.adk.runners import Runner
|
|
8
|
+
from google.adk.sessions import InMemorySessionService
|
|
9
|
+
|
|
10
|
+
plugin = AgentXADKPlugin(agentx.tracer, name="my-agent")
|
|
11
|
+
|
|
12
|
+
runner = Runner(
|
|
13
|
+
agent=agent,
|
|
14
|
+
app_name="my-app",
|
|
15
|
+
session_service=InMemorySessionService(),
|
|
16
|
+
plugins=[plugin],
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
Requires: ``pip install "agentx-python[google-adk]"``
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import time
|
|
24
|
+
from typing import Any, Dict, List, Optional
|
|
25
|
+
|
|
26
|
+
from agentx.tracing.tracer import Tracer, _safe_serialize
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from google.adk.plugins.base_plugin import BasePlugin
|
|
30
|
+
except ImportError as exc: # pragma: no cover
|
|
31
|
+
raise ImportError(
|
|
32
|
+
"google-adk is required for AgentXADKPlugin. "
|
|
33
|
+
"Install it with: pip install \"agentx-python[google-adk]\""
|
|
34
|
+
) from exc
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _content_to_text(content: Any) -> Optional[str]:
|
|
38
|
+
"""Extract plain text from a google.genai types.Content object."""
|
|
39
|
+
if content is None:
|
|
40
|
+
return None
|
|
41
|
+
if isinstance(content, str):
|
|
42
|
+
return content
|
|
43
|
+
parts = getattr(content, "parts", None)
|
|
44
|
+
if not parts:
|
|
45
|
+
return None
|
|
46
|
+
texts = []
|
|
47
|
+
for part in parts:
|
|
48
|
+
text = getattr(part, "text", None)
|
|
49
|
+
if text and isinstance(text, str):
|
|
50
|
+
texts.append(text)
|
|
51
|
+
return " ".join(texts) if texts else None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AgentXADKPlugin(BasePlugin):
|
|
55
|
+
"""
|
|
56
|
+
Google ADK plugin that sends one AgentX trace per runner invocation.
|
|
57
|
+
|
|
58
|
+
Captures input (user message), output (final model reply), model name,
|
|
59
|
+
tool calls, and latency via the ADK plugin callback hooks.
|
|
60
|
+
|
|
61
|
+
Register via the ``plugins`` list when constructing the ADK ``Runner``.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
tracer: Tracer,
|
|
67
|
+
name: str = "google-adk-agent",
|
|
68
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
69
|
+
session_id: Optional[str] = None,
|
|
70
|
+
) -> None:
|
|
71
|
+
super().__init__(name="agentx")
|
|
72
|
+
self._tracer = tracer
|
|
73
|
+
self._agent_name = name
|
|
74
|
+
self._metadata = metadata
|
|
75
|
+
self._session_id = session_id
|
|
76
|
+
# invocation_id → accumulated run state
|
|
77
|
+
self._runs: Dict[str, Dict[str, Any]] = {}
|
|
78
|
+
# id(tool_context) → start time float
|
|
79
|
+
self._tool_starts: Dict[int, float] = {}
|
|
80
|
+
|
|
81
|
+
# ------------------------------------------------------------------
|
|
82
|
+
# Run lifecycle
|
|
83
|
+
# ------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
async def before_run_callback(self, *, invocation_context: Any) -> None:
|
|
86
|
+
inv_id = invocation_context.invocation_id
|
|
87
|
+
agent_name = getattr(invocation_context.agent, "name", None) or self._agent_name
|
|
88
|
+
self._runs[inv_id] = {
|
|
89
|
+
"start": time.time(),
|
|
90
|
+
"name": agent_name,
|
|
91
|
+
"input": None,
|
|
92
|
+
"output": None,
|
|
93
|
+
"model": None,
|
|
94
|
+
"tool_calls": [],
|
|
95
|
+
"error": None,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async def on_user_message_callback(
|
|
99
|
+
self, *, invocation_context: Any, user_message: Any
|
|
100
|
+
) -> None:
|
|
101
|
+
inv_id = invocation_context.invocation_id
|
|
102
|
+
state = self._runs.get(inv_id)
|
|
103
|
+
if state is None:
|
|
104
|
+
return
|
|
105
|
+
text = _content_to_text(user_message)
|
|
106
|
+
if text and state["input"] is None:
|
|
107
|
+
state["input"] = text
|
|
108
|
+
|
|
109
|
+
async def after_run_callback(self, *, invocation_context: Any) -> None:
|
|
110
|
+
inv_id = invocation_context.invocation_id
|
|
111
|
+
state = self._runs.pop(inv_id, None)
|
|
112
|
+
if state is None:
|
|
113
|
+
return
|
|
114
|
+
latency_ms = int((time.time() - state["start"]) * 1000)
|
|
115
|
+
self._tracer._send(
|
|
116
|
+
name=state["name"],
|
|
117
|
+
input=state["input"],
|
|
118
|
+
output=state["output"],
|
|
119
|
+
latency_ms=latency_ms,
|
|
120
|
+
framework="google-adk",
|
|
121
|
+
model=state["model"],
|
|
122
|
+
tool_calls=state["tool_calls"] or None,
|
|
123
|
+
metadata=self._metadata,
|
|
124
|
+
session_id=self._session_id,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# ------------------------------------------------------------------
|
|
128
|
+
# Model callbacks — capture model name and output
|
|
129
|
+
# ------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
async def before_model_callback(
|
|
132
|
+
self, *, callback_context: Any, llm_request: Any
|
|
133
|
+
) -> None:
|
|
134
|
+
inv_id = callback_context.get_invocation_context().invocation_id
|
|
135
|
+
state = self._runs.get(inv_id)
|
|
136
|
+
if state and not state["model"] and llm_request.model:
|
|
137
|
+
state["model"] = str(llm_request.model)
|
|
138
|
+
|
|
139
|
+
async def after_model_callback(
|
|
140
|
+
self, *, callback_context: Any, llm_response: Any
|
|
141
|
+
) -> None:
|
|
142
|
+
inv_id = callback_context.get_invocation_context().invocation_id
|
|
143
|
+
state = self._runs.get(inv_id)
|
|
144
|
+
if state is None:
|
|
145
|
+
return
|
|
146
|
+
content = getattr(llm_response, "content", None)
|
|
147
|
+
text = _content_to_text(content)
|
|
148
|
+
# Keep updating — the last non-empty model reply is the final answer
|
|
149
|
+
if text:
|
|
150
|
+
state["output"] = text
|
|
151
|
+
|
|
152
|
+
# ------------------------------------------------------------------
|
|
153
|
+
# Tool callbacks
|
|
154
|
+
# ------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
async def before_tool_callback(
|
|
157
|
+
self, *, tool: Any, tool_args: Dict[str, Any], tool_context: Any
|
|
158
|
+
) -> None:
|
|
159
|
+
self._tool_starts[id(tool_context)] = time.time()
|
|
160
|
+
|
|
161
|
+
async def after_tool_callback(
|
|
162
|
+
self,
|
|
163
|
+
*,
|
|
164
|
+
tool: Any,
|
|
165
|
+
tool_args: Dict[str, Any],
|
|
166
|
+
tool_context: Any,
|
|
167
|
+
result: Dict[str, Any],
|
|
168
|
+
) -> None:
|
|
169
|
+
inv_id = tool_context.get_invocation_context().invocation_id
|
|
170
|
+
state = self._runs.get(inv_id)
|
|
171
|
+
if state is None:
|
|
172
|
+
return
|
|
173
|
+
start = self._tool_starts.pop(id(tool_context), None)
|
|
174
|
+
tool_call: Dict[str, Any] = {
|
|
175
|
+
"name": getattr(tool, "name", "unknown"),
|
|
176
|
+
"input": _safe_serialize(tool_args),
|
|
177
|
+
"output": str(result)[:500] if result is not None else None,
|
|
178
|
+
}
|
|
179
|
+
if start is not None:
|
|
180
|
+
tool_call["latency_ms"] = max(0, int((time.time() - start) * 1000))
|
|
181
|
+
state["tool_calls"].append(tool_call)
|
|
182
|
+
|
|
183
|
+
async def on_tool_error_callback(
|
|
184
|
+
self,
|
|
185
|
+
*,
|
|
186
|
+
tool: Any,
|
|
187
|
+
tool_args: Dict[str, Any],
|
|
188
|
+
tool_context: Any,
|
|
189
|
+
error: Exception,
|
|
190
|
+
) -> None:
|
|
191
|
+
inv_id = tool_context.get_invocation_context().invocation_id
|
|
192
|
+
state = self._runs.get(inv_id)
|
|
193
|
+
if state is None:
|
|
194
|
+
return
|
|
195
|
+
start = self._tool_starts.pop(id(tool_context), None)
|
|
196
|
+
tool_call: Dict[str, Any] = {
|
|
197
|
+
"name": getattr(tool, "name", "unknown"),
|
|
198
|
+
"input": _safe_serialize(tool_args),
|
|
199
|
+
"output": f"ERROR: {error}",
|
|
200
|
+
}
|
|
201
|
+
if start is not None:
|
|
202
|
+
tool_call["latency_ms"] = max(0, int((time.time() - start) * 1000))
|
|
203
|
+
state["tool_calls"].append(tool_call)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Google Gen AI SDK integration for AgentX production tracing.
|
|
3
|
+
|
|
4
|
+
Usage::
|
|
5
|
+
|
|
6
|
+
from agentx.integrations.google_genai import patch_genai_client
|
|
7
|
+
from google import genai
|
|
8
|
+
|
|
9
|
+
client = genai.Client(api_key="GEMINI_API_KEY")
|
|
10
|
+
patch_genai_client(client, agentx.tracer, name="gemini-agent")
|
|
11
|
+
|
|
12
|
+
# All subsequent client.models.generate_content() calls are now traced.
|
|
13
|
+
|
|
14
|
+
Requires: ``pip install "agentx-python[google-genai]"``
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import time
|
|
19
|
+
from typing import Any, Dict, Optional
|
|
20
|
+
|
|
21
|
+
from agentx.tracing.tracer import Tracer, _safe_serialize
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def patch_genai_client(
|
|
25
|
+
client: Any,
|
|
26
|
+
tracer: Tracer,
|
|
27
|
+
name: str = "gemini-agent",
|
|
28
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
29
|
+
session_id: Optional[str] = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Monkey-patch ``client.models.generate_content`` (and
|
|
33
|
+
``client.models.generate_content_stream`` if present) to automatically
|
|
34
|
+
send a trace for every call.
|
|
35
|
+
|
|
36
|
+
The original methods are still called and their return values are passed
|
|
37
|
+
through unchanged — nothing in the caller needs to change.
|
|
38
|
+
|
|
39
|
+
Calling this function on an already-patched client is a no-op.
|
|
40
|
+
"""
|
|
41
|
+
models = getattr(client, "models", None)
|
|
42
|
+
if models is None:
|
|
43
|
+
raise ValueError("Provided client does not have a .models attribute")
|
|
44
|
+
|
|
45
|
+
_patch_generate_content(models, tracer, name, metadata, session_id)
|
|
46
|
+
if hasattr(models, "generate_content_stream"):
|
|
47
|
+
_patch_generate_content_stream(models, tracer, name, metadata, session_id)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _extract_response_text(response: Any) -> Optional[str]:
|
|
51
|
+
"""Pull the generated text out of a GenerateContentResponse."""
|
|
52
|
+
# Convenience .text property (available on non-streaming responses)
|
|
53
|
+
text = getattr(response, "text", None)
|
|
54
|
+
if text and isinstance(text, str):
|
|
55
|
+
return text
|
|
56
|
+
# Fallback: walk candidates → content → parts
|
|
57
|
+
candidates = getattr(response, "candidates", None) or []
|
|
58
|
+
for candidate in candidates:
|
|
59
|
+
content = getattr(candidate, "content", None)
|
|
60
|
+
parts = getattr(content, "parts", None) or []
|
|
61
|
+
for part in parts:
|
|
62
|
+
t = getattr(part, "text", None)
|
|
63
|
+
if t and isinstance(t, str):
|
|
64
|
+
return t
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _patch_generate_content(
|
|
69
|
+
models: Any,
|
|
70
|
+
tracer: Tracer,
|
|
71
|
+
name: str,
|
|
72
|
+
metadata: Optional[Dict[str, Any]],
|
|
73
|
+
session_id: Optional[str],
|
|
74
|
+
) -> None:
|
|
75
|
+
original = models.generate_content
|
|
76
|
+
if getattr(original, "_agentx_patched", False):
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
def patched(*args, **kwargs):
|
|
80
|
+
start = time.time()
|
|
81
|
+
error: Optional[str] = None
|
|
82
|
+
response = None
|
|
83
|
+
try:
|
|
84
|
+
response = original(*args, **kwargs)
|
|
85
|
+
return response
|
|
86
|
+
except Exception as exc:
|
|
87
|
+
error = str(exc)
|
|
88
|
+
raise
|
|
89
|
+
finally:
|
|
90
|
+
latency_ms = int((time.time() - start) * 1000)
|
|
91
|
+
model = kwargs.get("model") or (args[0] if args else None)
|
|
92
|
+
contents = kwargs.get("contents") or (args[1] if len(args) > 1 else None)
|
|
93
|
+
output = _extract_response_text(response) if response is not None else None
|
|
94
|
+
tracer._send(
|
|
95
|
+
name=name,
|
|
96
|
+
input=contents if isinstance(contents, str) else _safe_serialize(contents),
|
|
97
|
+
output=output,
|
|
98
|
+
latency_ms=latency_ms,
|
|
99
|
+
error=error,
|
|
100
|
+
framework="google-genai",
|
|
101
|
+
model=str(model) if model else None,
|
|
102
|
+
metadata=metadata,
|
|
103
|
+
session_id=session_id,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
patched._agentx_patched = True
|
|
107
|
+
models.generate_content = patched
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _patch_generate_content_stream(
|
|
111
|
+
models: Any,
|
|
112
|
+
tracer: Tracer,
|
|
113
|
+
name: str,
|
|
114
|
+
metadata: Optional[Dict[str, Any]],
|
|
115
|
+
session_id: Optional[str],
|
|
116
|
+
) -> None:
|
|
117
|
+
original_stream = models.generate_content_stream
|
|
118
|
+
if getattr(original_stream, "_agentx_patched", False):
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
def patched_stream(*args, **kwargs):
|
|
122
|
+
start = time.time()
|
|
123
|
+
accumulated_text: list[str] = []
|
|
124
|
+
error: Optional[str] = None
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
for chunk in original_stream(*args, **kwargs):
|
|
128
|
+
text = getattr(chunk, "text", None)
|
|
129
|
+
if text:
|
|
130
|
+
accumulated_text.append(text)
|
|
131
|
+
yield chunk
|
|
132
|
+
except Exception as exc:
|
|
133
|
+
error = str(exc)
|
|
134
|
+
raise
|
|
135
|
+
finally:
|
|
136
|
+
latency_ms = int((time.time() - start) * 1000)
|
|
137
|
+
model = kwargs.get("model") or (args[0] if args else None)
|
|
138
|
+
contents = kwargs.get("contents") or (args[1] if len(args) > 1 else None)
|
|
139
|
+
tracer._send(
|
|
140
|
+
name=name,
|
|
141
|
+
input=contents if isinstance(contents, str) else _safe_serialize(contents),
|
|
142
|
+
output="".join(accumulated_text) or None,
|
|
143
|
+
latency_ms=latency_ms,
|
|
144
|
+
error=error,
|
|
145
|
+
framework="google-genai",
|
|
146
|
+
model=str(model) if model else None,
|
|
147
|
+
metadata=metadata,
|
|
148
|
+
session_id=session_id,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
patched_stream._agentx_patched = True
|
|
152
|
+
models.generate_content_stream = patched_stream
|
agentx/integrations/langchain.py
CHANGED
|
@@ -10,10 +10,13 @@ Usage::
|
|
|
10
10
|
# LCEL chain
|
|
11
11
|
chain.invoke({"query": q}, config={"callbacks": [handler]})
|
|
12
12
|
|
|
13
|
+
# LangGraph agent (create_agent / create_react_agent)
|
|
14
|
+
agent.invoke({"messages": [...]}, config={"callbacks": [handler]})
|
|
15
|
+
|
|
13
16
|
# AgentExecutor
|
|
14
17
|
agent.invoke({"input": q}, config={"callbacks": [handler]})
|
|
15
18
|
|
|
16
|
-
Requires: ``pip install agentx[langchain]``
|
|
19
|
+
Requires: ``pip install "agentx-python[langchain]"``
|
|
17
20
|
"""
|
|
18
21
|
from __future__ import annotations
|
|
19
22
|
|
|
@@ -29,14 +32,132 @@ try:
|
|
|
29
32
|
except ImportError as exc: # pragma: no cover
|
|
30
33
|
raise ImportError(
|
|
31
34
|
"langchain-core is required for AgentXCallbackHandler. "
|
|
32
|
-
"Install it with: pip install agentx[langchain]"
|
|
35
|
+
"Install it with: pip install \"agentx-python[langchain]\""
|
|
33
36
|
) from exc
|
|
34
37
|
|
|
35
38
|
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# Output extraction helpers
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
def _extract_output(outputs: Any) -> Any:
|
|
44
|
+
"""
|
|
45
|
+
Extract a clean, human-readable output from a chain's return value.
|
|
46
|
+
|
|
47
|
+
- LangGraph agents return {"messages": [HumanMessage, ..., AIMessage(final)]}
|
|
48
|
+
→ extract the last AI message's text content
|
|
49
|
+
- AgentExecutor returns {"output": "..."}
|
|
50
|
+
- LCEL chains return {"text": "..."} or a plain string
|
|
51
|
+
"""
|
|
52
|
+
if not isinstance(outputs, dict):
|
|
53
|
+
return _safe_serialize(outputs)
|
|
54
|
+
|
|
55
|
+
# LangGraph: {"messages": [...]}
|
|
56
|
+
messages = outputs.get("messages")
|
|
57
|
+
if isinstance(messages, list) and messages:
|
|
58
|
+
for msg in reversed(messages):
|
|
59
|
+
msg_type = getattr(msg, "type", None) or getattr(msg, "role", None)
|
|
60
|
+
if msg_type in ("ai", "assistant"):
|
|
61
|
+
content = getattr(msg, "content", None)
|
|
62
|
+
if content and isinstance(content, str) and content.strip():
|
|
63
|
+
return content
|
|
64
|
+
if isinstance(content, list):
|
|
65
|
+
# Multi-part content blocks
|
|
66
|
+
texts = [p.get("text", "") for p in content if isinstance(p, dict) and p.get("type") == "text"]
|
|
67
|
+
joined = " ".join(t for t in texts if t).strip()
|
|
68
|
+
if joined:
|
|
69
|
+
return joined
|
|
70
|
+
|
|
71
|
+
# Standard chain / AgentExecutor
|
|
72
|
+
for key in ("output", "text", "answer", "result", "response"):
|
|
73
|
+
val = outputs.get(key)
|
|
74
|
+
if val and isinstance(val, str):
|
|
75
|
+
return val
|
|
76
|
+
|
|
77
|
+
return _safe_serialize(outputs)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _extract_input(inputs: Any) -> Any:
|
|
81
|
+
"""
|
|
82
|
+
Extract a clean input from a chain's input dict.
|
|
83
|
+
|
|
84
|
+
- LangGraph: {"messages": [HumanMessage(...)]} → first human message text
|
|
85
|
+
- AgentExecutor: {"input": "..."} → the string
|
|
86
|
+
- LCEL: {"query": "...", "question": "..."} → the string value
|
|
87
|
+
"""
|
|
88
|
+
if not isinstance(inputs, dict):
|
|
89
|
+
return _safe_serialize(inputs)
|
|
90
|
+
|
|
91
|
+
# LangGraph: {"messages": [...]}
|
|
92
|
+
messages = inputs.get("messages")
|
|
93
|
+
if isinstance(messages, list) and messages:
|
|
94
|
+
for msg in messages:
|
|
95
|
+
msg_type = getattr(msg, "type", None) or getattr(msg, "role", None)
|
|
96
|
+
if msg_type in ("human", "user"):
|
|
97
|
+
content = getattr(msg, "content", None)
|
|
98
|
+
if isinstance(content, str):
|
|
99
|
+
return content
|
|
100
|
+
# Fallback: first message regardless of type
|
|
101
|
+
first = messages[0]
|
|
102
|
+
content = getattr(first, "content", None)
|
|
103
|
+
if isinstance(content, str):
|
|
104
|
+
return content
|
|
105
|
+
|
|
106
|
+
# Standard
|
|
107
|
+
for key in ("input", "query", "question", "human_input"):
|
|
108
|
+
val = inputs.get(key)
|
|
109
|
+
if val and isinstance(val, str):
|
|
110
|
+
return val
|
|
111
|
+
|
|
112
|
+
return _safe_serialize(inputs)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _extract_tool_calls_from_messages(outputs: Any) -> List[Dict[str, Any]]:
|
|
116
|
+
"""
|
|
117
|
+
Fallback: extract tool calls directly from the message history when
|
|
118
|
+
on_tool_end callbacks were not linked to the top-level run.
|
|
119
|
+
"""
|
|
120
|
+
if not isinstance(outputs, dict):
|
|
121
|
+
return []
|
|
122
|
+
|
|
123
|
+
messages = outputs.get("messages")
|
|
124
|
+
if not isinstance(messages, list):
|
|
125
|
+
return []
|
|
126
|
+
|
|
127
|
+
# Build map of tool_call_id → result from ToolMessages
|
|
128
|
+
results: Dict[str, str] = {}
|
|
129
|
+
for msg in messages:
|
|
130
|
+
if getattr(msg, "type", None) == "tool":
|
|
131
|
+
call_id = getattr(msg, "tool_call_id", None)
|
|
132
|
+
content = getattr(msg, "content", "")
|
|
133
|
+
if call_id:
|
|
134
|
+
results[call_id] = str(content)
|
|
135
|
+
|
|
136
|
+
# Extract tool calls from AIMessages
|
|
137
|
+
tool_calls: List[Dict[str, Any]] = []
|
|
138
|
+
for msg in messages:
|
|
139
|
+
if getattr(msg, "type", None) != "ai":
|
|
140
|
+
continue
|
|
141
|
+
for tc in getattr(msg, "tool_calls", []) or []:
|
|
142
|
+
if not isinstance(tc, dict):
|
|
143
|
+
continue
|
|
144
|
+
call_id = tc.get("id", "")
|
|
145
|
+
tool_calls.append({
|
|
146
|
+
"name": tc.get("name", "unknown"),
|
|
147
|
+
"input": str(tc.get("args", "")),
|
|
148
|
+
"output": results.get(call_id, ""),
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
return tool_calls
|
|
152
|
+
|
|
153
|
+
|
|
36
154
|
class AgentXCallbackHandler(BaseCallbackHandler):
|
|
37
155
|
"""
|
|
38
156
|
LangChain callback handler that captures the top-level chain run and all
|
|
39
157
|
nested tool calls, then sends one trace per top-level chain invocation.
|
|
158
|
+
|
|
159
|
+
Compatible with AgentExecutor, LCEL chains, and LangGraph agents
|
|
160
|
+
(``create_agent``, ``create_react_agent``).
|
|
40
161
|
"""
|
|
41
162
|
|
|
42
163
|
def __init__(
|
|
@@ -52,10 +173,10 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
52
173
|
self._metadata = metadata
|
|
53
174
|
self._session_id = session_id
|
|
54
175
|
|
|
55
|
-
# Keyed by run_id (UUID) → state dict
|
|
56
176
|
self._runs: Dict[UUID, Dict[str, Any]] = {}
|
|
57
|
-
# Track which run_ids are top-level (no parent)
|
|
58
177
|
self._top_level: Dict[UUID, bool] = {}
|
|
178
|
+
# Full parent-chain map so _find_top_ancestor can walk arbitrary depth
|
|
179
|
+
self._parents: Dict[UUID, Optional[UUID]] = {}
|
|
59
180
|
|
|
60
181
|
# ------------------------------------------------------------------
|
|
61
182
|
# Chain lifecycle
|
|
@@ -70,12 +191,13 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
70
191
|
parent_run_id: Optional[UUID] = None,
|
|
71
192
|
**kwargs,
|
|
72
193
|
) -> None:
|
|
194
|
+
self._parents[run_id] = parent_run_id
|
|
73
195
|
is_top = parent_run_id is None
|
|
74
196
|
self._top_level[run_id] = is_top
|
|
75
197
|
if is_top:
|
|
76
198
|
self._runs[run_id] = {
|
|
77
199
|
"start": time.time(),
|
|
78
|
-
"input":
|
|
200
|
+
"input": _extract_input(inputs),
|
|
79
201
|
"tool_calls": [],
|
|
80
202
|
"model": None,
|
|
81
203
|
}
|
|
@@ -94,18 +216,22 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
94
216
|
if state is None:
|
|
95
217
|
return
|
|
96
218
|
latency_ms = int((time.time() - state["start"]) * 1000)
|
|
219
|
+
output = _extract_output(outputs)
|
|
220
|
+
# If callbacks missed tool calls (deep nesting), extract from message history
|
|
221
|
+
tool_calls = state["tool_calls"] or _extract_tool_calls_from_messages(outputs)
|
|
97
222
|
self._tracer._send(
|
|
98
223
|
name=self._name,
|
|
99
224
|
input=state["input"],
|
|
100
|
-
output=
|
|
225
|
+
output=output,
|
|
101
226
|
latency_ms=latency_ms,
|
|
102
227
|
framework="langchain",
|
|
103
228
|
model=state.get("model"),
|
|
104
|
-
tool_calls=
|
|
229
|
+
tool_calls=tool_calls or None,
|
|
105
230
|
metadata=self._metadata,
|
|
106
231
|
session_id=self._session_id,
|
|
107
232
|
)
|
|
108
233
|
self._top_level.pop(run_id, None)
|
|
234
|
+
self._parents.pop(run_id, None)
|
|
109
235
|
|
|
110
236
|
def on_chain_error(
|
|
111
237
|
self,
|
|
@@ -133,9 +259,10 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
133
259
|
session_id=self._session_id,
|
|
134
260
|
)
|
|
135
261
|
self._top_level.pop(run_id, None)
|
|
262
|
+
self._parents.pop(run_id, None)
|
|
136
263
|
|
|
137
264
|
# ------------------------------------------------------------------
|
|
138
|
-
# LLM lifecycle
|
|
265
|
+
# LLM lifecycle
|
|
139
266
|
# ------------------------------------------------------------------
|
|
140
267
|
|
|
141
268
|
def on_llm_start(
|
|
@@ -147,18 +274,21 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
147
274
|
parent_run_id: Optional[UUID] = None,
|
|
148
275
|
**kwargs,
|
|
149
276
|
) -> None:
|
|
150
|
-
|
|
277
|
+
self._parents[run_id] = parent_run_id
|
|
151
278
|
self._runs.setdefault(run_id, {})["llm_start"] = time.time()
|
|
152
279
|
|
|
153
|
-
# Propagate model name to the top-level run
|
|
154
280
|
top = self._find_top_ancestor(parent_run_id)
|
|
155
281
|
if top and not self._runs[top].get("model"):
|
|
282
|
+
# Check all common field locations across langchain versions
|
|
283
|
+
kw = serialized.get("kwargs", {})
|
|
156
284
|
model = (
|
|
157
|
-
|
|
158
|
-
or
|
|
159
|
-
or
|
|
285
|
+
kw.get("model_name")
|
|
286
|
+
or kw.get("model")
|
|
287
|
+
or kwargs.get("invocation_params", {}).get("model")
|
|
288
|
+
or kwargs.get("invocation_params", {}).get("model_name")
|
|
289
|
+
or serialized.get("name")
|
|
160
290
|
)
|
|
161
|
-
if model:
|
|
291
|
+
if model and model not in ("None", "none"):
|
|
162
292
|
self._runs[top]["model"] = str(model)
|
|
163
293
|
|
|
164
294
|
def on_llm_end(
|
|
@@ -170,6 +300,7 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
170
300
|
**kwargs,
|
|
171
301
|
) -> None:
|
|
172
302
|
self._runs.pop(run_id, None)
|
|
303
|
+
self._parents.pop(run_id, None)
|
|
173
304
|
|
|
174
305
|
# ------------------------------------------------------------------
|
|
175
306
|
# Tool lifecycle
|
|
@@ -184,6 +315,7 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
184
315
|
parent_run_id: Optional[UUID] = None,
|
|
185
316
|
**kwargs,
|
|
186
317
|
) -> None:
|
|
318
|
+
self._parents[run_id] = parent_run_id
|
|
187
319
|
self._runs[run_id] = {
|
|
188
320
|
"tool_name": serialized.get("name", "unknown"),
|
|
189
321
|
"tool_input": input_str,
|
|
@@ -199,6 +331,7 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
199
331
|
**kwargs,
|
|
200
332
|
) -> None:
|
|
201
333
|
state = self._runs.pop(run_id, None)
|
|
334
|
+
self._parents.pop(run_id, None)
|
|
202
335
|
if state is None:
|
|
203
336
|
return
|
|
204
337
|
latency_ms = int((time.time() - state["start"]) * 1000)
|
|
@@ -221,6 +354,7 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
221
354
|
**kwargs,
|
|
222
355
|
) -> None:
|
|
223
356
|
state = self._runs.pop(run_id, None)
|
|
357
|
+
self._parents.pop(run_id, None)
|
|
224
358
|
if state is None:
|
|
225
359
|
return
|
|
226
360
|
tool_call = {
|
|
@@ -237,13 +371,13 @@ class AgentXCallbackHandler(BaseCallbackHandler):
|
|
|
237
371
|
# Helpers
|
|
238
372
|
# ------------------------------------------------------------------
|
|
239
373
|
|
|
240
|
-
def _find_top_ancestor(self,
|
|
241
|
-
"""Walk
|
|
242
|
-
current =
|
|
243
|
-
|
|
374
|
+
def _find_top_ancestor(self, start: Optional[UUID]) -> Optional[UUID]:
|
|
375
|
+
"""Walk the full parent chain to find the top-level run_id."""
|
|
376
|
+
current = start
|
|
377
|
+
seen: set = set()
|
|
378
|
+
while current is not None and current not in seen:
|
|
379
|
+
seen.add(current)
|
|
244
380
|
if self._top_level.get(current):
|
|
245
381
|
return current
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
break
|
|
249
|
-
return current if current in self._runs else None
|
|
382
|
+
current = self._parents.get(current)
|
|
383
|
+
return None
|
|
@@ -11,22 +11,111 @@ Usage::
|
|
|
11
11
|
from agents import add_trace_processor
|
|
12
12
|
add_trace_processor(processor)
|
|
13
13
|
|
|
14
|
-
Requires: ``pip install agentx[openai-agents]``
|
|
14
|
+
Requires: ``pip install "agentx-python[openai-agents]"``
|
|
15
15
|
"""
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
import time
|
|
19
|
-
from
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
from typing import Any, Dict, List, Optional
|
|
20
21
|
|
|
21
22
|
from agentx.tracing.tracer import Tracer, _safe_serialize
|
|
22
23
|
|
|
23
24
|
|
|
25
|
+
def _iso_to_ts(iso: Optional[str]) -> Optional[float]:
|
|
26
|
+
"""Parse an ISO-8601 timestamp string to a Unix timestamp float."""
|
|
27
|
+
if not iso:
|
|
28
|
+
return None
|
|
29
|
+
try:
|
|
30
|
+
return datetime.fromisoformat(iso.replace("Z", "+00:00")).timestamp()
|
|
31
|
+
except Exception:
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _span_latency_ms(span: Any) -> Optional[int]:
|
|
36
|
+
"""Return span duration in ms from ISO started_at / ended_at strings."""
|
|
37
|
+
t0 = _iso_to_ts(getattr(span, "started_at", None))
|
|
38
|
+
t1 = _iso_to_ts(getattr(span, "ended_at", None))
|
|
39
|
+
if t0 is not None and t1 is not None:
|
|
40
|
+
return max(0, int((t1 - t0) * 1000))
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _extract_text(output: Any) -> Optional[str]:
|
|
45
|
+
"""
|
|
46
|
+
Best-effort extraction of a human-readable string from a GenerationSpanData
|
|
47
|
+
or ResponseSpanData output, which can be a list of message dicts in either
|
|
48
|
+
the Chat Completions or Responses API format.
|
|
49
|
+
"""
|
|
50
|
+
if output is None:
|
|
51
|
+
return None
|
|
52
|
+
if isinstance(output, str):
|
|
53
|
+
return output
|
|
54
|
+
if not isinstance(output, (list, tuple)):
|
|
55
|
+
return _safe_serialize(output)
|
|
56
|
+
|
|
57
|
+
for item in reversed(output):
|
|
58
|
+
if not isinstance(item, dict):
|
|
59
|
+
# Could be a pydantic model — try .text or .content
|
|
60
|
+
text = getattr(item, "text", None) or getattr(item, "content", None)
|
|
61
|
+
if text and isinstance(text, str):
|
|
62
|
+
return text
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
# Responses API: {"type": "message", "content": [{"type": "output_text", "text": "..."}]}
|
|
66
|
+
if item.get("type") == "message":
|
|
67
|
+
content = item.get("content", [])
|
|
68
|
+
if isinstance(content, list):
|
|
69
|
+
for part in content:
|
|
70
|
+
if isinstance(part, dict) and part.get("type") == "output_text":
|
|
71
|
+
return part.get("text")
|
|
72
|
+
|
|
73
|
+
# Chat Completions API: {"role": "assistant", "content": "..."}
|
|
74
|
+
role = item.get("role", "")
|
|
75
|
+
if role == "assistant":
|
|
76
|
+
content = item.get("content")
|
|
77
|
+
if isinstance(content, str):
|
|
78
|
+
return content
|
|
79
|
+
if isinstance(content, list):
|
|
80
|
+
for part in content:
|
|
81
|
+
if isinstance(part, dict) and part.get("type") == "text":
|
|
82
|
+
return part.get("text")
|
|
83
|
+
|
|
84
|
+
return _safe_serialize(output)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _extract_input_text(input_data: Any) -> Optional[str]:
|
|
88
|
+
"""Extract the user's input query from a generation span's input messages."""
|
|
89
|
+
if input_data is None:
|
|
90
|
+
return None
|
|
91
|
+
if isinstance(input_data, str):
|
|
92
|
+
return input_data
|
|
93
|
+
if not isinstance(input_data, (list, tuple)):
|
|
94
|
+
return _safe_serialize(input_data)
|
|
95
|
+
|
|
96
|
+
# Walk messages and grab the last user message content
|
|
97
|
+
user_text = None
|
|
98
|
+
for item in input_data:
|
|
99
|
+
if not isinstance(item, dict):
|
|
100
|
+
continue
|
|
101
|
+
if item.get("role") == "user":
|
|
102
|
+
content = item.get("content")
|
|
103
|
+
if isinstance(content, str):
|
|
104
|
+
user_text = content
|
|
105
|
+
elif isinstance(content, list):
|
|
106
|
+
for part in content:
|
|
107
|
+
if isinstance(part, dict) and part.get("type") in ("text", "input_text"):
|
|
108
|
+
user_text = part.get("text")
|
|
109
|
+
|
|
110
|
+
return user_text or _safe_serialize(input_data)
|
|
111
|
+
|
|
112
|
+
|
|
24
113
|
class AgentXTracingProcessor:
|
|
25
114
|
"""
|
|
26
115
|
Implements the ``TracingProcessor`` interface expected by the OpenAI Agents SDK
|
|
27
116
|
(``agents.add_trace_processor``).
|
|
28
117
|
|
|
29
|
-
Sends one AgentX trace per top-level agent run
|
|
118
|
+
Sends one AgentX trace per top-level agent run.
|
|
30
119
|
"""
|
|
31
120
|
|
|
32
121
|
def __init__(
|
|
@@ -38,7 +127,7 @@ class AgentXTracingProcessor:
|
|
|
38
127
|
self._tracer = tracer
|
|
39
128
|
self._metadata = metadata
|
|
40
129
|
self._session_id = session_id
|
|
41
|
-
#
|
|
130
|
+
# trace_id → accumulated state
|
|
42
131
|
self._spans: Dict[str, Dict[str, Any]] = {}
|
|
43
132
|
|
|
44
133
|
# ------------------------------------------------------------------
|
|
@@ -46,18 +135,17 @@ class AgentXTracingProcessor:
|
|
|
46
135
|
# ------------------------------------------------------------------
|
|
47
136
|
|
|
48
137
|
def on_trace_start(self, trace: Any) -> None:
|
|
49
|
-
"""Called when a new trace (top-level run) begins."""
|
|
50
138
|
trace_id = getattr(trace, "trace_id", None) or str(id(trace))
|
|
51
139
|
self._spans[trace_id] = {
|
|
52
140
|
"start": time.time(),
|
|
53
141
|
"name": getattr(trace, "name", "openai-agent"),
|
|
54
142
|
"input": None,
|
|
143
|
+
"output": None,
|
|
55
144
|
"tool_calls": [],
|
|
56
145
|
"model": None,
|
|
57
146
|
}
|
|
58
147
|
|
|
59
148
|
def on_trace_end(self, trace: Any) -> None:
|
|
60
|
-
"""Called when the trace ends (agent run complete)."""
|
|
61
149
|
trace_id = getattr(trace, "trace_id", None) or str(id(trace))
|
|
62
150
|
state = self._spans.pop(trace_id, None)
|
|
63
151
|
if state is None:
|
|
@@ -66,7 +154,7 @@ class AgentXTracingProcessor:
|
|
|
66
154
|
self._tracer._send(
|
|
67
155
|
name=state["name"],
|
|
68
156
|
input=state.get("input"),
|
|
69
|
-
output=
|
|
157
|
+
output=state.get("output"),
|
|
70
158
|
latency_ms=latency_ms,
|
|
71
159
|
framework="openai-agents",
|
|
72
160
|
model=state.get("model"),
|
|
@@ -76,47 +164,58 @@ class AgentXTracingProcessor:
|
|
|
76
164
|
)
|
|
77
165
|
|
|
78
166
|
def on_span_start(self, span: Any) -> None:
|
|
79
|
-
|
|
80
|
-
span_type = getattr(span, "span_data", None)
|
|
81
|
-
if span_type is None:
|
|
82
|
-
return
|
|
83
|
-
|
|
84
|
-
# Capture model from LLM generation spans
|
|
85
|
-
if hasattr(span_type, "model"):
|
|
86
|
-
trace_id = getattr(span, "trace_id", None)
|
|
87
|
-
if trace_id and trace_id in self._spans and not self._spans[trace_id].get("model"):
|
|
88
|
-
self._spans[trace_id]["model"] = str(span_type.model)
|
|
89
|
-
|
|
90
|
-
# Capture input on the root span
|
|
91
|
-
if hasattr(span_type, "input") and span_type.input:
|
|
92
|
-
trace_id = getattr(span, "trace_id", None)
|
|
93
|
-
if trace_id and trace_id in self._spans and not self._spans[trace_id].get("input"):
|
|
94
|
-
self._spans[trace_id]["input"] = _safe_serialize(span_type.input)
|
|
167
|
+
pass # All data is captured in on_span_end when fields are fully populated
|
|
95
168
|
|
|
96
169
|
def on_span_end(self, span: Any) -> None:
|
|
97
|
-
"""Called when a nested span ends; captures tool-call results."""
|
|
98
170
|
span_data = getattr(span, "span_data", None)
|
|
99
171
|
if span_data is None:
|
|
100
172
|
return
|
|
101
173
|
|
|
102
|
-
|
|
103
|
-
if
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
174
|
+
trace_id = getattr(span, "trace_id", None)
|
|
175
|
+
if not trace_id or trace_id not in self._spans:
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
state = self._spans[trace_id]
|
|
179
|
+
span_type = getattr(span_data, "type", None)
|
|
180
|
+
|
|
181
|
+
if span_type == "generation":
|
|
182
|
+
# Capture input from the first generation span
|
|
183
|
+
if state["input"] is None and span_data.input:
|
|
184
|
+
state["input"] = _extract_input_text(span_data.input)
|
|
185
|
+
# Always update output to the latest generation (last one wins = final reply)
|
|
186
|
+
if span_data.output:
|
|
187
|
+
state["output"] = _extract_text(span_data.output)
|
|
188
|
+
# Capture model name
|
|
189
|
+
if not state["model"] and span_data.model:
|
|
190
|
+
state["model"] = str(span_data.model)
|
|
191
|
+
|
|
192
|
+
elif span_type == "response":
|
|
193
|
+
# Responses API path — extract from the response object
|
|
194
|
+
response = getattr(span_data, "response", None)
|
|
195
|
+
if response is not None:
|
|
196
|
+
if state["input"] is None:
|
|
197
|
+
raw_input = getattr(span_data, "input", None)
|
|
198
|
+
if raw_input:
|
|
199
|
+
state["input"] = _extract_input_text(raw_input) if isinstance(raw_input, list) else str(raw_input)
|
|
200
|
+
output_items = getattr(response, "output", None)
|
|
201
|
+
if output_items:
|
|
202
|
+
state["output"] = _extract_text(output_items)
|
|
203
|
+
if not state["model"]:
|
|
204
|
+
model = getattr(response, "model", None)
|
|
205
|
+
if model:
|
|
206
|
+
state["model"] = str(model)
|
|
207
|
+
|
|
208
|
+
elif span_type == "function":
|
|
209
|
+
# Tool / function call
|
|
210
|
+
tool_entry: Dict[str, Any] = {
|
|
211
|
+
"name": span_data.name,
|
|
212
|
+
"input": span_data.input,
|
|
213
|
+
"output": str(span_data.output)[:500] if span_data.output is not None else None,
|
|
214
|
+
}
|
|
215
|
+
latency = _span_latency_ms(span)
|
|
216
|
+
if latency is not None:
|
|
217
|
+
tool_entry["latency_ms"] = latency
|
|
218
|
+
state["tool_calls"].append(tool_entry)
|
|
120
219
|
|
|
121
220
|
def force_flush(self) -> None:
|
|
122
221
|
self._tracer.flush()
|
agentx/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
VERSION = "0.
|
|
1
|
+
VERSION = "0.5.1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentx-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Official Python SDK for AgentX (https://www.agentx.so/)
|
|
5
5
|
Home-page: https://github.com/AgentX-ai/AgentX-python
|
|
6
6
|
Author: Robin Wang and AgentX Team
|
|
@@ -23,11 +23,17 @@ Provides-Extra: openai-agents
|
|
|
23
23
|
Requires-Dist: openai-agents>=0.0.3; extra == "openai-agents"
|
|
24
24
|
Provides-Extra: anthropic
|
|
25
25
|
Requires-Dist: anthropic>=0.25.0; extra == "anthropic"
|
|
26
|
+
Provides-Extra: google-adk
|
|
27
|
+
Requires-Dist: google-adk>=1.0.0; extra == "google-adk"
|
|
28
|
+
Provides-Extra: google-genai
|
|
29
|
+
Requires-Dist: google-genai>=1.0.0; extra == "google-genai"
|
|
26
30
|
Provides-Extra: all
|
|
27
31
|
Requires-Dist: langchain-core>=0.1.0; extra == "all"
|
|
28
32
|
Requires-Dist: crewai>=0.80.0; extra == "all"
|
|
29
33
|
Requires-Dist: openai-agents>=0.0.3; extra == "all"
|
|
30
34
|
Requires-Dist: anthropic>=0.25.0; extra == "all"
|
|
35
|
+
Requires-Dist: google-adk>=1.0.0; extra == "all"
|
|
36
|
+
Requires-Dist: google-genai>=1.0.0; extra == "all"
|
|
31
37
|
Dynamic: author
|
|
32
38
|
Dynamic: author-email
|
|
33
39
|
Dynamic: classifier
|
|
@@ -2,7 +2,7 @@ agentx/__init__.py,sha256=4iMiGLNU4S-I-WyX0Z7l0BFUY_krVJ3seMQPqyO-ReA,602
|
|
|
2
2
|
agentx/agentx.py,sha256=tw3CUnkvTM1eopoO7u8TgKnAz9t0HNSxrVcjIJsytAg,3469
|
|
3
3
|
agentx/exceptions.py,sha256=TKooc1_pQ2P_4DqpLfZx_ap-6cfrjFLW8JS2qaGx_1M,1237
|
|
4
4
|
agentx/util.py,sha256=yOV3VVdc0KUbw5pEkSRiO2lZzEtL1vOsnB_f5tFk948,665
|
|
5
|
-
agentx/version.py,sha256=
|
|
5
|
+
agentx/version.py,sha256=Rm_tMXSiQe1ocDOCUKKIk-I7ZEL21NXSJBaSKX1ooCs,18
|
|
6
6
|
agentx/evaluations/__init__.py,sha256=OmdCHiBO3Qcsck0UkOyVqSvfRs6nXbSi-2FXzyRfsog,89
|
|
7
7
|
agentx/evaluations/_term.py,sha256=S4blZgH66wHvkt2hR29h8JE5xnakBeD7dlZdUW9Wfoo,2065
|
|
8
8
|
agentx/evaluations/client.py,sha256=3mC0uSBRk9uO4F3a7kIMM_UUsyypF99_97E5MSpYFzM,7736
|
|
@@ -17,11 +17,13 @@ agentx/evaluations/adapters/__init__.py,sha256=fK8Hx75usbiY03XUSmnLnSrwBXip2CQs2
|
|
|
17
17
|
agentx/evaluations/adapters/http_endpoint.py,sha256=3YDwpf9u6u3Xj8kLhERmShBeGMviKHCA0Dhr0t6KMmA,2061
|
|
18
18
|
agentx/evaluations/adapters/precomputed.py,sha256=BNNLQuzYFWyLSwsrMGv6MvRTroKY5Hz-Lm4F2RszESU,1210
|
|
19
19
|
agentx/evaluations/adapters/raw.py,sha256=FkDq_mdf21-xt5EHnGyIGcS6VZxkEkFcmM9VTd-T5sA,1130
|
|
20
|
-
agentx/integrations/__init__.py,sha256=
|
|
20
|
+
agentx/integrations/__init__.py,sha256=xpgFqfqdMqXSdCSucQoPRaewj9w5upSmkcaQtBKrtRE,545
|
|
21
21
|
agentx/integrations/anthropic.py,sha256=VzRdeICYm_zfgNBy6PEu6bYSUa9iKuSoRX6HSivWQpM,4802
|
|
22
22
|
agentx/integrations/crewai.py,sha256=o7tgFE1I2T7fxOc2lG7aY1nAAwihejV3y-EJII6z1j0,3488
|
|
23
|
-
agentx/integrations/
|
|
24
|
-
agentx/integrations/
|
|
23
|
+
agentx/integrations/google_adk.py,sha256=270HRdOuHo_4AWULoN0oyJ9XlrBKTeb4Ipjk5EZxzrY,6938
|
|
24
|
+
agentx/integrations/google_genai.py,sha256=fNKyoqegBYhxAeADYuRaI5K6-p1h6ntAdpBaNhFziQQ,5147
|
|
25
|
+
agentx/integrations/langchain.py,sha256=hSy2aRi0QzVo0eDpIRRacZg7ulJG-XqY_tH7C5YvNd0,12848
|
|
26
|
+
agentx/integrations/openai_agents.py,sha256=JscJELxs8sTj3toi9ixY5TOpH-lCVBtpgxYgahIaezE,8162
|
|
25
27
|
agentx/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
28
|
agentx/resources/agent.py,sha256=ZKpxDYzJbKgOX4-W86U__b4gko7XlbwB4q6L8Az9uo0,2011
|
|
27
29
|
agentx/resources/conversation.py,sha256=HK7gEGK6qIUz0KvG-ItUip08otWZdkP5S6qHiznvgZM,4443
|
|
@@ -30,8 +32,8 @@ agentx/tracing/__init__.py,sha256=l2mIRILE-wWrCD0fekgrIOQUEvtDObzdrkgpQUtXroU,40
|
|
|
30
32
|
agentx/tracing/ci_types.py,sha256=zFVcZGvc1qbVDdOlEPQ1i5R6terqJHxCzFF4ZjouxxI,1423
|
|
31
33
|
agentx/tracing/ingest_client.py,sha256=8aLabvWecgZsn0pkajZkI1it2TOk5boP0kC3o6TO52o,11487
|
|
32
34
|
agentx/tracing/tracer.py,sha256=nJILoeQg6HV4SR05xJPy6EiSiZCpkp4yFBTrN1BO_1w,16951
|
|
33
|
-
agentx_python-0.
|
|
34
|
-
agentx_python-0.
|
|
35
|
-
agentx_python-0.
|
|
36
|
-
agentx_python-0.
|
|
37
|
-
agentx_python-0.
|
|
35
|
+
agentx_python-0.5.1.dist-info/licenses/LICENSE,sha256=6ZbiPNFmv3xBb44LGhAa3PZYK0ROAztsd5LRFZDlGFE,1074
|
|
36
|
+
agentx_python-0.5.1.dist-info/METADATA,sha256=gq6txrb4Xjdar5TAzk-aHMiON2zM-i7CA4njI8UFLK8,6901
|
|
37
|
+
agentx_python-0.5.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
38
|
+
agentx_python-0.5.1.dist-info/top_level.txt,sha256=s-q-HB9Gb_QdrZNacSeQyF_c25gQooMy7DlxzgLOHPk,7
|
|
39
|
+
agentx_python-0.5.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|