@librechat/agents 3.1.28 → 3.1.30
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/cjs/common/enum.cjs +1 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +277 -1
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/providers.cjs +14 -13
- package/dist/cjs/llm/providers.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +1 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +277 -2
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/providers.mjs +2 -1
- package/dist/esm/llm/providers.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +2 -1
- package/dist/types/llm/openai/index.d.ts +13 -0
- package/dist/types/types/llm.d.ts +3 -1
- package/package.json +4 -3
- package/src/common/enum.ts +1 -0
- package/src/llm/openai/index.ts +347 -1
- package/src/llm/providers.ts +2 -0
- package/src/specs/deepseek.simple.test.ts +283 -0
- package/src/specs/moonshot.simple.test.ts +358 -0
- package/src/types/llm.ts +3 -0
- package/src/utils/llmConfig.ts +10 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
import { config } from 'dotenv';
|
|
4
|
+
config();
|
|
5
|
+
import { Calculator } from '@/tools/Calculator';
|
|
6
|
+
import {
|
|
7
|
+
HumanMessage,
|
|
8
|
+
BaseMessage,
|
|
9
|
+
UsageMetadata,
|
|
10
|
+
} from '@langchain/core/messages';
|
|
11
|
+
import type * as t from '@/types';
|
|
12
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
13
|
+
import { ContentTypes, GraphEvents, Providers } from '@/common';
|
|
14
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
15
|
+
import { capitalizeFirstLetter } from './spec.utils';
|
|
16
|
+
import { getLLMConfig } from '@/utils/llmConfig';
|
|
17
|
+
import { Run } from '@/run';
|
|
18
|
+
|
|
19
|
+
const provider = Providers.DEEPSEEK;
|
|
20
|
+
const llmConfig = getLLMConfig(provider);
|
|
21
|
+
|
|
22
|
+
const skipTests = process.env.DEEPSEEK_API_KEY == null;
|
|
23
|
+
|
|
24
|
+
(skipTests ? describe.skip : describe)(
|
|
25
|
+
`${capitalizeFirstLetter(provider)} Streaming Tests`,
|
|
26
|
+
() => {
|
|
27
|
+
jest.setTimeout(120000);
|
|
28
|
+
let run: Run<t.IState>;
|
|
29
|
+
let collectedUsage: UsageMetadata[];
|
|
30
|
+
let conversationHistory: BaseMessage[];
|
|
31
|
+
let aggregateContent: t.ContentAggregator;
|
|
32
|
+
let _contentParts: t.MessageContentComplex[];
|
|
33
|
+
|
|
34
|
+
const testConfig = {
|
|
35
|
+
configurable: {
|
|
36
|
+
thread_id: 'deepseek-test-1',
|
|
37
|
+
},
|
|
38
|
+
streamMode: 'values',
|
|
39
|
+
version: 'v2' as const,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
beforeEach(async () => {
|
|
43
|
+
conversationHistory = [];
|
|
44
|
+
collectedUsage = [];
|
|
45
|
+
const { contentParts: cp, aggregateContent: ac } =
|
|
46
|
+
createContentAggregator();
|
|
47
|
+
_contentParts = cp as t.MessageContentComplex[];
|
|
48
|
+
aggregateContent = ac;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const onMessageDeltaSpy = jest.fn();
|
|
52
|
+
const onReasoningDeltaSpy = jest.fn();
|
|
53
|
+
const onRunStepSpy = jest.fn();
|
|
54
|
+
|
|
55
|
+
afterAll(() => {
|
|
56
|
+
onMessageDeltaSpy.mockReset();
|
|
57
|
+
onReasoningDeltaSpy.mockReset();
|
|
58
|
+
onRunStepSpy.mockReset();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const setupCustomHandlers = (): Record<
|
|
62
|
+
string | GraphEvents,
|
|
63
|
+
t.EventHandler
|
|
64
|
+
> => ({
|
|
65
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
66
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
|
|
67
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
68
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
69
|
+
handle: (
|
|
70
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
71
|
+
data: t.StreamEventData
|
|
72
|
+
): void => {
|
|
73
|
+
aggregateContent({
|
|
74
|
+
event,
|
|
75
|
+
data: data as unknown as { result: t.ToolEndEvent },
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
80
|
+
handle: (
|
|
81
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
82
|
+
data: t.StreamEventData,
|
|
83
|
+
metadata,
|
|
84
|
+
graph
|
|
85
|
+
): void => {
|
|
86
|
+
onRunStepSpy(event, data, metadata, graph);
|
|
87
|
+
aggregateContent({ event, data: data as t.RunStep });
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
91
|
+
handle: (
|
|
92
|
+
event: GraphEvents.ON_RUN_STEP_DELTA,
|
|
93
|
+
data: t.StreamEventData
|
|
94
|
+
): void => {
|
|
95
|
+
aggregateContent({ event, data: data as t.RunStepDeltaEvent });
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
99
|
+
handle: (
|
|
100
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
101
|
+
data: t.StreamEventData,
|
|
102
|
+
metadata,
|
|
103
|
+
graph
|
|
104
|
+
): void => {
|
|
105
|
+
onMessageDeltaSpy(event, data, metadata, graph);
|
|
106
|
+
aggregateContent({ event, data: data as t.MessageDeltaEvent });
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
[GraphEvents.ON_REASONING_DELTA]: {
|
|
110
|
+
handle: (
|
|
111
|
+
event: GraphEvents.ON_REASONING_DELTA,
|
|
112
|
+
data: t.StreamEventData
|
|
113
|
+
): void => {
|
|
114
|
+
onReasoningDeltaSpy(event, data);
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
[GraphEvents.TOOL_START]: {
|
|
118
|
+
handle: (
|
|
119
|
+
_event: string,
|
|
120
|
+
_data: t.StreamEventData,
|
|
121
|
+
_metadata?: Record<string, unknown>
|
|
122
|
+
): void => {
|
|
123
|
+
// Handle tool start
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test(`${capitalizeFirstLetter(provider)}: should handle tool calls with reasoning_content preservation (streaming)`, async () => {
|
|
129
|
+
const customHandlers = setupCustomHandlers();
|
|
130
|
+
|
|
131
|
+
run = await Run.create<t.IState>({
|
|
132
|
+
runId: 'deepseek-tool-test',
|
|
133
|
+
graphConfig: {
|
|
134
|
+
type: 'standard',
|
|
135
|
+
llmConfig,
|
|
136
|
+
tools: [new Calculator()],
|
|
137
|
+
instructions:
|
|
138
|
+
'You are a helpful math assistant. Use the calculator tool to solve math problems.',
|
|
139
|
+
},
|
|
140
|
+
returnContent: true,
|
|
141
|
+
customHandlers,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const userMessage = 'What is 127 * 453?';
|
|
145
|
+
conversationHistory.push(new HumanMessage(userMessage));
|
|
146
|
+
|
|
147
|
+
const inputs = {
|
|
148
|
+
messages: conversationHistory,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
console.log('Starting DeepSeek streaming tool call test...');
|
|
152
|
+
const finalContentParts = await run.processStream(inputs, testConfig);
|
|
153
|
+
|
|
154
|
+
expect(finalContentParts).toBeDefined();
|
|
155
|
+
console.log('Final content parts:', finalContentParts);
|
|
156
|
+
|
|
157
|
+
const finalMessages = run.getRunMessages();
|
|
158
|
+
expect(finalMessages).toBeDefined();
|
|
159
|
+
expect(finalMessages?.length).toBeGreaterThan(0);
|
|
160
|
+
|
|
161
|
+
const hasToolCall = finalMessages?.some(
|
|
162
|
+
(msg) =>
|
|
163
|
+
msg.getType() === 'ai' &&
|
|
164
|
+
Array.isArray((msg as any).tool_calls) &&
|
|
165
|
+
(msg as any).tool_calls.length > 0
|
|
166
|
+
);
|
|
167
|
+
expect(hasToolCall).toBe(true);
|
|
168
|
+
|
|
169
|
+
const hasToolResult = finalMessages?.some(
|
|
170
|
+
(msg) => msg.getType() === 'tool'
|
|
171
|
+
);
|
|
172
|
+
expect(hasToolResult).toBe(true);
|
|
173
|
+
|
|
174
|
+
console.log(
|
|
175
|
+
'Streaming tool call test passed - reasoning_content was preserved'
|
|
176
|
+
);
|
|
177
|
+
console.log(
|
|
178
|
+
'Final response:',
|
|
179
|
+
finalMessages?.[finalMessages.length - 1]?.content
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test(`${capitalizeFirstLetter(provider)}: should handle tool calls with disableStreaming`, async () => {
|
|
184
|
+
const customHandlers = setupCustomHandlers();
|
|
185
|
+
|
|
186
|
+
const nonStreamingLlmConfig: t.LLMConfig = {
|
|
187
|
+
...llmConfig,
|
|
188
|
+
disableStreaming: true,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
run = await Run.create<t.IState>({
|
|
192
|
+
runId: 'deepseek-non-streaming-tool-test',
|
|
193
|
+
graphConfig: {
|
|
194
|
+
type: 'standard',
|
|
195
|
+
llmConfig: nonStreamingLlmConfig,
|
|
196
|
+
tools: [new Calculator()],
|
|
197
|
+
instructions:
|
|
198
|
+
'You are a helpful math assistant. Use the calculator tool to solve math problems.',
|
|
199
|
+
},
|
|
200
|
+
returnContent: true,
|
|
201
|
+
customHandlers,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const userMessage = 'What is 99 * 77?';
|
|
205
|
+
conversationHistory.push(new HumanMessage(userMessage));
|
|
206
|
+
|
|
207
|
+
const inputs = {
|
|
208
|
+
messages: conversationHistory,
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
console.log('Starting DeepSeek non-streaming tool call test...');
|
|
212
|
+
const finalContentParts = await run.processStream(inputs, testConfig);
|
|
213
|
+
|
|
214
|
+
expect(finalContentParts).toBeDefined();
|
|
215
|
+
console.log('Final content parts (non-streaming):', finalContentParts);
|
|
216
|
+
|
|
217
|
+
const finalMessages = run.getRunMessages();
|
|
218
|
+
expect(finalMessages).toBeDefined();
|
|
219
|
+
expect(finalMessages?.length).toBeGreaterThan(0);
|
|
220
|
+
|
|
221
|
+
const hasToolCall = finalMessages?.some(
|
|
222
|
+
(msg) =>
|
|
223
|
+
msg.getType() === 'ai' &&
|
|
224
|
+
Array.isArray((msg as any).tool_calls) &&
|
|
225
|
+
(msg as any).tool_calls.length > 0
|
|
226
|
+
);
|
|
227
|
+
expect(hasToolCall).toBe(true);
|
|
228
|
+
|
|
229
|
+
const hasToolResult = finalMessages?.some(
|
|
230
|
+
(msg) => msg.getType() === 'tool'
|
|
231
|
+
);
|
|
232
|
+
expect(hasToolResult).toBe(true);
|
|
233
|
+
|
|
234
|
+
console.log('Non-streaming tool call test passed');
|
|
235
|
+
console.log(
|
|
236
|
+
'Final response:',
|
|
237
|
+
finalMessages?.[finalMessages.length - 1]?.content
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test(`${capitalizeFirstLetter(provider)}: should process simple message without tools`, async () => {
|
|
242
|
+
const customHandlers = setupCustomHandlers();
|
|
243
|
+
|
|
244
|
+
run = await Run.create<t.IState>({
|
|
245
|
+
runId: 'deepseek-simple-test',
|
|
246
|
+
graphConfig: {
|
|
247
|
+
type: 'standard',
|
|
248
|
+
llmConfig,
|
|
249
|
+
tools: [],
|
|
250
|
+
instructions: 'You are a friendly AI assistant.',
|
|
251
|
+
},
|
|
252
|
+
returnContent: true,
|
|
253
|
+
customHandlers,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const userMessage = 'Hello! How are you today?';
|
|
257
|
+
conversationHistory.push(new HumanMessage(userMessage));
|
|
258
|
+
|
|
259
|
+
const inputs = {
|
|
260
|
+
messages: conversationHistory,
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const finalContentParts = await run.processStream(inputs, testConfig);
|
|
264
|
+
expect(finalContentParts).toBeDefined();
|
|
265
|
+
|
|
266
|
+
const allTextParts = finalContentParts?.every(
|
|
267
|
+
(part) => part.type === ContentTypes.TEXT
|
|
268
|
+
);
|
|
269
|
+
expect(allTextParts).toBe(true);
|
|
270
|
+
|
|
271
|
+
expect(collectedUsage.length).toBeGreaterThan(0);
|
|
272
|
+
expect(collectedUsage[0].input_tokens).toBeGreaterThan(0);
|
|
273
|
+
expect(collectedUsage[0].output_tokens).toBeGreaterThan(0);
|
|
274
|
+
|
|
275
|
+
const finalMessages = run.getRunMessages();
|
|
276
|
+
expect(finalMessages).toBeDefined();
|
|
277
|
+
console.log(
|
|
278
|
+
`${capitalizeFirstLetter(provider)} response:`,
|
|
279
|
+
finalMessages?.[finalMessages.length - 1]?.content
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
);
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
import { config } from 'dotenv';
|
|
4
|
+
config();
|
|
5
|
+
import { Calculator } from '@/tools/Calculator';
|
|
6
|
+
import {
|
|
7
|
+
HumanMessage,
|
|
8
|
+
BaseMessage,
|
|
9
|
+
UsageMetadata,
|
|
10
|
+
} from '@langchain/core/messages';
|
|
11
|
+
import type * as t from '@/types';
|
|
12
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
13
|
+
import { ContentTypes, GraphEvents, Providers } from '@/common';
|
|
14
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
15
|
+
import { capitalizeFirstLetter } from './spec.utils';
|
|
16
|
+
import { Run } from '@/run';
|
|
17
|
+
|
|
18
|
+
const provider = Providers.MOONSHOT;
|
|
19
|
+
|
|
20
|
+
const llmConfig: t.LLMConfig = {
|
|
21
|
+
provider,
|
|
22
|
+
model: 'kimi-k2.5',
|
|
23
|
+
configuration: {
|
|
24
|
+
apiKey: process.env.MOONSHOT_API_KEY,
|
|
25
|
+
baseURL: 'https://api.moonshot.ai/v1',
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const skipTests = process.env.MOONSHOT_API_KEY == null;
|
|
30
|
+
|
|
31
|
+
(skipTests ? describe.skip : describe)(
|
|
32
|
+
`${capitalizeFirstLetter(provider)} Streaming Tests`,
|
|
33
|
+
() => {
|
|
34
|
+
jest.setTimeout(120000);
|
|
35
|
+
let run: Run<t.IState>;
|
|
36
|
+
let collectedUsage: UsageMetadata[];
|
|
37
|
+
let conversationHistory: BaseMessage[];
|
|
38
|
+
let aggregateContent: t.ContentAggregator;
|
|
39
|
+
let _contentParts: t.MessageContentComplex[];
|
|
40
|
+
|
|
41
|
+
const testConfig = {
|
|
42
|
+
configurable: {
|
|
43
|
+
thread_id: 'moonshot-test-1',
|
|
44
|
+
},
|
|
45
|
+
streamMode: 'values',
|
|
46
|
+
version: 'v2' as const,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
beforeEach(async () => {
|
|
50
|
+
conversationHistory = [];
|
|
51
|
+
collectedUsage = [];
|
|
52
|
+
const { contentParts: cp, aggregateContent: ac } =
|
|
53
|
+
createContentAggregator();
|
|
54
|
+
_contentParts = cp as t.MessageContentComplex[];
|
|
55
|
+
aggregateContent = ac;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const onMessageDeltaSpy = jest.fn();
|
|
59
|
+
const onReasoningDeltaSpy = jest.fn();
|
|
60
|
+
const onRunStepSpy = jest.fn();
|
|
61
|
+
|
|
62
|
+
afterAll(() => {
|
|
63
|
+
onMessageDeltaSpy.mockReset();
|
|
64
|
+
onReasoningDeltaSpy.mockReset();
|
|
65
|
+
onRunStepSpy.mockReset();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const setupCustomHandlers = (): Record<
|
|
69
|
+
string | GraphEvents,
|
|
70
|
+
t.EventHandler
|
|
71
|
+
> => ({
|
|
72
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
73
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
|
|
74
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
75
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
76
|
+
handle: (
|
|
77
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
78
|
+
data: t.StreamEventData
|
|
79
|
+
): void => {
|
|
80
|
+
aggregateContent({
|
|
81
|
+
event,
|
|
82
|
+
data: data as unknown as { result: t.ToolEndEvent },
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
87
|
+
handle: (
|
|
88
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
89
|
+
data: t.StreamEventData,
|
|
90
|
+
metadata,
|
|
91
|
+
graph
|
|
92
|
+
): void => {
|
|
93
|
+
onRunStepSpy(event, data, metadata, graph);
|
|
94
|
+
aggregateContent({ event, data: data as t.RunStep });
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
98
|
+
handle: (
|
|
99
|
+
event: GraphEvents.ON_RUN_STEP_DELTA,
|
|
100
|
+
data: t.StreamEventData
|
|
101
|
+
): void => {
|
|
102
|
+
aggregateContent({ event, data: data as t.RunStepDeltaEvent });
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
106
|
+
handle: (
|
|
107
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
108
|
+
data: t.StreamEventData,
|
|
109
|
+
metadata,
|
|
110
|
+
graph
|
|
111
|
+
): void => {
|
|
112
|
+
onMessageDeltaSpy(event, data, metadata, graph);
|
|
113
|
+
aggregateContent({ event, data: data as t.MessageDeltaEvent });
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
[GraphEvents.ON_REASONING_DELTA]: {
|
|
117
|
+
handle: (
|
|
118
|
+
event: GraphEvents.ON_REASONING_DELTA,
|
|
119
|
+
data: t.StreamEventData
|
|
120
|
+
): void => {
|
|
121
|
+
onReasoningDeltaSpy(event, data);
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
[GraphEvents.TOOL_START]: {
|
|
125
|
+
handle: (
|
|
126
|
+
_event: string,
|
|
127
|
+
_data: t.StreamEventData,
|
|
128
|
+
_metadata?: Record<string, unknown>
|
|
129
|
+
): void => {
|
|
130
|
+
// Handle tool start
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test(`${capitalizeFirstLetter(provider)}: should handle tool calls with reasoning_content preservation`, async () => {
|
|
136
|
+
const customHandlers = setupCustomHandlers();
|
|
137
|
+
|
|
138
|
+
run = await Run.create<t.IState>({
|
|
139
|
+
runId: 'moonshot-tool-test',
|
|
140
|
+
graphConfig: {
|
|
141
|
+
type: 'standard',
|
|
142
|
+
llmConfig,
|
|
143
|
+
tools: [new Calculator()],
|
|
144
|
+
instructions:
|
|
145
|
+
'You are a helpful math assistant. Use the calculator tool to solve math problems.',
|
|
146
|
+
},
|
|
147
|
+
returnContent: true,
|
|
148
|
+
customHandlers,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const userMessage = 'What is 127 * 453?';
|
|
152
|
+
conversationHistory.push(new HumanMessage(userMessage));
|
|
153
|
+
|
|
154
|
+
const inputs = {
|
|
155
|
+
messages: conversationHistory,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
console.log('Starting Moonshot tool call test...');
|
|
159
|
+
const finalContentParts = await run.processStream(inputs, testConfig);
|
|
160
|
+
|
|
161
|
+
expect(finalContentParts).toBeDefined();
|
|
162
|
+
console.log('Final content parts:', finalContentParts);
|
|
163
|
+
|
|
164
|
+
const finalMessages = run.getRunMessages();
|
|
165
|
+
expect(finalMessages).toBeDefined();
|
|
166
|
+
expect(finalMessages?.length).toBeGreaterThan(0);
|
|
167
|
+
|
|
168
|
+
const hasToolCall = finalMessages?.some(
|
|
169
|
+
(msg) =>
|
|
170
|
+
msg.getType() === 'ai' &&
|
|
171
|
+
Array.isArray((msg as any).tool_calls) &&
|
|
172
|
+
(msg as any).tool_calls.length > 0
|
|
173
|
+
);
|
|
174
|
+
expect(hasToolCall).toBe(true);
|
|
175
|
+
|
|
176
|
+
const hasToolResult = finalMessages?.some(
|
|
177
|
+
(msg) => msg.getType() === 'tool'
|
|
178
|
+
);
|
|
179
|
+
expect(hasToolResult).toBe(true);
|
|
180
|
+
|
|
181
|
+
console.log('Tool call test passed - reasoning_content was preserved');
|
|
182
|
+
console.log(
|
|
183
|
+
'Final response:',
|
|
184
|
+
finalMessages?.[finalMessages.length - 1]?.content
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test(`${capitalizeFirstLetter(provider)}: should handle multi-turn conversation with tools`, async () => {
|
|
189
|
+
const customHandlers = setupCustomHandlers();
|
|
190
|
+
|
|
191
|
+
run = await Run.create<t.IState>({
|
|
192
|
+
runId: 'moonshot-multi-turn-test',
|
|
193
|
+
graphConfig: {
|
|
194
|
+
type: 'standard',
|
|
195
|
+
llmConfig,
|
|
196
|
+
tools: [new Calculator()],
|
|
197
|
+
instructions:
|
|
198
|
+
'You are a helpful math assistant. Use the calculator tool when needed.',
|
|
199
|
+
},
|
|
200
|
+
returnContent: true,
|
|
201
|
+
customHandlers,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
conversationHistory.push(new HumanMessage('What is 15 + 27?'));
|
|
205
|
+
|
|
206
|
+
let finalContentParts = await run.processStream(
|
|
207
|
+
{ messages: conversationHistory },
|
|
208
|
+
testConfig
|
|
209
|
+
);
|
|
210
|
+
expect(finalContentParts).toBeDefined();
|
|
211
|
+
|
|
212
|
+
let runMessages = run.getRunMessages();
|
|
213
|
+
conversationHistory.push(...(runMessages ?? []));
|
|
214
|
+
|
|
215
|
+
console.log(
|
|
216
|
+
'Turn 1 completed, conversation length:',
|
|
217
|
+
conversationHistory.length
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
conversationHistory.push(
|
|
221
|
+
new HumanMessage('Now multiply that result by 3')
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
run = await Run.create<t.IState>({
|
|
225
|
+
runId: 'moonshot-multi-turn-test-2',
|
|
226
|
+
graphConfig: {
|
|
227
|
+
type: 'standard',
|
|
228
|
+
llmConfig,
|
|
229
|
+
tools: [new Calculator()],
|
|
230
|
+
instructions:
|
|
231
|
+
'You are a helpful math assistant. Use the calculator tool when needed.',
|
|
232
|
+
},
|
|
233
|
+
returnContent: true,
|
|
234
|
+
customHandlers,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
finalContentParts = await run.processStream(
|
|
238
|
+
{ messages: conversationHistory },
|
|
239
|
+
{ ...testConfig, configurable: { thread_id: 'moonshot-test-2' } }
|
|
240
|
+
);
|
|
241
|
+
expect(finalContentParts).toBeDefined();
|
|
242
|
+
|
|
243
|
+
runMessages = run.getRunMessages();
|
|
244
|
+
expect(runMessages).toBeDefined();
|
|
245
|
+
|
|
246
|
+
console.log('Turn 2 completed');
|
|
247
|
+
console.log(
|
|
248
|
+
'Final response:',
|
|
249
|
+
runMessages?.[runMessages.length - 1]?.content
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const textParts = finalContentParts?.filter(
|
|
253
|
+
(part) => part.type === ContentTypes.TEXT
|
|
254
|
+
);
|
|
255
|
+
expect(textParts?.length).toBeGreaterThan(0);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test(`${capitalizeFirstLetter(provider)}: should process simple message without tools`, async () => {
|
|
259
|
+
const customHandlers = setupCustomHandlers();
|
|
260
|
+
|
|
261
|
+
run = await Run.create<t.IState>({
|
|
262
|
+
runId: 'moonshot-simple-test',
|
|
263
|
+
graphConfig: {
|
|
264
|
+
type: 'standard',
|
|
265
|
+
llmConfig,
|
|
266
|
+
tools: [],
|
|
267
|
+
instructions: 'You are a friendly AI assistant.',
|
|
268
|
+
},
|
|
269
|
+
returnContent: true,
|
|
270
|
+
customHandlers,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const userMessage = 'Hello! How are you today?';
|
|
274
|
+
conversationHistory.push(new HumanMessage(userMessage));
|
|
275
|
+
|
|
276
|
+
const inputs = {
|
|
277
|
+
messages: conversationHistory,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const finalContentParts = await run.processStream(inputs, testConfig);
|
|
281
|
+
expect(finalContentParts).toBeDefined();
|
|
282
|
+
|
|
283
|
+
const allTextParts = finalContentParts?.every(
|
|
284
|
+
(part) => part.type === ContentTypes.TEXT
|
|
285
|
+
);
|
|
286
|
+
expect(allTextParts).toBe(true);
|
|
287
|
+
|
|
288
|
+
expect(collectedUsage.length).toBeGreaterThan(0);
|
|
289
|
+
expect(collectedUsage[0].input_tokens).toBeGreaterThan(0);
|
|
290
|
+
expect(collectedUsage[0].output_tokens).toBeGreaterThan(0);
|
|
291
|
+
|
|
292
|
+
const finalMessages = run.getRunMessages();
|
|
293
|
+
expect(finalMessages).toBeDefined();
|
|
294
|
+
console.log(
|
|
295
|
+
`${capitalizeFirstLetter(provider)} response:`,
|
|
296
|
+
finalMessages?.[finalMessages.length - 1]?.content
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test(`${capitalizeFirstLetter(provider)}: should handle tool calls with disableStreaming`, async () => {
|
|
301
|
+
const customHandlers = setupCustomHandlers();
|
|
302
|
+
|
|
303
|
+
const nonStreamingLlmConfig: t.LLMConfig = {
|
|
304
|
+
...llmConfig,
|
|
305
|
+
disableStreaming: true,
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
run = await Run.create<t.IState>({
|
|
309
|
+
runId: 'moonshot-non-streaming-tool-test',
|
|
310
|
+
graphConfig: {
|
|
311
|
+
type: 'standard',
|
|
312
|
+
llmConfig: nonStreamingLlmConfig,
|
|
313
|
+
tools: [new Calculator()],
|
|
314
|
+
instructions:
|
|
315
|
+
'You are a helpful math assistant. Use the calculator tool to solve math problems.',
|
|
316
|
+
},
|
|
317
|
+
returnContent: true,
|
|
318
|
+
customHandlers,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const userMessage = 'What is 99 * 77?';
|
|
322
|
+
conversationHistory.push(new HumanMessage(userMessage));
|
|
323
|
+
|
|
324
|
+
const inputs = {
|
|
325
|
+
messages: conversationHistory,
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
console.log('Starting Moonshot non-streaming tool call test...');
|
|
329
|
+
const finalContentParts = await run.processStream(inputs, testConfig);
|
|
330
|
+
|
|
331
|
+
expect(finalContentParts).toBeDefined();
|
|
332
|
+
console.log('Final content parts (non-streaming):', finalContentParts);
|
|
333
|
+
|
|
334
|
+
const finalMessages = run.getRunMessages();
|
|
335
|
+
expect(finalMessages).toBeDefined();
|
|
336
|
+
expect(finalMessages?.length).toBeGreaterThan(0);
|
|
337
|
+
|
|
338
|
+
const hasToolCall = finalMessages?.some(
|
|
339
|
+
(msg) =>
|
|
340
|
+
msg.getType() === 'ai' &&
|
|
341
|
+
Array.isArray((msg as any).tool_calls) &&
|
|
342
|
+
(msg as any).tool_calls.length > 0
|
|
343
|
+
);
|
|
344
|
+
expect(hasToolCall).toBe(true);
|
|
345
|
+
|
|
346
|
+
const hasToolResult = finalMessages?.some(
|
|
347
|
+
(msg) => msg.getType() === 'tool'
|
|
348
|
+
);
|
|
349
|
+
expect(hasToolResult).toBe(true);
|
|
350
|
+
|
|
351
|
+
console.log('Non-streaming tool call test passed');
|
|
352
|
+
console.log(
|
|
353
|
+
'Final response:',
|
|
354
|
+
finalMessages?.[finalMessages.length - 1]?.content
|
|
355
|
+
);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
);
|
package/src/types/llm.ts
CHANGED
|
@@ -26,6 +26,7 @@ import type { ChatXAIInput } from '@langchain/xai';
|
|
|
26
26
|
import {
|
|
27
27
|
AzureChatOpenAI,
|
|
28
28
|
ChatDeepSeek,
|
|
29
|
+
ChatMoonshot,
|
|
29
30
|
ChatOpenAI,
|
|
30
31
|
ChatXAI,
|
|
31
32
|
} from '@/llm/openai';
|
|
@@ -110,6 +111,7 @@ export type ProviderOptionsMap = {
|
|
|
110
111
|
[Providers.OPENROUTER]: ChatOpenRouterCallOptions;
|
|
111
112
|
[Providers.BEDROCK]: BedrockConverseClientOptions;
|
|
112
113
|
[Providers.XAI]: XAIClientOptions;
|
|
114
|
+
[Providers.MOONSHOT]: OpenAIClientOptions;
|
|
113
115
|
};
|
|
114
116
|
|
|
115
117
|
export type ChatModelMap = {
|
|
@@ -124,6 +126,7 @@ export type ChatModelMap = {
|
|
|
124
126
|
[Providers.OPENROUTER]: ChatOpenRouter;
|
|
125
127
|
[Providers.BEDROCK]: CustomChatBedrockConverse;
|
|
126
128
|
[Providers.GOOGLE]: CustomChatGoogleGenerativeAI;
|
|
129
|
+
[Providers.MOONSHOT]: ChatMoonshot;
|
|
127
130
|
};
|
|
128
131
|
|
|
129
132
|
export type ChatModelConstructorMap = {
|
package/src/utils/llmConfig.ts
CHANGED
|
@@ -120,6 +120,16 @@ export const llmConfigs: Record<string, t.LLMConfig | undefined> = {
|
|
|
120
120
|
streaming: true,
|
|
121
121
|
streamUsage: true,
|
|
122
122
|
},
|
|
123
|
+
[Providers.MOONSHOT]: {
|
|
124
|
+
provider: Providers.MOONSHOT,
|
|
125
|
+
model: 'kimi-k2.5',
|
|
126
|
+
streaming: true,
|
|
127
|
+
streamUsage: true,
|
|
128
|
+
configuration: {
|
|
129
|
+
apiKey: process.env.MOONSHOT_API_KEY,
|
|
130
|
+
baseURL: 'https://api.moonshot.ai/v1',
|
|
131
|
+
},
|
|
132
|
+
},
|
|
123
133
|
[Providers.ANTHROPIC]: {
|
|
124
134
|
provider: Providers.ANTHROPIC,
|
|
125
135
|
model: 'claude-sonnet-4-5',
|