@langgraph-js/sdk 1.1.6 → 1.1.8
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/LICENSE +201 -201
- package/README.md +163 -163
- package/dist/LangGraphClient.d.ts +95 -9
- package/dist/LangGraphClient.js +73 -5
- package/dist/SpendTime.d.ts +28 -0
- package/dist/SpendTime.js +28 -0
- package/dist/ToolManager.d.ts +37 -19
- package/dist/ToolManager.js +36 -18
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/tool/createTool.d.ts +1 -1
- package/dist/tool/createTool.js +1 -1
- package/dist/tool/index.d.ts +2 -2
- package/dist/tool/index.js +2 -2
- package/dist/tool/utils.d.ts +1 -1
- package/dist/ui-store/UnionStore.d.ts +8 -0
- package/dist/ui-store/UnionStore.js +4 -0
- package/dist/ui-store/createChatStore.d.ts +41 -1
- package/dist/ui-store/createChatStore.js +76 -7
- package/dist/ui-store/index.d.ts +2 -2
- package/dist/ui-store/index.js +2 -2
- package/package.json +2 -4
- package/src/LangGraphClient.ts +554 -469
- package/src/SpendTime.ts +60 -29
- package/src/ToolManager.ts +123 -100
- package/src/index.ts +5 -5
- package/src/tool/copilotkit-actions.ts +72 -72
- package/src/tool/createTool.ts +78 -78
- package/src/tool/index.ts +2 -2
- package/src/tool/utils.ts +158 -158
- package/src/ui-store/UnionStore.ts +29 -20
- package/src/ui-store/createChatStore.ts +241 -167
- package/src/ui-store/index.ts +2 -2
- package/test/testResponse.json +5418 -5418
- package/tsconfig.json +112 -112
- package/.env +0 -0
- package/index.html +0 -12
- package/ui/index.ts +0 -182
- package/ui/tool.ts +0 -55
package/src/LangGraphClient.ts
CHANGED
|
@@ -1,469 +1,554 @@
|
|
|
1
|
-
import { Client, Thread, Message, Assistant, HumanMessage, AIMessage, ToolMessage, Command } from "@langchain/langgraph-sdk";
|
|
2
|
-
import { ToolManager } from "./ToolManager";
|
|
3
|
-
import { CallToolResult } from "./tool";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
this.
|
|
462
|
-
this.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
1
|
+
import { Client, Thread, Message, Assistant, HumanMessage, AIMessage, ToolMessage, Command } from "@langchain/langgraph-sdk";
|
|
2
|
+
import { ToolManager } from "./ToolManager.js";
|
|
3
|
+
import { CallToolResult } from "./tool/createTool.js";
|
|
4
|
+
interface AsyncCallerParams {
|
|
5
|
+
/**
|
|
6
|
+
* The maximum number of concurrent calls that can be made.
|
|
7
|
+
* Defaults to `Infinity`, which means no limit.
|
|
8
|
+
*/
|
|
9
|
+
maxConcurrency?: number;
|
|
10
|
+
/**
|
|
11
|
+
* The maximum number of retries that can be made for a single call,
|
|
12
|
+
* with an exponential backoff between each attempt. Defaults to 6.
|
|
13
|
+
*/
|
|
14
|
+
maxRetries?: number;
|
|
15
|
+
onFailedResponseHook?: any;
|
|
16
|
+
/**
|
|
17
|
+
* Specify a custom fetch implementation.
|
|
18
|
+
*
|
|
19
|
+
* By default we expect the `fetch` is available in the global scope.
|
|
20
|
+
*/
|
|
21
|
+
fetch?: typeof fetch | ((...args: any[]) => any);
|
|
22
|
+
}
|
|
23
|
+
export type RenderMessage = Message & {
|
|
24
|
+
/** 工具入参 ,聚合而来*/
|
|
25
|
+
tool_input?: string;
|
|
26
|
+
additional_kwargs?: {
|
|
27
|
+
done?: boolean;
|
|
28
|
+
tool_calls?: {
|
|
29
|
+
function: {
|
|
30
|
+
arguments: string;
|
|
31
|
+
};
|
|
32
|
+
}[];
|
|
33
|
+
};
|
|
34
|
+
usage_metadata?: {
|
|
35
|
+
total_tokens: number;
|
|
36
|
+
input_tokens: number;
|
|
37
|
+
output_tokens: number;
|
|
38
|
+
};
|
|
39
|
+
response_metadata?: {
|
|
40
|
+
create_time: string;
|
|
41
|
+
};
|
|
42
|
+
/** 耗时 */
|
|
43
|
+
spend_time?: number;
|
|
44
|
+
/** 渲染时的唯一 id,聚合而来*/
|
|
45
|
+
unique_id?: string;
|
|
46
|
+
};
|
|
47
|
+
export type SendMessageOptions = {
|
|
48
|
+
extraParams?: Record<string, any>;
|
|
49
|
+
_debug?: { streamResponse?: any };
|
|
50
|
+
command?: Command;
|
|
51
|
+
};
|
|
52
|
+
export interface LangGraphClientConfig {
|
|
53
|
+
apiUrl?: string;
|
|
54
|
+
apiKey?: string;
|
|
55
|
+
callerOptions?: AsyncCallerParams;
|
|
56
|
+
timeoutMs?: number;
|
|
57
|
+
defaultHeaders?: Record<string, string | null | undefined>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @zh StreamingMessageType 类用于判断消息的类型。
|
|
62
|
+
* @en The StreamingMessageType class is used to determine the type of a message.
|
|
63
|
+
*/
|
|
64
|
+
export class StreamingMessageType {
|
|
65
|
+
static isUser(m: Message) {
|
|
66
|
+
return m.type === "human";
|
|
67
|
+
}
|
|
68
|
+
static isTool(m: Message) {
|
|
69
|
+
return m.type === "tool";
|
|
70
|
+
}
|
|
71
|
+
static isAssistant(m: Message) {
|
|
72
|
+
return m.type === "ai" && !this.isToolAssistant(m);
|
|
73
|
+
}
|
|
74
|
+
static isToolAssistant(m: Message) {
|
|
75
|
+
/** @ts-ignore */
|
|
76
|
+
return m.type === "ai" && (m.tool_calls?.length || m.tool_call_chunks?.length);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type StreamingUpdateEvent = {
|
|
81
|
+
type: "message" | "value" | "update" | "error" | "thread" | "done";
|
|
82
|
+
data: any;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
type StreamingUpdateCallback = (event: StreamingUpdateEvent) => void;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @zh LangGraphClient 类是与 LangGraph 后端交互的主要客户端。
|
|
89
|
+
* @en The LangGraphClient class is the main client for interacting with the LangGraph backend.
|
|
90
|
+
*/
|
|
91
|
+
export class LangGraphClient extends Client {
|
|
92
|
+
private currentAssistant: Assistant | null = null;
|
|
93
|
+
private currentThread: Thread | null = null;
|
|
94
|
+
private streamingCallbacks: Set<StreamingUpdateCallback> = new Set();
|
|
95
|
+
tools: ToolManager = new ToolManager();
|
|
96
|
+
stopController: AbortController | null = null;
|
|
97
|
+
|
|
98
|
+
constructor(config: LangGraphClientConfig) {
|
|
99
|
+
super(config);
|
|
100
|
+
}
|
|
101
|
+
availableAssistants: Assistant[] = [];
|
|
102
|
+
private listAssistants() {
|
|
103
|
+
return this.assistants.search({
|
|
104
|
+
metadata: null,
|
|
105
|
+
offset: 0,
|
|
106
|
+
limit: 100,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* @zh 初始化 Assistant。
|
|
111
|
+
* @en Initializes the Assistant.
|
|
112
|
+
*/
|
|
113
|
+
async initAssistant(agentName: string) {
|
|
114
|
+
try {
|
|
115
|
+
const assistants = await this.listAssistants();
|
|
116
|
+
this.availableAssistants = assistants;
|
|
117
|
+
if (assistants.length > 0) {
|
|
118
|
+
this.currentAssistant = assistants.find((assistant) => assistant.graph_id === agentName) || null;
|
|
119
|
+
if (!this.currentAssistant) {
|
|
120
|
+
throw new Error("Agent not found: " + agentName);
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
throw new Error("No assistants found");
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error("Failed to initialize LangGraphClient:", error);
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @zh 创建一个新的 Thread。
|
|
133
|
+
* @en Creates a new Thread.
|
|
134
|
+
*/
|
|
135
|
+
async createThread({
|
|
136
|
+
threadId,
|
|
137
|
+
}: {
|
|
138
|
+
threadId?: string;
|
|
139
|
+
} = {}) {
|
|
140
|
+
try {
|
|
141
|
+
this.currentThread = await this.threads.create({
|
|
142
|
+
threadId,
|
|
143
|
+
});
|
|
144
|
+
return this.currentThread;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error("Failed to create new thread:", error);
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* @zh 列出所有的 Thread。
|
|
152
|
+
* @en Lists all Threads.
|
|
153
|
+
*/
|
|
154
|
+
async listThreads<T>() {
|
|
155
|
+
return this.threads.search<T>({
|
|
156
|
+
sortOrder: "desc",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* @zh 从历史中恢复 Thread 数据。
|
|
161
|
+
* @en Resets the Thread data from history.
|
|
162
|
+
*/
|
|
163
|
+
async resetThread(agent: string, threadId: string) {
|
|
164
|
+
await this.initAssistant(agent);
|
|
165
|
+
this.currentThread = await this.threads.get(threadId);
|
|
166
|
+
this.graphState = this.currentThread.values;
|
|
167
|
+
this.graphMessages = this.graphState.messages;
|
|
168
|
+
this.emitStreamingUpdate({
|
|
169
|
+
type: "value",
|
|
170
|
+
data: {
|
|
171
|
+
event: "messages/partial",
|
|
172
|
+
data: {
|
|
173
|
+
messages: this.graphMessages,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
streamingMessage: RenderMessage[] = [];
|
|
180
|
+
/** 图发过来的更新信息 */
|
|
181
|
+
graphMessages: RenderMessage[] = [];
|
|
182
|
+
cloneMessage(message: Message): Message {
|
|
183
|
+
return JSON.parse(JSON.stringify(message));
|
|
184
|
+
}
|
|
185
|
+
private replaceMessageWithValuesMessage(message: AIMessage | ToolMessage, isTool = false): Message {
|
|
186
|
+
const key = (isTool ? "tool_call_id" : "id") as any as "id";
|
|
187
|
+
const valuesMessage = this.graphMessages.find((i) => i[key] === message[key]);
|
|
188
|
+
if (valuesMessage) {
|
|
189
|
+
return {
|
|
190
|
+
...valuesMessage,
|
|
191
|
+
/** @ts-ignore */
|
|
192
|
+
tool_input: message.tool_input,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return message;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* @zh 用于 UI 中的流式渲染中的消息。
|
|
200
|
+
* @en Messages used for streaming rendering in the UI.
|
|
201
|
+
*/
|
|
202
|
+
get renderMessage() {
|
|
203
|
+
const previousMessage = new Map<string, Message>();
|
|
204
|
+
const result: Message[] = [];
|
|
205
|
+
const inputMessages = [...this.graphMessages, ...this.streamingMessage];
|
|
206
|
+
|
|
207
|
+
// 从后往前遍历,这样可以保证最新的消息在前面
|
|
208
|
+
for (let i = inputMessages.length - 1; i >= 0; i--) {
|
|
209
|
+
const message = this.cloneMessage(inputMessages[i]);
|
|
210
|
+
|
|
211
|
+
if (!message.id) {
|
|
212
|
+
result.unshift(message);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 如果已经处理过这个 id 的消息,跳过
|
|
217
|
+
if (previousMessage.has(message.id)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (StreamingMessageType.isToolAssistant(message)) {
|
|
222
|
+
const m = this.replaceMessageWithValuesMessage(message as AIMessage);
|
|
223
|
+
// 记录这个 id 的消息,并添加到结果中
|
|
224
|
+
previousMessage.set(message.id, m);
|
|
225
|
+
|
|
226
|
+
/** @ts-ignore */
|
|
227
|
+
const tool_calls: NonNullable<AIMessage["tool_calls"]> = (m as AIMessage).tool_calls?.length ? (m as AIMessage).tool_calls : (m as RenderMessage).tool_call_chunks;
|
|
228
|
+
const new_tool_calls = tool_calls!.map((tool, index) => {
|
|
229
|
+
return this.replaceMessageWithValuesMessage(
|
|
230
|
+
{
|
|
231
|
+
type: "tool",
|
|
232
|
+
additional_kwargs: {},
|
|
233
|
+
/** @ts-ignore */
|
|
234
|
+
tool_input: m.additional_kwargs?.tool_calls?.[index]?.function?.arguments,
|
|
235
|
+
id: tool.id,
|
|
236
|
+
name: tool.name,
|
|
237
|
+
response_metadata: {},
|
|
238
|
+
tool_call_id: tool.id!,
|
|
239
|
+
},
|
|
240
|
+
true
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
for (const tool of new_tool_calls) {
|
|
244
|
+
if (!previousMessage.has(tool.id!)) {
|
|
245
|
+
result.unshift(tool);
|
|
246
|
+
previousMessage.set(tool.id!, tool);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
result.unshift(m);
|
|
250
|
+
} else {
|
|
251
|
+
// 记录这个 id 的消息,并添加到结果中
|
|
252
|
+
const m = this.replaceMessageWithValuesMessage(message as AIMessage);
|
|
253
|
+
previousMessage.set(message.id, m);
|
|
254
|
+
result.unshift(m);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return this.attachInfoForMessage(this.composeToolMessages(result as RenderMessage[]));
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* @zh 为消息附加额外的信息,如耗时、唯一 ID 等。
|
|
262
|
+
* @en Attaches additional information to messages, such as spend time, unique ID, etc.
|
|
263
|
+
*/
|
|
264
|
+
private attachInfoForMessage(result: RenderMessage[]) {
|
|
265
|
+
let lastMessage: RenderMessage | null = null;
|
|
266
|
+
for (const message of result) {
|
|
267
|
+
const createTime = message.response_metadata?.create_time || "";
|
|
268
|
+
try {
|
|
269
|
+
// 用长度作为渲染 id,长度变了就要重新渲染
|
|
270
|
+
message.unique_id = message.id! + JSON.stringify(message.content).length;
|
|
271
|
+
} catch (e) {
|
|
272
|
+
message.unique_id = message.id!;
|
|
273
|
+
}
|
|
274
|
+
message.spend_time = new Date(createTime).getTime() - new Date(lastMessage?.response_metadata?.create_time || createTime).getTime();
|
|
275
|
+
if (!message.usage_metadata && (message as AIMessage).response_metadata?.usage) {
|
|
276
|
+
const usage = (message as AIMessage).response_metadata!.usage as {
|
|
277
|
+
prompt_tokens: number;
|
|
278
|
+
completion_tokens: number;
|
|
279
|
+
total_tokens: number;
|
|
280
|
+
};
|
|
281
|
+
message.usage_metadata = {
|
|
282
|
+
input_tokens: usage.prompt_tokens,
|
|
283
|
+
output_tokens: usage.completion_tokens,
|
|
284
|
+
total_tokens: usage.total_tokens,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
lastMessage = message;
|
|
288
|
+
}
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* @zh 组合工具消息,将 AI 的工具调用和工具的执行结果关联起来。
|
|
293
|
+
* @en Composes tool messages, associating AI tool calls with tool execution results.
|
|
294
|
+
*/
|
|
295
|
+
private composeToolMessages(messages: RenderMessage[]): RenderMessage[] {
|
|
296
|
+
const result: RenderMessage[] = [];
|
|
297
|
+
const assistantToolMessages = new Map<string, { args: string }>();
|
|
298
|
+
const toolParentMessage = new Map<string, RenderMessage>();
|
|
299
|
+
for (const message of messages) {
|
|
300
|
+
if (StreamingMessageType.isToolAssistant(message)) {
|
|
301
|
+
/** @ts-ignore 只有 tool_call_chunks 的 args 才是文本 */
|
|
302
|
+
(message.tool_calls || message.tool_call_chunks)?.forEach((element) => {
|
|
303
|
+
assistantToolMessages.set(element.id!, element);
|
|
304
|
+
toolParentMessage.set(element.id!, message);
|
|
305
|
+
});
|
|
306
|
+
if (!message.content) continue;
|
|
307
|
+
}
|
|
308
|
+
if (StreamingMessageType.isTool(message) && !message.tool_input) {
|
|
309
|
+
const assistantToolMessage = assistantToolMessages.get(message.tool_call_id!);
|
|
310
|
+
const parentMessage = toolParentMessage.get(message.tool_call_id!);
|
|
311
|
+
if (assistantToolMessage) {
|
|
312
|
+
message.tool_input = typeof assistantToolMessage.args !== "string" ? JSON.stringify(assistantToolMessage.args) : assistantToolMessage.args;
|
|
313
|
+
if (message.additional_kwargs) {
|
|
314
|
+
message.additional_kwargs.done = true;
|
|
315
|
+
} else {
|
|
316
|
+
message.additional_kwargs = {
|
|
317
|
+
done: true,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (parentMessage) {
|
|
322
|
+
message.usage_metadata = parentMessage.usage_metadata;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
result.push(message);
|
|
326
|
+
}
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* @zh 获取 Token 计数器信息。
|
|
331
|
+
* @en Gets the Token counter information.
|
|
332
|
+
*/
|
|
333
|
+
get tokenCounter() {
|
|
334
|
+
return this.graphMessages.reduce(
|
|
335
|
+
(acc, message) => {
|
|
336
|
+
if (message.usage_metadata) {
|
|
337
|
+
acc.total_tokens += message.usage_metadata?.total_tokens || 0;
|
|
338
|
+
acc.input_tokens += message.usage_metadata?.input_tokens || 0;
|
|
339
|
+
acc.output_tokens += message.usage_metadata?.output_tokens || 0;
|
|
340
|
+
} else if ((message as AIMessage).response_metadata?.usage) {
|
|
341
|
+
const usage = (message as AIMessage).response_metadata?.usage as {
|
|
342
|
+
prompt_tokens: number;
|
|
343
|
+
completion_tokens: number;
|
|
344
|
+
total_tokens: number;
|
|
345
|
+
};
|
|
346
|
+
acc.total_tokens += usage.total_tokens || 0;
|
|
347
|
+
acc.input_tokens += usage.prompt_tokens || 0;
|
|
348
|
+
acc.output_tokens += usage.completion_tokens || 0;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return acc;
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
total_tokens: 0,
|
|
355
|
+
input_tokens: 0,
|
|
356
|
+
output_tokens: 0,
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* @zh 注册流式更新的回调函数。
|
|
362
|
+
* @en Registers a callback function for streaming updates.
|
|
363
|
+
*/
|
|
364
|
+
onStreamingUpdate(callback: StreamingUpdateCallback) {
|
|
365
|
+
this.streamingCallbacks.add(callback);
|
|
366
|
+
return () => {
|
|
367
|
+
this.streamingCallbacks.delete(callback);
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private emitStreamingUpdate(event: StreamingUpdateEvent) {
|
|
372
|
+
this.streamingCallbacks.forEach((callback) => callback(event));
|
|
373
|
+
}
|
|
374
|
+
graphState: any = {};
|
|
375
|
+
currentRun?: { run_id: string };
|
|
376
|
+
/**
|
|
377
|
+
* @zh 取消当前的 Run。
|
|
378
|
+
* @en Cancels the current Run.
|
|
379
|
+
*/
|
|
380
|
+
cancelRun() {
|
|
381
|
+
if (this.currentThread?.thread_id && this.currentRun?.run_id) {
|
|
382
|
+
this.runs.cancel(this.currentThread!.thread_id, this.currentRun.run_id);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* @zh 发送消息到 LangGraph 后端。
|
|
387
|
+
* @en Sends a message to the LangGraph backend.
|
|
388
|
+
*/
|
|
389
|
+
async sendMessage(input: string | Message[], { extraParams, _debug, command }: SendMessageOptions = {}) {
|
|
390
|
+
if (!this.currentAssistant) {
|
|
391
|
+
throw new Error("Thread or Assistant not initialized");
|
|
392
|
+
}
|
|
393
|
+
if (!this.currentThread) {
|
|
394
|
+
await this.createThread();
|
|
395
|
+
this.emitStreamingUpdate({
|
|
396
|
+
type: "thread",
|
|
397
|
+
data: {
|
|
398
|
+
event: "thread/create",
|
|
399
|
+
data: {
|
|
400
|
+
thread: this.currentThread,
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const messagesToSend = Array.isArray(input)
|
|
407
|
+
? input
|
|
408
|
+
: [
|
|
409
|
+
{
|
|
410
|
+
type: "human",
|
|
411
|
+
content: input,
|
|
412
|
+
} as HumanMessage,
|
|
413
|
+
];
|
|
414
|
+
const streamResponse =
|
|
415
|
+
_debug?.streamResponse ||
|
|
416
|
+
this.runs.stream(this.currentThread!.thread_id, this.currentAssistant.assistant_id, {
|
|
417
|
+
input: { ...this.graphState, ...(extraParams || {}), messages: messagesToSend, fe_tools: this.tools.toJSON() },
|
|
418
|
+
streamMode: ["messages", "values"],
|
|
419
|
+
streamSubgraphs: true,
|
|
420
|
+
command,
|
|
421
|
+
});
|
|
422
|
+
const streamRecord: any[] = [];
|
|
423
|
+
for await (const chunk of streamResponse) {
|
|
424
|
+
streamRecord.push(chunk);
|
|
425
|
+
if (chunk.event === "metadata") {
|
|
426
|
+
this.currentRun = chunk.data;
|
|
427
|
+
} else if (chunk.event === "error") {
|
|
428
|
+
this.emitStreamingUpdate({
|
|
429
|
+
type: "error",
|
|
430
|
+
data: chunk,
|
|
431
|
+
});
|
|
432
|
+
} else if (chunk.event === "messages/partial") {
|
|
433
|
+
for (const message of chunk.data) {
|
|
434
|
+
this.streamingMessage.push(message);
|
|
435
|
+
}
|
|
436
|
+
this.emitStreamingUpdate({
|
|
437
|
+
type: "message",
|
|
438
|
+
data: chunk,
|
|
439
|
+
});
|
|
440
|
+
continue;
|
|
441
|
+
} else if (chunk.event.startsWith("values")) {
|
|
442
|
+
const data = chunk.data as { messages: Message[] };
|
|
443
|
+
|
|
444
|
+
if (data.messages) {
|
|
445
|
+
const isResume = !!command?.resume;
|
|
446
|
+
const isLongerThanLocal = data.messages.length >= this.graphMessages.length;
|
|
447
|
+
// resume 情况下,长度低于前端 message 的统统不接受
|
|
448
|
+
if (!isResume || (isResume && isLongerThanLocal)) {
|
|
449
|
+
this.graphMessages = data.messages as RenderMessage[];
|
|
450
|
+
this.emitStreamingUpdate({
|
|
451
|
+
type: "value",
|
|
452
|
+
data: chunk,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
this.graphState = chunk.data;
|
|
457
|
+
this.streamingMessage = [];
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
this.streamingMessage = [];
|
|
462
|
+
const data = await this.runFETool();
|
|
463
|
+
if (data) streamRecord.push(...data);
|
|
464
|
+
this.emitStreamingUpdate({
|
|
465
|
+
type: "done",
|
|
466
|
+
data: {
|
|
467
|
+
event: "done",
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
return streamRecord;
|
|
471
|
+
}
|
|
472
|
+
private runFETool() {
|
|
473
|
+
const data = this.graphMessages;
|
|
474
|
+
const lastMessage = data[data.length - 1];
|
|
475
|
+
// 如果最后一条消息是前端工具消息,则调用工具
|
|
476
|
+
if (lastMessage.type === "ai" && lastMessage.tool_calls?.length) {
|
|
477
|
+
const result = lastMessage.tool_calls.map((tool) => {
|
|
478
|
+
if (this.tools.getTool(tool.name!)) {
|
|
479
|
+
const toolMessage: ToolMessage = {
|
|
480
|
+
...tool,
|
|
481
|
+
tool_call_id: tool.id!,
|
|
482
|
+
/** @ts-ignore */
|
|
483
|
+
tool_input: JSON.stringify(tool.args),
|
|
484
|
+
additional_kwargs: {},
|
|
485
|
+
};
|
|
486
|
+
// json 校验
|
|
487
|
+
return this.callFETool(toolMessage, tool.args);
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
return Promise.all(result);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
private async callFETool(message: ToolMessage, args: any) {
|
|
494
|
+
const that = this; // 防止 this 被错误解析
|
|
495
|
+
const result = await this.tools.callTool(message.name!, args, { client: that, message });
|
|
496
|
+
return this.resume(result);
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* @zh 继续被前端工具中断的流程。
|
|
500
|
+
* @en Resumes a process interrupted by a frontend tool.
|
|
501
|
+
*/
|
|
502
|
+
resume(result: CallToolResult) {
|
|
503
|
+
return this.sendMessage([], {
|
|
504
|
+
command: {
|
|
505
|
+
resume: result,
|
|
506
|
+
},
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* @zh 标记前端工具等待已完成。
|
|
511
|
+
* @en Marks the frontend tool waiting as completed.
|
|
512
|
+
*/
|
|
513
|
+
doneFEToolWaiting(id: string, result: CallToolResult) {
|
|
514
|
+
const done = this.tools.doneWaiting(id, result);
|
|
515
|
+
if (!done && this.currentThread?.status === "interrupted") {
|
|
516
|
+
this.resume(result);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* @zh 获取当前的 Thread。
|
|
522
|
+
* @en Gets the current Thread.
|
|
523
|
+
*/
|
|
524
|
+
getCurrentThread() {
|
|
525
|
+
return this.currentThread;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* @zh 获取当前的 Assistant。
|
|
530
|
+
* @en Gets the current Assistant.
|
|
531
|
+
*/
|
|
532
|
+
getCurrentAssistant() {
|
|
533
|
+
return this.currentAssistant;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* @zh 重置客户端状态。
|
|
538
|
+
* @en Resets the client state.
|
|
539
|
+
*/
|
|
540
|
+
async reset() {
|
|
541
|
+
await this.initAssistant(this.currentAssistant?.name!);
|
|
542
|
+
this.currentThread = null;
|
|
543
|
+
this.graphState = {};
|
|
544
|
+
this.graphMessages = [];
|
|
545
|
+
this.streamingMessage = [];
|
|
546
|
+
this.currentRun = undefined;
|
|
547
|
+
this.emitStreamingUpdate({
|
|
548
|
+
type: "value",
|
|
549
|
+
data: {
|
|
550
|
+
event: "messages/partial",
|
|
551
|
+
},
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|