@dbx-tools/appkit-mastra 0.1.40 → 0.1.42
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 +59 -58
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- 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 +14 -11
- 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/memory.d.ts +34 -25
- package/dist/src/memory.js +48 -35
- package/dist/src/model.d.ts +24 -131
- package/dist/src/model.js +39 -268
- package/dist/src/plugin.d.ts +28 -12
- package/dist/src/plugin.js +86 -42
- 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/tools/email.d.ts +10 -1
- package/dist/src/writer.d.ts +2 -2
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/index.ts +2 -13
- package/package.json +7 -6
- package/src/agents.ts +3 -3
- package/src/chart.ts +3 -5
- package/src/config.ts +14 -11
- package/src/genie.ts +4 -22
- package/src/history.ts +6 -6
- package/src/memory.ts +48 -45
- package/src/model.ts +57 -291
- package/src/plugin.ts +99 -50
- 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/src/intercept.ts +0 -206
package/dist/src/memory.js
CHANGED
|
@@ -26,18 +26,46 @@
|
|
|
26
26
|
* (auto-defaulted to `true` in `plugin.ts` when the `lakebase` plugin
|
|
27
27
|
* is registered); per-agent settings cascade on top of that.
|
|
28
28
|
*/
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
29
|
+
import { getUsernameWithApiLookup } from "@databricks/appkit";
|
|
30
|
+
import { logUtils } from "@dbx-tools/shared";
|
|
31
31
|
import { fastembed } from "@mastra/fastembed";
|
|
32
32
|
import { Memory } from "@mastra/memory";
|
|
33
33
|
import { PgVector, PostgresStore } from "@mastra/pg";
|
|
34
34
|
import { randomUUID } from "node:crypto";
|
|
35
|
+
import { Pool } from "pg";
|
|
35
36
|
const log = logUtils.logger("mastra/memory");
|
|
37
|
+
/**
|
|
38
|
+
* Build a dedicated **service-principal** Lakebase pool for Mastra
|
|
39
|
+
* memory from the lakebase plugin's resolved SP pg config.
|
|
40
|
+
*
|
|
41
|
+
* The plugin's `exports().pool` is a `RoutingPool` that switches to
|
|
42
|
+
* the per-user (OBO) pool whenever a query runs inside an `asUser`
|
|
43
|
+
* scope - exactly the context the mastra plugin establishes around
|
|
44
|
+
* every chat turn. Memory (threads / messages + semantic recall) must
|
|
45
|
+
* instead always act as the app service principal: it owns the
|
|
46
|
+
* auto-created `mastra_*` schemas (a per-user role usually can't
|
|
47
|
+
* `CREATE SCHEMA`) and is shared across users, so it cannot inherit a
|
|
48
|
+
* request's OBO identity.
|
|
49
|
+
*
|
|
50
|
+
* `pgConfig` must be the plugin's `exports().getPgConfig()` evaluated
|
|
51
|
+
* **outside** any `asUser` scope (i.e. during setup), so it carries
|
|
52
|
+
* the SP connection target, OAuth token-refresh `password` callback,
|
|
53
|
+
* and any `lakebase({ pool })` tuning overrides - all of which this
|
|
54
|
+
* pool inherits. See the call site in `plugin.ts`.
|
|
55
|
+
*/
|
|
56
|
+
export async function createServicePrincipalPool(pgConfig) {
|
|
57
|
+
// `getPgConfig()` resolves the SP username synchronously from
|
|
58
|
+
// `PGUSER` / `DATABRICKS_CLIENT_ID`; fall back to the async API
|
|
59
|
+
// lookup (e.g. local dev authenticating via PAT) so the pool always
|
|
60
|
+
// has an identity to connect with.
|
|
61
|
+
const user = pgConfig.user ?? (await getUsernameWithApiLookup());
|
|
62
|
+
return new Pool({ ...pgConfig, user });
|
|
63
|
+
}
|
|
36
64
|
/**
|
|
37
65
|
* True when any plugin-level or per-agent setting could need the
|
|
38
|
-
* Lakebase pool. Used by `plugin.ts` to gate
|
|
39
|
-
*
|
|
40
|
-
*
|
|
66
|
+
* Lakebase pool. Used by `plugin.ts` to gate creation of the
|
|
67
|
+
* service-principal pool and the {@link MemoryBuilder} that consumes
|
|
68
|
+
* it; when false neither is built.
|
|
41
69
|
*/
|
|
42
70
|
export function needsLakebase(config) {
|
|
43
71
|
if (settingNeedsSharedPool(config.storage))
|
|
@@ -48,35 +76,26 @@ export function needsLakebase(config) {
|
|
|
48
76
|
return defs.some((d) => settingNeedsSharedPool(d.storage) || settingNeedsSharedPool(d.memory));
|
|
49
77
|
}
|
|
50
78
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
79
|
+
* Construct a per-agent {@link Memory} factory bound to the supplied
|
|
80
|
+
* service-principal pool (see {@link createServicePrincipalPool}).
|
|
81
|
+
* Caches the shared `PgVector` singleton (built on first need) so each
|
|
82
|
+
* agent build is O(1) after the first.
|
|
55
83
|
*/
|
|
56
|
-
export function
|
|
57
|
-
return
|
|
84
|
+
export function createMemoryBuilder(config, servicePrincipalPool) {
|
|
85
|
+
return new MemoryBuilder(config, servicePrincipalPool);
|
|
58
86
|
}
|
|
59
87
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*/
|
|
64
|
-
export function createMemoryBuilder(config, context) {
|
|
65
|
-
return new MemoryBuilder(config, context);
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Builds one `Memory` per agent. Per-instance state keeps the shared
|
|
69
|
-
* `PgVector` and the resolved Lakebase pool alive across calls so
|
|
70
|
-
* registering N agents stays cheap.
|
|
88
|
+
* Builds one `Memory` per agent against a shared service-principal
|
|
89
|
+
* Lakebase pool. Per-instance state keeps the shared `PgVector` alive
|
|
90
|
+
* across calls so registering N agents stays cheap.
|
|
71
91
|
*/
|
|
72
92
|
export class MemoryBuilder {
|
|
73
93
|
config;
|
|
74
|
-
|
|
94
|
+
servicePrincipalPool;
|
|
75
95
|
sharedVector;
|
|
76
|
-
|
|
77
|
-
constructor(config, context) {
|
|
96
|
+
constructor(config, servicePrincipalPool) {
|
|
78
97
|
this.config = config;
|
|
79
|
-
this.
|
|
98
|
+
this.servicePrincipalPool = servicePrincipalPool;
|
|
80
99
|
}
|
|
81
100
|
/**
|
|
82
101
|
* Build a `Memory` for `agentId` after the plugin/agent cascade.
|
|
@@ -105,7 +124,7 @@ export class MemoryBuilder {
|
|
|
105
124
|
return new PostgresStore({
|
|
106
125
|
id: "mastra-store__instance",
|
|
107
126
|
schemaName: "mastra_instance",
|
|
108
|
-
pool: this.
|
|
127
|
+
pool: this.servicePrincipalPool,
|
|
109
128
|
});
|
|
110
129
|
}
|
|
111
130
|
forAgent(agentId, def) {
|
|
@@ -143,7 +162,7 @@ export class MemoryBuilder {
|
|
|
143
162
|
return new PostgresStore({
|
|
144
163
|
id: `mastra-store__${agentId}`,
|
|
145
164
|
schemaName: `mastra_${agentId}`,
|
|
146
|
-
pool: this.
|
|
165
|
+
pool: this.servicePrincipalPool,
|
|
147
166
|
});
|
|
148
167
|
}
|
|
149
168
|
// Cast: `withId` guarantees `id` is set, but the distributive
|
|
@@ -169,16 +188,10 @@ export class MemoryBuilder {
|
|
|
169
188
|
}
|
|
170
189
|
getSharedVector() {
|
|
171
190
|
if (!this.sharedVector) {
|
|
172
|
-
this.sharedVector = buildSharedPgVector(this.
|
|
191
|
+
this.sharedVector = buildSharedPgVector(this.servicePrincipalPool);
|
|
173
192
|
}
|
|
174
193
|
return this.sharedVector;
|
|
175
194
|
}
|
|
176
|
-
requirePool() {
|
|
177
|
-
if (!this.pool) {
|
|
178
|
-
this.pool = resolveLakebasePool(this.context, this.config);
|
|
179
|
-
}
|
|
180
|
-
return this.pool;
|
|
181
|
-
}
|
|
182
195
|
}
|
|
183
196
|
/**
|
|
184
197
|
* Build the shared `PgVector` that backs the default
|
package/dist/src/model.d.ts
CHANGED
|
@@ -7,148 +7,41 @@
|
|
|
7
7
|
* fresh bearer header, then point Mastra's OpenAI-compatible provider
|
|
8
8
|
* at `/serving-endpoints` on that host.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* names like `"claude sonnet"` resolve to the real endpoint name.
|
|
23
|
-
* Fuzzy matching is best-effort: when the workspace client throws
|
|
24
|
-
* (network blip, expired token at cache-fill time) we fall back to
|
|
25
|
-
* the input verbatim and let Databricks return the canonical error.
|
|
10
|
+
* This module only adds the Mastra-specific glue. The actual model
|
|
11
|
+
* selection - listing the workspace catalogue and resolving an
|
|
12
|
+
* explicit name / tier / fallback chain to a real endpoint id - lives
|
|
13
|
+
* in `@dbx-tools/model` ({@link selectModel}) so non-Mastra consumers
|
|
14
|
+
* (e.g. a job that just needs a model name) can reuse it. Here we
|
|
15
|
+
* assemble the explicit ask from Mastra's request context (the
|
|
16
|
+
* per-request override under {@link MASTRA_MODEL_OVERRIDE_KEY}, the
|
|
17
|
+
* agent / plugin `modelId`, or `DATABRICKS_SERVING_ENDPOINT_NAME`),
|
|
18
|
+
* pass the plugin's fuzzy / tier / fallback knobs through, and wrap
|
|
19
|
+
* the resolved id in the OpenAI-compatible provider config Mastra
|
|
20
|
+
* expects. Catalogue fetches fail loud: network / auth errors
|
|
21
|
+
* propagate so callers see the real SDK message.
|
|
26
22
|
*/
|
|
23
|
+
import { type ModelTier } from "@dbx-tools/model";
|
|
27
24
|
import type { MastraModelConfig } from "@mastra/core/llm";
|
|
28
25
|
import type { RequestContext } from "@mastra/core/request-context";
|
|
29
26
|
import { type MastraPluginConfig } from "./config.js";
|
|
30
|
-
|
|
31
|
-
* Capability tiers for Databricks Foundation Model API endpoints.
|
|
32
|
-
*
|
|
33
|
-
* - {@link ModelTier.Thinking}: deepest reasoning / "thinking" models
|
|
34
|
-
* (Claude Opus, GPT-5.5 Pro, Gemini Pro, Llama 4 Maverick, etc).
|
|
35
|
-
* Highest cost and latency; reserve for hard multi-step reasoning.
|
|
36
|
-
* - {@link ModelTier.Balanced}: cost/latency sweet spot for general
|
|
37
|
-
* agent work (Claude Sonnet, GPT-5.x, Gemini Flash, Llama 3.3 70B).
|
|
38
|
-
* The right default for most agents.
|
|
39
|
-
* - {@link ModelTier.Fast}: cheap and quick; classification, routing,
|
|
40
|
-
* tool-arg extraction, simple summarisation (Claude Haiku, GPT-5
|
|
41
|
-
* mini/nano, Gemini Flash Lite, GPT-OSS 20B, Llama 3.1 8B).
|
|
42
|
-
*
|
|
43
|
-
* String enum so the value is the slug we use in cache keys, logs,
|
|
44
|
-
* and as the value users see in serialized configs.
|
|
45
|
-
*/
|
|
46
|
-
export declare enum ModelTier {
|
|
47
|
-
Thinking = "thinking",
|
|
48
|
-
Balanced = "balanced",
|
|
49
|
-
Fast = "fast"
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Catalogue of Databricks-hosted Foundation Model API endpoints,
|
|
53
|
-
* grouped by capability {@link ModelTier} and then by provider. Each
|
|
54
|
-
* inner array is priority-ordered (most powerful first within the
|
|
55
|
-
* same provider+tier).
|
|
56
|
-
*
|
|
57
|
-
* Provider buckets:
|
|
58
|
-
*
|
|
59
|
-
* - `claude`: Anthropic Claude family (closed; flagship reasoning).
|
|
60
|
-
* - `gpt`: OpenAI GPT-5 family (closed; "ChatGPT" on Databricks FMAPI).
|
|
61
|
-
* - `gemini`: Google Gemini family (closed; multimodal + web-search).
|
|
62
|
-
* - `openSource`: open-weights models (widest regional / SKU availability).
|
|
63
|
-
*
|
|
64
|
-
* The list is curated by hand; refresh from the Databricks "supported
|
|
65
|
-
* foundation models" doc when new endpoints land.
|
|
66
|
-
*/
|
|
67
|
-
export declare const MODEL_CATALOG: {
|
|
68
|
-
readonly thinking: {
|
|
69
|
-
readonly claude: readonly ["databricks-claude-opus-4-8", "databricks-claude-opus-4-7", "databricks-claude-opus-4-6", "databricks-claude-opus-4-5", "databricks-claude-opus-4-1"];
|
|
70
|
-
readonly gpt: readonly ["databricks-gpt-5-5-pro"];
|
|
71
|
-
readonly gemini: readonly ["databricks-gemini-3-1-pro", "databricks-gemini-3-pro", "databricks-gemini-2-5-pro"];
|
|
72
|
-
readonly openSource: readonly ["databricks-llama-4-maverick", "databricks-gpt-oss-120b", "databricks-meta-llama-3-1-405b-instruct"];
|
|
73
|
-
};
|
|
74
|
-
readonly balanced: {
|
|
75
|
-
readonly claude: readonly ["databricks-claude-sonnet-4-6", "databricks-claude-sonnet-4-5", "databricks-claude-sonnet-4"];
|
|
76
|
-
readonly gpt: readonly ["databricks-gpt-5-5", "databricks-gpt-5-4", "databricks-gpt-5-2", "databricks-gpt-5-1", "databricks-gpt-5"];
|
|
77
|
-
readonly gemini: readonly ["databricks-gemini-3-5-flash", "databricks-gemini-3-flash", "databricks-gemini-2-5-flash"];
|
|
78
|
-
readonly openSource: readonly ["databricks-meta-llama-3-3-70b-instruct", "databricks-qwen3-next-80b-a3b-instruct", "databricks-qwen35-122b-a10b"];
|
|
79
|
-
};
|
|
80
|
-
readonly fast: {
|
|
81
|
-
readonly claude: readonly ["databricks-claude-haiku-4-5"];
|
|
82
|
-
readonly gpt: readonly ["databricks-gpt-5-4-mini", "databricks-gpt-5-4-nano", "databricks-gpt-5-mini", "databricks-gpt-5-nano"];
|
|
83
|
-
readonly gemini: readonly ["databricks-gemini-3-1-flash-lite"];
|
|
84
|
-
readonly openSource: readonly ["databricks-gpt-oss-20b", "databricks-gemma-3-12b", "databricks-meta-llama-3-1-8b-instruct"];
|
|
85
|
-
};
|
|
86
|
-
};
|
|
87
|
-
/**
|
|
88
|
-
* Priority-ordered model ids for a single capability {@link ModelTier},
|
|
89
|
-
* interleaved across providers so a workspace missing the top Claude
|
|
90
|
-
* still lands on a flagship GPT / Gemini on the next probe.
|
|
91
|
-
*
|
|
92
|
-
* Provider order within the interleave: Claude, GPT, Gemini, then the
|
|
93
|
-
* open-weights tail appended verbatim as the universal floor (widest
|
|
94
|
-
* regional availability).
|
|
95
|
-
*
|
|
96
|
-
* @example
|
|
97
|
-
* ```ts
|
|
98
|
-
* mastra({
|
|
99
|
-
* defaultModelFallbacks: modelsForTier(ModelTier.Fast),
|
|
100
|
-
* });
|
|
101
|
-
* ```
|
|
102
|
-
*/
|
|
103
|
-
export declare function modelsForTier(tier: ModelTier): readonly string[];
|
|
104
|
-
/**
|
|
105
|
-
* Top model id at the given {@link ModelTier}. Sync; the agent-step
|
|
106
|
-
* resolver fuzzy-matches it against the workspace catalogue at call
|
|
107
|
-
* time, so this works even when the literal top pick isn't deployed.
|
|
108
|
-
*
|
|
109
|
-
* Use when wiring a tier-appropriate model into an agent definition:
|
|
110
|
-
*
|
|
111
|
-
* @example
|
|
112
|
-
* ```ts
|
|
113
|
-
* const classifier = createAgent({
|
|
114
|
-
* instructions: "Classify this email",
|
|
115
|
-
* model: modelForTier(ModelTier.Fast), // cheap, quick
|
|
116
|
-
* });
|
|
117
|
-
*
|
|
118
|
-
* const planner = createAgent({
|
|
119
|
-
* instructions: "Plan a multi-step migration",
|
|
120
|
-
* model: modelForTier(ModelTier.Thinking), // deep reasoning
|
|
121
|
-
* });
|
|
122
|
-
* ```
|
|
123
|
-
*/
|
|
124
|
-
export declare function modelForTier(tier: ModelTier): string;
|
|
125
|
-
/**
|
|
126
|
-
* Last-resort model ids used when neither `config.defaultModel`,
|
|
127
|
-
* per-agent `model`, nor `DATABRICKS_SERVING_ENDPOINT_NAME` is set.
|
|
128
|
-
*
|
|
129
|
-
* Walked in order at resolve time: the first id whose endpoint is
|
|
130
|
-
* actually present in the workspace's `/serving-endpoints` listing
|
|
131
|
-
* wins. Workspaces vary - not every region / SKU has every model,
|
|
132
|
-
* and the list of Foundation Model APIs evolves quickly - so the
|
|
133
|
-
* resolver degrades all the way from "best thinking model" down to
|
|
134
|
-
* "smallest commodity Llama" before giving up.
|
|
135
|
-
*
|
|
136
|
-
* Built by chaining the per-tier interleaves (Thinking -> Balanced
|
|
137
|
-
* -> Fast); within each tier the providers are round-robin-zipped
|
|
138
|
-
* (Claude, GPT, Gemini, then open-weights tail). Override the entire
|
|
139
|
-
* list via `MastraPluginConfig.defaultModelFallbacks` (e.g. to pin a
|
|
140
|
-
* regulated workspace to a specific approved subset, or to bias the
|
|
141
|
-
* priority toward a particular tier).
|
|
142
|
-
*/
|
|
143
|
-
export declare const FALLBACK_MODEL_IDS: readonly string[];
|
|
27
|
+
export { classifyEndpoints, FALLBACK_MODEL_IDS, modelForTier, modelsForTier, ModelTier, } from "@dbx-tools/model";
|
|
144
28
|
/** Optional overrides accepted by {@link buildModel}. */
|
|
145
29
|
export interface BuildModelOverrides {
|
|
146
30
|
/**
|
|
147
31
|
* Static model id from the agent / plugin config (string sugar on
|
|
148
32
|
* `def.model` or `config.defaultModel`). Loses to the per-request
|
|
149
|
-
* override but wins over env / fallback.
|
|
33
|
+
* override but wins over env / tier / fallback.
|
|
150
34
|
*/
|
|
151
35
|
modelId?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Capability tier to resolve when no explicit model id is supplied.
|
|
38
|
+
* Used by internal agents (e.g. the chart planner asks for
|
|
39
|
+
* {@link ModelTier.Fast}) to express intent without pinning an
|
|
40
|
+
* endpoint name; the live catalogue is classified and the top
|
|
41
|
+
* available model in the tier is chosen, falling back to the
|
|
42
|
+
* tier's static list when the workspace has none.
|
|
43
|
+
*/
|
|
44
|
+
tier?: ModelTier;
|
|
152
45
|
}
|
|
153
46
|
/**
|
|
154
47
|
* Resolve a `MastraModelConfig` for the current agent step. Runs
|
package/dist/src/model.js
CHANGED
|
@@ -7,214 +7,24 @@
|
|
|
7
7
|
* fresh bearer header, then point Mastra's OpenAI-compatible provider
|
|
8
8
|
* at `/serving-endpoints` on that host.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* names like `"claude sonnet"` resolve to the real endpoint name.
|
|
23
|
-
* Fuzzy matching is best-effort: when the workspace client throws
|
|
24
|
-
* (network blip, expired token at cache-fill time) we fall back to
|
|
25
|
-
* the input verbatim and let Databricks return the canonical error.
|
|
10
|
+
* This module only adds the Mastra-specific glue. The actual model
|
|
11
|
+
* selection - listing the workspace catalogue and resolving an
|
|
12
|
+
* explicit name / tier / fallback chain to a real endpoint id - lives
|
|
13
|
+
* in `@dbx-tools/model` ({@link selectModel}) so non-Mastra consumers
|
|
14
|
+
* (e.g. a job that just needs a model name) can reuse it. Here we
|
|
15
|
+
* assemble the explicit ask from Mastra's request context (the
|
|
16
|
+
* per-request override under {@link MASTRA_MODEL_OVERRIDE_KEY}, the
|
|
17
|
+
* agent / plugin `modelId`, or `DATABRICKS_SERVING_ENDPOINT_NAME`),
|
|
18
|
+
* pass the plugin's fuzzy / tier / fallback knobs through, and wrap
|
|
19
|
+
* the resolved id in the OpenAI-compatible provider config Mastra
|
|
20
|
+
* expects. Catalogue fetches fail loud: network / auth errors
|
|
21
|
+
* propagate so callers see the real SDK message.
|
|
26
22
|
*/
|
|
23
|
+
import { parseModelTier, selectModel } from "@dbx-tools/model";
|
|
27
24
|
import { commonUtils, logUtils, netUtils, stringUtils } from "@dbx-tools/shared";
|
|
28
25
|
import { MASTRA_USER_KEY } from "./config.js";
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
* Capability tiers for Databricks Foundation Model API endpoints.
|
|
32
|
-
*
|
|
33
|
-
* - {@link ModelTier.Thinking}: deepest reasoning / "thinking" models
|
|
34
|
-
* (Claude Opus, GPT-5.5 Pro, Gemini Pro, Llama 4 Maverick, etc).
|
|
35
|
-
* Highest cost and latency; reserve for hard multi-step reasoning.
|
|
36
|
-
* - {@link ModelTier.Balanced}: cost/latency sweet spot for general
|
|
37
|
-
* agent work (Claude Sonnet, GPT-5.x, Gemini Flash, Llama 3.3 70B).
|
|
38
|
-
* The right default for most agents.
|
|
39
|
-
* - {@link ModelTier.Fast}: cheap and quick; classification, routing,
|
|
40
|
-
* tool-arg extraction, simple summarisation (Claude Haiku, GPT-5
|
|
41
|
-
* mini/nano, Gemini Flash Lite, GPT-OSS 20B, Llama 3.1 8B).
|
|
42
|
-
*
|
|
43
|
-
* String enum so the value is the slug we use in cache keys, logs,
|
|
44
|
-
* and as the value users see in serialized configs.
|
|
45
|
-
*/
|
|
46
|
-
export var ModelTier;
|
|
47
|
-
(function (ModelTier) {
|
|
48
|
-
ModelTier["Thinking"] = "thinking";
|
|
49
|
-
ModelTier["Balanced"] = "balanced";
|
|
50
|
-
ModelTier["Fast"] = "fast";
|
|
51
|
-
})(ModelTier || (ModelTier = {}));
|
|
52
|
-
/**
|
|
53
|
-
* Catalogue of Databricks-hosted Foundation Model API endpoints,
|
|
54
|
-
* grouped by capability {@link ModelTier} and then by provider. Each
|
|
55
|
-
* inner array is priority-ordered (most powerful first within the
|
|
56
|
-
* same provider+tier).
|
|
57
|
-
*
|
|
58
|
-
* Provider buckets:
|
|
59
|
-
*
|
|
60
|
-
* - `claude`: Anthropic Claude family (closed; flagship reasoning).
|
|
61
|
-
* - `gpt`: OpenAI GPT-5 family (closed; "ChatGPT" on Databricks FMAPI).
|
|
62
|
-
* - `gemini`: Google Gemini family (closed; multimodal + web-search).
|
|
63
|
-
* - `openSource`: open-weights models (widest regional / SKU availability).
|
|
64
|
-
*
|
|
65
|
-
* The list is curated by hand; refresh from the Databricks "supported
|
|
66
|
-
* foundation models" doc when new endpoints land.
|
|
67
|
-
*/
|
|
68
|
-
export const MODEL_CATALOG = {
|
|
69
|
-
[ModelTier.Thinking]: {
|
|
70
|
-
claude: [
|
|
71
|
-
"databricks-claude-opus-4-8",
|
|
72
|
-
"databricks-claude-opus-4-7",
|
|
73
|
-
"databricks-claude-opus-4-6",
|
|
74
|
-
"databricks-claude-opus-4-5",
|
|
75
|
-
"databricks-claude-opus-4-1",
|
|
76
|
-
],
|
|
77
|
-
gpt: ["databricks-gpt-5-5-pro"],
|
|
78
|
-
gemini: [
|
|
79
|
-
"databricks-gemini-3-1-pro",
|
|
80
|
-
"databricks-gemini-3-pro",
|
|
81
|
-
"databricks-gemini-2-5-pro",
|
|
82
|
-
],
|
|
83
|
-
openSource: [
|
|
84
|
-
"databricks-llama-4-maverick",
|
|
85
|
-
"databricks-gpt-oss-120b",
|
|
86
|
-
"databricks-meta-llama-3-1-405b-instruct",
|
|
87
|
-
],
|
|
88
|
-
},
|
|
89
|
-
[ModelTier.Balanced]: {
|
|
90
|
-
claude: [
|
|
91
|
-
"databricks-claude-sonnet-4-6",
|
|
92
|
-
"databricks-claude-sonnet-4-5",
|
|
93
|
-
"databricks-claude-sonnet-4",
|
|
94
|
-
],
|
|
95
|
-
gpt: [
|
|
96
|
-
"databricks-gpt-5-5",
|
|
97
|
-
"databricks-gpt-5-4",
|
|
98
|
-
"databricks-gpt-5-2",
|
|
99
|
-
"databricks-gpt-5-1",
|
|
100
|
-
"databricks-gpt-5",
|
|
101
|
-
],
|
|
102
|
-
gemini: [
|
|
103
|
-
"databricks-gemini-3-5-flash",
|
|
104
|
-
"databricks-gemini-3-flash",
|
|
105
|
-
"databricks-gemini-2-5-flash",
|
|
106
|
-
],
|
|
107
|
-
openSource: [
|
|
108
|
-
"databricks-meta-llama-3-3-70b-instruct",
|
|
109
|
-
"databricks-qwen3-next-80b-a3b-instruct",
|
|
110
|
-
"databricks-qwen35-122b-a10b",
|
|
111
|
-
],
|
|
112
|
-
},
|
|
113
|
-
[ModelTier.Fast]: {
|
|
114
|
-
claude: ["databricks-claude-haiku-4-5"],
|
|
115
|
-
gpt: [
|
|
116
|
-
"databricks-gpt-5-4-mini",
|
|
117
|
-
"databricks-gpt-5-4-nano",
|
|
118
|
-
"databricks-gpt-5-mini",
|
|
119
|
-
"databricks-gpt-5-nano",
|
|
120
|
-
],
|
|
121
|
-
gemini: ["databricks-gemini-3-1-flash-lite"],
|
|
122
|
-
openSource: [
|
|
123
|
-
"databricks-gpt-oss-20b",
|
|
124
|
-
"databricks-gemma-3-12b",
|
|
125
|
-
"databricks-meta-llama-3-1-8b-instruct",
|
|
126
|
-
],
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
/**
|
|
130
|
-
* Round-robin zip: take one from each input list in order, skipping
|
|
131
|
-
* lists that have already been exhausted. Used to interleave provider
|
|
132
|
-
* buckets within a tier so the resolver alternates between vendors
|
|
133
|
-
* instead of draining one before trying the next.
|
|
134
|
-
*
|
|
135
|
-
* Example: `interleave(["a1","a2","a3"], ["b1","b2"])` ->
|
|
136
|
-
* `["a1","b1","a2","b2","a3"]`.
|
|
137
|
-
*/
|
|
138
|
-
function interleave(...lists) {
|
|
139
|
-
const out = [];
|
|
140
|
-
const max = Math.max(0, ...lists.map((l) => l.length));
|
|
141
|
-
for (let i = 0; i < max; i++) {
|
|
142
|
-
for (const list of lists) {
|
|
143
|
-
if (i < list.length)
|
|
144
|
-
out.push(list[i]);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return out;
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Priority-ordered model ids for a single capability {@link ModelTier},
|
|
151
|
-
* interleaved across providers so a workspace missing the top Claude
|
|
152
|
-
* still lands on a flagship GPT / Gemini on the next probe.
|
|
153
|
-
*
|
|
154
|
-
* Provider order within the interleave: Claude, GPT, Gemini, then the
|
|
155
|
-
* open-weights tail appended verbatim as the universal floor (widest
|
|
156
|
-
* regional availability).
|
|
157
|
-
*
|
|
158
|
-
* @example
|
|
159
|
-
* ```ts
|
|
160
|
-
* mastra({
|
|
161
|
-
* defaultModelFallbacks: modelsForTier(ModelTier.Fast),
|
|
162
|
-
* });
|
|
163
|
-
* ```
|
|
164
|
-
*/
|
|
165
|
-
export function modelsForTier(tier) {
|
|
166
|
-
const bucket = MODEL_CATALOG[tier];
|
|
167
|
-
return [
|
|
168
|
-
...interleave(bucket.claude, bucket.gpt, bucket.gemini),
|
|
169
|
-
...bucket.openSource,
|
|
170
|
-
];
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Top model id at the given {@link ModelTier}. Sync; the agent-step
|
|
174
|
-
* resolver fuzzy-matches it against the workspace catalogue at call
|
|
175
|
-
* time, so this works even when the literal top pick isn't deployed.
|
|
176
|
-
*
|
|
177
|
-
* Use when wiring a tier-appropriate model into an agent definition:
|
|
178
|
-
*
|
|
179
|
-
* @example
|
|
180
|
-
* ```ts
|
|
181
|
-
* const classifier = createAgent({
|
|
182
|
-
* instructions: "Classify this email",
|
|
183
|
-
* model: modelForTier(ModelTier.Fast), // cheap, quick
|
|
184
|
-
* });
|
|
185
|
-
*
|
|
186
|
-
* const planner = createAgent({
|
|
187
|
-
* instructions: "Plan a multi-step migration",
|
|
188
|
-
* model: modelForTier(ModelTier.Thinking), // deep reasoning
|
|
189
|
-
* });
|
|
190
|
-
* ```
|
|
191
|
-
*/
|
|
192
|
-
export function modelForTier(tier) {
|
|
193
|
-
return modelsForTier(tier)[0];
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Last-resort model ids used when neither `config.defaultModel`,
|
|
197
|
-
* per-agent `model`, nor `DATABRICKS_SERVING_ENDPOINT_NAME` is set.
|
|
198
|
-
*
|
|
199
|
-
* Walked in order at resolve time: the first id whose endpoint is
|
|
200
|
-
* actually present in the workspace's `/serving-endpoints` listing
|
|
201
|
-
* wins. Workspaces vary - not every region / SKU has every model,
|
|
202
|
-
* and the list of Foundation Model APIs evolves quickly - so the
|
|
203
|
-
* resolver degrades all the way from "best thinking model" down to
|
|
204
|
-
* "smallest commodity Llama" before giving up.
|
|
205
|
-
*
|
|
206
|
-
* Built by chaining the per-tier interleaves (Thinking -> Balanced
|
|
207
|
-
* -> Fast); within each tier the providers are round-robin-zipped
|
|
208
|
-
* (Claude, GPT, Gemini, then open-weights tail). Override the entire
|
|
209
|
-
* list via `MastraPluginConfig.defaultModelFallbacks` (e.g. to pin a
|
|
210
|
-
* regulated workspace to a specific approved subset, or to bias the
|
|
211
|
-
* priority toward a particular tier).
|
|
212
|
-
*/
|
|
213
|
-
export const FALLBACK_MODEL_IDS = [
|
|
214
|
-
...modelsForTier(ModelTier.Thinking),
|
|
215
|
-
...modelsForTier(ModelTier.Balanced),
|
|
216
|
-
...modelsForTier(ModelTier.Fast),
|
|
217
|
-
];
|
|
26
|
+
import { MASTRA_MODEL_OVERRIDE_KEY, resolveServingConfig } from "./serving.js";
|
|
27
|
+
export { classifyEndpoints, FALLBACK_MODEL_IDS, modelForTier, modelsForTier, ModelTier, } from "@dbx-tools/model";
|
|
218
28
|
/**
|
|
219
29
|
* Resolve a `MastraModelConfig` for the current agent step. Runs
|
|
220
30
|
* while `agent.stream` is inside the `asUser(req)` scope so tokens
|
|
@@ -232,76 +42,37 @@ export async function buildModel(config, requestContext, overrides = {}) {
|
|
|
232
42
|
// URL we hand it. Drop the trailing slash so the resulting URL stays
|
|
233
43
|
// well-formed (`/serving-endpoints/chat/completions`).
|
|
234
44
|
const url = new URL("/serving-endpoints", host).toString().replace(/\/$/, "");
|
|
235
|
-
const modelId = await pickModelId(config, requestContext, overrides, user, host);
|
|
236
|
-
return {
|
|
237
|
-
providerId: config.providerId ?? "databricks",
|
|
238
|
-
modelId,
|
|
239
|
-
url,
|
|
240
|
-
headers: Object.fromEntries(headers.entries()),
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Walk the resolution ladder and pick a modelId.
|
|
245
|
-
*
|
|
246
|
-
* 1. **Explicit ask** (per-request override, agent `model` string,
|
|
247
|
-
* `config.defaultModel` string, or `DATABRICKS_SERVING_ENDPOINT_NAME`):
|
|
248
|
-
* when fuzzy matching is on, snap the input to the closest live
|
|
249
|
-
* endpoint so loose names like `"claude sonnet"` resolve. When it's
|
|
250
|
-
* off (or no endpoint matches within threshold), the input is used
|
|
251
|
-
* verbatim and Databricks surfaces the canonical 404.
|
|
252
|
-
*
|
|
253
|
-
* 2. **No explicit ask**: walk
|
|
254
|
-
* {@link MastraPluginConfig.defaultModelFallbacks} (or
|
|
255
|
-
* {@link FALLBACK_MODEL_IDS} when unset) and return the first id
|
|
256
|
-
* whose endpoint is actually present in the workspace listing. A
|
|
257
|
-
* workspace without Claude Opus still gets a sensible default by
|
|
258
|
-
* skipping ahead to whichever Sonnet / GPT-5 / Llama variant is
|
|
259
|
-
* wired up.
|
|
260
|
-
*
|
|
261
|
-
* Catalogue fetches fail loud: network / auth errors propagate to the
|
|
262
|
-
* caller so they see the real SDK message instead of a silent fallback
|
|
263
|
-
* to the top of the priority list.
|
|
264
|
-
*/
|
|
265
|
-
async function pickModelId(config, requestContext, overrides, user, host) {
|
|
266
45
|
const log = logUtils.logger(config);
|
|
267
|
-
const serving = resolveServingConfig(config
|
|
46
|
+
const serving = resolveServingConfig(config);
|
|
268
47
|
const override = serving.allowOverride
|
|
269
48
|
? requestContext.get(MASTRA_MODEL_OVERRIDE_KEY)
|
|
270
49
|
: undefined;
|
|
271
|
-
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const
|
|
50
|
+
// The override / agent default / env value can be either a concrete
|
|
51
|
+
// endpoint name or a capability tier slug ("thinking" / "balanced" /
|
|
52
|
+
// "fast"). A tier slug becomes a tier intent (let the live catalogue
|
|
53
|
+
// pick the best model in that band); anything else is an explicit
|
|
54
|
+
// name fuzzy-matched against the catalogue. An internal
|
|
55
|
+
// `overrides.tier` (e.g. the chart planner) is the floor when nothing
|
|
56
|
+
// was requested.
|
|
57
|
+
const requested = override ?? overrides.modelId ?? process.env.DATABRICKS_SERVING_ENDPOINT_NAME;
|
|
58
|
+
const requestedTier = requested !== undefined ? parseModelTier(requested) : null;
|
|
59
|
+
const explicit = requestedTier === null ? requested : undefined;
|
|
60
|
+
const tier = requestedTier ?? overrides.tier;
|
|
61
|
+
const { modelId, source } = await selectModel(user.executionContext.client, host, {
|
|
62
|
+
...(explicit !== undefined ? { explicit } : {}),
|
|
63
|
+
fuzzy: serving.fuzzy,
|
|
64
|
+
threshold: serving.threshold,
|
|
65
|
+
...(tier !== undefined ? { tier } : {}),
|
|
66
|
+
fallbacks: serving.fallbacks,
|
|
279
67
|
ttlMs: serving.ttlMs,
|
|
280
68
|
});
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
:
|
|
284
|
-
log.debug("model selected", {
|
|
69
|
+
log.debug("model selected", { modelId, source, requested });
|
|
70
|
+
return {
|
|
71
|
+
providerId: config.providerId ?? "databricks",
|
|
285
72
|
modelId,
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
return modelId;
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* Find the first id in `fallbacks` whose endpoint is present in
|
|
293
|
-
* `endpoints`. Returns the top fallback when the workspace has none
|
|
294
|
-
* of them so callers always get a string; an offline workspace will
|
|
295
|
-
* then receive a clean 404 from Databricks instead of a malformed
|
|
296
|
-
* config.
|
|
297
|
-
*/
|
|
298
|
-
function pickFirstAvailable(fallbacks, endpoints) {
|
|
299
|
-
const present = new Set(endpoints.map((e) => e.name));
|
|
300
|
-
for (const candidate of fallbacks) {
|
|
301
|
-
if (present.has(candidate))
|
|
302
|
-
return candidate;
|
|
303
|
-
}
|
|
304
|
-
return fallbacks[0] ?? FALLBACK_MODEL_IDS[0];
|
|
73
|
+
url,
|
|
74
|
+
headers: Object.fromEntries(headers.entries()),
|
|
75
|
+
};
|
|
305
76
|
}
|
|
306
77
|
/** Path prefix that identifies a Databricks Model Serving REST call. */
|
|
307
78
|
const SERVING_ENDPOINTS_PATH_PREFIX = "/serving-endpoints/";
|