@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,279 @@
|
|
|
1
|
+
import { DeepSeekChatModel } from './deepseek-model';
|
|
2
|
+
import { ChatResponse } from './response';
|
|
3
|
+
import { createMsg } from '../message';
|
|
4
|
+
|
|
5
|
+
// Mock fetch for streaming responses
|
|
6
|
+
global.fetch = jest.fn();
|
|
7
|
+
|
|
8
|
+
describe('DeepSeekChatModel', () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
jest.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('Test stream generation with delta output', async () => {
|
|
14
|
+
// Mock streaming response with multiple chunks (SSE format)
|
|
15
|
+
// DeepSeek uses OpenAI-compatible API format
|
|
16
|
+
const mockStreamChunks = [
|
|
17
|
+
'data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1234567890,"model":"deepseek-reasoner","choices":[{"index":0,"delta":{"reasoning_content":"Analyzing the"},"finish_reason":null}]}\n\n',
|
|
18
|
+
'data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1234567890,"model":"deepseek-reasoner","choices":[{"index":0,"delta":{"reasoning_content":" weather query"},"finish_reason":null}]}\n\n',
|
|
19
|
+
'data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1234567890,"model":"deepseek-reasoner","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call-123","type":"function","function":{"name":"get_current_weather","arguments":"{\\"location\\""}}]},"finish_reason":null}]}\n\n',
|
|
20
|
+
'data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1234567890,"model":"deepseek-reasoner","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":":\\"Beijing\\"}"}}]},"finish_reason":null}]}\n\n',
|
|
21
|
+
'data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1234567890,"model":"deepseek-reasoner","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":100,"completion_tokens":50,"total_tokens":150}}\n\n',
|
|
22
|
+
'data: [DONE]\n\n',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const mockReadableStream = new ReadableStream({
|
|
26
|
+
start(controller) {
|
|
27
|
+
mockStreamChunks.forEach(chunk =>
|
|
28
|
+
controller.enqueue(new TextEncoder().encode(chunk))
|
|
29
|
+
);
|
|
30
|
+
controller.close();
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
(global.fetch as jest.Mock).mockResolvedValue({
|
|
35
|
+
ok: true,
|
|
36
|
+
body: mockReadableStream,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const model = new DeepSeekChatModel({
|
|
40
|
+
modelName: 'deepseek-reasoner',
|
|
41
|
+
apiKey: 'test-api-key',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const res = await model.call({
|
|
45
|
+
messages: [
|
|
46
|
+
createMsg({
|
|
47
|
+
name: 'user',
|
|
48
|
+
role: 'user',
|
|
49
|
+
content: [{ id: crypto.randomUUID(), type: 'text', text: '查询北京天气' }],
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
52
|
+
tools: [
|
|
53
|
+
{
|
|
54
|
+
type: 'function',
|
|
55
|
+
function: {
|
|
56
|
+
name: 'get_current_weather',
|
|
57
|
+
description: 'Get the current weather in a given location',
|
|
58
|
+
parameters: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: {
|
|
61
|
+
location: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
description: 'The city and state, e.g. San Francisco, CA',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
required: ['location'],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const generator = res as AsyncGenerator<ChatResponse, ChatResponse>;
|
|
74
|
+
let completeResponse: ChatResponse | undefined;
|
|
75
|
+
const yieldedChunks: ChatResponse[] = [];
|
|
76
|
+
|
|
77
|
+
// Manual iteration to capture both yielded and returned values
|
|
78
|
+
while (true) {
|
|
79
|
+
const result = await generator.next();
|
|
80
|
+
if (result.done) {
|
|
81
|
+
completeResponse = result.value;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
yieldedChunks.push(result.value);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Verify we received multiple yielded chunks
|
|
88
|
+
expect(yieldedChunks.length).toBeGreaterThan(0);
|
|
89
|
+
|
|
90
|
+
// Verify the final complete response has correct structure
|
|
91
|
+
expect(completeResponse.content.length).toBe(2);
|
|
92
|
+
|
|
93
|
+
// Check thinking block - should be complete after accumulation
|
|
94
|
+
const thinkingBlock = completeResponse.content.find(b => b.type === 'thinking');
|
|
95
|
+
expect(thinkingBlock).toBeDefined();
|
|
96
|
+
expect(thinkingBlock).toMatchObject({
|
|
97
|
+
type: 'thinking',
|
|
98
|
+
thinking: 'Analyzing the weather query',
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Check tool_call block - input should be complete after accumulation
|
|
102
|
+
const toolCallBlock = completeResponse.content.find(b => b.type === 'tool_call');
|
|
103
|
+
expect(toolCallBlock).toBeDefined();
|
|
104
|
+
expect(toolCallBlock).toMatchObject({
|
|
105
|
+
type: 'tool_call',
|
|
106
|
+
name: 'get_current_weather',
|
|
107
|
+
id: 'call-123',
|
|
108
|
+
input: '{"location":"Beijing"}',
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Verify usage
|
|
112
|
+
expect(completeResponse.usage).toBeDefined();
|
|
113
|
+
expect(completeResponse.usage?.inputTokens).toBe(100);
|
|
114
|
+
expect(completeResponse.usage?.outputTokens).toBe(50);
|
|
115
|
+
}, 10000);
|
|
116
|
+
|
|
117
|
+
test('Test non-streaming generation', async () => {
|
|
118
|
+
// Mock non-streaming response
|
|
119
|
+
const mockResponse = {
|
|
120
|
+
id: 'chatcmpl-123',
|
|
121
|
+
object: 'chat.completion',
|
|
122
|
+
created: 1234567890,
|
|
123
|
+
model: 'deepseek-reasoner',
|
|
124
|
+
choices: [
|
|
125
|
+
{
|
|
126
|
+
index: 0,
|
|
127
|
+
message: {
|
|
128
|
+
role: 'assistant',
|
|
129
|
+
reasoning_content: 'Analyzing the weather query',
|
|
130
|
+
tool_calls: [
|
|
131
|
+
{
|
|
132
|
+
id: 'call-123',
|
|
133
|
+
type: 'function',
|
|
134
|
+
function: {
|
|
135
|
+
name: 'get_current_weather',
|
|
136
|
+
arguments: '{"location":"Beijing"}',
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
finish_reason: 'tool_calls',
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
usage: {
|
|
145
|
+
prompt_tokens: 100,
|
|
146
|
+
completion_tokens: 50,
|
|
147
|
+
total_tokens: 150,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
(global.fetch as jest.Mock).mockResolvedValue({
|
|
152
|
+
ok: true,
|
|
153
|
+
json: async () => mockResponse,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const model = new DeepSeekChatModel({
|
|
157
|
+
modelName: 'deepseek-reasoner',
|
|
158
|
+
apiKey: 'test-api-key',
|
|
159
|
+
stream: false,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const res = await model.call({
|
|
163
|
+
messages: [
|
|
164
|
+
createMsg({
|
|
165
|
+
name: 'user',
|
|
166
|
+
role: 'user',
|
|
167
|
+
content: [{ id: crypto.randomUUID(), type: 'text', text: '查询北京天气' }],
|
|
168
|
+
}),
|
|
169
|
+
],
|
|
170
|
+
tools: [
|
|
171
|
+
{
|
|
172
|
+
type: 'function',
|
|
173
|
+
function: {
|
|
174
|
+
name: 'get_current_weather',
|
|
175
|
+
description: 'Get the current weather in a given location',
|
|
176
|
+
parameters: {
|
|
177
|
+
type: 'object',
|
|
178
|
+
properties: {
|
|
179
|
+
location: {
|
|
180
|
+
type: 'string',
|
|
181
|
+
description: 'The city and state, e.g. San Francisco, CA',
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
required: ['location'],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const completeResponse = res as ChatResponse;
|
|
192
|
+
|
|
193
|
+
// Verify complete response structure
|
|
194
|
+
expect(completeResponse.content.length).toBe(2);
|
|
195
|
+
|
|
196
|
+
// Check thinking block
|
|
197
|
+
const thinkingBlock = completeResponse.content.find(b => b.type === 'thinking');
|
|
198
|
+
expect(thinkingBlock).toBeDefined();
|
|
199
|
+
expect(thinkingBlock).toMatchObject({
|
|
200
|
+
type: 'thinking',
|
|
201
|
+
thinking: 'Analyzing the weather query',
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Check tool_call block
|
|
205
|
+
const toolCallBlock = completeResponse.content.find(b => b.type === 'tool_call');
|
|
206
|
+
expect(toolCallBlock).toBeDefined();
|
|
207
|
+
expect(toolCallBlock).toMatchObject({
|
|
208
|
+
type: 'tool_call',
|
|
209
|
+
name: 'get_current_weather',
|
|
210
|
+
id: 'call-123',
|
|
211
|
+
input: '{"location":"Beijing"}',
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Verify usage
|
|
215
|
+
expect(completeResponse.usage).toBeDefined();
|
|
216
|
+
expect(completeResponse.usage?.inputTokens).toBe(100);
|
|
217
|
+
expect(completeResponse.usage?.outputTokens).toBe(50);
|
|
218
|
+
}, 10000);
|
|
219
|
+
|
|
220
|
+
test('Test formatToolChoice function', () => {
|
|
221
|
+
const model = new DeepSeekChatModel({
|
|
222
|
+
modelName: 'deepseek-chat',
|
|
223
|
+
apiKey: 'test-api-key',
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Test 'auto' case
|
|
227
|
+
expect(model._formatToolChoice('auto')).toBe('auto');
|
|
228
|
+
|
|
229
|
+
// Test 'none' case
|
|
230
|
+
expect(model._formatToolChoice('none')).toBe('none');
|
|
231
|
+
|
|
232
|
+
// Test 'required' case
|
|
233
|
+
expect(model._formatToolChoice('required')).toBe('required');
|
|
234
|
+
|
|
235
|
+
// Test specific function name case
|
|
236
|
+
expect(model._formatToolChoice('get_current_weather')).toEqual({
|
|
237
|
+
type: 'function',
|
|
238
|
+
function: {
|
|
239
|
+
name: 'get_current_weather',
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Test undefined case (should default to 'auto')
|
|
244
|
+
expect(model._formatToolChoice(undefined)).toBe('auto');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('Test formatToolSchemas function', () => {
|
|
248
|
+
const model = new DeepSeekChatModel({
|
|
249
|
+
modelName: 'deepseek-chat',
|
|
250
|
+
apiKey: 'test-api-key',
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const toolSchemas = [
|
|
254
|
+
{
|
|
255
|
+
type: 'function' as const,
|
|
256
|
+
function: {
|
|
257
|
+
name: 'get_current_weather',
|
|
258
|
+
description: 'Get the current weather in a given location',
|
|
259
|
+
parameters: {
|
|
260
|
+
type: 'object' as const,
|
|
261
|
+
properties: {
|
|
262
|
+
location: {
|
|
263
|
+
type: 'string',
|
|
264
|
+
description: 'The city and state, e.g. San Francisco, CA',
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
required: ['location'],
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
// Test with tool schemas
|
|
274
|
+
expect(model._formatToolSchemas(toolSchemas)).toEqual(toolSchemas);
|
|
275
|
+
|
|
276
|
+
// Test with undefined (should return empty array)
|
|
277
|
+
expect(model._formatToolSchemas(undefined)).toEqual([]);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { ChatModelBase, ChatModelOptions, ChatModelRequestOptions } from './base';
|
|
2
|
+
import { ChatResponse } from './response';
|
|
3
|
+
import { DataBlock, TextBlock, ThinkingBlock, ToolCallBlock } from '../message';
|
|
4
|
+
import { ToolChoice, ToolSchema } from '../type';
|
|
5
|
+
import { ChatUsage } from './usage';
|
|
6
|
+
import { _parseStreamedResponse } from '../_utils';
|
|
7
|
+
import { DeepSeekChatFormatter } from '../formatter';
|
|
8
|
+
|
|
9
|
+
interface _DeepSeekStreamChunk {
|
|
10
|
+
choices?: {
|
|
11
|
+
delta?: {
|
|
12
|
+
content?: string;
|
|
13
|
+
reasoning_content?: string;
|
|
14
|
+
tool_calls?: {
|
|
15
|
+
index: number;
|
|
16
|
+
id?: string;
|
|
17
|
+
function?: {
|
|
18
|
+
name?: string;
|
|
19
|
+
arguments?: string;
|
|
20
|
+
};
|
|
21
|
+
}[];
|
|
22
|
+
};
|
|
23
|
+
finish_reason?: string | null;
|
|
24
|
+
}[];
|
|
25
|
+
usage?: {
|
|
26
|
+
prompt_tokens?: number;
|
|
27
|
+
completion_tokens?: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface DeepSeekThinkingConfig {
|
|
32
|
+
/**
|
|
33
|
+
* Whether to enable thinking or not.
|
|
34
|
+
*/
|
|
35
|
+
enableThinking: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface DeepSeekChatModelOptions extends ChatModelOptions {
|
|
39
|
+
/**
|
|
40
|
+
* The API key for authenticating with DeepSeek API.
|
|
41
|
+
*/
|
|
42
|
+
apiKey: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Thinking configuration for DeepSeek models.
|
|
46
|
+
*/
|
|
47
|
+
thinkingConfig?: DeepSeekThinkingConfig;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Preset generation parameters to include in each request.
|
|
51
|
+
* These parameters will be merged with the request-specific parameters.
|
|
52
|
+
*/
|
|
53
|
+
presetGenParams?: Record<string, unknown>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Preset headers that will be included in each request.
|
|
57
|
+
*/
|
|
58
|
+
presetHeaders?: Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The DeepSeek API chat model.
|
|
63
|
+
*/
|
|
64
|
+
export class DeepSeekChatModel extends ChatModelBase {
|
|
65
|
+
apiURL: string;
|
|
66
|
+
protected apiKey: string;
|
|
67
|
+
protected presetGenParams: Record<string, unknown> | undefined;
|
|
68
|
+
protected presetHeaders: Record<string, unknown> | undefined;
|
|
69
|
+
protected thinkingConfig: DeepSeekThinkingConfig;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Initializes a new instance of the DeepSeekChatModel class.
|
|
73
|
+
*
|
|
74
|
+
* @param options - The DeepSeek chat model options.
|
|
75
|
+
* @param options.modelName - The name of the model to use.
|
|
76
|
+
* @param options.apiKey - The API key for authentication.
|
|
77
|
+
* @param options.stream - Whether to use streaming responses. Default is true.
|
|
78
|
+
* @param options.thinkingConfig - Thinking configuration.
|
|
79
|
+
* @param options.maxRetries - The maximum number of retries for failed requests. Default is 0.
|
|
80
|
+
* @param options.fallbackModelName - The fallback model name to use if the primary model fails.
|
|
81
|
+
* @param options.presetGenParams - Preset generation parameters to include in each request.
|
|
82
|
+
* @param options.presetHeaders - Preset headers that will be included in each request.
|
|
83
|
+
* @param options.formatter
|
|
84
|
+
*/
|
|
85
|
+
constructor({
|
|
86
|
+
modelName,
|
|
87
|
+
apiKey,
|
|
88
|
+
stream = true,
|
|
89
|
+
thinkingConfig,
|
|
90
|
+
maxRetries = 0,
|
|
91
|
+
fallbackModelName,
|
|
92
|
+
presetGenParams,
|
|
93
|
+
presetHeaders,
|
|
94
|
+
formatter,
|
|
95
|
+
}: DeepSeekChatModelOptions) {
|
|
96
|
+
// If no formatter is provided, create a default DeepSeekChatFormatter
|
|
97
|
+
const defaultFormatter = formatter || new DeepSeekChatFormatter();
|
|
98
|
+
super({
|
|
99
|
+
modelName,
|
|
100
|
+
stream,
|
|
101
|
+
maxRetries,
|
|
102
|
+
fallbackModelName,
|
|
103
|
+
formatter: defaultFormatter,
|
|
104
|
+
} as ChatModelOptions);
|
|
105
|
+
|
|
106
|
+
this.apiKey = apiKey;
|
|
107
|
+
this.thinkingConfig = thinkingConfig || { enableThinking: false };
|
|
108
|
+
this.presetGenParams = presetGenParams;
|
|
109
|
+
this.presetHeaders = presetHeaders;
|
|
110
|
+
this.apiURL = 'https://api.deepseek.com/chat/completions';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Calls the DeepSeek API with the given parameters.
|
|
115
|
+
*
|
|
116
|
+
* @param modelName - The name of the model to use.
|
|
117
|
+
* @param options - The chat model options.
|
|
118
|
+
* @returns A promise that resolves to either a ChatResponse or an AsyncGenerator of ChatResponses.
|
|
119
|
+
*/
|
|
120
|
+
async _callAPI(
|
|
121
|
+
modelName: string,
|
|
122
|
+
options: ChatModelRequestOptions<Record<string, unknown>>
|
|
123
|
+
): Promise<ChatResponse | AsyncGenerator<ChatResponse, ChatResponse>> {
|
|
124
|
+
// Set up request data
|
|
125
|
+
const data = {
|
|
126
|
+
model: modelName,
|
|
127
|
+
messages: options.messages,
|
|
128
|
+
tools: this._formatToolSchemas(options.tools),
|
|
129
|
+
tool_choice: this._formatToolChoice(options.toolChoice),
|
|
130
|
+
thinking: this.thinkingConfig.enableThinking
|
|
131
|
+
? { type: 'enabled' }
|
|
132
|
+
: { type: 'disabled' },
|
|
133
|
+
stream: this.stream,
|
|
134
|
+
...(this.presetGenParams ?? {}),
|
|
135
|
+
} as Record<string, unknown>;
|
|
136
|
+
|
|
137
|
+
// Set up headers
|
|
138
|
+
const headers: Record<string, unknown> = {
|
|
139
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
140
|
+
'Content-Type': 'application/json',
|
|
141
|
+
...this.presetHeaders,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Counting the time cost
|
|
145
|
+
const startTime = Date.now();
|
|
146
|
+
const response = await fetch(this.apiURL, {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
headers: headers as HeadersInit,
|
|
149
|
+
body: JSON.stringify(data),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`DeepSeek API request failed with status ${response.status}: ${await response.text()}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (this.stream) {
|
|
159
|
+
// Handle the streaming response
|
|
160
|
+
return this._parseDeepSeekStreamedResponse(response, startTime);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Handle the non-streaming response
|
|
164
|
+
const blocks: Array<TextBlock | ToolCallBlock | ThinkingBlock | DataBlock> = [];
|
|
165
|
+
const res = await response.json();
|
|
166
|
+
const choice = res.choices[0];
|
|
167
|
+
|
|
168
|
+
if (choice.message.reasoning_content) {
|
|
169
|
+
blocks.push({
|
|
170
|
+
id: crypto.randomUUID(),
|
|
171
|
+
type: 'thinking',
|
|
172
|
+
thinking: choice.message.reasoning_content,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (choice.message.content) {
|
|
176
|
+
blocks.push({ id: crypto.randomUUID(), type: 'text', text: choice.message.content });
|
|
177
|
+
}
|
|
178
|
+
if (choice.message.tool_calls && Array.isArray(choice.message.tool_calls)) {
|
|
179
|
+
choice.message.tool_calls.forEach((toolCall: object) => {
|
|
180
|
+
if (
|
|
181
|
+
'id' in toolCall &&
|
|
182
|
+
'function' in toolCall &&
|
|
183
|
+
typeof toolCall.function === 'object' &&
|
|
184
|
+
toolCall.function &&
|
|
185
|
+
'name' in toolCall.function &&
|
|
186
|
+
'arguments' in toolCall.function
|
|
187
|
+
) {
|
|
188
|
+
const inputString = String(toolCall.function.arguments);
|
|
189
|
+
blocks.push({
|
|
190
|
+
type: 'tool_call',
|
|
191
|
+
id: String(toolCall.id),
|
|
192
|
+
name: String(toolCall.function.name),
|
|
193
|
+
input: inputString,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const usage = res.usage
|
|
200
|
+
? {
|
|
201
|
+
type: 'chat_usage',
|
|
202
|
+
inputTokens: res.usage.prompt_tokens || 0,
|
|
203
|
+
outputTokens: res.usage.completion_tokens || 0,
|
|
204
|
+
time: (Date.now() - startTime) / 1000,
|
|
205
|
+
}
|
|
206
|
+
: undefined;
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
type: 'chat',
|
|
210
|
+
id: crypto.randomUUID(),
|
|
211
|
+
createdAt: new Date().toISOString(),
|
|
212
|
+
content: blocks,
|
|
213
|
+
usage,
|
|
214
|
+
} as ChatResponse;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* The method to format the tool choice parameter.
|
|
219
|
+
*
|
|
220
|
+
* @param toolChoice - The tool choice option.
|
|
221
|
+
* @returns The formatted tool choice.
|
|
222
|
+
*/
|
|
223
|
+
_formatToolChoice(
|
|
224
|
+
toolChoice?: ToolChoice
|
|
225
|
+
): 'auto' | 'none' | 'required' | Record<string, unknown> {
|
|
226
|
+
if (toolChoice) {
|
|
227
|
+
if (toolChoice === 'auto') return 'auto';
|
|
228
|
+
if (toolChoice === 'none') return 'none';
|
|
229
|
+
if (this.thinkingConfig?.enableThinking) {
|
|
230
|
+
console.log(
|
|
231
|
+
`The deepseek reasoning model does not support tool choice options '${toolChoice}'. 'auto' will be used instead.`
|
|
232
|
+
);
|
|
233
|
+
return 'auto';
|
|
234
|
+
}
|
|
235
|
+
if (toolChoice === 'required') return 'required';
|
|
236
|
+
return {
|
|
237
|
+
type: 'function',
|
|
238
|
+
function: {
|
|
239
|
+
name: toolChoice,
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return 'auto';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Parses a streamed response from DeepSeek API specifically for chat responses.
|
|
248
|
+
* An async generator that yields delta ChatResponse objects as they are received.
|
|
249
|
+
*
|
|
250
|
+
* @param response - The fetch response object.
|
|
251
|
+
* @param startTime - The start time of the request for usage calculation.
|
|
252
|
+
* @returns An async generator yielding delta ChatResponse objects, and returns the complete ChatResponse.
|
|
253
|
+
*/
|
|
254
|
+
async *_parseDeepSeekStreamedResponse(
|
|
255
|
+
response: Response,
|
|
256
|
+
startTime: number
|
|
257
|
+
): AsyncGenerator<ChatResponse, ChatResponse> {
|
|
258
|
+
const asyncGenerator = _parseStreamedResponse<_DeepSeekStreamChunk>(response);
|
|
259
|
+
|
|
260
|
+
let accText: string = '';
|
|
261
|
+
let accThinking: string = '';
|
|
262
|
+
// Store accumulated input strings for each tool call
|
|
263
|
+
const accToolInputs: Map<string, string> = new Map();
|
|
264
|
+
// Store tool call metadata (id, name)
|
|
265
|
+
const toolCallMeta: Map<string, { id: string; name: string }> = new Map();
|
|
266
|
+
let lastUsage: ChatUsage | undefined = undefined;
|
|
267
|
+
|
|
268
|
+
for await (const jsonObj of asyncGenerator) {
|
|
269
|
+
if (jsonObj.choices && jsonObj.choices.length > 0) {
|
|
270
|
+
const choice = jsonObj.choices[0];
|
|
271
|
+
|
|
272
|
+
// Delta data for this chunk
|
|
273
|
+
let deltaText: string = '';
|
|
274
|
+
let deltaThinking: string = '';
|
|
275
|
+
const deltaToolCalls: Map<string, ToolCallBlock> = new Map();
|
|
276
|
+
|
|
277
|
+
if (choice.delta?.content) {
|
|
278
|
+
deltaText = choice.delta.content;
|
|
279
|
+
accText += deltaText;
|
|
280
|
+
}
|
|
281
|
+
if (choice.delta?.reasoning_content) {
|
|
282
|
+
deltaThinking = choice.delta.reasoning_content;
|
|
283
|
+
accThinking += deltaThinking;
|
|
284
|
+
}
|
|
285
|
+
if (choice.delta?.tool_calls) {
|
|
286
|
+
choice.delta.tool_calls.forEach(toolCall => {
|
|
287
|
+
const index = toolCall.index.toString();
|
|
288
|
+
|
|
289
|
+
// Initialize metadata if not exists
|
|
290
|
+
if (!toolCallMeta.has(index)) {
|
|
291
|
+
toolCallMeta.set(index, { id: '', name: '' });
|
|
292
|
+
}
|
|
293
|
+
if (!accToolInputs.has(index)) {
|
|
294
|
+
accToolInputs.set(index, '');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Update the tool use id
|
|
298
|
+
if (toolCall.id) {
|
|
299
|
+
toolCallMeta.get(index)!.id = toolCall.id;
|
|
300
|
+
}
|
|
301
|
+
// Update the tool use name
|
|
302
|
+
if (toolCall.function?.name) {
|
|
303
|
+
toolCallMeta.get(index)!.name = toolCall.function.name;
|
|
304
|
+
}
|
|
305
|
+
// Update the tool use input
|
|
306
|
+
if (toolCall.function?.arguments) {
|
|
307
|
+
const deltaArgs = toolCall.function.arguments;
|
|
308
|
+
accToolInputs.set(index, accToolInputs.get(index)! + deltaArgs);
|
|
309
|
+
|
|
310
|
+
// Create delta tool call with incremental input
|
|
311
|
+
const meta = toolCallMeta.get(index)!;
|
|
312
|
+
deltaToolCalls.set(index, {
|
|
313
|
+
type: 'tool_call',
|
|
314
|
+
id: meta.id,
|
|
315
|
+
name: meta.name,
|
|
316
|
+
input: deltaArgs,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Create a delta ChatResponse object
|
|
323
|
+
const deltaBlocks = this._accDataToBlocks(deltaText, deltaThinking, deltaToolCalls);
|
|
324
|
+
lastUsage = jsonObj.usage
|
|
325
|
+
? {
|
|
326
|
+
type: 'chat_usage',
|
|
327
|
+
inputTokens: jsonObj.usage.prompt_tokens || 0,
|
|
328
|
+
outputTokens: jsonObj.usage.completion_tokens || 0,
|
|
329
|
+
time: (Date.now() - startTime) / 1000,
|
|
330
|
+
}
|
|
331
|
+
: undefined;
|
|
332
|
+
|
|
333
|
+
yield {
|
|
334
|
+
type: 'chat',
|
|
335
|
+
id: crypto.randomUUID(),
|
|
336
|
+
createdAt: new Date().toISOString(),
|
|
337
|
+
content: deltaBlocks,
|
|
338
|
+
usage: lastUsage,
|
|
339
|
+
} as ChatResponse;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Build final tool calls with complete JSON strings
|
|
343
|
+
const finalToolCalls: Map<string, ToolCallBlock> = new Map();
|
|
344
|
+
toolCallMeta.forEach((meta, index) => {
|
|
345
|
+
finalToolCalls.set(index, {
|
|
346
|
+
type: 'tool_call',
|
|
347
|
+
id: meta.id,
|
|
348
|
+
name: meta.name,
|
|
349
|
+
input: accToolInputs.get(index) || '{}',
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const blocks = this._accDataToBlocks(accText, accThinking, finalToolCalls);
|
|
354
|
+
return {
|
|
355
|
+
type: 'chat',
|
|
356
|
+
id: crypto.randomUUID(),
|
|
357
|
+
createdAt: new Date().toISOString(),
|
|
358
|
+
content: blocks,
|
|
359
|
+
usage: lastUsage,
|
|
360
|
+
} as ChatResponse;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Convert data into blocks
|
|
365
|
+
*
|
|
366
|
+
* @param text - The text response from the llm API
|
|
367
|
+
* @param thinking - The thinking response
|
|
368
|
+
* @param toolCalls - The tool calls
|
|
369
|
+
* @returns An array of blocks
|
|
370
|
+
*/
|
|
371
|
+
_accDataToBlocks(
|
|
372
|
+
text: string,
|
|
373
|
+
thinking: string,
|
|
374
|
+
toolCalls: Map<string, ToolCallBlock>
|
|
375
|
+
): (TextBlock | ThinkingBlock | ToolCallBlock)[] {
|
|
376
|
+
const blocks: (TextBlock | ThinkingBlock | ToolCallBlock)[] = [];
|
|
377
|
+
if (thinking) {
|
|
378
|
+
blocks.push({ id: crypto.randomUUID(), type: 'thinking', thinking: thinking });
|
|
379
|
+
}
|
|
380
|
+
if (text) {
|
|
381
|
+
blocks.push({ id: crypto.randomUUID(), type: 'text', text: text });
|
|
382
|
+
}
|
|
383
|
+
// Push the tool calls into the blocks
|
|
384
|
+
if (toolCalls.size > 0) {
|
|
385
|
+
toolCalls.forEach(value => {
|
|
386
|
+
blocks.push(value);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return blocks;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Format the tool schemas to the expected API format for DeepSeek API.
|
|
395
|
+
* @param tools
|
|
396
|
+
* @returns The formatted tool schemas.
|
|
397
|
+
*/
|
|
398
|
+
_formatToolSchemas(tools: ToolSchema[] | undefined): ToolSchema[] {
|
|
399
|
+
return tools || [];
|
|
400
|
+
}
|
|
401
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ChatModelBase } from './base';
|
|
2
|
+
export { ChatResponse } from './response';
|
|
3
|
+
export { ChatUsage } from './usage';
|
|
4
|
+
export { DashScopeChatModel } from './dashscope-model';
|
|
5
|
+
export { DeepSeekChatModel } from './deepseek-model';
|
|
6
|
+
export { OllamaChatModel } from './ollama-model';
|
|
7
|
+
export { OpenAIChatModel } from './openai-model';
|