modelpact-providers 1.0.0 → 1.2.0
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 +264 -11
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/ollama.d.ts +39 -0
- package/dist/ollama.d.ts.map +1 -0
- package/dist/ollama.js +442 -0
- package/dist/ollama.js.map +1 -0
- package/dist/openai.d.ts +60 -0
- package/dist/openai.d.ts.map +1 -0
- package/dist/openai.js +488 -0
- package/dist/openai.js.map +1 -0
- package/dist/prompt-api.d.ts +26 -0
- package/dist/prompt-api.d.ts.map +1 -0
- package/dist/prompt-api.js +261 -0
- package/dist/prompt-api.js.map +1 -0
- package/dist/webgpu.d.ts +73 -0
- package/dist/webgpu.d.ts.map +1 -0
- package/dist/webgpu.js +214 -0
- package/dist/webgpu.js.map +1 -0
- package/package.json +29 -5
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chrome's built-in model, through the Prompt API.
|
|
3
|
+
*
|
|
4
|
+
* The one backend that keeps the conversation itself: `LanguageModel` is a
|
|
5
|
+
* session object, `prompt()` appends to it, and `request.history` is therefore
|
|
6
|
+
* read by nobody here. Two more things it owns that the others do not — it
|
|
7
|
+
* fires its own `contextoverflow`, which is forwarded rather than re-derived,
|
|
8
|
+
* and it reports usage against a window it decides.
|
|
9
|
+
*
|
|
10
|
+
* It lives in a page and nowhere else. In node the global is missing, and
|
|
11
|
+
* `access` answers `unavailable` with `unsupported` rather than throwing.
|
|
12
|
+
*
|
|
13
|
+
* The declarations are `@types/dom-chromium-ai`, patched under `patches/` to
|
|
14
|
+
* close the gap between the IDL and the spec: several states the algorithm
|
|
15
|
+
* rejects at runtime are writable in the IDL, and a TS error at the keyboard
|
|
16
|
+
* beats a `TypeError` in the browser. They are named in function bodies and
|
|
17
|
+
* nowhere in a signature that leaves this file: an exported type naming
|
|
18
|
+
* `LanguageModel` puts the same name in the emitted `.d.ts`, and a consumer
|
|
19
|
+
* who has not installed those declarations, or who lists `types` explicitly,
|
|
20
|
+
* then cannot compile this package at all. `src/surface.ts` is the guard that
|
|
21
|
+
* keeps it that way, and the WebGPU backend next door is where the lesson
|
|
22
|
+
* was learned.
|
|
23
|
+
*/
|
|
24
|
+
import { AiError, contextUsage, createProvider, err, failureFromError, fraction, ok, runTool, tokens, } from "modelpact/backend";
|
|
25
|
+
/**
|
|
26
|
+
* The page's own class, or nothing.
|
|
27
|
+
*
|
|
28
|
+
* There is no way to hand one in. A polyfill is already this — it defines the
|
|
29
|
+
* global — and a test sets the global too, which is the same path the browser
|
|
30
|
+
* takes rather than one beside it.
|
|
31
|
+
*
|
|
32
|
+
* `typeof` and not a bare read: outside a page the name is not declared, and
|
|
33
|
+
* naming it there throws rather than answering undefined.
|
|
34
|
+
*/
|
|
35
|
+
const getPlatformApi = () => typeof LanguageModel === "undefined" ? null : LanguageModel;
|
|
36
|
+
const toExpectations = (askedExpectations) => {
|
|
37
|
+
if (askedExpectations === undefined || askedExpectations.length === 0)
|
|
38
|
+
return undefined;
|
|
39
|
+
return askedExpectations.map((expectation) => ({
|
|
40
|
+
type: expectation.type,
|
|
41
|
+
...(expectation.languages === undefined
|
|
42
|
+
? {}
|
|
43
|
+
: { languages: [...expectation.languages] }),
|
|
44
|
+
}));
|
|
45
|
+
};
|
|
46
|
+
const NEVER_ABORTED = new AbortController().signal;
|
|
47
|
+
const makeToolTurn = () => ({ signal: NEVER_ABORTED, failure: null });
|
|
48
|
+
const asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value)
|
|
49
|
+
? value
|
|
50
|
+
: null;
|
|
51
|
+
/**
|
|
52
|
+
* The browser hands `execute` the model's arguments and nothing else. One
|
|
53
|
+
* plain object, which is what Chrome's examples pass, goes through as the
|
|
54
|
+
* input; anything else is kept whole under `arguments`. Unverified against a
|
|
55
|
+
* running call: no Chrome to date has created a session with tools here —
|
|
56
|
+
* 152 refuses at `create`, measured.
|
|
57
|
+
*/
|
|
58
|
+
const toToolInput = (args) => asRecord(args[0]) ?? { arguments: [...args] };
|
|
59
|
+
const toPlatformTools = (tools, turn) => tools.map((tool) => ({
|
|
60
|
+
name: tool.name,
|
|
61
|
+
description: tool.description,
|
|
62
|
+
inputSchema: tool.inputSchema,
|
|
63
|
+
execute: async (...args) => {
|
|
64
|
+
const toolResult = await runTool(tool, toToolInput(args), turn.signal);
|
|
65
|
+
if (toolResult.ok)
|
|
66
|
+
return toolResult.value;
|
|
67
|
+
// Remembered as well as thrown: whatever the browser does with a
|
|
68
|
+
// rejected execute, the turn ends with this failure.
|
|
69
|
+
turn.failure = toolResult.error;
|
|
70
|
+
throw new AiError(toolResult.error);
|
|
71
|
+
},
|
|
72
|
+
}));
|
|
73
|
+
/** Chrome 152 refuses tools unless a tool-call output is expected; the spec text does not say so (measured). */
|
|
74
|
+
const withToolCallOutput = (outputs, hasTools) => hasTools ? [...(outputs ?? []), { type: "tool-call" }] : outputs;
|
|
75
|
+
const toCoreOptions = (request, turn) => {
|
|
76
|
+
const tools = request.tools ?? [];
|
|
77
|
+
const inputs = toExpectations(request.inputs);
|
|
78
|
+
const outputs = withToolCallOutput(toExpectations(request.outputs), tools.length > 0);
|
|
79
|
+
return {
|
|
80
|
+
...(inputs === undefined ? {} : { expectedInputs: inputs }),
|
|
81
|
+
...(outputs === undefined ? {} : { expectedOutputs: outputs }),
|
|
82
|
+
...(tools.length === 0 ? {} : { tools: toPlatformTools(tools, turn) }),
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* The four the spec has, against the three the contract has. `downloading` and
|
|
87
|
+
* `downloadable` are one branch here and differ only in `started`, which is the
|
|
88
|
+
* question a caller actually has: is someone already fetching this.
|
|
89
|
+
*/
|
|
90
|
+
const toAvailability = (specAvailability) => {
|
|
91
|
+
switch (specAvailability) {
|
|
92
|
+
case "available":
|
|
93
|
+
return { kind: "ready" };
|
|
94
|
+
case "downloadable":
|
|
95
|
+
return { kind: "needs-download", started: false };
|
|
96
|
+
case "downloading":
|
|
97
|
+
return { kind: "needs-download", started: true };
|
|
98
|
+
case "unavailable":
|
|
99
|
+
// Empty lists: the spec says no for the whole request without naming a
|
|
100
|
+
// part, and inventing one would be worse than saying nothing.
|
|
101
|
+
return {
|
|
102
|
+
kind: "unavailable",
|
|
103
|
+
reason: { kind: "unsupported-config", languages: [] },
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const getAvailability = async (request) => {
|
|
108
|
+
const api = getPlatformApi();
|
|
109
|
+
if (api === null)
|
|
110
|
+
return { kind: "unavailable", reason: { kind: "unsupported" } };
|
|
111
|
+
try {
|
|
112
|
+
const specAvailability = await api.availability(toCoreOptions(request, makeToolTurn()));
|
|
113
|
+
return toAvailability(specAvailability);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
// `NotAllowedError` for the permissions policy, `InvalidStateError` for a
|
|
117
|
+
// document that is not fully active: both are refusals, not crashes.
|
|
118
|
+
return { kind: "unavailable", reason: failureFromError(error) };
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
/** System first or not at all, which is what the patched tuple type says too. */
|
|
122
|
+
const toInitialPrompts = (session) => {
|
|
123
|
+
const historyPrompts = (session.history ?? []).map((message) => ({ role: message.role, content: message.content }));
|
|
124
|
+
if (session.system === undefined)
|
|
125
|
+
return historyPrompts.length === 0 ? undefined : historyPrompts;
|
|
126
|
+
return [{ role: "system", content: session.system }, ...historyPrompts];
|
|
127
|
+
};
|
|
128
|
+
const toPromptOptions = (request) => request.schema === undefined
|
|
129
|
+
? { signal: request.signal }
|
|
130
|
+
: { signal: request.signal, responseConstraint: request.schema };
|
|
131
|
+
/**
|
|
132
|
+
* `contextUsage` and `contextWindow` replaced `inputUsage` and `inputQuota`,
|
|
133
|
+
* and a browser in the field may have either pair. Read as unknown because the
|
|
134
|
+
* declarations promise the new names on a version that has only the old.
|
|
135
|
+
*/
|
|
136
|
+
const readUsage = (session) => {
|
|
137
|
+
const sessionFields = session;
|
|
138
|
+
const used = readNumber(sessionFields, "contextUsage", "inputUsage");
|
|
139
|
+
const windowTokens = readNumber(sessionFields, "contextWindow", "inputQuota");
|
|
140
|
+
if (used === null || windowTokens === null)
|
|
141
|
+
return { kind: "unknown" };
|
|
142
|
+
const usedTokens = tokens(used);
|
|
143
|
+
return usedTokens === null
|
|
144
|
+
? { kind: "unknown" }
|
|
145
|
+
: contextUsage(usedTokens, windowTokens);
|
|
146
|
+
};
|
|
147
|
+
const readNumber = (sessionFields, currentName, olderName) => {
|
|
148
|
+
const value = sessionFields[currentName] ?? sessionFields[olderName];
|
|
149
|
+
return typeof value === "number" ? value : null;
|
|
150
|
+
};
|
|
151
|
+
class PromptApiModel {
|
|
152
|
+
#session;
|
|
153
|
+
#turn;
|
|
154
|
+
constructor(session, turn) {
|
|
155
|
+
this.#session = session;
|
|
156
|
+
this.#turn = turn;
|
|
157
|
+
}
|
|
158
|
+
generateStream = (input, request) => {
|
|
159
|
+
try {
|
|
160
|
+
this.#beginTurn(request);
|
|
161
|
+
const deltas = this.#session.promptStreaming(input, toPromptOptions(request));
|
|
162
|
+
return Promise.resolve(ok(deltas.pipeThrough(this.#failIfToolFailed())));
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
return Promise.resolve(err(this.#refineFailure(error)));
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
generateWhole = async (input, request) => {
|
|
169
|
+
try {
|
|
170
|
+
this.#beginTurn(request);
|
|
171
|
+
const answerText = await this.#session.prompt(input, toPromptOptions(request));
|
|
172
|
+
const toolFailure = this.#turn.failure;
|
|
173
|
+
return toolFailure === null ? ok(answerText) : err(toolFailure);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
return err(this.#refineFailure(error));
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
usage = () => readUsage(this.#session);
|
|
180
|
+
dispose = () => {
|
|
181
|
+
this.#session.destroy();
|
|
182
|
+
};
|
|
183
|
+
#beginTurn(request) {
|
|
184
|
+
this.#turn.signal = request.signal;
|
|
185
|
+
this.#turn.failure = null;
|
|
186
|
+
}
|
|
187
|
+
/** A stream that ended is not an answer if a tool failed on the way: the end is where that is checked. */
|
|
188
|
+
#failIfToolFailed() {
|
|
189
|
+
return new TransformStream({
|
|
190
|
+
flush: () => {
|
|
191
|
+
const toolFailure = this.#turn.failure;
|
|
192
|
+
if (toolFailure !== null)
|
|
193
|
+
throw new AiError(toolFailure);
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* `QuotaExceededError` carries no measurement, and the generic mapping says
|
|
199
|
+
* so; holding the session is what lets this one attach the reading. A tool
|
|
200
|
+
* failure outranks whatever the browser threw about it.
|
|
201
|
+
*/
|
|
202
|
+
#refineFailure(error) {
|
|
203
|
+
const toolFailure = this.#turn.failure;
|
|
204
|
+
if (toolFailure !== null)
|
|
205
|
+
return toolFailure;
|
|
206
|
+
const failure = failureFromError(error);
|
|
207
|
+
return failure.kind === "context-overflow"
|
|
208
|
+
? { ...failure, usage: this.usage() }
|
|
209
|
+
: failure;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const connectPromptApi = async (options) => {
|
|
213
|
+
const api = getPlatformApi();
|
|
214
|
+
if (api === null)
|
|
215
|
+
return err({ kind: "unsupported" });
|
|
216
|
+
const initialPrompts = toInitialPrompts(options.session);
|
|
217
|
+
const turn = makeToolTurn();
|
|
218
|
+
try {
|
|
219
|
+
const session = await api.create({
|
|
220
|
+
...toCoreOptions(options.request, turn),
|
|
221
|
+
...(initialPrompts === undefined ? {} : { initialPrompts }),
|
|
222
|
+
...(options.session.signal === undefined
|
|
223
|
+
? {}
|
|
224
|
+
: { signal: options.session.signal }),
|
|
225
|
+
// Translated rather than forwarded, though the browser's monitor would
|
|
226
|
+
// satisfy `DownloadMonitor` as it stands: going through the lifecycle's
|
|
227
|
+
// own is what applies the never-decreasing rule to every backend alike.
|
|
228
|
+
monitor: (monitor) => {
|
|
229
|
+
monitor.ondownloadprogress = (event) => {
|
|
230
|
+
const progress = fraction(event.total === 0 ? 0 : event.loaded / event.total);
|
|
231
|
+
if (progress !== null)
|
|
232
|
+
options.reportProgress(progress);
|
|
233
|
+
};
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
// Forwarded, not re-derived: by the time a listener reads the counters the
|
|
237
|
+
// dropped turns are gone, and `used` can be back under the window.
|
|
238
|
+
session.oncontextoverflow = () => {
|
|
239
|
+
options.reportOverflow();
|
|
240
|
+
};
|
|
241
|
+
return ok(new PromptApiModel(session, turn));
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
return err(failureFromError(error));
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
export function makePromptApiProvider() {
|
|
248
|
+
const backend = {
|
|
249
|
+
name: "prompt-api",
|
|
250
|
+
// Text only, because that is all `AiMessage` carries. The model takes more,
|
|
251
|
+
// and asking for it would promise a message this contract cannot build.
|
|
252
|
+
modalities: ["text"],
|
|
253
|
+
// The platform executes them itself; whether a given Chrome will open such
|
|
254
|
+
// a session is its `create` to refuse, and that refusal is what `open` returns.
|
|
255
|
+
tools: true,
|
|
256
|
+
availability: (request) => getAvailability(request),
|
|
257
|
+
connect: (options) => connectPromptApi(options),
|
|
258
|
+
};
|
|
259
|
+
return createProvider(backend);
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=prompt-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-api.js","sourceRoot":"","sources":["../src/prompt-api.ts"],"names":[],"mappings":"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"}
|
package/dist/webgpu.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A model in the tab itself, on WebGPU, through `@mlc-ai/web-llm`.
|
|
3
|
+
*
|
|
4
|
+
* `modelpact/backend` is the whole of what it reaches for, so if a backend
|
|
5
|
+
* cannot be built from what that door exports, this file is where it shows.
|
|
6
|
+
* It was written before this package existed, in a directory one repository
|
|
7
|
+
* over, to ask exactly that — and it needed nothing added to the contract.
|
|
8
|
+
*
|
|
9
|
+
* What the engine brings that the other backends do not: the download is the
|
|
10
|
+
* page's own, several hundred megabytes into browser storage, and it is the
|
|
11
|
+
* only one where `needs-download` costs the user their bandwidth rather than a
|
|
12
|
+
* daemon's.
|
|
13
|
+
*/
|
|
14
|
+
import { type AiProvider } from "modelpact/backend";
|
|
15
|
+
/** A delta as the engine sends it; these are the only fields read. */
|
|
16
|
+
export interface EngineChunk {
|
|
17
|
+
readonly choices: readonly {
|
|
18
|
+
readonly delta: {
|
|
19
|
+
readonly content?: string | null;
|
|
20
|
+
};
|
|
21
|
+
}[];
|
|
22
|
+
readonly usage?: {
|
|
23
|
+
readonly total_tokens: number;
|
|
24
|
+
} | null;
|
|
25
|
+
}
|
|
26
|
+
export interface EngineRequest {
|
|
27
|
+
readonly model: string;
|
|
28
|
+
readonly messages: readonly {
|
|
29
|
+
readonly role: "system" | "user" | "assistant";
|
|
30
|
+
readonly content: string;
|
|
31
|
+
}[];
|
|
32
|
+
readonly stream: true;
|
|
33
|
+
readonly stream_options: {
|
|
34
|
+
readonly include_usage: true;
|
|
35
|
+
};
|
|
36
|
+
readonly response_format?: {
|
|
37
|
+
readonly type: "json_object";
|
|
38
|
+
readonly schema: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* What this backend uses of the engine, which is less than the engine has.
|
|
43
|
+
*
|
|
44
|
+
* Named here and not imported, so that the emitted declarations name nothing
|
|
45
|
+
* from `@mlc-ai/web-llm`. Its own `.d.ts` files reference packages it does not
|
|
46
|
+
* depend on, and a consumer with `skipLibCheck` off inherits that break through
|
|
47
|
+
* a type they never asked for — the demo did, the moment it started resolving
|
|
48
|
+
* this package the way npm would. The Prompt API backend next door keeps
|
|
49
|
+
* `LanguageModel` out of its own declarations for the same reason.
|
|
50
|
+
*
|
|
51
|
+
* Method syntax on purpose: it is bivariant, which is what lets the real
|
|
52
|
+
* engine, with its overloads and wider request type, satisfy this narrower one
|
|
53
|
+
* without restating all of it.
|
|
54
|
+
*/
|
|
55
|
+
export interface WebGpuEngine {
|
|
56
|
+
readonly chat: {
|
|
57
|
+
readonly completions: {
|
|
58
|
+
create(request: EngineRequest): Promise<AsyncIterable<EngineChunk>>;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
unload(): Promise<void>;
|
|
62
|
+
interruptGenerate(): void;
|
|
63
|
+
}
|
|
64
|
+
export interface WebGpuConfig {
|
|
65
|
+
/** A tag from the engine's own catalogue, such as `SmolLM2-360M-Instruct-q4f16_1-MLC`. */
|
|
66
|
+
readonly model: string;
|
|
67
|
+
/** Tokens the session is measured against; the engine reports no window of its own. */
|
|
68
|
+
readonly contextWindow?: number;
|
|
69
|
+
/** For a test: an engine already built, instead of one loaded from the network. */
|
|
70
|
+
readonly engine?: (reportProgress: (progress: number) => void) => Promise<WebGpuEngine>;
|
|
71
|
+
}
|
|
72
|
+
export declare function makeWebGpuProvider(config: WebGpuConfig): AiProvider;
|
|
73
|
+
//# sourceMappingURL=webgpu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webgpu.d.ts","sourceRoot":"","sources":["../src/webgpu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EASL,KAAK,UAAU,EAQhB,MAAM,mBAAmB,CAAC;AAG3B,sEAAsE;AACtE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS;QACzB,QAAQ,CAAC,KAAK,EAAE;YAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,CAAC;KACtD,EAAE,CAAC;IACJ,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC3D;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS;QAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;QAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;KAC1B,EAAE,CAAC;IACJ,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE;QAAE,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAA;KAAE,CAAC;IAC1D,QAAQ,CAAC,eAAe,CAAC,EAAE;QACzB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;QAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE;QACb,QAAQ,CAAC,WAAW,EAAE;YACpB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;SACrE,CAAC;KACH,CAAC;IACF,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,iBAAiB,IAAI,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,0FAA0F;IAC1F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uFAAuF;IACvF,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,mFAAmF;IACnF,QAAQ,CAAC,MAAM,CAAC,EAAE,CAChB,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,KACvC,OAAO,CAAC,YAAY,CAAC,CAAC;CAC5B;AAyND,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,UAAU,CAQnE"}
|
package/dist/webgpu.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A model in the tab itself, on WebGPU, through `@mlc-ai/web-llm`.
|
|
3
|
+
*
|
|
4
|
+
* `modelpact/backend` is the whole of what it reaches for, so if a backend
|
|
5
|
+
* cannot be built from what that door exports, this file is where it shows.
|
|
6
|
+
* It was written before this package existed, in a directory one repository
|
|
7
|
+
* over, to ask exactly that — and it needed nothing added to the contract.
|
|
8
|
+
*
|
|
9
|
+
* What the engine brings that the other backends do not: the download is the
|
|
10
|
+
* page's own, several hundred megabytes into browser storage, and it is the
|
|
11
|
+
* only one where `needs-download` costs the user their bandwidth rather than a
|
|
12
|
+
* daemon's.
|
|
13
|
+
*/
|
|
14
|
+
import { contextUsage, createProvider, err, failureFromError, fraction, ok, tokens, } from "modelpact/backend";
|
|
15
|
+
import { CreateMLCEngine } from "@mlc-ai/web-llm";
|
|
16
|
+
const DEFAULTS = { contextWindow: 4096 };
|
|
17
|
+
const ZERO_TOKENS = tokens(0) ?? 0;
|
|
18
|
+
/** WebGPU or nothing: the engine has a WASM path, but not one worth offering. */
|
|
19
|
+
const hasWebGpu = () => typeof navigator !== "undefined" && "gpu" in navigator;
|
|
20
|
+
/**
|
|
21
|
+
* Weights live in the browser's cache, so "downloaded" is a question about
|
|
22
|
+
* this profile rather than about the machine. The engine exposes no such
|
|
23
|
+
* check, so the cache is asked directly.
|
|
24
|
+
*/
|
|
25
|
+
const isCached = async (model) => {
|
|
26
|
+
if (typeof caches === "undefined")
|
|
27
|
+
return false;
|
|
28
|
+
try {
|
|
29
|
+
const cacheNames = await caches.keys();
|
|
30
|
+
for (const cacheName of cacheNames) {
|
|
31
|
+
const cache = await caches.open(cacheName);
|
|
32
|
+
const cachedRequests = await cache.keys();
|
|
33
|
+
if (cachedRequests.some((request) => request.url.includes(model)))
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const getAvailability = async (config) => {
|
|
43
|
+
if (config.engine !== undefined)
|
|
44
|
+
return { kind: "ready" };
|
|
45
|
+
if (!hasWebGpu()) {
|
|
46
|
+
return {
|
|
47
|
+
kind: "unavailable",
|
|
48
|
+
reason: { kind: "unsupported-config", languages: [], modalities: [] },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const isModelCached = await isCached(config.model);
|
|
52
|
+
return isModelCached
|
|
53
|
+
? { kind: "ready" }
|
|
54
|
+
: { kind: "needs-download", started: false };
|
|
55
|
+
};
|
|
56
|
+
/** The engine reports a stage and a percentage; only the number is ours to pass on. */
|
|
57
|
+
const readProgress = (initReport) => typeof initReport.progress === "number" ? initReport.progress : 0;
|
|
58
|
+
class WebGpuConnection {
|
|
59
|
+
#engine;
|
|
60
|
+
#model;
|
|
61
|
+
#contextWindow;
|
|
62
|
+
#system;
|
|
63
|
+
#usedTokens = 0;
|
|
64
|
+
constructor(engine, config, options) {
|
|
65
|
+
this.#engine = engine;
|
|
66
|
+
this.#model = config.model;
|
|
67
|
+
this.#contextWindow = config.contextWindow ?? DEFAULTS.contextWindow;
|
|
68
|
+
this.#system = options.session.system;
|
|
69
|
+
}
|
|
70
|
+
generateStream = async (input, request) => {
|
|
71
|
+
try {
|
|
72
|
+
const chunks = await this.#engine.chat.completions.create({
|
|
73
|
+
model: this.#model,
|
|
74
|
+
messages: this.#toEngineMessages(input, request),
|
|
75
|
+
stream: true,
|
|
76
|
+
stream_options: { include_usage: true },
|
|
77
|
+
...(request.schema === undefined
|
|
78
|
+
? {}
|
|
79
|
+
: {
|
|
80
|
+
response_format: {
|
|
81
|
+
type: "json_object",
|
|
82
|
+
schema: JSON.stringify(request.schema),
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
return ok(this.#toDeltaStream(chunks));
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
return err(failureFromError(error));
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
usage = () => contextUsage(tokens(this.#usedTokens) ?? ZERO_TOKENS, this.#contextWindow);
|
|
93
|
+
dispose = () => {
|
|
94
|
+
void this.#engine.unload();
|
|
95
|
+
};
|
|
96
|
+
#toEngineMessages(input, request) {
|
|
97
|
+
const historyMessages = request.history.map((message) => ({
|
|
98
|
+
role: message.role,
|
|
99
|
+
content: message.content,
|
|
100
|
+
}));
|
|
101
|
+
const askedMessage = { role: "user", content: input };
|
|
102
|
+
return this.#system === undefined
|
|
103
|
+
? [...historyMessages, askedMessage]
|
|
104
|
+
: [
|
|
105
|
+
{ role: "system", content: this.#system },
|
|
106
|
+
...historyMessages,
|
|
107
|
+
askedMessage,
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* An async iterable into a `ReadableStream`, because that is what the
|
|
112
|
+
* contract carries.
|
|
113
|
+
*
|
|
114
|
+
* The abort is deliberately not policed here. The lifecycle checks the signal
|
|
115
|
+
* around every read and errors the stream itself, in the vocabulary; a
|
|
116
|
+
* backend that raced it to the punch with a plain `Error` would land in
|
|
117
|
+
* `unknown` instead of `aborted`, which is what the conformance suite caught
|
|
118
|
+
* when this file did exactly that.
|
|
119
|
+
*
|
|
120
|
+
* What is left to do is tell the engine, since it takes no signal of its own:
|
|
121
|
+
* `cancel` reaches every way out — the reader stopping, the abort, the close.
|
|
122
|
+
*/
|
|
123
|
+
#toDeltaStream(chunks) {
|
|
124
|
+
const iterator = chunks[Symbol.asyncIterator]();
|
|
125
|
+
return new ReadableStream({
|
|
126
|
+
// Loops until it can enqueue or close: a pull that returns empty-handed
|
|
127
|
+
// is not called again unless a read arrived meanwhile, and the engine
|
|
128
|
+
// sends chunks with no text — the usage one, a stop one — back to back.
|
|
129
|
+
// Found in the sibling package on `claude -p`, fixed here before it bit.
|
|
130
|
+
pull: async (controller) => {
|
|
131
|
+
for (;;) {
|
|
132
|
+
const iteration = await iterator.next();
|
|
133
|
+
if (iteration.done === true) {
|
|
134
|
+
controller.close();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const chunk = iteration.value;
|
|
138
|
+
if (chunk.usage != null)
|
|
139
|
+
this.#usedTokens = chunk.usage.total_tokens;
|
|
140
|
+
const delta = chunk.choices[0]?.delta.content ?? "";
|
|
141
|
+
if (delta !== "") {
|
|
142
|
+
controller.enqueue(delta);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
cancel: async () => {
|
|
148
|
+
this.#engine.interruptGenerate();
|
|
149
|
+
await iterator.return?.();
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The real engine behind `WebGpuEngine`. An adapter and not a cast: the real
|
|
156
|
+
* `create` is an overload set, and an overload set is not assignable to a
|
|
157
|
+
* single signature even when one member fits — so each call is written out,
|
|
158
|
+
* and the compiler resolves the streaming overload from `stream: true` and
|
|
159
|
+
* checks our request shape against it. Nothing here is asserted.
|
|
160
|
+
*/
|
|
161
|
+
const loadEngine = async (model, reportProgress) => {
|
|
162
|
+
const realEngine = await CreateMLCEngine(model, {
|
|
163
|
+
initProgressCallback: (initReport) => {
|
|
164
|
+
reportProgress(readProgress(initReport));
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
return {
|
|
168
|
+
chat: {
|
|
169
|
+
completions: {
|
|
170
|
+
// A fresh array: ours is `readonly`, which is right for a request
|
|
171
|
+
// nobody should edit, and the engine's signature wants a mutable one.
|
|
172
|
+
create: (request) => realEngine.chat.completions.create({
|
|
173
|
+
...request,
|
|
174
|
+
messages: [...request.messages],
|
|
175
|
+
}),
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
unload: () => realEngine.unload(),
|
|
179
|
+
interruptGenerate: () => {
|
|
180
|
+
// The engine's own is a promise, though its published interface says
|
|
181
|
+
// otherwise; dropped on purpose, and marked rather than left floating.
|
|
182
|
+
// By the time this runs the lifecycle has already errored the stream as
|
|
183
|
+
// `aborted`, so waiting for the engine to acknowledge would hold a turn
|
|
184
|
+
// whose caller has been answered.
|
|
185
|
+
void realEngine.interruptGenerate();
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
const connectEngine = async (config, options) => {
|
|
190
|
+
const reportProgress = (progress) => {
|
|
191
|
+
const progressFraction = fraction(progress);
|
|
192
|
+
if (progressFraction !== null)
|
|
193
|
+
options.reportProgress(progressFraction);
|
|
194
|
+
};
|
|
195
|
+
try {
|
|
196
|
+
const engine = config.engine === undefined
|
|
197
|
+
? await loadEngine(config.model, reportProgress)
|
|
198
|
+
: await config.engine(reportProgress);
|
|
199
|
+
return ok(new WebGpuConnection(engine, config, options));
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
return err(failureFromError(error));
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
export function makeWebGpuProvider(config) {
|
|
206
|
+
const backend = {
|
|
207
|
+
name: "webgpu",
|
|
208
|
+
modalities: ["text"],
|
|
209
|
+
availability: () => getAvailability(config),
|
|
210
|
+
connect: (options) => connectEngine(config, options),
|
|
211
|
+
};
|
|
212
|
+
return createProvider(backend);
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=webgpu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webgpu.js","sourceRoot":"","sources":["../src/webgpu.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "modelpact-providers",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Language-model backends written against the modelpact contract — one
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Language-model backends written against the modelpact contract — a daemon, the OpenAI dialect, the browser's own model, one in the tab, each green on the contract suite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm",
|
|
7
7
|
"modelpact",
|
|
8
8
|
"typescript",
|
|
9
9
|
"ollama",
|
|
10
|
+
"openai",
|
|
11
|
+
"openai-compatible",
|
|
10
12
|
"prompt-api",
|
|
11
13
|
"webgpu",
|
|
12
14
|
"on-device",
|
|
@@ -28,38 +30,60 @@
|
|
|
28
30
|
".": {
|
|
29
31
|
"types": "./dist/index.d.ts",
|
|
30
32
|
"import": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./webgpu": {
|
|
35
|
+
"types": "./dist/webgpu.d.ts",
|
|
36
|
+
"import": "./dist/webgpu.js"
|
|
31
37
|
}
|
|
32
38
|
},
|
|
33
39
|
"files": [
|
|
34
40
|
"dist"
|
|
35
41
|
],
|
|
42
|
+
"workspaces": [
|
|
43
|
+
"demo"
|
|
44
|
+
],
|
|
36
45
|
"sideEffects": false,
|
|
37
46
|
"engines": {
|
|
38
47
|
"node": ">=22"
|
|
39
48
|
},
|
|
40
49
|
"scripts": {
|
|
50
|
+
"prepare": "patch-package",
|
|
41
51
|
"build": "tsc -p tsconfig.build.json",
|
|
42
52
|
"test": "vitest run",
|
|
43
53
|
"test:watch": "vitest",
|
|
54
|
+
"test:e2e": "playwright test",
|
|
55
|
+
"demo": "npm --prefix demo run dev",
|
|
56
|
+
"demo:check": "npm --prefix demo run typecheck",
|
|
44
57
|
"lint": "eslint .",
|
|
45
58
|
"lint:fix": "eslint . --fix",
|
|
46
59
|
"format": "prettier --write .",
|
|
47
60
|
"format:check": "prettier --check .",
|
|
48
|
-
"typecheck": "tsc --noEmit",
|
|
61
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.patched.json && tsc -p e2e --noEmit",
|
|
62
|
+
"check:surface": "npm run build && tsc -p tsconfig.surface.json",
|
|
49
63
|
"prepublishOnly": "npm run build"
|
|
50
64
|
},
|
|
51
65
|
"peerDependencies": {
|
|
52
|
-
"modelpact": ">=2"
|
|
66
|
+
"modelpact": ">=2.2",
|
|
67
|
+
"@mlc-ai/web-llm": ">=0.2"
|
|
53
68
|
},
|
|
54
69
|
"devDependencies": {
|
|
55
70
|
"@eslint/js": "^10.0.1",
|
|
71
|
+
"@playwright/test": "^1.62.1",
|
|
72
|
+
"@mlc-ai/web-llm": "^0.2.84",
|
|
73
|
+
"@types/dom-chromium-ai": "^0.0.17",
|
|
56
74
|
"@types/node": "^26.4.1",
|
|
57
75
|
"eslint": "^10.9.1",
|
|
58
76
|
"eslint-config-prettier": "^10.1.8",
|
|
59
|
-
"modelpact": "^2.2.
|
|
77
|
+
"modelpact": "^2.2.1",
|
|
78
|
+
"patch-package": "^8.0.1",
|
|
60
79
|
"prettier": "^3.9.6",
|
|
61
80
|
"typescript": "~6.0.3",
|
|
62
81
|
"typescript-eslint": "^8.69.0",
|
|
63
82
|
"vitest": "^4.1.11"
|
|
83
|
+
},
|
|
84
|
+
"peerDependenciesMeta": {
|
|
85
|
+
"@mlc-ai/web-llm": {
|
|
86
|
+
"optional": true
|
|
87
|
+
}
|
|
64
88
|
}
|
|
65
89
|
}
|