@dbx-tools/model 0.9.40 → 0.9.46
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 +19 -2
- package/index.ts +2 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +3 -2
- package/lib/package.json +137 -0
- package/lib/src/_metadata-generator.js +11 -8
- package/lib/src/_ranking.js +31 -6
- package/lib/src/_retirement.d.ts +10 -0
- package/lib/src/_retirement.js +59 -0
- package/lib/src/bindings.d.ts +10 -0
- package/lib/src/bindings.js +11 -0
- package/lib/src/classify.d.ts +5 -0
- package/lib/src/classify.js +13 -5
- package/lib/src/metadata-cache.js +7 -10
- package/lib/src/metadata.d.ts +2 -7
- package/lib/src/metadata.js +16 -53
- package/lib/src/model-client.d.ts +2 -0
- package/lib/src/model-client.js +13 -7
- package/lib/src/policy.d.ts +14 -0
- package/lib/src/policy.js +47 -2
- package/lib/src/reasoning-translation.js +8 -3
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -6
- package/src/_metadata-generator.ts +11 -11
- package/src/_ranking.ts +29 -5
- package/src/_retirement.ts +70 -0
- package/src/bindings.ts +11 -0
- package/src/classify.ts +13 -4
- package/src/metadata-cache.ts +6 -11
- package/src/metadata.ts +43 -77
- package/src/model-client.ts +13 -8
- package/src/policy.ts +45 -1
- package/src/reasoning-translation.ts +7 -4
package/package.json
CHANGED
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@databricks/appkit": "0.81.0",
|
|
30
|
-
"@dbx-tools/appkit": "^0.9.
|
|
31
|
-
"@dbx-tools/auth": "^0.9.
|
|
32
|
-
"@dbx-tools/shared-auth": "^0.9.
|
|
33
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
34
|
-
"@dbx-tools/shared-model": "^0.9.
|
|
30
|
+
"@dbx-tools/appkit": "^0.9.46",
|
|
31
|
+
"@dbx-tools/auth": "^0.9.46",
|
|
32
|
+
"@dbx-tools/shared-auth": "^0.9.46",
|
|
33
|
+
"@dbx-tools/shared-core": "^0.9.46",
|
|
34
|
+
"@dbx-tools/shared-model": "^0.9.46",
|
|
35
35
|
"cacache": "^21.0.1",
|
|
36
36
|
"env-paths": "^4.0.0",
|
|
37
37
|
"fuse.js": "^7.4.2"
|
|
@@ -47,6 +47,10 @@
|
|
|
47
47
|
"types": "./lib/index.d.ts",
|
|
48
48
|
"default": "./lib/index.js"
|
|
49
49
|
},
|
|
50
|
+
"./bindings": {
|
|
51
|
+
"types": "./lib/src/bindings.d.ts",
|
|
52
|
+
"default": "./lib/src/bindings.js"
|
|
53
|
+
},
|
|
50
54
|
"./classes": {
|
|
51
55
|
"types": "./lib/src/classes.d.ts",
|
|
52
56
|
"default": "./lib/src/classes.js"
|
|
@@ -98,7 +102,7 @@
|
|
|
98
102
|
"./package.json": "./package.json"
|
|
99
103
|
}
|
|
100
104
|
},
|
|
101
|
-
"version": "0.9.
|
|
105
|
+
"version": "0.9.46",
|
|
102
106
|
"types": "./lib/index.d.ts",
|
|
103
107
|
"dbxToolsConfig": {
|
|
104
108
|
"tags": [
|
|
@@ -111,6 +115,10 @@
|
|
|
111
115
|
"types": "./lib/index.d.ts",
|
|
112
116
|
"default": "./lib/index.js"
|
|
113
117
|
},
|
|
118
|
+
"./bindings": {
|
|
119
|
+
"types": "./lib/src/bindings.d.ts",
|
|
120
|
+
"default": "./lib/src/bindings.js"
|
|
121
|
+
},
|
|
114
122
|
"./classes": {
|
|
115
123
|
"types": "./lib/src/classes.d.ts",
|
|
116
124
|
"default": "./lib/src/classes.js"
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
ReasoningModelsSnapshot,
|
|
8
8
|
RetiredModelsSnapshot,
|
|
9
9
|
} from "./_metadata-contract.ts";
|
|
10
|
-
import { modelSearchQuery } from "./policy.ts";
|
|
10
|
+
import { inheritsNativeWebSearch, modelSearchQuery } from "./policy.ts";
|
|
11
11
|
import {
|
|
12
12
|
parseReasoning,
|
|
13
13
|
REASONING_LEVELS,
|
|
@@ -132,10 +132,7 @@ export function parseModelRateLimits(html: string, generatedAt: number): ModelRa
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/** Parse reasoning-effort ladders from the Query reasoning models HTML table. */
|
|
135
|
-
export function parseReasoningModels(
|
|
136
|
-
html: string,
|
|
137
|
-
generatedAt: number,
|
|
138
|
-
): ReasoningModelsSnapshot {
|
|
135
|
+
export function parseReasoningModels(html: string, generatedAt: number): ReasoningModelsSnapshot {
|
|
139
136
|
const $ = load(html);
|
|
140
137
|
const models: Record<string, ReasoningLevel[]> = {};
|
|
141
138
|
$("table").each((_, table) => {
|
|
@@ -147,7 +144,10 @@ export function parseReasoningModels(
|
|
|
147
144
|
if (!headers.some((header) => header.includes("model"))) return;
|
|
148
145
|
if (!headers.some((header) => header.includes("parameter"))) return;
|
|
149
146
|
rows.slice(1).each((__, row) => {
|
|
150
|
-
const cells = $(row)
|
|
147
|
+
const cells = $(row)
|
|
148
|
+
.find("th, td")
|
|
149
|
+
.toArray()
|
|
150
|
+
.map((cell) => $(cell));
|
|
151
151
|
if (cells.length < 2) return;
|
|
152
152
|
const modelNames = reasoningModelNamesFromCell(cells[0]!, $);
|
|
153
153
|
if (modelNames.length === 0) return;
|
|
@@ -268,7 +268,6 @@ function modelsAfterHeading($: CheerioAPI, headingId: string): string[] {
|
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
function nativeWebSearchModels($: CheerioAPI, responses: readonly string[]): string[] {
|
|
271
|
-
const families = new Set<string>();
|
|
272
271
|
const models = new Set<string>();
|
|
273
272
|
let nativeSection = false;
|
|
274
273
|
for (const element of sectionElements($, "supported-models")) {
|
|
@@ -285,11 +284,10 @@ function nativeWebSearchModels($: CheerioAPI, responses: readonly string[]): str
|
|
|
285
284
|
const model = modelKey(value);
|
|
286
285
|
if (!model) continue;
|
|
287
286
|
models.add(model);
|
|
288
|
-
families.add(model.split("-", 1)[0]);
|
|
289
287
|
}
|
|
290
288
|
}
|
|
291
289
|
for (const model of responses) {
|
|
292
|
-
if (
|
|
290
|
+
if (inheritsNativeWebSearch(model, models)) models.add(model);
|
|
293
291
|
}
|
|
294
292
|
return [...models].sort();
|
|
295
293
|
}
|
|
@@ -307,11 +305,13 @@ function codeValuesAfterHeading($: CheerioAPI, headingId: string): string[] {
|
|
|
307
305
|
|
|
308
306
|
function sectionElements($: CheerioAPI, headingId: string): Selection[] {
|
|
309
307
|
const heading = $(`#${headingId}`).first();
|
|
310
|
-
if (heading.length === 0)
|
|
308
|
+
if (heading.length === 0) {
|
|
311
309
|
throw new Error(`Databricks capability page is missing section ${headingId}`);
|
|
310
|
+
}
|
|
312
311
|
const level = headingLevel(nodeTagName(heading.get(0)));
|
|
313
|
-
if (level === undefined)
|
|
312
|
+
if (level === undefined) {
|
|
314
313
|
throw new Error(`Databricks capability section ${headingId} is not a heading`);
|
|
314
|
+
}
|
|
315
315
|
const elements: Selection[] = [];
|
|
316
316
|
let sibling = heading.next();
|
|
317
317
|
while (sibling.length > 0) {
|
package/src/_ranking.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { toModelDisplayName } from "@dbx-tools/shared-model/display";
|
|
9
9
|
import Fuse from "fuse.js";
|
|
10
10
|
|
|
11
|
+
import { modelStatusFor } from "./_retirement.ts";
|
|
11
12
|
import { classesAtOrBelow, CHAT_CLASS_ORDER, MODEL_CLASS_ORDER } from "./classes.ts";
|
|
12
13
|
import {
|
|
13
14
|
classifyEndpoints,
|
|
@@ -15,7 +16,6 @@ import {
|
|
|
15
16
|
supportsToolsByFamily,
|
|
16
17
|
versionTuple,
|
|
17
18
|
} from "./classify.ts";
|
|
18
|
-
import { modelStatusFor } from "./metadata.ts";
|
|
19
19
|
import { modelFamily, modelReasoningEfforts, modelServiceNames } from "./policy.ts";
|
|
20
20
|
|
|
21
21
|
type ModelClass = ModelClassType;
|
|
@@ -34,8 +34,9 @@ export function rankEndpoints(
|
|
|
34
34
|
query: ModelQuery = {},
|
|
35
35
|
options: NativeRankingOptions = {},
|
|
36
36
|
): RankedModel[] {
|
|
37
|
-
const
|
|
38
|
-
const
|
|
37
|
+
const filtered = endpoints.filter((endpoint) => matchesModelQuery(endpoint, query));
|
|
38
|
+
const originalByName = new Map(filtered.map((endpoint) => [endpoint.name, endpoint]));
|
|
39
|
+
const normalized = filtered.map((endpoint) => ({
|
|
39
40
|
...endpoint,
|
|
40
41
|
...(options.modelClass ? { class: options.modelClass } : {}),
|
|
41
42
|
...(options.task ? { task: options.task } : {}),
|
|
@@ -50,13 +51,14 @@ export function rankEndpoints(
|
|
|
50
51
|
}
|
|
51
52
|
: classifyEndpoints(normalized);
|
|
52
53
|
const requestedClass = options.modelClass ?? query.modelClass;
|
|
54
|
+
const includeDeprecated = query.includeDeprecated ?? options.includeDeprecated ?? false;
|
|
53
55
|
const eligible = requestedClass ? classesAtOrBelow(requestedClass) : CHAT_CLASS_ORDER;
|
|
54
56
|
const candidates: RankedModel[] = [];
|
|
55
57
|
const search = query.search?.trim();
|
|
56
58
|
if (search && requestedClass === undefined) {
|
|
57
59
|
const classByName = classifyEndpointClasses(normalized);
|
|
58
60
|
for (const endpoint of normalized) {
|
|
59
|
-
if (!
|
|
61
|
+
if (!includeDeprecated && endpoint.status?.deprecated) continue;
|
|
60
62
|
const capabilities = endpointCapabilities(endpoint);
|
|
61
63
|
if (!capabilities.chat || (query.requiresTools && !capabilities.tools)) continue;
|
|
62
64
|
candidates.push({
|
|
@@ -67,7 +69,7 @@ export function rankEndpoints(
|
|
|
67
69
|
} else {
|
|
68
70
|
for (const modelClass of eligible) {
|
|
69
71
|
for (const endpoint of classified[modelClass]) {
|
|
70
|
-
if (!
|
|
72
|
+
if (!includeDeprecated && endpoint.status?.deprecated) continue;
|
|
71
73
|
if (query.requiresTools && !endpointCapabilities(endpoint).tools) continue;
|
|
72
74
|
candidates.push({ endpoint, modelClass });
|
|
73
75
|
}
|
|
@@ -125,6 +127,28 @@ export function rankEndpoints(
|
|
|
125
127
|
return query.limit === undefined ? restored : restored.slice(0, Math.max(0, query.limit));
|
|
126
128
|
}
|
|
127
129
|
|
|
130
|
+
function matchesModelQuery(endpoint: ServingEndpointSummary, query: ModelQuery): boolean {
|
|
131
|
+
if (query.name !== undefined && endpoint.name !== query.name) return false;
|
|
132
|
+
if (query.task !== undefined && endpoint.task !== query.task) return false;
|
|
133
|
+
if (
|
|
134
|
+
query.reasoningEffort !== undefined &&
|
|
135
|
+
!endpoint.reasoningEfforts?.includes(query.reasoningEffort)
|
|
136
|
+
) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if (
|
|
140
|
+
query.dimension !== undefined ||
|
|
141
|
+
query.minDimension !== undefined ||
|
|
142
|
+
query.maxDimension !== undefined
|
|
143
|
+
) {
|
|
144
|
+
if (endpoint.dimension === undefined) return false;
|
|
145
|
+
if (query.dimension !== undefined && endpoint.dimension !== query.dimension) return false;
|
|
146
|
+
if (query.minDimension !== undefined && endpoint.dimension < query.minDimension) return false;
|
|
147
|
+
if (query.maxDimension !== undefined && endpoint.dimension > query.maxDimension) return false;
|
|
148
|
+
}
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
128
152
|
/** Classify chat and embedding endpoints through the TypeScript-owned policy. */
|
|
129
153
|
export function classifyEndpointClasses(
|
|
130
154
|
endpoints: readonly ServingEndpointSummary[],
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** Lightweight model retirement state shared by discovery and metadata caches. */
|
|
2
|
+
import type { ModelStatus, ServingEndpointSummary } from "@dbx-tools/shared-model/contracts";
|
|
3
|
+
|
|
4
|
+
import type { RetiredModelsSnapshot } from "./_metadata-contract.ts";
|
|
5
|
+
import retiredModelsSnapshotJson from "./generated/retired-models.json" with { type: "json" };
|
|
6
|
+
|
|
7
|
+
const snapshot = retiredModelsSnapshotJson as RetiredModelsSnapshot;
|
|
8
|
+
const RETIRED_MODEL_PREFIXES = new Set([
|
|
9
|
+
"ai",
|
|
10
|
+
"anthropic",
|
|
11
|
+
"databricks",
|
|
12
|
+
"dbx",
|
|
13
|
+
"google",
|
|
14
|
+
"meta",
|
|
15
|
+
"openai",
|
|
16
|
+
"system",
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
let names: readonly string[] = Object.freeze([...snapshot.models]);
|
|
20
|
+
let keys = retiredKeys(names);
|
|
21
|
+
|
|
22
|
+
export const COMMITTED_RETIRED_MODELS = snapshot.models;
|
|
23
|
+
export const RETIRED_MODELS_GENERATED_AT = snapshot.generatedAt;
|
|
24
|
+
|
|
25
|
+
/** Replace retirement state after metadata cache hydration or refresh. */
|
|
26
|
+
export function replaceRetiredModelNames(models: readonly string[]): void {
|
|
27
|
+
names = Object.freeze([...models]);
|
|
28
|
+
keys = retiredKeys(names);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Return the active retired-model names. */
|
|
32
|
+
export function retiredModelNames(): readonly string[] {
|
|
33
|
+
return names;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Resolve whether any supplied identity is listed as retired. */
|
|
37
|
+
export function modelStatusFor(
|
|
38
|
+
...models: readonly (string | ServingEndpointSummary)[]
|
|
39
|
+
): ModelStatus {
|
|
40
|
+
const deprecated = models
|
|
41
|
+
.flatMap((model) => modelIdentities(model))
|
|
42
|
+
.some((identity) => {
|
|
43
|
+
const candidate = retiredModelKey(identity);
|
|
44
|
+
for (const key of keys) {
|
|
45
|
+
if (candidate === key || candidate.startsWith(`${key}-`)) return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
});
|
|
49
|
+
return { deprecated };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function retiredKeys(models: readonly string[]): ReadonlySet<string> {
|
|
53
|
+
return new Set(models.map(retiredModelKey).filter(Boolean));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function modelIdentities(model: string | ServingEndpointSummary): string[] {
|
|
57
|
+
if (typeof model === "string") return [model];
|
|
58
|
+
return [
|
|
59
|
+
model.name,
|
|
60
|
+
model.displayName,
|
|
61
|
+
model.modelServiceName,
|
|
62
|
+
...Object.values(model.serviceNames ?? {}),
|
|
63
|
+
].filter((value): value is string => Boolean(value));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function retiredModelKey(value: string): string {
|
|
67
|
+
const tokens = value.toLowerCase().match(/[a-z0-9]+/g) ?? [];
|
|
68
|
+
while (tokens[0] && RETIRED_MODEL_PREFIXES.has(tokens[0])) tokens.shift();
|
|
69
|
+
return tokens.join("-");
|
|
70
|
+
}
|
package/src/bindings.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binding-safe facade for Databricks model discovery and routing.
|
|
3
|
+
*
|
|
4
|
+
* Generated language bindings create one canonical model client so
|
|
5
|
+
* authentication, catalogue caching, ranking, and route selection remain
|
|
6
|
+
* attached to that client.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { createModelClient } from "./model-client.ts";
|
package/src/classify.ts
CHANGED
|
@@ -111,15 +111,24 @@ export function versionTuple(name: string): [number, number, number] {
|
|
|
111
111
|
return [nums[0] ?? 0, nums[1] ?? 0, nums[2] ?? 0];
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
/**
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Compare version tuples component-wise with missing parts as 0.
|
|
116
|
+
* Positive when `left` is newer.
|
|
117
|
+
*/
|
|
118
|
+
export function compareVersionTuples(left: readonly number[], right: readonly number[]): number {
|
|
119
|
+
const length = Math.max(left.length, right.length, 3);
|
|
120
|
+
for (let index = 0; index < length; index += 1) {
|
|
121
|
+
const diff = (left[index] ?? 0) - (right[index] ?? 0);
|
|
118
122
|
if (diff !== 0) return diff;
|
|
119
123
|
}
|
|
120
124
|
return 0;
|
|
121
125
|
}
|
|
122
126
|
|
|
127
|
+
/** Compare two version tuples so the higher version sorts first (descending). */
|
|
128
|
+
function compareVersionDesc(a: readonly number[], b: readonly number[]): number {
|
|
129
|
+
return compareVersionTuples(b, a);
|
|
130
|
+
}
|
|
131
|
+
|
|
123
132
|
/**
|
|
124
133
|
* Monotonic version key for ordering siblings within a family/tier
|
|
125
134
|
* (e.g. `opus-4-8` over `opus-4-7`). Encodes the {@link versionTuple}
|
package/src/metadata-cache.ts
CHANGED
|
@@ -21,6 +21,7 @@ import * as log from "@dbx-tools/shared-core/log";
|
|
|
21
21
|
import cacache from "cacache";
|
|
22
22
|
import envPaths from "env-paths";
|
|
23
23
|
|
|
24
|
+
import packageManifest from "../package.json" with { type: "json" };
|
|
24
25
|
import { MODEL_METADATA_TTL_MS } from "./_metadata-contract.ts";
|
|
25
26
|
|
|
26
27
|
const logger = log.logger("model/metadata-cache");
|
|
@@ -30,15 +31,10 @@ interface PackageIdentity {
|
|
|
30
31
|
readonly version: string;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
identifier: mod.PACKAGE_IDENTIFIER,
|
|
38
|
-
version: mod.PACKAGE_VERSION,
|
|
39
|
-
}));
|
|
40
|
-
return packageIdentityPromise;
|
|
41
|
-
}
|
|
34
|
+
const PACKAGE_IDENTITY: PackageIdentity = {
|
|
35
|
+
identifier: packageManifest.name,
|
|
36
|
+
version: packageManifest.version,
|
|
37
|
+
};
|
|
42
38
|
|
|
43
39
|
/** Envelope stored as JSON bytes in cacache for every key. */
|
|
44
40
|
export interface MetadataCacheRecord<T> {
|
|
@@ -247,7 +243,7 @@ async function resolveCacheDir(options: {
|
|
|
247
243
|
readonly version?: string;
|
|
248
244
|
}): Promise<string> {
|
|
249
245
|
if (options.cacheDir) return options.cacheDir;
|
|
250
|
-
const version = options.version ??
|
|
246
|
+
const version = options.version ?? PACKAGE_IDENTITY.version;
|
|
251
247
|
return defaultMetadataCacheDir(version);
|
|
252
248
|
}
|
|
253
249
|
|
|
@@ -319,4 +315,3 @@ function isCacheMiss(error: unknown): boolean {
|
|
|
319
315
|
(error as { code?: string }).code === "ENOENT",
|
|
320
316
|
);
|
|
321
317
|
}
|
|
322
|
-
|
package/src/metadata.ts
CHANGED
|
@@ -11,12 +11,10 @@
|
|
|
11
11
|
* @module
|
|
12
12
|
*/
|
|
13
13
|
import * as functionUtils from "@dbx-tools/shared-core/function-utils";
|
|
14
|
-
import * as log from "@dbx-tools/shared-core/log";
|
|
15
14
|
import type {
|
|
16
15
|
ModelCapabilities,
|
|
17
16
|
ModelMetadata,
|
|
18
17
|
ModelRateLimits,
|
|
19
|
-
ModelStatus,
|
|
20
18
|
ServingEndpointSummary,
|
|
21
19
|
} from "@dbx-tools/shared-model/contracts";
|
|
22
20
|
|
|
@@ -31,7 +29,6 @@ import {
|
|
|
31
29
|
type ModelCapabilityCatalogue,
|
|
32
30
|
type ModelRateLimitsSnapshot,
|
|
33
31
|
type ReasoningModelsSnapshot,
|
|
34
|
-
type RetiredModelsSnapshot,
|
|
35
32
|
} from "./_metadata-contract.ts";
|
|
36
33
|
import {
|
|
37
34
|
parseModelCapabilities,
|
|
@@ -39,17 +36,22 @@ import {
|
|
|
39
36
|
parseReasoningModels,
|
|
40
37
|
parseRetiredModels,
|
|
41
38
|
} from "./_metadata-generator.ts";
|
|
39
|
+
import {
|
|
40
|
+
COMMITTED_RETIRED_MODELS,
|
|
41
|
+
modelStatusFor,
|
|
42
|
+
replaceRetiredModelNames,
|
|
43
|
+
RETIRED_MODELS_GENERATED_AT,
|
|
44
|
+
} from "./_retirement.ts";
|
|
45
|
+
import capabilitiesSnapshotJson from "./generated/model-capabilities.json" with { type: "json" };
|
|
46
|
+
import rateLimitsSnapshotJson from "./generated/model-rate-limits.json" with { type: "json" };
|
|
47
|
+
import reasoningSnapshotJson from "./generated/model-reasoning.json" with { type: "json" };
|
|
42
48
|
import {
|
|
43
49
|
createMetadataCache,
|
|
44
50
|
mergePreferFreshList,
|
|
45
51
|
mergePreferFreshRecord,
|
|
46
52
|
type MetadataCache,
|
|
47
53
|
} from "./metadata-cache.ts";
|
|
48
|
-
import
|
|
49
|
-
import rateLimitsSnapshotJson from "./generated/model-rate-limits.json" with { type: "json" };
|
|
50
|
-
import reasoningSnapshotJson from "./generated/model-reasoning.json" with { type: "json" };
|
|
51
|
-
import retiredModelsSnapshotJson from "./generated/retired-models.json" with { type: "json" };
|
|
52
|
-
import { modelSearchQuery } from "./policy.ts";
|
|
54
|
+
import { inheritsNativeWebSearch, isFoundationModelIdentity, modelSearchQuery } from "./policy.ts";
|
|
53
55
|
import {
|
|
54
56
|
defaultReasoningLevels,
|
|
55
57
|
documentedReasoningLevels,
|
|
@@ -69,16 +71,11 @@ export {
|
|
|
69
71
|
WEB_SEARCH_MODELS_URL,
|
|
70
72
|
};
|
|
71
73
|
|
|
72
|
-
const logger = log.logger("model/metadata");
|
|
73
|
-
|
|
74
|
-
const retiredModelsSnapshot = retiredModelsSnapshotJson as RetiredModelsSnapshot;
|
|
75
74
|
const capabilitiesSnapshot = capabilitiesSnapshotJson as ModelCapabilitiesSnapshot;
|
|
76
75
|
const rateLimitsSnapshot = rateLimitsSnapshotJson as ModelRateLimitsSnapshot;
|
|
77
76
|
const reasoningSnapshot = reasoningSnapshotJson as ReasoningModelsSnapshot;
|
|
78
77
|
|
|
79
78
|
interface MetadataIndex {
|
|
80
|
-
readonly retiredNames: readonly string[];
|
|
81
|
-
readonly retiredKeys: ReadonlySet<string>;
|
|
82
79
|
readonly capabilities: {
|
|
83
80
|
readonly [Key in keyof ModelCapabilityCatalogue]: ReadonlySet<string>;
|
|
84
81
|
};
|
|
@@ -93,7 +90,7 @@ const USER_AGENT = "dbx-tools-model-metadata/1";
|
|
|
93
90
|
|
|
94
91
|
const retiredCache = createMetadataCache<readonly string[]>({
|
|
95
92
|
key: "retired-models",
|
|
96
|
-
fallback:
|
|
93
|
+
fallback: COMMITTED_RETIRED_MODELS,
|
|
97
94
|
load: async () => parseRetiredModels(await loadPage(RETIRED_MODELS_URL), nowSeconds()).models,
|
|
98
95
|
merge: mergePreferFreshList,
|
|
99
96
|
});
|
|
@@ -148,7 +145,9 @@ const reasoningDocsCache = createMetadataCache<Readonly<Record<string, readonly
|
|
|
148
145
|
merge: mergePreferFreshRecord,
|
|
149
146
|
});
|
|
150
147
|
|
|
151
|
-
const learnedReasoningCache = createMetadataCache<
|
|
148
|
+
const learnedReasoningCache = createMetadataCache<
|
|
149
|
+
Readonly<Record<string, readonly ReasoningLevel[]>>
|
|
150
|
+
>({
|
|
152
151
|
key: "learned-reasoning",
|
|
153
152
|
fallback: {},
|
|
154
153
|
merge: mergePreferFreshRecord,
|
|
@@ -156,11 +155,13 @@ const learnedReasoningCache = createMetadataCache<Readonly<Record<string, readon
|
|
|
156
155
|
|
|
157
156
|
let liveIndex: MetadataIndex | undefined;
|
|
158
157
|
|
|
159
|
-
const metadataIndex = functionUtils.memoize<MetadataIndex>(
|
|
158
|
+
const metadataIndex = functionUtils.memoize<MetadataIndex>(
|
|
159
|
+
() => liveIndex ?? buildIndexFromCommitted(),
|
|
160
|
+
);
|
|
160
161
|
|
|
161
162
|
function buildIndexFromCommitted(): MetadataIndex {
|
|
162
163
|
return buildIndex({
|
|
163
|
-
retiredNames:
|
|
164
|
+
retiredNames: COMMITTED_RETIRED_MODELS,
|
|
164
165
|
capabilities: capabilitiesSnapshot.capabilities,
|
|
165
166
|
rateLimits: rateLimitsSnapshot.catalogue.models,
|
|
166
167
|
reasoning: reasoningSnapshot.catalogue.models,
|
|
@@ -175,9 +176,8 @@ function buildIndex(input: {
|
|
|
175
176
|
readonly reasoning: Readonly<Record<string, readonly string[]>>;
|
|
176
177
|
readonly learnedReasoning: Readonly<Record<string, readonly ReasoningLevel[]>>;
|
|
177
178
|
}): MetadataIndex {
|
|
179
|
+
replaceRetiredModelNames(input.retiredNames);
|
|
178
180
|
return {
|
|
179
|
-
retiredNames: Object.freeze([...input.retiredNames]),
|
|
180
|
-
retiredKeys: new Set(input.retiredNames.map(retiredModelKey).filter(Boolean)),
|
|
181
181
|
capabilities: {
|
|
182
182
|
responses: new Set(input.capabilities.responses),
|
|
183
183
|
imageInput: new Set(input.capabilities.imageInput),
|
|
@@ -228,13 +228,15 @@ let refreshInflight: Promise<MetadataIndex> | undefined;
|
|
|
228
228
|
export async function refreshModelMetadata(): Promise<MetadataIndex> {
|
|
229
229
|
if (refreshInflight) return refreshInflight;
|
|
230
230
|
refreshInflight = (async () => {
|
|
231
|
-
const [retiredNames, capabilities, rateLimits, reasoning, learnedReasoning] = await Promise.all(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
231
|
+
const [retiredNames, capabilities, rateLimits, reasoning, learnedReasoning] = await Promise.all(
|
|
232
|
+
[
|
|
233
|
+
retiredCache.get(),
|
|
234
|
+
capabilitiesCache.get(),
|
|
235
|
+
rateLimitsCache.get(),
|
|
236
|
+
reasoningDocsCache.get(),
|
|
237
|
+
learnedReasoningCache.get(),
|
|
238
|
+
],
|
|
239
|
+
);
|
|
238
240
|
liveIndex = buildIndex({
|
|
239
241
|
retiredNames,
|
|
240
242
|
capabilities,
|
|
@@ -249,14 +251,10 @@ export async function refreshModelMetadata(): Promise<MetadataIndex> {
|
|
|
249
251
|
return refreshInflight;
|
|
250
252
|
}
|
|
251
253
|
|
|
252
|
-
void hydrateModelMetadata().catch((error) => {
|
|
253
|
-
logger.warn("metadata cache hydrate failed; using committed snapshots", { error });
|
|
254
|
-
});
|
|
255
|
-
|
|
256
254
|
/** Load cacache / memory entries without forcing a network refresh. */
|
|
257
255
|
export async function hydrateModelMetadata(): Promise<MetadataIndex> {
|
|
258
256
|
const [retiredNames, capabilities, rateLimits, reasoning, learnedReasoning] = await Promise.all([
|
|
259
|
-
peekOrFallback(retiredCache,
|
|
257
|
+
peekOrFallback(retiredCache, COMMITTED_RETIRED_MODELS),
|
|
260
258
|
peekOrFallback(capabilitiesCache, capabilitiesSnapshot.capabilities),
|
|
261
259
|
peekOrFallback(rateLimitsCache, rateLimitsSnapshot.catalogue.models),
|
|
262
260
|
peekOrFallback(reasoningDocsCache, reasoningSnapshot.catalogue.models),
|
|
@@ -283,10 +281,7 @@ export function modelMetadataCachePath(): string {
|
|
|
283
281
|
return retiredCache.path;
|
|
284
282
|
}
|
|
285
283
|
|
|
286
|
-
|
|
287
|
-
export function retiredModelNames(): readonly string[] {
|
|
288
|
-
return metadataIndex().retiredNames;
|
|
289
|
-
}
|
|
284
|
+
export { modelStatusFor, retiredModelNames } from "./_retirement.ts";
|
|
290
285
|
|
|
291
286
|
/** Return the committed reasoning-effort catalogue. Cached per process. */
|
|
292
287
|
export function reasoningModelCatalogue(): ReasoningModelCatalogue {
|
|
@@ -338,32 +333,23 @@ export async function learnReasoningLevelsFromError(
|
|
|
338
333
|
return rememberReasoningLevels(model, parseReasoningLevels(body));
|
|
339
334
|
}
|
|
340
335
|
|
|
341
|
-
/** Resolve whether any supplied identity is listed in the retirement snapshot. */
|
|
342
|
-
export function modelStatusFor(
|
|
343
|
-
...models: readonly (string | ServingEndpointSummary)[]
|
|
344
|
-
): ModelStatus {
|
|
345
|
-
const retired = metadataIndex().retiredKeys;
|
|
346
|
-
const deprecated = models
|
|
347
|
-
.flatMap((model) => modelIdentities(model))
|
|
348
|
-
.some((identity) => {
|
|
349
|
-
const candidate = retiredModelKey(identity);
|
|
350
|
-
for (const key of retired) {
|
|
351
|
-
if (candidate === key || candidate.startsWith(`${key}-`)) return true;
|
|
352
|
-
}
|
|
353
|
-
return false;
|
|
354
|
-
});
|
|
355
|
-
return { deprecated };
|
|
356
|
-
}
|
|
357
|
-
|
|
358
336
|
/** Resolve documented model capabilities from a name or endpoint summary. */
|
|
359
337
|
export function modelCapabilitiesFor(model: string | ServingEndpointSummary): ModelCapabilities {
|
|
360
338
|
const capabilities = metadataIndex().capabilities;
|
|
361
|
-
const
|
|
339
|
+
const identities = modelIdentities(model, false);
|
|
340
|
+
const keys = identities.flatMap((identity) => modelKey(identity) ?? []);
|
|
362
341
|
return {
|
|
363
342
|
responses: keys.some((key) => capabilities.responses.has(key)),
|
|
364
343
|
imageInput: keys.some((key) => capabilities.imageInput.has(key)),
|
|
365
344
|
applyPatch: keys.some((key) => capabilities.applyPatch.has(key)),
|
|
366
|
-
|
|
345
|
+
// Version inheritance only applies to hosted foundation identities.
|
|
346
|
+
webSearch:
|
|
347
|
+
keys.some((key) => capabilities.webSearch.has(key)) ||
|
|
348
|
+
identities.some(
|
|
349
|
+
(identity) =>
|
|
350
|
+
isFoundationModelIdentity(identity) &&
|
|
351
|
+
inheritsNativeWebSearch(identity, capabilities.webSearch),
|
|
352
|
+
),
|
|
367
353
|
};
|
|
368
354
|
}
|
|
369
355
|
|
|
@@ -388,9 +374,7 @@ export function modelRateLimitsFor(
|
|
|
388
374
|
* Does not parse error bodies; callers should use {@link parseReasoningLevels}
|
|
389
375
|
* and then {@link rememberReasoningLevels}.
|
|
390
376
|
*/
|
|
391
|
-
export function modelReasoningLevelsFor(
|
|
392
|
-
model: string | ServingEndpointSummary,
|
|
393
|
-
): ReasoningLevel[] {
|
|
377
|
+
export function modelReasoningLevelsFor(model: string | ServingEndpointSummary): ReasoningLevel[] {
|
|
394
378
|
const index = metadataIndex();
|
|
395
379
|
for (const identity of modelIdentities(model)) {
|
|
396
380
|
const learned = documentedReasoningLevels(identity, index.learnedReasoning);
|
|
@@ -400,8 +384,7 @@ export function modelReasoningLevelsFor(
|
|
|
400
384
|
const documented = documentedReasoningLevels(identity, index.reasoning);
|
|
401
385
|
if (documented?.length) return documented;
|
|
402
386
|
}
|
|
403
|
-
const primary =
|
|
404
|
-
typeof model === "string" ? model : (model.modelServiceName ?? model.name);
|
|
387
|
+
const primary = typeof model === "string" ? model : (model.modelServiceName ?? model.name);
|
|
405
388
|
return defaultReasoningLevels(primary);
|
|
406
389
|
}
|
|
407
390
|
|
|
@@ -423,7 +406,7 @@ export function modelMetadataGeneratedAt(): Readonly<{
|
|
|
423
406
|
reasoning: number;
|
|
424
407
|
}> {
|
|
425
408
|
return {
|
|
426
|
-
retiredModels:
|
|
409
|
+
retiredModels: RETIRED_MODELS_GENERATED_AT,
|
|
427
410
|
capabilities: capabilitiesSnapshot.generatedAt,
|
|
428
411
|
rateLimits: rateLimitsSnapshot.generatedAt,
|
|
429
412
|
reasoning: reasoningSnapshot.generatedAt,
|
|
@@ -447,23 +430,6 @@ function modelKey(value: string): string | undefined {
|
|
|
447
430
|
return modelSearchQuery(value)?.replaceAll(" ", "-");
|
|
448
431
|
}
|
|
449
432
|
|
|
450
|
-
const RETIRED_MODEL_PREFIXES = new Set([
|
|
451
|
-
"ai",
|
|
452
|
-
"anthropic",
|
|
453
|
-
"databricks",
|
|
454
|
-
"dbx",
|
|
455
|
-
"google",
|
|
456
|
-
"meta",
|
|
457
|
-
"openai",
|
|
458
|
-
"system",
|
|
459
|
-
]);
|
|
460
|
-
|
|
461
|
-
function retiredModelKey(value: string): string {
|
|
462
|
-
const tokens = value.toLowerCase().match(/[a-z0-9]+/g) ?? [];
|
|
463
|
-
while (tokens[0] && RETIRED_MODEL_PREFIXES.has(tokens[0])) tokens.shift();
|
|
464
|
-
return tokens.join("-");
|
|
465
|
-
}
|
|
466
|
-
|
|
467
433
|
async function loadPage(url: string): Promise<string> {
|
|
468
434
|
const response = await fetch(url, {
|
|
469
435
|
headers: { "user-agent": USER_AGENT },
|
package/src/model-client.ts
CHANGED
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This is the portable entry point used by generated Python bindings. It keeps
|
|
5
5
|
* authentication, endpoint discovery, caching, normalization, ranking, route
|
|
6
|
-
* selection, and published metadata in Node while exposing
|
|
6
|
+
* selection, route construction, and published metadata in Node while exposing
|
|
7
|
+
* one small client.
|
|
7
8
|
*
|
|
8
9
|
* @module
|
|
9
10
|
*/
|
|
10
11
|
import {
|
|
11
|
-
|
|
12
|
-
profile as authProfile,
|
|
12
|
+
createAuthClient,
|
|
13
13
|
type AuthClient,
|
|
14
14
|
type DatabricksAuthDependencies,
|
|
15
|
-
} from "@dbx-tools/auth";
|
|
15
|
+
} from "@dbx-tools/auth/client";
|
|
16
|
+
import { listProfiles as listAuthProfiles } from "@dbx-tools/auth/profile";
|
|
16
17
|
import type { DatabricksProfileSummary } from "@dbx-tools/shared-auth";
|
|
17
18
|
import * as log from "@dbx-tools/shared-core/log";
|
|
18
19
|
import * as object from "@dbx-tools/shared-core/object";
|
|
@@ -29,7 +30,7 @@ import {
|
|
|
29
30
|
import { normalizeEndpoints } from "./_ranking.ts";
|
|
30
31
|
import { lookupModels, resolveModel } from "./_selection.ts";
|
|
31
32
|
import { chatCompletionsUrl, invocationsUrl, responsesUpstreamUrl } from "./invoke.ts";
|
|
32
|
-
import { modelMetadataFor } from "./metadata.ts";
|
|
33
|
+
import { hydrateModelMetadata, modelMetadataFor } from "./metadata.ts";
|
|
33
34
|
import { modelServingApi } from "./policy.ts";
|
|
34
35
|
|
|
35
36
|
const logger = log.logger("model/client");
|
|
@@ -49,6 +50,8 @@ export interface ModelClientOptions {
|
|
|
49
50
|
auth?: ModelAuthOptions;
|
|
50
51
|
/** In-memory endpoint catalogue TTL in milliseconds. */
|
|
51
52
|
cacheTtlMs?: number;
|
|
53
|
+
/** Hydrate Node-only metadata caches before constructing the client. */
|
|
54
|
+
hydrateMetadata?: boolean;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
/** Secret-free runtime identity and cache configuration. */
|
|
@@ -246,15 +249,16 @@ class DefaultModelClient implements ModelClient {
|
|
|
246
249
|
/** Create an authentication-aware Databricks model client. */
|
|
247
250
|
export async function createModelClient(options: ModelClientOptions = {}): Promise<ModelClient> {
|
|
248
251
|
const cacheTtlMs = validateCacheTtl(options.cacheTtlMs);
|
|
252
|
+
if (options.hydrateMetadata ?? true) await hydrateModelMetadata();
|
|
249
253
|
return new DefaultModelClient(
|
|
250
|
-
new AuthenticatedModelClient(await
|
|
254
|
+
new AuthenticatedModelClient(await createAuthClient(options.auth), globalThis.fetch),
|
|
251
255
|
cacheTtlMs,
|
|
252
256
|
);
|
|
253
257
|
}
|
|
254
258
|
|
|
255
259
|
/** List configured Databricks profiles outside the model client facade. */
|
|
256
260
|
export function listProfiles(refresh = false): DatabricksProfileSummary[] {
|
|
257
|
-
return
|
|
261
|
+
return listAuthProfiles({ refresh });
|
|
258
262
|
}
|
|
259
263
|
|
|
260
264
|
/** @internal Construct a model client around a test or host-owned Databricks client. */
|
|
@@ -270,9 +274,10 @@ export async function createModelClientWithDependencies(
|
|
|
270
274
|
options: ModelClientOptions,
|
|
271
275
|
dependencies: DatabricksAuthDependencies,
|
|
272
276
|
): Promise<ModelClient> {
|
|
277
|
+
if (options.hydrateMetadata ?? true) await hydrateModelMetadata();
|
|
273
278
|
return new DefaultModelClient(
|
|
274
279
|
new AuthenticatedModelClient(
|
|
275
|
-
await
|
|
280
|
+
await createAuthClient(options.auth, dependencies),
|
|
276
281
|
dependencies.fetch ?? globalThis.fetch,
|
|
277
282
|
),
|
|
278
283
|
validateCacheTtl(options.cacheTtlMs),
|