@dbx-tools/model 0.1.112 → 0.3.2

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 CHANGED
@@ -1,105 +1,165 @@
1
1
  # @dbx-tools/model
2
2
 
3
- Workspace-aware model selection for Databricks Model Serving: list a
4
- workspace's `/serving-endpoints` (cached), fuzzy-match loose names like
5
- `"claude sonnet"` to real endpoint ids, rank endpoints by capability
6
- class, and resolve a single usable model id with an offline fallback
7
- floor.
3
+ Workspace-aware Databricks Model Serving selection.
4
+
5
+ Import this package when server-side code needs to turn a loose model request
6
+ like `"claude sonnet"` or `"chat-fast"` into a concrete serving endpoint in the
7
+ current workspace. It lists `/serving-endpoints`, caches and enriches the
8
+ catalogue, classifies endpoints by capability, fuzzy-matches names, and falls
9
+ back to a small static floor when the live catalogue is unavailable.
10
+
11
+ Browser-safe request/result schemas and endpoint classification types live in
12
+ [`@dbx-tools/shared-model`](../../shared/model).
13
+
14
+ Key features:
15
+
16
+ - Lists Databricks Model Serving endpoints through the SDK and normalizes them
17
+ into a stable summary shape.
18
+ - Classifies endpoints into chat-thinking, chat-balanced, chat-fast, and
19
+ embedding classes using Foundation Model API scores and family heuristics.
20
+ - Resolves loose user input such as `"sonnet"` or `"chat fast"` to a concrete
21
+ endpoint id.
22
+ - Supports class ceilings so callers can ask for a capability band without
23
+ accidentally escalating to a larger model.
24
+ - Caches enriched catalogues per workspace host through AppKit cache utilities.
25
+ - Provides a small static fallback floor for local tools and degraded workspace
26
+ access.
27
+
28
+ ## Why Not Just AppKit Serving?
29
+
30
+ Native AppKit's Model Serving plugin is the right choice when you already know
31
+ the endpoint alias you want. It gives you authenticated invoke/stream routes,
32
+ OBO execution, generated endpoint types, request-body filtering, and frontend
33
+ hooks.
34
+
35
+ Use this package before or beside that layer when the hard part is choosing the
36
+ endpoint:
37
+
38
+ - resolve loose human input such as `"sonnet"` or `"fast"` against the live
39
+ workspace catalogue;
40
+ - group endpoints into capability classes like `chat-thinking`, `chat-balanced`,
41
+ `chat-fast`, and `embedding`;
42
+ - enforce class ceilings so a caller can degrade to smaller models without
43
+ escalating to a larger one;
44
+ - build model pickers and debug routes from a cached, enriched endpoint list;
45
+ - keep local agents and CLIs working with a static fallback when catalogue
46
+ access is unavailable.
47
+
48
+ ## Select One Model
8
49
 
