@agentscope-ai/agentscope 0.0.2
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/dist/agent/index.d.mts +234 -0
- package/dist/agent/index.d.ts +234 -0
- package/dist/agent/index.js +1412 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/index.mjs +1375 -0
- package/dist/agent/index.mjs.map +1 -0
- package/dist/base-BOx3UzOl.d.mts +41 -0
- package/dist/base-BoIps2RL.d.ts +41 -0
- package/dist/base-C7jwyH4Z.d.mts +52 -0
- package/dist/base-Cwi4bjze.d.ts +127 -0
- package/dist/base-DYlBMCy_.d.mts +127 -0
- package/dist/base-NX-knWOv.d.ts +52 -0
- package/dist/block-VsnHrllL.d.mts +48 -0
- package/dist/block-VsnHrllL.d.ts +48 -0
- package/dist/event/index.d.mts +181 -0
- package/dist/event/index.d.ts +181 -0
- package/dist/event/index.js +58 -0
- package/dist/event/index.js.map +1 -0
- package/dist/event/index.mjs +33 -0
- package/dist/event/index.mjs.map +1 -0
- package/dist/formatter/index.d.mts +187 -0
- package/dist/formatter/index.d.ts +187 -0
- package/dist/formatter/index.js +647 -0
- package/dist/formatter/index.js.map +1 -0
- package/dist/formatter/index.mjs +616 -0
- package/dist/formatter/index.mjs.map +1 -0
- package/dist/index-BTJDlKvQ.d.mts +195 -0
- package/dist/index-BcatlwXQ.d.ts +195 -0
- package/dist/index-CAxQAkiP.d.mts +21 -0
- package/dist/index-CAxQAkiP.d.ts +21 -0
- package/dist/mcp/index.d.mts +9 -0
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.js +432 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/index.mjs +408 -0
- package/dist/mcp/index.mjs.map +1 -0
- package/dist/message/index.d.mts +10 -0
- package/dist/message/index.d.ts +10 -0
- package/dist/message/index.js +67 -0
- package/dist/message/index.js.map +1 -0
- package/dist/message/index.mjs +37 -0
- package/dist/message/index.mjs.map +1 -0
- package/dist/message-CkN21KaY.d.mts +99 -0
- package/dist/message-CzLeTlua.d.ts +99 -0
- package/dist/model/index.d.mts +377 -0
- package/dist/model/index.d.ts +377 -0
- package/dist/model/index.js +1880 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/index.mjs +1849 -0
- package/dist/model/index.mjs.map +1 -0
- package/dist/storage/index.d.mts +68 -0
- package/dist/storage/index.d.ts +68 -0
- package/dist/storage/index.js +250 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/index.mjs +212 -0
- package/dist/storage/index.mjs.map +1 -0
- package/dist/tool/index.d.mts +311 -0
- package/dist/tool/index.d.ts +311 -0
- package/dist/tool/index.js +1494 -0
- package/dist/tool/index.js.map +1 -0
- package/dist/tool/index.mjs +1447 -0
- package/dist/tool/index.mjs.map +1 -0
- package/dist/toolkit-CEpulFi0.d.ts +99 -0
- package/dist/toolkit-CGEZSZPa.d.mts +99 -0
- package/jest.config.js +11 -0
- package/package.json +92 -0
- package/src/_utils/common.ts +104 -0
- package/src/_utils/index.ts +1 -0
- package/src/agent/agent-base.ts +0 -0
- package/src/agent/agent.test.ts +1028 -0
- package/src/agent/agent.ts +1032 -0
- package/src/agent/index.ts +2 -0
- package/src/agent/interfaces.ts +23 -0
- package/src/agent/test-compression.ts +72 -0
- package/src/event/index.ts +250 -0
- package/src/formatter/base.ts +133 -0
- package/src/formatter/dashscope-chat-formatter.test.ts +372 -0
- package/src/formatter/dashscope-chat-formatter.ts +163 -0
- package/src/formatter/deepseek-chat-formatter.ts +130 -0
- package/src/formatter/index.ts +5 -0
- package/src/formatter/ollama-chat-formatter.ts +67 -0
- package/src/formatter/openai-chat-formatter.test.ts +263 -0
- package/src/formatter/openai-chat-formatter.ts +301 -0
- package/src/formatter/openai.md +767 -0
- package/src/mcp/base.ts +114 -0
- package/src/mcp/http.test.ts +303 -0
- package/src/mcp/http.ts +224 -0
- package/src/mcp/index.ts +2 -0
- package/src/mcp/stdio.test.ts +91 -0
- package/src/mcp/stdio.ts +119 -0
- package/src/message/block.ts +60 -0
- package/src/message/enums.ts +4 -0
- package/src/message/index.ts +12 -0
- package/src/message/message.test.ts +80 -0
- package/src/message/message.ts +131 -0
- package/src/model/base.ts +226 -0
- package/src/model/dashscope-model.test.ts +335 -0
- package/src/model/dashscope-model.ts +441 -0
- package/src/model/deepseek-model.test.ts +279 -0
- package/src/model/deepseek-model.ts +401 -0
- package/src/model/index.ts +7 -0
- package/src/model/ollama-model.test.ts +307 -0
- package/src/model/ollama-model.ts +356 -0
- package/src/model/openai-model.ts +327 -0
- package/src/model/response.ts +22 -0
- package/src/model/usage.ts +12 -0
- package/src/storage/base.ts +52 -0
- package/src/storage/file-system.test.ts +587 -0
- package/src/storage/file-system.ts +269 -0
- package/src/storage/index.ts +2 -0
- package/src/tool/base.ts +23 -0
- package/src/tool/bash.test.ts +174 -0
- package/src/tool/bash.ts +152 -0
- package/src/tool/edit.test.ts +83 -0
- package/src/tool/edit.ts +95 -0
- package/src/tool/glob.test.ts +63 -0
- package/src/tool/glob.ts +166 -0
- package/src/tool/grep.test.ts +74 -0
- package/src/tool/grep.ts +256 -0
- package/src/tool/index.ts +10 -0
- package/src/tool/read.test.ts +77 -0
- package/src/tool/read.ts +117 -0
- package/src/tool/response.ts +82 -0
- package/src/tool/task.test.ts +299 -0
- package/src/tool/task.ts +399 -0
- package/src/tool/toolkit.test.ts +636 -0
- package/src/tool/toolkit.ts +601 -0
- package/src/tool/write.test.ts +52 -0
- package/src/tool/write.ts +57 -0
- package/src/type/index.ts +52 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.cjs.json +11 -0
- package/tsconfig.esm.json +10 -0
- package/tsconfig.json +14 -0
- package/tsup.config.ts +20 -0
- package/typedoc.json +52 -0
|
@@ -0,0 +1,1849 @@
|
|
|
1
|
+
// src/message/message.ts
|
|
2
|
+
function createMsg({
|
|
3
|
+
name,
|
|
4
|
+
content,
|
|
5
|
+
role,
|
|
6
|
+
metadata = {},
|
|
7
|
+
id = crypto.randomUUID(),
|
|
8
|
+
timestamp = (/* @__PURE__ */ new Date()).toISOString(),
|
|
9
|
+
usage
|
|
10
|
+
}) {
|
|
11
|
+
return { id, name, role, content, metadata, timestamp, usage };
|
|
12
|
+
}
|
|
13
|
+
function getTextContent(msg, separator = "\n") {
|
|
14
|
+
const textBlocks = msg.content.filter((block) => block.type === "text");
|
|
15
|
+
if (textBlocks.length === 0) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return textBlocks.map((block) => block.text).join(separator);
|
|
19
|
+
}
|
|
20
|
+
function getContentBlocks(msg, blockType) {
|
|
21
|
+
if (!blockType) return msg.content;
|
|
22
|
+
return msg.content.filter((block) => block.type === blockType);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/model/base.ts
|
|
26
|
+
var ChatModelBase = class {
|
|
27
|
+
modelName;
|
|
28
|
+
stream;
|
|
29
|
+
maxRetries;
|
|
30
|
+
fallbackModelName;
|
|
31
|
+
formatter;
|
|
32
|
+
/**
|
|
33
|
+
* Initializes a new instance of the ChatModelBase class.
|
|
34
|
+
*
|
|
35
|
+
* @param options - The chat model options, including model name, streaming option, max retries, fallback
|
|
36
|
+
* model name, and formatter.
|
|
37
|
+
*
|
|
38
|
+
* @param options.modelName
|
|
39
|
+
* @param options.stream
|
|
40
|
+
* @param options.maxRetries
|
|
41
|
+
* @param options.fallbackModelName
|
|
42
|
+
* @param options.formatter
|
|
43
|
+
*/
|
|
44
|
+
constructor({
|
|
45
|
+
modelName,
|
|
46
|
+
stream,
|
|
47
|
+
maxRetries,
|
|
48
|
+
fallbackModelName,
|
|
49
|
+
formatter
|
|
50
|
+
}) {
|
|
51
|
+
this.modelName = modelName;
|
|
52
|
+
this.stream = stream ?? true;
|
|
53
|
+
this.maxRetries = maxRetries ?? 0;
|
|
54
|
+
this.fallbackModelName = fallbackModelName;
|
|
55
|
+
this.formatter = formatter;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Calls the chat model with the given messages.
|
|
59
|
+
* This is the main method to interact with the model.
|
|
60
|
+
*
|
|
61
|
+
* @param options - The chat model call options.
|
|
62
|
+
* @returns A promise that resolves to the model's response.
|
|
63
|
+
*/
|
|
64
|
+
async call(options) {
|
|
65
|
+
let formattedMessages;
|
|
66
|
+
if (this.formatter) {
|
|
67
|
+
formattedMessages = await this.formatter.format({ msgs: options.messages });
|
|
68
|
+
} else {
|
|
69
|
+
formattedMessages = options.messages;
|
|
70
|
+
}
|
|
71
|
+
const requestOptions = {
|
|
72
|
+
...options,
|
|
73
|
+
messages: formattedMessages
|
|
74
|
+
};
|
|
75
|
+
let lastError;
|
|
76
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
77
|
+
try {
|
|
78
|
+
return await this._callAPI(this.modelName, requestOptions);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
lastError = error;
|
|
81
|
+
if (attempt === this.maxRetries) {
|
|
82
|
+
throw error;
|
|
83
|
+
} else {
|
|
84
|
+
console.log(
|
|
85
|
+
`Attempt ${attempt + 1} failed for model ${this.modelName}. Retrying...`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (this.fallbackModelName) {
|
|
91
|
+
console.log(
|
|
92
|
+
`Using fallback model ${this.fallbackModelName} after ${this.maxRetries} failed attempts.`
|
|
93
|
+
);
|
|
94
|
+
return await this._callAPI(this.fallbackModelName, requestOptions);
|
|
95
|
+
}
|
|
96
|
+
throw lastError;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* A heuristic method to count the number of the tokens
|
|
100
|
+
* Note the multimodal content is ignored in the token counting
|
|
101
|
+
* @param options
|
|
102
|
+
* @param options.messages
|
|
103
|
+
* @param options.tools
|
|
104
|
+
* @returns The estimated number of tokens in the input messages and tools.
|
|
105
|
+
*/
|
|
106
|
+
async countTokens(options) {
|
|
107
|
+
let accText = "";
|
|
108
|
+
for (const msg of options.messages) {
|
|
109
|
+
accText += getTextContent(msg) || "";
|
|
110
|
+
}
|
|
111
|
+
if (options.tools) {
|
|
112
|
+
accText += JSON.stringify(options.tools);
|
|
113
|
+
}
|
|
114
|
+
const chineseMatches = accText.match(/[\u4e00-\u9fff\u3400-\u4dbf\u{20000}-\u{2a6df}]/gu)?.length ?? 0;
|
|
115
|
+
const englishMatches = accText.match(/[a-zA-Z]+/g)?.length ?? 0;
|
|
116
|
+
return chineseMatches * 2 + englishMatches * 1.5;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* A default implementation of the structured call method. For those supporting structured output, the model should
|
|
120
|
+
* override this method.
|
|
121
|
+
* @param options
|
|
122
|
+
* @returns The structured response from the model, which should conform to the provided Zod schema.
|
|
123
|
+
*/
|
|
124
|
+
async callStructured(options) {
|
|
125
|
+
const toolSchema = {
|
|
126
|
+
type: "function",
|
|
127
|
+
function: {
|
|
128
|
+
name: "GenerateStructuredResponse",
|
|
129
|
+
description: "Generate required structured response by this toll.",
|
|
130
|
+
parameters: options.schema.toJSONSchema({
|
|
131
|
+
target: "openapi-3.0"
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const res = await this.call({
|
|
136
|
+
messages: options.messages,
|
|
137
|
+
tools: [toolSchema],
|
|
138
|
+
toolChoice: "GenerateStructuredResponse"
|
|
139
|
+
});
|
|
140
|
+
let completedResponse;
|
|
141
|
+
if (this.stream) {
|
|
142
|
+
while (true) {
|
|
143
|
+
const { value, done } = await res.next();
|
|
144
|
+
if (done) {
|
|
145
|
+
completedResponse = value;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
completedResponse = res;
|
|
151
|
+
}
|
|
152
|
+
for (const block of completedResponse.content) {
|
|
153
|
+
if (block.type === "tool_call" && block.name === "GenerateStructuredResponse") {
|
|
154
|
+
const structuredContent = JSON.parse(block.input);
|
|
155
|
+
return {
|
|
156
|
+
...completedResponse,
|
|
157
|
+
content: structuredContent,
|
|
158
|
+
type: "structured"
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
throw new Error(`Failed to generate the structured response`);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// src/_utils/common.ts
|
|
167
|
+
import { jsonrepair } from "jsonrepair";
|
|
168
|
+
async function* _parseStreamedResponse(response) {
|
|
169
|
+
const reader = response.body?.getReader();
|
|
170
|
+
if (!reader) {
|
|
171
|
+
throw new Error("Failed to get reader from response body for streaming.");
|
|
172
|
+
}
|
|
173
|
+
const decoder = new TextDecoder();
|
|
174
|
+
let buffer = "";
|
|
175
|
+
try {
|
|
176
|
+
while (true) {
|
|
177
|
+
const { done, value } = await reader.read();
|
|
178
|
+
if (done) break;
|
|
179
|
+
buffer += decoder.decode(value, { stream: true });
|
|
180
|
+
const lines = buffer.split("\n");
|
|
181
|
+
buffer = lines.pop() || "";
|
|
182
|
+
for (const line of lines) {
|
|
183
|
+
const trimmedLine = line.trim();
|
|
184
|
+
if (!trimmedLine || trimmedLine.startsWith(":")) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (trimmedLine.startsWith("data:")) {
|
|
188
|
+
const jsonStr = trimmedLine.slice(5).trim();
|
|
189
|
+
if (jsonStr === "[DONE]") {
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
const json = JSON.parse(jsonStr);
|
|
194
|
+
yield json;
|
|
195
|
+
} catch (e) {
|
|
196
|
+
console.error("Failed to parse JSON:", e);
|
|
197
|
+
throw new Error(`Failed to parse JSON from stream: ${jsonStr}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} finally {
|
|
203
|
+
reader.releaseLock();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/formatter/base.ts
|
|
208
|
+
var FormatterBase = class {
|
|
209
|
+
/**
|
|
210
|
+
* Convert the tool output to string format for the LLM APIs that only accept text input. If
|
|
211
|
+
* `promoteMultimodalToolResult` is true, the multimodal content will be promoted to be a user message with
|
|
212
|
+
* "<system-info></system-info>" tags. Otherwise, the multimodal content will be saved to a storage and a URL link
|
|
213
|
+
* will be provided in the text output.
|
|
214
|
+
*
|
|
215
|
+
* @param output - The tool output, which can be a string or an array of content blocks.
|
|
216
|
+
* @param promoteMultimodalToolResult - Whether to promote the multimodal content to the prompt messages.
|
|
217
|
+
* @returns An object containing the text output and an optional promoted message.
|
|
218
|
+
*/
|
|
219
|
+
convertToolOutputToString(output, promoteMultimodalToolResult) {
|
|
220
|
+
if (typeof output === "string") return { text: output, promotedMsg: null };
|
|
221
|
+
let textualOutput = [];
|
|
222
|
+
const promotedData = [];
|
|
223
|
+
for (const block of output) {
|
|
224
|
+
switch (block.type) {
|
|
225
|
+
case "text":
|
|
226
|
+
textualOutput.push(block.text);
|
|
227
|
+
break;
|
|
228
|
+
default:
|
|
229
|
+
const type = block.source.mediaType.split("/")[0];
|
|
230
|
+
if (type !== "image" && type !== "audio" && type !== "video") {
|
|
231
|
+
console.log(
|
|
232
|
+
`Unsupported media type '${block.source.mediaType}' in tool output. Only image, audio and video are supported.`
|
|
233
|
+
);
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
if (block.source.type === "url") {
|
|
237
|
+
textualOutput.push(
|
|
238
|
+
`<system-info>One returned ${type} can be found at: ${block.source.url}</system-info>`
|
|
239
|
+
);
|
|
240
|
+
} else {
|
|
241
|
+
const shouldPromote = promoteMultimodalToolResult === true || typeof promoteMultimodalToolResult === "object" && promoteMultimodalToolResult[type];
|
|
242
|
+
if (shouldPromote) {
|
|
243
|
+
const dataID = Math.random().toString(36).substring(2, 10);
|
|
244
|
+
textualOutput.push(
|
|
245
|
+
`<system-info>One returned ${type} is embedded with ID '${dataID}' and will be attached within '<system-info></system-info>' tags later.</system-info>`
|
|
246
|
+
);
|
|
247
|
+
promotedData.push({ id: dataID, block });
|
|
248
|
+
} else {
|
|
249
|
+
textualOutput.push(`The returned ${block.type} is stored locally.`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const promotedBlocks = [];
|
|
255
|
+
promotedData.forEach(({ id, block }) => {
|
|
256
|
+
const type = block.source.mediaType.split("/")[0];
|
|
257
|
+
promotedBlocks.push({
|
|
258
|
+
id: crypto.randomUUID(),
|
|
259
|
+
type: "text",
|
|
260
|
+
text: `<${type}_data id='${id}'>`
|
|
261
|
+
});
|
|
262
|
+
promotedBlocks.push(block);
|
|
263
|
+
promotedBlocks.push({
|
|
264
|
+
id: crypto.randomUUID(),
|
|
265
|
+
type: "text",
|
|
266
|
+
text: `</${type}_data>
|
|
267
|
+
`
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
if (promotedBlocks.length > 0) {
|
|
271
|
+
const prefix = "<system-info>The multimodal contents returned from the tool call are as follows:\n";
|
|
272
|
+
if (promotedBlocks[0].type === "text") {
|
|
273
|
+
promotedBlocks[0].text = `${prefix}${promotedBlocks[0].text}`;
|
|
274
|
+
} else {
|
|
275
|
+
promotedBlocks.unshift({
|
|
276
|
+
id: crypto.randomUUID(),
|
|
277
|
+
type: "text",
|
|
278
|
+
text: `${prefix}`
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
const lastBlock = promotedBlocks[promotedBlocks.length - 1];
|
|
282
|
+
if (lastBlock.type === "text") {
|
|
283
|
+
promotedBlocks[promotedBlocks.length - 1] = {
|
|
284
|
+
id: crypto.randomUUID(),
|
|
285
|
+
type: "text",
|
|
286
|
+
text: `${lastBlock.text}</system-info>`
|
|
287
|
+
};
|
|
288
|
+
} else {
|
|
289
|
+
promotedBlocks.push({
|
|
290
|
+
id: crypto.randomUUID(),
|
|
291
|
+
type: "text",
|
|
292
|
+
text: `</system-info>`
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
text: textualOutput.join("\n"),
|
|
298
|
+
promotedMsg: createMsg({ name: "user", content: promotedBlocks, role: "user" })
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// src/formatter/dashscope-chat-formatter.ts
|
|
304
|
+
var DashScopeChatFormatter = class extends FormatterBase {
|
|
305
|
+
promoteMultimodalToolResult;
|
|
306
|
+
/**
|
|
307
|
+
* Initialize a DashScopeChatFormatter instance.
|
|
308
|
+
*
|
|
309
|
+
* @param promoteMultimodalToolResult - Since DashScope API doesn't support multimodal tool outputs, this option
|
|
310
|
+
* indicates whether to promote the multimodal tool results to the prompt messages, so that LLMs can see them.
|
|
311
|
+
* Note you should ensure your model supports the corresponding modalities.
|
|
312
|
+
* @param promoteMultimodalToolResult.promoteMultimodalToolResult
|
|
313
|
+
*/
|
|
314
|
+
constructor({ promoteMultimodalToolResult = false } = {}) {
|
|
315
|
+
super();
|
|
316
|
+
this.promoteMultimodalToolResult = promoteMultimodalToolResult;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Format the input message objects into the required format by DashScope API.
|
|
320
|
+
*
|
|
321
|
+
* @param msgs - An array of Msg instances to be formatted.
|
|
322
|
+
* @param msgs.msgs
|
|
323
|
+
* @returns A promise that resolves to an array of formatted message objects.
|
|
324
|
+
*/
|
|
325
|
+
async format({ msgs }) {
|
|
326
|
+
const formattedMsgs = [];
|
|
327
|
+
let index = 0;
|
|
328
|
+
while (index < msgs.length) {
|
|
329
|
+
const msg = msgs[index];
|
|
330
|
+
const formattedMsg = {
|
|
331
|
+
role: msg.role,
|
|
332
|
+
content: []
|
|
333
|
+
};
|
|
334
|
+
const cachedMsgs = [];
|
|
335
|
+
for (const block of getContentBlocks(msg)) {
|
|
336
|
+
switch (block.type) {
|
|
337
|
+
case "text":
|
|
338
|
+
formattedMsg.content.push(this._formatTextBlock(block));
|
|
339
|
+
break;
|
|
340
|
+
case "thinking":
|
|
341
|
+
break;
|
|
342
|
+
case "tool_call":
|
|
343
|
+
if (!formattedMsg.tool_calls) {
|
|
344
|
+
formattedMsg.tool_calls = [];
|
|
345
|
+
}
|
|
346
|
+
formattedMsg.tool_calls.push({
|
|
347
|
+
id: block.id,
|
|
348
|
+
type: "function",
|
|
349
|
+
function: {
|
|
350
|
+
name: block.name,
|
|
351
|
+
arguments: block.input
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
break;
|
|
355
|
+
case "tool_result":
|
|
356
|
+
const formattedToolResult = this.convertToolOutputToString(
|
|
357
|
+
block.output,
|
|
358
|
+
this.promoteMultimodalToolResult
|
|
359
|
+
);
|
|
360
|
+
cachedMsgs.push({
|
|
361
|
+
role: "tool",
|
|
362
|
+
tool_call_id: block.id,
|
|
363
|
+
name: block.name,
|
|
364
|
+
content: formattedToolResult.text
|
|
365
|
+
});
|
|
366
|
+
if (formattedToolResult.promotedMsg) {
|
|
367
|
+
msgs.splice(index + 1, 0, formattedToolResult.promotedMsg);
|
|
368
|
+
}
|
|
369
|
+
break;
|
|
370
|
+
case "data":
|
|
371
|
+
formattedMsg.content.push(...this._formatMultimodalBlock(block));
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (formattedMsg.content.length > 0 || formattedMsg.tool_calls) {
|
|
376
|
+
formattedMsgs.push(formattedMsg);
|
|
377
|
+
}
|
|
378
|
+
if (cachedMsgs.length > 0) {
|
|
379
|
+
formattedMsgs.push(...cachedMsgs);
|
|
380
|
+
}
|
|
381
|
+
index++;
|
|
382
|
+
}
|
|
383
|
+
return formattedMsgs;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Format a text content block into the required format.
|
|
387
|
+
*
|
|
388
|
+
* @param block - The text content block to format.
|
|
389
|
+
* @returns An object representing the formatted text content.
|
|
390
|
+
*/
|
|
391
|
+
_formatTextBlock(block) {
|
|
392
|
+
return { text: block.text };
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Format a multimodal data block into the required format.
|
|
396
|
+
* In DashScope API, the local file paths should be prefixed with "file://". URLs are kept unchanged.
|
|
397
|
+
*
|
|
398
|
+
* @param block - The multimodal content block to format.
|
|
399
|
+
* @returns An object representing the formatted multimodal content.
|
|
400
|
+
*/
|
|
401
|
+
_formatMultimodalBlock(block) {
|
|
402
|
+
const type = block.source.mediaType.split("/")[0];
|
|
403
|
+
if (!["image", "audio", "video"].includes(type)) {
|
|
404
|
+
console.log(
|
|
405
|
+
`Skip unsupported media type ${block.source.mediaType} in DashScopeChatFormatter. Only image, audio and video are supported.`
|
|
406
|
+
);
|
|
407
|
+
return [];
|
|
408
|
+
}
|
|
409
|
+
if (block.source.type === "url") {
|
|
410
|
+
return [{ [type]: block.source.url }];
|
|
411
|
+
}
|
|
412
|
+
return [
|
|
413
|
+
{
|
|
414
|
+
[type]: `data:${block.source.mediaType};base64,${block.source.data}`
|
|
415
|
+
}
|
|
416
|
+
];
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// src/formatter/deepseek-chat-formatter.ts
|
|
421
|
+
var DeepSeekChatFormatter = class extends FormatterBase {
|
|
422
|
+
promoteMultimodalToolResult;
|
|
423
|
+
/**
|
|
424
|
+
* Initializes a new instance of the DeepSeekChatFormatter class.
|
|
425
|
+
* @param root0
|
|
426
|
+
* @param root0.promoteMultimodalToolResult
|
|
427
|
+
*/
|
|
428
|
+
constructor({ promoteMultimodalToolResult = false } = {}) {
|
|
429
|
+
super();
|
|
430
|
+
this.promoteMultimodalToolResult = promoteMultimodalToolResult;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Format the input messages into the structure expected by DeepSeek Chat Completions API.
|
|
434
|
+
* @param root0
|
|
435
|
+
* @param root0.msgs
|
|
436
|
+
* @returns An array of formatted message objects ready to be sent to the DeepSeek API.
|
|
437
|
+
*/
|
|
438
|
+
async format({ msgs }) {
|
|
439
|
+
const formattedMsgs = [];
|
|
440
|
+
let index = 0;
|
|
441
|
+
while (index < msgs.length) {
|
|
442
|
+
const msg = msgs[index];
|
|
443
|
+
const formattedMsg = {
|
|
444
|
+
role: msg.role,
|
|
445
|
+
name: msg.name,
|
|
446
|
+
content: null
|
|
447
|
+
};
|
|
448
|
+
const content = [];
|
|
449
|
+
const cachedMsgs = [];
|
|
450
|
+
for (const block of getContentBlocks(msg)) {
|
|
451
|
+
switch (block.type) {
|
|
452
|
+
case "text":
|
|
453
|
+
content.push({
|
|
454
|
+
type: "text",
|
|
455
|
+
text: block.text
|
|
456
|
+
});
|
|
457
|
+
break;
|
|
458
|
+
case "thinking":
|
|
459
|
+
break;
|
|
460
|
+
case "tool_call":
|
|
461
|
+
if (!formattedMsg.tool_calls) {
|
|
462
|
+
formattedMsg.tool_calls = [];
|
|
463
|
+
}
|
|
464
|
+
formattedMsg.tool_calls.push({
|
|
465
|
+
id: block.id,
|
|
466
|
+
type: "function",
|
|
467
|
+
function: {
|
|
468
|
+
name: block.name,
|
|
469
|
+
arguments: block.input
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
break;
|
|
473
|
+
case "tool_result":
|
|
474
|
+
const formattedToolResult = this.convertToolOutputToString(
|
|
475
|
+
block.output,
|
|
476
|
+
this.promoteMultimodalToolResult
|
|
477
|
+
);
|
|
478
|
+
cachedMsgs.push({
|
|
479
|
+
role: "tool",
|
|
480
|
+
tool_call_id: block.id,
|
|
481
|
+
name: block.name,
|
|
482
|
+
content: formattedToolResult.text
|
|
483
|
+
});
|
|
484
|
+
if (formattedToolResult.promotedMsg?.content.length) {
|
|
485
|
+
msgs.splice(index + 1, 0, formattedToolResult.promotedMsg);
|
|
486
|
+
}
|
|
487
|
+
break;
|
|
488
|
+
case "data":
|
|
489
|
+
console.warn(
|
|
490
|
+
`DeepSeek models don't support multimodal data for now (2026-03), skip the data block in message content.`
|
|
491
|
+
);
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (content.length > 0) {
|
|
496
|
+
formattedMsg.content = content;
|
|
497
|
+
}
|
|
498
|
+
if (formattedMsg.content || formattedMsg.tool_calls) {
|
|
499
|
+
formattedMsgs.push(formattedMsg);
|
|
500
|
+
}
|
|
501
|
+
if (cachedMsgs.length > 0) {
|
|
502
|
+
formattedMsgs.push(...cachedMsgs);
|
|
503
|
+
}
|
|
504
|
+
index++;
|
|
505
|
+
}
|
|
506
|
+
return formattedMsgs;
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
// src/formatter/ollama-chat-formatter.ts
|
|
511
|
+
var OllamaChatFormatter = class extends FormatterBase {
|
|
512
|
+
// eslint-disable-next-line jsdoc/require-returns
|
|
513
|
+
/**
|
|
514
|
+
* Format messages for Ollama API
|
|
515
|
+
* @param root0
|
|
516
|
+
* @param root0.msgs
|
|
517
|
+
*/
|
|
518
|
+
async format({ msgs }) {
|
|
519
|
+
const formattedMsgs = [];
|
|
520
|
+
for (const msg of msgs) {
|
|
521
|
+
const formattedMsg = {
|
|
522
|
+
role: msg.role,
|
|
523
|
+
content: ""
|
|
524
|
+
};
|
|
525
|
+
const textContent = getTextContent(msg);
|
|
526
|
+
if (textContent) {
|
|
527
|
+
formattedMsg.content = textContent;
|
|
528
|
+
}
|
|
529
|
+
const toolCalls = getContentBlocks(msg, "tool_call");
|
|
530
|
+
if (toolCalls.length > 0) {
|
|
531
|
+
formattedMsg.tool_calls = toolCalls.map((toolCall) => ({
|
|
532
|
+
function: {
|
|
533
|
+
name: toolCall.name,
|
|
534
|
+
arguments: JSON.parse(toolCall.input)
|
|
535
|
+
}
|
|
536
|
+
}));
|
|
537
|
+
}
|
|
538
|
+
const toolResults = getContentBlocks(msg, "tool_result");
|
|
539
|
+
for (const toolResult of toolResults) {
|
|
540
|
+
const resultText = this.convertToolOutputToString(toolResult.output, false);
|
|
541
|
+
formattedMsgs.push({
|
|
542
|
+
role: "tool",
|
|
543
|
+
content: resultText.text
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
if (formattedMsg.content || formattedMsg.tool_calls) {
|
|
547
|
+
formattedMsgs.push(formattedMsg);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return formattedMsgs;
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
// src/formatter/openai-chat-formatter.ts
|
|
555
|
+
import { existsSync } from "fs";
|
|
556
|
+
import { readFile } from "fs/promises";
|
|
557
|
+
import { extname } from "path";
|
|
558
|
+
import { fileURLToPath } from "url";
|
|
559
|
+
var OpenAIChatFormatter = class extends FormatterBase {
|
|
560
|
+
promoteMultimodalToolResult;
|
|
561
|
+
/**
|
|
562
|
+
* Initializes a new instance of the OpenAIChatFormatter class.
|
|
563
|
+
* @param root0
|
|
564
|
+
* @param root0.promoteMultimodalToolResult
|
|
565
|
+
*/
|
|
566
|
+
constructor({ promoteMultimodalToolResult = false } = {}) {
|
|
567
|
+
super();
|
|
568
|
+
this.promoteMultimodalToolResult = promoteMultimodalToolResult;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Format the input messages into OpenAI Chat Completions message format.
|
|
572
|
+
* @param root0
|
|
573
|
+
* @param root0.msgs
|
|
574
|
+
* @returns An array of formatted messages compatible with OpenAI Chat Completions API.
|
|
575
|
+
*/
|
|
576
|
+
async format({ msgs }) {
|
|
577
|
+
const formattedMsgs = [];
|
|
578
|
+
let index = 0;
|
|
579
|
+
while (index < msgs.length) {
|
|
580
|
+
const msg = msgs[index];
|
|
581
|
+
const formattedMsg = {
|
|
582
|
+
role: msg.role,
|
|
583
|
+
name: msg.name,
|
|
584
|
+
content: null
|
|
585
|
+
};
|
|
586
|
+
const content = [];
|
|
587
|
+
const cachedMsgs = [];
|
|
588
|
+
for (const block of getContentBlocks(msg)) {
|
|
589
|
+
switch (block.type) {
|
|
590
|
+
case "text":
|
|
591
|
+
content.push(this._formatTextBlock(block));
|
|
592
|
+
break;
|
|
593
|
+
case "thinking":
|
|
594
|
+
break;
|
|
595
|
+
case "tool_call":
|
|
596
|
+
if (!formattedMsg.tool_calls) {
|
|
597
|
+
formattedMsg.tool_calls = [];
|
|
598
|
+
}
|
|
599
|
+
formattedMsg.tool_calls.push({
|
|
600
|
+
id: block.id,
|
|
601
|
+
type: "function",
|
|
602
|
+
function: {
|
|
603
|
+
name: block.name,
|
|
604
|
+
arguments: block.input
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
break;
|
|
608
|
+
case "tool_result":
|
|
609
|
+
const formattedToolResult = this.convertToolOutputToString(
|
|
610
|
+
block.output,
|
|
611
|
+
this.promoteMultimodalToolResult
|
|
612
|
+
);
|
|
613
|
+
cachedMsgs.push({
|
|
614
|
+
role: "tool",
|
|
615
|
+
tool_call_id: block.id,
|
|
616
|
+
name: block.name,
|
|
617
|
+
content: formattedToolResult.text
|
|
618
|
+
});
|
|
619
|
+
if (formattedToolResult.promotedMsg?.content.length) {
|
|
620
|
+
msgs.splice(index + 1, 0, formattedToolResult.promotedMsg);
|
|
621
|
+
}
|
|
622
|
+
break;
|
|
623
|
+
case "data":
|
|
624
|
+
content.push(
|
|
625
|
+
...await this._formatMultimodalBlock({ block, role: msg.role })
|
|
626
|
+
);
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (content.length > 0) {
|
|
631
|
+
formattedMsg.content = content;
|
|
632
|
+
}
|
|
633
|
+
if (formattedMsg.content || formattedMsg.tool_calls) {
|
|
634
|
+
formattedMsgs.push(formattedMsg);
|
|
635
|
+
}
|
|
636
|
+
if (cachedMsgs.length > 0) {
|
|
637
|
+
formattedMsgs.push(...cachedMsgs);
|
|
638
|
+
}
|
|
639
|
+
index++;
|
|
640
|
+
}
|
|
641
|
+
return formattedMsgs;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Format a text block into OpenAI Chat Completions message content format.
|
|
645
|
+
* @param block
|
|
646
|
+
* @returns An object representing the formatted text block.
|
|
647
|
+
*/
|
|
648
|
+
_formatTextBlock(block) {
|
|
649
|
+
return {
|
|
650
|
+
type: "text",
|
|
651
|
+
text: block.text
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Format a multimodal data block into OpenAI Chat Completions message content format.
|
|
656
|
+
* @param root0
|
|
657
|
+
* @param root0.block
|
|
658
|
+
* @param root0.role
|
|
659
|
+
* @returns The formatted content blocks
|
|
660
|
+
*/
|
|
661
|
+
async _formatMultimodalBlock({
|
|
662
|
+
block,
|
|
663
|
+
role
|
|
664
|
+
}) {
|
|
665
|
+
const type = block.source.mediaType.split("/")[0];
|
|
666
|
+
if (type === "image") {
|
|
667
|
+
return [
|
|
668
|
+
{
|
|
669
|
+
type: "image_url",
|
|
670
|
+
image_url: {
|
|
671
|
+
url: await this._toOpenAIImageURL(block)
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
];
|
|
675
|
+
}
|
|
676
|
+
if (type === "audio") {
|
|
677
|
+
if (role === "assistant") {
|
|
678
|
+
return [];
|
|
679
|
+
}
|
|
680
|
+
return [
|
|
681
|
+
{
|
|
682
|
+
type: "input_audio",
|
|
683
|
+
input_audio: await this._toOpenAIAudioData(block)
|
|
684
|
+
}
|
|
685
|
+
];
|
|
686
|
+
}
|
|
687
|
+
console.log(
|
|
688
|
+
`Skip unsupported media type ${block.source.mediaType} in OpenAIChatFormatter. Only image and audio are supported.`
|
|
689
|
+
);
|
|
690
|
+
return [];
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Convert the data block to an OpenAI compatible image URL.
|
|
694
|
+
* @param block
|
|
695
|
+
* @returns A promise that resolves to a string representing the image URL in a format compatible with OpenAI Chat Completions API.
|
|
696
|
+
*/
|
|
697
|
+
async _toOpenAIImageURL(block) {
|
|
698
|
+
if (block.source.type === "base64") {
|
|
699
|
+
return `data:${block.source.mediaType};base64,${block.source.data}`;
|
|
700
|
+
}
|
|
701
|
+
const sourceUrl = block.source.url;
|
|
702
|
+
if (sourceUrl.startsWith("http://") || sourceUrl.startsWith("https://")) {
|
|
703
|
+
return sourceUrl;
|
|
704
|
+
}
|
|
705
|
+
if (sourceUrl.startsWith("data:")) {
|
|
706
|
+
return sourceUrl;
|
|
707
|
+
}
|
|
708
|
+
const localPath = this._toLocalPath(sourceUrl);
|
|
709
|
+
if (!localPath || !existsSync(localPath)) {
|
|
710
|
+
throw new Error(`Image path not found: ${sourceUrl}`);
|
|
711
|
+
}
|
|
712
|
+
const ext = extname(localPath).toLowerCase();
|
|
713
|
+
const supportedImageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".webp"];
|
|
714
|
+
if (!supportedImageExtensions.includes(ext)) {
|
|
715
|
+
throw new TypeError(
|
|
716
|
+
`Unsupported image extension: ${ext}. Supported: ${supportedImageExtensions.join(", ")}`
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
const file = await readFile(localPath);
|
|
720
|
+
const mime = block.source.mediaType || `image/${ext.slice(1)}`;
|
|
721
|
+
return `data:${mime};base64,${file.toString("base64")}`;
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Converts a data block to OpenAI compatible audio data format.
|
|
725
|
+
*
|
|
726
|
+
* @param block - The data block containing audio information.
|
|
727
|
+
* @returns A promise that resolves to an object with audio data and format.
|
|
728
|
+
*/
|
|
729
|
+
async _toOpenAIAudioData(block) {
|
|
730
|
+
const supportedMediaTypes = /* @__PURE__ */ new Map([
|
|
731
|
+
["audio/wav", "wav"],
|
|
732
|
+
["audio/mp3", "mp3"],
|
|
733
|
+
["audio/mpeg", "mp3"]
|
|
734
|
+
]);
|
|
735
|
+
if (block.source.type === "base64") {
|
|
736
|
+
const format2 = supportedMediaTypes.get(block.source.mediaType);
|
|
737
|
+
if (!format2) {
|
|
738
|
+
throw new TypeError(
|
|
739
|
+
`Unsupported audio media type: ${block.source.mediaType}, only audio/wav and audio/mp3 are supported.`
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
return { data: block.source.data, format: format2 };
|
|
743
|
+
}
|
|
744
|
+
const sourceUrl = block.source.url;
|
|
745
|
+
const localPath = this._toLocalPath(sourceUrl);
|
|
746
|
+
let data;
|
|
747
|
+
if (localPath && existsSync(localPath)) {
|
|
748
|
+
const file = await readFile(localPath);
|
|
749
|
+
data = file.toString("base64");
|
|
750
|
+
} else if (sourceUrl.startsWith("http://") || sourceUrl.startsWith("https://")) {
|
|
751
|
+
const response = await fetch(sourceUrl);
|
|
752
|
+
if (!response.ok) {
|
|
753
|
+
throw new Error(
|
|
754
|
+
`Failed to fetch audio from URL: ${sourceUrl} (${response.status})`
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
const arr = await response.arrayBuffer();
|
|
758
|
+
data = Buffer.from(arr).toString("base64");
|
|
759
|
+
} else {
|
|
760
|
+
throw new Error(
|
|
761
|
+
`Unsupported audio source: ${sourceUrl}, it should be a local file path, file URL, or an HTTP URL.`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
const ext = extname(localPath || sourceUrl).toLowerCase();
|
|
765
|
+
const extToFormat = /* @__PURE__ */ new Map([
|
|
766
|
+
[".wav", "wav"],
|
|
767
|
+
[".mp3", "mp3"]
|
|
768
|
+
]);
|
|
769
|
+
const format = extToFormat.get(ext);
|
|
770
|
+
if (!format) {
|
|
771
|
+
throw new TypeError(`Unsupported audio extension: ${ext}, wav and mp3 are supported.`);
|
|
772
|
+
}
|
|
773
|
+
return { data, format };
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Converts a URL or path to a local file path.
|
|
777
|
+
*
|
|
778
|
+
* @param urlOrPath - The URL or path to convert.
|
|
779
|
+
* @returns The local file path, or null if not a local path.
|
|
780
|
+
*/
|
|
781
|
+
_toLocalPath(urlOrPath) {
|
|
782
|
+
if (urlOrPath.startsWith("file://")) {
|
|
783
|
+
return fileURLToPath(urlOrPath);
|
|
784
|
+
}
|
|
785
|
+
if (!urlOrPath.includes("://")) {
|
|
786
|
+
return urlOrPath;
|
|
787
|
+
}
|
|
788
|
+
return null;
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
// src/model/dashscope-model.ts
|
|
793
|
+
var DashScopeChatModel = class extends ChatModelBase {
|
|
794
|
+
apiURL;
|
|
795
|
+
apiKey;
|
|
796
|
+
presetGenParams;
|
|
797
|
+
presetHeaders;
|
|
798
|
+
thinkingConfig;
|
|
799
|
+
/**
|
|
800
|
+
* Initializes a new instance of the DashScopeChatModel class.
|
|
801
|
+
*
|
|
802
|
+
* @param options - The DashScope chat model options.
|
|
803
|
+
* @param options.modelName - The name of the model to use.
|
|
804
|
+
* @param options.apiKey - The API key for authentication.
|
|
805
|
+
* @param options.stream - Whether to use streaming responses. Default is true.
|
|
806
|
+
* @param options.thinkingConfig - The thinking configuration for DashScope models, including whether to enable thinking and the thinking budget.
|
|
807
|
+
* @param options.maxRetries - The maximum number of retries for failed requests. Default is 3.
|
|
808
|
+
* @param options.fallbackModelName - The fallback model name to use if the primary model fails.
|
|
809
|
+
* @param options.presetGenParams - Preset generation parameters to include in each request.
|
|
810
|
+
* @param options.presetHeaders - Preset headers that will be included in each request.
|
|
811
|
+
* @param options.multimodal - Whether the model is multimodal or not, this will decide the default API endpoint. If not provided, it will be inferred from the model name.
|
|
812
|
+
* @param options.formatter - An optional custom formatter. If not provided, a default DashScopeChatFormatter will be used.
|
|
813
|
+
*/
|
|
814
|
+
constructor({
|
|
815
|
+
modelName,
|
|
816
|
+
apiKey,
|
|
817
|
+
stream = true,
|
|
818
|
+
thinkingConfig,
|
|
819
|
+
maxRetries = 0,
|
|
820
|
+
fallbackModelName,
|
|
821
|
+
presetGenParams,
|
|
822
|
+
presetHeaders,
|
|
823
|
+
multimodal,
|
|
824
|
+
formatter
|
|
825
|
+
}) {
|
|
826
|
+
const defaultFormatter = formatter || new DashScopeChatFormatter();
|
|
827
|
+
super({
|
|
828
|
+
modelName,
|
|
829
|
+
stream,
|
|
830
|
+
maxRetries,
|
|
831
|
+
fallbackModelName,
|
|
832
|
+
formatter: defaultFormatter
|
|
833
|
+
});
|
|
834
|
+
this.apiKey = apiKey;
|
|
835
|
+
this.thinkingConfig = thinkingConfig;
|
|
836
|
+
this.presetGenParams = presetGenParams;
|
|
837
|
+
this.presetHeaders = presetHeaders;
|
|
838
|
+
if (multimodal === void 0) {
|
|
839
|
+
multimodal = modelName.includes("vl") || modelName.includes("qwen3.5-plus") || modelName.includes("qvq");
|
|
840
|
+
}
|
|
841
|
+
this.apiURL = multimodal ? "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation" : "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation";
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Calls the DashScope API with the given parameters.
|
|
845
|
+
*
|
|
846
|
+
* @param modelName - The name of the model to use.
|
|
847
|
+
* @param options - The chat model options.
|
|
848
|
+
* @returns A promise that resolves to either a ChatResponse or an AsyncGenerator of ChatResponses.
|
|
849
|
+
*/
|
|
850
|
+
async _callAPI(modelName, options) {
|
|
851
|
+
const data = {
|
|
852
|
+
model: modelName,
|
|
853
|
+
input: {
|
|
854
|
+
messages: options.messages
|
|
855
|
+
},
|
|
856
|
+
parameters: {
|
|
857
|
+
result_format: "message",
|
|
858
|
+
tools: this._formatToolSchemas(options.tools),
|
|
859
|
+
toolChoice: this._formatToolChoice(options.toolChoice),
|
|
860
|
+
enable_thinking: this.thinkingConfig?.enableThinking ?? false,
|
|
861
|
+
...this.thinkingConfig?.thinkingBudget !== void 0 && {
|
|
862
|
+
thinking_budget: this.thinkingConfig.thinkingBudget
|
|
863
|
+
},
|
|
864
|
+
...this.presetGenParams ?? {},
|
|
865
|
+
incremental_output: true
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
const headers = {
|
|
869
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
870
|
+
"Content-Type": "application/json",
|
|
871
|
+
...this.presetHeaders
|
|
872
|
+
};
|
|
873
|
+
if (this.stream) {
|
|
874
|
+
headers["X-DashScope-SSE"] = "enable";
|
|
875
|
+
}
|
|
876
|
+
const startTime = Date.now();
|
|
877
|
+
const response = await fetch(this.apiURL, {
|
|
878
|
+
method: "POST",
|
|
879
|
+
headers,
|
|
880
|
+
body: JSON.stringify(data)
|
|
881
|
+
});
|
|
882
|
+
if (!response.ok) {
|
|
883
|
+
throw new Error(
|
|
884
|
+
`DashScope API request failed with status ${response.status}: ${await response.text()}`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
if (this.stream) {
|
|
888
|
+
return this._parseDashScopeStreamedResponse(response, startTime);
|
|
889
|
+
}
|
|
890
|
+
const blocks = [];
|
|
891
|
+
const res = await response.json();
|
|
892
|
+
const choice = res.output.choices[0];
|
|
893
|
+
if (choice.message.reasoning_content) {
|
|
894
|
+
blocks.push({
|
|
895
|
+
type: "thinking",
|
|
896
|
+
thinking: choice.message.reasoning_content,
|
|
897
|
+
id: crypto.randomUUID()
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
if (choice.message.content) {
|
|
901
|
+
blocks.push({ type: "text", text: choice.message.content, id: crypto.randomUUID() });
|
|
902
|
+
}
|
|
903
|
+
if (choice.message.tool_calls && Array.isArray(choice.message.tool_calls)) {
|
|
904
|
+
choice.message.tool_calls.forEach((toolCall) => {
|
|
905
|
+
if ("id" in toolCall && "function" in toolCall && typeof toolCall.function === "object" && toolCall.function && "name" in toolCall.function && "arguments" in toolCall.function) {
|
|
906
|
+
const inputString = String(toolCall.function.arguments);
|
|
907
|
+
blocks.push({
|
|
908
|
+
type: "tool_call",
|
|
909
|
+
id: String(toolCall.id),
|
|
910
|
+
name: String(toolCall.function.name),
|
|
911
|
+
input: inputString
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
const usage = res.usage ? {
|
|
917
|
+
type: "chat_usage",
|
|
918
|
+
inputTokens: res.usage.input_tokens || 0,
|
|
919
|
+
outputTokens: res.usage.output_tokens || 0,
|
|
920
|
+
time: (Date.now() - startTime) / 1e3
|
|
921
|
+
} : void 0;
|
|
922
|
+
return {
|
|
923
|
+
type: "chat",
|
|
924
|
+
id: crypto.randomUUID(),
|
|
925
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
926
|
+
content: blocks,
|
|
927
|
+
usage
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* The method to format the tool choice parameter.
|
|
932
|
+
*
|
|
933
|
+
* @param toolChoice - The tool choice option.
|
|
934
|
+
* @returns The formatted tool choice.
|
|
935
|
+
*/
|
|
936
|
+
_formatToolChoice(toolChoice) {
|
|
937
|
+
if (toolChoice) {
|
|
938
|
+
if (toolChoice === "auto") return "auto";
|
|
939
|
+
if (toolChoice === "none") return "none";
|
|
940
|
+
return {
|
|
941
|
+
type: "function",
|
|
942
|
+
function: {
|
|
943
|
+
name: toolChoice
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
return "auto";
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Parses a streamed response from DashScope API specifically for chat responses.
|
|
951
|
+
* An async generator that yields delta ChatResponse objects as they are received.
|
|
952
|
+
*
|
|
953
|
+
* @param response - The fetch response object.
|
|
954
|
+
* @param startTime - The start time of the request for usage calculation.
|
|
955
|
+
* @returns An async generator yielding delta ChatResponse objects, and returns the complete ChatResponse.
|
|
956
|
+
*/
|
|
957
|
+
async *_parseDashScopeStreamedResponse(response, startTime) {
|
|
958
|
+
const asyncGenerator = _parseStreamedResponse(response);
|
|
959
|
+
let accText = "";
|
|
960
|
+
let accThinking = "";
|
|
961
|
+
const accToolInputs = /* @__PURE__ */ new Map();
|
|
962
|
+
const toolCallMeta = /* @__PURE__ */ new Map();
|
|
963
|
+
let lastUsage = void 0;
|
|
964
|
+
for await (const jsonObj of asyncGenerator) {
|
|
965
|
+
if (jsonObj.output && jsonObj.output.choices) {
|
|
966
|
+
const choice = jsonObj.output.choices[0];
|
|
967
|
+
let deltaText = "";
|
|
968
|
+
let deltaThinking = "";
|
|
969
|
+
const deltaToolCalls = /* @__PURE__ */ new Map();
|
|
970
|
+
const content = choice.message?.content;
|
|
971
|
+
if (content) {
|
|
972
|
+
if (typeof content === "string") {
|
|
973
|
+
deltaText = content;
|
|
974
|
+
} else if (Array.isArray(content)) {
|
|
975
|
+
for (const block of content) {
|
|
976
|
+
if (block.text) {
|
|
977
|
+
deltaText += block.text;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
accText += deltaText;
|
|
982
|
+
}
|
|
983
|
+
if (choice.message?.reasoning_content) {
|
|
984
|
+
deltaThinking = choice.message.reasoning_content;
|
|
985
|
+
accThinking += deltaThinking;
|
|
986
|
+
}
|
|
987
|
+
if (choice.message?.tool_calls) {
|
|
988
|
+
choice.message.tool_calls.forEach((toolCall) => {
|
|
989
|
+
const index = toolCall.index.toString();
|
|
990
|
+
if (!toolCallMeta.has(index)) {
|
|
991
|
+
toolCallMeta.set(index, { id: "", name: "" });
|
|
992
|
+
}
|
|
993
|
+
if (!accToolInputs.has(index)) {
|
|
994
|
+
accToolInputs.set(index, "");
|
|
995
|
+
}
|
|
996
|
+
if (toolCall.id) {
|
|
997
|
+
toolCallMeta.get(index).id = toolCall.id;
|
|
998
|
+
}
|
|
999
|
+
if (toolCall.function?.name) {
|
|
1000
|
+
toolCallMeta.get(index).name = toolCall.function.name;
|
|
1001
|
+
}
|
|
1002
|
+
if (toolCall.function?.arguments) {
|
|
1003
|
+
const deltaArgs = toolCall.function.arguments;
|
|
1004
|
+
accToolInputs.set(index, accToolInputs.get(index) + deltaArgs);
|
|
1005
|
+
const meta = toolCallMeta.get(index);
|
|
1006
|
+
deltaToolCalls.set(index, {
|
|
1007
|
+
type: "tool_call",
|
|
1008
|
+
id: meta.id,
|
|
1009
|
+
name: meta.name,
|
|
1010
|
+
input: deltaArgs
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
const deltaBlocks = this._dataToBlocks(deltaText, deltaThinking, deltaToolCalls);
|
|
1016
|
+
lastUsage = jsonObj.usage ? {
|
|
1017
|
+
type: "chat_usage",
|
|
1018
|
+
inputTokens: jsonObj.usage.input_tokens || 0,
|
|
1019
|
+
outputTokens: jsonObj.usage.output_tokens || 0,
|
|
1020
|
+
time: (Date.now() - startTime) / 1e3
|
|
1021
|
+
} : void 0;
|
|
1022
|
+
yield {
|
|
1023
|
+
type: "chat",
|
|
1024
|
+
id: crypto.randomUUID(),
|
|
1025
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1026
|
+
content: deltaBlocks,
|
|
1027
|
+
usage: lastUsage
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
const finalToolCalls = /* @__PURE__ */ new Map();
|
|
1032
|
+
toolCallMeta.forEach((meta, index) => {
|
|
1033
|
+
finalToolCalls.set(index, {
|
|
1034
|
+
type: "tool_call",
|
|
1035
|
+
id: meta.id,
|
|
1036
|
+
name: meta.name,
|
|
1037
|
+
input: accToolInputs.get(index) || "{}"
|
|
1038
|
+
});
|
|
1039
|
+
});
|
|
1040
|
+
const blocks = this._dataToBlocks(accText, accThinking, finalToolCalls);
|
|
1041
|
+
return {
|
|
1042
|
+
type: "chat",
|
|
1043
|
+
id: crypto.randomUUID(),
|
|
1044
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1045
|
+
content: blocks,
|
|
1046
|
+
usage: lastUsage
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Convert data into blocks
|
|
1051
|
+
*
|
|
1052
|
+
* @param text - The text response from the llm API
|
|
1053
|
+
* @param thinking - The thinking response
|
|
1054
|
+
* @param toolCalls - The tool calls
|
|
1055
|
+
* @returns An array of blocks
|
|
1056
|
+
*/
|
|
1057
|
+
_dataToBlocks(text, thinking, toolCalls) {
|
|
1058
|
+
const blocks = [];
|
|
1059
|
+
if (thinking) {
|
|
1060
|
+
blocks.push({ type: "thinking", thinking, id: crypto.randomUUID() });
|
|
1061
|
+
}
|
|
1062
|
+
if (text) {
|
|
1063
|
+
blocks.push({ type: "text", text, id: crypto.randomUUID() });
|
|
1064
|
+
}
|
|
1065
|
+
if (toolCalls.size > 0) {
|
|
1066
|
+
toolCalls.forEach((value) => {
|
|
1067
|
+
blocks.push(value);
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
return blocks;
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Format the tool schemas to the expected API format.
|
|
1074
|
+
* @param tools
|
|
1075
|
+
* @returns The formatted tool schemas.
|
|
1076
|
+
*/
|
|
1077
|
+
_formatToolSchemas(tools) {
|
|
1078
|
+
return tools || [];
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
// src/model/deepseek-model.ts
|
|
1083
|
+
var DeepSeekChatModel = class extends ChatModelBase {
|
|
1084
|
+
apiURL;
|
|
1085
|
+
apiKey;
|
|
1086
|
+
presetGenParams;
|
|
1087
|
+
presetHeaders;
|
|
1088
|
+
thinkingConfig;
|
|
1089
|
+
/**
|
|
1090
|
+
* Initializes a new instance of the DeepSeekChatModel class.
|
|
1091
|
+
*
|
|
1092
|
+
* @param options - The DeepSeek chat model options.
|
|
1093
|
+
* @param options.modelName - The name of the model to use.
|
|
1094
|
+
* @param options.apiKey - The API key for authentication.
|
|
1095
|
+
* @param options.stream - Whether to use streaming responses. Default is true.
|
|
1096
|
+
* @param options.thinkingConfig - Thinking configuration.
|
|
1097
|
+
* @param options.maxRetries - The maximum number of retries for failed requests. Default is 0.
|
|
1098
|
+
* @param options.fallbackModelName - The fallback model name to use if the primary model fails.
|
|
1099
|
+
* @param options.presetGenParams - Preset generation parameters to include in each request.
|
|
1100
|
+
* @param options.presetHeaders - Preset headers that will be included in each request.
|
|
1101
|
+
* @param options.formatter
|
|
1102
|
+
*/
|
|
1103
|
+
constructor({
|
|
1104
|
+
modelName,
|
|
1105
|
+
apiKey,
|
|
1106
|
+
stream = true,
|
|
1107
|
+
thinkingConfig,
|
|
1108
|
+
maxRetries = 0,
|
|
1109
|
+
fallbackModelName,
|
|
1110
|
+
presetGenParams,
|
|
1111
|
+
presetHeaders,
|
|
1112
|
+
formatter
|
|
1113
|
+
}) {
|
|
1114
|
+
const defaultFormatter = formatter || new DeepSeekChatFormatter();
|
|
1115
|
+
super({
|
|
1116
|
+
modelName,
|
|
1117
|
+
stream,
|
|
1118
|
+
maxRetries,
|
|
1119
|
+
fallbackModelName,
|
|
1120
|
+
formatter: defaultFormatter
|
|
1121
|
+
});
|
|
1122
|
+
this.apiKey = apiKey;
|
|
1123
|
+
this.thinkingConfig = thinkingConfig || { enableThinking: false };
|
|
1124
|
+
this.presetGenParams = presetGenParams;
|
|
1125
|
+
this.presetHeaders = presetHeaders;
|
|
1126
|
+
this.apiURL = "https://api.deepseek.com/chat/completions";
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Calls the DeepSeek API with the given parameters.
|
|
1130
|
+
*
|
|
1131
|
+
* @param modelName - The name of the model to use.
|
|
1132
|
+
* @param options - The chat model options.
|
|
1133
|
+
* @returns A promise that resolves to either a ChatResponse or an AsyncGenerator of ChatResponses.
|
|
1134
|
+
*/
|
|
1135
|
+
async _callAPI(modelName, options) {
|
|
1136
|
+
const data = {
|
|
1137
|
+
model: modelName,
|
|
1138
|
+
messages: options.messages,
|
|
1139
|
+
tools: this._formatToolSchemas(options.tools),
|
|
1140
|
+
tool_choice: this._formatToolChoice(options.toolChoice),
|
|
1141
|
+
thinking: this.thinkingConfig.enableThinking ? { type: "enabled" } : { type: "disabled" },
|
|
1142
|
+
stream: this.stream,
|
|
1143
|
+
...this.presetGenParams ?? {}
|
|
1144
|
+
};
|
|
1145
|
+
const headers = {
|
|
1146
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
1147
|
+
"Content-Type": "application/json",
|
|
1148
|
+
...this.presetHeaders
|
|
1149
|
+
};
|
|
1150
|
+
const startTime = Date.now();
|
|
1151
|
+
const response = await fetch(this.apiURL, {
|
|
1152
|
+
method: "POST",
|
|
1153
|
+
headers,
|
|
1154
|
+
body: JSON.stringify(data)
|
|
1155
|
+
});
|
|
1156
|
+
if (!response.ok) {
|
|
1157
|
+
throw new Error(
|
|
1158
|
+
`DeepSeek API request failed with status ${response.status}: ${await response.text()}`
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
if (this.stream) {
|
|
1162
|
+
return this._parseDeepSeekStreamedResponse(response, startTime);
|
|
1163
|
+
}
|
|
1164
|
+
const blocks = [];
|
|
1165
|
+
const res = await response.json();
|
|
1166
|
+
const choice = res.choices[0];
|
|
1167
|
+
if (choice.message.reasoning_content) {
|
|
1168
|
+
blocks.push({
|
|
1169
|
+
id: crypto.randomUUID(),
|
|
1170
|
+
type: "thinking",
|
|
1171
|
+
thinking: choice.message.reasoning_content
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
if (choice.message.content) {
|
|
1175
|
+
blocks.push({ id: crypto.randomUUID(), type: "text", text: choice.message.content });
|
|
1176
|
+
}
|
|
1177
|
+
if (choice.message.tool_calls && Array.isArray(choice.message.tool_calls)) {
|
|
1178
|
+
choice.message.tool_calls.forEach((toolCall) => {
|
|
1179
|
+
if ("id" in toolCall && "function" in toolCall && typeof toolCall.function === "object" && toolCall.function && "name" in toolCall.function && "arguments" in toolCall.function) {
|
|
1180
|
+
const inputString = String(toolCall.function.arguments);
|
|
1181
|
+
blocks.push({
|
|
1182
|
+
type: "tool_call",
|
|
1183
|
+
id: String(toolCall.id),
|
|
1184
|
+
name: String(toolCall.function.name),
|
|
1185
|
+
input: inputString
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
const usage = res.usage ? {
|
|
1191
|
+
type: "chat_usage",
|
|
1192
|
+
inputTokens: res.usage.prompt_tokens || 0,
|
|
1193
|
+
outputTokens: res.usage.completion_tokens || 0,
|
|
1194
|
+
time: (Date.now() - startTime) / 1e3
|
|
1195
|
+
} : void 0;
|
|
1196
|
+
return {
|
|
1197
|
+
type: "chat",
|
|
1198
|
+
id: crypto.randomUUID(),
|
|
1199
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1200
|
+
content: blocks,
|
|
1201
|
+
usage
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* The method to format the tool choice parameter.
|
|
1206
|
+
*
|
|
1207
|
+
* @param toolChoice - The tool choice option.
|
|
1208
|
+
* @returns The formatted tool choice.
|
|
1209
|
+
*/
|
|
1210
|
+
_formatToolChoice(toolChoice) {
|
|
1211
|
+
if (toolChoice) {
|
|
1212
|
+
if (toolChoice === "auto") return "auto";
|
|
1213
|
+
if (toolChoice === "none") return "none";
|
|
1214
|
+
if (this.thinkingConfig?.enableThinking) {
|
|
1215
|
+
console.log(
|
|
1216
|
+
`The deepseek reasoning model does not support tool choice options '${toolChoice}'. 'auto' will be used instead.`
|
|
1217
|
+
);
|
|
1218
|
+
return "auto";
|
|
1219
|
+
}
|
|
1220
|
+
if (toolChoice === "required") return "required";
|
|
1221
|
+
return {
|
|
1222
|
+
type: "function",
|
|
1223
|
+
function: {
|
|
1224
|
+
name: toolChoice
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
return "auto";
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Parses a streamed response from DeepSeek API specifically for chat responses.
|
|
1232
|
+
* An async generator that yields delta ChatResponse objects as they are received.
|
|
1233
|
+
*
|
|
1234
|
+
* @param response - The fetch response object.
|
|
1235
|
+
* @param startTime - The start time of the request for usage calculation.
|
|
1236
|
+
* @returns An async generator yielding delta ChatResponse objects, and returns the complete ChatResponse.
|
|
1237
|
+
*/
|
|
1238
|
+
async *_parseDeepSeekStreamedResponse(response, startTime) {
|
|
1239
|
+
const asyncGenerator = _parseStreamedResponse(response);
|
|
1240
|
+
let accText = "";
|
|
1241
|
+
let accThinking = "";
|
|
1242
|
+
const accToolInputs = /* @__PURE__ */ new Map();
|
|
1243
|
+
const toolCallMeta = /* @__PURE__ */ new Map();
|
|
1244
|
+
let lastUsage = void 0;
|
|
1245
|
+
for await (const jsonObj of asyncGenerator) {
|
|
1246
|
+
if (jsonObj.choices && jsonObj.choices.length > 0) {
|
|
1247
|
+
const choice = jsonObj.choices[0];
|
|
1248
|
+
let deltaText = "";
|
|
1249
|
+
let deltaThinking = "";
|
|
1250
|
+
const deltaToolCalls = /* @__PURE__ */ new Map();
|
|
1251
|
+
if (choice.delta?.content) {
|
|
1252
|
+
deltaText = choice.delta.content;
|
|
1253
|
+
accText += deltaText;
|
|
1254
|
+
}
|
|
1255
|
+
if (choice.delta?.reasoning_content) {
|
|
1256
|
+
deltaThinking = choice.delta.reasoning_content;
|
|
1257
|
+
accThinking += deltaThinking;
|
|
1258
|
+
}
|
|
1259
|
+
if (choice.delta?.tool_calls) {
|
|
1260
|
+
choice.delta.tool_calls.forEach((toolCall) => {
|
|
1261
|
+
const index = toolCall.index.toString();
|
|
1262
|
+
if (!toolCallMeta.has(index)) {
|
|
1263
|
+
toolCallMeta.set(index, { id: "", name: "" });
|
|
1264
|
+
}
|
|
1265
|
+
if (!accToolInputs.has(index)) {
|
|
1266
|
+
accToolInputs.set(index, "");
|
|
1267
|
+
}
|
|
1268
|
+
if (toolCall.id) {
|
|
1269
|
+
toolCallMeta.get(index).id = toolCall.id;
|
|
1270
|
+
}
|
|
1271
|
+
if (toolCall.function?.name) {
|
|
1272
|
+
toolCallMeta.get(index).name = toolCall.function.name;
|
|
1273
|
+
}
|
|
1274
|
+
if (toolCall.function?.arguments) {
|
|
1275
|
+
const deltaArgs = toolCall.function.arguments;
|
|
1276
|
+
accToolInputs.set(index, accToolInputs.get(index) + deltaArgs);
|
|
1277
|
+
const meta = toolCallMeta.get(index);
|
|
1278
|
+
deltaToolCalls.set(index, {
|
|
1279
|
+
type: "tool_call",
|
|
1280
|
+
id: meta.id,
|
|
1281
|
+
name: meta.name,
|
|
1282
|
+
input: deltaArgs
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
const deltaBlocks = this._accDataToBlocks(deltaText, deltaThinking, deltaToolCalls);
|
|
1288
|
+
lastUsage = jsonObj.usage ? {
|
|
1289
|
+
type: "chat_usage",
|
|
1290
|
+
inputTokens: jsonObj.usage.prompt_tokens || 0,
|
|
1291
|
+
outputTokens: jsonObj.usage.completion_tokens || 0,
|
|
1292
|
+
time: (Date.now() - startTime) / 1e3
|
|
1293
|
+
} : void 0;
|
|
1294
|
+
yield {
|
|
1295
|
+
type: "chat",
|
|
1296
|
+
id: crypto.randomUUID(),
|
|
1297
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1298
|
+
content: deltaBlocks,
|
|
1299
|
+
usage: lastUsage
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
const finalToolCalls = /* @__PURE__ */ new Map();
|
|
1304
|
+
toolCallMeta.forEach((meta, index) => {
|
|
1305
|
+
finalToolCalls.set(index, {
|
|
1306
|
+
type: "tool_call",
|
|
1307
|
+
id: meta.id,
|
|
1308
|
+
name: meta.name,
|
|
1309
|
+
input: accToolInputs.get(index) || "{}"
|
|
1310
|
+
});
|
|
1311
|
+
});
|
|
1312
|
+
const blocks = this._accDataToBlocks(accText, accThinking, finalToolCalls);
|
|
1313
|
+
return {
|
|
1314
|
+
type: "chat",
|
|
1315
|
+
id: crypto.randomUUID(),
|
|
1316
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1317
|
+
content: blocks,
|
|
1318
|
+
usage: lastUsage
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Convert data into blocks
|
|
1323
|
+
*
|
|
1324
|
+
* @param text - The text response from the llm API
|
|
1325
|
+
* @param thinking - The thinking response
|
|
1326
|
+
* @param toolCalls - The tool calls
|
|
1327
|
+
* @returns An array of blocks
|
|
1328
|
+
*/
|
|
1329
|
+
_accDataToBlocks(text, thinking, toolCalls) {
|
|
1330
|
+
const blocks = [];
|
|
1331
|
+
if (thinking) {
|
|
1332
|
+
blocks.push({ id: crypto.randomUUID(), type: "thinking", thinking });
|
|
1333
|
+
}
|
|
1334
|
+
if (text) {
|
|
1335
|
+
blocks.push({ id: crypto.randomUUID(), type: "text", text });
|
|
1336
|
+
}
|
|
1337
|
+
if (toolCalls.size > 0) {
|
|
1338
|
+
toolCalls.forEach((value) => {
|
|
1339
|
+
blocks.push(value);
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
return blocks;
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Format the tool schemas to the expected API format for DeepSeek API.
|
|
1346
|
+
* @param tools
|
|
1347
|
+
* @returns The formatted tool schemas.
|
|
1348
|
+
*/
|
|
1349
|
+
_formatToolSchemas(tools) {
|
|
1350
|
+
return tools || [];
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
|
|
1354
|
+
// src/model/ollama-model.ts
|
|
1355
|
+
import { Ollama } from "ollama";
|
|
1356
|
+
var OllamaChatModel = class extends ChatModelBase {
|
|
1357
|
+
client;
|
|
1358
|
+
options;
|
|
1359
|
+
keepAlive;
|
|
1360
|
+
thinkingConfig;
|
|
1361
|
+
generateKwargs;
|
|
1362
|
+
/**
|
|
1363
|
+
* Initializes a new instance of the OllamaChatModel class.
|
|
1364
|
+
* @param root0
|
|
1365
|
+
* @param root0.modelName
|
|
1366
|
+
* @param root0.stream
|
|
1367
|
+
* @param root0.options
|
|
1368
|
+
* @param root0.keepAlive
|
|
1369
|
+
* @param root0.thinkingConfig
|
|
1370
|
+
* @param root0.host
|
|
1371
|
+
* @param root0.maxRetries
|
|
1372
|
+
* @param root0.fallbackModelName
|
|
1373
|
+
* @param root0.clientKwargs
|
|
1374
|
+
* @param root0.generateKwargs
|
|
1375
|
+
* @param root0.formatter
|
|
1376
|
+
*/
|
|
1377
|
+
constructor({
|
|
1378
|
+
modelName,
|
|
1379
|
+
stream = true,
|
|
1380
|
+
options,
|
|
1381
|
+
keepAlive = "5m",
|
|
1382
|
+
thinkingConfig,
|
|
1383
|
+
host,
|
|
1384
|
+
maxRetries = 0,
|
|
1385
|
+
fallbackModelName,
|
|
1386
|
+
clientKwargs,
|
|
1387
|
+
generateKwargs,
|
|
1388
|
+
formatter
|
|
1389
|
+
}) {
|
|
1390
|
+
const defaultFormatter = formatter || new OllamaChatFormatter();
|
|
1391
|
+
super({
|
|
1392
|
+
modelName,
|
|
1393
|
+
stream,
|
|
1394
|
+
maxRetries,
|
|
1395
|
+
fallbackModelName,
|
|
1396
|
+
formatter: defaultFormatter
|
|
1397
|
+
});
|
|
1398
|
+
this.options = options;
|
|
1399
|
+
this.keepAlive = keepAlive;
|
|
1400
|
+
this.thinkingConfig = thinkingConfig || {
|
|
1401
|
+
enableThinking: false
|
|
1402
|
+
};
|
|
1403
|
+
this.generateKwargs = generateKwargs || {};
|
|
1404
|
+
this.client = new Ollama({
|
|
1405
|
+
host,
|
|
1406
|
+
...clientKwargs
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Calls the Ollama API with the given parameters.
|
|
1411
|
+
* @param modelName
|
|
1412
|
+
* @param options
|
|
1413
|
+
* @returns A promise that resolves to either a ChatResponse or an AsyncGenerator of ChatResponses.
|
|
1414
|
+
*/
|
|
1415
|
+
async _callAPI(modelName, options) {
|
|
1416
|
+
const kwargs = {
|
|
1417
|
+
model: modelName,
|
|
1418
|
+
messages: options.messages,
|
|
1419
|
+
stream: this.stream,
|
|
1420
|
+
options: this.options,
|
|
1421
|
+
keep_alive: this.keepAlive,
|
|
1422
|
+
...this.generateKwargs
|
|
1423
|
+
};
|
|
1424
|
+
if (this.thinkingConfig.enableThinking) {
|
|
1425
|
+
kwargs.think = this.thinkingConfig.thinkingLevel || true;
|
|
1426
|
+
} else {
|
|
1427
|
+
kwargs.think = false;
|
|
1428
|
+
}
|
|
1429
|
+
if (options.tools) {
|
|
1430
|
+
kwargs.tools = this._formatToolSchemas(options.tools);
|
|
1431
|
+
}
|
|
1432
|
+
if (options.toolChoice) {
|
|
1433
|
+
console.warn("Ollama does not support tool_choice yet, ignored.");
|
|
1434
|
+
}
|
|
1435
|
+
const startTime = Date.now();
|
|
1436
|
+
if (this.stream) {
|
|
1437
|
+
const response2 = await this.client.chat({
|
|
1438
|
+
...kwargs,
|
|
1439
|
+
stream: true
|
|
1440
|
+
});
|
|
1441
|
+
return this._parseOllamaStreamResponse(response2, startTime);
|
|
1442
|
+
}
|
|
1443
|
+
const response = await this.client.chat({
|
|
1444
|
+
...kwargs,
|
|
1445
|
+
stream: false
|
|
1446
|
+
});
|
|
1447
|
+
return this._parseOllamaResponse(response, startTime);
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Parse Ollama streaming response.
|
|
1451
|
+
* @param stream
|
|
1452
|
+
* @param startTime
|
|
1453
|
+
* @returns An async generator that yields delta ChatResponse objects and returns the complete ChatResponse.
|
|
1454
|
+
*/
|
|
1455
|
+
async *_parseOllamaStreamResponse(stream, startTime) {
|
|
1456
|
+
let accText = "";
|
|
1457
|
+
let accThinking = "";
|
|
1458
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
1459
|
+
let lastUsage = null;
|
|
1460
|
+
for await (const chunk of stream) {
|
|
1461
|
+
const msg = chunk.message;
|
|
1462
|
+
let deltaText = "";
|
|
1463
|
+
let deltaThinking = "";
|
|
1464
|
+
const deltaToolCalls = /* @__PURE__ */ new Map();
|
|
1465
|
+
if (msg.thinking) {
|
|
1466
|
+
deltaThinking = msg.thinking;
|
|
1467
|
+
accThinking += msg.thinking;
|
|
1468
|
+
}
|
|
1469
|
+
if (msg.content) {
|
|
1470
|
+
deltaText = msg.content;
|
|
1471
|
+
accText += msg.content;
|
|
1472
|
+
}
|
|
1473
|
+
if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
|
|
1474
|
+
for (let idx = 0; idx < msg.tool_calls.length; idx++) {
|
|
1475
|
+
const toolCall = msg.tool_calls[idx];
|
|
1476
|
+
const func = toolCall.function;
|
|
1477
|
+
const toolId = `${idx}_${func.name}`;
|
|
1478
|
+
const toolCallBlock = {
|
|
1479
|
+
type: "tool_call",
|
|
1480
|
+
id: toolId,
|
|
1481
|
+
name: func.name,
|
|
1482
|
+
input: JSON.stringify(func.arguments)
|
|
1483
|
+
};
|
|
1484
|
+
toolCalls.set(toolId, toolCallBlock);
|
|
1485
|
+
deltaToolCalls.set(toolId, toolCallBlock);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
const currentTime = (Date.now() - startTime) / 1e3;
|
|
1489
|
+
lastUsage = {
|
|
1490
|
+
type: "chat_usage",
|
|
1491
|
+
inputTokens: chunk.prompt_eval_count || 0,
|
|
1492
|
+
outputTokens: chunk.eval_count || 0,
|
|
1493
|
+
time: currentTime
|
|
1494
|
+
};
|
|
1495
|
+
const deltaBlocks = this._buildContentBlocks(deltaText, deltaThinking, deltaToolCalls);
|
|
1496
|
+
yield {
|
|
1497
|
+
type: "chat",
|
|
1498
|
+
id: crypto.randomUUID(),
|
|
1499
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1500
|
+
content: deltaBlocks,
|
|
1501
|
+
usage: lastUsage
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
const blocks = this._buildContentBlocks(accText, accThinking, toolCalls);
|
|
1505
|
+
return {
|
|
1506
|
+
type: "chat",
|
|
1507
|
+
id: crypto.randomUUID(),
|
|
1508
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1509
|
+
content: blocks,
|
|
1510
|
+
usage: lastUsage
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
* Parse Ollama non-streaming response.
|
|
1515
|
+
* @param response
|
|
1516
|
+
* @param startTime
|
|
1517
|
+
* @returns A ChatResponse object containing the content blocks and usage.
|
|
1518
|
+
*/
|
|
1519
|
+
_parseOllamaResponse(response, startTime) {
|
|
1520
|
+
const blocks = [];
|
|
1521
|
+
if (response.message.thinking) {
|
|
1522
|
+
blocks.push({
|
|
1523
|
+
id: crypto.randomUUID(),
|
|
1524
|
+
type: "thinking",
|
|
1525
|
+
thinking: response.message.thinking
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
if (response.message.content) {
|
|
1529
|
+
blocks.push({
|
|
1530
|
+
id: crypto.randomUUID(),
|
|
1531
|
+
type: "text",
|
|
1532
|
+
text: response.message.content
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
if (response.message.tool_calls && Array.isArray(response.message.tool_calls)) {
|
|
1536
|
+
for (let idx = 0; idx < response.message.tool_calls.length; idx++) {
|
|
1537
|
+
const toolCall = response.message.tool_calls[idx];
|
|
1538
|
+
blocks.push({
|
|
1539
|
+
type: "tool_call",
|
|
1540
|
+
id: `${idx}_${toolCall.function.name}`,
|
|
1541
|
+
name: toolCall.function.name,
|
|
1542
|
+
input: JSON.stringify(toolCall.function.arguments)
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
const usage = response.prompt_eval_count !== void 0 && response.eval_count !== void 0 ? {
|
|
1547
|
+
type: "chat_usage",
|
|
1548
|
+
inputTokens: response.prompt_eval_count || 0,
|
|
1549
|
+
outputTokens: response.eval_count || 0,
|
|
1550
|
+
time: (Date.now() - startTime) / 1e3
|
|
1551
|
+
} : void 0;
|
|
1552
|
+
return {
|
|
1553
|
+
type: "chat",
|
|
1554
|
+
id: crypto.randomUUID(),
|
|
1555
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1556
|
+
content: blocks,
|
|
1557
|
+
usage
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Build content blocks from accumulated data.
|
|
1562
|
+
* @param text
|
|
1563
|
+
* @param thinking
|
|
1564
|
+
* @param toolCalls
|
|
1565
|
+
* @returns An array of content blocks.
|
|
1566
|
+
*/
|
|
1567
|
+
_buildContentBlocks(text, thinking, toolCalls) {
|
|
1568
|
+
const blocks = [];
|
|
1569
|
+
if (thinking) {
|
|
1570
|
+
blocks.push({ id: crypto.randomUUID(), type: "thinking", thinking });
|
|
1571
|
+
}
|
|
1572
|
+
if (text) {
|
|
1573
|
+
blocks.push({ id: crypto.randomUUID(), type: "text", text });
|
|
1574
|
+
}
|
|
1575
|
+
toolCalls.forEach((toolCall) => {
|
|
1576
|
+
blocks.push(toolCall);
|
|
1577
|
+
});
|
|
1578
|
+
return blocks;
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Format tool choice parameter (not supported by Ollama).
|
|
1582
|
+
* @param _toolChoice
|
|
1583
|
+
* @returns undefined as Ollama does not support tool choice.
|
|
1584
|
+
*/
|
|
1585
|
+
_formatToolChoice(_toolChoice) {
|
|
1586
|
+
return void 0;
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Format tool schemas for Ollama API (no special formatting needed).
|
|
1590
|
+
* @param tools
|
|
1591
|
+
* @returns The same array of tool schemas, or an empty array if undefined.
|
|
1592
|
+
*/
|
|
1593
|
+
_formatToolSchemas(tools) {
|
|
1594
|
+
return tools || [];
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
|
|
1598
|
+
// src/model/openai-model.ts
|
|
1599
|
+
import { OpenAI } from "openai";
|
|
1600
|
+
var OpenAIChatModel = class extends ChatModelBase {
|
|
1601
|
+
client;
|
|
1602
|
+
presetGenParams;
|
|
1603
|
+
/**
|
|
1604
|
+
* Initializes a new instance of the OpenAIChatModel class.
|
|
1605
|
+
* @param options
|
|
1606
|
+
* @param options.modelName
|
|
1607
|
+
* @param options.apiKey
|
|
1608
|
+
* @param options.stream
|
|
1609
|
+
* @param options.maxRetries
|
|
1610
|
+
* @param options.fallbackModelName
|
|
1611
|
+
* @param options.presetGenParams
|
|
1612
|
+
* @param options.baseURL
|
|
1613
|
+
* @param options.formatter
|
|
1614
|
+
*/
|
|
1615
|
+
constructor({
|
|
1616
|
+
modelName,
|
|
1617
|
+
apiKey,
|
|
1618
|
+
stream = true,
|
|
1619
|
+
maxRetries = 3,
|
|
1620
|
+
fallbackModelName,
|
|
1621
|
+
presetGenParams,
|
|
1622
|
+
baseURL,
|
|
1623
|
+
formatter
|
|
1624
|
+
}) {
|
|
1625
|
+
const defaultFormatter = formatter || new OpenAIChatFormatter();
|
|
1626
|
+
super({
|
|
1627
|
+
modelName,
|
|
1628
|
+
stream,
|
|
1629
|
+
maxRetries,
|
|
1630
|
+
fallbackModelName,
|
|
1631
|
+
formatter: defaultFormatter
|
|
1632
|
+
});
|
|
1633
|
+
this.client = new OpenAI({
|
|
1634
|
+
apiKey,
|
|
1635
|
+
baseURL
|
|
1636
|
+
});
|
|
1637
|
+
this.presetGenParams = presetGenParams;
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Calls the OpenAI API with the given parameters.
|
|
1641
|
+
*
|
|
1642
|
+
* @param modelName - The name of the model to use.
|
|
1643
|
+
* @param options - The chat model options.
|
|
1644
|
+
* @returns A promise that resolves to either a ChatResponse or an AsyncGenerator of ChatResponses.
|
|
1645
|
+
*/
|
|
1646
|
+
async _callAPI(modelName, options) {
|
|
1647
|
+
const startTime = Date.now();
|
|
1648
|
+
if (this.stream) {
|
|
1649
|
+
const stream = await this.client.chat.completions.create({
|
|
1650
|
+
model: modelName,
|
|
1651
|
+
messages: options.messages,
|
|
1652
|
+
tools: this._formatToolSchemas(options.tools),
|
|
1653
|
+
tool_choice: this._formatToolChoice(options.toolChoice),
|
|
1654
|
+
stream: true,
|
|
1655
|
+
...this.presetGenParams ?? {}
|
|
1656
|
+
});
|
|
1657
|
+
return this._parseOpenAIStreamedResponse(stream, startTime);
|
|
1658
|
+
}
|
|
1659
|
+
const response = await this.client.chat.completions.create({
|
|
1660
|
+
model: modelName,
|
|
1661
|
+
messages: options.messages,
|
|
1662
|
+
tools: options.tools,
|
|
1663
|
+
tool_choice: this._formatToolChoice(options.toolChoice),
|
|
1664
|
+
stream: false,
|
|
1665
|
+
...this.presetGenParams ?? {}
|
|
1666
|
+
});
|
|
1667
|
+
const choice = response.choices[0];
|
|
1668
|
+
const blocks = [];
|
|
1669
|
+
if (choice.message.content) {
|
|
1670
|
+
blocks.push({ id: crypto.randomUUID(), type: "text", text: choice.message.content });
|
|
1671
|
+
}
|
|
1672
|
+
if (choice.message.tool_calls && Array.isArray(choice.message.tool_calls)) {
|
|
1673
|
+
choice.message.tool_calls.forEach((toolCall) => {
|
|
1674
|
+
if (toolCall.type === "function") {
|
|
1675
|
+
blocks.push({
|
|
1676
|
+
type: "tool_call",
|
|
1677
|
+
id: toolCall.id,
|
|
1678
|
+
name: toolCall.function.name,
|
|
1679
|
+
input: toolCall.function.arguments
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
const usage = response.usage ? {
|
|
1685
|
+
type: "chat_usage",
|
|
1686
|
+
inputTokens: response.usage.prompt_tokens,
|
|
1687
|
+
outputTokens: response.usage.completion_tokens,
|
|
1688
|
+
time: (Date.now() - startTime) / 1e3
|
|
1689
|
+
} : void 0;
|
|
1690
|
+
return {
|
|
1691
|
+
type: "chat",
|
|
1692
|
+
id: response.id,
|
|
1693
|
+
createdAt: new Date(response.created * 1e3).toISOString(),
|
|
1694
|
+
content: blocks,
|
|
1695
|
+
usage
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1699
|
+
* Formats the tool choice for the API request.
|
|
1700
|
+
*
|
|
1701
|
+
* TODO: supports grouped tool choices.
|
|
1702
|
+
*
|
|
1703
|
+
* @param toolChoice - The tool choice option.
|
|
1704
|
+
* @returns The formatted tool choice.
|
|
1705
|
+
*/
|
|
1706
|
+
_formatToolChoice(toolChoice) {
|
|
1707
|
+
if (toolChoice) {
|
|
1708
|
+
if (toolChoice === "none" || toolChoice === "auto" || toolChoice === "required") {
|
|
1709
|
+
return toolChoice;
|
|
1710
|
+
}
|
|
1711
|
+
return {
|
|
1712
|
+
type: "function",
|
|
1713
|
+
function: {
|
|
1714
|
+
name: toolChoice
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
return "auto";
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Parses a streamed response from OpenAI API.
|
|
1722
|
+
* An async generator that yields delta ChatResponse objects as they are received.
|
|
1723
|
+
*
|
|
1724
|
+
* @param stream - The OpenAI stream object.
|
|
1725
|
+
* @param startTime - The start time of the request for usage calculation.
|
|
1726
|
+
* @returns An async generator yielding delta ChatResponse objects, and returns the complete ChatResponse.
|
|
1727
|
+
*/
|
|
1728
|
+
async *_parseOpenAIStreamedResponse(stream, startTime) {
|
|
1729
|
+
let accText = "";
|
|
1730
|
+
const accToolInputs = /* @__PURE__ */ new Map();
|
|
1731
|
+
const toolCallMeta = /* @__PURE__ */ new Map();
|
|
1732
|
+
let lastUsage = null;
|
|
1733
|
+
let responseId = "";
|
|
1734
|
+
let createdTimestamp = 0;
|
|
1735
|
+
for await (const chunk of stream) {
|
|
1736
|
+
if (!responseId && chunk.id) {
|
|
1737
|
+
responseId = chunk.id;
|
|
1738
|
+
}
|
|
1739
|
+
if (!createdTimestamp && chunk.created) {
|
|
1740
|
+
createdTimestamp = chunk.created;
|
|
1741
|
+
}
|
|
1742
|
+
if (chunk.choices && chunk.choices.length > 0) {
|
|
1743
|
+
const choice = chunk.choices[0];
|
|
1744
|
+
let deltaText = "";
|
|
1745
|
+
const deltaToolCalls = /* @__PURE__ */ new Map();
|
|
1746
|
+
if (choice.delta?.content) {
|
|
1747
|
+
deltaText = choice.delta.content;
|
|
1748
|
+
accText += deltaText;
|
|
1749
|
+
}
|
|
1750
|
+
if (choice.delta?.tool_calls) {
|
|
1751
|
+
choice.delta.tool_calls.forEach((toolCall) => {
|
|
1752
|
+
const index = toolCall.index.toString();
|
|
1753
|
+
if (!toolCallMeta.has(index)) {
|
|
1754
|
+
toolCallMeta.set(index, { id: "", name: "" });
|
|
1755
|
+
}
|
|
1756
|
+
if (!accToolInputs.has(index)) {
|
|
1757
|
+
accToolInputs.set(index, "");
|
|
1758
|
+
}
|
|
1759
|
+
if (toolCall.id) {
|
|
1760
|
+
toolCallMeta.get(index).id = toolCall.id;
|
|
1761
|
+
}
|
|
1762
|
+
if (toolCall.function?.name) {
|
|
1763
|
+
toolCallMeta.get(index).name = toolCall.function.name;
|
|
1764
|
+
}
|
|
1765
|
+
if (toolCall.function?.arguments) {
|
|
1766
|
+
const deltaArgs = toolCall.function.arguments;
|
|
1767
|
+
accToolInputs.set(index, accToolInputs.get(index) + deltaArgs);
|
|
1768
|
+
const meta = toolCallMeta.get(index);
|
|
1769
|
+
deltaToolCalls.set(index, {
|
|
1770
|
+
type: "tool_call",
|
|
1771
|
+
id: meta.id,
|
|
1772
|
+
name: meta.name,
|
|
1773
|
+
input: deltaArgs
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
const deltaBlocks = this._accDataToBlocks(deltaText, deltaToolCalls);
|
|
1779
|
+
yield {
|
|
1780
|
+
type: "chat",
|
|
1781
|
+
id: responseId || crypto.randomUUID(),
|
|
1782
|
+
createdAt: createdTimestamp ? new Date(createdTimestamp * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
|
|
1783
|
+
content: deltaBlocks,
|
|
1784
|
+
usage: lastUsage
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
if (chunk.usage) {
|
|
1788
|
+
lastUsage = {
|
|
1789
|
+
type: "chat_usage",
|
|
1790
|
+
inputTokens: chunk.usage.prompt_tokens || 0,
|
|
1791
|
+
outputTokens: chunk.usage.completion_tokens || 0,
|
|
1792
|
+
time: (Date.now() - startTime) / 1e3
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
const finalToolCalls = /* @__PURE__ */ new Map();
|
|
1797
|
+
toolCallMeta.forEach((meta, index) => {
|
|
1798
|
+
finalToolCalls.set(index, {
|
|
1799
|
+
type: "tool_call",
|
|
1800
|
+
id: meta.id,
|
|
1801
|
+
name: meta.name,
|
|
1802
|
+
input: accToolInputs.get(index) || "{}"
|
|
1803
|
+
});
|
|
1804
|
+
});
|
|
1805
|
+
const blocks = this._accDataToBlocks(accText, finalToolCalls);
|
|
1806
|
+
return {
|
|
1807
|
+
type: "chat",
|
|
1808
|
+
id: responseId || crypto.randomUUID(),
|
|
1809
|
+
createdAt: createdTimestamp ? new Date(createdTimestamp * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
|
|
1810
|
+
content: blocks,
|
|
1811
|
+
usage: lastUsage
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Convert data into blocks
|
|
1816
|
+
*
|
|
1817
|
+
* @param text - The text response from the llm API
|
|
1818
|
+
* @param toolCalls - The tool calls
|
|
1819
|
+
* @returns An array of blocks
|
|
1820
|
+
*/
|
|
1821
|
+
_accDataToBlocks(text, toolCalls) {
|
|
1822
|
+
const blocks = [];
|
|
1823
|
+
if (text) {
|
|
1824
|
+
blocks.push({ id: crypto.randomUUID(), type: "text", text });
|
|
1825
|
+
}
|
|
1826
|
+
if (toolCalls.size > 0) {
|
|
1827
|
+
toolCalls.forEach((value) => {
|
|
1828
|
+
blocks.push(value);
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
return blocks;
|
|
1832
|
+
}
|
|
1833
|
+
/**
|
|
1834
|
+
* Format the tool schemas to the expected API format.
|
|
1835
|
+
* @param tools
|
|
1836
|
+
* @returns The formatted tool schemas.
|
|
1837
|
+
*/
|
|
1838
|
+
_formatToolSchemas(tools) {
|
|
1839
|
+
return tools || [];
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
export {
|
|
1843
|
+
ChatModelBase,
|
|
1844
|
+
DashScopeChatModel,
|
|
1845
|
+
DeepSeekChatModel,
|
|
1846
|
+
OllamaChatModel,
|
|
1847
|
+
OpenAIChatModel
|
|
1848
|
+
};
|
|
1849
|
+
//# sourceMappingURL=index.mjs.map
|