@dbx-tools/model 0.1.111 → 0.3.1
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 +134 -74
- package/index.ts +10 -0
- package/package.json +41 -20
- package/src/classes.ts +73 -0
- package/src/fallback.ts +73 -0
- package/src/resolve.ts +275 -0
- package/src/serving.ts +381 -0
- package/test/classes.test.ts +74 -0
- package/test/resolve.test.ts +169 -0
- package/tsconfig.json +41 -0
- package/dist/index.d.ts +0 -291
- package/dist/index.js +0 -576
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
|
|
4
|
+
import { model } from "@dbx-tools/shared-model";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
CHAT_CLASS_ORDER,
|
|
8
|
+
classesAtOrBelow,
|
|
9
|
+
isChatClass,
|
|
10
|
+
MODEL_CLASS_ORDER,
|
|
11
|
+
parseModelClass,
|
|
12
|
+
} from "../src/classes";
|
|
13
|
+
|
|
14
|
+
const { ModelClass } = model;
|
|
15
|
+
|
|
16
|
+
describe("CHAT_CLASS_ORDER / MODEL_CLASS_ORDER", () => {
|
|
17
|
+
it("orders chat bands most-capable first, embedding excluded from the ladder", () => {
|
|
18
|
+
assert.deepEqual(CHAT_CLASS_ORDER, [
|
|
19
|
+
ModelClass.ChatThinking,
|
|
20
|
+
ModelClass.ChatBalanced,
|
|
21
|
+
ModelClass.ChatFast,
|
|
22
|
+
]);
|
|
23
|
+
assert.deepEqual(MODEL_CLASS_ORDER, [
|
|
24
|
+
ModelClass.ChatThinking,
|
|
25
|
+
ModelClass.ChatBalanced,
|
|
26
|
+
ModelClass.ChatFast,
|
|
27
|
+
ModelClass.Embedding,
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("isChatClass", () => {
|
|
33
|
+
it("is true for chat bands and false for embedding", () => {
|
|
34
|
+
assert.equal(isChatClass(ModelClass.ChatThinking), true);
|
|
35
|
+
assert.equal(isChatClass(ModelClass.ChatFast), true);
|
|
36
|
+
assert.equal(isChatClass(ModelClass.Embedding), false);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("classesAtOrBelow", () => {
|
|
41
|
+
it("treats a chat band as a ceiling - the band and everything below it", () => {
|
|
42
|
+
assert.deepEqual(classesAtOrBelow(ModelClass.ChatThinking), [
|
|
43
|
+
ModelClass.ChatThinking,
|
|
44
|
+
ModelClass.ChatBalanced,
|
|
45
|
+
ModelClass.ChatFast,
|
|
46
|
+
]);
|
|
47
|
+
assert.deepEqual(classesAtOrBelow(ModelClass.ChatBalanced), [
|
|
48
|
+
ModelClass.ChatBalanced,
|
|
49
|
+
ModelClass.ChatFast,
|
|
50
|
+
]);
|
|
51
|
+
assert.deepEqual(classesAtOrBelow(ModelClass.ChatFast), [ModelClass.ChatFast]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("keeps embedding to itself - it is not a rung on the chat ladder", () => {
|
|
55
|
+
assert.deepEqual(classesAtOrBelow(ModelClass.Embedding), [ModelClass.Embedding]);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("parseModelClass", () => {
|
|
60
|
+
it("accepts full slugs and rejects junk", () => {
|
|
61
|
+
assert.equal(parseModelClass("chat-thinking"), ModelClass.ChatThinking);
|
|
62
|
+
assert.equal(parseModelClass("chat-balanced"), ModelClass.ChatBalanced);
|
|
63
|
+
assert.equal(parseModelClass("chat-fast"), ModelClass.ChatFast);
|
|
64
|
+
assert.equal(parseModelClass("embedding"), ModelClass.Embedding);
|
|
65
|
+
assert.equal(parseModelClass("medium"), null);
|
|
66
|
+
assert.equal(parseModelClass(undefined), null);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("resolves a bare chat band via the chat- prefix shorthand", () => {
|
|
70
|
+
assert.equal(parseModelClass("thinking"), ModelClass.ChatThinking);
|
|
71
|
+
assert.equal(parseModelClass("balanced"), ModelClass.ChatBalanced);
|
|
72
|
+
assert.equal(parseModelClass("fast"), ModelClass.ChatFast);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
|
|
4
|
+
import { model, type ServingEndpointSummary } from "@dbx-tools/shared-model";
|
|
5
|
+
|
|
6
|
+
import { FALLBACK_MODEL_IDS, modelsForClass } from "../src/fallback";
|
|
7
|
+
import { rankModels, resolveModel } from "../src/resolve";
|
|
8
|
+
import { resolveModelId, searchServingEndpoints } from "../src/serving";
|
|
9
|
+
|
|
10
|
+
const { ModelClass } = model;
|
|
11
|
+
|
|
12
|
+
const CHAT_TASK = "llm/v1/chat";
|
|
13
|
+
const EMBEDDING_TASK = "llm/v1/embeddings";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Unscored chat endpoints classify deterministically by family name (opus ->
|
|
17
|
+
* ChatThinking, sonnet -> ChatBalanced, haiku -> ChatFast), so class membership
|
|
18
|
+
* in these tests doesn't depend on quality quantiles.
|
|
19
|
+
*/
|
|
20
|
+
function chat(name: string): ServingEndpointSummary {
|
|
21
|
+
return { name, task: CHAT_TASK };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** An embedding endpoint - classified into ModelClass.Embedding by task. */
|
|
25
|
+
function embedding(name: string): ServingEndpointSummary {
|
|
26
|
+
return { name, task: EMBEDDING_TASK };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const OPUS_8 = "databricks-claude-opus-4-8";
|
|
30
|
+
const OPUS_7 = "databricks-claude-opus-4-7";
|
|
31
|
+
const OPUS_6 = "databricks-claude-opus-4-6";
|
|
32
|
+
const SONNET = "databricks-claude-sonnet-4-6";
|
|
33
|
+
const HAIKU_5 = "databricks-claude-haiku-4-5";
|
|
34
|
+
const HAIKU_3 = "databricks-claude-haiku-4-3";
|
|
35
|
+
const GTE = "databricks-gte-large-en";
|
|
36
|
+
const BGE = "databricks-bge-large-en";
|
|
37
|
+
|
|
38
|
+
/** opus (ChatThinking) / sonnet (ChatBalanced) / haiku (ChatFast) - one per band. */
|
|
39
|
+
const TIERED = [chat(OPUS_8), chat(SONNET), chat(HAIKU_5)];
|
|
40
|
+
|
|
41
|
+
function names(models: { endpoint: ServingEndpointSummary }[]): string[] {
|
|
42
|
+
return models.map((m) => m.endpoint.name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe("searchServingEndpoints / resolveModelId", () => {
|
|
46
|
+
const endpoints = [chat(OPUS_8), chat(SONNET), chat(HAIKU_5)];
|
|
47
|
+
|
|
48
|
+
it("short-circuits an exact name to score 0", () => {
|
|
49
|
+
const [best] = searchServingEndpoints(SONNET, endpoints);
|
|
50
|
+
assert.equal(best?.endpoint.name, SONNET);
|
|
51
|
+
assert.equal(best?.score, 0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("tokenized fuzzy-matches a loose name", () => {
|
|
55
|
+
const result = resolveModelId("claude sonnet", endpoints);
|
|
56
|
+
assert.equal(result.matched, true);
|
|
57
|
+
assert.equal(result.modelId, SONNET);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("returns the input verbatim when nothing matches", () => {
|
|
61
|
+
const result = resolveModelId("zzz-no-such-model", endpoints);
|
|
62
|
+
assert.equal(result.matched, false);
|
|
63
|
+
assert.equal(result.modelId, "zzz-no-such-model");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("returns [] for an empty catalogue", () => {
|
|
67
|
+
assert.deepEqual(searchServingEndpoints("opus", []), []);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("rankModels", () => {
|
|
72
|
+
it("ranks a search match-then-class, version breaking the tie", () => {
|
|
73
|
+
const ranked = rankModels([chat(OPUS_6), chat(OPUS_8), chat(OPUS_7)], {
|
|
74
|
+
search: "opus",
|
|
75
|
+
});
|
|
76
|
+
assert.deepEqual(names(ranked), [OPUS_8, OPUS_7, OPUS_6]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("with no search, orders by class then within-class rank", () => {
|
|
80
|
+
const ranked = rankModels(TIERED);
|
|
81
|
+
assert.deepEqual(names(ranked), [OPUS_8, SONNET, HAIKU_5]);
|
|
82
|
+
assert.deepEqual(
|
|
83
|
+
ranked.map((m) => m.modelClass),
|
|
84
|
+
[ModelClass.ChatThinking, ModelClass.ChatBalanced, ModelClass.ChatFast],
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("excludes embeddings from the default (chat-only) ranking", () => {
|
|
89
|
+
const ranked = rankModels([chat(OPUS_8), embedding(GTE), embedding(BGE)]);
|
|
90
|
+
assert.deepEqual(names(ranked), [OPUS_8]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("ranks embeddings only when ModelClass.Embedding is requested", () => {
|
|
94
|
+
const ranked = rankModels([chat(OPUS_8), embedding(GTE), embedding(BGE)], {
|
|
95
|
+
modelClass: ModelClass.Embedding,
|
|
96
|
+
});
|
|
97
|
+
assert.deepEqual(names(ranked), [GTE, BGE]); // no chat model leaks in
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("treats a chat class as a ceiling: that band and below, never above", () => {
|
|
101
|
+
const ranked = rankModels(TIERED, { modelClass: ModelClass.ChatBalanced });
|
|
102
|
+
assert.deepEqual(names(ranked), [SONNET, HAIKU_5]); // no opus (ChatThinking)
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("degrades to a lower band when the requested band is empty", () => {
|
|
106
|
+
// "medium" requested but only "small" exists -> the highest small is
|
|
107
|
+
// returned, never a "large".
|
|
108
|
+
const ranked = rankModels([chat(OPUS_8), chat(HAIKU_5), chat(HAIKU_3)], {
|
|
109
|
+
modelClass: ModelClass.ChatBalanced,
|
|
110
|
+
limit: 1,
|
|
111
|
+
});
|
|
112
|
+
assert.deepEqual(names(ranked), [HAIKU_5]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("scopes a search to the class ceiling", () => {
|
|
116
|
+
const ranked = rankModels(TIERED, {
|
|
117
|
+
search: "claude",
|
|
118
|
+
modelClass: ModelClass.ChatFast,
|
|
119
|
+
});
|
|
120
|
+
assert.deepEqual(names(ranked), [HAIKU_5]); // opus / sonnet excluded by ceiling
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("applies a limit", () => {
|
|
124
|
+
assert.equal(rankModels(TIERED, { limit: 2 }).length, 2);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("resolveModel", () => {
|
|
129
|
+
it("fuzzy-resolves an explicit name to the best ranked match (limit 1)", () => {
|
|
130
|
+
const result = resolveModel([chat(OPUS_6), chat(OPUS_8), chat(OPUS_7)], {
|
|
131
|
+
explicit: "opus",
|
|
132
|
+
});
|
|
133
|
+
assert.deepEqual(result, { modelId: OPUS_8, source: "fuzzy-match" });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("returns an explicit name verbatim when fuzzy is off", () => {
|
|
137
|
+
const result = resolveModel(TIERED, { explicit: "my-pinned-model", fuzzy: false });
|
|
138
|
+
assert.deepEqual(result, { modelId: "my-pinned-model", source: "explicit" });
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("resolves a class ask to the top of that band and below", () => {
|
|
142
|
+
const result = resolveModel(TIERED, { modelClass: ModelClass.ChatBalanced });
|
|
143
|
+
assert.deepEqual(result, { modelId: SONNET, source: "class" });
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("never selects an embedding model for a general (chat) ask", () => {
|
|
147
|
+
const result = resolveModel([embedding(GTE), chat(SONNET)]);
|
|
148
|
+
assert.equal(result.modelId, SONNET);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("lets an operator-pinned fallback present in the catalogue win", () => {
|
|
152
|
+
const pinned = "databricks-approved-custom";
|
|
153
|
+
const result = resolveModel([...TIERED, chat(pinned)], { fallbacks: [pinned] });
|
|
154
|
+
assert.deepEqual(result, { modelId: pinned, source: "fallback" });
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("falls back to the class's static floor for an empty catalogue", () => {
|
|
158
|
+
const result = resolveModel([], { modelClass: ModelClass.ChatBalanced });
|
|
159
|
+
assert.deepEqual(result, {
|
|
160
|
+
modelId: modelsForClass(ModelClass.ChatBalanced)[0]!,
|
|
161
|
+
source: "class",
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("falls back to the static floor with no intent and an empty catalogue", () => {
|
|
166
|
+
const result = resolveModel([], {});
|
|
167
|
+
assert.deepEqual(result, { modelId: FALLBACK_MODEL_IDS[0]!, source: "fallback" });
|
|
168
|
+
});
|
|
169
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// ~~ Generated by projen. To modify, edit .projenrc.js and run "pnpm exec projen".
|
|
2
|
+
{
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "src",
|
|
5
|
+
"outDir": "lib",
|
|
6
|
+
"alwaysStrict": true,
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"experimentalDecorators": true,
|
|
10
|
+
"inlineSourceMap": true,
|
|
11
|
+
"inlineSources": true,
|
|
12
|
+
"lib": [
|
|
13
|
+
"ES2022"
|
|
14
|
+
],
|
|
15
|
+
"module": "ESNext",
|
|
16
|
+
"noEmitOnError": false,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"noImplicitAny": true,
|
|
19
|
+
"noImplicitReturns": true,
|
|
20
|
+
"noImplicitThis": true,
|
|
21
|
+
"noUnusedLocals": true,
|
|
22
|
+
"noUnusedParameters": true,
|
|
23
|
+
"resolveJsonModule": true,
|
|
24
|
+
"strict": true,
|
|
25
|
+
"strictNullChecks": true,
|
|
26
|
+
"strictPropertyInitialization": true,
|
|
27
|
+
"stripInternal": true,
|
|
28
|
+
"target": "ES2022",
|
|
29
|
+
"types": [
|
|
30
|
+
"node"
|
|
31
|
+
],
|
|
32
|
+
"moduleResolution": "bundler",
|
|
33
|
+
"skipLibCheck": true
|
|
34
|
+
},
|
|
35
|
+
"include": [
|
|
36
|
+
"src/**/*.ts"
|
|
37
|
+
],
|
|
38
|
+
"exclude": [
|
|
39
|
+
"node_modules"
|
|
40
|
+
]
|
|
41
|
+
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import { ModelClass, ModelQuery, RankedModel, ServingEndpointSummary } from "@dbx-tools/model-shared";
|
|
2
|
-
import { appkitUtils } from "@dbx-tools/shared";
|
|
3
|
-
export * from "@dbx-tools/model-shared";
|
|
4
|
-
|
|
5
|
-
//#region packages/model/src/classes.d.ts
|
|
6
|
-
/**
|
|
7
|
-
* Chat capability ladder in descending order - most capable
|
|
8
|
-
* ({@link ModelClass.ChatThinking}) first, least
|
|
9
|
-
* ({@link ModelClass.ChatFast}) last. {@link ModelClass.Embedding} is a
|
|
10
|
-
* separate modality and is deliberately absent: the ceiling never spans
|
|
11
|
-
* it. This is the order used to filter "this class and below" and to
|
|
12
|
-
* break ranking ties toward the more capable model.
|
|
13
|
-
*/
|
|
14
|
-
declare const CHAT_CLASS_ORDER: readonly ModelClass[];
|
|
15
|
-
/**
|
|
16
|
-
* Every class in display order: the chat ladder followed by
|
|
17
|
-
* {@link ModelClass.Embedding}. Used when flattening a full
|
|
18
|
-
* classification (e.g. stamping the class onto each cached endpoint).
|
|
19
|
-
*/
|
|
20
|
-
declare const MODEL_CLASS_ORDER: readonly ModelClass[];
|
|
21
|
-
/** Whether `cls` is one of the chat capability bands (vs. embedding). */
|
|
22
|
-
declare function isChatClass(cls: ModelClass): boolean;
|
|
23
|
-
/**
|
|
24
|
-
* Coerce an arbitrary value (query string, header, body field) to a
|
|
25
|
-
* {@link ModelClass}, returning `null` when it isn't a known class.
|
|
26
|
-
* Lets a route accept a class request without throwing on junk input.
|
|
27
|
-
*
|
|
28
|
-
* Matches the full slug first (`"chat-fast"`, `"embedding"`), then -
|
|
29
|
-
* for a bare chat band - retries with a `chat-` prefix, so the shorthand
|
|
30
|
-
* `"thinking"` / `"balanced"` / `"fast"` resolves to the corresponding
|
|
31
|
-
* `chat-*` class.
|
|
32
|
-
*/
|
|
33
|
-
declare function parseModelClass(value: unknown): ModelClass | null;
|
|
34
|
-
/**
|
|
35
|
-
* Classes at or below `cls` in chat capability: the class itself plus
|
|
36
|
-
* every less-capable chat band, in {@link CHAT_CLASS_ORDER}. Treats a
|
|
37
|
-
* chat `cls` as a ceiling - requesting {@link ModelClass.ChatBalanced}
|
|
38
|
-
* yields `[ChatBalanced, ChatFast]`, never
|
|
39
|
-
* {@link ModelClass.ChatThinking} - so a class ask can degrade downward
|
|
40
|
-
* to a smaller chat model but never escalate to a larger one.
|
|
41
|
-
*
|
|
42
|
-
* {@link ModelClass.Embedding} is its own modality, not a rung on the
|
|
43
|
-
* chat ladder, so it yields just `[Embedding]`. An unrecognized class
|
|
44
|
-
* yields the full chat ladder.
|
|
45
|
-
*/
|
|
46
|
-
declare function classesAtOrBelow(cls: ModelClass): ModelClass[];
|
|
47
|
-
//#endregion
|
|
48
|
-
//#region packages/model/src/fallback.d.ts
|
|
49
|
-
/**
|
|
50
|
-
* Static fallback model ids for a chat class, drawn from the small
|
|
51
|
-
* built-in {@link FALLBACK_MODEL_NAMES} list and ordered best-first by
|
|
52
|
-
* family rank. Sync and workspace-independent: this is the *fallback
|
|
53
|
-
* opinion* used to seed default lists or when the live catalogue is
|
|
54
|
-
* unreachable - live resolution prefers `classifyEndpoints`. Returns
|
|
55
|
-
* `[]` for {@link ModelClass.Embedding} (the floor is chat-only).
|
|
56
|
-
*/
|
|
57
|
-
declare function modelsForClass(cls: ModelClass): readonly string[];
|
|
58
|
-
/** Top static fallback model id for a chat class. */
|
|
59
|
-
declare function modelForClass(cls: ModelClass): string;
|
|
60
|
-
/**
|
|
61
|
-
* Priority-ordered fallback chain (ChatThinking -> ChatBalanced ->
|
|
62
|
-
* ChatFast) over the small built-in list. The floor walked at resolve
|
|
63
|
-
* time when no agent / plugin / env / request model is set *and* the
|
|
64
|
-
* live catalogue yields nothing.
|
|
65
|
-
*/
|
|
66
|
-
declare const FALLBACK_MODEL_IDS: readonly string[];
|
|
67
|
-
//#endregion
|
|
68
|
-
//#region packages/model/src/serving.d.ts
|
|
69
|
-
/**
|
|
70
|
-
* Structural type for the Databricks workspace client, re-exported from
|
|
71
|
-
* `@dbx-tools/shared` so the rest of this package can keep importing it
|
|
72
|
-
* from here. See `appkitUtils.WorkspaceClientLike` for the canonical
|
|
73
|
-
* definition.
|
|
74
|
-
*/
|
|
75
|
-
type WorkspaceClientLike = appkitUtils.WorkspaceClientLike;
|
|
76
|
-
/** Default TTL for the in-memory endpoint cache. Matches the Databricks SDK's session lifetime budget. */
|
|
77
|
-
declare const DEFAULT_MODEL_CACHE_TTL_MS: number;
|
|
78
|
-
/** Default Fuse.js score threshold below which a fuzzy match is accepted. */
|
|
79
|
-
declare const DEFAULT_FUZZY_THRESHOLD = 0.4;
|
|
80
|
-
/** Options for {@link listServingEndpoints}. */
|
|
81
|
-
interface ListServingEndpointsOptions {
|
|
82
|
-
/**
|
|
83
|
-
* Override the default cache TTL for this call, in milliseconds.
|
|
84
|
-
* Forwarded to `CacheManager` as seconds.
|
|
85
|
-
*/
|
|
86
|
-
ttlMs?: number;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* List Model Serving endpoints for the workspace owning `client`,
|
|
90
|
-
* routed through AppKit's `CacheManager`. The manager gives us
|
|
91
|
-
* everything `cachetools.TTLCache` provides plus what
|
|
92
|
-
* `cachetools-async` adds on top: per-entry TTL, in-flight request
|
|
93
|
-
* coalescing (concurrent callers share one fetch via the manager's
|
|
94
|
-
* internal `inFlightRequests` map), bounded size, telemetry spans
|
|
95
|
-
* (`cache.getOrExecute`), and optional Lakebase persistence so the
|
|
96
|
-
* catalogue survives restarts when the lakebase plugin is wired up.
|
|
97
|
-
*
|
|
98
|
-
* Returns plain {@link ServingEndpointSummary} objects (a stable
|
|
99
|
-
* subset of the SDK type) so cache hits never expose stale SDK
|
|
100
|
-
* internals. Errors from `CacheManager` or the SDK fetch propagate
|
|
101
|
-
* to the caller - we don't swallow them so users see the real
|
|
102
|
-
* auth / network issue.
|
|
103
|
-
*
|
|
104
|
-
* @param host - Workspace host used as the cache key. Pass the value
|
|
105
|
-
* resolved from `client.config.getHost()` so multi-host apps share
|
|
106
|
-
* one entry per workspace.
|
|
107
|
-
* @param options.ttlMs - Override the default TTL just for this call.
|
|
108
|
-
* Forwarded to `CacheManager` as seconds.
|
|
109
|
-
*/
|
|
110
|
-
declare function listServingEndpoints(client: WorkspaceClientLike, host: string, options?: ListServingEndpointsOptions): Promise<ServingEndpointSummary[]>;
|
|
111
|
-
/**
|
|
112
|
-
* List the workspace's serving endpoints as minimal
|
|
113
|
-
* {@link ServingEndpointSummary} objects straight from the SDK: no
|
|
114
|
-
* caching, and none of the cache-load enrichment ({@link listServingEndpoints}
|
|
115
|
-
* adds the {@link ModelClass} stamp and the embedding-dimension probe).
|
|
116
|
-
* Use this for a one-shot, dependency-light listing - e.g. a CLI that
|
|
117
|
-
* only needs names/tasks for fuzzy resolution and doesn't want AppKit's
|
|
118
|
-
* `CacheManager` or the per-embedding ping cost. Prefer
|
|
119
|
-
* {@link listServingEndpoints} for the cached, enriched view.
|
|
120
|
-
*/
|
|
121
|
-
declare function listServingEndpointsUncached(client: WorkspaceClientLike): Promise<ServingEndpointSummary[]>;
|
|
122
|
-
/**
|
|
123
|
-
* Force-evict cached endpoint listings via AppKit's `CacheManager`.
|
|
124
|
-
* With a `host` deletes that one workspace's entry; without one
|
|
125
|
-
* clears every cache entry on the manager (since `CacheManager`
|
|
126
|
-
* doesn't expose a namespace-scoped clear, this is the brute-force
|
|
127
|
-
* path - fine for tests, avoid in steady-state code).
|
|
128
|
-
*/
|
|
129
|
-
declare function clearServingEndpointsCache(host?: string): Promise<void>;
|
|
130
|
-
/**
|
|
131
|
-
* Result of fuzzy-resolving a user-supplied model name against the
|
|
132
|
-
* live endpoint list. `score` is Fuse.js's distance (`0` is exact,
|
|
133
|
-
* `1` is no match); `matched` is `false` when the score exceeds the
|
|
134
|
-
* configured threshold so callers can fall back to the original
|
|
135
|
-
* input (Databricks will then return a clean 404).
|
|
136
|
-
*/
|
|
137
|
-
interface ResolvedModel {
|
|
138
|
-
modelId: string;
|
|
139
|
-
matched: boolean;
|
|
140
|
-
score?: number;
|
|
141
|
-
}
|
|
142
|
-
/** Options accepted by {@link resolveModelId} / {@link searchServingEndpoints}. */
|
|
143
|
-
interface ResolveModelOptions {
|
|
144
|
-
/** Fuse.js threshold (0 = exact, 1 = anything). Default `0.4`. */
|
|
145
|
-
threshold?: number;
|
|
146
|
-
}
|
|
147
|
-
/** A serving endpoint paired with its fuzzy-match distance for a query. */
|
|
148
|
-
interface ScoredEndpoint {
|
|
149
|
-
endpoint: ServingEndpointSummary;
|
|
150
|
-
/** Fuse.js distance: `0` is exact, `1` is no match. */
|
|
151
|
-
score: number;
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Fuzzy-rank endpoints by how closely their `name` matches `input`,
|
|
155
|
-
* best (lowest score) first, keeping only those within `threshold`:
|
|
156
|
-
*
|
|
157
|
-
* 1. An exact name match short-circuits to a single `score: 0` result.
|
|
158
|
-
* 2. Otherwise the input is tokenized (dashes / underscores / spaces
|
|
159
|
-
* become separators) and fed through Fuse.js extended search, which
|
|
160
|
-
* AND-s each token with fuzzy matching enabled - the "tokenized
|
|
161
|
-
* fuzzy match" a caller reaches for when they type `"claude sonnet"`
|
|
162
|
-
* instead of the full endpoint name.
|
|
163
|
-
*
|
|
164
|
-
* Returns `[]` for an empty endpoint list or when `input` tokenizes to
|
|
165
|
-
* nothing, so callers fall back to the raw input and let Databricks
|
|
166
|
-
* surface a clean 404. This multi-result core is shared by
|
|
167
|
-
* {@link resolveModelId} (single best) and the ranked `rankModels`
|
|
168
|
-
* selector.
|
|
169
|
-
*/
|
|
170
|
-
declare function searchServingEndpoints(input: string, endpoints: readonly ServingEndpointSummary[], options?: ResolveModelOptions): ScoredEndpoint[];
|
|
171
|
-
/**
|
|
172
|
-
* Snap a user-supplied model name to the single closest configured
|
|
173
|
-
* serving endpoint via {@link searchServingEndpoints}. Returns the
|
|
174
|
-
* input unchanged with `matched: false` when nothing scores within the
|
|
175
|
-
* threshold (or the catalogue is empty), so a deliberate model id is
|
|
176
|
-
* never silently rewritten to a similar-looking neighbour and the
|
|
177
|
-
* upstream call surfaces the canonical 404.
|
|
178
|
-
*/
|
|
179
|
-
declare function resolveModelId(input: string, endpoints: readonly ServingEndpointSummary[], options?: ResolveModelOptions): ResolvedModel;
|
|
180
|
-
//#endregion
|
|
181
|
-
//#region packages/model/src/resolve.d.ts
|
|
182
|
-
/** Caller intent passed to {@link resolveModel}. */
|
|
183
|
-
interface ResolveModelInput {
|
|
184
|
-
/**
|
|
185
|
-
* Explicit model id / loose name (per-request override, agent /
|
|
186
|
-
* plugin default, or env var). When set it wins over `modelClass`
|
|
187
|
-
* and `fallbacks`.
|
|
188
|
-
*/
|
|
189
|
-
explicit?: string;
|
|
190
|
-
/**
|
|
191
|
-
* Fuzzy-match an `explicit` name against the live catalogue so loose
|
|
192
|
-
* names like `"claude sonnet"` resolve. Default `true`. When `false`
|
|
193
|
-
* the explicit input is returned verbatim (Databricks surfaces the
|
|
194
|
-
* canonical 404 if it doesn't exist).
|
|
195
|
-
*/
|
|
196
|
-
fuzzy?: boolean;
|
|
197
|
-
/** Fuse.js threshold forwarded to the fuzzy `search` match ({@link searchServingEndpoints}). */
|
|
198
|
-
threshold?: number;
|
|
199
|
-
/**
|
|
200
|
-
* Chat capability class to resolve when no `explicit` id is given.
|
|
201
|
-
* The live catalogue is classified by its Foundation Model API scores
|
|
202
|
-
* and the top available model in the class (and the chat bands below
|
|
203
|
-
* it) wins, falling back to the class's small static list.
|
|
204
|
-
*/
|
|
205
|
-
modelClass?: ModelClass;
|
|
206
|
-
/**
|
|
207
|
-
* Operator-supplied fallback ids tried *first* in the no-explicit,
|
|
208
|
-
* no-class path (e.g. a regulated workspace pinned to an approved
|
|
209
|
-
* subset), ahead of the auto-classified catalogue.
|
|
210
|
-
*/
|
|
211
|
-
fallbacks?: readonly string[];
|
|
212
|
-
}
|
|
213
|
-
/** Outcome of {@link resolveModel}: the chosen id plus how it was reached. */
|
|
214
|
-
interface ResolvedModelSelection {
|
|
215
|
-
modelId: string;
|
|
216
|
-
source: "explicit" | "fuzzy-match" | "class" | "fallback";
|
|
217
|
-
}
|
|
218
|
-
/** Intent + catalogue knobs passed to {@link selectModel}. */
|
|
219
|
-
interface SelectModelInput extends ResolveModelInput {
|
|
220
|
-
/** TTL override for the cached `/serving-endpoints` listing, in ms. */
|
|
221
|
-
ttlMs?: number;
|
|
222
|
-
}
|
|
223
|
-
/** TTL override merged into a {@link ModelQuery} for {@link searchModels}. */
|
|
224
|
-
interface SearchModelsInput extends ModelQuery {
|
|
225
|
-
/** TTL override for the cached `/serving-endpoints` listing, in ms. */
|
|
226
|
-
ttlMs?: number;
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Rank the live catalogue against a {@link ModelQuery}, best-first.
|
|
230
|
-
*
|
|
231
|
-
* Candidates are the classified endpoints in the eligible classes:
|
|
232
|
-
* {@link classesAtOrBelow} the requested `modelClass`, or - when none is
|
|
233
|
-
* given - the chat bands only ({@link CHAT_CLASS_ORDER}), so a general
|
|
234
|
-
* ask never surfaces an embedding endpoint. Each class bucket is
|
|
235
|
-
* already best-first from {@link classifyEndpoints}. Ranking is **match
|
|
236
|
-
* then class**:
|
|
237
|
-
*
|
|
238
|
-
* 1. With a `search`, only endpoints matching it survive, ordered by
|
|
239
|
-
* match distance (bucketed via {@link matchBucket} so near-identical
|
|
240
|
-
* scores tie), then by class (more capable first), then by the stable
|
|
241
|
-
* within-class rank.
|
|
242
|
-
* 2. Without a `search`, the class-then-rank candidate order stands.
|
|
243
|
-
*
|
|
244
|
-
* A `limit` truncates the result. Returns `[]` when nothing is
|
|
245
|
-
* eligible or matches - callers layer their own fallback.
|
|
246
|
-
*/
|
|
247
|
-
declare function rankModels(endpoints: readonly ServingEndpointSummary[], query?: ModelQuery): RankedModel[];
|
|
248
|
-
/**
|
|
249
|
-
* Rank a workspace's catalogue in one call: list its
|
|
250
|
-
* `/serving-endpoints` (cached) and run {@link rankModels} over the
|
|
251
|
-
* result. The list counterpart to {@link selectModel}, for a consumer
|
|
252
|
-
* that wants the full ranked set (a model picker, a CLI) rather than a
|
|
253
|
-
* single id. Catalogue fetches fail loud: network / auth errors
|
|
254
|
-
* propagate so the caller sees the real SDK message.
|
|
255
|
-
*
|
|
256
|
-
* @param host - Workspace host used as the cache key. Pass the value
|
|
257
|
-
* resolved from `client.config.getHost()`.
|
|
258
|
-
*/
|
|
259
|
-
declare function searchModels(client: WorkspaceClientLike, host: string, input?: SearchModelsInput): Promise<RankedModel[]>;
|
|
260
|
-
/**
|
|
261
|
-
* Resolve a model id for a workspace in one call: list its
|
|
262
|
-
* `/serving-endpoints` (cached) and run {@link resolveModel} over the
|
|
263
|
-
* result. This is the entry point for any consumer that holds a
|
|
264
|
-
* `WorkspaceClient` and just wants a usable model name - a Lakeflow
|
|
265
|
-
* job, a one-off script, or the Mastra plugin alike.
|
|
266
|
-
*
|
|
267
|
-
* Cheap exit: when an `explicit` name is given and `fuzzy` is off, the
|
|
268
|
-
* catalogue is never fetched - the name is returned verbatim and
|
|
269
|
-
* Databricks surfaces the canonical 404 if it doesn't exist. Catalogue
|
|
270
|
-
* fetches otherwise fail loud: network / auth errors propagate so the
|
|
271
|
-
* caller sees the real SDK message instead of a silent fallback.
|
|
272
|
-
*
|
|
273
|
-
* @param host - Workspace host used as the cache key. Pass the value
|
|
274
|
-
* resolved from `client.config.getHost()`.
|
|
275
|
-
*/
|
|
276
|
-
declare function selectModel(client: WorkspaceClientLike, host: string, input?: SelectModelInput): Promise<ResolvedModelSelection>;
|
|
277
|
-
/**
|
|
278
|
-
* Resolve a single model id from the live catalogue and caller intent,
|
|
279
|
-
* delegating the live selection to {@link rankModels} with `limit: 1`.
|
|
280
|
-
*
|
|
281
|
-
* 1. **Explicit ask**: with `fuzzy` off, returned verbatim; otherwise
|
|
282
|
-
* fuzzy-ranked within the (optional) class ceiling and the best taken,
|
|
283
|
-
* falling back to the input verbatim when nothing matches.
|
|
284
|
-
* 2. **No explicit ask**: an operator-pinned `fallback` that exists in
|
|
285
|
-
* the live catalogue wins first; then the ranked live catalogue
|
|
286
|
-
* (class ceiling applied); then the static {@link FALLBACK_MODEL_IDS}
|
|
287
|
-
* floor when the catalogue yields nothing in range.
|
|
288
|
-
*/
|
|
289
|
-
declare function resolveModel(endpoints: readonly ServingEndpointSummary[], input?: ResolveModelInput): ResolvedModelSelection;
|
|
290
|
-
//#endregion
|
|
291
|
-
export { CHAT_CLASS_ORDER, DEFAULT_FUZZY_THRESHOLD, DEFAULT_MODEL_CACHE_TTL_MS, FALLBACK_MODEL_IDS, ListServingEndpointsOptions, MODEL_CLASS_ORDER, ResolveModelInput, ResolveModelOptions, ResolvedModel, ResolvedModelSelection, ScoredEndpoint, SearchModelsInput, SelectModelInput, WorkspaceClientLike, classesAtOrBelow, clearServingEndpointsCache, isChatClass, listServingEndpoints, listServingEndpointsUncached, modelForClass, modelsForClass, parseModelClass, rankModels, resolveModel, resolveModelId, searchModels, searchServingEndpoints, selectModel };
|