@dbx-tools/appkit-mastra 0.1.41 → 0.1.48
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.
- package/README.md +122 -461
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -3
- package/dist/src/agents.d.ts +3 -3
- package/dist/src/agents.js +1 -1
- package/dist/src/chart.d.ts +7 -1
- package/dist/src/chart.js +3 -5
- package/dist/src/config.d.ts +18 -13
- package/dist/src/genie.d.ts +0 -12
- package/dist/src/genie.js +2 -16
- package/dist/src/history.d.ts +6 -6
- package/dist/src/history.js +6 -6
- package/dist/src/model.d.ts +24 -131
- package/dist/src/model.js +39 -268
- package/dist/src/plugin.d.ts +13 -12
- package/dist/src/plugin.js +38 -34
- package/dist/src/server.d.ts +13 -20
- package/dist/src/server.js +15 -22
- package/dist/src/serving.d.ts +14 -98
- package/dist/src/serving.js +18 -163
- package/dist/src/writer.d.ts +2 -2
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/index.ts +2 -14
- package/package.json +6 -11
- package/src/agents.ts +3 -3
- package/src/chart.ts +3 -5
- package/src/config.ts +18 -13
- package/src/genie.ts +4 -22
- package/src/history.ts +6 -6
- package/src/model.ts +57 -291
- package/src/plugin.ts +45 -42
- package/src/server.ts +15 -22
- package/src/serving.ts +18 -220
- package/src/writer.ts +2 -2
- package/dist/src/intercept.d.ts +0 -48
- package/dist/src/intercept.js +0 -167
- package/dist/src/tools/email.d.ts +0 -74
- package/dist/src/tools/email.js +0 -121
- package/src/intercept.ts +0 -206
- package/src/tools/email.ts +0 -158
package/README.md
CHANGED
|
@@ -2,21 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
An AppKit plugin that hosts [Mastra](https://mastra.ai) agents inside a
|
|
4
4
|
Databricks App with user-scoped workspace auth (OBO), optional
|
|
5
|
-
Lakebase-backed memory, and
|
|
6
|
-
|
|
5
|
+
Lakebase-backed memory, and the standard Mastra agent stream the React
|
|
6
|
+
client drives via `@mastra/client-js`.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
`[agents](https://developers.databricks.com/docs/appkit/v0/plugins/agents)`
|
|
8
|
+
Wiring it up looks the same as the AppKit
|
|
9
|
+
[`agents`](https://developers.databricks.com/docs/appkit/v0/plugins/agents)
|
|
11
10
|
plugin - same `createAgent` / `tool` helpers, same `tools(plugins)`
|
|
12
|
-
callback
|
|
13
|
-
|
|
11
|
+
callback, same `ToolkitOptions`. Switching a given agent between the two
|
|
12
|
+
is a one-line import change.
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
The implementation lives under `src/`: agent registration in
|
|
15
|
+
[`agents.ts`](src/agents.ts), the plugin + routes in
|
|
16
|
+
[`plugin.ts`](src/plugin.ts) / [`server.ts`](src/server.ts), Model
|
|
17
|
+
Serving resolution in [`model.ts`](src/model.ts) / [`serving.ts`](src/serving.ts),
|
|
18
|
+
Genie tooling in [`genie.ts`](src/genie.ts), and the chart pipeline in
|
|
19
|
+
[`chart.ts`](src/chart.ts).
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
example - swap `agents` for `mastra` and the imports stay structurally
|
|
19
|
-
identical:
|
|
21
|
+
## Quick start
|
|
20
22
|
|
|
21
23
|
```ts
|
|
22
24
|
import { analytics, createApp, files, lakebase, server } from "@databricks/appkit";
|
|
@@ -44,511 +46,170 @@ await createApp({
|
|
|
44
46
|
analytics(),
|
|
45
47
|
files(),
|
|
46
48
|
// Drop `lakebase()` in and `mastra` auto-enables per-agent thread
|
|
47
|
-
// storage
|
|
48
|
-
// semantic-recall vector index. Skip it for a stateless agent.
|
|
49
|
+
// storage plus shared semantic recall. Skip it for a stateless agent.
|
|
49
50
|
lakebase(),
|
|
50
51
|
mastra({ agents: support }),
|
|
51
52
|
],
|
|
52
53
|
});
|
|
53
54
|
```
|
|
54
55
|
|
|
55
|
-
`createAgent` is a no-op identity helper that anchors type inference
|
|
56
|
+
`createAgent` is a no-op identity helper that anchors type inference;
|
|
56
57
|
`tool` is the AppKit-shaped factory (`{ description, schema, execute }`)
|
|
57
|
-
that
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- **No `lakebase()` registered** ▸ agent is fully stateless. No threads,
|
|
62
|
-
no recall. Same as `mastra()` alone.
|
|
63
|
-
- `**lakebase()` registered, no `storage` / `memory` config** ▸ both
|
|
64
|
-
auto-turn on. Each agent gets its own `PostgresStore` schema; every
|
|
65
|
-
agent shares one `PgVector` recall index.
|
|
66
|
-
- **Per-agent opt-out** ▸ `createAgent({ ..., memory: false, storage: false })`
|
|
67
|
-
for routing / one-shot agents that don't need history.
|
|
68
|
-
- **Per-agent override** ▸ pass a `PgVectorConfig` / `PostgresStoreConfig` object on the agent for a private index or a shared external schema.
|
|
69
|
-
|
|
70
|
-
See [Memory + storage](#memory--storage) for the full cascade and worked
|
|
71
|
-
examples.
|
|
58
|
+
that adapts to Mastra's `createTool` under the hood. The full config
|
|
59
|
+
shapes (`MastraAgentDefinition`, the plugin config) - every field and
|
|
60
|
+
default - are typed in [`config.ts`](src/config.ts).
|
|
72
61
|
|
|
73
|
-
On the React side,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
so
|
|
62
|
+
On the React side, drop in the prebuilt chat UI from
|
|
63
|
+
[`@dbx-tools/appkit-mastra-ui`](../appkit-mastra-ui); it wires itself
|
|
64
|
+
from the plugin's published client config and streams over
|
|
65
|
+
`@mastra/client-js`, so there's no transport code to write:
|
|
77
66
|
|
|
78
67
|
```tsx
|
|
79
|
-
import {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
import { useMemo } from "react";
|
|
84
|
-
|
|
85
|
-
function Chat() {
|
|
86
|
-
const config = usePluginClientConfig<MastraClientConfig>("mastra");
|
|
87
|
-
const transport = useMemo(
|
|
88
|
-
() => new DefaultChatTransport({ api: chatUrl(config) }),
|
|
89
|
-
[config],
|
|
90
|
-
);
|
|
91
|
-
const { messages, sendMessage } = useChat({ transport });
|
|
92
|
-
// ...
|
|
68
|
+
import { MastraChat } from "@dbx-tools/appkit-mastra-ui/react";
|
|
69
|
+
|
|
70
|
+
export default function ChatPage() {
|
|
71
|
+
return <MastraChat showModelPicker />;
|
|
93
72
|
}
|
|
94
73
|
```
|
|
95
74
|
|
|
96
|
-
|
|
97
|
-
shape and per-agent selection.
|
|
98
|
-
|
|
99
|
-
## Sensible defaults
|
|
100
|
-
|
|
101
|
-
The plugin is opinionated about what "no config" should mean. Everything
|
|
102
|
-
below can be overridden, but the bare path works:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
| Scenario | What you get |
|
|
106
|
-
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
107
|
-
| `mastra()` | One built-in `default` analyst agent, model from `/serving-endpoints`; memory + storage auto-on if the `lakebase` plugin is registered |
|
|
108
|
-
| `mastra({ agents: def })` | Single-agent shorthand - `def` is registered and marked as default |
|
|
109
|
-
| `mastra({ agents: [def1, def2] })` | Array shorthand - keys come from each `def.name` (or `agent_<i>`); first one is default |
|
|
110
|
-
| `mastra({ agents: { x: def, y: def }})` | Record - keys are the registered ids; first key is the default |
|
|
111
|
-
| No `defaultAgent` set | First registered agent wins |
|
|
112
|
-
| No `model` on an agent | Falls back to `config.defaultModel`, then `DATABRICKS_SERVING_ENDPOINT_NAME`, then walks `defaultModelFallbacks` (Thinking → Balanced → Fast tiers, Claude ↔ GPT ↔ Gemini interleaved within each, then open-weights) and picks the first endpoint actually present in the workspace |
|
|
113
|
-
| No `name` on a definition | Uses the registry key as `Agent.name` |
|
|
114
|
-
| No `tools` on an agent | Inherits plugin-level `config.tools` ambient set (if any) |
|
|
115
|
-
| No `storage` / `memory` and `lakebase()` registered | Both auto-default to `true`. Pass `false` (or a custom config object) on the plugin or an agent to opt out / override. |
|
|
116
|
-
| `storage` / `memory` on an agent | Cascades from plugin: storage namespaces **per-agent** (`schemaName: "mastra_<agentId>"`), vector recall is **shared** across agents on one `PgVector` singleton. |
|
|
75
|
+
## Memory + storage
|
|
117
76
|
|
|
77
|
+
With no config, memory and storage are driven entirely by whether the
|
|
78
|
+
`lakebase` plugin is registered:
|
|
118
79
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
| `name` | Display name. Defaults to the registry key. |
|
|
125
|
-
| `description` | Long-form description, surfaced as `Agent.description`. |
|
|
126
|
-
| `instructions` | System prompt body. Required. |
|
|
127
|
-
| `model` | Per-agent model override. String = `modelId` sugar; otherwise a Mastra `DynamicArgument`. |
|
|
128
|
-
| `tools` | Plain record OR `(plugins) => tools` callback (see below). |
|
|
129
|
-
| `memory` | `false`, `true`, or a `PgVectorConfig`. Cascades from `config.memory`. **Default: shared singleton `PgVector` across every agent** - object override switches to a dedicated index. |
|
|
130
|
-
| `storage` | `false`, `true`, or a `PostgresStoreConfig`. Cascades from `config.storage`. **Default: per-agent namespace** via `schemaName: "mastra_<agentId>"` so threads stay isolated. |
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
Plugin-level fields:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
| Field | Description |
|
|
137
|
-
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
138
|
-
| `agents` | The registry. Omit for a built-in `default` analyst. |
|
|
139
|
-
| `defaultAgent` | Id that `chatRoute` binds to when no `:agentId` is supplied. Defaults to first registered. |
|
|
140
|
-
| `defaultModel` | Fallback for any agent that omits `model`. Same shape (string sugar or `DynamicArgument`). |
|
|
141
|
-
| `defaultModelFallbacks` | Priority-ordered list walked when no `model` / env / override is set. First entry whose endpoint exists in the workspace wins. Default chains the three `ModelTier`s (Thinking → Balanced → Fast); within each tier providers are interleaved Claude ↔ GPT ↔ Gemini with open-weights appended. Compose your own with `modelsForTier(ModelTier.Fast)` or read straight from `MODEL_CATALOG`. |
|
|
142
|
-
| `tools` | Ambient tools merged into every agent (per-agent tools win on collisions). |
|
|
143
|
-
| `storage` | `undefined` (default) auto-enables when `lakebase()` is registered; `true` does the same explicitly; `false` opts out; an object opens a dedicated `PostgresStore`. Per-agent default is `schemaName: "mastra_<agentId>"`. |
|
|
144
|
-
| `memory` | `undefined` (default) auto-enables when `lakebase()` is registered; `true` does the same explicitly; `false` opts out; an object opens a dedicated `PgVector`. Default behavior: one shared `PgVector` singleton across every agent. |
|
|
145
|
-
| `modelFuzzyMatch` | `false` to disable fuzzy snapping of model ids against the workspace's Model Serving catalogue. Defaults to `true`. |
|
|
146
|
-
| `modelFuzzyThreshold` | Fuse.js score threshold (`0` exact, `1` anything). Defaults to `0.4`. |
|
|
147
|
-
| `modelCacheTtlMs` | TTL for the cached endpoint list, per workspace host. Defaults to 5 minutes. Concurrent callers share one in-flight fetch. |
|
|
148
|
-
| `modelOverride` | `false` to disable per-request `X-Mastra-Model` / `?model=` / body overrides. Defaults to `true`. |
|
|
149
|
-
| `styleInstructions` | Style guardrails appended to every agent's `instructions` to curb LLM-isms (em dashes, emojis, sycophantic openers, throwaway closers). `undefined` (default) uses the built-in `DEFAULT_STYLE_INSTRUCTIONS`; a string replaces it; `false` disables. |
|
|
80
|
+
- **No `lakebase()`** - the agent is fully stateless: no threads, no
|
|
81
|
+
recall.
|
|
82
|
+
- **`lakebase()` registered** - both auto-enable. Each agent gets its
|
|
83
|
+
own `PostgresStore` schema (threads stay isolated per agent); every
|
|
84
|
+
agent shares one `PgVector` semantic-recall index.
|
|
150
85
|
|
|
86
|
+
Override per plugin or per agent by passing `memory` / `storage` as
|
|
87
|
+
`false` (opt out), `true` (explicit on), or a config object (dedicated
|
|
88
|
+
index / schema). The fields are typed in [`config.ts`](src/config.ts);
|
|
89
|
+
the wiring is in [`memory.ts`](src/memory.ts).
|
|
151
90
|
|
|
152
91
|
## The `tools(plugins)` callback
|
|
153
92
|
|
|
154
|
-
Each
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
- AppKit-shaped tools built with the `tool()` wrapper (below)
|
|
160
|
-
- Vercel AI SDK tools (`tool({...})` from `ai`)
|
|
161
|
-
- Provider-defined tools (e.g. `openai.tools.webSearch(...)`)
|
|
162
|
-
- Toolkits returned from `plugins.<name>.toolkit(...)`
|
|
93
|
+
Each agent supplies either a static `tools: { ... }` record or a
|
|
94
|
+
`tools(plugins)` callback. The returned record accepts any tool shape
|
|
95
|
+
Mastra understands - Mastra `createTool` tools, AppKit-shaped `tool()`
|
|
96
|
+
tools, Vercel AI SDK tools, provider-defined tools, and toolkits from
|
|
97
|
+
`plugins.<name>.toolkit(...)`.
|
|
163
98
|
|
|
164
99
|
```ts
|
|
165
100
|
tools(plugins) {
|
|
166
101
|
return {
|
|
167
|
-
//
|
|
168
|
-
...plugins.
|
|
169
|
-
...plugins.files.toolkit({ only: ["uploads.read"] }), // filtered subset
|
|
170
|
-
|
|
171
|
-
// AppKit-shaped inline tool.
|
|
102
|
+
...plugins.analytics.toolkit(), // every analytics tool
|
|
103
|
+
...plugins.files.toolkit({ only: ["uploads.read"] }), // filtered subset
|
|
172
104
|
get_weather: tool({
|
|
173
105
|
description: "Weather",
|
|
174
106
|
schema: z.object({ city: z.string() }),
|
|
175
107
|
execute: async ({ city }) => `Sunny in ${city}`,
|
|
176
108
|
}),
|
|
177
|
-
|
|
178
|
-
// Existing Mastra tool dropped in unchanged.
|
|
179
|
-
save_doc: existingMastraTool,
|
|
180
109
|
};
|
|
181
110
|
}
|
|
182
111
|
```
|
|
183
112
|
|
|
184
|
-
`plugins` is a
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
`
|
|
190
|
-
`executeAgentTool`, so OBO auth (`asUser`) and telemetry spans stay
|
|
191
|
-
intact.
|
|
192
|
-
|
|
193
|
-
`plugins.genie` is special-cased: it returns a flat set of
|
|
194
|
-
Mastra tools the central agent drives directly (no inner
|
|
195
|
-
orchestrator agent). Two shared, space-agnostic tools register
|
|
196
|
-
once regardless of how many spaces are wired:
|
|
197
|
-
|
|
198
|
-
- `get_statement(statement_id, limit?)` - fetch rows for a
|
|
199
|
-
Genie statement. Use only when the agent needs to read values
|
|
200
|
-
to reason about them; otherwise embed `[data:<statement_id>]`.
|
|
201
|
-
- `prepare_chart({statement_id, title?, description?})` - mint a
|
|
202
|
-
`chartId` (v4 UUID), kick off a background chart-planner task,
|
|
203
|
-
cache the resolved Echarts spec under the id for one hour.
|
|
204
|
-
Returns `{chartId}` synchronously so the agent can embed
|
|
205
|
-
`[chart:<chartId>]` in prose without blocking.
|
|
206
|
-
|
|
207
|
-
Three per-space tools register per wired alias (`ask_genie`,
|
|
208
|
-
`get_space_description`, `get_space_serialized` for the
|
|
209
|
-
`default` alias; `ask_genie_<alias>`, etc. for additional
|
|
210
|
-
aliases):
|
|
211
|
-
|
|
212
|
-
- `ask_genie({question})` - drives one `genieEventChat` turn
|
|
213
|
-
and streams wire events (status / thinking / sql) through
|
|
214
|
-
`ctx.writer`. Returns `{message: GenieMessage}`; rows are
|
|
215
|
-
NOT fetched eagerly.
|
|
216
|
-
- `get_space_description()` - cheap title / description /
|
|
217
|
-
warehouse id lookup.
|
|
218
|
-
- `get_space_serialized()` - full `GenieSpace` JSON for
|
|
219
|
-
column-level grounding when the description isn't enough.
|
|
220
|
-
|
|
221
|
-
AppKit's `genie()` plugin is honored only for its `spaces`
|
|
222
|
-
config (and the matching `app.yaml`-declared resources). The
|
|
223
|
-
tools talk to Genie directly via `@dbx-tools/genie`
|
|
224
|
-
(`genieEventChat`) and the workspace
|
|
225
|
-
`statementExecution.getStatement` API.
|
|
226
|
-
|
|
227
|
-
The orchestration prompt (decompose, ask Genie focused
|
|
228
|
-
sub-questions, place markers in prose) ships as the exported
|
|
229
|
-
`GENIE_INSTRUCTIONS` string - compose it into your agent's
|
|
230
|
-
`instructions` to get the canonical behavior:
|
|
231
|
-
|
|
232
|
-
```ts
|
|
233
|
-
import { createAgent, GENIE_INSTRUCTIONS } from "@dbx-tools/appkit-mastra";
|
|
234
|
-
|
|
235
|
-
const support = createAgent({
|
|
236
|
-
instructions: `${baseInstructions}\n\n${GENIE_INSTRUCTIONS}`,
|
|
237
|
-
tools(plugins) {
|
|
238
|
-
return { ...plugins.genie?.toolkit() };
|
|
239
|
-
},
|
|
240
|
-
});
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
`GENIE_INSTRUCTIONS` references the default (`default` alias)
|
|
244
|
-
tool names. Multi-space deployments should write a custom
|
|
245
|
-
variant that names the suffixed per-space tools
|
|
246
|
-
(e.g. `ask_genie_sales`).
|
|
247
|
-
|
|
248
|
-
Genie writer event flow:
|
|
249
|
-
|
|
250
|
-
- Each `ask_genie` call emits a Mastra-only `started` event
|
|
251
|
-
before any network round-trip, then forwards every wire
|
|
252
|
-
`GenieChatEvent` (status / thinking / sql / rows / suggested
|
|
253
|
-
/ message / result / error) through `ctx.writer`.
|
|
254
|
-
- Charts ride out-of-band: `prepare_chart` mints a `chartId`
|
|
255
|
-
synchronously, the planner runs in the background and writes
|
|
256
|
-
the spec to the chart cache (1h TTL). The host UI long-polls
|
|
257
|
-
`${MastraClientConfig.embedPathTemplate}` (`/embed/chart/:id`)
|
|
258
|
-
by id and renders inline at the matching `[chart:<chartId>]`
|
|
259
|
-
marker.
|
|
260
|
-
- After a hard reload, the live `started` / `status` / `sql`
|
|
261
|
-
events are gone (they only ride the writer, not persisted
|
|
262
|
-
tool results); the central agent's text reply (with embedded
|
|
263
|
-
`[data:<statement_id>]` / `[chart:<chartId>]` markers) is
|
|
264
|
-
preserved in the message history and the host UI re-resolves
|
|
265
|
-
the markers on its own.
|
|
266
|
-
|
|
267
|
-
### `render_data` (system-default ambient tool)
|
|
268
|
-
|
|
269
|
-
`buildAgents` registers a system-level `render_data` tool on
|
|
270
|
-
every agent so the model can submit any tabular dataset for
|
|
271
|
-
inline charting. Users can shadow it by including a same-named
|
|
272
|
-
tool in `config.tools` or in a per-agent `tools` map; otherwise
|
|
273
|
-
it's just there.
|
|
274
|
-
|
|
275
|
-
The tool is generic - not coupled to Genie or any particular
|
|
276
|
-
upstream. Input is `{ title, description?, data: Row[] }` where
|
|
277
|
-
`data` is an array of objects keyed by column name (a SQL row
|
|
278
|
-
set, an API response, a hand-built array, etc.).
|
|
279
|
-
|
|
280
|
-
#### How it works
|
|
281
|
-
|
|
282
|
-
`render_data` is a thin wrapper around `prepareChart`, the same
|
|
283
|
-
orchestrator the Genie `prepare_chart` tool uses. It mints a
|
|
284
|
-
`chartId` synchronously, caches an empty placeholder, and kicks
|
|
285
|
-
off the chart-planner in the background. The tool returns just
|
|
286
|
-
`{ chartId }` so the model can embed `[chart:<chartId>]` in
|
|
287
|
-
prose immediately without blocking on chart generation.
|
|
288
|
-
|
|
289
|
-
1. Mint a `chartId` (v4 UUID via `crypto.randomUUID()`).
|
|
290
|
-
2. Cache an empty `{ chartId }` placeholder so the first
|
|
291
|
-
`fetchChart` call always sees an entry (no spurious 404 race).
|
|
292
|
-
3. Fire-and-forget: resolve the dataset (trivial for
|
|
293
|
-
`render_data` since the rows are already in hand) and run
|
|
294
|
-
`runChartPlanner` in the background. On success the cache
|
|
295
|
-
entry settles with `{ chartId, result }` (containing the
|
|
296
|
-
`chartType` and full `EChartsOption`). On failure it settles
|
|
297
|
-
with `{ chartId, error }`.
|
|
298
|
-
4. Return `{ chartId }` to the LLM immediately.
|
|
299
|
-
|
|
300
|
-
The host UI resolves `[chart:<chartId>]` markers by hitting
|
|
301
|
-
the plugin's generic `GET /embed/chart/:id` route, which long-polls
|
|
302
|
-
until the entry settles or the server-side timeout elapses.
|
|
303
|
-
|
|
304
|
-
#### Inline placement contract
|
|
305
|
-
|
|
306
|
-
The model embeds `[chart:<chartId>]` on its own line in its
|
|
307
|
-
markdown reply at the position where the chart should appear
|
|
308
|
-
(the `<chartId>` is the v4 UUID the tool returned):
|
|
309
|
-
|
|
310
|
-
```markdown
|
|
311
|
-
## Audit Score
|
|
312
|
-
|
|
313
|
-
Audit Score is stable at ~94%, hovering between 93.5 and 95.0.
|
|
314
|
-
|
|
315
|
-
[chart:a3f9c1d2-7b4e-4c1a-9f2d-1e6b8c0a5d31]
|
|
316
|
-
|
|
317
|
-
## Service Time
|
|
318
|
-
|
|
319
|
-
Service time is the outlier at 162.5s, up from a target of 150s.
|
|
320
|
-
|
|
321
|
-
[chart:b7e2d4f1-3a9c-4e58-8d10-2f7a4b6c9e02]
|
|
322
|
-
```
|
|
113
|
+
`plugins` is a runtime Proxy that auto-discovers any registered AppKit
|
|
114
|
+
`ToolProvider` plugin (`analytics`, `files`, `lakebase`, `genie`, and
|
|
115
|
+
any third-party plugin implementing the standard toolkit interface).
|
|
116
|
+
Tool calls dispatch through the plugin's `executeAgentTool`, so OBO auth
|
|
117
|
+
and telemetry spans stay intact. Plugins that aren't registered resolve
|
|
118
|
+
to `undefined`, so guard optional backings with `?.` / `?? {}`.
|
|
323
119
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
before its chart settles in the cache shows a "Queueing chart"
|
|
327
|
-
skeleton; a chart whose marker the model forgot to place falls
|
|
328
|
-
through to the end of the reply as a fallback. All three states
|
|
329
|
-
(queueing, rendering, rendered) share the same fixed-height
|
|
330
|
-
frame so the layout doesn't jump as charts resolve.
|
|
331
|
-
|
|
332
|
-
#### Trade-offs
|
|
333
|
-
|
|
334
|
-
- Chart entries live in the cache with a 1h TTL. After expiry
|
|
335
|
-
the `/embed/chart/:id` route returns 404. The model can call
|
|
336
|
-
`render_data` again on the next turn if the user wants the
|
|
337
|
-
chart back.
|
|
338
|
-
- The chart-planner is a separate model call per dataset (fast
|
|
339
|
-
tier, but still ~1-3s each). For an N-chart turn, latency is
|
|
340
|
-
`max(planners)` since the planners run concurrently in the
|
|
341
|
-
background and the tool returns immediately.
|
|
342
|
-
|
|
343
|
-
Plugins that aren't registered (or don't implement the toolkit
|
|
344
|
-
interface) resolve to `undefined` at runtime, so guard with `?.` /
|
|
345
|
-
`?? {}` when a backing plugin is optional in some environments:
|
|
120
|
+
`toolkit(opts)` takes the same `ToolkitOptions` AppKit exposes
|
|
121
|
+
(`prefix`, `only`, `except`, `rename`), passed through verbatim.
|
|
346
122
|
|
|
347
|
-
|
|
348
|
-
tools(plugins) {
|
|
349
|
-
return {
|
|
350
|
-
...(plugins.analytics?.toolkit() ?? {}),
|
|
351
|
-
...(plugins.genie?.toolkit({ prefix: "g_" }) ?? {}),
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
```
|
|
123
|
+
### `tool()` vs `createTool()`
|
|
355
124
|
|
|
356
|
-
`
|
|
357
|
-
AppKit
|
|
125
|
+
`tool()` mirrors `@databricks/appkit/beta`'s shape so tool code is
|
|
126
|
+
portable between the AppKit `agents` plugin and this one. Reach for
|
|
127
|
+
Mastra's `createTool` when you need Mastra-only fields (`outputSchema`,
|
|
128
|
+
`suspendSchema`, `requireApproval`, `mcp`, ...). An omitted `tool()` `id`
|
|
129
|
+
is auto-derived from the description (slug + short hash) so traces stay
|
|
130
|
+
stable across runs.
|
|
358
131
|
|
|
359
|
-
|
|
360
|
-
- `only?: string[]` / `except?: string[]` - allow/deny list against the
|
|
361
|
-
local tool name
|
|
362
|
-
- `rename?: Record<string, string>` - remap individual keys
|
|
132
|
+
## Genie
|
|
363
133
|
|
|
364
|
-
|
|
134
|
+
`plugins.genie` returns a flat set of Mastra tools the central agent
|
|
135
|
+
drives directly - no inner orchestrator agent. The agent asks the
|
|
136
|
+
configured Genie space focused sub-questions, embeds inline markers in
|
|
137
|
+
its prose, and the host UI resolves them (see [Embeds](#embeds)).
|
|
365
138
|
|
|
366
|
-
The
|
|
367
|
-
|
|
368
|
-
|
|
139
|
+
The orchestration prompt ships as the exported `GENIE_INSTRUCTIONS`
|
|
140
|
+
string; compose it into your agent's `instructions` to get the canonical
|
|
141
|
+
behavior:
|
|
369
142
|
|
|
370
143
|
```ts
|
|
371
|
-
import {
|
|
372
|
-
|
|
373
|
-
// AppKit-shaped (description / schema / flat-arg execute).
|
|
374
|
-
const weather = tool({
|
|
375
|
-
description: "Weather",
|
|
376
|
-
schema: z.object({ city: z.string() }),
|
|
377
|
-
execute: async ({ city }) => `Sunny in ${city}`,
|
|
378
|
-
});
|
|
144
|
+
import { createAgent, GENIE_INSTRUCTIONS } from "@dbx-tools/appkit-mastra";
|
|
379
145
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
inputSchema: z.object({ key: z.string(), value: z.any() }),
|
|
385
|
-
outputSchema: z.object({ saved: z.boolean() }),
|
|
386
|
-
requireApproval: true,
|
|
387
|
-
execute: async (input, ctx) => {
|
|
388
|
-
await ctx.mastra?.getStorage()?.set(input.key, input.value);
|
|
389
|
-
return { saved: true };
|
|
146
|
+
const support = createAgent({
|
|
147
|
+
instructions: `${baseInstructions}\n\n${GENIE_INSTRUCTIONS}`,
|
|
148
|
+
tools(plugins) {
|
|
149
|
+
return { ...plugins.genie?.toolkit() };
|
|
390
150
|
},
|
|
391
151
|
});
|
|
392
152
|
```
|
|
393
153
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
154
|
+
AppKit's stock `genie()` plugin is honored only for its `spaces` config
|
|
155
|
+
(and the matching `app.yaml` resources); the tools talk to Genie
|
|
156
|
+
directly via `@dbx-tools/genie` (`genieEventChat`) and the workspace
|
|
157
|
+
`statementExecution` API. Each Genie turn forwards its wire
|
|
158
|
+
`GenieChatEvent`s through `ctx.writer` for live UI progress. The exact
|
|
159
|
+
tool set, ids, and writer-event contract live in [`genie.ts`](src/genie.ts)
|
|
160
|
+
and the `GenieWriterEvent` docs in
|
|
161
|
+
[`@dbx-tools/appkit-mastra-shared`](../appkit-mastra-shared).
|
|
162
|
+
|
|
163
|
+
## Embeds
|
|
164
|
+
|
|
165
|
+
Charts and data tables ride out-of-band via inline markers so the model
|
|
166
|
+
never blocks on or round-trips large payloads:
|
|
167
|
+
|
|
168
|
+
- `render_data` (a system-default ambient tool) and the Genie
|
|
169
|
+
`prepare_chart` tool both mint a `chartId` synchronously, kick chart
|
|
170
|
+
planning into the background, and return `{ chartId }` immediately. The
|
|
171
|
+
model embeds `[chart:<chartId>]` on its own line where the chart should
|
|
172
|
+
appear.
|
|
173
|
+
- `[data:<statement_id>]` markers resolve to inline table data.
|
|
174
|
+
|
|
175
|
+
The host UI splits the assistant text on these markers and long-polls
|
|
176
|
+
the plugin's generic `${basePath}/embed/:type/:id` route until each
|
|
177
|
+
entry settles. Cache entries carry a 1h TTL; after expiry the route
|
|
178
|
+
404s and the marker falls through harmlessly. Implementation and the
|
|
179
|
+
placement contract are in [`chart.ts`](src/chart.ts); the wire shapes
|
|
180
|
+
are in [`@dbx-tools/appkit-mastra-shared`](../appkit-mastra-shared).
|
|
401
181
|
|
|
402
182
|
## Model resolution
|
|
403
183
|
|
|
404
|
-
Each agent call resolves a
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
When any of these is set the resolver fuzzy-matches that single id
|
|
412
|
-
against the live `/serving-endpoints` list, in priority order:
|
|
413
|
-
|
|
414
|
-
1. Per-request override (`X-Mastra-Model` header, `?model=` query,
|
|
415
|
-
or `model` / `modelId` body field; see below)
|
|
416
|
-
2. Per-agent `def.model` (string sugar or `DynamicArgument`)
|
|
417
|
-
3. Plugin-level `config.defaultModel`
|
|
418
|
-
4. `DATABRICKS_SERVING_ENDPOINT_NAME` env var
|
|
419
|
-
|
|
420
|
-
The matcher is `fuse.js` extended search with tokens split on
|
|
421
|
-
non-word characters and AND-joined. Exact matches win immediately;
|
|
422
|
-
loose tokens like `"claude sonnet"` snap to
|
|
423
|
-
`databricks-claude-sonnet-4-6`, `"llama 70b"` to
|
|
424
|
-
`databricks-meta-llama-3-3-70b-instruct`, `"DBRX"` to
|
|
425
|
-
`databricks-dbrx-instruct`, and so on. If no candidate scores below
|
|
426
|
-
`modelFuzzyThreshold` (default `0.4`) the input is returned verbatim
|
|
427
|
-
and Databricks surfaces the canonical 404.
|
|
428
|
-
|
|
429
|
-
### No explicit ask (tier-aware fallback list)
|
|
430
|
-
|
|
431
|
-
When nothing is set, the resolver walks an opinionated
|
|
432
|
-
priority-ordered list and returns **the first id that is actually
|
|
433
|
-
present in the workspace's endpoint listing**. This is how a workspace
|
|
434
|
-
without Claude Opus still gets a sensible default automatically -
|
|
435
|
-
the resolver skips ahead to whichever Sonnet / GPT-5 / Gemini / Llama
|
|
436
|
-
variant is wired up.
|
|
437
|
-
|
|
438
|
-
The catalogue is grouped two ways:
|
|
439
|
-
|
|
440
|
-
- By **capability tier** via the `ModelTier` enum:
|
|
441
|
-
`ModelTier.Thinking` (deepest reasoning), `ModelTier.Balanced`
|
|
442
|
-
(cost/latency sweet spot), `ModelTier.Fast` (cheap & quick for
|
|
443
|
-
classification / routing / simple summarisation).
|
|
444
|
-
- By **provider** within each tier: `claude`, `gpt`, `gemini`,
|
|
445
|
-
`openSource`.
|
|
446
|
-
|
|
447
|
-
Both views live on `MODEL_CATALOG[tier][provider]`. The walked
|
|
448
|
-
`FALLBACK_MODEL_IDS` chains the three tiers in descending power
|
|
449
|
-
(Thinking → Balanced → Fast); within each tier providers are
|
|
450
|
-
round-robin-zipped (Claude ↔ GPT ↔ Gemini) before the open-weights
|
|
451
|
-
tail is appended as the universal floor.
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
| Tier (most powerful first) | Claude | GPT | Gemini | Open weights |
|
|
455
|
-
| -------------------------- | -------------------------------- | ------------------------------------- | ------------------------------- | ---------------------------------------------- |
|
|
456
|
-
| `ModelTier.Thinking` | Opus 4.8 → 4.7 → 4.6 → 4.5 → 4.1 | 5.5 Pro | 3.1 Pro → 3 Pro → 2.5 Pro | Llama 4 Maverick, GPT-OSS 120B, Llama 3.1 405B |
|
|
457
|
-
| `ModelTier.Balanced` | Sonnet 4.6 → 4.5 → 4 | 5.5 → 5.4 → 5.2 → 5.1 → 5 | 3.5 Flash → 3 Flash → 2.5 Flash | Llama 3.3 70B, Qwen3-Next 80B, Qwen35 122B |
|
|
458
|
-
| `ModelTier.Fast` | Haiku 4.5 | 5.4 mini → 5.4 nano → 5 mini → 5 nano | 3.1 Flash Lite | GPT-OSS 20B, Gemma 3 12B, Llama 3.1 8B |
|
|
459
|
-
|
|
184
|
+
Each agent call resolves a model lazily (so concurrent requests keep
|
|
185
|
+
distinct user identities), delegating to
|
|
186
|
+
[`@dbx-tools/model`](../model) for the actual workspace-aware selection
|
|
187
|
+
(fuzzy name matching, score-based class classification, offline
|
|
188
|
+
fallbacks). Its exports are re-exported here, so `ModelClass` /
|
|
189
|
+
`modelForClass` and friends are available straight off
|
|
190
|
+
`@dbx-tools/appkit-mastra`.
|
|
460
191
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
call time so it still works when the literal top pick isn't deployed.
|
|
192
|
+
The plugin layers on the config sources for *which* model an agent asks
|
|
193
|
+
for, in priority order: a per-request override, the per-agent `model`,
|
|
194
|
+
the plugin `defaultModel`, then `DATABRICKS_SERVING_ENDPOINT_NAME`. With
|
|
195
|
+
none set, resolution falls through to the dynamic class picker.
|
|
466
196
|
|
|
467
197
|
```ts
|
|
468
|
-
import { createAgent,
|
|
198
|
+
import { createAgent, ModelClass, modelForClass } from "@dbx-tools/appkit-mastra";
|
|
469
199
|
|
|
470
200
|
const classifier = createAgent({
|
|
471
|
-
instructions: "Classify this email into
|
|
472
|
-
model:
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
const planner = createAgent({
|
|
476
|
-
instructions: "Plan a multi-step data migration.",
|
|
477
|
-
model: modelForTier(ModelTier.Thinking),
|
|
478
|
-
});
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
#### Bias the plugin-level fallback toward a tier
|
|
482
|
-
|
|
483
|
-
`modelsForTier(tier)` returns the priority-ordered list for one tier;
|
|
484
|
-
pass it to `defaultModelFallbacks` to scope the auto-resolver:
|
|
485
|
-
|
|
486
|
-
```ts
|
|
487
|
-
import { mastra, ModelTier, modelsForTier } from "@dbx-tools/appkit-mastra";
|
|
488
|
-
|
|
489
|
-
mastra({
|
|
490
|
-
// All agents that omit `model` will land on a Fast-tier endpoint.
|
|
491
|
-
defaultModelFallbacks: modelsForTier(ModelTier.Fast),
|
|
201
|
+
instructions: "Classify this email into billing, support, or spam.",
|
|
202
|
+
model: modelForClass(ModelClass.ChatFast),
|
|
492
203
|
});
|
|
493
204
|
```
|
|
494
205
|
|
|
495
|
-
|
|
206
|
+
Per-request overrides arrive via the `X-Mastra-Model` header, a
|
|
207
|
+
`?model=` query, or a `model` / `modelId` body field (see
|
|
208
|
+
[`serving.ts`](src/serving.ts)). The cached endpoint catalogue is
|
|
209
|
+
exposed at `GET ${basePath}/models` for model pickers; the same data is
|
|
210
|
+
available in-process from a sibling plugin via
|
|
211
|
+
`appkitUtils.require(this.context, mastra).exports()`.
|
|
496
212
|
|
|
497
|
-
|
|
498
|
-
allowlists, etc) in front of the catalogue:
|
|
499
|
-
|
|
500
|
-
```ts
|
|
501
|
-
mastra({
|
|
502
|
-
defaultModelFallbacks: [
|
|
503
|
-
"my-org-finetune-v2", // try internal endpoint first
|
|
504
|
-
"databricks-claude-sonnet-4-6", // approved fallback
|
|
505
|
-
],
|
|
506
|
-
});
|
|
507
|
-
```
|
|
508
|
-
|
|
509
|
-
If the workspace has none of the listed ids, the top fallback is
|
|
510
|
-
returned and Databricks surfaces the canonical error.
|
|
511
|
-
|
|
512
|
-
The endpoint list is cached per workspace host through AppKit's
|
|
513
|
-
built-in `CacheManager` (`CacheManager.getInstanceSync().getOrExecute`),
|
|
514
|
-
which is the TypeScript counterpart of Python's `cachetools.TTLCache`
|
|
515
|
-
plus `cachetools-async` rolled into one: per-entry TTL (default 5
|
|
516
|
-
minutes via `modelCacheTtlMs`), bounded size, in-flight request
|
|
517
|
-
coalescing (the manager's internal `inFlightRequests` map shares one
|
|
518
|
-
fetch across every concurrent caller), telemetry spans, and optional
|
|
519
|
-
Lakebase persistence when the `lakebase` plugin is wired up. No extra
|
|
520
|
-
dependency lives in this package; the catalogue piggybacks on whatever
|
|
521
|
-
storage backend AppKit picked at boot.
|
|
522
|
-
|
|
523
|
-
String values (`"databricks-claude-sonnet-4-6"`) are `modelId` sugar
|
|
524
|
-
layered on top of the auto-resolver - workspace URL, provider, and OBO
|
|
525
|
-
auth stay default. Pass a `DynamicArgument<MastraModelConfig>` on
|
|
526
|
-
`def.model` / `config.defaultModel` when you need full control over
|
|
527
|
-
auth, provider, or URL; that path bypasses the fuzzy matcher and
|
|
528
|
-
per-request override.
|
|
529
|
-
|
|
530
|
-
### `GET /api/mastra/models`
|
|
531
|
-
|
|
532
|
-
The plugin exposes the cached endpoint catalogue at `/models` (mounted
|
|
533
|
-
under the plugin prefix, default `/api/mastra`) so clients can populate
|
|
534
|
-
model pickers and validate `?model=` choices without a separate
|
|
535
|
-
Databricks SDK round-trip:
|
|
536
|
-
|
|
537
|
-
```bash
|
|
538
|
-
curl -s http://localhost:8000/api/mastra/models | jq
|
|
539
|
-
# {
|
|
540
|
-
# "endpoints": [
|
|
541
|
-
# { "name": "databricks-claude-sonnet-4-6", "task": "llm/v1/chat", "state": "READY", ... },
|
|
542
|
-
# { "name": "databricks-meta-llama-3-3-70b-instruct", ... },
|
|
543
|
-
# ...
|
|
544
|
-
# ]
|
|
545
|
-
# }
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
Same payload from a sibling plugin or script (no HTTP round-trip):
|
|
549
|
-
|
|
550
|
-
```ts
|
|
551
|
-
import { appkitUtils } from "@dbx-tools/shared";
|
|
552
|
-
import { mastra } from "@dbx-tools/appkit-mastra";
|
|
213
|
+
## License
|
|
553
214
|
|
|
554
|
-
|
|
215
|
+
Apache-2.0
|