agentx-python 0.6.22__tar.gz → 0.6.24__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 (70) hide show
  1. {agentx_python-0.6.22 → agentx_python-0.6.24}/PKG-INFO +3 -1
  2. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/integrations/_traced_call.py +37 -1
  3. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/integrations/anthropic.py +5 -1
  4. agentx_python-0.6.24/agentx/integrations/databricks.py +405 -0
  5. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/integrations/google_genai.py +32 -1
  6. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/integrations/langchain.py +32 -2
  7. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/integrations/litellm.py +6 -1
  8. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/integrations/moveworks.py +44 -30
  9. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/integrations/openai.py +3 -1
  10. agentx_python-0.6.24/agentx/version.py +1 -0
  11. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx_python.egg-info/PKG-INFO +3 -1
  12. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx_python.egg-info/SOURCES.txt +1 -0
  13. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx_python.egg-info/entry_points.txt +1 -0
  14. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx_python.egg-info/requires.txt +3 -0
  15. {agentx_python-0.6.22 → agentx_python-0.6.24}/setup.py +4 -0
  16. agentx_python-0.6.22/agentx/version.py +0 -1
  17. {agentx_python-0.6.22 → agentx_python-0.6.24}/LICENSE +0 -0
  18. {agentx_python-0.6.22 → agentx_python-0.6.24}/README.md +0 -0
  19. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/__init__.py +0 -0
  20. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/agentx.py +0 -0
  21. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/cli.py +0 -0
  22. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/__init__.py +0 -0
  23. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/_term.py +0 -0
  24. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/adapters/__init__.py +0 -0
  25. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/adapters/http_endpoint.py +0 -0
  26. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/adapters/precomputed.py +0 -0
  27. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/adapters/raw.py +0 -0
  28. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/client.py +0 -0
  29. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/datasets.py +0 -0
  30. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/evaluation_settings.py +0 -0
  31. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/models.py +0 -0
  32. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/prompts.py +0 -0
  33. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/redaction.py +0 -0
  34. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/reporting.py +0 -0
  35. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/results.py +0 -0
  36. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/runner.py +0 -0
  37. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/evaluations/tracing.py +0 -0
  38. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/exceptions.py +0 -0
  39. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/feedback.py +0 -0
  40. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/integrations/__init__.py +0 -0
  41. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/integrations/autogen.py +0 -0
  42. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/integrations/crewai.py +0 -0
  43. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/integrations/google_adk.py +0 -0
  44. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/integrations/llamaindex.py +0 -0
  45. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/integrations/openai_agents.py +0 -0
  46. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/monitor/__init__.py +0 -0
  47. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/monitor/client.py +0 -0
  48. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/monitor/models.py +0 -0
  49. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/monitor/online_evaluators.py +0 -0
  50. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/monitor/patterns.py +0 -0
  51. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/monitor/profile.py +0 -0
  52. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/monitor/signals.py +0 -0
  53. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/outcomes.py +0 -0
  54. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/py.typed +0 -0
  55. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/resources/__init__.py +0 -0
  56. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/resources/agent.py +0 -0
  57. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/resources/conversation.py +0 -0
  58. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/resources/workforce.py +0 -0
  59. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/tracing/__init__.py +0 -0
  60. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/tracing/ci_types.py +0 -0
  61. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/tracing/ingest_client.py +0 -0
  62. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/tracing/tracer.py +0 -0
  63. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx/util.py +0 -0
  64. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx_python.egg-info/dependency_links.txt +0 -0
  65. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx_python.egg-info/not-zip-safe +0 -0
  66. {agentx_python-0.6.22 → agentx_python-0.6.24}/agentx_python.egg-info/top_level.txt +0 -0
  67. {agentx_python-0.6.22 → agentx_python-0.6.24}/setup.cfg +0 -0
  68. {agentx_python-0.6.22 → agentx_python-0.6.24}/tests/test_integration.py +0 -0
  69. {agentx_python-0.6.22 → agentx_python-0.6.24}/tests/test_integrations.py +0 -0
  70. {agentx_python-0.6.22 → agentx_python-0.6.24}/tests/test_span_tree.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentx-python
3
- Version: 0.6.22
3
+ Version: 0.6.24
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
@@ -36,6 +36,8 @@ Requires-Dist: llama-index-core>=0.10.0; extra == "llamaindex"
36
36
  Provides-Extra: autogen
37
37
  Requires-Dist: autogen-agentchat>=0.4.0; extra == "autogen"
38
38
  Requires-Dist: autogen-core>=0.4.0; extra == "autogen"
39
+ Provides-Extra: databricks
40
+ Requires-Dist: mlflow>=3.6.0; extra == "databricks"
39
41
  Provides-Extra: all
40
42
  Requires-Dist: langchain-core>=0.1.0; extra == "all"
41
43
  Requires-Dist: crewai>=0.80.0; extra == "all"
@@ -13,9 +13,10 @@ from __future__ import annotations
13
13
 
14
14
  import asyncio
15
15
  import inspect
16
+ import json
16
17
  from typing import Any, Callable, Dict, Optional
17
18
 
18
- from agentx.tracing.tracer import Tracer
19
+ from agentx.tracing.tracer import Tracer, _safe_serialize
19
20
 
20
21
 
