@langgraph-js/sdk 1.12.0 → 2.0.0

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.
@@ -1,47 +1,38 @@
1
- import { Client } from "@langchain/langgraph-sdk";
1
+ import { EventEmitter } from "eventemitter3";
2
2
  import { ToolManager } from "./ToolManager.js";
3
- /**
4
- * @zh StreamingMessageType 类用于判断消息的类型。
5
- * @en The StreamingMessageType class is used to determine the type of a message.
6
- */
7
- export class StreamingMessageType {
8
- static isUser(m) {
9
- return m.type === "human";
10
- }
11
- static isTool(m) {
12
- return m.type === "tool";
13
- }
14
- static isAssistant(m) {
15
- return m.type === "ai" && !this.isToolAssistant(m);
16
- }
17
- static isToolAssistant(m) {
18
- var _a, _b;
19
- /** @ts-ignore */
20
- return m.type === "ai" && (((_a = m.tool_calls) === null || _a === void 0 ? void 0 : _a.length) || ((_b = m.tool_call_chunks) === null || _b === void 0 ? void 0 : _b.length));
21
- }
22
- }
3
+ import { MessageProcessor } from "./MessageProcessor.js";
23
4
  /**
24
5
  * @zh LangGraphClient 类是与 LangGraph 后端交互的主要客户端。
25
6
  * @en The LangGraphClient class is the main client for interacting with the LangGraph backend.
26
7
  */
