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.
- databricks_mason/__init__.py +1 -0
- databricks_mason/auth.py +83 -0
- databricks_mason/cli.py +72 -0
- databricks_mason/client.py +298 -0
- databricks_mason/deploy.py +325 -0
- databricks_mason/errors.py +53 -0
- databricks_mason/memory.py +347 -0
- databricks_mason/render.py +185 -0
- databricks_mason/sessions.py +442 -0
- databricks_mason/timefmt.py +85 -0
- databricks_mason/tracing.py +263 -0
- databricks_mason-0.1.0.dev0.dist-info/METADATA +80 -0
- databricks_mason-0.1.0.dev0.dist-info/RECORD +15 -0
- databricks_mason-0.1.0.dev0.dist-info/WHEEL +4 -0
- databricks_mason-0.1.0.dev0.dist-info/entry_points.txt +2 -0
|
@@ -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,,
|