@dbx-tools/model 0.9.129 → 0.9.132
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 +56 -35
- package/index.ts +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +2 -2
- package/lib/src/_ranking.d.ts +1 -0
- package/lib/src/_ranking.js +40 -17
- package/lib/src/_selection.d.ts +8 -3
- package/lib/src/_selection.js +8 -8
- package/lib/src/model-client.d.ts +3 -0
- package/lib/src/model-client.js +27 -13
- package/lib/src/policy.d.ts +10 -0
- package/lib/src/policy.js +38 -1
- package/lib/src/resolve.d.ts +3 -1
- package/lib/src/resolve.js +5 -5
- package/package.json +7 -7
- package/src/_ranking.ts +43 -16
- package/src/_selection.ts +22 -9
- package/src/model-client.ts +38 -8
- package/src/policy.ts +43 -0
- package/src/resolve.ts +10 -3
package/lib/src/resolve.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { lookupModels, rankModelId, resolveModel } from "./_selection.js";
|
|
1
|
+
import { lookupModels, rankModelId, resolveModel, } from "./_selection.js";
|
|
2
2
|
import { listServingEndpoints, } from "./model-catalog.js";
|
|
3
3
|
export { lookupModels, rankModelId, resolveModel } from "./_selection.js";
|
|
4
4
|
/** Resolve against a potentially stale catalogue, refreshing once on a miss. */
|
|
5
|
-
export async function rankModelIdLive(load, search, options = {}) {
|
|
6
|
-
const resolved = rankModelId(await load(false), search, options);
|
|
5
|
+
export async function rankModelIdLive(load, search, options = {}, policy = {}) {
|
|
6
|
+
const resolved = rankModelId(await load(false), search, options, policy);
|
|
7
7
|
if (resolved.matched)
|
|
8
8
|
return resolved;
|
|
9
|
-
return rankModelId(await load(true), search, options);
|
|
9
|
+
return rankModelId(await load(true), search, options, policy);
|
|
10
10
|
}
|
|
11
11
|
/** List and rank a workspace model catalogue. */
|
|
12
12
|
export async function searchModels(client, host, input = {}) {
|
|
@@ -27,4 +27,4 @@ function catalogueOptions(input) {
|
|
|
27
27
|
...(input.cacheIdentity !== undefined ? { cacheIdentity: input.cacheIdentity } : {}),
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
30
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9yZXNvbHZlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQWVBLE9BQU8sRUFDTCxZQUFZLEVBQ1osV0FBVyxFQUNYLFlBQVksR0FFYixNQUFNLGlCQUFpQixDQUFDO0FBQ3pCLE9BQU8sRUFDTCxvQkFBb0IsR0FHckIsTUFBTSxvQkFBb0IsQ0FBQztBQUU1QixPQUFPLEVBQUUsWUFBWSxFQUFFLFdBQVcsRUFBRSxZQUFZLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQWUxRSxnRkFBZ0Y7QUFDaEYsTUFBTSxDQUFDLEtBQUssVUFBVSxlQUFlLENBQ25DLElBQW9FLEVBQ3BFLE1BQWMsRUFDZCxVQUErQixFQUFFLEVBQ2pDLFNBQXNDLEVBQUU7SUFFeEMsTUFBTSxRQUFRLEdBQUcsV0FBVyxDQUFDLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDekUsSUFBSSxRQUFRLENBQUMsT0FBTztRQUFFLE9BQU8sUUFBUSxDQUFDO0lBQ3RDLE9BQU8sV0FBVyxDQUFDLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUM7QUFDaEUsQ0FBQztBQUVELGlEQUFpRDtBQUNqRCxNQUFNLENBQUMsS0FBSyxVQUFVLFlBQVksQ0FDaEMsTUFBMkIsRUFDM0IsSUFBWSxFQUNaLFFBQTJCLEVBQUU7SUFFN0IsTUFBTSxTQUFTLEdBQUcsTUFBTSxvQkFBb0IsQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLGdCQUFnQixDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDcEYsT0FBTyxZQUFZLENBQUMsU0FBUyxFQUFFLEtBQUssQ0FBQyxDQUFDO0FBQ3hDLENBQUM7QUFFRCx3REFBd0Q7QUFDeEQsTUFBTSxDQUFDLEtBQUssVUFBVSxXQUFXLENBQy9CLE1BQTJCLEVBQzNCLElBQVksRUFDWixRQUEwQixFQUFFO0lBRTVCLElBQUksS0FBSyxDQUFDLFFBQVEsS0FBSyxTQUFTLElBQUksS0FBSyxDQUFDLEtBQUssS0FBSyxLQUFLLElBQUksQ0FBQyxLQUFLLENBQUMsYUFBYSxFQUFFLENBQUM7UUFDbEYsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLENBQUMsUUFBUSxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsQ0FBQztJQUN6RCxDQUFDO0lBQ0QsTUFBTSxTQUFTLEdBQUcsTUFBTSxvQkFBb0IsQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLGdCQUFnQixDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDcEYsT0FBTyxZQUFZLENBQUMsU0FBUyxFQUFFLEtBQUssQ0FBQyxDQUFDO0FBQ3hDLENBQUM7QUFFRCxTQUFTLGdCQUFnQixDQUFDLEtBR3pCO0lBQ0MsT0FBTztRQUNMLEdBQUcsQ0FBQyxLQUFLLENBQUMsS0FBSyxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsRUFBRSxLQUFLLEVBQUUsS0FBSyxDQUFDLEtBQUssRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDNUQsR0FBRyxDQUFDLEtBQUssQ0FBQyxhQUFhLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLGFBQWEsRUFBRSxLQUFLLENBQUMsYUFBYSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztLQUNyRixDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogV29ya3NwYWNlLWF3YXJlIG1vZGVsIHNlbGVjdGlvbiB3cmFwcGVycy5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cbmltcG9ydCB0eXBlIHtcbiAgTW9kZWxRdWVyeSxcbiAgUmFua2VkTW9kZWwsXG4gIFJlc29sdmVkTW9kZWwsXG4gIFJlc29sdmVkTW9kZWxTZWxlY3Rpb24sXG4gIFJlc29sdmVNb2RlbElucHV0LFxuICBSZXNvbHZlTW9kZWxPcHRpb25zLFxuICBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5LFxufSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtbW9kZWxcIjtcblxuaW1wb3J0IHtcbiAgbG9va3VwTW9kZWxzLFxuICByYW5rTW9kZWxJZCxcbiAgcmVzb2x2ZU1vZGVsLFxuICB0eXBlIE1vZGVsU2VsZWN0aW9uUG9saWN5T3B0aW9ucyxcbn0gZnJvbSBcIi4vX3NlbGVjdGlvbi50c1wiO1xuaW1wb3J0IHtcbiAgbGlzdFNlcnZpbmdFbmRwb2ludHMsXG4gIHR5cGUgTGlzdFNlcnZpbmdFbmRwb2ludHNPcHRpb25zLFxuICB0eXBlIFdvcmtzcGFjZUNsaWVudExpa2UsXG59IGZyb20gXCIuL21vZGVsLWNhdGFsb2cudHNcIjtcblxuZXhwb3J0IHsgbG9va3VwTW9kZWxzLCByYW5rTW9kZWxJZCwgcmVzb2x2ZU1vZGVsIH0gZnJvbSBcIi4vX3NlbGVjdGlvbi50c1wiO1xuZXhwb3J0IHR5cGUgeyBNb2RlbFNlbGVjdGlvblBvbGljeU9wdGlvbnMgfSBmcm9tIFwiLi9fc2VsZWN0aW9uLnRzXCI7XG5cbi8qKiBJbnRlbnQgcGx1cyBjYXRhbG9ndWUgY2FjaGUgY29udHJvbHMgcGFzc2VkIHRvIHtAbGluayBzZWxlY3RNb2RlbH0uICovXG5leHBvcnQgaW50ZXJmYWNlIFNlbGVjdE1vZGVsSW5wdXQgZXh0ZW5kcyBSZXNvbHZlTW9kZWxJbnB1dCB7XG4gIHR0bE1zPzogbnVtYmVyO1xuICBjYWNoZUlkZW50aXR5Pzogc3RyaW5nO1xufVxuXG4vKiogUXVlcnkgcGx1cyBjYXRhbG9ndWUgY2FjaGUgY29udHJvbHMgcGFzc2VkIHRvIHtAbGluayBzZWFyY2hNb2RlbHN9LiAqL1xuZXhwb3J0IGludGVyZmFjZSBTZWFyY2hNb2RlbHNJbnB1dCBleHRlbmRzIE1vZGVsUXVlcnkge1xuICB0dGxNcz86IG51bWJlcjtcbiAgY2FjaGVJZGVudGl0eT86IHN0cmluZztcbn1cblxuLyoqIFJlc29sdmUgYWdhaW5zdCBhIHBvdGVudGlhbGx5IHN0YWxlIGNhdGFsb2d1ZSwgcmVmcmVzaGluZyBvbmNlIG9uIGEgbWlzcy4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByYW5rTW9kZWxJZExpdmUoXG4gIGxvYWQ6IChmb3JjZTogYm9vbGVhbikgPT4gUHJvbWlzZTxyZWFkb25seSBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5W10+LFxuICBzZWFyY2g6IHN0cmluZyxcbiAgb3B0aW9uczogUmVzb2x2ZU1vZGVsT3B0aW9ucyA9IHt9LFxuICBwb2xpY3k6IE1vZGVsU2VsZWN0aW9uUG9saWN5T3B0aW9ucyA9IHt9LFxuKTogUHJvbWlzZTxSZXNvbHZlZE1vZGVsPiB7XG4gIGNvbnN0IHJlc29sdmVkID0gcmFua01vZGVsSWQoYXdhaXQgbG9hZChmYWxzZSksIHNlYXJjaCwgb3B0aW9ucywgcG9saWN5KTtcbiAgaWYgKHJlc29sdmVkLm1hdGNoZWQpIHJldHVybiByZXNvbHZlZDtcbiAgcmV0dXJuIHJhbmtNb2RlbElkKGF3YWl0IGxvYWQodHJ1ZSksIHNlYXJjaCwgb3B0aW9ucywgcG9saWN5KTtcbn1cblxuLyoqIExpc3QgYW5kIHJhbmsgYSB3b3Jrc3BhY2UgbW9kZWwgY2F0YWxvZ3VlLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHNlYXJjaE1vZGVscyhcbiAgY2xpZW50OiBXb3Jrc3BhY2VDbGllbnRMaWtlLFxuICBob3N0OiBzdHJpbmcsXG4gIGlucHV0OiBTZWFyY2hNb2RlbHNJbnB1dCA9IHt9LFxuKTogUHJvbWlzZTxSYW5rZWRNb2RlbFtdPiB7XG4gIGNvbnN0IGVuZHBvaW50cyA9IGF3YWl0IGxpc3RTZXJ2aW5nRW5kcG9pbnRzKGNsaWVudCwgaG9zdCwgY2F0YWxvZ3VlT3B0aW9ucyhpbnB1dCkpO1xuICByZXR1cm4gbG9va3VwTW9kZWxzKGVuZHBvaW50cywgaW5wdXQpO1xufVxuXG4vKiogTGlzdCBhIHdvcmtzcGFjZSBjYXRhbG9ndWUgYW5kIHJlc29sdmUgb25lIG1vZGVsLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHNlbGVjdE1vZGVsKFxuICBjbGllbnQ6IFdvcmtzcGFjZUNsaWVudExpa2UsXG4gIGhvc3Q6IHN0cmluZyxcbiAgaW5wdXQ6IFNlbGVjdE1vZGVsSW5wdXQgPSB7fSxcbik6IFByb21pc2U8UmVzb2x2ZWRNb2RlbFNlbGVjdGlvbj4ge1xuICBpZiAoaW5wdXQuZXhwbGljaXQgIT09IHVuZGVmaW5lZCAmJiBpbnB1dC5mdXp6eSA9PT0gZmFsc2UgJiYgIWlucHV0LnJlcXVpcmVzVG9vbHMpIHtcbiAgICByZXR1cm4geyBtb2RlbElkOiBpbnB1dC5leHBsaWNpdCwgc291cmNlOiBcImV4cGxpY2l0XCIgfTtcbiAgfVxuICBjb25zdCBlbmRwb2ludHMgPSBhd2FpdCBsaXN0U2VydmluZ0VuZHBvaW50cyhjbGllbnQsIGhvc3QsIGNhdGFsb2d1ZU9wdGlvbnMoaW5wdXQpKTtcbiAgcmV0dXJuIHJlc29sdmVNb2RlbChlbmRwb2ludHMsIGlucHV0KTtcbn1cblxuZnVuY3Rpb24gY2F0YWxvZ3VlT3B0aW9ucyhpbnB1dDoge1xuICByZWFkb25seSB0dGxNcz86IG51bWJlcjtcbiAgcmVhZG9ubHkgY2FjaGVJZGVudGl0eT86IHN0cmluZztcbn0pOiBMaXN0U2VydmluZ0VuZHBvaW50c09wdGlvbnMge1xuICByZXR1cm4ge1xuICAgIC4uLihpbnB1dC50dGxNcyAhPT0gdW5kZWZpbmVkID8geyB0dGxNczogaW5wdXQudHRsTXMgfSA6IHt9KSxcbiAgICAuLi4oaW5wdXQuY2FjaGVJZGVudGl0eSAhPT0gdW5kZWZpbmVkID8geyBjYWNoZUlkZW50aXR5OiBpbnB1dC5jYWNoZUlkZW50aXR5IH0gOiB7fSksXG4gIH07XG59XG4iXX0=
|
package/package.json
CHANGED
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@databricks/appkit": "0.81.0",
|
|
28
|
-
"@dbx-tools/appkit": "^0.9.
|
|
29
|
-
"@dbx-tools/auth": "^0.9.
|
|
30
|
-
"@dbx-tools/cache": "^0.9.
|
|
31
|
-
"@dbx-tools/shared-auth": "^0.9.
|
|
32
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
33
|
-
"@dbx-tools/shared-model": "^0.9.
|
|
28
|
+
"@dbx-tools/appkit": "^0.9.132",
|
|
29
|
+
"@dbx-tools/auth": "^0.9.132",
|
|
30
|
+
"@dbx-tools/cache": "^0.9.132",
|
|
31
|
+
"@dbx-tools/shared-auth": "^0.9.132",
|
|
32
|
+
"@dbx-tools/shared-core": "^0.9.132",
|
|
33
|
+
"@dbx-tools/shared-model": "^0.9.132",
|
|
34
34
|
"cheerio": "^1.2.0",
|
|
35
35
|
"fuse.js": "^7.4.2"
|
|
36
36
|
},
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"./package.json": "./package.json"
|
|
101
101
|
}
|
|
102
102
|
},
|
|
103
|
-
"version": "0.9.
|
|
103
|
+
"version": "0.9.132",
|
|
104
104
|
"types": "./lib/index.d.ts",
|
|
105
105
|
"dbxToolsConfig": {
|
|
106
106
|
"tags": [
|
package/src/_ranking.ts
CHANGED
|
@@ -32,6 +32,7 @@ const DEFAULT_FUZZY_THRESHOLD = 0.4;
|
|
|
32
32
|
interface NativeRankingOptions {
|
|
33
33
|
readonly includeDeprecated?: boolean;
|
|
34
34
|
readonly modelClass?: ModelClass;
|
|
35
|
+
readonly preferSystemModels?: boolean;
|
|
35
36
|
readonly task?: string;
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -60,6 +61,7 @@ export function rankEndpoints(
|
|
|
60
61
|
const search = query.search?.trim();
|
|
61
62
|
const requestedClass = options.modelClass ?? query.modelClass;
|
|
62
63
|
const includeDeprecated = query.includeDeprecated ?? options.includeDeprecated ?? false;
|
|
64
|
+
const preferSystemModels = options.preferSystemModels ?? false;
|
|
63
65
|
const eligible = requestedClass ? classesAtOrBelow(requestedClass) : CHAT_CLASS_ORDER;
|
|
64
66
|
const candidates: RankedModel[] = [];
|
|
65
67
|
if (search && requestedClass === undefined) {
|
|
@@ -83,18 +85,24 @@ export function rankEndpoints(
|
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
const exact = search
|
|
89
|
+
? candidates.find((candidate) => candidate.endpoint.name === search)
|
|
90
|
+
: undefined;
|
|
91
|
+
let ranked: RankedModel[];
|
|
92
|
+
if (exact) {
|
|
93
|
+
ranked = [{ ...exact, score: 0 }];
|
|
94
|
+
} else {
|
|
95
|
+
const selectable = preferSystemModels
|
|
96
|
+
? preferSystemCandidates(candidates)
|
|
97
|
+
: candidates.filter((candidate) => !isSystemModel(candidate.endpoint));
|
|
98
|
+
ranked =
|
|
99
|
+
!search && requestedClass === undefined
|
|
100
|
+
? preferLatestWithinLeadingFamily(selectable, preferSystemModels)
|
|
101
|
+
: selectable;
|
|
102
|
+
if (search) {
|
|
95
103
|
const threshold = query.threshold ?? DEFAULT_FUZZY_THRESHOLD;
|
|
96
104
|
const searchTokens = tokenize(search);
|
|
97
|
-
const tokenMatches =
|
|
105
|
+
const tokenMatches = selectable
|
|
98
106
|
.filter((candidate) => {
|
|
99
107
|
const candidateTokens = new Set(searchableValues(candidate.endpoint).flatMap(tokenize));
|
|
100
108
|
return (
|
|
@@ -103,9 +111,9 @@ export function rankEndpoints(
|
|
|
103
111
|
})
|
|
104
112
|
.map((candidate) => ({ ...candidate, score: 0 }));
|
|
105
113
|
if (tokenMatches.length > 0) {
|
|
106
|
-
ranked = tokenMatches.sort(compareRanked);
|
|
114
|
+
ranked = tokenMatches.sort((left, right) => compareRanked(left, right, preferSystemModels));
|
|
107
115
|
} else {
|
|
108
|
-
const fuse = new Fuse(
|
|
116
|
+
const fuse = new Fuse(selectable, {
|
|
109
117
|
keys: [
|
|
110
118
|
"endpoint.name",
|
|
111
119
|
"endpoint.displayName",
|
|
@@ -125,7 +133,7 @@ export function rankEndpoints(
|
|
|
125
133
|
.search(normalizedSearch)
|
|
126
134
|
.filter((result) => (result.score ?? 0) <= threshold)
|
|
127
135
|
.map((result) => ({ ...result.item, score: result.score ?? 0 }))
|
|
128
|
-
.sort(compareRanked)
|
|
136
|
+
.sort((left, right) => compareRanked(left, right, preferSystemModels))
|
|
129
137
|
: [];
|
|
130
138
|
}
|
|
131
139
|
}
|
|
@@ -137,14 +145,17 @@ export function rankEndpoints(
|
|
|
137
145
|
return query.limit === undefined ? restored : restored.slice(0, Math.max(0, query.limit));
|
|
138
146
|
}
|
|
139
147
|
|
|
140
|
-
function preferLatestWithinLeadingFamily(
|
|
148
|
+
function preferLatestWithinLeadingFamily(
|
|
149
|
+
candidates: readonly RankedModel[],
|
|
150
|
+
preferSystemModels: boolean,
|
|
151
|
+
): RankedModel[] {
|
|
141
152
|
const leading = candidates[0];
|
|
142
153
|
if (!leading) return [];
|
|
143
154
|
const family = modelFamily(leading.endpoint.name);
|
|
144
155
|
if (!family) return [...candidates];
|
|
145
156
|
const sameFamily = candidates
|
|
146
157
|
.filter((candidate) => modelFamily(candidate.endpoint.name) === family)
|
|
147
|
-
.sort(compareRanked);
|
|
158
|
+
.sort((left, right) => compareRanked(left, right, preferSystemModels));
|
|
148
159
|
const selected = new Set(sameFamily.map((candidate) => candidate.endpoint.name));
|
|
149
160
|
return [
|
|
150
161
|
...sameFamily,
|
|
@@ -252,9 +263,14 @@ export function normalizeEndpoints(endpoints: readonly unknown[]): ServingEndpoi
|
|
|
252
263
|
});
|
|
253
264
|
}
|
|
254
265
|
|
|
255
|
-
function compareRanked(left: RankedModel, right: RankedModel): number {
|
|
266
|
+
function compareRanked(left: RankedModel, right: RankedModel, preferSystemModels: boolean): number {
|
|
256
267
|
const score = Math.round((left.score ?? 0) * 1000) - Math.round((right.score ?? 0) * 1000);
|
|
257
268
|
if (score !== 0) return score;
|
|
269
|
+
if (preferSystemModels) {
|
|
270
|
+
const leftSystem = isSystemModel(left.endpoint);
|
|
271
|
+
const rightSystem = isSystemModel(right.endpoint);
|
|
272
|
+
if (leftSystem !== rightSystem) return leftSystem ? -1 : 1;
|
|
273
|
+
}
|
|
258
274
|
const leftOss = isOpenWeightsGpt(left.endpoint.name);
|
|
259
275
|
const rightOss = isOpenWeightsGpt(right.endpoint.name);
|
|
260
276
|
if (leftOss !== rightOss) return leftOss ? 1 : -1;
|
|
@@ -269,6 +285,17 @@ function compareRanked(left: RankedModel, right: RankedModel): number {
|
|
|
269
285
|
return MODEL_CLASS_ORDER.indexOf(left.modelClass) - MODEL_CLASS_ORDER.indexOf(right.modelClass);
|
|
270
286
|
}
|
|
271
287
|
|
|
288
|
+
function preferSystemCandidates(candidates: readonly RankedModel[]): RankedModel[] {
|
|
289
|
+
return [
|
|
290
|
+
...candidates.filter((candidate) => isSystemModel(candidate.endpoint)),
|
|
291
|
+
...candidates.filter((candidate) => !isSystemModel(candidate.endpoint)),
|
|
292
|
+
];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function isSystemModel(endpoint: ServingEndpointSummary): boolean {
|
|
296
|
+
return endpoint.name.toLowerCase().startsWith("system.ai.");
|
|
297
|
+
}
|
|
298
|
+
|
|
272
299
|
function modelVariantRank(name: string): number {
|
|
273
300
|
if (!/(?:^|[-_.])gpt(?:[-_.]|$)/i.test(name)) return 2;
|
|
274
301
|
if (/(?:^|[-_.])sol(?:[-_.]|$)/i.test(name)) return 0;
|
package/src/_selection.ts
CHANGED
|
@@ -18,12 +18,19 @@ import { rankEndpoints } from "./_ranking.ts";
|
|
|
18
18
|
import { FALLBACK_MODEL_IDS, modelsForClass } from "./fallback.ts";
|
|
19
19
|
import { endpointSupportsTools } from "./policy.ts";
|
|
20
20
|
|
|
21
|
+
/** In-process model-ranking policy controls. */
|
|
22
|
+
export interface ModelSelectionPolicyOptions {
|
|
23
|
+
/** Prefer Databricks system endpoints instead of excluding them from non-exact selection. */
|
|
24
|
+
readonly preferSystemModels?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
/** Rank a catalogue against a model query, best-first. */
|
|
22
28
|
export function lookupModels(
|
|
23
29
|
endpoints: readonly ServingEndpointSummary[],
|
|
24
30
|
query: ModelQuery = {},
|
|
31
|
+
policy: ModelSelectionPolicyOptions = {},
|
|
25
32
|
): RankedModel[] {
|
|
26
|
-
return rankEndpoints(endpoints, query);
|
|
33
|
+
return rankEndpoints(endpoints, query, policy);
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
/** Resolve the closest model id in one catalogue snapshot. */
|
|
@@ -31,13 +38,18 @@ export function rankModelId(
|
|
|
31
38
|
endpoints: readonly ServingEndpointSummary[],
|
|
32
39
|
search: string,
|
|
33
40
|
options: ResolveModelOptions = {},
|
|
41
|
+
policy: ModelSelectionPolicyOptions = {},
|
|
34
42
|
): ResolvedModel {
|
|
35
|
-
const [top] = lookupModels(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
const [top] = lookupModels(
|
|
44
|
+
endpoints,
|
|
45
|
+
{
|
|
46
|
+
search,
|
|
47
|
+
limit: 1,
|
|
48
|
+
...(options.threshold !== undefined ? { threshold: options.threshold } : {}),
|
|
49
|
+
...(options.requiresTools !== undefined ? { requiresTools: options.requiresTools } : {}),
|
|
50
|
+
},
|
|
51
|
+
policy,
|
|
52
|
+
);
|
|
41
53
|
if (!top) return { modelId: search, matched: false };
|
|
42
54
|
return { modelId: top.endpoint.name, matched: true, score: top.score };
|
|
43
55
|
}
|
|
@@ -46,13 +58,14 @@ export function rankModelId(
|
|
|
46
58
|
export function resolveModel(
|
|
47
59
|
endpoints: readonly ServingEndpointSummary[],
|
|
48
60
|
input: ResolveModelInput = {},
|
|
61
|
+
policy: ModelSelectionPolicyOptions = {},
|
|
49
62
|
): ResolvedModelSelection {
|
|
50
63
|
if (input.explicit !== undefined) {
|
|
51
64
|
if (input.fuzzy === false) {
|
|
52
65
|
if (input.requiresTools) assertToolSupport(endpoints, input.explicit);
|
|
53
66
|
return { modelId: input.explicit, source: "explicit" };
|
|
54
67
|
}
|
|
55
|
-
const [top] = lookupModels(endpoints, buildQuery(input, input.explicit));
|
|
68
|
+
const [top] = lookupModels(endpoints, buildQuery(input, input.explicit), policy);
|
|
56
69
|
if (input.requiresTools && !top) {
|
|
57
70
|
throw new Error(`No tool-capable model matches "${input.explicit}"`);
|
|
58
71
|
}
|
|
@@ -70,7 +83,7 @@ export function resolveModel(
|
|
|
70
83
|
}
|
|
71
84
|
|
|
72
85
|
const source = input.modelClass !== undefined ? "class" : "fallback";
|
|
73
|
-
const [top] = lookupModels(endpoints, buildQuery(input, undefined));
|
|
86
|
+
const [top] = lookupModels(endpoints, buildQuery(input, undefined), policy);
|
|
74
87
|
if (top) return { modelId: top.endpoint.name, source };
|
|
75
88
|
|
|
76
89
|
if (input.liveOnly) {
|
package/src/model-client.ts
CHANGED
|
@@ -30,9 +30,14 @@ import {
|
|
|
30
30
|
|
|
31
31
|
import { normalizeEndpoints } from "./_ranking.ts";
|
|
32
32
|
import { lookupModels, resolveModel } from "./_selection.ts";
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
aiGatewayCodexResponsesUrl,
|
|
35
|
+
chatCompletionsUrl,
|
|
36
|
+
invocationsUrl,
|
|
37
|
+
responsesUpstreamUrl,
|
|
38
|
+
} from "./invoke.ts";
|
|
34
39
|
import { hydrateModelMetadata, modelMetadataFor } from "./metadata.ts";
|
|
35
|
-
import { modelServingApi } from "./policy.ts";
|
|
40
|
+
import { modelServingApi, prefersSystemModelService, systemModelService } from "./policy.ts";
|
|
36
41
|
|
|
37
42
|
const logger = log.logger("model/client");
|
|
38
43
|
|
|
@@ -53,6 +58,8 @@ export interface ModelClientOptions {
|
|
|
53
58
|
cacheTtlMs?: number;
|
|
54
59
|
/** Hydrate Node-only metadata caches before constructing the client. */
|
|
55
60
|
hydrateMetadata?: boolean;
|
|
61
|
+
/** Prefer `system.ai.*` services over serving endpoints for non-exact model selection. */
|
|
62
|
+
preferSystemModels?: boolean;
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
/** Secret-free runtime identity and cache configuration. */
|
|
@@ -62,6 +69,7 @@ export interface ModelClientStatus {
|
|
|
62
69
|
readonly principal: string;
|
|
63
70
|
readonly workspaceId?: string;
|
|
64
71
|
readonly cacheTtlMs: number;
|
|
72
|
+
readonly preferSystemModels: boolean;
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
/** Serving protocol selected for one resolved model. */
|
|
@@ -162,6 +170,7 @@ class DefaultModelClient implements ModelClient {
|
|
|
162
170
|
constructor(
|
|
163
171
|
private readonly client: DatabricksModelClient,
|
|
164
172
|
private readonly cacheTtlMs: number,
|
|
173
|
+
private readonly preferSystemModels: boolean,
|
|
165
174
|
) {}
|
|
166
175
|
|
|
167
176
|
async listModels(refresh = false): Promise<ServingEndpointSummary[]> {
|
|
@@ -169,7 +178,9 @@ class DefaultModelClient implements ModelClient {
|
|
|
169
178
|
}
|
|
170
179
|
|
|
171
180
|
async searchModels(query: ModelQuery = {}, refresh = false): Promise<RankedModel[]> {
|
|
172
|
-
return lookupModels(await this.catalogue(refresh), query
|
|
181
|
+
return lookupModels(await this.catalogue(refresh), query, {
|
|
182
|
+
preferSystemModels: this.preferSystemModels,
|
|
183
|
+
}).map(({ endpoint, ...ranked }) => ({
|
|
173
184
|
...ranked,
|
|
174
185
|
endpoint: cloneEndpoint(endpoint),
|
|
175
186
|
}));
|
|
@@ -182,19 +193,34 @@ class DefaultModelClient implements ModelClient {
|
|
|
182
193
|
? { ...selection, modelClass: ModelClass.Embedding }
|
|
183
194
|
: selection;
|
|
184
195
|
const catalogue = await this.catalogue(refresh);
|
|
185
|
-
let resolved = resolveModel(catalogue, effectiveSelection
|
|
196
|
+
let resolved = resolveModel(catalogue, effectiveSelection, {
|
|
197
|
+
preferSystemModels: this.preferSystemModels,
|
|
198
|
+
});
|
|
186
199
|
if (isUnmatchedExplicit(catalogue, effectiveSelection, resolved)) {
|
|
187
|
-
resolved = resolveModel(await this.catalogue(true), effectiveSelection
|
|
200
|
+
resolved = resolveModel(await this.catalogue(true), effectiveSelection, {
|
|
201
|
+
preferSystemModels: this.preferSystemModels,
|
|
202
|
+
});
|
|
188
203
|
}
|
|
189
204
|
const endpoint = (await this.catalogue(false)).find(
|
|
190
205
|
(candidate) => candidate.name === resolved.modelId,
|
|
191
206
|
);
|
|
192
207
|
const protocol = requestedProtocol ?? inferProtocol(endpoint, resolved.modelId);
|
|
193
208
|
const host = this.client.host();
|
|
194
|
-
const
|
|
209
|
+
const service = protocol === "responses" && endpoint ? systemModelService(endpoint) : undefined;
|
|
210
|
+
const system =
|
|
211
|
+
service !== undefined &&
|
|
212
|
+
prefersSystemModelService(
|
|
213
|
+
selection.explicit,
|
|
214
|
+
resolved.modelId,
|
|
215
|
+
service,
|
|
216
|
+
this.preferSystemModels,
|
|
217
|
+
);
|
|
218
|
+
const url = system
|
|
219
|
+
? aiGatewayCodexResponsesUrl(host)
|
|
220
|
+
: routeUrl(host, resolved.modelId, protocol);
|
|
195
221
|
const headers = await this.client.auth.headers({ login });
|
|
196
222
|
return {
|
|
197
|
-
modelId: resolved.modelId,
|
|
223
|
+
modelId: system ? service : resolved.modelId,
|
|
198
224
|
...(endpoint ? { endpoint: cloneEndpoint(endpoint) } : {}),
|
|
199
225
|
source: resolved.source,
|
|
200
226
|
protocol,
|
|
@@ -217,6 +243,7 @@ class DefaultModelClient implements ModelClient {
|
|
|
217
243
|
principal: this.client.principal(),
|
|
218
244
|
...(this.client.workspaceId() ? { workspaceId: this.client.workspaceId() } : {}),
|
|
219
245
|
cacheTtlMs: this.cacheTtlMs,
|
|
246
|
+
preferSystemModels: this.preferSystemModels,
|
|
220
247
|
};
|
|
221
248
|
}
|
|
222
249
|
|
|
@@ -298,6 +325,7 @@ export async function createModelClient(options: ModelClientOptions = {}): Promi
|
|
|
298
325
|
return new DefaultModelClient(
|
|
299
326
|
new AuthenticatedModelClient(await createAuthClient(options.auth), globalThis.fetch),
|
|
300
327
|
cacheTtlMs,
|
|
328
|
+
options.preferSystemModels ?? false,
|
|
301
329
|
);
|
|
302
330
|
}
|
|
303
331
|
|
|
@@ -310,8 +338,9 @@ export function listProfiles(refresh = false): DatabricksProfileSummary[] {
|
|
|
310
338
|
export function createModelClientWithDatabricksClient(
|
|
311
339
|
client: DatabricksModelClient,
|
|
312
340
|
cacheTtlMs = DEFAULT_MODEL_CLIENT_CACHE_TTL_MS,
|
|
341
|
+
preferSystemModels = false,
|
|
313
342
|
): ModelClient {
|
|
314
|
-
return new DefaultModelClient(client, validateCacheTtl(cacheTtlMs));
|
|
343
|
+
return new DefaultModelClient(client, validateCacheTtl(cacheTtlMs), preferSystemModels);
|
|
315
344
|
}
|
|
316
345
|
|
|
317
346
|
/** @internal Create a model client with injectable auth host capabilities. */
|
|
@@ -326,6 +355,7 @@ export async function createModelClientWithDependencies(
|
|
|
326
355
|
dependencies.fetch ?? globalThis.fetch,
|
|
327
356
|
),
|
|
328
357
|
validateCacheTtl(options.cacheTtlMs),
|
|
358
|
+
options.preferSystemModels ?? false,
|
|
329
359
|
);
|
|
330
360
|
}
|
|
331
361
|
|
package/src/policy.ts
CHANGED
|
@@ -96,6 +96,49 @@ export function isFoundationModelIdentity(name: string): boolean {
|
|
|
96
96
|
return /^(?:databricks-|system\.ai\.)/i.test(name.trim());
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/** Normalize a served-entity identity to its AI Gateway model-service name. */
|
|
100
|
+
export function modelServiceName(value: string | undefined): string | undefined {
|
|
101
|
+
const normalized = value?.trim().replace(/^databricks\//, "");
|
|
102
|
+
if (!normalized) return undefined;
|
|
103
|
+
if (normalized.startsWith("system.ai.databricks-")) {
|
|
104
|
+
return `system.ai.${normalized.slice("system.ai.databricks-".length)}`;
|
|
105
|
+
}
|
|
106
|
+
if (normalized.startsWith("databricks-")) {
|
|
107
|
+
return `system.ai.${normalized.slice("databricks-".length)}`;
|
|
108
|
+
}
|
|
109
|
+
return normalized;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Return the `system.ai.*` AI Gateway Codex service behind an endpoint, when one exists. */
|
|
113
|
+
export function systemModelService(
|
|
114
|
+
endpoint: Pick<ServingEndpointSummary, "name" | "modelServiceName">,
|
|
115
|
+
): string | undefined {
|
|
116
|
+
const service = modelServiceName(endpoint.modelServiceName);
|
|
117
|
+
if (!service?.startsWith("system.ai.")) return undefined;
|
|
118
|
+
const family = modelFamily(endpoint.name);
|
|
119
|
+
return family === ModelFamily.Claude || family === ModelFamily.Gemini ? undefined : service;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Return whether a request should use the `system.ai.*` service instead of the
|
|
124
|
+
* serving endpoint. An exact endpoint id or exact service name always wins;
|
|
125
|
+
* otherwise `preferSystemModels` decides.
|
|
126
|
+
*/
|
|
127
|
+
export function prefersSystemModelService(
|
|
128
|
+
requested: string | undefined,
|
|
129
|
+
endpointName: string,
|
|
130
|
+
serviceName: string,
|
|
131
|
+
preferSystemModels: boolean,
|
|
132
|
+
): boolean {
|
|
133
|
+
const name = requested
|
|
134
|
+
?.trim()
|
|
135
|
+
.replace(/^(?:dbx|databricks)\//i, "")
|
|
136
|
+
.toLowerCase();
|
|
137
|
+
if (name === endpointName.toLowerCase()) return false;
|
|
138
|
+
if (name === serviceName.toLowerCase()) return true;
|
|
139
|
+
return preferSystemModels;
|
|
140
|
+
}
|
|
141
|
+
|
|
99
142
|
/** Return whether an identity is hosted GPT-OSS rather than a GPT generation. */
|
|
100
143
|
export function isOpenWeightsGpt(name: string): boolean {
|
|
101
144
|
return modelFamily(name) === "gpt" && /(?:^|[-_.])oss(?:[-_.]|$)/i.test(name);
|
package/src/resolve.ts
CHANGED
|
@@ -13,7 +13,12 @@ import type {
|
|
|
13
13
|
ServingEndpointSummary,
|
|
14
14
|
} from "@dbx-tools/shared-model";
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
lookupModels,
|
|
18
|
+
rankModelId,
|
|
19
|
+
resolveModel,
|
|
20
|
+
type ModelSelectionPolicyOptions,
|
|
21
|
+
} from "./_selection.ts";
|
|
17
22
|
import {
|
|
18
23
|
listServingEndpoints,
|
|
19
24
|
type ListServingEndpointsOptions,
|
|
@@ -21,6 +26,7 @@ import {
|
|
|
21
26
|
} from "./model-catalog.ts";
|
|
22
27
|
|
|
23
28
|
export { lookupModels, rankModelId, resolveModel } from "./_selection.ts";
|
|
29
|
+
export type { ModelSelectionPolicyOptions } from "./_selection.ts";
|
|
24
30
|
|
|
25
31
|
/** Intent plus catalogue cache controls passed to {@link selectModel}. */
|
|
26
32
|
export interface SelectModelInput extends ResolveModelInput {
|
|
@@ -39,10 +45,11 @@ export async function rankModelIdLive(
|
|
|
39
45
|
load: (force: boolean) => Promise<readonly ServingEndpointSummary[]>,
|
|
40
46
|
search: string,
|
|
41
47
|
options: ResolveModelOptions = {},
|
|
48
|
+
policy: ModelSelectionPolicyOptions = {},
|
|
42
49
|
): Promise<ResolvedModel> {
|
|
43
|
-
const resolved = rankModelId(await load(false), search, options);
|
|
50
|
+
const resolved = rankModelId(await load(false), search, options, policy);
|
|
44
51
|
if (resolved.matched) return resolved;
|
|
45
|
-
return rankModelId(await load(true), search, options);
|
|
52
|
+
return rankModelId(await load(true), search, options, policy);
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
/** List and rank a workspace model catalogue. */
|