@zhivex-ai/qwen 0.7.8 → 0.8.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 +137 -44
- package/dist/index.d.ts +22 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +903 -304
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { toJSONSchema } from "zod";
|
|
2
|
-
import { CallbackRealtimeSession, ConfigurationError, decodeBase64WithLimit, ProviderHTTPError, readBodyWithLimit, readErrorBodyWithLimit, readJsonWithLimit, resolveAudioResponseLimits, UnsupportedFeatureError, createProviderAdapter, hostedTool, openWebSocketConnection, isCallableToolDefinition, normalizeFinishReason, providerDataPart, streamSSE, withRetry, withTimeoutSignal } from "@zhivex-ai/core";
|
|
2
|
+
import { CallbackRealtimeSession, ConfigurationError, decodeBase64WithLimit, ProviderHTTPError, readBodyWithLimit, readErrorBodyWithLimit, readJsonWithLimit, resolveAudioResponseLimits, UnsupportedFeatureError, createProviderAdapter, hostedTool, openWebSocketConnection, isCallableToolDefinition, normalizeFinishReason, providerDataPart, streamSSE, toToolSet, withRetry, withTimeoutSignal } from "@zhivex-ai/core";
|
|
3
3
|
const capabilities = {
|
|
4
4
|
streaming: true,
|
|
5
5
|
tools: true,
|
|
6
6
|
structuredOutput: true,
|
|
7
7
|
jsonMode: true,
|
|
8
8
|
toolChoice: true,
|
|
9
|
-
parallelToolCalls:
|
|
9
|
+
parallelToolCalls: false,
|
|
10
10
|
vision: true,
|
|
11
11
|
files: false,
|
|
12
12
|
audioInput: false,
|
|
@@ -57,6 +57,173 @@ const qwenTaskBaseURLFrom = (baseURL) => {
|
|
|
57
57
|
const url = new URL(baseURL);
|
|
58
58
|
return `${url.protocol}//${url.host}/api/v1`;
|
|
59
59
|
};
|
|
60
|
+
const QWEN_REGION_HOSTS = {
|
|
61
|
+
singapore: "ap-southeast-1.maas.aliyuncs.com",
|
|
62
|
+
beijing: "cn-beijing.maas.aliyuncs.com",
|
|
63
|
+
"hong-kong": "cn-hongkong.maas.aliyuncs.com",
|
|
64
|
+
tokyo: "ap-northeast-1.maas.aliyuncs.com",
|
|
65
|
+
frankfurt: "eu-central-1.maas.aliyuncs.com",
|
|
66
|
+
virginia: "dashscope-us.aliyuncs.com"
|
|
67
|
+
};
|
|
68
|
+
const qwenWorkspaceURLs = (workspaceId, region) => {
|
|
69
|
+
const origin = region === "virginia"
|
|
70
|
+
? `https://${QWEN_REGION_HOSTS[region]}`
|
|
71
|
+
: `https://${workspaceId}.${QWEN_REGION_HOSTS[region]}`;
|
|
72
|
+
return {
|
|
73
|
+
baseURL: `${origin}/compatible-mode/v1`,
|
|
74
|
+
taskBaseURL: `${origin}/api/v1`,
|
|
75
|
+
realtimeURL: `${origin.replace(/^https:/, "wss:")}/api-ws/v1/realtime`
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
const QWEN_REALTIME_QUEUE_LIMIT = 256;
|
|
79
|
+
const nodeWebSocketDataToText = (data) => {
|
|
80
|
+
if (Array.isArray(data)) {
|
|
81
|
+
return Buffer.concat(data).toString("utf8");
|
|
82
|
+
}
|
|
83
|
+
if (data instanceof ArrayBuffer) {
|
|
84
|
+
return Buffer.from(data).toString("utf8");
|
|
85
|
+
}
|
|
86
|
+
return Buffer.from(data).toString("utf8");
|
|
87
|
+
};
|
|
88
|
+
const openQwenRealtimeConnection = async (url, headers, options) => {
|
|
89
|
+
if (typeof process === "undefined" || !process.versions?.node) {
|
|
90
|
+
return openWebSocketConnection(url, headers, options);
|
|
91
|
+
}
|
|
92
|
+
if (options?.signal?.aborted) {
|
|
93
|
+
throw new Error("Qwen realtime connection aborted.");
|
|
94
|
+
}
|
|
95
|
+
const { default: WebSocket } = await import("ws");
|
|
96
|
+
const socket = options?.subprotocols?.length
|
|
97
|
+
? new WebSocket(url, options.subprotocols, { headers })
|
|
98
|
+
: new WebSocket(url, { headers });
|
|
99
|
+
await new Promise((resolve, reject) => {
|
|
100
|
+
let finished = false;
|
|
101
|
+
const finish = (callback) => {
|
|
102
|
+
if (finished) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
finished = true;
|
|
106
|
+
if (timer) {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
}
|
|
109
|
+
options?.signal?.removeEventListener("abort", onAbort);
|
|
110
|
+
socket.off("open", onOpen);
|
|
111
|
+
socket.off("error", onError);
|
|
112
|
+
callback();
|
|
113
|
+
};
|
|
114
|
+
const onOpen = () => finish(resolve);
|
|
115
|
+
const onError = (error) => finish(() => reject(error));
|
|
116
|
+
const onAbort = () => {
|
|
117
|
+
socket.close();
|
|
118
|
+
finish(() => reject(new Error("Qwen realtime connection aborted.")));
|
|
119
|
+
};
|
|
120
|
+
const timer = options?.timeoutMs
|
|
121
|
+
? setTimeout(() => {
|
|
122
|
+
socket.close();
|
|
123
|
+
finish(() => reject(new Error(`Qwen realtime connection timed out after ${options.timeoutMs}ms.`)));
|
|
124
|
+
}, options.timeoutMs)
|
|
125
|
+
: undefined;
|
|
126
|
+
socket.once("open", onOpen);
|
|
127
|
+
socket.once("error", onError);
|
|
128
|
+
options?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
129
|
+
});
|
|
130
|
+
const queue = [];
|
|
131
|
+
const readers = [];
|
|
132
|
+
let closed = false;
|
|
133
|
+
let connectionError;
|
|
134
|
+
const rejectReaders = (error) => {
|
|
135
|
+
for (const reader of readers.splice(0)) {
|
|
136
|
+
reader.reject(error);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const closeReaders = () => {
|
|
140
|
+
for (const reader of readers.splice(0)) {
|
|
141
|
+
reader.resolve(undefined);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
socket.on("message", (data) => {
|
|
145
|
+
const text = nodeWebSocketDataToText(data);
|
|
146
|
+
const reader = readers.shift();
|
|
147
|
+
if (reader) {
|
|
148
|
+
try {
|
|
149
|
+
reader.resolve(JSON.parse(text));
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
reader.reject(error);
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (queue.length >= QWEN_REALTIME_QUEUE_LIMIT) {
|
|
157
|
+
connectionError = new Error(`Qwen realtime receive buffer exceeded ${QWEN_REALTIME_QUEUE_LIMIT} messages.`);
|
|
158
|
+
connectionError.name = "StreamBufferOverflowError";
|
|
159
|
+
socket.close();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
queue.push(text);
|
|
163
|
+
});
|
|
164
|
+
socket.on("error", (error) => {
|
|
165
|
+
connectionError = error;
|
|
166
|
+
rejectReaders(error);
|
|
167
|
+
});
|
|
168
|
+
socket.on("close", () => {
|
|
169
|
+
closed = true;
|
|
170
|
+
if (connectionError) {
|
|
171
|
+
rejectReaders(connectionError);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
closeReaders();
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
const connection = {
|
|
178
|
+
async sendJson(payload) {
|
|
179
|
+
if (connectionError) {
|
|
180
|
+
throw connectionError;
|
|
181
|
+
}
|
|
182
|
+
if (closed) {
|
|
183
|
+
throw new Error("Qwen realtime connection is closed.");
|
|
184
|
+
}
|
|
185
|
+
await new Promise((resolve, reject) => {
|
|
186
|
+
socket.send(JSON.stringify(payload), (error) => {
|
|
187
|
+
if (error) {
|
|
188
|
+
reject(error);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
resolve();
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
},
|
|
196
|
+
async recvJson() {
|
|
197
|
+
if (connectionError) {
|
|
198
|
+
throw connectionError;
|
|
199
|
+
}
|
|
200
|
+
if (queue.length > 0) {
|
|
201
|
+
return JSON.parse(queue.shift());
|
|
202
|
+
}
|
|
203
|
+
if (closed) {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
return new Promise((resolve, reject) => readers.push({ resolve, reject }));
|
|
207
|
+
},
|
|
208
|
+
async close() {
|
|
209
|
+
if (closed) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
await new Promise((resolve) => {
|
|
213
|
+
const timer = setTimeout(() => {
|
|
214
|
+
socket.terminate();
|
|
215
|
+
resolve();
|
|
216
|
+
}, 1_000);
|
|
217
|
+
socket.once("close", () => {
|
|
218
|
+
clearTimeout(timer);
|
|
219
|
+
resolve();
|
|
220
|
+
});
|
|
221
|
+
socket.close();
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
return connection;
|
|
226
|
+
};
|
|
60
227
|
const toUint8Array = async (data) => {
|
|
61
228
|
if (data instanceof Uint8Array) {
|
|
62
229
|
return data;
|
|
@@ -70,6 +237,24 @@ const toUint8Array = async (data) => {
|
|
|
70
237
|
return Uint8Array.from(Buffer.from(String(data), "base64"));
|
|
71
238
|
};
|
|
72
239
|
const toBase64 = async (data) => typeof data === "string" ? data : Buffer.from(await toUint8Array(data)).toString("base64");
|
|
240
|
+
const toDataURL = (data, mediaType) => {
|
|
241
|
+
if (typeof data === "string" && (/^data:/i.test(data) || /^https?:\/\//i.test(data))) {
|
|
242
|
+
return data;
|
|
243
|
+
}
|
|
244
|
+
const encoded = typeof data === "string"
|
|
245
|
+
? data
|
|
246
|
+
: Buffer.from(data instanceof Uint8Array ? data : new Uint8Array(data)).toString("base64");
|
|
247
|
+
return `data:${mediaType};base64,${encoded}`;
|
|
248
|
+
};
|
|
249
|
+
const mediaValue = (input) => {
|
|
250
|
+
if (input.uri) {
|
|
251
|
+
return input.uri;
|
|
252
|
+
}
|
|
253
|
+
if (input.data === undefined) {
|
|
254
|
+
throw new ConfigurationError("Qwen media input requires either uri or data.");
|
|
255
|
+
}
|
|
256
|
+
return toDataURL(input.data, input.mediaType);
|
|
257
|
+
};
|
|
73
258
|
const createFile = async (data, mediaType, filename) => {
|
|
74
259
|
const bytes = await toUint8Array(data);
|
|
75
260
|
return new File([bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)], filename, { type: mediaType });
|
|
@@ -83,6 +268,50 @@ const appendQuery = (url, query) => {
|
|
|
83
268
|
}
|
|
84
269
|
return parsed.toString();
|
|
85
270
|
};
|
|
271
|
+
const encodeQwenResourceId = (value, resource) => {
|
|
272
|
+
if (!value || value === "." || value === ".." || /[\\/?#\s]/.test(value)) {
|
|
273
|
+
throw new ConfigurationError(`Qwen ${resource} ID must be a non-empty opaque identifier without path separators.`);
|
|
274
|
+
}
|
|
275
|
+
return encodeURIComponent(value);
|
|
276
|
+
};
|
|
277
|
+
const isQwenOmniLanguageModel = (modelId) => /^qwen3(?:\.5)?-omni-(?:plus|flash)(?:$|-)/i.test(modelId);
|
|
278
|
+
const isOfficialQwenSpeechAudioURL = (url) => !url.username &&
|
|
279
|
+
!url.password &&
|
|
280
|
+
/(?:^|\.)oss(?:-[a-z0-9-]+)?\.aliyuncs\.com$/i.test(url.hostname);
|
|
281
|
+
const parseQwenSpeechAudioURL = async (value, validator) => {
|
|
282
|
+
let url;
|
|
283
|
+
try {
|
|
284
|
+
url = new URL(value);
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
throw new ProviderHTTPError("Qwen speech response contained an invalid audio download URL.", 502);
|
|
288
|
+
}
|
|
289
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
290
|
+
throw new ProviderHTTPError("Qwen speech audio downloads require an HTTP(S) URL.", 502);
|
|
291
|
+
}
|
|
292
|
+
const allowed = validator ? await validator(url) : isOfficialQwenSpeechAudioURL(url);
|
|
293
|
+
if (!allowed) {
|
|
294
|
+
throw new ProviderHTTPError("Qwen speech response contained an audio download URL rejected by the configured safety policy.", 502);
|
|
295
|
+
}
|
|
296
|
+
return url;
|
|
297
|
+
};
|
|
298
|
+
const downloadQwenSpeechAudio = async (initialURL, fetcher, signal, validator, maxRedirects) => {
|
|
299
|
+
let url = await parseQwenSpeechAudioURL(initialURL, validator);
|
|
300
|
+
for (let redirects = 0;; redirects += 1) {
|
|
301
|
+
const response = await fetcher(url, { method: "GET", redirect: "manual", signal });
|
|
302
|
+
if (![301, 302, 303, 307, 308].includes(response.status)) {
|
|
303
|
+
return response;
|
|
304
|
+
}
|
|
305
|
+
if (redirects >= maxRedirects) {
|
|
306
|
+
throw new ProviderHTTPError(`Qwen speech audio download exceeded ${maxRedirects} redirects.`, 502);
|
|
307
|
+
}
|
|
308
|
+
const location = response.headers.get("location");
|
|
309
|
+
if (!location) {
|
|
310
|
+
throw new ProviderHTTPError("Qwen speech audio redirect did not include a Location header.", 502);
|
|
311
|
+
}
|
|
312
|
+
url = await parseQwenSpeechAudioURL(new URL(location, url).toString(), validator);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
86
315
|
const providerHeaders = (apiKey, providerOptions) => {
|
|
87
316
|
const headers = jsonHeaders(apiKey);
|
|
88
317
|
const sessionCache = providerOptions?.["x-dashscope-session-cache"];
|
|
@@ -96,26 +325,69 @@ const stripHeaderOptions = (providerOptions) => {
|
|
|
96
325
|
delete next["x-dashscope-session-cache"];
|
|
97
326
|
return next;
|
|
98
327
|
};
|
|
328
|
+
const stripResponsesRequestOptions = (providerOptions) => {
|
|
329
|
+
const next = stripHeaderOptions(providerOptions);
|
|
330
|
+
for (const key of [
|
|
331
|
+
"model",
|
|
332
|
+
"input",
|
|
333
|
+
"stream",
|
|
334
|
+
"tools",
|
|
335
|
+
"tool_choice",
|
|
336
|
+
"temperature",
|
|
337
|
+
"reasoning",
|
|
338
|
+
"previous_response_id",
|
|
339
|
+
"max_output_tokens",
|
|
340
|
+
"response_format"
|
|
341
|
+
]) {
|
|
342
|
+
delete next[key];
|
|
343
|
+
}
|
|
344
|
+
return next;
|
|
345
|
+
};
|
|
99
346
|
const modelFamily = (modelId) => modelId.toLowerCase();
|
|
100
|
-
const supportsQwenReasoning = (modelId) =>
|
|
347
|
+
const supportsQwenReasoning = (modelId) => !isQwenOmniLanguageModel(modelId) &&
|
|
348
|
+
supportsQwenTools(modelId) &&
|
|
349
|
+
/^(qwen-(plus|turbo|max|flash)|qwq|qwen3|qwen3\.)/i.test(modelId);
|
|
101
350
|
const supportsQwenVision = (modelId) => {
|
|
102
351
|
const model = modelFamily(modelId);
|
|
103
352
|
return (model.includes("vl") ||
|
|
104
353
|
model.includes("omni") ||
|
|
354
|
+
model.includes("ocr") ||
|
|
105
355
|
model.includes("vision") ||
|
|
106
|
-
/^qwen3\.7-plus(?:$|-)/.test(model)
|
|
356
|
+
/^qwen3\.7-plus(?:$|-)/.test(model) ||
|
|
357
|
+
/^qwen3\.6-flash(?:$|-)/.test(model));
|
|
358
|
+
};
|
|
359
|
+
const supportsQwenTools = (modelId) => !/(embedding|rerank|asr|tts|image|realtime|ocr|translation|character|long-context)/.test(modelFamily(modelId));
|
|
360
|
+
const supportsQwenFiles = (modelId) => /^qwen3\.5-ocr(?:$|-)/.test(modelFamily(modelId));
|
|
361
|
+
const supportsQwenAudioInput = (modelId) => /(omni|audio|asr)/.test(modelFamily(modelId));
|
|
362
|
+
const qwenLanguageCapabilities = (modelId) => {
|
|
363
|
+
const tools = supportsQwenTools(modelId);
|
|
364
|
+
const omni = isQwenOmniLanguageModel(modelId);
|
|
365
|
+
const reasoning = supportsQwenReasoning(modelId);
|
|
366
|
+
return {
|
|
367
|
+
...capabilities,
|
|
368
|
+
vision: supportsQwenVision(modelId),
|
|
369
|
+
tools,
|
|
370
|
+
structuredOutput: tools && !omni,
|
|
371
|
+
jsonMode: tools && !omni,
|
|
372
|
+
toolChoice: tools,
|
|
373
|
+
parallelToolCalls: false,
|
|
374
|
+
webSearch: tools && !omni,
|
|
375
|
+
files: supportsQwenFiles(modelId),
|
|
376
|
+
audioInput: supportsQwenAudioInput(modelId),
|
|
377
|
+
reasoning,
|
|
378
|
+
reasoningEfforts: reasoning ? ["none", "minimal", "low", "medium", "high"] : undefined,
|
|
379
|
+
agentCapabilities: {
|
|
380
|
+
...capabilities.agentCapabilities,
|
|
381
|
+
supportTier: tools && !omni ? "tier-b" : "tier-c",
|
|
382
|
+
toolChoiceNone: tools,
|
|
383
|
+
hostedWebSearch: tools && !omni,
|
|
384
|
+
hostedFileSearch: tools && !omni,
|
|
385
|
+
remoteMcp: tools && !omni,
|
|
386
|
+
codeExecution: tools && !omni,
|
|
387
|
+
webExtraction: tools && !omni
|
|
388
|
+
}
|
|
389
|
+
};
|
|
107
390
|
};
|
|
108
|
-
const supportsQwenTools = (modelId) => !modelFamily(modelId).includes("embedding");
|
|
109
|
-
const qwenLanguageCapabilities = (modelId) => ({
|
|
110
|
-
...capabilities,
|
|
111
|
-
vision: supportsQwenVision(modelId),
|
|
112
|
-
tools: supportsQwenTools(modelId),
|
|
113
|
-
structuredOutput: supportsQwenTools(modelId),
|
|
114
|
-
jsonMode: supportsQwenTools(modelId),
|
|
115
|
-
toolChoice: supportsQwenTools(modelId),
|
|
116
|
-
parallelToolCalls: supportsQwenTools(modelId),
|
|
117
|
-
reasoning: supportsQwenReasoning(modelId)
|
|
118
|
-
});
|
|
119
391
|
const transcriptionCapabilities = {
|
|
120
392
|
...embeddingCapabilities,
|
|
121
393
|
audioInput: true
|
|
@@ -176,23 +448,25 @@ const parseJson = async (response, options = {}) => {
|
|
|
176
448
|
: response.json();
|
|
177
449
|
};
|
|
178
450
|
const mapContentParts = (message) => {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
451
|
+
const hasMedia = message.parts.some((part) => part.type === "image" || part.type === "audio");
|
|
452
|
+
if (!hasMedia) {
|
|
453
|
+
return message.parts
|
|
454
|
+
.filter((part) => part.type === "text")
|
|
455
|
+
.map((part) => part.text)
|
|
456
|
+
.join("");
|
|
183
457
|
}
|
|
184
|
-
return
|
|
185
|
-
|
|
186
|
-
type: "text",
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
458
|
+
return message.parts.flatMap((part) => {
|
|
459
|
+
if (part.type === "text") {
|
|
460
|
+
return [{ type: "text", text: part.text }];
|
|
461
|
+
}
|
|
462
|
+
if (part.type === "image") {
|
|
463
|
+
return [{ type: "image_url", image_url: { url: part.image } }];
|
|
464
|
+
}
|
|
465
|
+
if (part.type === "audio") {
|
|
466
|
+
return [{ type: "input_audio", input_audio: { data: toDataURL(part.data, part.mediaType) } }];
|
|
467
|
+
}
|
|
468
|
+
return [];
|
|
469
|
+
});
|
|
196
470
|
};
|
|
197
471
|
const mapMessages = (messages) => messages.map((message) => {
|
|
198
472
|
if (message.role === "tool") {
|
|
@@ -264,7 +538,7 @@ const mapResponsesTools = (tools) => tools
|
|
|
264
538
|
};
|
|
265
539
|
})
|
|
266
540
|
: undefined;
|
|
267
|
-
const
|
|
541
|
+
const mapChatToolChoice = (toolChoice) => {
|
|
268
542
|
if (!toolChoice) {
|
|
269
543
|
return undefined;
|
|
270
544
|
}
|
|
@@ -278,21 +552,48 @@ const mapToolChoice = (toolChoice) => {
|
|
|
278
552
|
}
|
|
279
553
|
};
|
|
280
554
|
};
|
|
281
|
-
const
|
|
282
|
-
if (!
|
|
555
|
+
const mapResponsesToolChoice = (toolChoice, tools) => {
|
|
556
|
+
if (!toolChoice) {
|
|
283
557
|
return undefined;
|
|
284
558
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
name: input.structuredOutput.name ?? "response",
|
|
289
|
-
strict: true,
|
|
290
|
-
schema: toJSONSchema(input.structuredOutput.schema)
|
|
559
|
+
if (typeof toolChoice === "string") {
|
|
560
|
+
if (toolChoice === "required" && Object.keys(tools ?? {}).length !== 1) {
|
|
561
|
+
throw new UnsupportedFeatureError('Qwen Responses supports toolChoice "required" only when exactly one tool is provided.');
|
|
291
562
|
}
|
|
563
|
+
return toolChoice;
|
|
564
|
+
}
|
|
565
|
+
return {
|
|
566
|
+
type: "allowed_tools",
|
|
567
|
+
mode: "required",
|
|
568
|
+
tools: [{ type: "function", name: toolChoice.toolName }]
|
|
292
569
|
};
|
|
293
570
|
};
|
|
571
|
+
const mapStructuredOutput = (input) => {
|
|
572
|
+
if (!input.structuredOutput || input.structuredOutput.mode !== "native") {
|
|
573
|
+
return undefined;
|
|
574
|
+
}
|
|
575
|
+
return { type: "json_object" };
|
|
576
|
+
};
|
|
577
|
+
const withStructuredOutputMessages = (input) => {
|
|
578
|
+
if (!input.structuredOutput || input.structuredOutput.mode !== "native") {
|
|
579
|
+
return input.messages;
|
|
580
|
+
}
|
|
581
|
+
const schema = JSON.stringify(toJSONSchema(input.structuredOutput.schema));
|
|
582
|
+
return [
|
|
583
|
+
{
|
|
584
|
+
role: "system",
|
|
585
|
+
parts: [
|
|
586
|
+
{
|
|
587
|
+
type: "text",
|
|
588
|
+
text: `Return only valid JSON matching this JSON Schema: ${schema}`
|
|
589
|
+
}
|
|
590
|
+
]
|
|
591
|
+
},
|
|
592
|
+
...input.messages
|
|
593
|
+
];
|
|
594
|
+
};
|
|
294
595
|
const hasPreservedReasoning = (messages) => messages.some((message) => message.role === "assistant" && reasoningContentFromMessage(message));
|
|
295
|
-
const
|
|
596
|
+
const mapChatReasoning = (input) => {
|
|
296
597
|
if (!input.reasoning) {
|
|
297
598
|
return undefined;
|
|
298
599
|
}
|
|
@@ -304,6 +605,63 @@ const mapReasoning = (input) => {
|
|
|
304
605
|
...(hasPreservedReasoning(input.messages) ? { preserve_thinking: true } : {})
|
|
305
606
|
};
|
|
306
607
|
};
|
|
608
|
+
const mapResponsesReasoning = (input) => {
|
|
609
|
+
const effort = input.reasoning?.effort;
|
|
610
|
+
if (!effort) {
|
|
611
|
+
return undefined;
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
reasoning: {
|
|
615
|
+
effort: effort === "low" ? "minimal" : effort
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
};
|
|
619
|
+
const mapResponsesUsage = (usage) => usage
|
|
620
|
+
? {
|
|
621
|
+
inputTokens: usage.input_tokens,
|
|
622
|
+
cachedInputTokens: usage.input_tokens_details?.cached_tokens,
|
|
623
|
+
outputTokens: usage.output_tokens,
|
|
624
|
+
reasoningTokens: usage.output_tokens_details?.reasoning_tokens,
|
|
625
|
+
totalTokens: usage.total_tokens
|
|
626
|
+
}
|
|
627
|
+
: undefined;
|
|
628
|
+
const mapChatUsage = (usage) => usage
|
|
629
|
+
? {
|
|
630
|
+
inputTokens: usage.prompt_tokens,
|
|
631
|
+
cachedInputTokens: usage.prompt_tokens_details?.cached_tokens ?? usage.input_tokens_details?.cached_tokens,
|
|
632
|
+
outputTokens: usage.completion_tokens,
|
|
633
|
+
reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? usage.output_tokens_details?.reasoning_tokens,
|
|
634
|
+
totalTokens: usage.total_tokens
|
|
635
|
+
}
|
|
636
|
+
: undefined;
|
|
637
|
+
const hasHostedTools = (tools) => Object.values(tools ?? {}).some((tool) => !isCallableToolDefinition(tool));
|
|
638
|
+
const hasMessagePart = (messages, type) => messages.some((message) => message.parts.some((part) => part.type === type));
|
|
639
|
+
const resolveApiMode = (modelId, requestedMode, input) => {
|
|
640
|
+
const needsChat = input.maxTokens !== undefined ||
|
|
641
|
+
input.reasoning?.budgetTokens !== undefined ||
|
|
642
|
+
input.structuredOutput?.mode === "native" ||
|
|
643
|
+
hasMessagePart(input.messages, "audio");
|
|
644
|
+
const needsResponses = hasHostedTools(input.tools) || hasMessagePart(input.messages, "file");
|
|
645
|
+
if (needsChat && needsResponses) {
|
|
646
|
+
throw new UnsupportedFeatureError("Qwen cannot combine Responses-only hosted/file inputs with Chat-only maxTokens, audio, reasoning budgets, or structured output.");
|
|
647
|
+
}
|
|
648
|
+
if (isQwenOmniLanguageModel(modelId)) {
|
|
649
|
+
if (requestedMode === "responses" || needsResponses) {
|
|
650
|
+
throw new UnsupportedFeatureError("Qwen Omni uses streaming Chat Completions and does not support Responses hosted tools or file inputs.");
|
|
651
|
+
}
|
|
652
|
+
return "chat";
|
|
653
|
+
}
|
|
654
|
+
if (requestedMode === "responses" && needsChat) {
|
|
655
|
+
throw new UnsupportedFeatureError('Qwen Responses does not process maxTokens, audio input, reasoning budgets, or native structured output; use apiMode "chat" or "auto".');
|
|
656
|
+
}
|
|
657
|
+
if (requestedMode === "chat" && needsResponses) {
|
|
658
|
+
throw new UnsupportedFeatureError('Qwen Chat Completions does not support Responses hosted tools or OCR file inputs.');
|
|
659
|
+
}
|
|
660
|
+
if (requestedMode === "responses" || requestedMode === "chat") {
|
|
661
|
+
return requestedMode;
|
|
662
|
+
}
|
|
663
|
+
return needsChat ? "chat" : "responses";
|
|
664
|
+
};
|
|
307
665
|
const parseAssistantMessage = (message) => ({
|
|
308
666
|
role: "assistant",
|
|
309
667
|
parts: [
|
|
@@ -366,6 +724,16 @@ const toResponsesInput = (messages) => {
|
|
|
366
724
|
case "image":
|
|
367
725
|
content.push({ type: "input_image", image_url: part.image });
|
|
368
726
|
break;
|
|
727
|
+
case "file":
|
|
728
|
+
if (!/^https?:\/\//i.test(part.data)) {
|
|
729
|
+
throw new UnsupportedFeatureError("Qwen Responses file input requires a public HTTP(S) file URL; DashScope Files IDs are reserved for batch jobs.");
|
|
730
|
+
}
|
|
731
|
+
content.push({
|
|
732
|
+
type: "input_file",
|
|
733
|
+
file_url: part.data,
|
|
734
|
+
...(part.filename ? { filename: part.filename } : {})
|
|
735
|
+
});
|
|
736
|
+
break;
|
|
369
737
|
case "tool-call":
|
|
370
738
|
if (message.role === "assistant") {
|
|
371
739
|
input.push({
|
|
@@ -376,6 +744,15 @@ const toResponsesInput = (messages) => {
|
|
|
376
744
|
});
|
|
377
745
|
}
|
|
378
746
|
break;
|
|
747
|
+
case "provider-data":
|
|
748
|
+
if (message.role === "assistant" &&
|
|
749
|
+
part.provider === "qwen" &&
|
|
750
|
+
part.data &&
|
|
751
|
+
typeof part.data === "object" &&
|
|
752
|
+
typeof part.data.type === "string") {
|
|
753
|
+
input.push(part.data);
|
|
754
|
+
}
|
|
755
|
+
break;
|
|
379
756
|
}
|
|
380
757
|
}
|
|
381
758
|
if (content.length) {
|
|
@@ -553,17 +930,18 @@ const streamResponses = async function* (response) {
|
|
|
553
930
|
}
|
|
554
931
|
if (type === "response.completed" || type === "response.failed" || type === "response.incomplete") {
|
|
555
932
|
const responseData = json.response ?? {};
|
|
933
|
+
if (typeof responseData.id === "string") {
|
|
934
|
+
yield {
|
|
935
|
+
type: "provider-data",
|
|
936
|
+
provider: "qwen",
|
|
937
|
+
data: { responseId: responseData.id }
|
|
938
|
+
};
|
|
939
|
+
}
|
|
556
940
|
yield {
|
|
557
941
|
type: "finish",
|
|
558
942
|
finishReason: normalizeResponsesFinishReason(responseData.status, sawToolCalls),
|
|
559
943
|
providerFinishReason: responseData.status,
|
|
560
|
-
usage: responseData.usage
|
|
561
|
-
? {
|
|
562
|
-
inputTokens: responseData.usage.input_tokens,
|
|
563
|
-
outputTokens: responseData.usage.output_tokens,
|
|
564
|
-
totalTokens: responseData.usage.total_tokens
|
|
565
|
-
}
|
|
566
|
-
: undefined
|
|
944
|
+
usage: mapResponsesUsage(responseData.usage)
|
|
567
945
|
};
|
|
568
946
|
}
|
|
569
947
|
}
|
|
@@ -583,14 +961,17 @@ class QwenLanguageModel {
|
|
|
583
961
|
this.capabilities = qwenLanguageCapabilities(modelId);
|
|
584
962
|
}
|
|
585
963
|
async generate(input) {
|
|
964
|
+
if (isQwenOmniLanguageModel(this.modelId)) {
|
|
965
|
+
throw new UnsupportedFeatureError(`Qwen Omni model "${this.modelId}" is streaming-only. Use streamText() instead of generateText().`);
|
|
966
|
+
}
|
|
586
967
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
587
968
|
const providerOptions = { ...(input.providerOptions ?? {}) };
|
|
588
|
-
const apiMode = providerOptions.apiMode ?? "
|
|
969
|
+
const apiMode = resolveApiMode(this.modelId, providerOptions.apiMode ?? "auto", input);
|
|
589
970
|
delete providerOptions.apiMode;
|
|
590
971
|
try {
|
|
591
972
|
if (apiMode === "responses") {
|
|
592
973
|
const previousResponse = getProviderResponseId(input.messages);
|
|
593
|
-
const responseProviderOptions =
|
|
974
|
+
const responseProviderOptions = stripResponsesRequestOptions(providerOptions);
|
|
594
975
|
const messages = previousResponse && previousResponse.index < input.messages.length - 1
|
|
595
976
|
? input.messages.slice(previousResponse.index + 1)
|
|
596
977
|
: input.messages;
|
|
@@ -603,12 +984,10 @@ class QwenLanguageModel {
|
|
|
603
984
|
model: this.modelId,
|
|
604
985
|
...(previousResponse ? { previous_response_id: previousResponse.responseId } : {}),
|
|
605
986
|
...(messages.length ? { input: toResponsesInput(messages) } : {}),
|
|
606
|
-
...(input.structuredOutput?.mode === "native" ? { response_format: mapStructuredOutput(input) } : {}),
|
|
607
987
|
tools: mapResponsesTools(input.tools),
|
|
608
|
-
tool_choice:
|
|
988
|
+
tool_choice: mapResponsesToolChoice(input.toolChoice, input.tools),
|
|
609
989
|
temperature: input.temperature,
|
|
610
|
-
|
|
611
|
-
...mapReasoning(input),
|
|
990
|
+
...mapResponsesReasoning(input),
|
|
612
991
|
stream: false
|
|
613
992
|
})
|
|
614
993
|
}), input);
|
|
@@ -623,29 +1002,25 @@ class QwenLanguageModel {
|
|
|
623
1002
|
.join(""),
|
|
624
1003
|
finishReason: normalizeResponsesFinishReason(json.status, hasToolCalls),
|
|
625
1004
|
providerFinishReason: json.status,
|
|
626
|
-
usage:
|
|
627
|
-
inputTokens: json.usage?.input_tokens,
|
|
628
|
-
outputTokens: json.usage?.output_tokens,
|
|
629
|
-
totalTokens: json.usage?.total_tokens
|
|
630
|
-
},
|
|
1005
|
+
usage: mapResponsesUsage(json.usage),
|
|
631
1006
|
rawResponse: json
|
|
632
1007
|
};
|
|
633
1008
|
}
|
|
634
1009
|
const response = await withRetry(() => this.fetcher(`${this.baseURL}/chat/completions`, {
|
|
635
1010
|
method: "POST",
|
|
636
|
-
headers:
|
|
1011
|
+
headers: providerHeaders(this.apiKey, providerOptions),
|
|
637
1012
|
signal,
|
|
638
1013
|
body: JSON.stringify({
|
|
639
|
-
...providerOptions,
|
|
1014
|
+
...stripHeaderOptions(providerOptions),
|
|
640
1015
|
model: this.modelId,
|
|
641
|
-
messages: mapMessages(input
|
|
1016
|
+
messages: mapMessages(withStructuredOutputMessages(input)),
|
|
642
1017
|
tools: mapChatTools(input.tools),
|
|
643
|
-
tool_choice:
|
|
1018
|
+
tool_choice: mapChatToolChoice(input.toolChoice),
|
|
644
1019
|
response_format: mapStructuredOutput(input),
|
|
645
1020
|
temperature: input.temperature,
|
|
646
1021
|
max_tokens: input.maxTokens,
|
|
647
1022
|
stream: false,
|
|
648
|
-
...
|
|
1023
|
+
...mapChatReasoning(input)
|
|
649
1024
|
})
|
|
650
1025
|
}), input);
|
|
651
1026
|
const json = await parseJson(response);
|
|
@@ -660,11 +1035,7 @@ class QwenLanguageModel {
|
|
|
660
1035
|
.join(""),
|
|
661
1036
|
finishReason: normalizeFinishReason(choice?.finish_reason),
|
|
662
1037
|
providerFinishReason: choice?.finish_reason,
|
|
663
|
-
usage:
|
|
664
|
-
inputTokens: json.usage?.prompt_tokens,
|
|
665
|
-
outputTokens: json.usage?.completion_tokens,
|
|
666
|
-
totalTokens: json.usage?.total_tokens
|
|
667
|
-
},
|
|
1038
|
+
usage: mapChatUsage(json.usage),
|
|
668
1039
|
rawResponse: json
|
|
669
1040
|
};
|
|
670
1041
|
}
|
|
@@ -675,10 +1046,14 @@ class QwenLanguageModel {
|
|
|
675
1046
|
async stream(input) {
|
|
676
1047
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
677
1048
|
const providerOptions = { ...(input.providerOptions ?? {}) };
|
|
678
|
-
const apiMode = providerOptions.apiMode ?? "
|
|
1049
|
+
const apiMode = resolveApiMode(this.modelId, providerOptions.apiMode ?? "auto", input);
|
|
679
1050
|
delete providerOptions.apiMode;
|
|
680
1051
|
if (apiMode === "responses") {
|
|
681
|
-
const
|
|
1052
|
+
const previousResponse = getProviderResponseId(input.messages);
|
|
1053
|
+
const responseProviderOptions = stripResponsesRequestOptions(providerOptions);
|
|
1054
|
+
const messages = previousResponse && previousResponse.index < input.messages.length - 1
|
|
1055
|
+
? input.messages.slice(previousResponse.index + 1)
|
|
1056
|
+
: input.messages;
|
|
682
1057
|
const response = await withRetry(() => this.fetcher(`${this.baseURL}/responses`, {
|
|
683
1058
|
method: "POST",
|
|
684
1059
|
headers: providerHeaders(this.apiKey, providerOptions),
|
|
@@ -686,13 +1061,12 @@ class QwenLanguageModel {
|
|
|
686
1061
|
body: JSON.stringify({
|
|
687
1062
|
...responseProviderOptions,
|
|
688
1063
|
model: this.modelId,
|
|
689
|
-
|
|
690
|
-
...(
|
|
1064
|
+
...(previousResponse ? { previous_response_id: previousResponse.responseId } : {}),
|
|
1065
|
+
...(messages.length ? { input: toResponsesInput(messages) } : {}),
|
|
691
1066
|
tools: mapResponsesTools(input.tools),
|
|
692
|
-
tool_choice:
|
|
1067
|
+
tool_choice: mapResponsesToolChoice(input.toolChoice, input.tools),
|
|
693
1068
|
temperature: input.temperature,
|
|
694
|
-
|
|
695
|
-
...mapReasoning(input),
|
|
1069
|
+
...mapResponsesReasoning(input),
|
|
696
1070
|
stream: true
|
|
697
1071
|
})
|
|
698
1072
|
}), input);
|
|
@@ -707,20 +1081,21 @@ class QwenLanguageModel {
|
|
|
707
1081
|
}
|
|
708
1082
|
const response = await withRetry(() => this.fetcher(`${this.baseURL}/chat/completions`, {
|
|
709
1083
|
method: "POST",
|
|
710
|
-
headers:
|
|
1084
|
+
headers: providerHeaders(this.apiKey, providerOptions),
|
|
711
1085
|
signal,
|
|
712
1086
|
body: JSON.stringify({
|
|
713
|
-
...providerOptions,
|
|
1087
|
+
...stripHeaderOptions(providerOptions),
|
|
714
1088
|
model: this.modelId,
|
|
715
|
-
|
|
1089
|
+
modalities: isQwenOmniLanguageModel(this.modelId) ? providerOptions.modalities ?? ["text"] : undefined,
|
|
1090
|
+
messages: mapMessages(withStructuredOutputMessages(input)),
|
|
716
1091
|
tools: mapChatTools(input.tools),
|
|
717
|
-
tool_choice:
|
|
1092
|
+
tool_choice: mapChatToolChoice(input.toolChoice),
|
|
718
1093
|
response_format: mapStructuredOutput(input),
|
|
719
1094
|
temperature: input.temperature,
|
|
720
1095
|
max_tokens: input.maxTokens,
|
|
721
1096
|
stream: true,
|
|
722
1097
|
stream_options: { include_usage: true },
|
|
723
|
-
...
|
|
1098
|
+
...mapChatReasoning(input)
|
|
724
1099
|
})
|
|
725
1100
|
}), input);
|
|
726
1101
|
return (async function* () {
|
|
@@ -747,37 +1122,40 @@ class QwenLanguageModel {
|
|
|
747
1122
|
yield { type: "text-delta", textDelta: delta.content };
|
|
748
1123
|
}
|
|
749
1124
|
for (const toolCall of delta?.tool_calls ?? []) {
|
|
750
|
-
const
|
|
751
|
-
const existing = toolBuffers.get(
|
|
1125
|
+
const index = Number(toolCall.index ?? 0);
|
|
1126
|
+
const existing = toolBuffers.get(index) ?? {
|
|
1127
|
+
id: toolCall.id ?? `${index}`,
|
|
752
1128
|
name: toolCall.function?.name ?? "",
|
|
753
|
-
args: ""
|
|
1129
|
+
args: "",
|
|
1130
|
+
emitted: false
|
|
754
1131
|
};
|
|
1132
|
+
existing.id = toolCall.id ?? existing.id;
|
|
755
1133
|
existing.name ||= toolCall.function?.name ?? "";
|
|
756
1134
|
existing.args += toolCall.function?.arguments ?? "";
|
|
757
|
-
toolBuffers.set(
|
|
758
|
-
if (choice?.finish_reason === "tool_calls") {
|
|
759
|
-
yield {
|
|
760
|
-
type: "tool-call",
|
|
761
|
-
toolCall: {
|
|
762
|
-
id,
|
|
763
|
-
name: existing.name,
|
|
764
|
-
input: JSON.parse(existing.args || "{}")
|
|
765
|
-
}
|
|
766
|
-
};
|
|
767
|
-
}
|
|
1135
|
+
toolBuffers.set(index, existing);
|
|
768
1136
|
}
|
|
769
1137
|
if (choice?.finish_reason) {
|
|
1138
|
+
if (choice.finish_reason === "tool_calls") {
|
|
1139
|
+
for (const toolCall of toolBuffers.values()) {
|
|
1140
|
+
if (toolCall.emitted) {
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
toolCall.emitted = true;
|
|
1144
|
+
yield {
|
|
1145
|
+
type: "tool-call",
|
|
1146
|
+
toolCall: {
|
|
1147
|
+
id: toolCall.id,
|
|
1148
|
+
name: toolCall.name,
|
|
1149
|
+
input: JSON.parse(toolCall.args || "{}")
|
|
1150
|
+
}
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
770
1154
|
yield {
|
|
771
1155
|
type: "finish",
|
|
772
1156
|
finishReason: normalizeFinishReason(choice.finish_reason),
|
|
773
1157
|
providerFinishReason: choice.finish_reason,
|
|
774
|
-
usage: json.usage
|
|
775
|
-
? {
|
|
776
|
-
inputTokens: json.usage.prompt_tokens,
|
|
777
|
-
outputTokens: json.usage.completion_tokens,
|
|
778
|
-
totalTokens: json.usage.total_tokens
|
|
779
|
-
}
|
|
780
|
-
: undefined
|
|
1158
|
+
usage: mapChatUsage(json.usage)
|
|
781
1159
|
};
|
|
782
1160
|
}
|
|
783
1161
|
}
|
|
@@ -834,6 +1212,67 @@ class QwenEmbeddingModel {
|
|
|
834
1212
|
}
|
|
835
1213
|
}
|
|
836
1214
|
}
|
|
1215
|
+
const toMultimodalEmbeddingContent = (value) => {
|
|
1216
|
+
if (typeof value === "string") {
|
|
1217
|
+
return { text: value };
|
|
1218
|
+
}
|
|
1219
|
+
if (value.mediaType.startsWith("image/")) {
|
|
1220
|
+
return { image: mediaValue(value) };
|
|
1221
|
+
}
|
|
1222
|
+
if (value.mediaType.startsWith("video/")) {
|
|
1223
|
+
if (!value.uri) {
|
|
1224
|
+
throw new UnsupportedFeatureError("Qwen multimodal embedding requires a public URL for video input.");
|
|
1225
|
+
}
|
|
1226
|
+
return { video: value.uri };
|
|
1227
|
+
}
|
|
1228
|
+
throw new UnsupportedFeatureError(`Qwen multimodal embedding supports text, image, and video inputs, not "${value.mediaType}".`);
|
|
1229
|
+
};
|
|
1230
|
+
class QwenMultimodalEmbeddingModelImpl {
|
|
1231
|
+
modelId;
|
|
1232
|
+
apiKey;
|
|
1233
|
+
taskBaseURL;
|
|
1234
|
+
fetcher;
|
|
1235
|
+
provider = "qwen";
|
|
1236
|
+
capabilities = {
|
|
1237
|
+
...embeddingCapabilities,
|
|
1238
|
+
vision: true
|
|
1239
|
+
};
|
|
1240
|
+
constructor(modelId, apiKey, taskBaseURL, fetcher) {
|
|
1241
|
+
this.modelId = modelId;
|
|
1242
|
+
this.apiKey = apiKey;
|
|
1243
|
+
this.taskBaseURL = taskBaseURL;
|
|
1244
|
+
this.fetcher = fetcher;
|
|
1245
|
+
}
|
|
1246
|
+
async embed(input) {
|
|
1247
|
+
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1248
|
+
try {
|
|
1249
|
+
const response = await withRetry(() => this.fetcher(`${this.taskBaseURL}/services/embeddings/multimodal-embedding/multimodal-embedding`, {
|
|
1250
|
+
method: "POST",
|
|
1251
|
+
headers: jsonHeaders(this.apiKey),
|
|
1252
|
+
signal,
|
|
1253
|
+
body: JSON.stringify({
|
|
1254
|
+
model: this.modelId,
|
|
1255
|
+
input: { contents: input.values.map(toMultimodalEmbeddingContent) },
|
|
1256
|
+
...(input.providerOptions && Object.keys(input.providerOptions).length
|
|
1257
|
+
? { parameters: input.providerOptions }
|
|
1258
|
+
: {})
|
|
1259
|
+
})
|
|
1260
|
+
}), input);
|
|
1261
|
+
const json = await parseJson(response);
|
|
1262
|
+
return {
|
|
1263
|
+
embeddings: (json.output?.embeddings ?? []).map((entry) => entry.embedding),
|
|
1264
|
+
usage: {
|
|
1265
|
+
inputTokens: json.usage?.input_tokens,
|
|
1266
|
+
totalTokens: json.usage?.total_tokens
|
|
1267
|
+
},
|
|
1268
|
+
rawResponse: json
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
finally {
|
|
1272
|
+
cleanup();
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
837
1276
|
const normalizeUploadedFile = (json) => ({
|
|
838
1277
|
name: json.id ?? json.name ?? json.file_id ?? "",
|
|
839
1278
|
uri: json.url ?? json.uri,
|
|
@@ -844,14 +1283,6 @@ const normalizeUploadedFile = (json) => ({
|
|
|
844
1283
|
rawResponse: json,
|
|
845
1284
|
providerMetadata: json
|
|
846
1285
|
});
|
|
847
|
-
const normalizeFileSearchStore = (json) => ({
|
|
848
|
-
name: json.id ?? json.name ?? "",
|
|
849
|
-
displayName: json.name ?? json.display_name ?? json.displayName,
|
|
850
|
-
createTime: json.created_at ? String(json.created_at) : json.createTime,
|
|
851
|
-
updateTime: json.updated_at ? String(json.updated_at) : json.updateTime,
|
|
852
|
-
rawResponse: json,
|
|
853
|
-
providerMetadata: json
|
|
854
|
-
});
|
|
855
1286
|
const normalizeBatchJob = (json) => ({
|
|
856
1287
|
name: json.id ?? json.name ?? "",
|
|
857
1288
|
model: json.model,
|
|
@@ -863,10 +1294,13 @@ const normalizeBatchJob = (json) => ({
|
|
|
863
1294
|
providerMetadata: json
|
|
864
1295
|
});
|
|
865
1296
|
const normalizeOperation = (json) => ({
|
|
866
|
-
name: json.task_id ?? json.id ?? json.name ?? "",
|
|
1297
|
+
name: json.output?.task_id ?? json.task_id ?? json.id ?? json.name ?? "",
|
|
867
1298
|
done: json.output?.task_status ? ["SUCCEEDED", "FAILED", "CANCELED"].includes(json.output.task_status) : json.done,
|
|
868
1299
|
response: json.output ?? json.response,
|
|
869
|
-
error: json.error
|
|
1300
|
+
error: json.error ??
|
|
1301
|
+
(json.output?.task_status === "FAILED"
|
|
1302
|
+
? { message: json.output?.message ?? "Qwen task failed.", code: json.output?.code }
|
|
1303
|
+
: undefined),
|
|
870
1304
|
metadata: json.usage ?? json.metadata,
|
|
871
1305
|
rawResponse: json
|
|
872
1306
|
});
|
|
@@ -900,7 +1334,8 @@ class QwenFilesClient {
|
|
|
900
1334
|
async get(input) {
|
|
901
1335
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
902
1336
|
try {
|
|
903
|
-
const
|
|
1337
|
+
const fileId = encodeQwenResourceId(input.name, "file");
|
|
1338
|
+
const response = await withRetry(() => this.fetcher(`${this.baseURL}/files/${fileId}`, { method: "GET", headers: jsonHeaders(this.apiKey), signal }), input);
|
|
904
1339
|
return normalizeUploadedFile(await parseJson(response));
|
|
905
1340
|
}
|
|
906
1341
|
finally {
|
|
@@ -925,88 +1360,8 @@ class QwenFilesClient {
|
|
|
925
1360
|
async delete(input) {
|
|
926
1361
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
927
1362
|
try {
|
|
928
|
-
const
|
|
929
|
-
const
|
|
930
|
-
return { name: input.name, rawResponse: json };
|
|
931
|
-
}
|
|
932
|
-
finally {
|
|
933
|
-
cleanup();
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
class QwenFileSearchStoresClient {
|
|
938
|
-
apiKey;
|
|
939
|
-
baseURL;
|
|
940
|
-
fetcher;
|
|
941
|
-
constructor(apiKey, baseURL, fetcher) {
|
|
942
|
-
this.apiKey = apiKey;
|
|
943
|
-
this.baseURL = baseURL;
|
|
944
|
-
this.fetcher = fetcher;
|
|
945
|
-
}
|
|
946
|
-
async create(input = {}) {
|
|
947
|
-
const { signal, cleanup } = withTimeoutSignal(input);
|
|
948
|
-
try {
|
|
949
|
-
const response = await withRetry(() => this.fetcher(`${this.baseURL}/file_search_stores`, {
|
|
950
|
-
method: "POST",
|
|
951
|
-
headers: jsonHeaders(this.apiKey),
|
|
952
|
-
signal,
|
|
953
|
-
body: JSON.stringify({ ...input.providerOptions, name: input.displayName })
|
|
954
|
-
}), input);
|
|
955
|
-
return normalizeFileSearchStore(await parseJson(response));
|
|
956
|
-
}
|
|
957
|
-
finally {
|
|
958
|
-
cleanup();
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
async upload(input) {
|
|
962
|
-
const files = new QwenFilesClient(this.apiKey, this.baseURL, this.fetcher);
|
|
963
|
-
const file = await files.upload(input);
|
|
964
|
-
return this.importFile({ ...input, fileName: file.name });
|
|
965
|
-
}
|
|
966
|
-
async importFile(input) {
|
|
967
|
-
const { signal, cleanup } = withTimeoutSignal(input);
|
|
968
|
-
try {
|
|
969
|
-
const response = await withRetry(() => this.fetcher(`${this.baseURL}/file_search_stores/${input.storeName}/files`, {
|
|
970
|
-
method: "POST",
|
|
971
|
-
headers: jsonHeaders(this.apiKey),
|
|
972
|
-
signal,
|
|
973
|
-
body: JSON.stringify({ ...input.providerOptions, file_id: input.fileName })
|
|
974
|
-
}), input);
|
|
975
|
-
return normalizeOperation(await parseJson(response));
|
|
976
|
-
}
|
|
977
|
-
finally {
|
|
978
|
-
cleanup();
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
async get(input) {
|
|
982
|
-
const { signal, cleanup } = withTimeoutSignal(input);
|
|
983
|
-
try {
|
|
984
|
-
const response = await withRetry(() => this.fetcher(`${this.baseURL}/file_search_stores/${input.name}`, { method: "GET", headers: jsonHeaders(this.apiKey), signal }), input);
|
|
985
|
-
return normalizeFileSearchStore(await parseJson(response));
|
|
986
|
-
}
|
|
987
|
-
finally {
|
|
988
|
-
cleanup();
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
async list(input = {}) {
|
|
992
|
-
const { signal, cleanup } = withTimeoutSignal(input);
|
|
993
|
-
try {
|
|
994
|
-
const response = await withRetry(() => this.fetcher(appendQuery(`${this.baseURL}/file_search_stores`, { limit: input.pageSize, after: input.pageToken }), { method: "GET", headers: jsonHeaders(this.apiKey), signal }), input);
|
|
995
|
-
const json = await parseJson(response);
|
|
996
|
-
return {
|
|
997
|
-
stores: (json.data ?? json.file_search_stores ?? json.fileSearchStores ?? []).map(normalizeFileSearchStore),
|
|
998
|
-
nextPageToken: json.next ?? json.nextPageToken,
|
|
999
|
-
rawResponse: json
|
|
1000
|
-
};
|
|
1001
|
-
}
|
|
1002
|
-
finally {
|
|
1003
|
-
cleanup();
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
async delete(input) {
|
|
1007
|
-
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1008
|
-
try {
|
|
1009
|
-
const response = await withRetry(() => this.fetcher(`${this.baseURL}/file_search_stores/${input.name}`, { method: "DELETE", headers: jsonHeaders(this.apiKey), signal }), input);
|
|
1363
|
+
const fileId = encodeQwenResourceId(input.name, "file");
|
|
1364
|
+
const response = await withRetry(() => this.fetcher(`${this.baseURL}/files/${fileId}`, { method: "DELETE", headers: jsonHeaders(this.apiKey), signal }), input);
|
|
1010
1365
|
const json = await parseJson(response);
|
|
1011
1366
|
return { name: input.name, rawResponse: json };
|
|
1012
1367
|
}
|
|
@@ -1025,6 +1380,9 @@ class QwenBatchesClient {
|
|
|
1025
1380
|
this.fetcher = fetcher;
|
|
1026
1381
|
}
|
|
1027
1382
|
async create(input) {
|
|
1383
|
+
if (!input.fileName) {
|
|
1384
|
+
throw new ConfigurationError("Qwen batch creation requires a JSONL fileName uploaded with purpose \"batch\".");
|
|
1385
|
+
}
|
|
1028
1386
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1029
1387
|
try {
|
|
1030
1388
|
const response = await withRetry(() => this.fetcher(`${this.baseURL}/batches`, {
|
|
@@ -1036,9 +1394,7 @@ class QwenBatchesClient {
|
|
|
1036
1394
|
input_file_id: input.fileName,
|
|
1037
1395
|
endpoint: input.providerOptions?.endpoint ?? "/v1/chat/completions",
|
|
1038
1396
|
completion_window: input.providerOptions?.completion_window ?? "24h",
|
|
1039
|
-
metadata: input.displayName ? { displayName: input.displayName } : undefined
|
|
1040
|
-
model: input.modelId,
|
|
1041
|
-
requests: input.requests
|
|
1397
|
+
metadata: input.displayName ? { displayName: input.displayName } : undefined
|
|
1042
1398
|
})
|
|
1043
1399
|
}), input);
|
|
1044
1400
|
return normalizeBatchJob(await parseJson(response));
|
|
@@ -1050,7 +1406,8 @@ class QwenBatchesClient {
|
|
|
1050
1406
|
async get(input) {
|
|
1051
1407
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1052
1408
|
try {
|
|
1053
|
-
const
|
|
1409
|
+
const batchId = encodeQwenResourceId(input.name, "batch");
|
|
1410
|
+
const response = await withRetry(() => this.fetcher(`${this.baseURL}/batches/${batchId}`, { method: "GET", headers: jsonHeaders(this.apiKey), signal }), input);
|
|
1054
1411
|
return normalizeBatchJob(await parseJson(response));
|
|
1055
1412
|
}
|
|
1056
1413
|
finally {
|
|
@@ -1071,7 +1428,8 @@ class QwenBatchesClient {
|
|
|
1071
1428
|
async cancel(input) {
|
|
1072
1429
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1073
1430
|
try {
|
|
1074
|
-
const
|
|
1431
|
+
const batchId = encodeQwenResourceId(input.name, "batch");
|
|
1432
|
+
const response = await withRetry(() => this.fetcher(`${this.baseURL}/batches/${batchId}/cancel`, { method: "POST", headers: jsonHeaders(this.apiKey), signal }), input);
|
|
1075
1433
|
return normalizeBatchJob(await parseJson(response));
|
|
1076
1434
|
}
|
|
1077
1435
|
finally {
|
|
@@ -1081,7 +1439,8 @@ class QwenBatchesClient {
|
|
|
1081
1439
|
async delete(input) {
|
|
1082
1440
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1083
1441
|
try {
|
|
1084
|
-
const
|
|
1442
|
+
const batchId = encodeQwenResourceId(input.name, "batch");
|
|
1443
|
+
const response = await withRetry(() => this.fetcher(`${this.baseURL}/batches/${batchId}`, { method: "DELETE", headers: jsonHeaders(this.apiKey), signal }), input);
|
|
1085
1444
|
const json = await parseJson(response);
|
|
1086
1445
|
return { name: input.name, rawResponse: json };
|
|
1087
1446
|
}
|
|
@@ -1106,29 +1465,59 @@ class QwenTranscriptionModel {
|
|
|
1106
1465
|
this.responseLimits = responseLimits;
|
|
1107
1466
|
}
|
|
1108
1467
|
async transcribe(input) {
|
|
1468
|
+
if (input.prompt) {
|
|
1469
|
+
throw new UnsupportedFeatureError("Qwen3-ASR-Flash does not expose the common transcription prompt field; use providerOptions for documented asr_options.");
|
|
1470
|
+
}
|
|
1109
1471
|
const { signal, cleanup, abort } = withTimeoutSignal(input);
|
|
1110
|
-
const
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
form.set("file", await createFile(input.audio.data, input.audio.mediaType, input.audio.filename ?? "audio"));
|
|
1119
|
-
if (input.prompt)
|
|
1120
|
-
form.set("prompt", input.prompt);
|
|
1121
|
-
if (input.language)
|
|
1122
|
-
form.set("language", input.language);
|
|
1472
|
+
const providerOptions = { ...(input.providerOptions ?? {}) };
|
|
1473
|
+
const nestedOptions = providerOptions.asr_options && typeof providerOptions.asr_options === "object"
|
|
1474
|
+
? providerOptions.asr_options
|
|
1475
|
+
: {};
|
|
1476
|
+
delete providerOptions.asr_options;
|
|
1477
|
+
delete providerOptions.model;
|
|
1478
|
+
delete providerOptions.messages;
|
|
1479
|
+
delete providerOptions.stream;
|
|
1123
1480
|
try {
|
|
1124
|
-
const response = await withRetry(() => this.fetcher(`${this.baseURL}/
|
|
1481
|
+
const response = await withRetry(() => this.fetcher(`${this.baseURL}/chat/completions`, {
|
|
1482
|
+
method: "POST",
|
|
1483
|
+
headers: jsonHeaders(this.apiKey),
|
|
1484
|
+
signal,
|
|
1485
|
+
body: JSON.stringify({
|
|
1486
|
+
model: this.modelId,
|
|
1487
|
+
messages: [
|
|
1488
|
+
{
|
|
1489
|
+
role: "user",
|
|
1490
|
+
content: [
|
|
1491
|
+
{
|
|
1492
|
+
type: "input_audio",
|
|
1493
|
+
input_audio: {
|
|
1494
|
+
data: toDataURL(input.audio.data, input.audio.mediaType)
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
]
|
|
1498
|
+
}
|
|
1499
|
+
],
|
|
1500
|
+
stream: false,
|
|
1501
|
+
asr_options: {
|
|
1502
|
+
...providerOptions,
|
|
1503
|
+
...nestedOptions,
|
|
1504
|
+
...(input.language ? { language: input.language } : {})
|
|
1505
|
+
}
|
|
1506
|
+
})
|
|
1507
|
+
}), input);
|
|
1125
1508
|
const json = await parseJson(response, {
|
|
1126
1509
|
maxBytes: this.responseLimits.transcriptionBytes,
|
|
1127
1510
|
errorBodyBytes: this.responseLimits.errorBodyBytes,
|
|
1128
|
-
endpoint: "
|
|
1511
|
+
endpoint: "chat/completions",
|
|
1129
1512
|
abort
|
|
1130
1513
|
});
|
|
1131
|
-
|
|
1514
|
+
const content = json.choices?.[0]?.message?.content;
|
|
1515
|
+
const text = Array.isArray(content)
|
|
1516
|
+
? content.map((part) => part?.text ?? "").join("")
|
|
1517
|
+
: typeof content === "string"
|
|
1518
|
+
? content
|
|
1519
|
+
: "";
|
|
1520
|
+
return { text, rawResponse: json };
|
|
1132
1521
|
}
|
|
1133
1522
|
finally {
|
|
1134
1523
|
cleanup();
|
|
@@ -1138,73 +1527,106 @@ class QwenTranscriptionModel {
|
|
|
1138
1527
|
class QwenSpeechModel {
|
|
1139
1528
|
modelId;
|
|
1140
1529
|
apiKey;
|
|
1141
|
-
|
|
1530
|
+
taskBaseURL;
|
|
1142
1531
|
fetcher;
|
|
1143
1532
|
responseLimits;
|
|
1533
|
+
speechAudioURLValidator;
|
|
1534
|
+
speechAudioMaxRedirects;
|
|
1144
1535
|
provider = "qwen";
|
|
1145
1536
|
capabilities = speechCapabilities;
|
|
1146
|
-
constructor(modelId, apiKey,
|
|
1537
|
+
constructor(modelId, apiKey, taskBaseURL, fetcher, responseLimits = resolveAudioResponseLimits(), speechAudioURLValidator, speechAudioMaxRedirects = 3) {
|
|
1147
1538
|
this.modelId = modelId;
|
|
1148
1539
|
this.apiKey = apiKey;
|
|
1149
|
-
this.
|
|
1540
|
+
this.taskBaseURL = taskBaseURL;
|
|
1150
1541
|
this.fetcher = fetcher;
|
|
1151
1542
|
this.responseLimits = responseLimits;
|
|
1543
|
+
this.speechAudioURLValidator = speechAudioURLValidator;
|
|
1544
|
+
this.speechAudioMaxRedirects = speechAudioMaxRedirects;
|
|
1152
1545
|
}
|
|
1153
1546
|
async generateSpeech(input) {
|
|
1154
1547
|
const { signal, cleanup, abort } = withTimeoutSignal(input);
|
|
1548
|
+
const providerOptions = { ...(input.providerOptions ?? {}) };
|
|
1549
|
+
const nestedInput = providerOptions.input && typeof providerOptions.input === "object"
|
|
1550
|
+
? providerOptions.input
|
|
1551
|
+
: {};
|
|
1552
|
+
const parameters = providerOptions.parameters && typeof providerOptions.parameters === "object"
|
|
1553
|
+
? providerOptions.parameters
|
|
1554
|
+
: undefined;
|
|
1555
|
+
delete providerOptions.input;
|
|
1556
|
+
delete providerOptions.parameters;
|
|
1557
|
+
delete providerOptions.model;
|
|
1155
1558
|
try {
|
|
1156
|
-
const response = await withRetry(() => this.fetcher(`${this.
|
|
1559
|
+
const response = await withRetry(() => this.fetcher(`${this.taskBaseURL}/services/aigc/multimodal-generation/generation`, {
|
|
1157
1560
|
method: "POST",
|
|
1158
1561
|
headers: jsonHeaders(this.apiKey),
|
|
1159
1562
|
signal,
|
|
1160
|
-
body: JSON.stringify({
|
|
1563
|
+
body: JSON.stringify({
|
|
1564
|
+
model: this.modelId,
|
|
1565
|
+
input: {
|
|
1566
|
+
...providerOptions,
|
|
1567
|
+
...nestedInput,
|
|
1568
|
+
text: input.input,
|
|
1569
|
+
voice: input.voice ?? "Cherry"
|
|
1570
|
+
},
|
|
1571
|
+
...(parameters ? { parameters } : {})
|
|
1572
|
+
})
|
|
1161
1573
|
}), input);
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1574
|
+
const json = await parseJson(response, {
|
|
1575
|
+
maxBytes: this.responseLimits.speechJsonBytes,
|
|
1576
|
+
errorBodyBytes: this.responseLimits.errorBodyBytes,
|
|
1577
|
+
endpoint: "multimodal-generation/generation",
|
|
1578
|
+
abort
|
|
1579
|
+
});
|
|
1580
|
+
const audio = json.audio?.data ?? json.output?.audio?.data;
|
|
1581
|
+
const audioURL = json.audio?.url ?? json.output?.audio?.url;
|
|
1582
|
+
const rawResponse = {
|
|
1583
|
+
...json,
|
|
1584
|
+
...(json.audio && typeof json.audio === "object"
|
|
1585
|
+
? { audio: { ...json.audio, data: undefined, data_omitted: audio ? true : undefined } }
|
|
1586
|
+
: {}),
|
|
1587
|
+
...(json.output && typeof json.output === "object"
|
|
1588
|
+
? {
|
|
1589
|
+
output: {
|
|
1590
|
+
...json.output,
|
|
1591
|
+
...(json.output.audio && typeof json.output.audio === "object"
|
|
1592
|
+
? { audio: { ...json.output.audio, data: undefined, data_omitted: audio ? true : undefined } }
|
|
1593
|
+
: {})
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
: {})
|
|
1597
|
+
};
|
|
1598
|
+
if (audio) {
|
|
1170
1599
|
const decodedAudio = decodeBase64WithLimit(String(audio), {
|
|
1171
1600
|
maxBytes: this.responseLimits.speechBytes,
|
|
1172
1601
|
provider: "qwen",
|
|
1173
|
-
endpoint: "
|
|
1602
|
+
endpoint: "multimodal-generation/generation",
|
|
1174
1603
|
abort
|
|
1175
1604
|
});
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
: {}),
|
|
1181
|
-
...(json.output && typeof json.output === "object"
|
|
1182
|
-
? {
|
|
1183
|
-
output: {
|
|
1184
|
-
...json.output,
|
|
1185
|
-
...(json.output.audio && typeof json.output.audio === "object"
|
|
1186
|
-
? { audio: { ...json.output.audio, data: undefined, data_omitted: true } }
|
|
1187
|
-
: {})
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
: {})
|
|
1605
|
+
return {
|
|
1606
|
+
audio: decodedAudio,
|
|
1607
|
+
mediaType: json.audio?.media_type ?? json.output?.audio?.media_type ?? "audio/pcm",
|
|
1608
|
+
rawResponse
|
|
1191
1609
|
};
|
|
1192
|
-
return { audio: decodedAudio, mediaType: json.audio?.media_type ?? json.output?.audio?.media_type ?? "audio/mpeg", rawResponse };
|
|
1193
1610
|
}
|
|
1194
|
-
if (
|
|
1195
|
-
|
|
1196
|
-
|
|
1611
|
+
if (typeof audioURL !== "string" || !/^https?:\/\//i.test(audioURL)) {
|
|
1612
|
+
throw new ProviderHTTPError("Qwen speech response did not contain audio data or a downloadable audio URL.", 502);
|
|
1613
|
+
}
|
|
1614
|
+
const audioResponse = await withRetry(() => downloadQwenSpeechAudio(audioURL, this.fetcher, signal, this.speechAudioURLValidator, this.speechAudioMaxRedirects), input);
|
|
1615
|
+
if (!audioResponse.ok) {
|
|
1616
|
+
const body = await readErrorBodyWithLimit(audioResponse, this.responseLimits.errorBodyBytes);
|
|
1617
|
+
throw new ProviderHTTPError(`Qwen audio download failed with status ${audioResponse.status}.`, audioResponse.status, {
|
|
1197
1618
|
responseBody: body
|
|
1198
1619
|
});
|
|
1199
1620
|
}
|
|
1200
1621
|
return {
|
|
1201
|
-
audio: await readBodyWithLimit(
|
|
1622
|
+
audio: await readBodyWithLimit(audioResponse, {
|
|
1202
1623
|
maxBytes: this.responseLimits.speechBytes,
|
|
1203
1624
|
provider: "qwen",
|
|
1204
|
-
endpoint: "audio
|
|
1625
|
+
endpoint: "speech-audio-download",
|
|
1205
1626
|
abort
|
|
1206
1627
|
}),
|
|
1207
|
-
mediaType:
|
|
1628
|
+
mediaType: audioResponse.headers.get("content-type") ?? json.output?.audio?.media_type ?? "audio/wav",
|
|
1629
|
+
rawResponse
|
|
1208
1630
|
};
|
|
1209
1631
|
}
|
|
1210
1632
|
finally {
|
|
@@ -1213,7 +1635,7 @@ class QwenSpeechModel {
|
|
|
1213
1635
|
}
|
|
1214
1636
|
}
|
|
1215
1637
|
const normalizeGeneratedMedia = (item, fallbackMimeType) => ({
|
|
1216
|
-
uri: item.url ?? item.uri,
|
|
1638
|
+
uri: item.url ?? item.uri ?? item.image ?? item.video_url,
|
|
1217
1639
|
data: item.b64_json || item.base64 ? Uint8Array.from(Buffer.from(item.b64_json ?? item.base64, "base64")) : undefined,
|
|
1218
1640
|
mediaType: item.mime_type ?? item.mediaType ?? fallbackMimeType,
|
|
1219
1641
|
text: item.revised_prompt ?? item.text,
|
|
@@ -1222,15 +1644,13 @@ const normalizeGeneratedMedia = (item, fallbackMimeType) => ({
|
|
|
1222
1644
|
class QwenImageGenerationModel {
|
|
1223
1645
|
modelId;
|
|
1224
1646
|
apiKey;
|
|
1225
|
-
baseURL;
|
|
1226
1647
|
taskBaseURL;
|
|
1227
1648
|
fetcher;
|
|
1228
1649
|
provider = "qwen";
|
|
1229
1650
|
capabilities = imageGenerationCapabilities;
|
|
1230
|
-
constructor(modelId, apiKey,
|
|
1651
|
+
constructor(modelId, apiKey, taskBaseURL, fetcher) {
|
|
1231
1652
|
this.modelId = modelId;
|
|
1232
1653
|
this.apiKey = apiKey;
|
|
1233
|
-
this.baseURL = baseURL;
|
|
1234
1654
|
this.taskBaseURL = taskBaseURL;
|
|
1235
1655
|
this.fetcher = fetcher;
|
|
1236
1656
|
}
|
|
@@ -1238,25 +1658,53 @@ class QwenImageGenerationModel {
|
|
|
1238
1658
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1239
1659
|
const providerOptions = { ...(input.providerOptions ?? {}) };
|
|
1240
1660
|
delete providerOptions.endpoint;
|
|
1241
|
-
const
|
|
1661
|
+
const nestedParameters = providerOptions.parameters && typeof providerOptions.parameters === "object"
|
|
1662
|
+
? providerOptions.parameters
|
|
1663
|
+
: {};
|
|
1664
|
+
delete providerOptions.parameters;
|
|
1665
|
+
delete providerOptions.model;
|
|
1666
|
+
delete providerOptions.input;
|
|
1667
|
+
const endpoint = `${this.taskBaseURL}/services/aigc/multimodal-generation/generation`;
|
|
1668
|
+
const sizeFromAspectRatio = {
|
|
1669
|
+
"1:1": "2048*2048",
|
|
1670
|
+
"16:9": "1664*928",
|
|
1671
|
+
"9:16": "928*1664",
|
|
1672
|
+
"4:3": "1472*1104",
|
|
1673
|
+
"3:4": "1104*1472"
|
|
1674
|
+
};
|
|
1242
1675
|
try {
|
|
1243
1676
|
const response = await withRetry(() => this.fetcher(endpoint, {
|
|
1244
1677
|
method: "POST",
|
|
1245
1678
|
headers: jsonHeaders(this.apiKey),
|
|
1246
1679
|
signal,
|
|
1247
1680
|
body: JSON.stringify({
|
|
1248
|
-
...providerOptions,
|
|
1249
1681
|
model: this.modelId,
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1682
|
+
input: {
|
|
1683
|
+
messages: [
|
|
1684
|
+
{
|
|
1685
|
+
role: "user",
|
|
1686
|
+
content: [
|
|
1687
|
+
...(input.images ?? []).map((image) => ({ image: mediaValue(image) })),
|
|
1688
|
+
{ text: input.prompt }
|
|
1689
|
+
]
|
|
1690
|
+
}
|
|
1691
|
+
]
|
|
1692
|
+
},
|
|
1693
|
+
parameters: {
|
|
1694
|
+
...providerOptions,
|
|
1695
|
+
...nestedParameters,
|
|
1696
|
+
n: input.count,
|
|
1697
|
+
size: input.size ?? (input.aspectRatio ? sizeFromAspectRatio[input.aspectRatio] : undefined),
|
|
1698
|
+
negative_prompt: input.negativePrompt
|
|
1699
|
+
}
|
|
1256
1700
|
})
|
|
1257
1701
|
}), input);
|
|
1258
1702
|
const json = await parseJson(response);
|
|
1259
|
-
const data = json.
|
|
1703
|
+
const data = json.output?.choices?.flatMap((choice) => choice.message?.content ?? []) ??
|
|
1704
|
+
json.output?.results ??
|
|
1705
|
+
json.output?.images ??
|
|
1706
|
+
json.data ??
|
|
1707
|
+
[];
|
|
1260
1708
|
return {
|
|
1261
1709
|
images: data.map((item) => normalizeGeneratedMedia(item, input.outputMimeType ?? "image/png")),
|
|
1262
1710
|
text: json.output?.text,
|
|
@@ -1283,20 +1731,49 @@ class QwenVideoGenerationModel {
|
|
|
1283
1731
|
}
|
|
1284
1732
|
async generateVideo(input) {
|
|
1285
1733
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1734
|
+
const providerOptions = { ...(input.providerOptions ?? {}) };
|
|
1735
|
+
delete providerOptions.endpoint;
|
|
1736
|
+
const nestedInput = providerOptions.input && typeof providerOptions.input === "object"
|
|
1737
|
+
? providerOptions.input
|
|
1738
|
+
: {};
|
|
1739
|
+
const nestedParameters = providerOptions.parameters && typeof providerOptions.parameters === "object"
|
|
1740
|
+
? providerOptions.parameters
|
|
1741
|
+
: {};
|
|
1742
|
+
delete providerOptions.input;
|
|
1743
|
+
delete providerOptions.parameters;
|
|
1744
|
+
delete providerOptions.model;
|
|
1745
|
+
const isWan27 = /^wan2\.7(?:$|-)/i.test(this.modelId);
|
|
1286
1746
|
try {
|
|
1287
|
-
const response = await withRetry(() => this.fetcher(`${this.taskBaseURL}/services/aigc/video-generation/
|
|
1747
|
+
const response = await withRetry(() => this.fetcher(`${this.taskBaseURL}/services/aigc/video-generation/video-synthesis`, {
|
|
1288
1748
|
method: "POST",
|
|
1289
1749
|
headers: { ...jsonHeaders(this.apiKey), "X-DashScope-Async": "enable" },
|
|
1290
1750
|
signal,
|
|
1291
1751
|
body: JSON.stringify({
|
|
1292
|
-
...input.providerOptions,
|
|
1293
1752
|
model: this.modelId,
|
|
1294
|
-
input: {
|
|
1295
|
-
|
|
1753
|
+
input: {
|
|
1754
|
+
...nestedInput,
|
|
1755
|
+
prompt: input.prompt,
|
|
1756
|
+
negative_prompt: input.negativePrompt,
|
|
1757
|
+
...(input.image
|
|
1758
|
+
? isWan27
|
|
1759
|
+
? { media: [{ type: "first_frame", url: mediaValue(input.image) }] }
|
|
1760
|
+
: { img_url: mediaValue(input.image) }
|
|
1761
|
+
: {})
|
|
1762
|
+
},
|
|
1763
|
+
parameters: {
|
|
1764
|
+
...providerOptions,
|
|
1765
|
+
...nestedParameters,
|
|
1766
|
+
ratio: input.aspectRatio,
|
|
1767
|
+
duration: input.durationSeconds,
|
|
1768
|
+
n: input.count,
|
|
1769
|
+
output_storage_uri: input.outputStorageUri
|
|
1770
|
+
}
|
|
1296
1771
|
})
|
|
1297
1772
|
}), input);
|
|
1298
1773
|
const json = await parseJson(response);
|
|
1299
|
-
const items = json.output?.results ??
|
|
1774
|
+
const items = json.output?.results ??
|
|
1775
|
+
json.output?.videos ??
|
|
1776
|
+
(json.output?.video_url ? [{ url: json.output.video_url }] : []);
|
|
1300
1777
|
return {
|
|
1301
1778
|
videos: items.map((item) => normalizeGeneratedMedia(item, "video/mp4")),
|
|
1302
1779
|
operationName: json.output?.task_id ?? json.task_id,
|
|
@@ -1311,23 +1788,47 @@ class QwenVideoGenerationModel {
|
|
|
1311
1788
|
class QwenRerankModelImpl {
|
|
1312
1789
|
modelId;
|
|
1313
1790
|
apiKey;
|
|
1314
|
-
|
|
1791
|
+
taskBaseURL;
|
|
1315
1792
|
fetcher;
|
|
1316
1793
|
provider = "qwen";
|
|
1317
|
-
constructor(modelId, apiKey,
|
|
1794
|
+
constructor(modelId, apiKey, taskBaseURL, fetcher) {
|
|
1318
1795
|
this.modelId = modelId;
|
|
1319
1796
|
this.apiKey = apiKey;
|
|
1320
|
-
this.
|
|
1797
|
+
this.taskBaseURL = taskBaseURL;
|
|
1321
1798
|
this.fetcher = fetcher;
|
|
1322
1799
|
}
|
|
1323
1800
|
async rerank(input) {
|
|
1324
1801
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1802
|
+
const isTextRerank = /^qwen3-rerank(?:$|-)/i.test(this.modelId);
|
|
1803
|
+
if (isTextRerank &&
|
|
1804
|
+
(typeof input.query !== "string" || input.documents.some((document) => typeof document !== "string"))) {
|
|
1805
|
+
throw new UnsupportedFeatureError("qwen3-rerank accepts text queries and text documents only.");
|
|
1806
|
+
}
|
|
1807
|
+
const toRerankContent = (value) => {
|
|
1808
|
+
if (typeof value === "string") {
|
|
1809
|
+
return /^qwen3-vl-rerank(?:$|-)/i.test(this.modelId) ? { text: value } : value;
|
|
1810
|
+
}
|
|
1811
|
+
return toMultimodalEmbeddingContent(value);
|
|
1812
|
+
};
|
|
1325
1813
|
try {
|
|
1326
|
-
const response = await withRetry(() => this.fetcher(`${this.
|
|
1814
|
+
const response = await withRetry(() => this.fetcher(`${this.taskBaseURL}/services/rerank/text-rerank/text-rerank`, {
|
|
1327
1815
|
method: "POST",
|
|
1328
1816
|
headers: jsonHeaders(this.apiKey),
|
|
1329
1817
|
signal,
|
|
1330
|
-
body: JSON.stringify({
|
|
1818
|
+
body: JSON.stringify({
|
|
1819
|
+
model: this.modelId,
|
|
1820
|
+
input: {
|
|
1821
|
+
query: isTextRerank ? input.query : toRerankContent(input.query),
|
|
1822
|
+
documents: isTextRerank
|
|
1823
|
+
? input.documents
|
|
1824
|
+
: input.documents.map(toRerankContent)
|
|
1825
|
+
},
|
|
1826
|
+
parameters: {
|
|
1827
|
+
return_documents: true,
|
|
1828
|
+
...input.providerOptions,
|
|
1829
|
+
top_n: input.topN
|
|
1830
|
+
}
|
|
1831
|
+
})
|
|
1331
1832
|
}), input);
|
|
1332
1833
|
const json = await parseJson(response);
|
|
1333
1834
|
return {
|
|
@@ -1357,7 +1858,8 @@ class QwenTasksClientImpl {
|
|
|
1357
1858
|
async get(input) {
|
|
1358
1859
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1359
1860
|
try {
|
|
1360
|
-
const
|
|
1861
|
+
const taskId = encodeQwenResourceId(input.name, "task");
|
|
1862
|
+
const response = await withRetry(() => this.fetcher(`${this.taskBaseURL}/tasks/${taskId}`, { method: "GET", headers: jsonHeaders(this.apiKey), signal }), input);
|
|
1361
1863
|
return normalizeOperation(await parseJson(response));
|
|
1362
1864
|
}
|
|
1363
1865
|
finally {
|
|
@@ -1367,7 +1869,8 @@ class QwenTasksClientImpl {
|
|
|
1367
1869
|
async cancel(input) {
|
|
1368
1870
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1369
1871
|
try {
|
|
1370
|
-
const
|
|
1872
|
+
const taskId = encodeQwenResourceId(input.name, "task");
|
|
1873
|
+
const response = await withRetry(() => this.fetcher(`${this.taskBaseURL}/tasks/${taskId}/cancel`, { method: "POST", headers: jsonHeaders(this.apiKey), signal }), input);
|
|
1371
1874
|
return normalizeOperation(await parseJson(response));
|
|
1372
1875
|
}
|
|
1373
1876
|
finally {
|
|
@@ -1377,23 +1880,103 @@ class QwenTasksClientImpl {
|
|
|
1377
1880
|
}
|
|
1378
1881
|
const parseRealtimeEvent = (payload) => {
|
|
1379
1882
|
const type = String(payload.type ?? "");
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
if (type
|
|
1384
|
-
return [{ type: "realtime-
|
|
1883
|
+
const metadata = payload;
|
|
1884
|
+
const itemId = typeof payload.item_id === "string" ? payload.item_id : undefined;
|
|
1885
|
+
const responseId = typeof payload.response_id === "string" ? payload.response_id : undefined;
|
|
1886
|
+
if (type === "response.text.delta" && typeof payload.delta === "string") {
|
|
1887
|
+
return [{ type: "realtime-text-delta", textDelta: payload.delta, itemId, responseId, providerMetadata: metadata }];
|
|
1888
|
+
}
|
|
1889
|
+
if (type === "response.audio.delta" && typeof payload.delta === "string") {
|
|
1890
|
+
return [{
|
|
1891
|
+
type: "realtime-audio-output",
|
|
1892
|
+
audio: Uint8Array.from(Buffer.from(payload.delta, "base64")),
|
|
1893
|
+
mediaType: "audio/pcm",
|
|
1894
|
+
sampleRateHz: 24_000,
|
|
1895
|
+
channels: 1,
|
|
1896
|
+
itemId,
|
|
1897
|
+
responseId,
|
|
1898
|
+
providerMetadata: metadata
|
|
1899
|
+
}];
|
|
1900
|
+
}
|
|
1901
|
+
if (type === "response.audio_transcript.delta" && typeof payload.delta === "string") {
|
|
1902
|
+
return [{ type: "realtime-transcript", text: payload.delta, role: "assistant", isFinal: false, itemId, responseId, providerMetadata: metadata }];
|
|
1903
|
+
}
|
|
1904
|
+
if (type === "response.audio_transcript.done" && typeof payload.transcript === "string") {
|
|
1905
|
+
return [{ type: "realtime-transcript", text: payload.transcript, role: "assistant", isFinal: true, itemId, responseId, providerMetadata: metadata }];
|
|
1906
|
+
}
|
|
1907
|
+
if (type === "conversation.item.input_audio_transcription.delta") {
|
|
1908
|
+
const text = `${typeof payload.text === "string" ? payload.text : ""}${typeof payload.stash === "string" ? payload.stash : ""}`;
|
|
1909
|
+
return [{ type: "realtime-transcript", text, role: "user", isFinal: false, itemId, providerMetadata: metadata }];
|
|
1910
|
+
}
|
|
1911
|
+
if (type === "conversation.item.input_audio_transcription.completed" && typeof payload.transcript === "string") {
|
|
1912
|
+
return [{ type: "realtime-transcript", text: payload.transcript, role: "user", isFinal: true, itemId, providerMetadata: metadata }];
|
|
1913
|
+
}
|
|
1914
|
+
if (type === "response.function_call_arguments.done") {
|
|
1915
|
+
return [{
|
|
1916
|
+
type: "realtime-tool-call",
|
|
1917
|
+
toolCall: {
|
|
1918
|
+
id: String(payload.call_id ?? payload.item_id ?? "qwen-tool-call"),
|
|
1919
|
+
name: String(payload.name ?? ""),
|
|
1920
|
+
input: JSON.parse(typeof payload.arguments === "string" ? payload.arguments : "{}")
|
|
1921
|
+
}
|
|
1922
|
+
}];
|
|
1385
1923
|
}
|
|
1386
|
-
if (type
|
|
1387
|
-
|
|
1924
|
+
if (type === "response.done") {
|
|
1925
|
+
const response = payload.response;
|
|
1926
|
+
return [{ type: "realtime-response-complete", reason: typeof response?.status === "string" ? response.status : undefined, providerMetadata: metadata }];
|
|
1388
1927
|
}
|
|
1389
|
-
if (type
|
|
1390
|
-
return [{ type: "realtime-
|
|
1928
|
+
if (type === "session.finished") {
|
|
1929
|
+
return [{ type: "realtime-end", reason: "finished", providerMetadata: metadata }];
|
|
1391
1930
|
}
|
|
1392
|
-
if (type.
|
|
1393
|
-
|
|
1931
|
+
if (type === "error" || type.endsWith(".failed")) {
|
|
1932
|
+
const error = payload.error;
|
|
1933
|
+
return [{
|
|
1934
|
+
type: "realtime-error",
|
|
1935
|
+
message: String(error?.message ?? payload.message ?? "Qwen realtime error"),
|
|
1936
|
+
providerMetadata: metadata
|
|
1937
|
+
}];
|
|
1394
1938
|
}
|
|
1395
1939
|
return [];
|
|
1396
1940
|
};
|
|
1941
|
+
const mapRealtimeTools = (config) => {
|
|
1942
|
+
const tools = toToolSet(config.tools);
|
|
1943
|
+
if (!tools) {
|
|
1944
|
+
return undefined;
|
|
1945
|
+
}
|
|
1946
|
+
return Object.values(tools).map((tool) => {
|
|
1947
|
+
if (!isCallableToolDefinition(tool)) {
|
|
1948
|
+
throw new UnsupportedFeatureError("Qwen realtime supports callable function tools only.");
|
|
1949
|
+
}
|
|
1950
|
+
return {
|
|
1951
|
+
type: "function",
|
|
1952
|
+
function: {
|
|
1953
|
+
name: tool.name,
|
|
1954
|
+
description: tool.description,
|
|
1955
|
+
parameters: toJSONSchema(tool.schema)
|
|
1956
|
+
}
|
|
1957
|
+
};
|
|
1958
|
+
});
|
|
1959
|
+
};
|
|
1960
|
+
const mapRealtimeSession = (config) => ({
|
|
1961
|
+
...(config.providerOptions ?? {}),
|
|
1962
|
+
instructions: config.instructions,
|
|
1963
|
+
voice: config.voice,
|
|
1964
|
+
input_audio_format: "pcm",
|
|
1965
|
+
output_audio_format: "pcm",
|
|
1966
|
+
turn_detection: config.turnDetection,
|
|
1967
|
+
input_audio_transcription: typeof config.inputAudioTranscription === "object"
|
|
1968
|
+
? config.inputAudioTranscription
|
|
1969
|
+
: config.inputAudioTranscription
|
|
1970
|
+
? {}
|
|
1971
|
+
: undefined,
|
|
1972
|
+
tools: mapRealtimeTools(config)
|
|
1973
|
+
});
|
|
1974
|
+
const encodeRealtimeData = (data) => {
|
|
1975
|
+
if (typeof data === "string") {
|
|
1976
|
+
return data.startsWith("data:") ? data.slice(data.indexOf(",") + 1) : data;
|
|
1977
|
+
}
|
|
1978
|
+
return Buffer.from(data instanceof Uint8Array ? data : new Uint8Array(data)).toString("base64");
|
|
1979
|
+
};
|
|
1397
1980
|
class QwenRealtimeModel {
|
|
1398
1981
|
modelId;
|
|
1399
1982
|
apiKey;
|
|
@@ -1409,7 +1992,7 @@ class QwenRealtimeModel {
|
|
|
1409
1992
|
}
|
|
1410
1993
|
async connect(config = {}, options) {
|
|
1411
1994
|
const url = appendQuery(this.realtimeURL, { model: this.modelId });
|
|
1412
|
-
const connection = await (this.connectionFactory ??
|
|
1995
|
+
const connection = await (this.connectionFactory ?? openQwenRealtimeConnection)(url, { authorization: `Bearer ${this.apiKey}` }, options);
|
|
1413
1996
|
const session = new CallbackRealtimeSession({
|
|
1414
1997
|
provider: this.provider,
|
|
1415
1998
|
modelId: this.modelId,
|
|
@@ -1418,13 +2001,21 @@ class QwenRealtimeModel {
|
|
|
1418
2001
|
connection,
|
|
1419
2002
|
callbacks: {
|
|
1420
2003
|
parseEvent: parseRealtimeEvent,
|
|
1421
|
-
buildInitialPayloads: (value) => [{ type: "session.update", session:
|
|
1422
|
-
buildAudioPayloads: (frame) => [{ type: "input_audio_buffer.append", audio:
|
|
1423
|
-
buildMediaPayloads: (frame) =>
|
|
2004
|
+
buildInitialPayloads: (value) => [{ type: "session.update", session: mapRealtimeSession(value) }],
|
|
2005
|
+
buildAudioPayloads: (frame) => [{ type: "input_audio_buffer.append", audio: encodeRealtimeData(frame.data) }],
|
|
2006
|
+
buildMediaPayloads: (frame) => {
|
|
2007
|
+
if (!/^image\/(jpeg|jpg)$/i.test(frame.mediaType)) {
|
|
2008
|
+
throw new UnsupportedFeatureError("Qwen realtime media frames must be JPEG images.");
|
|
2009
|
+
}
|
|
2010
|
+
return [{ type: "input_image_buffer.append", image: encodeRealtimeData(frame.data) }];
|
|
2011
|
+
},
|
|
1424
2012
|
buildTextPayloads: (text) => [{ type: "conversation.item.create", item: { type: "message", role: "user", content: [{ type: "input_text", text }] } }, { type: "response.create" }],
|
|
1425
|
-
buildToolResultPayloads: (result) => [
|
|
1426
|
-
|
|
1427
|
-
|
|
2013
|
+
buildToolResultPayloads: (result) => [
|
|
2014
|
+
{ type: "conversation.item.create", item: { type: "function_call_output", call_id: result.toolCallId, output: JSON.stringify(result.isError ? result.error : result.output ?? null) } },
|
|
2015
|
+
{ type: "response.create" }
|
|
2016
|
+
],
|
|
2017
|
+
buildUpdatePayloads: (value) => [{ type: "session.update", session: mapRealtimeSession(value) }],
|
|
2018
|
+
buildClosePayloads: () => []
|
|
1428
2019
|
}
|
|
1429
2020
|
});
|
|
1430
2021
|
await session.initialize();
|
|
@@ -1436,25 +2027,33 @@ export const createQwen = (options = {}) => {
|
|
|
1436
2027
|
if (!apiKey) {
|
|
1437
2028
|
throw new ConfigurationError("Missing Qwen API key.");
|
|
1438
2029
|
}
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
|
|
2030
|
+
const workspaceURLs = options.workspaceId
|
|
2031
|
+
? qwenWorkspaceURLs(options.workspaceId, options.region ?? "singapore")
|
|
2032
|
+
: undefined;
|
|
2033
|
+
const baseURL = options.baseURL ?? workspaceURLs?.baseURL ?? "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
|
|
2034
|
+
const taskBaseURL = options.taskBaseURL ?? workspaceURLs?.taskBaseURL ?? qwenTaskBaseURLFrom(baseURL);
|
|
2035
|
+
const realtimeURL = options.realtimeURL ??
|
|
2036
|
+
workspaceURLs?.realtimeURL ??
|
|
2037
|
+
"wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime";
|
|
1442
2038
|
const fetcher = options.fetch ?? globalThis.fetch;
|
|
1443
2039
|
const responseLimits = resolveAudioResponseLimits(options.responseLimits);
|
|
2040
|
+
const speechAudioMaxRedirects = options.speechAudioMaxRedirects ?? 3;
|
|
2041
|
+
if (!Number.isInteger(speechAudioMaxRedirects) || speechAudioMaxRedirects < 0 || speechAudioMaxRedirects > 10) {
|
|
2042
|
+
throw new ConfigurationError("Qwen speechAudioMaxRedirects must be an integer between 0 and 10.");
|
|
2043
|
+
}
|
|
1444
2044
|
return createProviderAdapter({
|
|
1445
2045
|
name: "qwen",
|
|
1446
2046
|
languageModel: (modelId) => new QwenLanguageModel(modelId, apiKey, baseURL, fetcher),
|
|
1447
2047
|
embeddingModel: (modelId) => new QwenEmbeddingModel(modelId, apiKey, baseURL, fetcher),
|
|
1448
2048
|
transcriptionModel: (modelId) => new QwenTranscriptionModel(modelId, apiKey, baseURL, fetcher, responseLimits),
|
|
1449
|
-
speechModel: (modelId) => new QwenSpeechModel(modelId, apiKey,
|
|
1450
|
-
imageGenerationModel: (modelId) => new QwenImageGenerationModel(modelId, apiKey,
|
|
2049
|
+
speechModel: (modelId) => new QwenSpeechModel(modelId, apiKey, taskBaseURL, fetcher, responseLimits, options.speechAudioURLValidator, speechAudioMaxRedirects),
|
|
2050
|
+
imageGenerationModel: (modelId) => new QwenImageGenerationModel(modelId, apiKey, taskBaseURL, fetcher),
|
|
1451
2051
|
videoGenerationModel: (modelId) => new QwenVideoGenerationModel(modelId, apiKey, taskBaseURL, fetcher),
|
|
1452
2052
|
realtimeModel: (modelId) => new QwenRealtimeModel(modelId, apiKey, realtimeURL, options.realtimeConnectionFactory),
|
|
1453
2053
|
files: new QwenFilesClient(apiKey, baseURL, fetcher),
|
|
1454
|
-
fileSearchStores: new QwenFileSearchStoresClient(apiKey, baseURL, fetcher),
|
|
1455
2054
|
batches: new QwenBatchesClient(apiKey, baseURL, fetcher),
|
|
1456
|
-
rerankModel: (modelId) => new QwenRerankModelImpl(modelId, apiKey,
|
|
1457
|
-
multimodalEmbeddingModel: (modelId) => new
|
|
2055
|
+
rerankModel: (modelId) => new QwenRerankModelImpl(modelId, apiKey, taskBaseURL, fetcher),
|
|
2056
|
+
multimodalEmbeddingModel: (modelId) => new QwenMultimodalEmbeddingModelImpl(modelId, apiKey, taskBaseURL, fetcher),
|
|
1458
2057
|
tasks: new QwenTasksClientImpl(apiKey, taskBaseURL, fetcher),
|
|
1459
2058
|
rawFetch: fetcher
|
|
1460
2059
|
});
|