bios-sdk 0.1.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +292 -0
- package/dist/client.d.ts +57 -0
- package/dist/client.js +181 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +80 -0
- package/dist/resources/datasets.d.ts +180 -0
- package/dist/resources/datasets.js +358 -0
- package/dist/resources/gpu-priorities.d.ts +14 -0
- package/dist/resources/gpu-priorities.js +54 -0
- package/dist/resources/gpu.d.ts +60 -0
- package/dist/resources/gpu.js +101 -0
- package/dist/resources/inference.d.ts +82 -0
- package/dist/resources/inference.js +529 -0
- package/dist/resources/models.d.ts +75 -0
- package/dist/resources/models.js +115 -0
- package/dist/resources/training.d.ts +146 -0
- package/dist/resources/training.js +399 -0
- package/dist/resources/wallet.d.ts +44 -0
- package/dist/resources/wallet.js +52 -0
- package/dist/types.d.ts +1466 -0
- package/dist/types.js +4 -0
- package/package.json +49 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import { ApiError } from '../client.js';
|
|
2
|
+
import { normalizeGPUPlacement } from './gpu-priorities.js';
|
|
3
|
+
const FUNCTION_NAME = /^[A-Za-z0-9_-]{1,64}$/;
|
|
4
|
+
const ROLES = new Set(['system', 'developer', 'user', 'assistant', 'tool', 'function']);
|
|
5
|
+
const INFERENCE_IDEMPOTENCY_KEY = /^[A-Za-z0-9._:-]{8,128}$/;
|
|
6
|
+
const INFERENCE_TOOL_CALL_PARSERS = new Set([
|
|
7
|
+
'deepseekv3', 'deepseekv31', 'deepseekv32',
|
|
8
|
+
'glm', 'glm45', 'glm47', 'gpt-oss', 'kimi_k2',
|
|
9
|
+
'lfm2', 'llama3', 'mimo', 'mistral',
|
|
10
|
+
'omega17', 'omega17_exp', 'omega17_vl_exp', 'pythonic',
|
|
11
|
+
'qwen', 'qwen25', 'qwen3_coder', 'step3', 'step3p5',
|
|
12
|
+
'minimax-m2', 'trinity', 'interns1', 'hermes', 'gigachat3',
|
|
13
|
+
'usf_omega', 'usf_milli', 'usf_mini',
|
|
14
|
+
]);
|
|
15
|
+
function inferenceIdempotencyKey(value) {
|
|
16
|
+
const key = value?.trim();
|
|
17
|
+
if (!INFERENCE_IDEMPOTENCY_KEY.test(key)) {
|
|
18
|
+
throw new Error("BiOS: idempotencyKey is required and must be 8-128 characters using letters, numbers, '.', '_', ':', or '-'");
|
|
19
|
+
}
|
|
20
|
+
return key;
|
|
21
|
+
}
|
|
22
|
+
function buildInferenceRequest(params) {
|
|
23
|
+
const queueEnabled = params.allowCapacityQueue ?? false;
|
|
24
|
+
const placement = normalizeGPUPlacement(params.gpuPriorities, queueEnabled, params.gpuType, params.gpuCount, 'allowCapacityQueue');
|
|
25
|
+
if (!params.name?.trim())
|
|
26
|
+
throw new Error('BiOS: deployment name is required');
|
|
27
|
+
if (!placement.gpuType)
|
|
28
|
+
throw new Error('BiOS: gpuType is required');
|
|
29
|
+
if (!Number.isInteger(placement.gpuCount) || placement.gpuCount < 1 || placement.gpuCount > 8) {
|
|
30
|
+
throw new Error('BiOS: gpuCount must be an integer between 1 and 8');
|
|
31
|
+
}
|
|
32
|
+
if (params.sourceType === 'checkpoint') {
|
|
33
|
+
if (!params.sourceJobId || !params.sourceCheckpointId) {
|
|
34
|
+
throw new Error('BiOS: checkpoint deployments require sourceJobId and sourceCheckpointId');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (params.sourceType === 'hf_model') {
|
|
38
|
+
if (!params.hfModelId)
|
|
39
|
+
throw new Error('BiOS: hf_model deployments require hfModelId');
|
|
40
|
+
if (params.servingMode !== undefined && params.servingMode !== 'full') {
|
|
41
|
+
throw new Error('BiOS: hf_model deployments use servingMode=full; checkpoint modes are derived from the verified artifact');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
throw new Error('BiOS: sourceType must be checkpoint or hf_model');
|
|
46
|
+
}
|
|
47
|
+
if (params.servingMode !== undefined && !['full', 'adapter', 'merged'].includes(params.servingMode)) {
|
|
48
|
+
throw new Error('BiOS: servingMode must be full, adapter, or merged');
|
|
49
|
+
}
|
|
50
|
+
if (params.gpuTier !== undefined && params.gpuTier !== 'secure') {
|
|
51
|
+
throw new Error('BiOS: gpuTier must be secure; other deployment capacity tiers are not supported');
|
|
52
|
+
}
|
|
53
|
+
const requestedParser = params.servingConfig?.tool_call_parser;
|
|
54
|
+
if (requestedParser !== undefined && !INFERENCE_TOOL_CALL_PARSERS.has(requestedParser)) {
|
|
55
|
+
throw new Error('BiOS: servingConfig.tool_call_parser must name a parser supported by the deployed SGLang runtime');
|
|
56
|
+
}
|
|
57
|
+
const body = {
|
|
58
|
+
name: params.name.trim(),
|
|
59
|
+
source_type: params.sourceType,
|
|
60
|
+
gpu_type: placement.gpuType,
|
|
61
|
+
gpu_count: placement.gpuCount,
|
|
62
|
+
gpu_tier: params.gpuTier || 'secure',
|
|
63
|
+
allow_capacity_queue: queueEnabled,
|
|
64
|
+
};
|
|
65
|
+
if (placement.priorities !== undefined)
|
|
66
|
+
body.gpu_priorities = placement.priorities;
|
|
67
|
+
if (params.sourceJobId !== undefined)
|
|
68
|
+
body.source_job_id = params.sourceJobId;
|
|
69
|
+
if (params.sourceCheckpointId !== undefined)
|
|
70
|
+
body.source_checkpoint_id = params.sourceCheckpointId;
|
|
71
|
+
if (params.hfModelId !== undefined)
|
|
72
|
+
body.hf_model_id = params.hfModelId;
|
|
73
|
+
if (params.hfModelRevision !== undefined)
|
|
74
|
+
body.hf_model_revision = params.hfModelRevision;
|
|
75
|
+
if (params.hfIntegrationId !== undefined)
|
|
76
|
+
body.hf_integration_id = params.hfIntegrationId;
|
|
77
|
+
if (params.baseModelId !== undefined)
|
|
78
|
+
body.base_model_id = params.baseModelId;
|
|
79
|
+
if (params.baseModelRevision !== undefined)
|
|
80
|
+
body.base_model_revision = params.baseModelRevision;
|
|
81
|
+
if (params.servingMode !== undefined)
|
|
82
|
+
body.serving_mode = params.servingMode;
|
|
83
|
+
if (params.modelTask !== undefined)
|
|
84
|
+
body.model_task = params.modelTask;
|
|
85
|
+
if (params.supportsImages !== undefined)
|
|
86
|
+
body.supports_images = params.supportsImages;
|
|
87
|
+
if (params.storageGb !== undefined)
|
|
88
|
+
body.storage_gb = params.storageGb;
|
|
89
|
+
if (params.contextLength !== undefined)
|
|
90
|
+
body.context_length = params.contextLength;
|
|
91
|
+
if (params.quant !== undefined)
|
|
92
|
+
body.quant = params.quant;
|
|
93
|
+
if (params.servingConfig !== undefined)
|
|
94
|
+
body.serving_config = params.servingConfig;
|
|
95
|
+
if (params.maxPriceHourCents !== undefined)
|
|
96
|
+
body.max_price_hour_cents = params.maxPriceHourCents;
|
|
97
|
+
return body;
|
|
98
|
+
}
|
|
99
|
+
export function validateChatRequest(body) {
|
|
100
|
+
const messages = body.messages;
|
|
101
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
102
|
+
throw new Error('messages must be a non-empty array');
|
|
103
|
+
}
|
|
104
|
+
const tools = body.tools ?? [];
|
|
105
|
+
if (!Array.isArray(tools))
|
|
106
|
+
throw new Error('tools must be an array');
|
|
107
|
+
const names = [];
|
|
108
|
+
for (const rawTool of tools) {
|
|
109
|
+
if (!rawTool || typeof rawTool !== 'object')
|
|
110
|
+
throw new Error('each tool must be an object');
|
|
111
|
+
const tool = rawTool;
|
|
112
|
+
if ((tool.type ?? 'function') !== 'function')
|
|
113
|
+
throw new Error('each tool must have type="function"');
|
|
114
|
+
const fn = tool.function;
|
|
115
|
+
if (!fn || typeof fn !== 'object')
|
|
116
|
+
throw new Error('each tool requires a function object');
|
|
117
|
+
const definition = fn;
|
|
118
|
+
if (typeof definition.name !== 'string' || !FUNCTION_NAME.test(definition.name)) {
|
|
119
|
+
throw new Error('tool function names must be 1-64 safe characters');
|
|
120
|
+
}
|
|
121
|
+
const parameters = definition.parameters;
|
|
122
|
+
if (parameters !== undefined && (!parameters || typeof parameters !== 'object' || Array.isArray(parameters)
|
|
123
|
+
|| (parameters.type ?? 'object') !== 'object')) {
|
|
124
|
+
throw new Error('tool parameters must be a JSON Schema object');
|
|
125
|
+
}
|
|
126
|
+
names.push(definition.name);
|
|
127
|
+
}
|
|
128
|
+
if (new Set(names).size !== names.length)
|
|
129
|
+
throw new Error('tool function names must be unique');
|
|
130
|
+
const choice = body.tool_choice;
|
|
131
|
+
if (choice && typeof choice === 'object') {
|
|
132
|
+
const fn = choice.function;
|
|
133
|
+
const selected = fn && typeof fn === 'object' ? fn.name : undefined;
|
|
134
|
+
if (typeof selected !== 'string' || !names.includes(selected)) {
|
|
135
|
+
throw new Error('tool_choice references an undefined function');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else if ((choice === 'auto' || choice === 'required') && tools.length === 0) {
|
|
139
|
+
throw new Error('tool_choice requires at least one tool');
|
|
140
|
+
}
|
|
141
|
+
const pending = new Set();
|
|
142
|
+
for (const rawMessage of messages) {
|
|
143
|
+
if (!rawMessage || typeof rawMessage !== 'object')
|
|
144
|
+
throw new Error('every message must be an object');
|
|
145
|
+
const message = rawMessage;
|
|
146
|
+
const role = message.role;
|
|
147
|
+
if (typeof role !== 'string' || !ROLES.has(role))
|
|
148
|
+
throw new Error('every message requires a valid role');
|
|
149
|
+
const calls = message.tool_calls;
|
|
150
|
+
if (calls !== undefined && role !== 'assistant')
|
|
151
|
+
throw new Error('tool_calls are only valid on assistant messages');
|
|
152
|
+
if (role === 'assistant' && Array.isArray(calls) && calls.length > 0) {
|
|
153
|
+
if (pending.size > 0)
|
|
154
|
+
throw new Error('tool calls must be resolved before the next turn');
|
|
155
|
+
for (const rawCall of calls) {
|
|
156
|
+
if (!rawCall || typeof rawCall !== 'object')
|
|
157
|
+
throw new Error('assistant tool calls must be objects');
|
|
158
|
+
const call = rawCall;
|
|
159
|
+
const id = call.id;
|
|
160
|
+
const fn = call.function;
|
|
161
|
+
if (typeof id !== 'string' || !id)
|
|
162
|
+
throw new Error('assistant tool calls require a non-empty id');
|
|
163
|
+
if (pending.has(id))
|
|
164
|
+
throw new Error('assistant tool call ids must be unique');
|
|
165
|
+
if (!fn || typeof fn !== 'object')
|
|
166
|
+
throw new Error('assistant tool calls require a function');
|
|
167
|
+
const functionCall = fn;
|
|
168
|
+
if (tools.length > 0 && !names.includes(String(functionCall.name ?? ''))) {
|
|
169
|
+
throw new Error('assistant tool call references an undefined function');
|
|
170
|
+
}
|
|
171
|
+
let args = functionCall.arguments;
|
|
172
|
+
if (typeof args === 'string') {
|
|
173
|
+
try {
|
|
174
|
+
args = JSON.parse(args);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
throw new Error('tool call arguments must be valid JSON');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)) {
|
|
181
|
+
throw new Error('tool call arguments must encode a JSON object');
|
|
182
|
+
}
|
|
183
|
+
pending.add(id);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else if (role === 'tool') {
|
|
187
|
+
const id = message.tool_call_id;
|
|
188
|
+
if (typeof id !== 'string' || !pending.delete(id)) {
|
|
189
|
+
throw new Error('tool message references an unknown tool_call_id');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (pending.size > 0) {
|
|
193
|
+
throw new Error('tool calls must be resolved before the next turn');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (pending.size > 0)
|
|
197
|
+
throw new Error('assistant tool calls are missing tool response messages');
|
|
198
|
+
}
|
|
199
|
+
function extractFrames(buffer, final = false) {
|
|
200
|
+
const frames = [];
|
|
201
|
+
let rest = buffer;
|
|
202
|
+
while (true) {
|
|
203
|
+
const match = /\r\n\r\n|\n\n|\r\r/.exec(rest);
|
|
204
|
+
if (!match || match.index === undefined)
|
|
205
|
+
break;
|
|
206
|
+
frames.push(rest.slice(0, match.index));
|
|
207
|
+
rest = rest.slice(match.index + match[0].length);
|
|
208
|
+
}
|
|
209
|
+
if (final && rest) {
|
|
210
|
+
frames.push(rest);
|
|
211
|
+
rest = '';
|
|
212
|
+
}
|
|
213
|
+
return { frames, rest };
|
|
214
|
+
}
|
|
215
|
+
function dataFromFrame(frame) {
|
|
216
|
+
const data = [];
|
|
217
|
+
for (const line of frame.replaceAll('\r\n', '\n').replaceAll('\r', '\n').split('\n')) {
|
|
218
|
+
if (!line || line.startsWith(':'))
|
|
219
|
+
continue;
|
|
220
|
+
const colon = line.indexOf(':');
|
|
221
|
+
const field = colon < 0 ? line : line.slice(0, colon);
|
|
222
|
+
let value = colon < 0 ? '' : line.slice(colon + 1);
|
|
223
|
+
if (value.startsWith(' '))
|
|
224
|
+
value = value.slice(1);
|
|
225
|
+
if (field === 'data')
|
|
226
|
+
data.push(value);
|
|
227
|
+
}
|
|
228
|
+
return data.length > 0 ? data.join('\n') : undefined;
|
|
229
|
+
}
|
|
230
|
+
export async function* parseSSE(body) {
|
|
231
|
+
const reader = body.getReader();
|
|
232
|
+
const decoder = new TextDecoder();
|
|
233
|
+
let buffer = '';
|
|
234
|
+
try {
|
|
235
|
+
while (true) {
|
|
236
|
+
const { done, value } = await reader.read();
|
|
237
|
+
if (done)
|
|
238
|
+
break;
|
|
239
|
+
buffer += decoder.decode(value, { stream: true });
|
|
240
|
+
const extracted = extractFrames(buffer);
|
|
241
|
+
buffer = extracted.rest;
|
|
242
|
+
for (const frame of extracted.frames) {
|
|
243
|
+
const data = dataFromFrame(frame);
|
|
244
|
+
if (data !== undefined)
|
|
245
|
+
yield data;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
buffer += decoder.decode();
|
|
249
|
+
const extracted = extractFrames(buffer, true);
|
|
250
|
+
for (const frame of extracted.frames) {
|
|
251
|
+
const data = dataFromFrame(frame);
|
|
252
|
+
if (data !== undefined)
|
|
253
|
+
yield data;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
await reader.cancel().catch(() => undefined);
|
|
258
|
+
reader.releaseLock();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
async function apiError(response) {
|
|
262
|
+
const text = await response.text().catch(() => '');
|
|
263
|
+
let body = { error: text || `Inference API error ${response.status}` };
|
|
264
|
+
try {
|
|
265
|
+
body = JSON.parse(text);
|
|
266
|
+
}
|
|
267
|
+
catch { /* use text envelope */ }
|
|
268
|
+
return new ApiError(response.status, body);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Inference surface. Combines control-plane management of model-serving
|
|
272
|
+
* deployments (`/api/inference*`) with OpenAI-compatible key-scoped inference
|
|
273
|
+
* (`/v1/chat/completions`). Requests are dispatched once; an idempotency header
|
|
274
|
+
* is forwarded but server-side replay is not assumed.
|
|
275
|
+
*/
|
|
276
|
+
export class Inference {
|
|
277
|
+
key;
|
|
278
|
+
baseUrl;
|
|
279
|
+
timeout;
|
|
280
|
+
_http;
|
|
281
|
+
constructor(config = {}, http) {
|
|
282
|
+
this.key = config.inferenceKey;
|
|
283
|
+
this.baseUrl = (config.baseUrl || 'https://api-dev.usbios.ai').replace(/\/+$/, '');
|
|
284
|
+
this.timeout = config.timeout ?? 900_000;
|
|
285
|
+
this._http = http;
|
|
286
|
+
}
|
|
287
|
+
/** @internal Control-plane transport; present when constructed by the SDK client. */
|
|
288
|
+
get http() {
|
|
289
|
+
if (!this._http) {
|
|
290
|
+
throw new Error('BiOS: control-plane inference management requires an SDK client (apiKey or accessToken)');
|
|
291
|
+
}
|
|
292
|
+
return this._http;
|
|
293
|
+
}
|
|
294
|
+
// --------------------------------------------------------------------------
|
|
295
|
+
// Control-plane deployment management — `/api/inference*`
|
|
296
|
+
// --------------------------------------------------------------------------
|
|
297
|
+
/** Side-effect-free validation with authoritative stock, prices, alternatives, and hold terms. */
|
|
298
|
+
preflight(params) {
|
|
299
|
+
return this.http.fetchPost('/api/inference/preflight', buildInferenceRequest(params));
|
|
300
|
+
}
|
|
301
|
+
/** Create after preflight. Reuse idempotencyKey after a timeout to recover the same deployment and inference key. */
|
|
302
|
+
create(params, idempotencyKey) {
|
|
303
|
+
return this.http.fetchPost('/api/inference', buildInferenceRequest(params), {
|
|
304
|
+
'Idempotency-Key': inferenceIdempotencyKey(idempotencyKey),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
/** Fetch one bounded newest-first page. Reuse next_cursor with unchanged filters. */
|
|
308
|
+
async listPage(params = {}) {
|
|
309
|
+
const query = new URLSearchParams();
|
|
310
|
+
if (params.limit !== undefined) {
|
|
311
|
+
if (!Number.isInteger(params.limit) || params.limit < 1) {
|
|
312
|
+
throw new Error('BiOS: deployment list limit must be a positive integer');
|
|
313
|
+
}
|
|
314
|
+
query.set('limit', String(Math.min(params.limit, 200)));
|
|
315
|
+
}
|
|
316
|
+
if (params.cursor)
|
|
317
|
+
query.set('cursor', params.cursor);
|
|
318
|
+
if (params.status && params.status !== 'all')
|
|
319
|
+
query.set('status', params.status);
|
|
320
|
+
if (params.search?.trim())
|
|
321
|
+
query.set('search', params.search.trim());
|
|
322
|
+
const suffix = query.toString();
|
|
323
|
+
const response = await this.http.fetchGet(`/api/inference${suffix ? `?${suffix}` : ''}`);
|
|
324
|
+
// Compatibility with pre-pagination servers during a rolling release.
|
|
325
|
+
if (Array.isArray(response)) {
|
|
326
|
+
return { deployments: response, has_more: false, next_cursor: null, limit: response.length };
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
deployments: response.deployments || [],
|
|
330
|
+
has_more: response.has_more === true,
|
|
331
|
+
next_cursor: response.next_cursor ?? null,
|
|
332
|
+
limit: response.limit || params.limit || 50,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
/** Compatibility helper returning only one bounded page. Prefer listPage for pagination. */
|
|
336
|
+
async list(params = {}) {
|
|
337
|
+
return (await this.listPage(params)).deployments;
|
|
338
|
+
}
|
|
339
|
+
/** Lazily traverse pages without materializing an unbounded tenant list. */
|
|
340
|
+
async *iterate(params = {}) {
|
|
341
|
+
let cursor;
|
|
342
|
+
do {
|
|
343
|
+
const page = await this.listPage({ ...params, cursor });
|
|
344
|
+
for (const deployment of page.deployments)
|
|
345
|
+
yield deployment;
|
|
346
|
+
cursor = page.has_more && page.next_cursor ? page.next_cursor : undefined;
|
|
347
|
+
} while (cursor);
|
|
348
|
+
}
|
|
349
|
+
/** Current durable lifecycle, queue, price-cap, and wallet-authorization state. */
|
|
350
|
+
get(id) {
|
|
351
|
+
return this.http.fetchGet(`/api/inference/${encodeURIComponent(id)}`);
|
|
352
|
+
}
|
|
353
|
+
/** Alias for get(), useful in polling automations. */
|
|
354
|
+
status(id) {
|
|
355
|
+
return this.get(id);
|
|
356
|
+
}
|
|
357
|
+
/** Durable email delivery history, including bounded retries and dead letters. */
|
|
358
|
+
async notifications(id, limit = 50) {
|
|
359
|
+
const safeLimit = Math.max(1, Math.min(100, Math.trunc(limit)));
|
|
360
|
+
const response = await this.http.fetchGet(`/api/inference/${encodeURIComponent(id)}/notifications?limit=${safeLimit}`);
|
|
361
|
+
return response.notifications || [];
|
|
362
|
+
}
|
|
363
|
+
stop(id) {
|
|
364
|
+
return this.http.fetchPost(`/api/inference/${encodeURIComponent(id)}/stop`);
|
|
365
|
+
}
|
|
366
|
+
resume(id) {
|
|
367
|
+
return this.http.fetchPost(`/api/inference/${encodeURIComponent(id)}/resume`);
|
|
368
|
+
}
|
|
369
|
+
restart(id) {
|
|
370
|
+
return this.http.fetchPost(`/api/inference/${encodeURIComponent(id)}/restart`);
|
|
371
|
+
}
|
|
372
|
+
update(id, params) {
|
|
373
|
+
const body = {};
|
|
374
|
+
if (params.allowCapacityQueue !== undefined)
|
|
375
|
+
body.allow_capacity_queue = params.allowCapacityQueue;
|
|
376
|
+
if (params.maxPriceHourCents !== undefined)
|
|
377
|
+
body.max_price_hour_cents = params.maxPriceHourCents;
|
|
378
|
+
if (params.contextLength !== undefined)
|
|
379
|
+
body.context_length = params.contextLength;
|
|
380
|
+
if (params.quant !== undefined)
|
|
381
|
+
body.quant = params.quant;
|
|
382
|
+
if (params.servingConfig !== undefined)
|
|
383
|
+
body.serving_config = params.servingConfig;
|
|
384
|
+
return this.http.fetchPatch(`/api/inference/${encodeURIComponent(id)}`, body);
|
|
385
|
+
}
|
|
386
|
+
delete(id) {
|
|
387
|
+
return this.http.fetchDelete(`/api/inference/${encodeURIComponent(id)}`);
|
|
388
|
+
}
|
|
389
|
+
/** Model-fit GPU choices joined to the authoritative deployment market snapshot. */
|
|
390
|
+
getGPUOptions(params) {
|
|
391
|
+
if (params.gpuTier !== undefined && params.gpuTier !== 'secure') {
|
|
392
|
+
throw new Error('BiOS: gpuTier must be secure; other deployment capacity tiers are not supported');
|
|
393
|
+
}
|
|
394
|
+
const query = new URLSearchParams({ params_b: String(params.paramsB) });
|
|
395
|
+
if (params.activeParamsB !== undefined)
|
|
396
|
+
query.set('active_params_b', String(params.activeParamsB));
|
|
397
|
+
if (params.isMoe !== undefined)
|
|
398
|
+
query.set('is_moe', String(params.isMoe));
|
|
399
|
+
if (params.quant !== undefined)
|
|
400
|
+
query.set('quant', params.quant);
|
|
401
|
+
if (params.contextLength !== undefined)
|
|
402
|
+
query.set('context_length', String(params.contextLength));
|
|
403
|
+
if (params.kvHeads !== undefined)
|
|
404
|
+
query.set('kv_heads', String(params.kvHeads));
|
|
405
|
+
if (params.numLayers !== undefined)
|
|
406
|
+
query.set('num_layers', String(params.numLayers));
|
|
407
|
+
if (params.kvLayers !== undefined)
|
|
408
|
+
query.set('kv_layers', String(params.kvLayers));
|
|
409
|
+
if (params.headDim !== undefined)
|
|
410
|
+
query.set('head_dim', String(params.headDim));
|
|
411
|
+
if (params.attention !== undefined)
|
|
412
|
+
query.set('attn', params.attention);
|
|
413
|
+
if (params.numAttentionHeads !== undefined)
|
|
414
|
+
query.set('num_attention_heads', String(params.numAttentionHeads));
|
|
415
|
+
if (params.kvLoraRank !== undefined)
|
|
416
|
+
query.set('kv_lora_rank', String(params.kvLoraRank));
|
|
417
|
+
if (params.qkRopeHeadDim !== undefined)
|
|
418
|
+
query.set('qk_rope_head_dim', String(params.qkRopeHeadDim));
|
|
419
|
+
if (params.gpuTier !== undefined)
|
|
420
|
+
query.set('gpu_tier', params.gpuTier);
|
|
421
|
+
return this.http.fetchGet(`/api/inference/gpu-options?${query}`);
|
|
422
|
+
}
|
|
423
|
+
// --------------------------------------------------------------------------
|
|
424
|
+
// OpenAI-compatible key-scoped inference — `/v1/chat/completions`
|
|
425
|
+
// --------------------------------------------------------------------------
|
|
426
|
+
prepare(params, stream) {
|
|
427
|
+
const { messages, model, tools, toolChoice, inferenceKey, idempotencyKey, requestId, signal, ...extra } = params;
|
|
428
|
+
const key = inferenceKey || this.key;
|
|
429
|
+
if (!key)
|
|
430
|
+
throw new Error('an inferenceKey is required');
|
|
431
|
+
const body = { ...extra, messages, stream };
|
|
432
|
+
if (model)
|
|
433
|
+
body.model = model;
|
|
434
|
+
if (tools !== undefined)
|
|
435
|
+
body.tools = tools;
|
|
436
|
+
if (toolChoice !== undefined)
|
|
437
|
+
body.tool_choice = toolChoice;
|
|
438
|
+
validateChatRequest(body);
|
|
439
|
+
const headers = {
|
|
440
|
+
Authorization: `Bearer ${key}`,
|
|
441
|
+
Accept: stream ? 'text/event-stream' : 'application/json',
|
|
442
|
+
'Content-Type': 'application/json',
|
|
443
|
+
'X-Request-ID': requestId || crypto.randomUUID(),
|
|
444
|
+
};
|
|
445
|
+
if (idempotencyKey)
|
|
446
|
+
headers['Idempotency-Key'] = idempotencyKey;
|
|
447
|
+
return { body, key, headers, signal };
|
|
448
|
+
}
|
|
449
|
+
abortContext(signal) {
|
|
450
|
+
const controller = new AbortController();
|
|
451
|
+
const relay = () => controller.abort(signal?.reason);
|
|
452
|
+
if (signal?.aborted)
|
|
453
|
+
relay();
|
|
454
|
+
else
|
|
455
|
+
signal?.addEventListener('abort', relay, { once: true });
|
|
456
|
+
const timeoutId = setTimeout(() => controller.abort(new Error(`Inference timed out after ${this.timeout}ms`)), this.timeout);
|
|
457
|
+
return {
|
|
458
|
+
controller,
|
|
459
|
+
timeoutId,
|
|
460
|
+
remove: () => signal?.removeEventListener('abort', relay),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
async chatCompletions(params) {
|
|
464
|
+
const prepared = this.prepare(params, false);
|
|
465
|
+
const abort = this.abortContext(prepared.signal);
|
|
466
|
+
try {
|
|
467
|
+
const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
468
|
+
method: 'POST', headers: prepared.headers, body: JSON.stringify(prepared.body), signal: abort.controller.signal,
|
|
469
|
+
});
|
|
470
|
+
if (!response.ok)
|
|
471
|
+
throw await apiError(response);
|
|
472
|
+
return await response.json();
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
if (abort.controller.signal.aborted && !(error instanceof ApiError)) {
|
|
476
|
+
throw new ApiError(0, { error: String(abort.controller.signal.reason || 'Inference request aborted') });
|
|
477
|
+
}
|
|
478
|
+
throw error;
|
|
479
|
+
}
|
|
480
|
+
finally {
|
|
481
|
+
clearTimeout(abort.timeoutId);
|
|
482
|
+
abort.remove();
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
async *streamChatCompletions(params) {
|
|
486
|
+
const prepared = this.prepare(params, true);
|
|
487
|
+
const abort = this.abortContext(prepared.signal);
|
|
488
|
+
try {
|
|
489
|
+
const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
490
|
+
method: 'POST', headers: prepared.headers, body: JSON.stringify(prepared.body), signal: abort.controller.signal,
|
|
491
|
+
});
|
|
492
|
+
if (!response.ok)
|
|
493
|
+
throw await apiError(response);
|
|
494
|
+
const contentType = response.headers.get('content-type')?.toLowerCase() ?? '';
|
|
495
|
+
if (!contentType.includes('text/event-stream')) {
|
|
496
|
+
throw new ApiError(response.status, { error: `Expected text/event-stream, received ${contentType || 'no content type'}` });
|
|
497
|
+
}
|
|
498
|
+
if (!response.body)
|
|
499
|
+
throw new ApiError(0, { error: 'Inference response has no body' });
|
|
500
|
+
for await (const data of parseSSE(response.body)) {
|
|
501
|
+
if (data === '[DONE]')
|
|
502
|
+
return;
|
|
503
|
+
let event;
|
|
504
|
+
try {
|
|
505
|
+
event = JSON.parse(data);
|
|
506
|
+
}
|
|
507
|
+
catch {
|
|
508
|
+
throw new ApiError(0, { error: 'Invalid JSON SSE event' });
|
|
509
|
+
}
|
|
510
|
+
if (event && typeof event === 'object' && 'error' in event) {
|
|
511
|
+
throw new ApiError(0, event);
|
|
512
|
+
}
|
|
513
|
+
yield event;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
if (abort.controller.signal.aborted && !(error instanceof ApiError)) {
|
|
518
|
+
throw new ApiError(0, { error: String(abort.controller.signal.reason || 'Inference request aborted') });
|
|
519
|
+
}
|
|
520
|
+
throw error;
|
|
521
|
+
}
|
|
522
|
+
finally {
|
|
523
|
+
abort.controller.abort('stream closed');
|
|
524
|
+
clearTimeout(abort.timeoutId);
|
|
525
|
+
abort.remove();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
export { buildInferenceRequest };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { HttpClient } from '../client.js';
|
|
2
|
+
import type { ModelSearchParams, ModelSearchResponse, ModelConfig, AdapterCompatibilityParams, AdapterCompatibilityResponse, ArchitectureScope, SupportedArchitecturesResponse } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Access the BIOS model catalog -- search HuggingFace models, fetch
|
|
5
|
+
* training-relevant configuration, and check adapter compatibility.
|
|
6
|
+
*/
|
|
7
|
+
export declare class Models {
|
|
8
|
+
private readonly _http;
|
|
9
|
+
/** @internal */
|
|
10
|
+
constructor(_http: HttpClient);
|
|
11
|
+
/**
|
|
12
|
+
* Search the HuggingFace model catalog for fine-tunable models.
|
|
13
|
+
*
|
|
14
|
+
* Results are filtered to exclude quantized derivatives (GGUF, GPTQ, AWQ)
|
|
15
|
+
* and adapters by default -- pass `kind: "adapter"` to find LoRA adapters.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const res = await client.models.search({ query: 'llama', type: 'llm', limit: 10 });
|
|
20
|
+
* for (const m of res.models) {
|
|
21
|
+
* console.log(`${m.id} -- ${m.totalParams}B params`);
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
search(params?: ModelSearchParams): Promise<ModelSearchResponse>;
|
|
26
|
+
/**
|
|
27
|
+
* Fetch the training configuration for a specific model from HuggingFace.
|
|
28
|
+
*
|
|
29
|
+
* Returns parameter counts, architecture type, and whether the model
|
|
30
|
+
* uses Mixture-of-Experts -- information needed to choose the right
|
|
31
|
+
* GPU and adapter configuration.
|
|
32
|
+
*
|
|
33
|
+
* @param modelId - Full HuggingFace model ID (e.g. "meta-llama/Llama-3.1-8B").
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const config = await client.models.getConfig('meta-llama/Llama-3.1-8B');
|
|
38
|
+
* console.log(`${config.totalParams}B params, MoE: ${config.isMoE}`);
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
getConfig(modelId: string): Promise<ModelConfig>;
|
|
42
|
+
/**
|
|
43
|
+
* Check which adapters are compatible with a given architecture,
|
|
44
|
+
* training method, and (optionally) RLHF algorithm.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const compat = await client.models.getAdapterCompatibility({
|
|
49
|
+
* modelType: 'llama',
|
|
50
|
+
* trainingMethod: 'rlhf',
|
|
51
|
+
* rlhfAlgorithm: 'dpo',
|
|
52
|
+
* });
|
|
53
|
+
* const usable = compat.adapters.filter(a => a.compatible);
|
|
54
|
+
* console.log(`${usable.length} compatible adapters`);
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
getAdapterCompatibility(params?: AdapterCompatibilityParams): Promise<AdapterCompatibilityResponse>;
|
|
58
|
+
/**
|
|
59
|
+
* List the architectures the platform supports for a given scope. This is
|
|
60
|
+
* the authoritative registry the platform gates on:
|
|
61
|
+
* - `inference` — HF architecture classes that can be served (deployed).
|
|
62
|
+
* - `training` — architecture keys the fine-tuning wizard/gate accepts.
|
|
63
|
+
*
|
|
64
|
+
* A `count` of 0 means the registry is empty and nothing is explicitly
|
|
65
|
+
* restricted (every architecture the engine supports is allowed).
|
|
66
|
+
*
|
|
67
|
+
* ```ts
|
|
68
|
+
* const { architectures } = await client.models.getSupportedArchitectures({ scope: 'inference' });
|
|
69
|
+
* const servable = architectures.filter(a => a.enabled).map(a => a.architecture);
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
getSupportedArchitectures(params?: {
|
|
73
|
+
scope?: ArchitectureScope;
|
|
74
|
+
}): Promise<SupportedArchitecturesResponse>;
|
|
75
|
+
}
|