@dbx-tools/model 0.9.32 → 0.9.37
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 +3 -3
- package/exports.ts +1 -0
- package/index.ts +12 -8
- package/lib/index.d.ts +12 -8
- package/lib/index.js +8 -5
- package/lib/scripts/generate-metadata.d.ts +1 -0
- package/lib/scripts/generate-metadata.js +65 -0
- package/lib/src/_metadata-contract.d.ts +12 -6
- package/lib/src/_metadata-contract.js +3 -1
- package/lib/src/_metadata-generator.d.ts +3 -1
- package/lib/src/_metadata-generator.js +148 -1
- package/lib/src/_ranking.js +39 -12
- package/lib/src/_selection.d.ts +1 -17
- package/lib/src/_selection.js +1 -1
- package/lib/src/classify.d.ts +1 -17
- package/lib/src/classify.js +1 -1
- package/lib/src/generated/model-reasoning.json +147 -0
- package/lib/src/invoke.d.ts +8 -0
- package/lib/src/invoke.js +13 -1
- package/lib/src/metadata-cache.d.ts +84 -0
- package/lib/src/metadata-cache.js +234 -0
- package/lib/src/metadata.d.ts +52 -17
- package/lib/src/metadata.js +251 -18
- package/lib/src/model-catalog.d.ts +1 -19
- package/lib/src/model-catalog.js +9 -2
- package/lib/src/model-client.d.ts +11 -19
- package/lib/src/model-client.js +42 -24
- package/lib/src/reasoning-translation.d.ts +105 -0
- package/lib/src/reasoning-translation.js +444 -0
- package/lib/src/resolve.d.ts +2 -4
- package/lib/src/resolve.js +2 -2
- package/lib/src/serving-wire.d.ts +37 -0
- package/lib/src/serving-wire.js +211 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +110 -17
- package/src/_metadata-contract.ts +17 -7
- package/src/_metadata-generator.ts +160 -0
- package/src/_ranking.ts +40 -11
- package/src/_selection.ts +4 -19
- package/src/classify.ts +2 -18
- package/src/generated/model-reasoning.json +147 -0
- package/src/invoke.ts +16 -0
- package/src/metadata-cache.ts +322 -0
- package/src/metadata.ts +334 -43
- package/src/model-catalog.ts +8 -25
- package/src/model-client.ts +67 -44
- package/src/reasoning-translation.ts +495 -0
- package/src/resolve.ts +10 -11
- package/src/serving-wire.ts +231 -0
- package/lib/src/python.d.ts +0 -2
- package/lib/src/python.js +0 -3
- package/src/python.ts +0 -2
package/src/resolve.ts
CHANGED
|
@@ -3,25 +3,24 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
ModelQuery,
|
|
8
|
+
RankedModel,
|
|
9
|
+
ResolvedModel,
|
|
10
|
+
ResolvedModelSelection,
|
|
11
|
+
ResolveModelInput,
|
|
12
|
+
ResolveModelOptions,
|
|
13
|
+
ServingEndpointSummary,
|
|
14
|
+
} from "@dbx-tools/shared-model";
|
|
7
15
|
|
|
8
|
-
import {
|
|
9
|
-
lookupModels,
|
|
10
|
-
rankModelId,
|
|
11
|
-
type ResolveModelInput,
|
|
12
|
-
type ResolvedModelSelection,
|
|
13
|
-
resolveModel,
|
|
14
|
-
} from "./_selection.ts";
|
|
16
|
+
import { lookupModels, rankModelId, resolveModel } from "./_selection.ts";
|
|
15
17
|
import {
|
|
16
18
|
listServingEndpoints,
|
|
17
19
|
type ListServingEndpointsOptions,
|
|
18
|
-
type ResolvedModel,
|
|
19
|
-
type ResolveModelOptions,
|
|
20
20
|
type WorkspaceClientLike,
|
|
21
21
|
} from "./model-catalog.ts";
|
|
22
22
|
|
|
23
23
|
export { lookupModels, rankModelId, resolveModel } from "./_selection.ts";
|
|
24
|
-
export type { ResolveModelInput, ResolvedModelSelection } from "./_selection.ts";
|
|
25
24
|
|
|
26
25
|
/** Intent plus catalogue cache controls passed to {@link selectModel}. */
|
|
27
26
|
export interface SelectModelInput extends ResolveModelInput {
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-neutral repairs for OpenAI Chat traffic sent through Databricks
|
|
3
|
+
* Model Serving.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { json, stringUtils } from "@dbx-tools/shared-core";
|
|
9
|
+
import { type ChatMessage, type ChatRole, openaiChat } from "@dbx-tools/shared-model";
|
|
10
|
+
|
|
11
|
+
import { chatToolReasoningEffort } from "./policy.ts";
|
|
12
|
+
|
|
13
|
+
/** Chat message plus provider reasoning fields emitted by Databricks models. */
|
|
14
|
+
export interface ServingChatMessage extends ChatMessage {
|
|
15
|
+
role: ChatRole | "reasoning";
|
|
16
|
+
reasoning?: unknown;
|
|
17
|
+
reasoning_content?: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Prepared fetch arguments and the sanitized request body. */
|
|
21
|
+
export interface RewrittenServingRequest {
|
|
22
|
+
input: Parameters<typeof fetch>[0];
|
|
23
|
+
init: Parameters<typeof fetch>[1];
|
|
24
|
+
body: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const REASONING_PART_TYPES: ReadonlySet<string> = new Set([
|
|
28
|
+
"reasoning",
|
|
29
|
+
"thinking",
|
|
30
|
+
"redacted_thinking",
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
/** Sanitize a serialized Chat Completions request when provider quirks require it. */
|
|
34
|
+
export function rewriteServingBody(body: string): string {
|
|
35
|
+
const parsed = json.parseRecord(body);
|
|
36
|
+
if (!parsed) return body;
|
|
37
|
+
let changed =
|
|
38
|
+
openaiChat.stripUnsupportedChatFields(parsed).length > 0 ||
|
|
39
|
+
applyToolReasoningCompatibility(parsed);
|
|
40
|
+
if (Array.isArray(parsed.messages)) {
|
|
41
|
+
const messages = parsed.messages as ServingChatMessage[];
|
|
42
|
+
const stripped = stripReasoningFromServingMessages(messages);
|
|
43
|
+
const repaired = repairAssistantPrefill(messages);
|
|
44
|
+
changed = changed || stripped || repaired;
|
|
45
|
+
}
|
|
46
|
+
return changed ? JSON.stringify(parsed) : body;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Read and sanitize a Chat Completions request without retaining stale byte headers. */
|
|
50
|
+
export async function rewriteServingRequest(
|
|
51
|
+
input: Parameters<typeof fetch>[0],
|
|
52
|
+
init?: Parameters<typeof fetch>[1],
|
|
53
|
+
): Promise<RewrittenServingRequest> {
|
|
54
|
+
const request = new Request(input, init);
|
|
55
|
+
const body = await request.clone().text();
|
|
56
|
+
const rewritten = rewriteServingBody(body);
|
|
57
|
+
if (rewritten === body) return { input, init, body };
|
|
58
|
+
const headers = new Headers(request.headers);
|
|
59
|
+
headers.delete("content-length");
|
|
60
|
+
headers.delete("content-encoding");
|
|
61
|
+
return {
|
|
62
|
+
input: new Request(request, { body: rewritten, headers }),
|
|
63
|
+
init: undefined,
|
|
64
|
+
body: rewritten,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Normalize Chat Completions reasoning for GPT tool calls. */
|
|
69
|
+
export function applyToolReasoningCompatibility(body: Record<string, unknown>): boolean {
|
|
70
|
+
if (!Array.isArray(body.tools) || body.tools.length === 0) return false;
|
|
71
|
+
const model = stringUtils.trimToNull(body.model);
|
|
72
|
+
if (!model || chatToolReasoningEffort(model) !== "none") return false;
|
|
73
|
+
if (body.reasoning_effort === "none") return false;
|
|
74
|
+
body.reasoning_effort = "none";
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Remove provider reasoning blocks that cannot be replayed safely. */
|
|
79
|
+
export function stripReasoningFromServingMessages(messages: ServingChatMessage[]): boolean {
|
|
80
|
+
let changed = false;
|
|
81
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
82
|
+
const message = messages[index];
|
|
83
|
+
if (!message) continue;
|
|
84
|
+
if (message.role === "reasoning") {
|
|
85
|
+
messages.splice(index, 1);
|
|
86
|
+
changed = true;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (message.reasoning !== undefined) {
|
|
90
|
+
delete message.reasoning;
|
|
91
|
+
changed = true;
|
|
92
|
+
}
|
|
93
|
+
if (message.reasoning_content !== undefined) {
|
|
94
|
+
delete message.reasoning_content;
|
|
95
|
+
changed = true;
|
|
96
|
+
}
|
|
97
|
+
const parts = openaiChat.chatContentParts(message.content);
|
|
98
|
+
if (!parts) continue;
|
|
99
|
+
const filtered = parts.filter((part) => {
|
|
100
|
+
const type = part?.type;
|
|
101
|
+
if (typeof type !== "string" || !REASONING_PART_TYPES.has(type)) return true;
|
|
102
|
+
changed = true;
|
|
103
|
+
return false;
|
|
104
|
+
});
|
|
105
|
+
if (filtered.length !== parts.length) message.content = filtered;
|
|
106
|
+
const hasToolCalls = Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
|
|
107
|
+
if (message.role === "assistant" && !hasToolCalls && isEmptyServingContent(message.content)) {
|
|
108
|
+
messages.splice(index, 1);
|
|
109
|
+
changed = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return changed;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Fold a trailing assistant prefill back into its preceding tool-call turn. */
|
|
116
|
+
export function repairAssistantPrefill(messages: ServingChatMessage[]): boolean {
|
|
117
|
+
if (messages.length < 2) return false;
|
|
118
|
+
const last = messages[messages.length - 1];
|
|
119
|
+
if (!last || last.role !== "assistant" || (last.tool_calls && last.tool_calls.length > 0)) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
let index = messages.length - 2;
|
|
123
|
+
while (index >= 0 && messages[index]?.role === "tool") index--;
|
|
124
|
+
const opener = messages[index];
|
|
125
|
+
if (
|
|
126
|
+
!opener ||
|
|
127
|
+
opener.role !== "assistant" ||
|
|
128
|
+
!opener.tool_calls ||
|
|
129
|
+
opener.tool_calls.length === 0
|
|
130
|
+
) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
opener.content = [
|
|
134
|
+
stringUtils.trimToNull(textFromServingContent(opener.content)),
|
|
135
|
+
stringUtils.trimToNull(textFromServingContent(last.content)),
|
|
136
|
+
]
|
|
137
|
+
.filter((value): value is string => value !== null)
|
|
138
|
+
.join("\n\n");
|
|
139
|
+
messages.pop();
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Normalize array-valued content in a non-streaming Chat Completions response. */
|
|
144
|
+
export function rewriteServingResponseBody(body: string): string {
|
|
145
|
+
const parsed = json.parseRecord(body);
|
|
146
|
+
if (!parsed) return body;
|
|
147
|
+
return flattenChoiceMessageContent(parsed) ? JSON.stringify(parsed) : body;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Normalize array-valued content in an SSE stream without buffering the stream. */
|
|
151
|
+
export function rewriteServingResponseStream(
|
|
152
|
+
body: ReadableStream<Uint8Array>,
|
|
153
|
+
): ReadableStream<Uint8Array> {
|
|
154
|
+
const decoder = new TextDecoder();
|
|
155
|
+
const encoder = new TextEncoder();
|
|
156
|
+
let buffer = "";
|
|
157
|
+
return body.pipeThrough(
|
|
158
|
+
new TransformStream<Uint8Array, Uint8Array>({
|
|
159
|
+
transform(chunk, controller) {
|
|
160
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
161
|
+
let newline = buffer.indexOf("\n");
|
|
162
|
+
while (newline >= 0) {
|
|
163
|
+
const line = buffer.slice(0, newline + 1);
|
|
164
|
+
buffer = buffer.slice(newline + 1);
|
|
165
|
+
controller.enqueue(encoder.encode(rewriteServingResponseStreamLine(line)));
|
|
166
|
+
newline = buffer.indexOf("\n");
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
flush(controller) {
|
|
170
|
+
buffer += decoder.decode();
|
|
171
|
+
if (buffer) controller.enqueue(encoder.encode(rewriteServingResponseStreamLine(buffer)));
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Flatten Chat Completions `choices[].message.content` arrays to strings. */
|
|
178
|
+
export function flattenChoiceMessageContent(payload: Record<string, unknown>): boolean {
|
|
179
|
+
return flattenChoiceContent(payload, "message");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Flatten Chat Completions `choices[].delta.content` arrays to strings. */
|
|
183
|
+
export function flattenChoiceDeltaContent(payload: Record<string, unknown>): boolean {
|
|
184
|
+
return flattenChoiceContent(payload, "delta");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function rewriteServingResponseStreamLine(line: string): string {
|
|
188
|
+
const lineEnding = line.endsWith("\r\n") ? "\r\n" : line.endsWith("\n") ? "\n" : "";
|
|
189
|
+
const content = lineEnding ? line.slice(0, -lineEnding.length) : line;
|
|
190
|
+
const match = /^(data:\s*)(.*)$/.exec(content);
|
|
191
|
+
if (!match || match[2] === "[DONE]") return line;
|
|
192
|
+
const parsed = json.parseRecord(match[2]);
|
|
193
|
+
if (!parsed || !flattenChoiceDeltaContent(parsed)) return line;
|
|
194
|
+
return `${match[1]}${JSON.stringify(parsed)}${lineEnding}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function flattenChoiceContent(
|
|
198
|
+
payload: Record<string, unknown>,
|
|
199
|
+
field: "message" | "delta",
|
|
200
|
+
): boolean {
|
|
201
|
+
if (!Array.isArray(payload.choices)) return false;
|
|
202
|
+
let changed = false;
|
|
203
|
+
for (const choice of payload.choices) {
|
|
204
|
+
if (!choice || typeof choice !== "object") continue;
|
|
205
|
+
const container = (choice as Record<string, unknown>)[field];
|
|
206
|
+
if (!container || typeof container !== "object") continue;
|
|
207
|
+
const target = container as { content?: unknown };
|
|
208
|
+
const parts = openaiChat.chatContentParts(target.content);
|
|
209
|
+
if (!parts) continue;
|
|
210
|
+
target.content = openaiChat.chatContentToText(parts, { types: ["text"] });
|
|
211
|
+
changed = true;
|
|
212
|
+
}
|
|
213
|
+
return changed;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function textFromServingContent(content: ServingChatMessage["content"]): string {
|
|
217
|
+
return openaiChat.chatContentToText(content, { separator: "\n\n", types: ["text"] });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function isEmptyServingContent(content: ServingChatMessage["content"]): boolean {
|
|
221
|
+
if (content === undefined) return true;
|
|
222
|
+
if (typeof content === "string") return content.trim().length === 0;
|
|
223
|
+
const parts = openaiChat.chatContentParts(content);
|
|
224
|
+
if (!parts) return true;
|
|
225
|
+
return parts.every((part) => {
|
|
226
|
+
if (part?.type === "text") {
|
|
227
|
+
return typeof part.text !== "string" || part.text.trim().length === 0;
|
|
228
|
+
}
|
|
229
|
+
return false;
|
|
230
|
+
});
|
|
231
|
+
}
|
package/lib/src/python.d.ts
DELETED
package/lib/src/python.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
/** Portable entry point used by generated Python bindings. */
|
|
2
|
-
export { createModelClient } from "./model-client.js";
|
|
3
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHl0aG9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3B5dGhvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSw4REFBOEQ7QUFDOUQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sbUJBQW1CLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKiogUG9ydGFibGUgZW50cnkgcG9pbnQgdXNlZCBieSBnZW5lcmF0ZWQgUHl0aG9uIGJpbmRpbmdzLiAqL1xuZXhwb3J0IHsgY3JlYXRlTW9kZWxDbGllbnQgfSBmcm9tIFwiLi9tb2RlbC1jbGllbnQudHNcIjtcbiJdfQ==
|
package/src/python.ts
DELETED