21
22
  def call_and_trace(
@@ -62,6 +63,32 @@ async def _await_and_finish(
62
63
  return response
63
64
 
64
65
 
66
+ # The engine's unregistered-tool surfacing (Tools & MCPs -> Unregistered) reads a trace's
67
+ # metadata "tools" key to show the REAL definition instead of one inferred from observed
68
+ # arguments (see AgentX-trace-eval's toolSchemas.ts draftFromMetadata). Raw-client patches see
69
+ # the request's tools=[...] right in kwargs, so capture it - capped so a huge toolbox never
70
+ # blows up the trace's metadata budget.
71
+ _MAX_TOOL_DEFINITIONS = 20
72
+ _MAX_TOOL_DEFINITIONS_BYTES = 12_000
73
+
74
+
75
+ def capture_tool_definitions(tools: Any) -> Optional[list]:
76
+ """Return a metadata-ready copy of a request's ``tools=[...]`` list, or None."""
77
+ if not isinstance(tools, list) or not tools:
78
+ return None
79
+ # A plain JSON round-trip preserves nested schema objects exactly (default=str catches the
80
+ # odd SDK object inside); _safe_serialize would repr-stringify nested dicts, turning a
81
+ # parameters schema into an unusable string.
82
+ try:
83
+ text = json.dumps(tools[:_MAX_TOOL_DEFINITIONS], default=str)
84
+ if len(text) > _MAX_TOOL_DEFINITIONS_BYTES:
85
+ return None
86
+ serialized = json.loads(text)
87
+ except (TypeError, ValueError):
88
+ return None
89
+ return serialized if isinstance(serialized, list) else None
90
+
91
+
65
92
  def finish_llm_call(
66
93
  tracer: Tracer,
67
94
  *,
@@ -79,6 +106,7 @@ def finish_llm_call(
79
106
  error: Optional[str],
80
107
  cache_read_tokens: Optional[int] = None,
81
108
  cache_write_tokens: Optional[int] = None,
109
+ tool_definitions: Optional[list] = None,
82
110
  ) -> None:
83
111
  """
84
112
  Close out one raw-client LLM call - shared by the ``on_finish``/exit
@@ -96,8 +124,16 @@ def finish_llm_call(
96
124
  """
97
125
  latency_ms = int((end_t - start_t) * 1000)
98
126
 
127
+ if tool_definitions:
128
+ metadata = {**(metadata or {}), "tools": tool_definitions}
129
+
99
130
  active_span = tracer.current_span
100
131
  if active_span is not None:
132
+ # The definitions describe the whole call's toolbox - attach them to the enclosing
133
+ # span's metadata (first capture wins) so the ROOT trace carries them for the
134
+ # unregistered-tool listing, same as the standalone-trace path below.
135
+ if tool_definitions and not (active_span._metadata or {}).get("tools"):
136
+ active_span._metadata = {**(active_span._metadata or {}), "tools": tool_definitions}
101
137
  if error is not None:
102
138
  active_span.set_error(error)
103
139
  active_span._record_llm_call(
@@ -22,7 +22,7 @@ import time
22
22
  from typing import Any, Dict, Optional, Tuple
23
23
 
24
24
  from agentx.tracing.tracer import Tracer, _safe_serialize
25
- from agentx.integrations._traced_call import call_and_trace, finish_llm_call
25
+ from agentx.integrations._traced_call import capture_tool_definitions, call_and_trace, finish_llm_call
26
26
 
27
27
 
28
28
  def _extract_output_text(response: Any) -> Optional[str]:
@@ -136,6 +136,7 @@ def _patch_create(
136
136
  kwargs.get("system"),
137
137
  )
138
138
  model = kwargs.get("model")
139
+ tool_definitions = capture_tool_definitions(kwargs.get("tools"))
139
140
 
140
141
  input_repr = _safe_serialize(input_messages)
141
142
 
@@ -171,6 +172,7 @@ def _patch_create(
171
172
  cache_read_tokens=cache_read_tokens,
172
173
  cache_write_tokens=cache_write_tokens,
173
174
  error=error,
175
+ tool_definitions=tool_definitions,
174
176
  )
175
177
 
176
178
  return call_and_trace(original, args, kwargs, on_finish)
@@ -199,6 +201,7 @@ def _patch_stream(
199
201
  ctx = original_stream(*args, **kwargs)
200
202
  input_repr = _safe_serialize(_prepend_system(kwargs.get("messages"), kwargs.get("system")))
201
203
  model = kwargs.get("model")
204
+ tool_definitions = capture_tool_definitions(kwargs.get("tools"))
202
205
 
203
206
  def build_and_send(end_t: float, error: Optional[str], final_message: Optional[Any]) -> None:
204
207
  output = None
@@ -230,6 +233,7 @@ def _patch_stream(
230
233
  cache_read_tokens=cache_read_tokens,
231
234
  cache_write_tokens=cache_write_tokens,
232
235
  error=error,
236
+ tool_definitions=tool_definitions,
233
237
  )
234
238
 
235
239
  class _TracedStream:
@@ -0,0 +1,405 @@
1
+ """
2
+ Databricks / MLflow integration for AgentX.
3
+
4
+ Two complementary paths for agents built on Databricks (Agent Bricks or the Mosaic AI Agent
5
+ Framework - both are auto-instrumented by MLflow 3 Tracing):
6
+
7
+ 1. **Push (live)** - ``enable_mlflow_export()``: point MLflow Tracing's native OTLP exporter at
8
+ the AgentX engine's OTel endpoint. Works anywhere MLflow traces run (notebooks, jobs, Model
9
+ Serving endpoints via environment variables). Dual export keeps Databricks' own MLflow UI and
10
+ inference tables working alongside AgentX::
11
+
12
+ from agentx.integrations.databricks import enable_mlflow_export
13
+
14
+ enable_mlflow_export(
15
+ api_key=os.environ["AGENTX_API_KEY"],
16
+ base_url="http://localhost:4700/api/v1", # your AgentX engine
17
+ service_name="my-databricks-agent",
18
+ )
19
+ # ... then trace as usual (@mlflow.trace, autolog, Agent Framework, ...)
20
+
21
+ On a Model Serving endpoint, set the equivalent environment variables instead (this helper
22
+ prints them with ``dry_run=True``).
23
+
24
+ 2. **Pull (batch)** - ``agentx-databricks sync``: import finished MLflow traces from a Databricks
25
+ (or any MLflow 3) tracking server into AgentX - full span trees, tool calls, sessions -
26
+ deduplicated on deterministic span ids so re-running a window never duplicates, with
27
+ ``--monitor`` / ``--judge-sessions`` mirroring ``agentx-moveworks``::
28
+
29
+ export AGENTX_API_KEY=... # AgentX project key
30
+ export DATABRICKS_HOST=... DATABRICKS_TOKEN=... # or MLFLOW_TRACKING_URI
31
+ agentx-databricks sync --experiment-id 123456 --since 24h
32
+
33
+ Requires (pull path): ``pip install "agentx-python[databricks]"`` (mlflow>=3).
34
+ """
35
+ from __future__ import annotations
36
+
37
+ import argparse
38
+ import json
39
+ import os
40
+ from datetime import datetime, timedelta, timezone
41
+ from pathlib import Path
42
+ from typing import Any, Dict, Iterable, List, Optional
43
+
44
+ from agentx.tracing.ingest_client import IngestClient
45
+ from agentx.version import VERSION
46
+ from agentx.integrations.moveworks import (
47
+ _parse_since,
48
+ _parse_time,
49
+ _read_cursor,
50
+ _write_cursor,
51
+ judge_sessions_via_engine,
52
+ )
53
+
54
+ _DEFAULT_CURSOR_FILE = Path.home() / ".agentx" / "databricks_sync_cursor.json"
55
+
56
+ # MLflow's session grouping metadata key (mlflow.update_current_trace(metadata={...})) - traces
57
+ # sharing it become one AgentX session, judged as a conversation by session-scoped evaluators.
58
+ _SESSION_METADATA_KEYS = ("mlflow.trace.session", "mlflow.trace.session_id", "session_id")
59
+
60
+
61
+ # ----------------------------------------------------------------------------------------------
62
+ # Path 1: push - MLflow OTLP export pointed at the AgentX engine
63
+ # ----------------------------------------------------------------------------------------------
64
+
65
+ def enable_mlflow_export(
66
+ *,
67
+ api_key: str,
68
+ base_url: str,
69
+ service_name: str = "databricks-agent",
70
+ dual: bool = True,
71
+ genai_semconv: bool = False,
72
+ dry_run: bool = False,
73
+ ) -> Dict[str, str]:
74
+ """
75
+ Configure MLflow Tracing's built-in OTLP exporter to send every trace to the AgentX engine.
76
+
77
+ Must run BEFORE the first trace starts (MLflow reads these once, at tracer setup). ``dual``
78
+ keeps MLflow's own tracking export too (Databricks MLflow UI / inference tables keep
79
+ working); ``genai_semconv`` switches the wire format to OTel GenAI semantic conventions -
80
+ AgentX ingests both, and the default (MLflow-native attributes) is the higher-fidelity
81
+ mapping for plain ``@mlflow.trace`` functions. Returns the environment variables set - with
82
+ ``dry_run=True`` nothing is set, so the dict can be copied onto a Databricks Model Serving
83
+ endpoint's environment variables instead.
84
+ """
85
+ env = {
86
+ "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": f"{base_url.rstrip('/')}/otel/v1/traces",
87
+ "OTEL_EXPORTER_OTLP_TRACES_HEADERS": f"x-api-key={api_key}",
88
+ "OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
89
+ "OTEL_SERVICE_NAME": service_name,
90
+ }
91
+ if dual:
92
+ env["MLFLOW_TRACE_ENABLE_OTLP_DUAL_EXPORT"] = "true"
93
+ if genai_semconv:
94
+ env["MLFLOW_ENABLE_OTEL_GENAI_SEMCONV"] = "true"
95
+ if not dry_run:
96
+ os.environ.update(env)
97
+ return env
98
+
99
+
100
+ # ----------------------------------------------------------------------------------------------
101
+ # Path 2: pull - MLflow trace search -> AgentX span trees
102
+ # ----------------------------------------------------------------------------------------------
103
+
104
+ class DatabricksSyncReport:
105
+ def __init__(self) -> None:
106
+ self.traces = 0
107
+ self.spans = 0
108
+ self.tool_calls = 0
109
+ self.ingested = 0
110
+ self.failed = 0
111
+ self.skipped_in_progress = 0
112
+ self.session_ids: List[str] = []
113
+ self.sessions_judged = 0
114
+ self.sessions_judge_skipped = 0
115
+ self.sessions_judge_failed = 0
116
+
117
+ def __repr__(self) -> str: # also what the CLI prints
118
+ parts = [
119
+ f"traces={self.traces}",
120
+ f"spans={self.spans}",
121
+ f"tool_calls={self.tool_calls}",
122
+ f"ingested={self.ingested}",
123
+ f"failed={self.failed}",
124
+ f"sessions={len(self.session_ids)}",
125
+ ]
126
+ if self.skipped_in_progress:
127
+ parts.append(f"skipped_in_progress={self.skipped_in_progress}")
128
+ if self.sessions_judged or self.sessions_judge_skipped or self.sessions_judge_failed:
129
+ parts.append(
130
+ f"judged={self.sessions_judged} judge_skipped={self.sessions_judge_skipped} "
131
+ f"judge_failed={self.sessions_judge_failed}"
132
+ )
133
+ return f"DatabricksSyncReport({' '.join(parts)})"
134
+
135
+
136
+ def _span_time_ns(span: Any, attr: str) -> Optional[int]:
137
+ value = getattr(span, attr, None)
138
+ return int(value) if isinstance(value, (int, float)) and value > 0 else None
139
+
140
+
141
+ def _span_error(span: Any) -> Optional[str]:
142
+ status = getattr(span, "status", None)
143
+ code = str(getattr(status, "status_code", "") or "")
144
+ if "ERROR" in code.upper():
145
+ return str(getattr(status, "description", None) or "error")
146
+ return None
147
+
148
+
149
+ def _serialize(value: Any) -> Any:
150
+ if value is None or isinstance(value, (str, int, float, bool)):
151
+ return value
152
+ try:
153
+ return json.loads(json.dumps(value, default=str))
154
+ except (TypeError, ValueError):
155
+ return str(value)
156
+
157
+
158
+ class DatabricksTraceImporter:
159
+ """
160
+ Replays finished MLflow traces (Databricks-hosted or any MLflow 3 tracking server) into
161
+ AgentX as full span trees. ``span_id`` is deterministic (``dbx:<trace_id>[:<span_id>]``), so
162
+ re-syncing a window is idempotent - the engine dedupes on replay and skips re-judging.
163
+ """
164
+
165
+ def __init__(
166
+ self,
167
+ mlflow_client: Any,
168
+ *,
169
+ agentx_api_key: str,
170
+ agentx_base_url: Optional[str] = None,
171
+ agent_name: Optional[str] = None,
172
+ ) -> None:
173
+ self._client = mlflow_client
174
+ self._agent_name = agent_name
175
+ self._ingest = IngestClient(agentx_api_key, sdk_version=VERSION, base_url=agentx_base_url)
176
+
177
+ # -- trace -> wires ------------------------------------------------------------------------
178
+
179
+ def _trace_wires(self, trace: Any) -> "tuple[List[Dict[str, Any]], Optional[str], int]":
180
+ """One MLflow Trace -> [root wire, *child wires], its session id, and its tool-call count."""
181
+ info = trace.info
182
+ trace_id = str(getattr(info, "trace_id", None) or getattr(info, "request_id", ""))
183
+ spans = list(getattr(trace.data, "spans", None) or [])
184
+ if not trace_id or not spans:
185
+ return [], None, 0
186
+
187
+ metadata_bag: Dict[str, Any] = {}
188
+ for source in (getattr(info, "trace_metadata", None), getattr(info, "tags", None)):
189
+ if isinstance(source, dict):
190
+ metadata_bag.update(source)
191
+ session_raw = next((metadata_bag[k] for k in _SESSION_METADATA_KEYS if metadata_bag.get(k)), None)
192
+ session_id = f"dbx_{session_raw}" if session_raw else None
193
+
194
+ root = next((s for s in spans if not getattr(s, "parent_id", None)), spans[0])
195
+ root_span_id = str(getattr(root, "span_id", "") or "root")
196
+ wire_span_id = {root_span_id: f"dbx:{trace_id}"}
197
+ for span in spans:
198
+ sid = str(getattr(span, "span_id", "") or "")
199
+ if sid and sid not in wire_span_id:
200
+ wire_span_id[sid] = f"dbx:{trace_id}:{sid}"
201
+
202
+ tool_calls: List[Dict[str, Any]] = []
203
+ wires: List[Dict[str, Any]] = []
204
+ for span in spans:
205
+ sid = str(getattr(span, "span_id", "") or "")
206
+ is_root = span is root
207
+ start_ns = _span_time_ns(span, "start_time_ns")
208
+ end_ns = _span_time_ns(span, "end_time_ns")
209
+ error = _span_error(span)
210
+ span_type = str(getattr(span, "span_type", "") or "").upper()
211
+ wire: Dict[str, Any] = {
212
+ "name": (self._agent_name if is_root and self._agent_name else str(getattr(span, "name", "span"))),
213
+ "framework": "databricks",
214
+ "span_id": wire_span_id.get(sid, f"dbx:{trace_id}:{sid or 'span'}"),
215
+ }
216
+ if not is_root:
217
+ parent_sid = str(getattr(span, "parent_id", "") or "")
218
+ wire["parent_span_id"] = wire_span_id.get(parent_sid, f"dbx:{trace_id}")
219
+ if session_id:
220
+ wire["session_id"] = session_id
221
+ if start_ns:
222
+ wire["started_at_unix_nano"] = str(start_ns)
223
+ if start_ns and end_ns and end_ns > start_ns:
224
+ wire["latency_ms"] = int((end_ns - start_ns) / 1_000_000)
225
+ inputs = _serialize(getattr(span, "inputs", None))
226
+ outputs = _serialize(getattr(span, "outputs", None))
227
+ if inputs is not None:
228
+ wire["input"] = inputs
229
+ if outputs is not None:
230
+ wire["output"] = outputs
231
+ if error:
232
+ wire["error"] = error
233
+ if is_root:
234
+ wire["metadata"] = {
235
+ "source": "databricks",
236
+ "mlflowTraceId": trace_id,
237
+ **({"experimentId": str(getattr(info, "experiment_id", ""))} if getattr(info, "experiment_id", None) else {}),
238
+ }
239
+ if span_type == "TOOL":
240
+ tool_calls.append(
241
+ {
242
+ "name": str(getattr(span, "name", "tool")),
243
+ "input": inputs,
244
+ "output": outputs,
245
+ "latency_ms": wire.get("latency_ms"),
246
+ "success": not error,
247
+ }
248
+ )
249
+ wires.append(wire)
250
+
251
+ # Root carries the flat tool_calls mirror - what the engine's Tool-failure check and
252
+ # trajectory matching read (same posture as tracer._merge_child_run).
253
+ if tool_calls:
254
+ wires[spans.index(root)]["tool_calls"] = tool_calls
255
+ # Root first so the engine resolves the agent before children arrive.
256
+ wires.sort(key=lambda w: 0 if "parent_span_id" not in w else 1)
257
+ return wires, session_id, len(tool_calls)
258
+
259
+ def _search(self, experiment_ids: List[str], since: datetime, until: datetime) -> Iterable[Any]:
260
+ """Newest-first paginated search, stopping once a page is entirely older than ``since``."""
261
+ page_token: Optional[str] = None
262
+ since_ms = int(since.timestamp() * 1000)
263
+ until_ms = int(until.timestamp() * 1000)
264
+ while True:
265
+ page = self._client.search_traces(
266
+ experiment_ids=experiment_ids,
267
+ max_results=100,
268
+ page_token=page_token,
269
+ order_by=["timestamp_ms DESC"],
270
+ )
271
+ oldest_seen = None
272
+ for trace in page:
273
+ ts = getattr(trace.info, "request_time", None) or getattr(trace.info, "timestamp_ms", None)
274
+ ts_ms = int(ts.timestamp() * 1000) if isinstance(ts, datetime) else (int(ts) if ts else None)
275
+ oldest_seen = ts_ms if ts_ms is not None else oldest_seen
276
+ if ts_ms is not None and (ts_ms < since_ms or ts_ms >= until_ms):
277
+ if ts_ms < since_ms:
278
+ continue
279
+ continue
280
+ yield trace
281
+ page_token = getattr(page, "token", None)
282
+ if not page_token or (oldest_seen is not None and oldest_seen < since_ms):
283
+ return
284
+
285
+ # -- public --------------------------------------------------------------------------------
286
+
287
+ def sync(
288
+ self,
289
+ experiment_ids: List[str],
290
+ since: datetime,
291
+ until: Optional[datetime] = None,
292
+ *,
293
+ monitor: bool = False,
294
+ judge_sessions: bool = False,
295
+ dry_run: bool = False,
296
+ on_payload: Optional[Any] = None,
297
+ ) -> DatabricksSyncReport:
298
+ """
299
+ Import every finished MLflow trace in ``[since, until)`` from the given experiments.
300
+ Safe to re-run over the same window (span_id dedupe). ``monitor=True`` opts every
301
+ imported root trace into the engine's ingest-time checks; ``judge_sessions=True`` judges
302
+ each imported session afterwards (``ifStale`` - never duplicates the engine's own sweep).
303
+ """
304
+ report = DatabricksSyncReport()
305
+ until = until or datetime.now(timezone.utc)
306
+ for trace in self._search(experiment_ids, since, until):
307
+ state = str(getattr(trace.info, "state", "") or "")
308
+ if state and "IN_PROGRESS" in state.upper():
309
+ report.skipped_in_progress += 1
310
+ continue
311
+ wires, session_id, tool_count = self._trace_wires(trace)
312
+ if not wires:
313
+ continue
314
+ report.traces += 1
315
+ report.spans += len(wires)
316
+ report.tool_calls += tool_count
317
+ if session_id and session_id not in report.session_ids:
318
+ report.session_ids.append(session_id)
319
+ for wire in wires:
320
+ if monitor and "parent_span_id" not in wire:
321
+ wire["monitor"] = True
322
+ if on_payload is not None:
323
+ on_payload(wire)
324
+ if dry_run:
325
+ continue
326
+ if self._ingest.send_trace_sync(wire) is not None:
327
+ report.ingested += 1
328
+ else:
329
+ report.failed += 1
330
+ if judge_sessions and not dry_run:
331
+ judged, skipped, failed = judge_sessions_via_engine(self._ingest, report.session_ids)
332
+ report.sessions_judged += judged
333
+ report.sessions_judge_skipped += skipped
334
+ report.sessions_judge_failed += failed
335
+ return report
336
+
337
+
338
+ # ----------------------------------------------------------------------------------------------
339
+ # CLI: agentx-databricks sync
340
+ # ----------------------------------------------------------------------------------------------
341
+
342
+ def cli_main(argv: Optional[List[str]] = None) -> None:
343
+ parser = argparse.ArgumentParser(
344
+ prog="agentx-databricks",
345
+ description="Sync MLflow traces (Databricks agents) into AgentX as span trees/sessions.",
346
+ )
347
+ sub = parser.add_subparsers(dest="command", required=True)
348
+ sync = sub.add_parser("sync", help="Import a time window of MLflow traces")
349
+ sync.add_argument("--experiment-id", action="append", required=True, help="MLflow experiment id (repeatable)")
350
+ sync.add_argument("--since", help="ISO timestamp or relative (7d, 24h). Default: cursor file, else 24h")
351
+ sync.add_argument("--until", help="ISO timestamp (default: now)")
352
+ sync.add_argument("--cursor-file", default=str(_DEFAULT_CURSOR_FILE), help="Incremental cursor path")
353
+ sync.add_argument("--no-cursor", action="store_true", help="Ignore and don't update the cursor file")
354
+ sync.add_argument("--agent-name", help="Attribute every trace to this agent (default: the root span's name)")
355
+ sync.add_argument(
356
+ "--tracking-uri",
357
+ default=os.getenv("MLFLOW_TRACKING_URI", "databricks"),
358
+ help='MLflow tracking URI (default: MLFLOW_TRACKING_URI, else "databricks" - uses DATABRICKS_HOST/TOKEN)',
359
+ )
360
+ sync.add_argument("--monitor", action="store_true", help="Run ingest-time checks on each imported trace")
361
+ sync.add_argument(
362
+ "--judge-sessions",
363
+ action="store_true",
364
+ help="After the sync, judge each imported session with every enabled session-scoped evaluator "
365
+ "(skips sessions that already have an up-to-date verdict)",
366
+ )
367
+ sync.add_argument("--dry-run", action="store_true", help="Map and print payloads without ingesting")
368
+
369
+ args = parser.parse_args(argv)
370
+
371
+ agentx_key = os.getenv("AGENTX_API_KEY", "")
372
+ if not agentx_key and not args.dry_run:
373
+ raise SystemExit("agentx-databricks: set AGENTX_API_KEY (your AgentX project API key)")
374
+
375
+ try:
376
+ from mlflow.client import MlflowClient
377
+ except ImportError as exc: # pragma: no cover
378
+ raise SystemExit('agentx-databricks: pip install "agentx-python[databricks]" (needs mlflow>=3)') from exc
379
+
380
+ cursor_path = Path(args.cursor_file)
381
+ since = (
382
+ _parse_since(args.since)
383
+ if args.since
384
+ else ((None if args.no_cursor else _read_cursor(cursor_path)) or (datetime.now(timezone.utc) - timedelta(hours=24)))
385
+ )
386
+ until = _parse_time(args.until) if args.until else datetime.now(timezone.utc)
387
+
388
+ importer = DatabricksTraceImporter(
389
+ MlflowClient(tracking_uri=args.tracking_uri),
390
+ agentx_api_key=agentx_key,
391
+ agentx_base_url=os.getenv("AGENTX_API_BASE_URL") or None,
392
+ agent_name=args.agent_name,
393
+ )
394
+ report = importer.sync(
395
+ args.experiment_id,
396
+ since,
397
+ until,
398
+ monitor=args.monitor,
399
+ judge_sessions=args.judge_sessions,
400
+ dry_run=args.dry_run,
401
+ on_payload=(lambda wire: print(json.dumps(wire, default=str))) if args.dry_run else None,
402
+ )
403
+ print(report)
404
+ if not args.no_cursor and not args.dry_run and until is not None:
405
+ _write_cursor(cursor_path, until)
@@ -20,7 +20,7 @@ import time
20
20
  from typing import Any, Dict, List, Optional
21
21
 
22
22
  from agentx.tracing.tracer import Tracer, _safe_serialize
23
- from agentx.integrations._traced_call import call_and_trace, finish_llm_call
23
+ from agentx.integrations._traced_call import capture_tool_definitions, call_and_trace, finish_llm_call
24
24
 
25
25
 
26
26
  def patch_genai_client(
@@ -90,6 +90,31 @@ def _extract_response_text(response: Any) -> Optional[str]:
90
90
  return None
91
91
 
92
92
 
93
+
94
+ def _genai_tool_definitions(config: Any) -> Optional[list]:
95
+ """
96
+ Flatten a GenAI request's config.tools (each a Tool holding function_declarations) into the
97
+ flat [{name, description, parameters}, ...] list the engine's unregistered-tool listing
98
+ reads from trace metadata. Plain dicts and SDK objects both handled.
99
+ """
100
+ tools = getattr(config, "tools", None) if config is not None and not isinstance(config, dict) else (config or {}).get("tools") if isinstance(config, dict) else None
101
+ if not tools:
102
+ return None
103
+ flat = []
104
+ for tool in tools:
105
+ decls = getattr(tool, "function_declarations", None) if not isinstance(tool, dict) else tool.get("function_declarations")
106
+ for decl in decls or []:
107
+ if isinstance(decl, dict):
108
+ flat.append(decl)
109
+ else:
110
+ flat.append({
111
+ "name": getattr(decl, "name", None),
112
+ "description": getattr(decl, "description", None),
113
+ "parameters": getattr(decl, "parameters", None),
114
+ })
115
+ return capture_tool_definitions(flat)
116
+
117
+
93
118
  def _patch_generate_content(
94
119
  models: Any,
95
120
  tracer: Tracer,
@@ -106,6 +131,7 @@ def _patch_generate_content(
106
131
  model = kwargs.get("model") or (args[0] if args else None)
107
132
  contents = kwargs.get("contents") or (args[1] if len(args) > 1 else None)
108
133
  input_repr = contents if isinstance(contents, str) else _safe_serialize(contents)
134
+ tool_definitions = _genai_tool_definitions(kwargs.get("config"))
109
135
 
110
136
  def on_finish(response: Optional[Any], error: Optional[str]) -> None:
111
137
  end_t = time.time()
@@ -137,6 +163,7 @@ def _patch_generate_content(
137
163
  output_tokens=output_tokens,
138
164
  cache_read_tokens=cache_read_tokens,
139
165
  error=error,
166
+ tool_definitions=tool_definitions,
140
167
  )
141
168
 
142
169
  return call_and_trace(original, args, kwargs, on_finish)
@@ -180,6 +207,7 @@ def _patch_sync_generate_content_stream(
180
207
  def patched_stream(*args, **kwargs):
181
208
  start_t = time.time()
182
209
  model, input_repr = _stream_input_and_model(args, kwargs)
210
+ tool_definitions = _genai_tool_definitions(kwargs.get("config"))
183
211
  accumulated_text: List[str] = []
184
212
  last_usage_metadata = None
185
213
  error: Optional[str] = None
@@ -222,6 +250,7 @@ def _patch_sync_generate_content_stream(
222
250
  output_tokens=output_tokens,
223
251
  cache_read_tokens=cache_read_tokens,
224
252
  error=error,
253
+ tool_definitions=tool_definitions,
225
254
  )
226
255
 
227
256
  patched_stream._agentx_patched = True
@@ -247,6 +276,7 @@ def _patch_async_generate_content_stream(
247
276
  # drives it.
248
277
  start_t = time.time()
249
278
  model, input_repr = _stream_input_and_model(args, kwargs)
279
+ tool_definitions = _genai_tool_definitions(kwargs.get("config"))
250
280
  inner = await original_stream(*args, **kwargs)
251
281
 
252
282
  async def traced_agen():
@@ -290,6 +320,7 @@ def _patch_async_generate_content_stream(
290
320
  output_tokens=output_tokens,
291
321
  cache_read_tokens=cache_read_tokens,
292
322
  error=error,
323
+ tool_definitions=tool_definitions,
293
324
  )
294
325
 
295
326
  return traced_agen()
@@ -26,6 +26,7 @@ from typing import Any, Dict, List, Optional, Union
26
26
  from uuid import UUID
27
27
 
28
28
  from agentx.tracing.tracer import Tracer, _safe_serialize
29
+ from agentx.integrations._traced_call import capture_tool_definitions
29
30
 
30
31
  try:
31
32
  from langchain_core.callbacks.base import BaseCallbackHandler
@@ -375,6 +376,10 @@ class AgentXCallbackHandler(BaseCallbackHandler):
375
376
  # skipped plumbing runs to the nearest emitted ancestor.
376
377
  "node_runs": {},
377
378
  "chain_parents": {},
379
+ # The request's tools=[...] as seen on the first LLM call's invocation params -
380
+ # attached to the root trace's metadata so the engine's unregistered-tool
381
+ # listing can surface the REAL definition (not one inferred from arguments).
382
+ "tool_definitions": None,
378
383
  }
379
384
  else:
380
385
  top = self._find_top_ancestor(parent_run_id)
@@ -522,6 +527,8 @@ class AgentXCallbackHandler(BaseCallbackHandler):
522
527
 
523
528
  active_span = self._tracer.current_span
524
529
  if active_span is not None:
530
+ if state.get("tool_definitions") and not (active_span._metadata or {}).get("tools"):
531
+ active_span._metadata = {**(active_span._metadata or {}), "tools": state["tool_definitions"]}
525
532
  # Part of a `with tracer.trace(...)` block (e.g. an orchestrator
526
533
  # spanning several chain/agent/retriever calls) - fold this
527
534
  # top-level run into it instead of sending an independent trace.
@@ -540,7 +547,15 @@ class AgentXCallbackHandler(BaseCallbackHandler):
540
547
  # Standalone usage (no enclosing `with tracer.trace()`): open a real root span for
541
548
  # this chain invocation and let _merge_child_run explode its accumulated
542
549
  # execution_steps/tool_calls/retrieval_steps into real child-span rows.
543
- with self._tracer.trace(self._name, metadata=self._metadata, session_id=self._session_id) as span:
550
+ with self._tracer.trace(
551
+ self._name,
552
+ metadata=(
553
+ {**(self._metadata or {}), "tools": state["tool_definitions"]}
554
+ if state.get("tool_definitions")
555
+ else self._metadata
556
+ ),
557
+ session_id=self._session_id,
558
+ ) as span:
544
559
  # __enter__ just set _start to "now" - overridden to the chain's real start time,
545
560
  # see llamaindex.py's _send_trace for the identical fix and full rationale.
546
561
  span._start = state["start"]
@@ -577,6 +592,8 @@ class AgentXCallbackHandler(BaseCallbackHandler):
577
592
 
578
593
  active_span = self._tracer.current_span
579
594
  if active_span is not None:
595
+ if state.get("tool_definitions") and not (active_span._metadata or {}).get("tools"):
596
+ active_span._metadata = {**(active_span._metadata or {}), "tools": state["tool_definitions"]}
580
597
  active_span.set_error(str(error))
581
598
  active_span._merge_child_run(
582
599
  tool_calls=state["tool_calls"],
@@ -590,7 +607,15 @@ class AgentXCallbackHandler(BaseCallbackHandler):
590
607
  self._emit_span_tree(active_span, state, state["tool_calls"])
591
608
  else:
592
609
  # See on_chain_end's matching branch - same standalone-usage handling.
593
- with self._tracer.trace(self._name, metadata=self._metadata, session_id=self._session_id) as span:
610
+ with self._tracer.trace(
611
+ self._name,
612
+ metadata=(
613
+ {**(self._metadata or {}), "tools": state["tool_definitions"]}
614
+ if state.get("tool_definitions")
615
+ else self._metadata
616
+ ),
617
+ session_id=self._session_id,
618
+ ) as span:
594
619
  span._start = state["start"]
595
620
  span.set_error(str(error))
596
621
  span._merge_child_run(
@@ -629,6 +654,11 @@ class AgentXCallbackHandler(BaseCallbackHandler):
629
654
  or serialized.get("name")
630
655
  )
631
656
  model = str(model) if model and model not in ("None", "none") else None
657
+ top_for_tools = self._find_top_ancestor(parent_run_id)
658
+ if top_for_tools and top_for_tools in self._runs and not self._runs[top_for_tools].get("tool_definitions"):
659
+ captured = capture_tool_definitions(kwargs.get("invocation_params", {}).get("tools"))
660
+ if captured:
661
+ self._runs[top_for_tools]["tool_definitions"] = captured
632
662
  self._runs[run_id] = {
633
663
  "llm_start": time.time(),
634
664
  "model": model,
@@ -18,7 +18,7 @@ from __future__ import annotations
18
18
  from typing import Any, Dict, Optional, Tuple
19
19
 
20
20
  from agentx.tracing.tracer import Tracer, _safe_serialize
21
- from agentx.integrations._traced_call import finish_llm_call
21
+ from agentx.integrations._traced_call import capture_tool_definitions, finish_llm_call
22
22
 
23
23
  try:
24
24
  from litellm.integrations.custom_logger import CustomLogger
@@ -98,6 +98,10 @@ class AgentXLiteLLMLogger(CustomLogger):
98
98
  def _finish(self, kwargs: Dict[str, Any], response_obj: Any, start_time: Any, end_time: Any, error: Optional[str]) -> None:
99
99
  model = kwargs.get("model")
100
100
  input_repr = _safe_serialize(kwargs.get("messages"))
101
+ # LiteLLM surfaces the request's tools under optional_params (and sometimes top-level).
102
+ tool_definitions = capture_tool_definitions(
103
+ kwargs.get("tools") or (kwargs.get("optional_params") or {}).get("tools")
104
+ )
101
105
  output = None
102
106
  input_tokens = None
103
107
  output_tokens = None
@@ -118,6 +122,7 @@ class AgentXLiteLLMLogger(CustomLogger):
118
122
  output=output,
119
123
  model=model,
120
124
  input_tokens=input_tokens,
125
+ tool_definitions=tool_definitions,
121
126
  output_tokens=output_tokens,
122
127
  cache_read_tokens=cache_read_tokens,
123
128
  error=error,
@@ -189,6 +189,46 @@ class MoveworksSyncReport:
189
189
  return base + ")"
190
190
 
191
191
 
192
+ def judge_sessions_via_engine(ingest: IngestClient, session_ids: List[str]) -> "tuple[int, int, int]":
193
+ """
194
+ Judge each session with every enabled session-scoped evaluator via the engine's on-demand
195
+ judge route (``ifStale=true``: an up-to-date verdict from the sweep or a previous run is left
196
+ alone). Shared by every pull importer (Moveworks, Databricks). Returns (judged, skipped,
197
+ failed) counts.
198
+ """
199
+ # Same base/key the ingest client already resolved - the judge routes live under
200
+ # /agent-monitoring on the same engine.
201
+ base = ingest._base_url
202
+ http = ingest._session
203
+ try:
204
+ response = http.get(f"{base}/agent-monitoring/online-evaluators", timeout=30)
205
+ response.raise_for_status()
206
+ evaluators = [
207
+ e for e in response.json().get("evaluators", [])
208
+ if e.get("enabled") and e.get("scope") == "session"
209
+ ]
210
+ except requests.RequestException:
211
+ return 0, 0, len(session_ids)
212
+ judged = skipped = failed = 0
213
+ for session_id in session_ids:
214
+ for evaluator in evaluators:
215
+ try:
216
+ result = http.post(
217
+ f"{base}/agent-monitoring/sessions/{session_id}/judge/{evaluator['_id']}",
218
+ params={"ifStale": "true"},
219
+ timeout=120,
220
+ )
221
+ if result.status_code == 200 and result.json().get("skipped"):
222
+ skipped += 1
223
+ elif result.ok:
224
+ judged += 1
225
+ else:
226
+ failed += 1
227
+ except requests.RequestException:
228
+ failed += 1
229
+ return judged, skipped, failed
230
+
231
+
192
232
  class MoveworksImporter:
193
233
  """
194
234
  Pulls Moveworks Data API records and replays them into AgentX as traces.
@@ -386,36 +426,10 @@ class MoveworksImporter:
386
426
  run) is left alone instead of judged again.
387
427
  """
388
428
  report = report or MoveworksSyncReport()
389
- # Same base/key the ingest client already resolved - the judge routes live under
390
- # /agent-monitoring on the same engine.
391
- base = self._ingest._base_url
392
- http = self._ingest._session
393
- try:
394
- response = http.get(f"{base}/agent-monitoring/online-evaluators", timeout=30)
395
- response.raise_for_status()
396
- evaluators = [
397
- e for e in response.json().get("evaluators", [])
398
- if e.get("enabled") and e.get("scope") == "session"
399
- ]
400
- except requests.RequestException:
401
- report.sessions_judge_failed += len(session_ids)
402
- return report
403
- for session_id in session_ids:
404
- for evaluator in evaluators:
405
- try:
406
- result = http.post(
407
- f"{base}/agent-monitoring/sessions/{session_id}/judge/{evaluator['_id']}",
408
- params={"ifStale": "true"},
409
- timeout=120,
410
- )
411
- if result.status_code == 200 and result.json().get("skipped"):
412
- report.sessions_judge_skipped += 1
413
- elif result.ok:
414
- report.sessions_judged += 1
415
- else:
416
- report.sessions_judge_failed += 1
417
- except requests.RequestException:
418
- report.sessions_judge_failed += 1
429
+ judged, skipped, failed = judge_sessions_via_engine(self._ingest, session_ids)
430
+ report.sessions_judged += judged
431
+ report.sessions_judge_skipped += skipped
432
+ report.sessions_judge_failed += failed
419
433
  return report
420
434
 
421
435
 
@@ -28,7 +28,7 @@ import time
28
28
  from typing import Any, Dict, Optional, Tuple
29
29
 
30
30
  from agentx.tracing.tracer import Tracer, _safe_serialize
31
- from agentx.integrations._traced_call import call_and_trace, finish_llm_call
31
+ from agentx.integrations._traced_call import capture_tool_definitions, call_and_trace, finish_llm_call
32
32
 
33
33
 
34
34
  def _extract_output_text(response: Any) -> Optional[str]:
@@ -128,6 +128,7 @@ def _patch_chat_completions_create(
128
128
  input_messages = kwargs.get("messages") or (args[0] if args else None)
129
129
  model = kwargs.get("model")
130
130
  input_repr = _safe_serialize(input_messages)
131
+ tool_definitions = capture_tool_definitions(kwargs.get("tools"))
131
132
 
132
133
  def on_finish(response: Optional[Any], error: Optional[str]) -> None:
133
134
  end_t = time.time()
@@ -159,6 +160,7 @@ def _patch_chat_completions_create(
159
160
  output_tokens=output_tokens,
160
161
  cache_read_tokens=cache_read_tokens,
161
162
  error=error,
163
+ tool_definitions=tool_definitions,
162
164
  )
163
165
 
164
166
  return call_and_trace(original, args, kwargs, on_finish)
@@ -0,0 +1 @@
1
+ VERSION = "0.6.24"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentx-python
3
- Version: 0.6.22
3
+ Version: 0.6.24
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
@@ -36,6 +36,8 @@ Requires-Dist: llama-index-core>=0.10.0; extra == "llamaindex"
36
36
  Provides-Extra: autogen
37
37
  Requires-Dist: autogen-agentchat>=0.4.0; extra == "autogen"
38
38
  Requires-Dist: autogen-core>=0.4.0; extra == "autogen"
39
+ Provides-Extra: databricks
40
+ Requires-Dist: mlflow>=3.6.0; extra == "databricks"
39
41
  Provides-Extra: all
40
42
  Requires-Dist: langchain-core>=0.1.0; extra == "all"
41
43
  Requires-Dist: crewai>=0.80.0; extra == "all"
@@ -31,6 +31,7 @@ agentx/integrations/_traced_call.py
31
31
  agentx/integrations/anthropic.py
32
32
  agentx/integrations/autogen.py
33
33
  agentx/integrations/crewai.py
34
+ agentx/integrations/databricks.py
34
35
  agentx/integrations/google_adk.py
35
36
  agentx/integrations/google_genai.py
36
37
  agentx/integrations/langchain.py
@@ -1,3 +1,4 @@
1
1
  [console_scripts]
2
+ agentx-databricks = agentx.integrations.databricks:cli_main
2
3
  agentx-moveworks = agentx.integrations.moveworks:cli_main
3
4
  agentx-trace-eval = agentx.cli:main
@@ -26,6 +26,9 @@ autogen-core>=0.4.0
26
26
  [crewai]
27
27
  crewai>=0.80.0
28
28
 
29
+ [databricks]
30
+ mlflow>=3.6.0
31
+
29
32
  [google-adk]
30
33
  google-adk>=1.0.0
31
34
 
@@ -35,6 +35,9 @@ setup(
35
35
  # Moveworks Data API -> AgentX traces sync (see agentx/integrations/moveworks.py) -
36
36
  # a pull importer, since Moveworks agents run in their cloud with no in-process hook.
37
37
  "agentx-moveworks=agentx.integrations.moveworks:cli_main",
38
+ # MLflow/Databricks trace sync (see agentx/integrations/databricks.py) - pull importer
39
+ # for agents whose serving endpoints can't set OTLP egress env vars.
40
+ "agentx-databricks=agentx.integrations.databricks:cli_main",
38
41
  ],
39
42
  },
40
43
  install_requires=[
@@ -54,6 +57,7 @@ setup(
54
57
  "litellm": ["litellm>=1.0.0"],
55
58
  "llamaindex": ["llama-index-core>=0.10.0"],
56
59
  "autogen": ["autogen-agentchat>=0.4.0", "autogen-core>=0.4.0"],
60
+ "databricks": ["mlflow>=3.6.0"],
57
61
  "all": [
58
62
  "langchain-core>=0.1.0",
59
63
  "crewai>=0.80.0",
@@ -1 +0,0 @@
1
- VERSION = "0.6.22"
File without changes
File without changes
File without changes