databricks-mason 0.1.0.dev0__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.
@@ -0,0 +1,263 @@
1
+ """`mason tracing` — route an agent's traces to MLflow / Unity Catalog and inspect them.
2
+
3
+ Parallel to `mason memory` and `mason sessions`: `setup` provisions the trace destination
4
+ (links a UC schema to an MLflow experiment, the analog of creating a store), `list`/`get`
5
+ read traces back, and `instrument` prints the wiring snippet (the "Starter code" analog).
6
+ `mason deploy --with-traces` injects the destination into a deployment's app.yaml, exactly as
7
+ `--with-memory-store` / `--with-session-store` inject their stores.
8
+
9
+ MLflow is an optional dependency: `setup`/`list`/`get` need `mlflow[databricks]>=3.9.0`
10
+ installed and lazily import it; `instrument` (and the deploy wiring) are pure and need nothing.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import os
16
+ from typing import Any, Optional
17
+
18
+ import click
19
+
20
+ from databricks_mason import render, timefmt
21
+ from databricks_mason.errors import AgentCliError
22
+
23
+ _BREADCRUMB = "Agent Tracing"
24
+ _DEFAULT_EXPERIMENT = "/Shared/mason-agent-traces"
25
+
26
+ # Env vars the deployed agent reads (see deploy.py). MLFLOW_TRACING_DESTINATION is MLflow's own
27
+ # "catalog.schema" convention; MLFLOW_EXPERIMENT_NAME is the standard MLflow experiment selector.
28
+ TRACES_DEST_ENV = "MLFLOW_TRACING_DESTINATION"
29
+ TRACES_EXPERIMENT_ENV = "MLFLOW_EXPERIMENT_NAME"
30
+
31
+
32
+ def _mlflow():
33
+ """Import mlflow lazily so the core CLI (and offline wheel) don't depend on it."""
34
+ try:
35
+ import mlflow # noqa: PLC0415 - intentional lazy import
36
+
37
+ return mlflow
38
+ except ImportError as exc:
39
+ raise AgentCliError(
40
+ "MLflow is required for `mason tracing` setup/list/get.",
41
+ hint="Install it: pip install 'mlflow[databricks]>=3.9.0'",
42
+ ) from exc
43
+
44
+
45
+ def _uc_trace_symbols():
46
+ """Import the version-specific UC-tracing symbols, surfacing the same clean error as `_mlflow`.
47
+
48
+ `import mlflow` succeeding doesn't guarantee these exist — they were added in the tracing API
49
+ this feature needs. Guard them so an older installed MLflow yields Mason's install hint rather
50
+ than a raw ImportError traceback.
51
+ """
52
+ try:
53
+ from mlflow.entities import UCSchemaLocation # noqa: PLC0415 - lazy, version-specific
54
+ from mlflow.tracing.enablement import set_experiment_trace_location # noqa: PLC0415
55
+
56
+ return UCSchemaLocation, set_experiment_trace_location
57
+ except ImportError as exc:
58
+ raise AgentCliError(
59
+ "This MLflow version is too old for `mason tracing setup` (UC trace destinations).",
60
+ hint="Upgrade it: pip install 'mlflow[databricks]>=3.9.0'",
61
+ ) from exc
62
+
63
+
64
+ def _configure(mlflow, profile: Optional[str], warehouse_id: Optional[str]) -> None:
65
+ """Point MLflow at the workspace (honoring mason's --profile) for UC-backed tracing."""
66
+ mlflow.set_tracking_uri(f"databricks://{profile}" if profile else "databricks")
67
+ if warehouse_id:
68
+ os.environ["MLFLOW_TRACING_SQL_WAREHOUSE_ID"] = warehouse_id
69
+
70
+
71
+ def _ensure_experiment(mlflow, name: str) -> str:
72
+ experiment = mlflow.get_experiment_by_name(name)
73
+ return experiment.experiment_id if experiment else mlflow.create_experiment(name)
74
+
75
+
76
+ def _attr(obj: Any, *paths: str, default: Any = None) -> Any:
77
+ """Read the first present dotted attribute path (MLflow object shapes vary by version)."""
78
+ for path in paths:
79
+ cur = obj
80
+ for part in path.split("."):
81
+ cur = getattr(cur, part, None)
82
+ if cur is None:
83
+ break
84
+ if cur is not None:
85
+ return cur
86
+ return default
87
+
88
+
89
+ def _status_str(status: Any) -> Optional[str]:
90
+ if status is None:
91
+ return None
92
+ return getattr(status, "name", None) or str(status)
93
+
94
+
95
+ def _split_destination(destination: str) -> tuple[str, str]:
96
+ catalog, _, schema = destination.partition(".")
97
+ if not catalog or not schema:
98
+ raise AgentCliError("--destination must be 'catalog.schema'.")
99
+ return catalog, schema
100
+
101
+
102
+ # --- group ------------------------------------------------------------------
103
+
104
+
105
+ @click.group()
106
+ def tracing() -> None:
107
+ """Set up and inspect MLflow traces (in Unity Catalog) for your agents."""
108
+
109
+
110
+ # --- setup: provision the UC trace destination ------------------------------
111
+
112
+
113
+ @tracing.command("setup")
114
+ @click.option("--catalog", required=True, help="Unity Catalog catalog to store traces in.")
115
+ @click.option("--schema", required=True, help="Unity Catalog schema to store traces in.")
116
+ @click.option(
117
+ "--experiment", default=None, help=f"MLflow experiment path (default: {_DEFAULT_EXPERIMENT})."
118
+ )
119
+ @click.option(
120
+ "--warehouse-id",
121
+ default=None,
122
+ help="SQL warehouse id for trace queries (MLFLOW_TRACING_SQL_WAREHOUSE_ID).",
123
+ )
124
+ @click.pass_obj
125
+ def tracing_setup(obj, catalog, schema, experiment, warehouse_id) -> None:
126
+ """Link a UC schema to an MLflow experiment so agent traces land in Unity Catalog."""
127
+ mlflow = _mlflow()
128
+ _configure(mlflow, obj.profile, warehouse_id)
129
+ exp_name = experiment or _DEFAULT_EXPERIMENT
130
+ exp_id = _ensure_experiment(mlflow, exp_name)
131
+
132
+ UCSchemaLocation, set_experiment_trace_location = _uc_trace_symbols()
133
+ set_experiment_trace_location(
134
+ location=UCSchemaLocation(catalog_name=catalog, schema_name=schema), experiment_id=exp_id
135
+ )
136
+ destination = f"{catalog}.{schema}"
137
+
138
+ if obj.output == "json":
139
+ render.emit_json(
140
+ {"experiment": exp_name, "experiment_id": exp_id, "destination": destination}
141
+ )
142
+ return
143
+ render.success(
144
+ f"Linked traces for '{exp_name}' to {destination}",
145
+ fields={"Experiment": exp_name, "Destination": destination},
146
+ next_steps=[
147
+ f"mason tracing instrument --destination {destination}",
148
+ f"mason deploy <name> --source ./app --with-traces {destination}",
149
+ f"mason tracing list --experiment {exp_name}",
150
+ ],
151
+ )
152
+
153
+
154
+ # --- list / get -------------------------------------------------------------
155
+
156
+
157
+ @tracing.command("list")
158
+ @click.option(
159
+ "--experiment", default=None, help=f"MLflow experiment path (default: {_DEFAULT_EXPERIMENT})."
160
+ )
161
+ @click.option("--limit", type=int, default=20)
162
+ @click.pass_obj
163
+ def tracing_list(obj, experiment, limit) -> None:
164
+ """List recent agent traces in an experiment."""
165
+ mlflow = _mlflow()
166
+ _configure(mlflow, obj.profile, None)
167
+ exp_name = experiment or _DEFAULT_EXPERIMENT
168
+ traces = mlflow.search_traces(
169
+ experiment_names=[exp_name], max_results=limit, return_type="list"
170
+ )
171
+
172
+ if obj.output == "json":
173
+ render.emit_json([_trace_json(t) for t in traces])
174
+ return
175
+ rows = [
176
+ [
177
+ _attr(t, "info.trace_id", "info.request_id"),
178
+ render.status_pill(_status_str(_attr(t, "info.status", "info.state"))),
179
+ _attr(t, "info.execution_time_ms", "info.execution_duration_ms"),
180
+ timefmt.relative(_attr(t, "info.timestamp_ms", "info.request_time")),
181
+ ]
182
+ for t in traces
183
+ ]
184
+ render.resource_table(
185
+ f"Agent Traces · {exp_name}",
186
+ [("Trace ID", "left"), ("Status", "left"), ("Latency (ms)", "left"), ("Created", "left")],
187
+ rows,
188
+ )
189
+
190
+
191
+ @tracing.command("get")
192
+ @click.argument("trace_id")
193
+ @click.pass_obj
194
+ def tracing_get(obj, trace_id) -> None:
195
+ """Get a single trace by id (status, latency, span count, previews)."""
196
+ mlflow = _mlflow()
197
+ _configure(mlflow, obj.profile, None)
198
+ trace = mlflow.get_trace(trace_id)
199
+ if obj.output == "json":
200
+ render.emit_json(_trace_json(trace))
201
+ return
202
+ spans = _attr(trace, "data.spans", default=[]) or []
203
+ render.detail(
204
+ _BREADCRUMB,
205
+ trace_id,
206
+ {
207
+ "Status": _status_str(_attr(trace, "info.status", "info.state")),
208
+ "Latency (ms)": _attr(trace, "info.execution_time_ms", "info.execution_duration_ms"),
209
+ "Spans": len(spans),
210
+ "Request": _attr(trace, "info.request_preview", "data.request"),
211
+ "Response": _attr(trace, "info.response_preview", "data.response"),
212
+ "Created": timefmt.absolute(_attr(trace, "info.timestamp_ms", "info.request_time")),
213
+ },
214
+ status=_status_str(_attr(trace, "info.status", "info.state")),
215
+ )
216
+
217
+
218
+ def _trace_json(trace: Any) -> dict:
219
+ return {
220
+ "trace_id": _attr(trace, "info.trace_id", "info.request_id"),
221
+ "status": _status_str(_attr(trace, "info.status", "info.state")),
222
+ "execution_time_ms": _attr(trace, "info.execution_time_ms", "info.execution_duration_ms"),
223
+ "timestamp_ms": _attr(trace, "info.timestamp_ms", "info.request_time"),
224
+ }
225
+
226
+
227
+ # --- instrument: print the agent wiring snippet (no MLflow needed) ----------
228
+
229
+
230
+ @tracing.command("instrument")
231
+ @click.option(
232
+ "--destination",
233
+ default=None,
234
+ help="UC trace destination 'catalog.schema' (from `mason tracing setup`).",
235
+ )
236
+ @click.option(
237
+ "--experiment", default=None, help=f"MLflow experiment path (default: {_DEFAULT_EXPERIMENT})."
238
+ )
239
+ @click.pass_obj
240
+ def tracing_instrument(obj, destination, experiment) -> None:
241
+ """Print the snippet that routes an OpenAI Agents SDK agent's traces to UC."""
242
+ catalog, schema = _split_destination(destination) if destination else ("<catalog>", "<schema>")
243
+ exp_name = experiment or _DEFAULT_EXPERIMENT
244
+ dest = destination or f"{catalog}.{schema}"
245
+ code = (
246
+ "import mlflow\n"
247
+ "from mlflow.entities import UCSchemaLocation\n\n"
248
+ 'mlflow.set_tracking_uri("databricks")\n'
249
+ f'mlflow.set_experiment("{exp_name}")\n'
250
+ f'mlflow.tracing.set_destination(UCSchemaLocation(catalog_name="{catalog}", schema_name="{schema}"))\n'
251
+ "mlflow.openai.autolog() # OpenAI Agents SDK spans -> Unity Catalog traces\n"
252
+ "# NOTE: do NOT call agents.set_tracing_disabled(True) — that turns tracing off."
253
+ )
254
+ if obj.output == "json":
255
+ render.emit_json({"destination": dest, "experiment": exp_name, "snippet": code})
256
+ return
257
+ render.detail(
258
+ _BREADCRUMB,
259
+ dest,
260
+ {"Experiment": exp_name, "Destination": dest, "Requires": "mlflow[databricks]>=3.9.0"},
261
+ status="ACTIVE",
262
+ snippets=[("python", "python", code)],
263
+ )
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.5
2
+ Name: databricks-mason
3
+ Version: 0.1.0.dev0
4
+ Summary: Databricks integration for Mason
5
+ Author-email: Databricks <agent-feedback@databricks.com>
6
+ License: Apache-2.0
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: click>=8.1
9
+ Requires-Dist: databricks-sdk>=0.49
10
+ Requires-Dist: pyyaml>=6.0
11
+ Requires-Dist: rich>=13.7
12
+ Provides-Extra: tracing
13
+ Requires-Dist: mlflow[databricks]>=3.9.0; extra == 'tracing'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # `databricks-mason`
17
+
18
+ Mason is an experimental CLI for Databricks custom agent preview APIs and
19
+ deployments. It manages memory, sessions, tracing, and deployments from one
20
+ authenticated command.
21
+
22
+ > The underlying APIs are in preview and may need workspace enablement.
23
+
24
+ ## Installation
25
+
26
+ From PyPI:
27
+
28
+ ```sh
29
+ pip install databricks-mason
30
+ ```
31
+
32
+ From source:
33
+
34
+ ```sh
35
+ pip install 'git+https://github.com/databricks/databricks-ai-bridge.git#subdirectory=integrations/mason'
36
+ ```
37
+
38
+ For tracing commands, install Mason with tracing extras:
39
+
40
+ ```sh
41
+ pip install 'databricks-mason[tracing]'
42
+ ```
43
+
44
+ ## Authentication
45
+
46
+ Mason uses [Databricks authentication](https://docs.databricks.com/aws/en/dev-tools/cli/authentication).
47
+ If you do not already have credentials, authenticate a named profile first. You can
48
+ then ask Mason to validate and remember that profile:
49
+
50
+ ```sh
51
+ databricks auth login --profile <profile>
52
+ mason login --profile <profile>
53
+ mason sessions stores list
54
+ ```
55
+
56
+ `mason login` does not create credentials; it stores the selected profile in
57
+ `~/.mason/config.json`. `mason logout` forgets that selection without revoking the
58
+ underlying credentials. If Databricks SDK default authentication is already configured,
59
+ you can skip `mason login`. You can also pass `--profile/-p` for an individual command.
60
+ Use `--output json` for scripting.
61
+
62
+ ## Commands
63
+
64
+ ```text
65
+ mason [-p <profile>] [-o text|json]
66
+ login [--profile P]
67
+ logout
68
+ memory
69
+ stores create | list | get | update | delete
70
+ entries create | get | list | search | update | delete
71
+ sessions create | list | get | update | delete | fork
72
+ stores create | list | get | update | delete
73
+ items list | append | pop | clear
74
+ tracing
75
+ setup --catalog C --schema S [--experiment E]
76
+ list | get | instrument
77
+ deploy <name> --source PATH [--with-memory-store N]
78
+ [--with-session-store N] [--with-traces C.S] [--create-stores]
79
+ deployments list | get | logs | start | stop | delete
80
+ ```
@@ -0,0 +1,15 @@
1
+ databricks_mason/__init__.py,sha256=pgc0wgXP4fCC75mUUbHps-beYcmYkMBCRc7ZZ8GTuGg,40
2
+ databricks_mason/auth.py,sha256=pTEfMDsCo6U0x_AkJIi4VjmA-b0KyQQrdRmGujTzn6U,2810
3
+ databricks_mason/cli.py,sha256=JXcfCvAM9qrrTBpmduqlJDCuOllZNF5WE-0Ua1TSdJk,2235
4
+ databricks_mason/client.py,sha256=U-RTxKQ-i75Uaml7xFqHXoFCzIE9cglXB2vpciu0UrQ,10784
5
+ databricks_mason/deploy.py,sha256=fB0RaWC69M7Bv94iAHn3iEpxywXhEVtSr3rVtLcWFd8,10700
6
+ databricks_mason/errors.py,sha256=2wG6zAZcx52H3LiMjXVEw2ZA3njfCj-J1PGZd4yUyKQ,2020
7
+ databricks_mason/memory.py,sha256=ZpLKHb6MaEASXjfVzhqqUVxm1bk2WRdTrBhrQux4WRc,10993
8
+ databricks_mason/render.py,sha256=YnWm0ztqNavGYslpv7FSm_70KqGl3LgpZkzHkbw8AS0,5675
9
+ databricks_mason/sessions.py,sha256=kuwbhPNWTI1w4ikP0s-9eBRriPWpOXJd5jVqu-28r90,15085
10
+ databricks_mason/timefmt.py,sha256=LnvX7oodEItgYSiuY87q7vcumD9OjeMJ1N4O6veudfo,2824
11
+ databricks_mason/tracing.py,sha256=ou-KRGVdYLQcXQcAXVdjwA8lI62b22EYDoY1dv3q9G0,10127
12
+ databricks_mason-0.1.0.dev0.dist-info/METADATA,sha256=1pC5vbVJ5TtM0sk7pIYNblOO6IrKo7a_7Xia8jMx0-Y,2440
13
+ databricks_mason-0.1.0.dev0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
14
+ databricks_mason-0.1.0.dev0.dist-info/entry_points.txt,sha256=LDCR3fMglmwzFoxAU4goafUEVDMF7jfPAZRT4X_-Eqw,52
15
+ databricks_mason-0.1.0.dev0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mason = databricks_mason.cli:main