9
- This is the server-side package (it holds a `WorkspaceClient` and
10
- AppKit's cache). Browser consumers want the pure
11
- [`@dbx-tools/model-shared`](../model-shared) surface, which this package
12
- re-exports.
50
+ ```ts
51
+ import { WorkspaceClient } from "@databricks/sdk-experimental";
52
+ import { resolve } from "@dbx-tools/model";
53
+
54
+ const client = new WorkspaceClient({});
55
+ const host = String(await client.config.getHost());
13
56
 
14
- ## Install
57
+ const selected = await resolve.selectModel(client, host, {
58
+ explicit: "claude sonnet",
59
+ });
15
60
 
16
- ```bash
17
- npm install @dbx-tools/model
61
+ console.log(selected.modelId, selected.source);
18
62
  ```
19
63
 
20
- Peer: `@databricks/appkit` (provides the cache the listing runs through).
64
+ `selectModel()` is the high-level helper for agents and CLIs. It reads the live
65
+ catalogue, applies fuzzy matching when an explicit string is present, then
66
+ returns a single `modelId` plus a source label explaining why that endpoint won.
21
67
 
22
- ## Usage
68
+ The `source` label is useful for logs and debug UIs. It distinguishes explicit
69
+ matches from class-based selection, environment defaults, and fallback results,
70
+ so operators can tell whether a request used the intended model policy.
23
71
 
24
- Hold a `WorkspaceClient` and just want a usable model id? `selectModel`
25
- lists the catalogue (cached) and resolves in one call. An `explicit`
26
- name is fuzzy-matched; without one, a `modelClass` ceiling (or the
27
- operator `fallbacks`, then the static floor) decides.
72
+ ## Build A Model Picker
28
73
 
29
74
  ```ts
30
- import { WorkspaceClient } from "@databricks/sdk-experimental";
31
- import { selectModel, searchModels } from "@dbx-tools/model";
75
+ import { resolve } from "@dbx-tools/model";
32
76
 
33
- const client = new WorkspaceClient({});
34
- const host = (await client.config.getHost()).toString();
77
+ const ranked = await resolve.searchModels(client, host, {
78
+ search: "opus",
79
+ modelClass: "chat-thinking",
80
+ limit: 5,
81
+ });
82
+ ```
35
83
 
36
- // Fuzzy name -> real endpoint id (+ how it was reached).
37
- const { modelId, source } = await selectModel(client, host, {
38
- explicit: "claude sonnet",
39
- }); // -> { modelId: "databricks-claude-sonnet-4-6", source: "fuzzy-match" }
84
+ Use `searchModels()` for UI pickers and debug routes. It returns ranked models
85
+ with match scores and endpoint summaries, using the same fuzzy threshold and
86
+ class ceiling logic as `selectModel()`.
40
87
 
41
- // Best balanced-or-cheaper chat model the workspace actually has.
42
- const fast = await selectModel(client, host, { modelClass: "chat-balanced" });
88
+ ## Work With A Held Catalogue
43
89
 
44
- // Or get the full ranked list (model picker / CLI).
45
- const ranked = await searchModels(client, host, { search: "opus", limit: 5 });
90
+ When you already have endpoint summaries, use the pure resolver functions from
91
+ `resolve` and `serving` without another workspace call:
92
+
93
+ ```ts
94
+ import { resolve, serving } from "@dbx-tools/model";
95
+
96
+ const endpoints = await serving.listServingEndpoints(client, host);
97
+ const ranked = resolve.rankModels(endpoints, { search: "sonnet", limit: 3 });
98
+ const picked = resolve.resolveModel(endpoints, {
99
+ explicit: "claude sonnet",
100
+ modelClass: "chat-balanced",
101
+ });
46
102
  ```
47
103
 
48
- The capability class acts as a **ceiling**: `chat-balanced` can fall to
49
- `chat-fast` but never escalate to `chat-thinking`. Embedding endpoints
50
- surface only when `modelClass` is explicitly `embedding`.
104
+ The class acts as a ceiling. `chat-balanced` may fall back to `chat-fast`, but
105
+ will not escalate to `chat-thinking`. Embedding endpoints are considered only
106
+ when the requested class is `embedding`.
107
+
108
+ ## List And Cache Serving Endpoints
109
+
110
+ ```ts
111
+ import { serving } from "@dbx-tools/model";
112
+
113
+ const endpoints = await serving.listServingEndpoints(client, host, {
114
+ ttlMs: 5 * 60_000,
115
+ });
116
+
117
+ const raw = await serving.listServingEndpointsUncached(client);
118
+ await serving.clearServingEndpointsCache(host);
119
+ ```
51
120
 
52
- ### Pure helpers (no I/O)
121
+ `listServingEndpoints()` uses AppKit's `CacheManager`, enriches endpoints with
122
+ classification and embedding dimensions, and keys the cache by workspace host.
123
+ `listServingEndpointsUncached()` is useful for simple scripts that only need the
124
+ SDK response and do not want a cache dependency.
53
125
 
54
- If you already hold the endpoint list (e.g. from a cached `/models`
55
- response), use the pure functions directly:
126
+ ## Fuzzy Resolve Endpoint Names
56
127
 
57
128
  ```ts
58
- import { resolveModelId, rankModels, resolveModel } from "@dbx-tools/model";
129
+ const matches = serving.searchServingEndpoints("claude sonnet", endpoints, {
130
+ threshold: 0.35,
131
+ });
59
132
 
60
- resolveModelId("claude sonnet", endpoints); // single best fuzzy match
61
- rankModels(endpoints, { search: "opus", limit: 3 }); // ranked list
62
- resolveModel(endpoints, { modelClass: "chat-fast" }); // single id + source
133
+ const endpointName = serving.resolveModelId("sonnet", endpoints);
63
134
  ```
64
135
 
65
- ### Listing the catalogue directly
136
+ Fuzzy matching is intentionally a server concern because it depends on the live
137
+ workspace catalogue and may re-list on misses. Disable it in callers that require
138
+ exact endpoint ids.
66
139
 
67
- `selectModel` / `searchModels` list endpoints for you, but you can also
68
- reach the catalogue functions:
140
+ ## Use Static Fallbacks
69
141
 
70
142
  ```ts
71
- import {
72
- listServingEndpoints,
73
- listServingEndpointsUncached,
74
- searchServingEndpoints,
75
- clearServingEndpointsCache,
76
- } from "@dbx-tools/model";
77
-
78
- // Cached + enriched (model-class stamp, embedding dimensions) through
79
- // AppKit's CacheManager. Keyed by `host`.
80
- const endpoints = await listServingEndpoints(client, host);
81
-
82
- // One-shot, dependency-light: straight from the SDK, no cache and no
83
- // enrichment. For CLIs / non-AppKit contexts that only need names.
84
- const raw = await listServingEndpointsUncached(client);
85
-
86
- // Fuzzy-rank a held list by name (the core resolveModelId builds on).
87
- searchServingEndpoints("claude sonnet", endpoints); // ScoredEndpoint[]
88
-
89
- // Force-evict one host's cache entry (or all when omitted).
90
- await clearServingEndpointsCache(host);
143
+ import { classes, fallback } from "@dbx-tools/model";
144
+ import { model } from "@dbx-tools/shared-model";
145
+
146
+ const cls = classes.parseModelClass("chat-fast") ?? model.ModelClass.ChatFast;
147
+ const modelId = fallback.modelForClass(cls);
91
148
  ```
92
149
 
93
- ## How resolution works
150
+ The fallback floor gives agents and local scripts a stable answer when a
151
+ workspace cannot list endpoints. Prefer live catalogue resolution for production
152
+ policy decisions; fallbacks are a last resort.
153
+
154
+ ## Modules
94
155
 
95
- 1. **Explicit ask** - fuzzy-matched within the optional class ceiling
96
- (or returned verbatim when `fuzzy: false`).
97
- 2. **No explicit ask** - the first operator-pinned `fallbacks` entry
98
- that exists in the live catalogue wins first, then the ranked live
99
- catalogue, then a small static `FALLBACK_MODEL_IDS` floor when the
100
- catalogue yields nothing in range (so a model id is always returned
101
- even offline).
156
+ - `resolve` - high-level `selectModel`, ranked search, and catalogue-held
157
+ resolver functions.
158
+ - `serving` - Databricks serving-endpoint listing, cache management, fuzzy
159
+ search, and endpoint-id resolution.
160
+ - `classes` - model-class parsing, ordering, and class-ceiling helpers.
161
+ - `fallback` - static fallback model ids per class.
102
162
 
103
- Capability bands are derived from the live workspace catalogue (the
104
- Foundation Model API quality/speed/cost scores), not a hand-maintained
105
- table - see `@dbx-tools/model-shared` for the classifier.
163
+ The AppKit-Mastra integration uses this package through
164
+ [`@dbx-tools/appkit-mastra`](../appkit-mastra); the local OpenAI-compatible
165
+ gateway uses it through [`@dbx-tools/model-proxy`](../../cli/model-proxy).
package/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ // GENERATED by projen watch - DO NOT EDIT.
2
+ // Regenerated from the exporting modules in ./src.
3
+ // Hand edits are overwritten on the next watch; this file is read-only.
4
+
5
+ export * as classes from "./src/classes";
6
+ export * as fallback from "./src/fallback";
7
+ export * as resolve from "./src/resolve";
8
+ export * as serving from "./src/serving";
9
+ export type { ResolveModelInput, ResolvedModelSelection, SelectModelInput, SearchModelsInput } from "./src/resolve";
10
+ export type { WorkspaceClientLike, ListServingEndpointsOptions, ResolvedModel, ResolveModelOptions, ScoredEndpoint } from "./src/serving";
package/package.json CHANGED
@@ -1,28 +1,49 @@
1
1
  {
2
2
  "name": "@dbx-tools/model",
3
- "version": "0.1.112",
4
- "dependencies": {
5
- "@dbx-tools/model-shared": "0.1.112",
6
- "@dbx-tools/shared": "0.1.112",
7
- "fuse.js": "^7.4.2"
3
+ "repository": {
4
+ "type": "git",
5
+ "url": "git+https://github.com/reggie-db/dbx-tools.git",
6
+ "directory": "workspaces/node/model"
8
7
  },
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.ts",
12
- "default": "./dist/index.js"
13
- }
8
+ "devDependencies": {
9
+ "@types/node": "^24.6.0",
10
+ "tsx": "^4.23.0",
11
+ "typescript": "^5.9.3"
14
12
  },
15
- "peerDependencies": {
16
- "@databricks/appkit": "^0.43.0"
13
+ "dependencies": {
14
+ "@databricks/appkit": "^0.43.0",
15
+ "fuse.js": "^7.4.2",
16
+ "@dbx-tools/appkit": "0.3.2",
17
+ "@dbx-tools/shared-core": "0.3.2",
18
+ "@dbx-tools/shared-model": "0.3.2"
17
19
  },
18
- "type": "module",
19
- "main": "./dist/index.js",
20
- "types": "./dist/index.d.ts",
21
- "files": [
22
- "dist"
23
- ],
24
- "license": "Apache-2.0",
20
+ "main": "index.ts",
21
+ "license": "UNLICENSED",
25
22
  "publishConfig": {
26
23
  "access": "public"
24
+ },
25
+ "version": "0.3.2",
26
+ "types": "index.ts",
27
+ "type": "module",
28
+ "exports": {
29
+ ".": "./index.ts",
30
+ "./package.json": "./package.json"
31
+ },
32
+ "dbxToolsConfig": {
33
+ "tags": [
34
+ "node"
35
+ ]
36
+ },
37
+ "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"pnpm exec projen\".",
38
+ "scripts": {
39
+ "build": "projen build",
40
+ "compile": "projen compile",
41
+ "default": "projen default",
42
+ "package": "projen package",
43
+ "post-compile": "projen post-compile",
44
+ "pre-compile": "projen pre-compile",
45
+ "test": "projen test",
46
+ "watch": "projen watch",
47
+ "projen": "projen"
27
48
  }
28
- }
49
+ }
package/src/classes.ts ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Model-class ordering for the model service.
3
+ *
4
+ * `@dbx-tools/shared-model` declares the {@link ModelClass} values and their
5
+ * schema; the *behavior* over those values - the chat capability order, the
6
+ * "this class and below" ceiling, and coercing loose request input to a class -
7
+ * lives here in the service so the shared wire-format surface stays purely
8
+ * declarative.
9
+ */
10
+
11
+ import { model } from "@dbx-tools/shared-model";
12
+
13
+ type ModelClass = model.ModelClass;
14
+ const { ModelClass, ModelClassSchema } = model;
15
+
16
+ /**
17
+ * Chat capability ladder in descending order - most capable
18
+ * ({@link ModelClass.ChatThinking}) first, least ({@link ModelClass.ChatFast})
19
+ * last. {@link ModelClass.Embedding} is a separate modality and is deliberately
20
+ * absent: the ceiling never spans it. This is the order used to filter "this
21
+ * class and below" and to break ranking ties toward the more capable model.
22
+ */
23
+ export const CHAT_CLASS_ORDER: readonly ModelClass[] = [
24
+ ModelClass.ChatThinking,
25
+ ModelClass.ChatBalanced,
26
+ ModelClass.ChatFast,
27
+ ];
28
+
29
+ /**
30
+ * Every class in display order: the chat ladder followed by
31
+ * {@link ModelClass.Embedding}. Used when flattening a full classification
32
+ * (e.g. stamping the class onto each cached endpoint).
33
+ */
34
+ export const MODEL_CLASS_ORDER: readonly ModelClass[] = [...CHAT_CLASS_ORDER, ModelClass.Embedding];
35
+
36
+ /** Whether `cls` is one of the chat capability bands (vs. embedding). */
37
+ export function isChatClass(cls: ModelClass): boolean {
38
+ return CHAT_CLASS_ORDER.includes(cls);
39
+ }
40
+
41
+ /**
42
+ * Coerce an arbitrary value (query string, header, body field) to a
43
+ * {@link ModelClass}, returning `null` when it isn't a known class. Lets a
44
+ * route accept a class request without throwing on junk input.
45
+ *
46
+ * Matches the full slug first (`"chat-fast"`, `"embedding"`), then - for a bare
47
+ * chat band - retries with a `chat-` prefix, so the shorthand `"thinking"` /
48
+ * `"balanced"` / `"fast"` resolves to the corresponding `chat-*` class.
49
+ */
50
+ export function parseModelClass(value: unknown): ModelClass | null {
51
+ const exact = ModelClassSchema.safeParse(value);
52
+ if (exact.success) return exact.data;
53
+ const prefixed = ModelClassSchema.safeParse(`chat-${value}`);
54
+ return prefixed.success ? prefixed.data : null;
55
+ }
56
+
57
+ /**
58
+ * Classes at or below `cls` in chat capability: the class itself plus every
59
+ * less-capable chat band, in {@link CHAT_CLASS_ORDER}. Treats a chat `cls` as a
60
+ * ceiling - requesting {@link ModelClass.ChatBalanced} yields
61
+ * `[ChatBalanced, ChatFast]`, never {@link ModelClass.ChatThinking} - so a
62
+ * class ask can degrade downward to a smaller chat model but never escalate to
63
+ * a larger one.
64
+ *
65
+ * {@link ModelClass.Embedding} is its own modality, not a rung on the chat
66
+ * ladder, so it yields just `[Embedding]`. An unrecognized class yields the
67
+ * full chat ladder.
68
+ */
69
+ export function classesAtOrBelow(cls: ModelClass): ModelClass[] {
70
+ if (cls === ModelClass.Embedding) return [ModelClass.Embedding];
71
+ const index = CHAT_CLASS_ORDER.indexOf(cls);
72
+ return index < 0 ? [...CHAT_CLASS_ORDER] : [...CHAT_CLASS_ORDER.slice(index)];
73
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Server-side offline fallback opinion for model selection.
3
+ *
4
+ * When the live `/serving-endpoints` catalogue can't be read at all - no user
5
+ * token, the service principal can't list, or the workspace is unreachable -
6
+ * the resolver still has to name *some* endpoint. This module holds that floor:
7
+ * a small, hard-coded set of well-known Foundation Model API endpoint names,
8
+ * each bucketed into a chat {@link ModelClass} by the shared
9
+ * {@link classify.classifyByFamily} heuristic (no classes are hard-coded here)
10
+ * and ordered best-first.
11
+ *
12
+ * This is deliberately *server-only*. A browser client never talks to
13
+ * Databricks directly - it always goes through this server - so it has nothing
14
+ * to fall back to and must not assume a stale, baked-in model list; it consumes
15
+ * the live `/models` response instead. The pure classifier
16
+ * ({@link classify.classifyEndpoints}) is what the client shares.
17
+ */
18
+
19
+ import { classify, model, type FamilyClass } from "@dbx-tools/shared-model";
20
+
21
+ type ModelClass = model.ModelClass;
22
+ const { ModelClass } = model;
23
+
24
+ /**
25
+ * Small, last-resort set of well-known Foundation Model API endpoint names,
26
+ * ordered best-first within each class by {@link classify.classifyByFamily}.
27
+ * Used only as the floor when the live `/serving-endpoints` catalogue can't be
28
+ * read at resolve time; the live, score-driven classification supersedes it
29
+ * whenever the workspace listing is available. Classes are not hard-coded here
30
+ * - each name is classified by family heuristic.
31
+ */
32
+ const FALLBACK_MODEL_NAMES: readonly string[] = [
33
+ "databricks-claude-opus-4-8",
34
+ "databricks-gpt-5-5-pro",
35
+ "databricks-gemini-3-1-pro",
36
+ "databricks-claude-sonnet-4-6",
37
+ "databricks-gpt-5-5",
38
+ "databricks-meta-llama-3-3-70b-instruct",
39
+ "databricks-claude-haiku-4-5",
40
+ "databricks-gpt-5-nano",
41
+ "databricks-meta-llama-3-1-8b-instruct",
42
+ ];
43
+
44
+ /**
45
+ * Static fallback model ids for a chat class, drawn from the small built-in
46
+ * {@link FALLBACK_MODEL_NAMES} list and ordered best-first by family rank. Sync
47
+ * and workspace-independent: this is the *fallback opinion* used to seed default
48
+ * lists or when the live catalogue is unreachable - live resolution prefers
49
+ * {@link classify.classifyEndpoints}. Returns `[]` for
50
+ * {@link ModelClass.Embedding} (the floor is chat-only).
51
+ */
52
+ export function modelsForClass(cls: ModelClass): readonly string[] {
53
+ return FALLBACK_MODEL_NAMES.map((name) => ({ name, c: classify.classifyByFamily(name) }))
54
+ .filter((x): x is { name: string; c: FamilyClass } => x.c !== null && x.c.class === cls)
55
+ .sort((a, b) => b.c.rank - a.c.rank)
56
+ .map((x) => x.name);
57
+ }
58
+
59
+ /** Top static fallback model id for a chat class. */
60
+ export function modelForClass(cls: ModelClass): string {
61
+ return modelsForClass(cls)[0]!;
62
+ }
63
+
64
+ /**
65
+ * Priority-ordered fallback chain (ChatThinking -> ChatBalanced -> ChatFast)
66
+ * over the small built-in list. The floor walked at resolve time when no agent
67
+ * / plugin / env / request model is set *and* the live catalogue yields nothing.
68
+ */
69
+ export const FALLBACK_MODEL_IDS: readonly string[] = [
70
+ ...modelsForClass(ModelClass.ChatThinking),
71
+ ...modelsForClass(ModelClass.ChatBalanced),
72
+ ...modelsForClass(ModelClass.ChatFast),
73
+ ];