27
- export class LangGraphClient extends Client {
8
+ export class LangGraphClient extends EventEmitter {
28
9
  constructor(config) {
29
- super(config);
10
+ super();
30
11
  this.currentAssistant = null;
31
12
  this.currentThread = null;
32
- this.streamingCallbacks = new Set();
33
13
  this.tools = new ToolManager();
34
14
  this.stopController = null;
35
15
  /** 用于存储 subAgent 状态数据的键 */
36
16
  this.subAgentsKey = "task_store";
37
17
  this.availableAssistants = [];
38
- this.streamingMessage = [];
39
- /** 图发过来的更新信息 */
40
- this.graphMessages = [];
41
18
  this.graphState = {};
42
19
  /** 当前子图位置,但是依赖 stream,不太适合稳定使用*/
43
20
  this.graphPosition = "";
44
21
  this.extraParams = {};
22
+ this.client = config.client;
23
+ this.messageProcessor = new MessageProcessor(this.subAgentsKey);
24
+ }
25
+ /** 代理 assistants 属性到内部 client */
26
+ get assistants() {
27
+ return this.client.assistants;
28
+ }
29
+ /** 代理 threads 属性到内部 client */
30
+ get threads() {
31
+ return this.client.threads;
32
+ }
33
+ /** 代理 runs 属性到内部 client */
34
+ get runs() {
35
+ return this.client.runs;
45
36
  }
46
37
  listAssistants() {
47
38
  return this.assistants.search({
@@ -109,6 +100,9 @@ export class LangGraphClient extends Client {
109
100
  sortOrder: "desc",
110
101
  });
111
102
  }
103
+ async deleteThread(threadId) {
104
+ return this.threads.delete(threadId);
105
+ }
112
106
  /**
113
107
  * @zh 从历史中恢复 Thread 数据。
114
108
  * @en Resets the Thread data from history.
@@ -118,14 +112,12 @@ export class LangGraphClient extends Client {
118
112
  await this.initAssistant(agent);
119
113
  this.currentThread = await this.threads.get(threadId);
120
114
  this.graphState = this.currentThread.values;
121
- this.graphMessages = ((_a = this.graphState) === null || _a === void 0 ? void 0 : _a.messages) || [];
122
- this.emitStreamingUpdate({
123
- type: "value",
115
+ const graphMessages = ((_a = this.graphState) === null || _a === void 0 ? void 0 : _a.messages) || [];
116
+ this.messageProcessor.setGraphMessages(graphMessages);
117
+ this.emit("value", {
118
+ event: "messages/partial",
124
119
  data: {
125
- event: "messages/partial",
126
- data: {
127
- messages: this.graphMessages,
128
- },
120
+ messages: this.messageProcessor.getGraphMessages(),
129
121
  },
130
122
  });
131
123
  return this.currentThread;
@@ -139,224 +131,21 @@ export class LangGraphClient extends Client {
139
131
  }
140
132
  }
141
133
  cloneMessage(message) {
142
- return JSON.parse(JSON.stringify(message));
143
- }
144
- updateStreamingMessage(message) {
145
- const lastMessage = this.streamingMessage[this.streamingMessage.length - 1];
146
- if (!(lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.id) || message.id !== lastMessage.id) {
147
- this.streamingMessage.push(message);
148
- return;
149
- }
150
- this.streamingMessage[this.streamingMessage.length - 1] = message;
151
- }
152
- /** 将 graphMessages 和 streamingMessage 合并,并返回新的消息数组 */
153
- combineGraphMessagesWithStreamingMessages() {
154
- const idMap = new Map(this.streamingMessage.map((i) => [i.id, i]));
155
- return [
156
- ...this.graphMessages.map((i) => {
157
- if (idMap.has(i.id)) {
158
- const newValue = idMap.get(i.id);
159
- idMap.delete(i.id);
160
- return newValue;
161
- }
162
- return i;
163
- }),
164
- ...idMap.values(),
165
- ];
134
+ return this.messageProcessor.cloneMessage(message);
166
135
  }
167
136
  /**
168
137
  * @zh 用于 UI 中的流式渲染中的消息。
169
138
  * @en Messages used for streaming rendering in the UI.
170
139
  */
171
140
  get renderMessage() {
172
- var _a;
173
- const previousMessage = new Map();
174
- const closedToolCallIds = new Set();
175
- const result = [];
176
- const inputMessages = this.combineGraphMessagesWithStreamingMessages();
177
- // console.log(inputMessages);
178
- // 从后往前遍历,这样可以保证最新的消息在前面
179
- for (let i = inputMessages.length - 1; i >= 0; i--) {
180
- const message = this.cloneMessage(inputMessages[i]);
181
- if (!message.id) {
182
- result.unshift(message);
183
- continue;
184
- }
185
- if (message.type === "ai") {
186
- /** @ts-ignore */
187
- if (!message.name)
188
- message.name = this.getGraphNodeNow().name;
189
- }
190
- if (StreamingMessageType.isToolAssistant(message)) {
191
- const m = message;
192
- // 记录这个 id 的消息,并添加到结果中
193
- previousMessage.set(message.id, m);
194
- /** @ts-ignore */
195
- const tool_calls = ((_a = m.tool_calls) === null || _a === void 0 ? void 0 : _a.length) ? m.tool_calls : m.tool_call_chunks;
196
- const new_tool_calls = tool_calls
197
- .filter((i) => {
198
- return !closedToolCallIds.has(i.id);
199
- })
200
- .map((tool, index) => {
201
- var _a, _b, _c, _d;
202
- return {
203
- type: "tool",
204
- additional_kwargs: {},
205
- /** @ts-ignore */
206
- tool_input: (_d = (_c = (_b = (_a = m.additional_kwargs) === null || _a === void 0 ? void 0 : _a.tool_calls) === null || _b === void 0 ? void 0 : _b[index]) === null || _c === void 0 ? void 0 : _c.function) === null || _d === void 0 ? void 0 : _d.arguments,
207
- id: tool.id,
208
- name: tool.name,
209
- response_metadata: {},
210
- tool_call_id: tool.id,
211
- content: "",
212
- };
213
- });
214
- for (const tool of new_tool_calls) {
215
- if (!previousMessage.has(tool.id)) {
216
- result.unshift(tool);
217
- previousMessage.set(tool.id, tool);
218
- }
219
- }
220
- result.unshift(m);
221
- }
222
- else {
223
- if (message.type === "tool" && message.tool_call_id) {
224
- closedToolCallIds.add(message.tool_call_id);
225
- }
226
- previousMessage.set(message.id, message);
227
- result.unshift(message);
228
- }
229
- }
230
- return this.convertSubAgentMessages(this.attachInfoForMessage(this.composeToolMessages(result)));
231
- }
232
- /** 转换 subAgent 消息为工具的子消息 */
233
- convertSubAgentMessages(messages) {
234
- const origin_task_store = this.graphState[this.subAgentsKey];
235
- if (!origin_task_store)
236
- return messages;
237
- const task_store = JSON.parse(JSON.stringify(origin_task_store));
238
- console.log(messages);
239
- /** 获取 subAgent 消息的 id,用于流式过程中对数据进行标记 */
240
- messages
241
- .filter((i) => {
242
- var _a;
243
- return (_a = i.node_name) === null || _a === void 0 ? void 0 : _a.startsWith("subagent_");
244
- })
245
- .forEach((i) => {
246
- const tool_call_id = i.node_name.replace("subagent_", "");
247
- const store = task_store[tool_call_id];
248
- if (store) {
249
- // 根据 id 进行去重
250
- const exists = store.messages.some((msg) => msg.id === i.id);
251
- if (!exists) {
252
- store.messages.push(i);
253
- }
254
- }
255
- else {
256
- task_store[tool_call_id] = {
257
- messages: [i],
258
- };
259
- }
260
- });
261
- const ignoreIds = new Set();
262
- Object.values(task_store).forEach((task) => {
263
- task.messages.forEach((message) => {
264
- ignoreIds.add(message.id);
265
- });
266
- });
267
- const result = [];
268
- for (const message of messages) {
269
- if (message.type === "tool" && message.tool_call_id) {
270
- const task = task_store[message.tool_call_id];
271
- if (task) {
272
- message.sub_agent_messages = this.attachInfoForMessage(this.composeToolMessages(task.messages));
273
- }
274
- }
275
- if (message.id && ignoreIds.has(message.id))
276
- continue;
277
- result.push(message);
278
- }
279
- return result;
280
- }
281
- /**
282
- * @zh 为消息附加额外的信息,如耗时、唯一 ID 等。
283
- * @en Attaches additional information to messages, such as spend time, unique ID, etc.
284
- */
285
- attachInfoForMessage(result) {
286
- var _a, _b, _c;
287
- let lastMessage = null;
288
- for (const message of result) {
289
- const createTime = ((_a = message.response_metadata) === null || _a === void 0 ? void 0 : _a.create_time) || "";
290
- // 工具必须要使用 tool_call_id 来保证一致性
291
- message.unique_id = message.tool_call_id || message.id;
292
- message.spend_time = new Date(createTime).getTime() - new Date(((_b = lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.response_metadata) === null || _b === void 0 ? void 0 : _b.create_time) || createTime).getTime();
293
- if (!message.usage_metadata && ((_c = message.response_metadata) === null || _c === void 0 ? void 0 : _c.usage)) {
294
- const usage = message.response_metadata.usage;
295
- message.usage_metadata = {
296
- ...usage,
297
- input_tokens: usage.prompt_tokens,
298
- output_tokens: usage.completion_tokens,
299
- total_tokens: usage.total_tokens,
300
- };
301
- }
302
- lastMessage = message;
303
- }
304
- return result;
305
- }
306
- /**
307
- * @zh 组合工具消息,将 AI 的工具调用和工具的执行结果关联起来。
308
- * @en Composes tool messages, associating AI tool calls with tool execution results.
309
- */
310
- composeToolMessages(messages) {
311
- var _a, _b;
312
- const result = [];
313
- const assistantToolMessages = new Map();
314
- const toolParentMessage = new Map();
315
- for (const message of messages) {
316
- if (StreamingMessageType.isToolAssistant(message)) {
317
- /** @ts-ignore 只有 tool_call_chunks 的 args 才是文本 */
318
- (_a = (message.tool_calls || message.tool_call_chunks)) === null || _a === void 0 ? void 0 : _a.forEach((element) => {
319
- assistantToolMessages.set(element.id, element);
320
- toolParentMessage.set(element.id, message);
321
- });
322
- if (!message.content)
323
- continue;
324
- }
325
- if (StreamingMessageType.isTool(message) && !message.tool_input) {
326
- const assistantToolMessage = assistantToolMessages.get(message.tool_call_id);
327
- const parentMessage = toolParentMessage.get(message.tool_call_id);
328
- if (assistantToolMessage) {
329
- message.tool_input = typeof assistantToolMessage.args !== "string" ? JSON.stringify(assistantToolMessage.args) : assistantToolMessage.args;
330
- if (message.additional_kwargs) {
331
- message.additional_kwargs.done = true;
332
- message.done = true;
333
- }
334
- else {
335
- message.done = true;
336
- message.additional_kwargs = {
337
- done: true,
338
- };
339
- }
340
- }
341
- if (parentMessage) {
342
- message.usage_metadata = parentMessage.usage_metadata;
343
- message.node_name = parentMessage.name;
344
- // 修补特殊情况下,tool name 丢失的问题
345
- if (!message.name) {
346
- message.name = (_b = parentMessage.tool_calls.find((i) => i.id === message.tool_call_id)) === null || _b === void 0 ? void 0 : _b.name;
347
- }
348
- }
349
- }
350
- result.push(message);
351
- }
352
- return result;
141
+ return this.messageProcessor.renderMessages(this.graphState, () => this.getGraphNodeNow());
353
142
  }
354
143
  /**
355
144
  * @zh 获取 Token 计数器信息。
356
145
  * @en Gets the Token counter information.
357
146
  */
358
147
  get tokenCounter() {
359
- return this.graphMessages.reduce((acc, message) => {
148
+ return this.messageProcessor.getGraphMessages().reduce((acc, message) => {
360
149
  var _a, _b, _c, _d, _e;
361
150
  if (message.usage_metadata) {
362
151
  acc.total_tokens += ((_a = message.usage_metadata) === null || _a === void 0 ? void 0 : _a.total_tokens) || 0;
@@ -376,19 +165,6 @@ export class LangGraphClient extends Client {
376
165
  output_tokens: 0,
377
166
  });
378
167
  }
379
- /**
380
- * @zh 注册流式更新的回调函数。
381
- * @en Registers a callback function for streaming updates.
382
- */
383
- onStreamingUpdate(callback) {
384
- this.streamingCallbacks.add(callback);
385
- return () => {
386
- this.streamingCallbacks.delete(callback);
387
- };
388
- }
389
- emitStreamingUpdate(event) {
390
- this.streamingCallbacks.forEach((callback) => callback(event));
391
- }
392
168
  /** 前端工具人机交互时,锁住面板 */
393
169
  isFELocking(messages) {
394
170
  var _a;
@@ -420,13 +196,10 @@ export class LangGraphClient extends Client {
420
196
  }
421
197
  if (!this.currentThread) {
422
198
  await this.createThread();
423
- this.emitStreamingUpdate({
424
- type: "thread",
199
+ this.emit("thread", {
200
+ event: "thread/create",
425
201
  data: {
426
- event: "thread/create",
427
- data: {
428
- thread: this.currentThread,
429
- },
202
+ thread: this.currentThread,
430
203
  },
431
204
  });
432
205
  }
@@ -460,11 +233,8 @@ export class LangGraphClient extends Client {
460
233
  };
461
234
  const streamResponse = await createStreamResponse();
462
235
  const streamRecord = [];
463
- this.emitStreamingUpdate({
464
- type: "start",
465
- data: {
466
- event: "start",
467
- },
236
+ this.emit("start", {
237
+ event: "start",
468
238
  });
469
239
  for await (const chunk of streamResponse) {
470
240
  streamRecord.push(chunk);
@@ -472,33 +242,24 @@ export class LangGraphClient extends Client {
472
242
  this.currentRun = chunk.data;
473
243
  }
474
244
  else if (chunk.event === "error") {
475
- this.emitStreamingUpdate({
476
- type: "error",
477
- data: chunk,
478
- });
245
+ this.emit("error", chunk);
479
246
  }
480
247
  else if (chunk.event === "messages/partial") {
481
248
  for (const message of chunk.data) {
482
- this.updateStreamingMessage(message);
249
+ this.messageProcessor.updateStreamingMessage(message);
483
250
  }
484
- this.emitStreamingUpdate({
485
- type: "message",
486
- data: chunk,
487
- });
251
+ this.emit("message", chunk);
488
252
  continue;
489
253
  }
490
254
  else if (chunk.event === "values") {
491
255
  const data = chunk.data;
492
256
  if (data.messages) {
493
257
  const isResume = !!(command === null || command === void 0 ? void 0 : command.resume);
494
- const isLongerThanLocal = data.messages.length >= this.graphMessages.length;
258
+ const isLongerThanLocal = data.messages.length >= this.messageProcessor.getGraphMessages().length;
495
259
  // resume 情况下,长度低于前端 message 的统统不接受
496
260
  if (!isResume || (isResume && isLongerThanLocal)) {
497
- this.graphMessages = data.messages;
498
- this.emitStreamingUpdate({
499
- type: "value",
500
- data: chunk,
501
- });
261
+ this.messageProcessor.setGraphMessages(data.messages);
262
+ this.emit("value", chunk);
502
263
  }
503
264
  this.graphState = chunk.data;
504
265
  }
@@ -507,7 +268,7 @@ export class LangGraphClient extends Client {
507
268
  else if (chunk.event.startsWith("values|")) {
508
269
  // 这个 values 必然是子 values
509
270
  if ((_a = chunk.data) === null || _a === void 0 ? void 0 : _a.messages) {
510
- this.mergeSubGraphMessagesToStreamingMessages(chunk.data.messages);
271
+ this.messageProcessor.mergeSubGraphMessagesToStreamingMessages(chunk.data.messages);
511
272
  }
512
273
  this.graphPosition = chunk.event.split("|")[1];
513
274
  }
@@ -515,13 +276,10 @@ export class LangGraphClient extends Client {
515
276
  const data = await this.runFETool();
516
277
  if (data)
517
278
  streamRecord.push(...data);
518
- this.emitStreamingUpdate({
519
- type: "done",
520
- data: {
521
- event: "done",
522
- },
279
+ this.emit("done", {
280
+ event: "done",
523
281
  });
524
- this.streamingMessage = [];
282
+ this.messageProcessor.clearStreamingMessages();
525
283
  return streamRecord;
526
284
  }
527
285
  getGraphPosition() {
@@ -537,26 +295,9 @@ export class LangGraphClient extends Client {
537
295
  const position = this.getGraphPosition();
538
296
  return position[position.length - 1];
539
297
  }
540
- /** 子图的数据需要通过 merge 的方式重新进行合并更新 */
541
- mergeSubGraphMessagesToStreamingMessages(messages) {
542
- const map = new Map(messages.filter((i) => i.id).map((i) => [i.id, i]));
543
- this.streamingMessage.forEach((i) => {
544
- if (map.has(i.id)) {
545
- const newValue = map.get(i.id);
546
- Object.assign(i, newValue);
547
- map.delete(i.id);
548
- }
549
- });
550
- // 剩余的 message 一定不在 streamMessage 中
551
- map.forEach((i) => {
552
- if (i.type === "tool" && i.tool_call_id) {
553
- this.streamingMessage.push(i);
554
- }
555
- });
556
- }
557
298
  runFETool() {
558
299
  var _a;
559
- const data = this.streamingMessage; // 需要保证不被清理
300
+ const data = this.messageProcessor.getStreamingMessages(); // 需要保证不被清理
560
301
  const lastMessage = data[data.length - 1];
561
302
  if (!lastMessage)
562
303
  return;
@@ -632,14 +373,14 @@ export class LangGraphClient extends Client {
632
373
  await this.initAssistant((_a = this.currentAssistant) === null || _a === void 0 ? void 0 : _a.graph_id);
633
374
  this.currentThread = null;
634
375
  this.graphState = {};
635
- this.graphMessages = [];
636
- this.streamingMessage = [];
376
+ this.messageProcessor.setGraphMessages([]);
377
+ this.messageProcessor.clearStreamingMessages();
637
378
  this.currentRun = undefined;
638
379
  this.tools.clearWaiting();
639
- this.emitStreamingUpdate({
640
- type: "value",
380
+ this.emit("value", {
381
+ event: "messages/partial",
641
382
  data: {
642
- event: "messages/partial",
383
+ messages: [],
643
384
  },
644
385
  });
645
386
  }
@@ -0,0 +1,94 @@
1
+ import { Message, AIMessage, ToolMessage } from "@langchain/langgraph-sdk";
2
+ import { RenderMessage } from "./LangGraphClient.js";
3
+ /**
4
+ * @zh StreamingMessageType 类用于判断消息的类型。
5
+ * @en The StreamingMessageType class is used to determine the type of a message.
6
+ */
7
+ export declare class StreamingMessageType {
8
+ static isTool(m: Message): m is ToolMessage;
9
+ static isToolAssistant(m: Message): m is AIMessage;
10
+ }
11
+ /**
12
+ * @zh MessageProcessor 类用于统一处理 Message 相关的逻辑,避免重复处理。
13
+ * @en The MessageProcessor class is used to uniformly handle Message-related logic and avoid duplicate processing.
14
+ */
15
+ export declare class MessageProcessor {
16
+ private subAgentsKey;
17
+ /** 流式消息缓存 */
18
+ private streamingMessage;
19
+ /** 图发过来的更新信息 */
20
+ private graphMessages;
21
+ constructor(subAgentsKey?: string);
22
+ /**
23
+ * @zh 获取流式消息
24
+ * @en Get streaming messages
25
+ */
26
+ getStreamingMessages(): RenderMessage[];
27
+ /**
28
+ * @zh 设置流式消息
29
+ * @en Set streaming messages
30
+ */
31
+ setStreamingMessages(messages: RenderMessage[]): void;
32
+ /**
33
+ * @zh 清空流式消息
34
+ * @en Clear streaming messages
35
+ */
36
+ clearStreamingMessages(): void;
37
+ /**
38
+ * @zh 获取图消息
39
+ * @en Get graph messages
40
+ */
41
+ getGraphMessages(): RenderMessage[];
42
+ /**
43
+ * @zh 设置图消息
44
+ * @en Set graph messages
45
+ */
46
+ setGraphMessages(messages: RenderMessage[]): void;
47
+ /**
48
+ * @zh 更新流式消息
49
+ * @en Update streaming message
50
+ */
51
+ updateStreamingMessage(message: RenderMessage): void;
52
+ /**
53
+ * @zh 将 graphMessages 和 streamingMessage 合并,并返回新的消息数组
54
+ * @en Combine graphMessages and streamingMessage and return a new message array
55
+ */
56
+ combineGraphMessagesWithStreamingMessages(): RenderMessage[];
57
+ /**
58
+ * @zh 子图的数据需要通过 merge 的方式重新进行合并更新
59
+ * @en Subgraph data needs to be merged and updated through merge method
60
+ */
61
+ mergeSubGraphMessagesToStreamingMessages(messages: Message[]): void;
62
+ /**
63
+ * @zh 克隆消息对象
64
+ * @en Clone message object
65
+ */
66
+ cloneMessage(message: Message): Message;
67
+ /**
68
+ * @zh 为消息附加额外的信息,如耗时、唯一 ID 等。
69
+ * @en Attaches additional information to messages, such as spend time, unique ID, etc.
70
+ */
71
+ attachInfoForMessage(messages: RenderMessage[]): RenderMessage[];
72
+ /**
73
+ * @zh 组合工具消息,将 AI 的工具调用和工具的执行结果关联起来。
74
+ * @en Composes tool messages, associating AI tool calls with tool execution results.
75
+ */
76
+ composeToolMessages(messages: RenderMessage[]): RenderMessage[];
77
+ /**
78
+ * @zh 转换 subAgent 消息为工具的子消息
79
+ * @en Convert subAgent messages to tool sub-messages
80
+ */
81
+ convertSubAgentMessages(messages: RenderMessage[], graphState: any): RenderMessage[];
82
+ /**
83
+ * @zh 生成用于 UI 中的流式渲染的消息
84
+ * @en Generate messages used for streaming rendering in the UI
85
+ */
86
+ renderMessages(graphState: any, getGraphNodeNow: () => {
87
+ name: string;
88
+ }): RenderMessage[];
89
+ /**
90
+ * @zh 统一的消息处理入口,按顺序执行所有处理步骤
91
+ * @en Unified message processing entry point, executing all processing steps in order
92
+ */
93
+ processMessages(messages: RenderMessage[], graphState?: any): RenderMessage[];
94
+ }