@langgraph-js/sdk 3.8.3 → 4.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.
- package/dist/LangGraphClient.d.ts +9 -15
- package/dist/LangGraphClient.js +51 -6
- package/dist/ToolManager.d.ts +3 -2
- package/dist/ToolManager.js +6 -3
- package/dist/humanInTheLoop.d.ts +38 -0
- package/dist/humanInTheLoop.js +4 -0
- package/dist/tool/ToolUI.d.ts +8 -3
- package/dist/tool/ToolUI.js +25 -6
- package/dist/tool/createTool.d.ts +3 -19
- package/package.json +1 -1
- package/src/LangGraphClient.ts +60 -25
- package/src/ToolManager.ts +6 -3
- package/src/humanInTheLoop.ts +40 -0
- package/src/tool/ToolUI.ts +27 -8
- package/src/tool/createTool.ts +4 -18
|
@@ -2,6 +2,7 @@ import type { Thread, Message, Assistant, Command } from "@langchain/langgraph-s
|
|
|
2
2
|
import { EventEmitter } from "eventemitter3";
|
|
3
3
|
import { ToolManager } from "./ToolManager.js";
|
|
4
4
|
import { CallToolResult } from "./tool/createTool.js";
|
|
5
|
+
import { HumanInTheLoopDecision, HumanInTheLoopState } from "./humanInTheLoop.js";
|
|
5
6
|
import { type ILangGraphClient } from "@langgraph-js/pure-graph/dist/types.js";
|
|
6
7
|
import { RevertChatToOptions } from "./time-travel/index.js";
|
|
7
8
|
export type RenderMessage = Message & {
|
|
@@ -44,20 +45,6 @@ export type SendMessageOptions = {
|
|
|
44
45
|
command?: Command;
|
|
45
46
|
joinRunId?: string;
|
|
46
47
|
};
|
|
47
|
-
export type InterruptData = {
|
|
48
|
-
id: string;
|
|
49
|
-
value: {
|
|
50
|
-
actionRequests: {
|
|
51
|
-
name: string;
|
|
52
|
-
description: string;
|
|
53
|
-
args: any;
|
|
54
|
-
}[];
|
|
55
|
-
reviewConfigs: {
|
|
56
|
-
actionName: string;
|
|
57
|
-
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
|
|
58
|
-
}[];
|
|
59
|
-
};
|
|
60
|
-
}[];
|
|
61
48
|
export interface LangGraphClientConfig {
|
|
62
49
|
apiUrl?: string;
|
|
63
50
|
apiKey?: string;
|
|
@@ -244,7 +231,7 @@ export declare class LangGraphClient<TStateType = unknown> extends EventEmitter<
|
|
|
244
231
|
messages: Message[];
|
|
245
232
|
}>;
|
|
246
233
|
messagesMetadata: {};
|
|
247
|
-
humanInTheLoop:
|
|
234
|
+
humanInTheLoop: HumanInTheLoopState | null;
|
|
248
235
|
/**
|
|
249
236
|
* @zh 发送消息到 LangGraph 后端。
|
|
250
237
|
* @en Sends a message to the LangGraph backend.
|
|
@@ -267,6 +254,7 @@ export declare class LangGraphClient<TStateType = unknown> extends EventEmitter<
|
|
|
267
254
|
*/
|
|
268
255
|
private processStreamChunk;
|
|
269
256
|
private runFETool;
|
|
257
|
+
private responseHumanInTheLoop;
|
|
270
258
|
private callFETool;
|
|
271
259
|
extraParams: Record<string, any>;
|
|
272
260
|
/**
|
|
@@ -277,8 +265,14 @@ export declare class LangGraphClient<TStateType = unknown> extends EventEmitter<
|
|
|
277
265
|
/**
|
|
278
266
|
* @zh 标记前端工具等待已完成。
|
|
279
267
|
* @en Marks the frontend tool waiting as completed.
|
|
268
|
+
* @deprecated 请使用 doneHumanInTheLoopWaiting 和规范的 humanInTheLoop 协议定义状态
|
|
280
269
|
*/
|
|
281
270
|
doneFEToolWaiting(id: string, result: CallToolResult): void;
|
|
271
|
+
/**
|
|
272
|
+
* @zh 标记人机交互等待已完成。
|
|
273
|
+
* @en Marks the human in the loop waiting as completed.
|
|
274
|
+
*/
|
|
275
|
+
doneHumanInTheLoopWaiting(tool_id: string, action_request_id: string, result: HumanInTheLoopDecision): void;
|
|
282
276
|
/**
|
|
283
277
|
* @zh 获取当前的 Thread。
|
|
284
278
|
* @en Gets the current Thread.
|
package/dist/LangGraphClient.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from "eventemitter3";
|
|
2
2
|
import { ToolManager } from "./ToolManager.js";
|
|
3
|
+
import { createActionRequestID } from "./humanInTheLoop.js";
|
|
3
4
|
import { MessageProcessor } from "./MessageProcessor.js";
|
|
4
5
|
import { revertChatTo } from "./time-travel/index.js";
|
|
5
6
|
import camelcaseKeys from "camelcase-keys";
|
|
@@ -281,9 +282,9 @@ export class LangGraphClient extends EventEmitter {
|
|
|
281
282
|
}
|
|
282
283
|
}
|
|
283
284
|
const data = await this.runFETool();
|
|
285
|
+
await this.responseHumanInTheLoop();
|
|
284
286
|
if (data)
|
|
285
287
|
streamRecord.push(...data);
|
|
286
|
-
this.humanInTheLoop = null;
|
|
287
288
|
this._status = "idle";
|
|
288
289
|
this.emit("done", {
|
|
289
290
|
event: "done",
|
|
@@ -332,13 +333,34 @@ export class LangGraphClient extends EventEmitter {
|
|
|
332
333
|
}
|
|
333
334
|
else if (chunk.event === "values") {
|
|
334
335
|
const data = chunk.data;
|
|
335
|
-
if (data
|
|
336
|
+
if (data?.__interrupt__) {
|
|
336
337
|
this._status = "interrupted";
|
|
337
|
-
|
|
338
|
-
|
|
338
|
+
const humanInTheLoop = data?.__interrupt__.map((i) => {
|
|
339
|
+
// 修复 python 版本是以下划线命名的,而 js 版本是以驼峰命名的
|
|
340
|
+
if (i && "review_configs" in i.value) {
|
|
341
|
+
return {
|
|
342
|
+
id: i.id,
|
|
343
|
+
value: {
|
|
344
|
+
/** @ts-ignore */
|
|
345
|
+
actionRequests: i.value.action_requests,
|
|
346
|
+
reviewConfigs: camelcaseKeys(i.value.review_configs, { deep: true }),
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
return i;
|
|
351
|
+
});
|
|
352
|
+
humanInTheLoop.forEach((i) => {
|
|
353
|
+
i.value.actionRequests.forEach((j) => {
|
|
354
|
+
j.id = j.id || createActionRequestID(j);
|
|
355
|
+
});
|
|
356
|
+
return i;
|
|
339
357
|
});
|
|
358
|
+
this.humanInTheLoop = {
|
|
359
|
+
interruptData: humanInTheLoop,
|
|
360
|
+
result: {},
|
|
361
|
+
};
|
|
340
362
|
}
|
|
341
|
-
else if (data
|
|
363
|
+
else if (data?.messages) {
|
|
342
364
|
const isResume = !!command?.resume;
|
|
343
365
|
const isLongerThanLocal = data.messages.length >= this.messageProcessor.getGraphMessages().length;
|
|
344
366
|
// resume 情况下,长度低于前端 message 的统统不接受
|
|
@@ -375,16 +397,27 @@ export class LangGraphClient extends EventEmitter {
|
|
|
375
397
|
});
|
|
376
398
|
this._status = "interrupted";
|
|
377
399
|
this.currentThread.status = "interrupted"; // 修复某些机制下,状态不为 interrupted 与后端有差异
|
|
400
|
+
console.log("batch call tools", result.length);
|
|
378
401
|
return Promise.all(result);
|
|
379
402
|
}
|
|
380
403
|
}
|
|
404
|
+
async responseHumanInTheLoop() {
|
|
405
|
+
if (this.humanInTheLoop) {
|
|
406
|
+
const humanInTheLoop = this.humanInTheLoop;
|
|
407
|
+
this.humanInTheLoop = null;
|
|
408
|
+
return this.resume({
|
|
409
|
+
decisions: humanInTheLoop.interruptData[0].value.actionRequests.map((i) => {
|
|
410
|
+
return humanInTheLoop.result[i.id];
|
|
411
|
+
}),
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
381
415
|
async callFETool(message, args) {
|
|
382
416
|
const that = this; // 防止 this 被错误解析
|
|
383
417
|
const result = await this.tools.callTool(message.name, args, { client: that, message });
|
|
384
418
|
if (!result) {
|
|
385
419
|
return;
|
|
386
420
|
}
|
|
387
|
-
return this.resume(result);
|
|
388
421
|
}
|
|
389
422
|
extraParams = {};
|
|
390
423
|
/**
|
|
@@ -401,6 +434,7 @@ export class LangGraphClient extends EventEmitter {
|
|
|
401
434
|
/**
|
|
402
435
|
* @zh 标记前端工具等待已完成。
|
|
403
436
|
* @en Marks the frontend tool waiting as completed.
|
|
437
|
+
* @deprecated 请使用 doneHumanInTheLoopWaiting 和规范的 humanInTheLoop 协议定义状态
|
|
404
438
|
*/
|
|
405
439
|
doneFEToolWaiting(id, result) {
|
|
406
440
|
const done = this.tools.doneWaiting(id, result);
|
|
@@ -408,6 +442,17 @@ export class LangGraphClient extends EventEmitter {
|
|
|
408
442
|
this.resume(result);
|
|
409
443
|
}
|
|
410
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* @zh 标记人机交互等待已完成。
|
|
447
|
+
* @en Marks the human in the loop waiting as completed.
|
|
448
|
+
*/
|
|
449
|
+
doneHumanInTheLoopWaiting(tool_id, action_request_id, result) {
|
|
450
|
+
// 移除等待状态
|
|
451
|
+
this.tools.doneWaiting(tool_id, result);
|
|
452
|
+
if (this.humanInTheLoop) {
|
|
453
|
+
this.humanInTheLoop.result[action_request_id] = result;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
411
456
|
/**
|
|
412
457
|
* @zh 获取当前的 Thread。
|
|
413
458
|
* @en Gets the current Thread.
|
package/dist/ToolManager.d.ts
CHANGED
|
@@ -62,11 +62,12 @@ export declare class ToolManager {
|
|
|
62
62
|
* @en Marks the tool waiting with the specified ID as completed and passes the result.
|
|
63
63
|
*/
|
|
64
64
|
doneWaiting(id: string, value: CallToolResult): boolean;
|
|
65
|
+
isAllToolCompleted(): number;
|
|
65
66
|
/**
|
|
66
67
|
* @zh 等待指定 ID 的工具完成。
|
|
67
68
|
* @en Waits for the tool with the specified ID to complete.
|
|
68
69
|
*/
|
|
69
|
-
waitForDone(id: string): Promise<unknown
|
|
70
|
+
waitForDone(id: string): Promise<unknown>;
|
|
70
71
|
/**
|
|
71
72
|
* @zh 一个静态方法,用于在前端等待用户界面操作完成。
|
|
72
73
|
* @en A static method used in the frontend to wait for user interface operations to complete.
|
|
@@ -74,5 +75,5 @@ export declare class ToolManager {
|
|
|
74
75
|
static waitForUIDone<T>(_: T, context: {
|
|
75
76
|
client: LangGraphClient;
|
|
76
77
|
message: ToolMessage;
|
|
77
|
-
}): Promise<unknown
|
|
78
|
+
}): Promise<unknown>;
|
|
78
79
|
}
|
package/dist/ToolManager.js
CHANGED
|
@@ -92,14 +92,17 @@ export class ToolManager {
|
|
|
92
92
|
return false;
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
+
isAllToolCompleted() {
|
|
96
|
+
return this.waitingMap.size;
|
|
97
|
+
}
|
|
95
98
|
/**
|
|
96
99
|
* @zh 等待指定 ID 的工具完成。
|
|
97
100
|
* @en Waits for the tool with the specified ID to complete.
|
|
98
101
|
*/
|
|
99
102
|
waitForDone(id) {
|
|
100
|
-
if (this.waitingMap.has(id)) {
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
+
// if (this.waitingMap.has(id)) {
|
|
104
|
+
// return this.waitingMap.get(id);
|
|
105
|
+
// }
|
|
103
106
|
const promise = new Promise((resolve, reject) => {
|
|
104
107
|
this.waitingMap.set(id, resolve);
|
|
105
108
|
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type HumanInTheLoopDecision = {
|
|
2
|
+
type: "approve" | "edit" | "reject" | "respond";
|
|
3
|
+
edited_action?: {
|
|
4
|
+
name: string;
|
|
5
|
+
args: Record<string, any>;
|
|
6
|
+
};
|
|
7
|
+
message?: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* HumanInTheLoop 的标准回复格式
|
|
11
|
+
*/
|
|
12
|
+
export type InterruptResponse = {
|
|
13
|
+
decisions: HumanInTheLoopDecision[];
|
|
14
|
+
};
|
|
15
|
+
/** 由于 langchain human in the loop 没有设计调用 id,所以我们需要给一个 id */
|
|
16
|
+
export declare const createActionRequestID: (j: {
|
|
17
|
+
name: string;
|
|
18
|
+
args: any;
|
|
19
|
+
}) => string;
|
|
20
|
+
export type HumanInTheLoopState = {
|
|
21
|
+
interruptData: InterruptData;
|
|
22
|
+
result: Record<string, HumanInTheLoopDecision>;
|
|
23
|
+
};
|
|
24
|
+
export type InterruptData = {
|
|
25
|
+
id: string;
|
|
26
|
+
value: {
|
|
27
|
+
actionRequests: {
|
|
28
|
+
id?: string;
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
args: any;
|
|
32
|
+
}[];
|
|
33
|
+
reviewConfigs: {
|
|
34
|
+
actionName: string;
|
|
35
|
+
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
|
|
36
|
+
}[];
|
|
37
|
+
};
|
|
38
|
+
}[];
|
package/dist/tool/ToolUI.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RenderMessage } from "../LangGraphClient.js";
|
|
2
2
|
import { LangGraphClient } from "../LangGraphClient.js";
|
|
3
|
-
import {
|
|
3
|
+
import { HumanInTheLoopDecision } from "../humanInTheLoop.js";
|
|
4
4
|
export type DeepPartial<T> = {
|
|
5
5
|
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
|
6
6
|
};
|
|
@@ -8,16 +8,19 @@ export declare class ToolRenderData<I, D> {
|
|
|
8
8
|
message: RenderMessage;
|
|
9
9
|
client: LangGraphClient;
|
|
10
10
|
constructor(message: RenderMessage, client: LangGraphClient);
|
|
11
|
+
private getToolActionRequestID;
|
|
11
12
|
/**
|
|
12
13
|
* 获取人机交互数据
|
|
13
14
|
* 直接使用 reviewConfig 获取可以显示的按钮
|
|
14
15
|
* actionRequest 获取当前工具的入参
|
|
15
16
|
*/
|
|
16
17
|
getHumanInTheLoopData(): {
|
|
18
|
+
actionRequestIndex: number;
|
|
17
19
|
config: {
|
|
18
20
|
id: string;
|
|
19
21
|
value: {
|
|
20
22
|
actionRequests: {
|
|
23
|
+
id?: string;
|
|
21
24
|
name: string;
|
|
22
25
|
description: string;
|
|
23
26
|
args: any;
|
|
@@ -33,14 +36,16 @@ export declare class ToolRenderData<I, D> {
|
|
|
33
36
|
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
|
|
34
37
|
};
|
|
35
38
|
actionRequest: {
|
|
39
|
+
id?: string;
|
|
36
40
|
name: string;
|
|
37
41
|
description: string;
|
|
38
42
|
args: any;
|
|
39
43
|
};
|
|
44
|
+
result: HumanInTheLoopDecision;
|
|
40
45
|
} | null;
|
|
41
46
|
/** 发送恢复状态的数据 */
|
|
42
|
-
sendResumeData(response:
|
|
43
|
-
get state(): "idle" | "
|
|
47
|
+
sendResumeData(response: HumanInTheLoopDecision): void;
|
|
48
|
+
get state(): "idle" | "done" | "loading";
|
|
44
49
|
get input(): I | null;
|
|
45
50
|
get output(): string;
|
|
46
51
|
getJSONOutput(): D;
|
package/dist/tool/ToolUI.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getMessageContent } from "../ui-store/createChatStore.js";
|
|
2
2
|
import { jsonrepair } from "jsonrepair";
|
|
3
|
+
import { createActionRequestID } from "../humanInTheLoop.js";
|
|
3
4
|
export class ToolRenderData {
|
|
4
5
|
message;
|
|
5
6
|
client;
|
|
@@ -7,19 +8,33 @@ export class ToolRenderData {
|
|
|
7
8
|
this.message = message;
|
|
8
9
|
this.client = client;
|
|
9
10
|
}
|
|
11
|
+
getToolActionRequestID() {
|
|
12
|
+
return createActionRequestID({
|
|
13
|
+
name: this.message.name,
|
|
14
|
+
args: this.getInputRepaired(),
|
|
15
|
+
});
|
|
16
|
+
}
|
|
10
17
|
/**
|
|
11
18
|
* 获取人机交互数据
|
|
12
19
|
* 直接使用 reviewConfig 获取可以显示的按钮
|
|
13
20
|
* actionRequest 获取当前工具的入参
|
|
14
21
|
*/
|
|
15
22
|
getHumanInTheLoopData() {
|
|
16
|
-
const
|
|
23
|
+
const toolActionRequestID = this.getToolActionRequestID();
|
|
24
|
+
if (!this.client.humanInTheLoop)
|
|
25
|
+
return null;
|
|
26
|
+
const configOfHumanInTheLoop = this.client.humanInTheLoop.interruptData.find((i) => i.value.actionRequests.some((j) => j.id === toolActionRequestID));
|
|
17
27
|
if (!configOfHumanInTheLoop)
|
|
18
28
|
return null;
|
|
29
|
+
const actionRequestIndex = configOfHumanInTheLoop.value.actionRequests.findIndex((j) => j.id === toolActionRequestID);
|
|
30
|
+
if (actionRequestIndex === -1)
|
|
31
|
+
return null;
|
|
19
32
|
return {
|
|
33
|
+
actionRequestIndex: actionRequestIndex,
|
|
20
34
|
config: configOfHumanInTheLoop,
|
|
21
|
-
reviewConfig: configOfHumanInTheLoop.value.reviewConfigs.find((j) => j.actionName ===
|
|
22
|
-
actionRequest: configOfHumanInTheLoop.value.actionRequests
|
|
35
|
+
reviewConfig: configOfHumanInTheLoop.value.reviewConfigs.find((j) => j.actionName === configOfHumanInTheLoop.value.actionRequests[actionRequestIndex].name),
|
|
36
|
+
actionRequest: configOfHumanInTheLoop.value.actionRequests[actionRequestIndex],
|
|
37
|
+
result: this.client.humanInTheLoop.result[toolActionRequestID],
|
|
23
38
|
};
|
|
24
39
|
}
|
|
25
40
|
/** 发送恢复状态的数据 */
|
|
@@ -28,14 +43,18 @@ export class ToolRenderData {
|
|
|
28
43
|
/**@ts-ignore 修复 sb 的 langchain 官方的命名不统一,我们一致采用下划线版本,而非驼峰版本 */
|
|
29
44
|
response.editedAction = response.edited_action;
|
|
30
45
|
}
|
|
31
|
-
return this.client.
|
|
46
|
+
return this.client.doneHumanInTheLoopWaiting(this.message.id, this.getToolActionRequestID(), response);
|
|
32
47
|
}
|
|
33
48
|
get state() {
|
|
34
49
|
if (this.message.type === "tool" && this.message?.additional_kwargs?.done) {
|
|
35
50
|
return "done";
|
|
36
51
|
}
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
const humanInTheLoopData = this.getHumanInTheLoopData();
|
|
53
|
+
if (humanInTheLoopData?.result) {
|
|
54
|
+
return "done";
|
|
55
|
+
}
|
|
56
|
+
if (this.client.status === "interrupted" && humanInTheLoopData?.actionRequest) {
|
|
57
|
+
return "done";
|
|
39
58
|
}
|
|
40
59
|
if (this.message.tool_input) {
|
|
41
60
|
return "loading";
|
|
@@ -2,6 +2,7 @@ import { z, ZodRawShape } from "zod";
|
|
|
2
2
|
import { Action, Parameter } from "./copilotkit-actions.js";
|
|
3
3
|
import { Message } from "@langchain/langgraph-sdk";
|
|
4
4
|
import { ToolRenderData } from "./ToolUI.js";
|
|
5
|
+
import { HumanInTheLoopDecision, InterruptResponse as HumanInTheLoopResponse } from "../humanInTheLoop.js";
|
|
5
6
|
export interface UnionTool<Args extends ZodRawShape, Child extends Object = Object, ResponseType = any> {
|
|
6
7
|
name: string;
|
|
7
8
|
description: string;
|
|
@@ -22,27 +23,10 @@ export interface UnionTool<Args extends ZodRawShape, Child extends Object = Obje
|
|
|
22
23
|
isPureParams?: boolean;
|
|
23
24
|
}
|
|
24
25
|
export type ToolCallback<Args extends ZodRawShape> = (args: z.infer<z.ZodObject<Args>>, context?: any) => CallToolResult | Promise<CallToolResult>;
|
|
25
|
-
/**
|
|
26
|
-
* HumanInTheLoop 的标准回复格式
|
|
27
|
-
*/
|
|
28
|
-
export type InterruptResponse = {
|
|
29
|
-
decisions: ({
|
|
30
|
-
type: "approve";
|
|
31
|
-
} | {
|
|
32
|
-
type: "edit";
|
|
33
|
-
edited_action: {
|
|
34
|
-
name: string;
|
|
35
|
-
args: Record<string, any>;
|
|
36
|
-
};
|
|
37
|
-
} | {
|
|
38
|
-
type: "reject";
|
|
39
|
-
message?: string;
|
|
40
|
-
})[];
|
|
41
|
-
};
|
|
42
26
|
export type CallToolResult = string | {
|
|
43
27
|
type: "text";
|
|
44
28
|
text: string;
|
|
45
|
-
}[] |
|
|
29
|
+
}[] | HumanInTheLoopDecision | HumanInTheLoopResponse;
|
|
46
30
|
/** 用于格式校验 */
|
|
47
31
|
export declare const createTool: <Args extends ZodRawShape>(tool: UnionTool<Args>) => UnionTool<Args, Object, any>;
|
|
48
32
|
/**
|
|
@@ -173,7 +157,7 @@ export declare const createMCPTool: <Args extends ZodRawShape>(tool: UnionTool<A
|
|
|
173
157
|
}[];
|
|
174
158
|
isError?: undefined;
|
|
175
159
|
} | {
|
|
176
|
-
content:
|
|
160
|
+
content: HumanInTheLoopDecision | HumanInTheLoopResponse | {
|
|
177
161
|
type: "text";
|
|
178
162
|
text: string;
|
|
179
163
|
}[] | undefined;
|
package/package.json
CHANGED
package/src/LangGraphClient.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Thread, Message, Assistant, HumanMessage, AIMessage, ToolMessage,
|
|
|
2
2
|
import { EventEmitter } from "eventemitter3";
|
|
3
3
|
import { ToolManager } from "./ToolManager.js";
|
|
4
4
|
import { CallToolResult } from "./tool/createTool.js";
|
|
5
|
+
import { createActionRequestID, HumanInTheLoopDecision, HumanInTheLoopState, InterruptData } from "./humanInTheLoop.js";
|
|
5
6
|
import { type ILangGraphClient } from "@langgraph-js/pure-graph/dist/types.js";
|
|
6
7
|
import { MessageProcessor } from "./MessageProcessor.js";
|
|
7
8
|
import { revertChatTo, RevertChatToOptions } from "./time-travel/index.js";
|
|
@@ -45,20 +46,7 @@ export type SendMessageOptions = {
|
|
|
45
46
|
command?: Command;
|
|
46
47
|
joinRunId?: string;
|
|
47
48
|
};
|
|
48
|
-
|
|
49
|
-
id: string;
|
|
50
|
-
value: {
|
|
51
|
-
actionRequests: {
|
|
52
|
-
name: string;
|
|
53
|
-
description: string;
|
|
54
|
-
args: any;
|
|
55
|
-
}[];
|
|
56
|
-
reviewConfigs: {
|
|
57
|
-
actionName: string;
|
|
58
|
-
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
|
|
59
|
-
}[];
|
|
60
|
-
};
|
|
61
|
-
}[];
|
|
49
|
+
|
|
62
50
|
export interface LangGraphClientConfig {
|
|
63
51
|
apiUrl?: string;
|
|
64
52
|
apiKey?: string;
|
|
@@ -337,7 +325,7 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
|
|
|
337
325
|
return state;
|
|
338
326
|
}
|
|
339
327
|
public messagesMetadata = {};
|
|
340
|
-
public humanInTheLoop:
|
|
328
|
+
public humanInTheLoop: HumanInTheLoopState | null = null;
|
|
341
329
|
/**
|
|
342
330
|
* @zh 发送消息到 LangGraph 后端。
|
|
343
331
|
* @en Sends a message to the LangGraph backend.
|
|
@@ -411,8 +399,8 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
|
|
|
411
399
|
}
|
|
412
400
|
}
|
|
413
401
|
const data = await this.runFETool();
|
|
402
|
+
await this.responseHumanInTheLoop();
|
|
414
403
|
if (data) streamRecord.push(...data);
|
|
415
|
-
this.humanInTheLoop = null;
|
|
416
404
|
this._status = "idle";
|
|
417
405
|
this.emit("done", {
|
|
418
406
|
event: "done",
|
|
@@ -457,17 +445,40 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
|
|
|
457
445
|
this.emit("message", chunk);
|
|
458
446
|
return true;
|
|
459
447
|
} else if (chunk.event === "values") {
|
|
460
|
-
const data = chunk.data as
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
448
|
+
const data = chunk.data as
|
|
449
|
+
| {
|
|
450
|
+
__interrupt__?: InterruptData;
|
|
451
|
+
messages: Message[];
|
|
452
|
+
}
|
|
453
|
+
| undefined;
|
|
464
454
|
|
|
465
|
-
if (data
|
|
455
|
+
if (data?.__interrupt__) {
|
|
466
456
|
this._status = "interrupted";
|
|
467
|
-
|
|
468
|
-
|
|
457
|
+
const humanInTheLoop = data?.__interrupt__.map((i) => {
|
|
458
|
+
// 修复 python 版本是以下划线命名的,而 js 版本是以驼峰命名的
|
|
459
|
+
if (i && "review_configs" in i.value) {
|
|
460
|
+
return {
|
|
461
|
+
id: i.id,
|
|
462
|
+
value: {
|
|
463
|
+
/** @ts-ignore */
|
|
464
|
+
actionRequests: i.value.action_requests,
|
|
465
|
+
reviewConfigs: camelcaseKeys(i.value.review_configs as any[], { deep: true }),
|
|
466
|
+
},
|
|
467
|
+
} as InterruptData[number];
|
|
468
|
+
}
|
|
469
|
+
return i;
|
|
469
470
|
});
|
|
470
|
-
|
|
471
|
+
humanInTheLoop.forEach((i) => {
|
|
472
|
+
i.value.actionRequests.forEach((j) => {
|
|
473
|
+
j.id = j.id || createActionRequestID(j);
|
|
474
|
+
});
|
|
475
|
+
return i;
|
|
476
|
+
});
|
|
477
|
+
this.humanInTheLoop = {
|
|
478
|
+
interruptData: humanInTheLoop,
|
|
479
|
+
result: {},
|
|
480
|
+
};
|
|
481
|
+
} else if (data?.messages) {
|
|
471
482
|
const isResume = !!command?.resume;
|
|
472
483
|
const isLongerThanLocal = data.messages.length >= this.messageProcessor.getGraphMessages().length;
|
|
473
484
|
// resume 情况下,长度低于前端 message 的统统不接受
|
|
@@ -503,16 +514,27 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
|
|
|
503
514
|
});
|
|
504
515
|
this._status = "interrupted";
|
|
505
516
|
this.currentThread!.status = "interrupted"; // 修复某些机制下,状态不为 interrupted 与后端有差异
|
|
517
|
+
console.log("batch call tools", result.length);
|
|
506
518
|
return Promise.all(result);
|
|
507
519
|
}
|
|
508
520
|
}
|
|
521
|
+
private async responseHumanInTheLoop() {
|
|
522
|
+
if (this.humanInTheLoop) {
|
|
523
|
+
const humanInTheLoop = this.humanInTheLoop;
|
|
524
|
+
this.humanInTheLoop = null;
|
|
525
|
+
return this.resume({
|
|
526
|
+
decisions: humanInTheLoop.interruptData[0].value.actionRequests.map((i) => {
|
|
527
|
+
return humanInTheLoop.result[i.id!] as HumanInTheLoopDecision;
|
|
528
|
+
}),
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
509
532
|
private async callFETool(message: ToolMessage, args: any) {
|
|
510
533
|
const that = this; // 防止 this 被错误解析
|
|
511
534
|
const result = await this.tools.callTool(message.name!, args, { client: that, message });
|
|
512
535
|
if (!result) {
|
|
513
536
|
return;
|
|
514
537
|
}
|
|
515
|
-
return this.resume(result);
|
|
516
538
|
}
|
|
517
539
|
extraParams: Record<string, any> = {};
|
|
518
540
|
|
|
@@ -530,6 +552,7 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
|
|
|
530
552
|
/**
|
|
531
553
|
* @zh 标记前端工具等待已完成。
|
|
532
554
|
* @en Marks the frontend tool waiting as completed.
|
|
555
|
+
* @deprecated 请使用 doneHumanInTheLoopWaiting 和规范的 humanInTheLoop 协议定义状态
|
|
533
556
|
*/
|
|
534
557
|
doneFEToolWaiting(id: string, result: CallToolResult) {
|
|
535
558
|
const done = this.tools.doneWaiting(id, result);
|
|
@@ -538,6 +561,18 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
|
|
|
538
561
|
}
|
|
539
562
|
}
|
|
540
563
|
|
|
564
|
+
/**
|
|
565
|
+
* @zh 标记人机交互等待已完成。
|
|
566
|
+
* @en Marks the human in the loop waiting as completed.
|
|
567
|
+
*/
|
|
568
|
+
doneHumanInTheLoopWaiting(tool_id: string, action_request_id: string, result: HumanInTheLoopDecision) {
|
|
569
|
+
// 移除等待状态
|
|
570
|
+
this.tools.doneWaiting(tool_id, result);
|
|
571
|
+
if (this.humanInTheLoop) {
|
|
572
|
+
this.humanInTheLoop.result[action_request_id] = result;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
541
576
|
/**
|
|
542
577
|
* @zh 获取当前的 Thread。
|
|
543
578
|
* @en Gets the current Thread.
|
package/src/ToolManager.ts
CHANGED
|
@@ -102,15 +102,18 @@ export class ToolManager {
|
|
|
102
102
|
return false;
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
isAllToolCompleted() {
|
|
106
|
+
return this.waitingMap.size;
|
|
107
|
+
}
|
|
105
108
|
|
|
106
109
|
/**
|
|
107
110
|
* @zh 等待指定 ID 的工具完成。
|
|
108
111
|
* @en Waits for the tool with the specified ID to complete.
|
|
109
112
|
*/
|
|
110
113
|
waitForDone(id: string) {
|
|
111
|
-
if (this.waitingMap.has(id)) {
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
+
// if (this.waitingMap.has(id)) {
|
|
115
|
+
// return this.waitingMap.get(id);
|
|
116
|
+
// }
|
|
114
117
|
const promise = new Promise((resolve, reject) => {
|
|
115
118
|
this.waitingMap.set(id, resolve);
|
|
116
119
|
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type HumanInTheLoopDecision = {
|
|
2
|
+
type: "approve" | "edit" | "reject" | "respond";
|
|
3
|
+
edited_action?: {
|
|
4
|
+
name: string;
|
|
5
|
+
args: Record<string, any>;
|
|
6
|
+
};
|
|
7
|
+
message?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* HumanInTheLoop 的标准回复格式
|
|
12
|
+
*/
|
|
13
|
+
export type InterruptResponse = {
|
|
14
|
+
decisions: HumanInTheLoopDecision[];
|
|
15
|
+
};
|
|
16
|
+
/** 由于 langchain human in the loop 没有设计调用 id,所以我们需要给一个 id */
|
|
17
|
+
export const createActionRequestID = (j: { name: string; args: any }) => {
|
|
18
|
+
return j.name + JSON.stringify(j.args);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type HumanInTheLoopState = {
|
|
22
|
+
interruptData: InterruptData;
|
|
23
|
+
result: Record<string, HumanInTheLoopDecision>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type InterruptData = {
|
|
27
|
+
id: string;
|
|
28
|
+
value: {
|
|
29
|
+
actionRequests: {
|
|
30
|
+
id?: string;
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
args: any;
|
|
34
|
+
}[];
|
|
35
|
+
reviewConfigs: {
|
|
36
|
+
actionName: string;
|
|
37
|
+
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
|
|
38
|
+
}[];
|
|
39
|
+
};
|
|
40
|
+
}[];
|
package/src/tool/ToolUI.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { RenderMessage } from "../LangGraphClient.js";
|
|
|
3
3
|
import { LangGraphClient } from "../LangGraphClient.js";
|
|
4
4
|
import { getMessageContent } from "../ui-store/createChatStore.js";
|
|
5
5
|
import { jsonrepair } from "jsonrepair";
|
|
6
|
-
import { InterruptResponse } from "
|
|
6
|
+
import { createActionRequestID, HumanInTheLoopDecision, InterruptResponse } from "../humanInTheLoop.js";
|
|
7
7
|
|
|
8
8
|
export type DeepPartial<T> = {
|
|
9
9
|
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
|
@@ -13,34 +13,53 @@ export class ToolRenderData<I, D> {
|
|
|
13
13
|
public message: RenderMessage,
|
|
14
14
|
public client: LangGraphClient
|
|
15
15
|
) {}
|
|
16
|
+
private getToolActionRequestID() {
|
|
17
|
+
return createActionRequestID({
|
|
18
|
+
name: this.message.name!,
|
|
19
|
+
args: this.getInputRepaired(),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
16
22
|
/**
|
|
17
23
|
* 获取人机交互数据
|
|
18
24
|
* 直接使用 reviewConfig 获取可以显示的按钮
|
|
19
25
|
* actionRequest 获取当前工具的入参
|
|
20
26
|
*/
|
|
21
27
|
getHumanInTheLoopData() {
|
|
22
|
-
const
|
|
28
|
+
const toolActionRequestID = this.getToolActionRequestID();
|
|
29
|
+
if (!this.client.humanInTheLoop) return null;
|
|
30
|
+
const configOfHumanInTheLoop = this.client.humanInTheLoop.interruptData.find((i) => i.value.actionRequests.some((j) => j.id === toolActionRequestID));
|
|
23
31
|
if (!configOfHumanInTheLoop) return null;
|
|
32
|
+
|
|
33
|
+
const actionRequestIndex = configOfHumanInTheLoop.value.actionRequests.findIndex((j) => j.id === toolActionRequestID);
|
|
34
|
+
if (actionRequestIndex === -1) return null;
|
|
35
|
+
|
|
24
36
|
return {
|
|
37
|
+
actionRequestIndex: actionRequestIndex,
|
|
25
38
|
config: configOfHumanInTheLoop,
|
|
26
|
-
reviewConfig: configOfHumanInTheLoop.value.reviewConfigs.find((j) => j.actionName ===
|
|
27
|
-
actionRequest: configOfHumanInTheLoop.value.actionRequests
|
|
39
|
+
reviewConfig: configOfHumanInTheLoop.value.reviewConfigs.find((j) => j.actionName === configOfHumanInTheLoop.value.actionRequests[actionRequestIndex].name)!,
|
|
40
|
+
actionRequest: configOfHumanInTheLoop.value.actionRequests[actionRequestIndex],
|
|
41
|
+
result: this.client.humanInTheLoop.result[toolActionRequestID],
|
|
28
42
|
};
|
|
29
43
|
}
|
|
30
44
|
/** 发送恢复状态的数据 */
|
|
31
|
-
sendResumeData(response:
|
|
45
|
+
sendResumeData(response: HumanInTheLoopDecision) {
|
|
32
46
|
if (response.type === "edit") {
|
|
33
47
|
/**@ts-ignore 修复 sb 的 langchain 官方的命名不统一,我们一致采用下划线版本,而非驼峰版本 */
|
|
34
48
|
response.editedAction = response.edited_action;
|
|
35
49
|
}
|
|
36
|
-
|
|
50
|
+
|
|
51
|
+
return this.client.doneHumanInTheLoopWaiting(this.message.id!, this.getToolActionRequestID(), response);
|
|
37
52
|
}
|
|
38
53
|
get state() {
|
|
39
54
|
if (this.message.type === "tool" && this.message?.additional_kwargs?.done) {
|
|
40
55
|
return "done";
|
|
41
56
|
}
|
|
42
|
-
|
|
43
|
-
|
|
57
|
+
const humanInTheLoopData = this.getHumanInTheLoopData();
|
|
58
|
+
if (humanInTheLoopData?.result) {
|
|
59
|
+
return "done";
|
|
60
|
+
}
|
|
61
|
+
if (this.client.status === "interrupted" && humanInTheLoopData?.actionRequest) {
|
|
62
|
+
return "done";
|
|
44
63
|
}
|
|
45
64
|
if (this.message.tool_input) {
|
|
46
65
|
return "loading";
|
package/src/tool/createTool.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { actionParametersToJsonSchema, convertJsonSchemaToZodRawShape } from "./utils.js";
|
|
2
|
-
import { z, ZodRawShape } from "zod";
|
|
2
|
+
import { uuidv4, z, ZodRawShape } from "zod";
|
|
3
3
|
import { Action, Parameter } from "./copilotkit-actions.js";
|
|
4
4
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
5
5
|
import { Message } from "@langchain/langgraph-sdk";
|
|
6
6
|
import { ToolRenderData } from "./ToolUI.js";
|
|
7
|
+
import { HumanInTheLoopDecision, InterruptResponse as HumanInTheLoopResponse } from "../humanInTheLoop.js";
|
|
7
8
|
|
|
8
9
|
export interface UnionTool<Args extends ZodRawShape, Child extends Object = Object, ResponseType = any> {
|
|
9
10
|
name: string;
|
|
@@ -25,23 +26,8 @@ export interface UnionTool<Args extends ZodRawShape, Child extends Object = Obje
|
|
|
25
26
|
isPureParams?: boolean;
|
|
26
27
|
}
|
|
27
28
|
export type ToolCallback<Args extends ZodRawShape> = (args: z.infer<z.ZodObject<Args>>, context?: any) => CallToolResult | Promise<CallToolResult>;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*/
|
|
31
|
-
export type InterruptResponse = {
|
|
32
|
-
decisions: (
|
|
33
|
-
| { type: "approve" }
|
|
34
|
-
| {
|
|
35
|
-
type: "edit";
|
|
36
|
-
edited_action: {
|
|
37
|
-
name: string;
|
|
38
|
-
args: Record<string, any>;
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
| { type: "reject"; message?: string }
|
|
42
|
-
)[];
|
|
43
|
-
};
|
|
44
|
-
export type CallToolResult = string | { type: "text"; text: string }[] | InterruptResponse;
|
|
29
|
+
|
|
30
|
+
export type CallToolResult = string | { type: "text"; text: string }[] | HumanInTheLoopDecision | HumanInTheLoopResponse;
|
|
45
31
|
|
|
46
32
|
/** 用于格式校验 */
|
|
47
33
|
export const createTool = <Args extends ZodRawShape>(tool: UnionTool<Args>) => {
|