@langgraph-js/sdk 4.0.1 → 4.1.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/.turbo/turbo-build.log +0 -5
- package/dist/LangGraphClient.d.ts +11 -1
- package/dist/LangGraphClient.js +59 -23
- package/dist/client/LanggraphServer.js +18 -1
- package/dist/react/ChatContext.d.ts +4 -1
- package/dist/ui-store/createChatStore.d.ts +5 -1
- package/dist/ui-store/createChatStore.js +42 -2
- package/dist/vue/ChatContext.d.ts +4 -1
- package/package.json +1 -1
- package/src/LangGraphClient.ts +62 -25
- package/src/client/LanggraphServer.ts +18 -1
- package/src/ui-store/createChatStore.ts +50 -3
package/.turbo/turbo-build.log
CHANGED
|
@@ -107,6 +107,10 @@ export interface LangGraphEvents {
|
|
|
107
107
|
done: {
|
|
108
108
|
event: "done";
|
|
109
109
|
};
|
|
110
|
+
/** 中断事件 */
|
|
111
|
+
interruptChange: {
|
|
112
|
+
event: "interruptChange";
|
|
113
|
+
};
|
|
110
114
|
}
|
|
111
115
|
/**
|
|
112
116
|
* @zh LangGraphClient 类是与 LangGraph 后端交互的主要客户端。
|
|
@@ -132,7 +136,10 @@ export declare class LangGraphClient<TStateType = unknown> extends EventEmitter<
|
|
|
132
136
|
/** 代理 assistants 属性到内部 client */
|
|
133
137
|
get assistants(): {
|
|
134
138
|
search(query?: {
|
|
135
|
-
graphId
|
|
139
|
+
graphId? /**
|
|
140
|
+
* The maximum number of retries that can be made for a single call,
|
|
141
|
+
* with an exponential backoff between each attempt. Defaults to 6.
|
|
142
|
+
*/: string;
|
|
136
143
|
metadata?: import("@langchain/langgraph-sdk").Metadata;
|
|
137
144
|
limit?: number;
|
|
138
145
|
offset?: number;
|
|
@@ -232,6 +239,8 @@ export declare class LangGraphClient<TStateType = unknown> extends EventEmitter<
|
|
|
232
239
|
}>;
|
|
233
240
|
messagesMetadata: {};
|
|
234
241
|
humanInTheLoop: HumanInTheLoopState | null;
|
|
242
|
+
/** 此 interruptData 不包括 humanInTheLoop 的数据 */
|
|
243
|
+
interruptData: any | null;
|
|
235
244
|
/**
|
|
236
245
|
* @zh 发送消息到 LangGraph 后端。
|
|
237
246
|
* @en Sends a message to the LangGraph backend.
|
|
@@ -254,6 +263,7 @@ export declare class LangGraphClient<TStateType = unknown> extends EventEmitter<
|
|
|
254
263
|
*/
|
|
255
264
|
private processStreamChunk;
|
|
256
265
|
private runFETool;
|
|
266
|
+
private getHumanInTheLoopData;
|
|
257
267
|
private responseHumanInTheLoop;
|
|
258
268
|
private callFETool;
|
|
259
269
|
extraParams: Record<string, any>;
|
package/dist/LangGraphClient.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createActionRequestID } from "./humanInTheLoop.js";
|
|
|
4
4
|
import { MessageProcessor } from "./MessageProcessor.js";
|
|
5
5
|
import { revertChatTo } from "./time-travel/index.js";
|
|
6
6
|
import camelcaseKeys from "camelcase-keys";
|
|
7
|
+
import z from "zod";
|
|
7
8
|
/**
|
|
8
9
|
* @zh LangGraphClient 类是与 LangGraph 后端交互的主要客户端。
|
|
9
10
|
* @en The LangGraphClient class is the main client for interacting with the LangGraph backend.
|
|
@@ -116,6 +117,8 @@ export class LangGraphClient extends EventEmitter {
|
|
|
116
117
|
sortBy: options.sortBy || "updated_at",
|
|
117
118
|
offset: options.offset || 0,
|
|
118
119
|
limit: options.limit || 10,
|
|
120
|
+
/** @ts-ignore: 用于删除不需要的字段 */
|
|
121
|
+
without_details: true,
|
|
119
122
|
});
|
|
120
123
|
}
|
|
121
124
|
async deleteThread(threadId) {
|
|
@@ -212,6 +215,8 @@ export class LangGraphClient extends EventEmitter {
|
|
|
212
215
|
}
|
|
213
216
|
messagesMetadata = {};
|
|
214
217
|
humanInTheLoop = null;
|
|
218
|
+
/** 此 interruptData 不包括 humanInTheLoop 的数据 */
|
|
219
|
+
interruptData = null;
|
|
215
220
|
/**
|
|
216
221
|
* @zh 发送消息到 LangGraph 后端。
|
|
217
222
|
* @en Sends a message to the LangGraph backend.
|
|
@@ -229,6 +234,12 @@ export class LangGraphClient extends EventEmitter {
|
|
|
229
234
|
},
|
|
230
235
|
});
|
|
231
236
|
}
|
|
237
|
+
if (this.interruptData) {
|
|
238
|
+
this.interruptData = null;
|
|
239
|
+
this.emit("interruptChange", {
|
|
240
|
+
event: "interruptChange",
|
|
241
|
+
});
|
|
242
|
+
}
|
|
232
243
|
const messagesToSend = Array.isArray(input)
|
|
233
244
|
? input
|
|
234
245
|
: [
|
|
@@ -335,30 +346,16 @@ export class LangGraphClient extends EventEmitter {
|
|
|
335
346
|
const data = chunk.data;
|
|
336
347
|
if (data?.__interrupt__) {
|
|
337
348
|
this._status = "interrupted";
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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;
|
|
349
|
+
const humanInTheLoopData = this.getHumanInTheLoopData(data?.__interrupt__);
|
|
350
|
+
if (humanInTheLoopData) {
|
|
351
|
+
this.humanInTheLoop = humanInTheLoopData;
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
this.interruptData = data.__interrupt__;
|
|
355
|
+
}
|
|
356
|
+
this.emit("interruptChange", {
|
|
357
|
+
event: "interruptChange",
|
|
357
358
|
});
|
|
358
|
-
this.humanInTheLoop = {
|
|
359
|
-
interruptData: humanInTheLoop,
|
|
360
|
-
result: {},
|
|
361
|
-
};
|
|
362
359
|
}
|
|
363
360
|
else if (data?.messages) {
|
|
364
361
|
const isResume = !!command?.resume;
|
|
@@ -401,6 +398,45 @@ export class LangGraphClient extends EventEmitter {
|
|
|
401
398
|
return Promise.all(result);
|
|
402
399
|
}
|
|
403
400
|
}
|
|
401
|
+
getHumanInTheLoopData(interruptData) {
|
|
402
|
+
const humanInTheLoopData = z
|
|
403
|
+
.array(z.object({
|
|
404
|
+
id: z.string(),
|
|
405
|
+
value: z.union([
|
|
406
|
+
z.object({
|
|
407
|
+
review_configs: z.array(z.any()),
|
|
408
|
+
}),
|
|
409
|
+
z.object({ reviewConfigs: z.array(z.any()) }),
|
|
410
|
+
]),
|
|
411
|
+
}))
|
|
412
|
+
.safeParse(interruptData);
|
|
413
|
+
if (humanInTheLoopData.success) {
|
|
414
|
+
const humanInTheLoop = interruptData.map((i) => {
|
|
415
|
+
// 修复 python 版本是以下划线命名的,而 js 版本是以驼峰命名的
|
|
416
|
+
if (i && "review_configs" in i.value) {
|
|
417
|
+
return {
|
|
418
|
+
id: i.id,
|
|
419
|
+
value: {
|
|
420
|
+
/** @ts-ignore */
|
|
421
|
+
actionRequests: i.value.action_requests,
|
|
422
|
+
reviewConfigs: camelcaseKeys(i.value.review_configs, { deep: true }),
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
return i;
|
|
427
|
+
});
|
|
428
|
+
humanInTheLoop.forEach((i) => {
|
|
429
|
+
i.value.actionRequests.forEach((j) => {
|
|
430
|
+
j.id = j.id || createActionRequestID(j);
|
|
431
|
+
});
|
|
432
|
+
return i;
|
|
433
|
+
});
|
|
434
|
+
return {
|
|
435
|
+
interruptData: humanInTheLoop,
|
|
436
|
+
result: {},
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
}
|
|
404
440
|
async responseHumanInTheLoop() {
|
|
405
441
|
if (this.humanInTheLoop) {
|
|
406
442
|
const humanInTheLoop = this.humanInTheLoop;
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import { Client } from "@langchain/langgraph-sdk";
|
|
2
2
|
export const createLangGraphServerClient = async (config) => {
|
|
3
|
-
|
|
3
|
+
const client = new Client(config);
|
|
4
|
+
client.threads.search = function (query) {
|
|
5
|
+
return this.fetch("/threads/search", {
|
|
6
|
+
method: "POST",
|
|
7
|
+
json: {
|
|
8
|
+
metadata: (query == null ? void 0 : query.metadata) ?? void 0,
|
|
9
|
+
ids: (query == null ? void 0 : query.ids) ?? void 0,
|
|
10
|
+
limit: (query == null ? void 0 : query.limit) ?? 10,
|
|
11
|
+
offset: (query == null ? void 0 : query.offset) ?? 0,
|
|
12
|
+
status: query == null ? void 0 : query.status,
|
|
13
|
+
sort_by: query == null ? void 0 : query.sortBy,
|
|
14
|
+
sort_order: query == null ? void 0 : query.sortOrder,
|
|
15
|
+
select: (query == null ? void 0 : query.select) ?? void 0,
|
|
16
|
+
without_details: (query == null ? void 0 : query.without_details) ?? false,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}.bind(client.threads);
|
|
20
|
+
return client;
|
|
4
21
|
};
|
|
@@ -16,6 +16,8 @@ export declare const useChat: () => UnionStore<{
|
|
|
16
16
|
currentAgent: import("nanostores").PreinitializedWritableAtom<string> & object;
|
|
17
17
|
currentChatId: import("nanostores").PreinitializedWritableAtom<string | null> & object;
|
|
18
18
|
currentNodeName: import("nanostores").PreinitializedWritableAtom<string> & object;
|
|
19
|
+
interruptData: import("nanostores").PreinitializedWritableAtom<import("../humanInTheLoop.js").InterruptData | null> & object;
|
|
20
|
+
isInterrupted: import("nanostores").PreinitializedWritableAtom<boolean> & object;
|
|
19
21
|
tools: import("nanostores").PreinitializedWritableAtom<import("../index.js").UnionTool<any, Object, any>[]> & object;
|
|
20
22
|
collapsedTools: import("nanostores").PreinitializedWritableAtom<string[]> & object;
|
|
21
23
|
showGraph: import("nanostores").PreinitializedWritableAtom<boolean> & object;
|
|
@@ -35,7 +37,7 @@ export declare const useChat: () => UnionStore<{
|
|
|
35
37
|
createNewSession: () => Promise<void>;
|
|
36
38
|
refreshSessionList: () => Promise<void>;
|
|
37
39
|
refreshHistoryList: () => Promise<void>;
|
|
38
|
-
sendMessage: (message?: import("@langchain/langgraph-sdk").Message[], extraData?: import("../LangGraphClient.js").SendMessageOptions, withoutCheck?: boolean) => Promise<void>;
|
|
40
|
+
sendMessage: (message?: import("@langchain/langgraph-sdk").Message[], extraData?: import("../LangGraphClient.js").SendMessageOptions, withoutCheck?: boolean, isResume?: boolean) => Promise<void>;
|
|
39
41
|
stopGeneration: () => void;
|
|
40
42
|
setUserInput: (input: string) => void;
|
|
41
43
|
revertChatTo(messageId: string, resend?: boolean, sendOptions?: import("../LangGraphClient.js").SendMessageOptions & import("../time-travel/index.js").RevertChatToOptions): Promise<void>;
|
|
@@ -48,6 +50,7 @@ export declare const useChat: () => UnionStore<{
|
|
|
48
50
|
toggleGraphVisible(): void;
|
|
49
51
|
refreshGraph: () => Promise<void>;
|
|
50
52
|
setCurrentAgent(agent: string): Promise<import("../History.js").History>;
|
|
53
|
+
resumeFromInterrupt(data: any): Promise<void>;
|
|
51
54
|
addToHistory: (thread: import("@langchain/langgraph-sdk").Thread<{
|
|
52
55
|
messages: import("@langchain/langgraph-sdk").Message[];
|
|
53
56
|
}>) => void;
|
|
@@ -3,6 +3,7 @@ import { AssistantGraph, Message, Thread } from "@langchain/langgraph-sdk";
|
|
|
3
3
|
import { UnionTool } from "../tool/createTool.js";
|
|
4
4
|
import { RevertChatToOptions } from "../time-travel/index.js";
|
|
5
5
|
import { History, SessionInfo } from "../History.js";
|
|
6
|
+
import { InterruptData } from "../humanInTheLoop.js";
|
|
6
7
|
export declare const formatTime: (date: Date) => string;
|
|
7
8
|
export declare const formatTokens: (tokens: number) => string;
|
|
8
9
|
export declare const getMessageContent: (content: any) => string;
|
|
@@ -30,6 +31,8 @@ export declare const createChatStore: (initClientName: string, config: Partial<L
|
|
|
30
31
|
currentAgent: import("nanostores").PreinitializedWritableAtom<string> & object;
|
|
31
32
|
currentChatId: import("nanostores").PreinitializedWritableAtom<string | null> & object;
|
|
32
33
|
currentNodeName: import("nanostores").PreinitializedWritableAtom<string> & object;
|
|
34
|
+
interruptData: import("nanostores").PreinitializedWritableAtom<InterruptData | null> & object;
|
|
35
|
+
isInterrupted: import("nanostores").PreinitializedWritableAtom<boolean> & object;
|
|
33
36
|
tools: import("nanostores").PreinitializedWritableAtom<UnionTool<any, Object, any>[]> & object;
|
|
34
37
|
collapsedTools: import("nanostores").PreinitializedWritableAtom<string[]> & object;
|
|
35
38
|
showGraph: import("nanostores").PreinitializedWritableAtom<boolean> & object;
|
|
@@ -49,7 +52,7 @@ export declare const createChatStore: (initClientName: string, config: Partial<L
|
|
|
49
52
|
createNewSession: () => Promise<void>;
|
|
50
53
|
refreshSessionList: () => Promise<void>;
|
|
51
54
|
refreshHistoryList: () => Promise<void>;
|
|
52
|
-
sendMessage: (message?: Message[], extraData?: SendMessageOptions, withoutCheck?: boolean) => Promise<void>;
|
|
55
|
+
sendMessage: (message?: Message[], extraData?: SendMessageOptions, withoutCheck?: boolean, isResume?: boolean) => Promise<void>;
|
|
53
56
|
stopGeneration: () => void;
|
|
54
57
|
setUserInput: (input: string) => void;
|
|
55
58
|
revertChatTo(messageId: string, resend?: boolean, sendOptions?: SendMessageOptions & RevertChatToOptions): Promise<void>;
|
|
@@ -62,6 +65,7 @@ export declare const createChatStore: (initClientName: string, config: Partial<L
|
|
|
62
65
|
toggleGraphVisible(): void;
|
|
63
66
|
refreshGraph: () => Promise<void>;
|
|
64
67
|
setCurrentAgent(agent: string): Promise<History>;
|
|
68
|
+
resumeFromInterrupt(data: any): Promise<void>;
|
|
65
69
|
addToHistory: (thread: Thread<{
|
|
66
70
|
messages: Message[];
|
|
67
71
|
}>) => void;
|
|
@@ -48,6 +48,9 @@ export const createChatStore = (initClientName, config, context = {}) => {
|
|
|
48
48
|
const currentAgent = atom(initClientName);
|
|
49
49
|
const currentChatId = atom(null);
|
|
50
50
|
const currentNodeName = atom("__start__");
|
|
51
|
+
// Interrupt 状态
|
|
52
|
+
const interruptData = atom(null);
|
|
53
|
+
const isInterrupted = atom(false);
|
|
51
54
|
// 工具和图表
|
|
52
55
|
const tools = atom([]);
|
|
53
56
|
const collapsedTools = atom([]);
|
|
@@ -62,7 +65,8 @@ export const createChatStore = (initClientName, config, context = {}) => {
|
|
|
62
65
|
const updateLoadingFromClientStatus = () => {
|
|
63
66
|
const c = client.get();
|
|
64
67
|
if (c) {
|
|
65
|
-
|
|
68
|
+
// interrupted 状态也应该被视为 loading,因为用户需要处理中断
|
|
69
|
+
loading.set(c.status === "busy" || c.status === "interrupted");
|
|
66
70
|
}
|
|
67
71
|
};
|
|
68
72
|
// ============ UI 更新逻辑 ============
|
|
@@ -182,12 +186,27 @@ export const createChatStore = (initClientName, config, context = {}) => {
|
|
|
182
186
|
updateUI(newClient);
|
|
183
187
|
}
|
|
184
188
|
};
|
|
189
|
+
const onInterrupted = (event) => {
|
|
190
|
+
if (!isActiveClient())
|
|
191
|
+
return;
|
|
192
|
+
const interruptInfo = newClient.interruptData;
|
|
193
|
+
if (interruptInfo) {
|
|
194
|
+
interruptData.set(interruptInfo);
|
|
195
|
+
isInterrupted.set(true);
|
|
196
|
+
updateLoadingFromClientStatus();
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
interruptData.set(null);
|
|
200
|
+
isInterrupted.set(false);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
185
203
|
newClient.on("start", onStart);
|
|
186
204
|
newClient.on("thread", onThread);
|
|
187
205
|
newClient.on("done", onDone);
|
|
188
206
|
newClient.on("error", onError);
|
|
189
207
|
newClient.on("message", onMessage);
|
|
190
208
|
newClient.on("value", onValue);
|
|
209
|
+
newClient.on("interruptChange", onInterrupted);
|
|
191
210
|
return () => {
|
|
192
211
|
newClient.off("start", onStart);
|
|
193
212
|
newClient.off("thread", onThread);
|
|
@@ -195,6 +214,7 @@ export const createChatStore = (initClientName, config, context = {}) => {
|
|
|
195
214
|
newClient.off("error", onError);
|
|
196
215
|
newClient.off("message", onMessage);
|
|
197
216
|
newClient.off("value", onValue);
|
|
217
|
+
newClient.off("interruptChange", onInterrupted);
|
|
198
218
|
};
|
|
199
219
|
}
|
|
200
220
|
// ============ 会话激活逻辑 ============
|
|
@@ -208,6 +228,8 @@ export const createChatStore = (initClientName, config, context = {}) => {
|
|
|
208
228
|
cleanupCurrentClient = null;
|
|
209
229
|
}
|
|
210
230
|
inChatError.set(null);
|
|
231
|
+
interruptData.set(null);
|
|
232
|
+
isInterrupted.set(false);
|
|
211
233
|
const session = await historyManager.activateSession(sessionId);
|
|
212
234
|
const activeClient = session.client;
|
|
213
235
|
if (activeClient) {
|
|
@@ -227,6 +249,14 @@ export const createChatStore = (initClientName, config, context = {}) => {
|
|
|
227
249
|
if (currentThread && (currentThread.status === "running" || currentThread.status === "pending")) {
|
|
228
250
|
await activeClient.resetStream();
|
|
229
251
|
}
|
|
252
|
+
// 检查是否处于中断状态
|
|
253
|
+
if (currentThread?.status === "interrupted") {
|
|
254
|
+
const interruptInfo = activeClient.interruptData;
|
|
255
|
+
if (interruptInfo) {
|
|
256
|
+
interruptData.set(interruptInfo);
|
|
257
|
+
isInterrupted.set(true);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
230
260
|
}
|
|
231
261
|
}
|
|
232
262
|
catch (error) {
|
|
@@ -235,7 +265,7 @@ export const createChatStore = (initClientName, config, context = {}) => {
|
|
|
235
265
|
}
|
|
236
266
|
}
|
|
237
267
|
// ============ 消息和交互逻辑 ============
|
|
238
|
-
async function sendMessage(message, extraData, withoutCheck = false) {
|
|
268
|
+
async function sendMessage(message, extraData, withoutCheck = false, isResume = false) {
|
|
239
269
|
const c = client.get();
|
|
240
270
|
if ((!withoutCheck && !userInput.get().trim() && !message?.length) || !c)
|
|
241
271
|
return;
|
|
@@ -301,6 +331,9 @@ export const createChatStore = (initClientName, config, context = {}) => {
|
|
|
301
331
|
currentAgent,
|
|
302
332
|
currentChatId,
|
|
303
333
|
currentNodeName,
|
|
334
|
+
// Interrupt 状态
|
|
335
|
+
interruptData,
|
|
336
|
+
isInterrupted,
|
|
304
337
|
// 工具和图表
|
|
305
338
|
tools,
|
|
306
339
|
collapsedTools,
|
|
@@ -356,6 +389,13 @@ export const createChatStore = (initClientName, config, context = {}) => {
|
|
|
356
389
|
currentAgent.set(agent);
|
|
357
390
|
return initClient();
|
|
358
391
|
},
|
|
392
|
+
resumeFromInterrupt(data) {
|
|
393
|
+
return sendMessage([], {
|
|
394
|
+
command: {
|
|
395
|
+
resume: data,
|
|
396
|
+
},
|
|
397
|
+
}, true);
|
|
398
|
+
},
|
|
359
399
|
// 历史记录(兼容旧 API)
|
|
360
400
|
addToHistory,
|
|
361
401
|
createNewChat: createNewSession,
|
|
@@ -59,6 +59,8 @@ export declare const useChatProvider: (props: ChatProviderProps) => {
|
|
|
59
59
|
currentAgent: PreinitializedWritableAtom<string> & object;
|
|
60
60
|
currentChatId: PreinitializedWritableAtom<string | null> & object;
|
|
61
61
|
currentNodeName: PreinitializedWritableAtom<string> & object;
|
|
62
|
+
interruptData: PreinitializedWritableAtom<import("../humanInTheLoop.js").InterruptData | null> & object;
|
|
63
|
+
isInterrupted: PreinitializedWritableAtom<boolean> & object;
|
|
62
64
|
tools: PreinitializedWritableAtom<import("../index.js").UnionTool<any, Object, any>[]> & object;
|
|
63
65
|
collapsedTools: PreinitializedWritableAtom<string[]> & object;
|
|
64
66
|
showGraph: PreinitializedWritableAtom<boolean> & object;
|
|
@@ -78,7 +80,7 @@ export declare const useChatProvider: (props: ChatProviderProps) => {
|
|
|
78
80
|
createNewSession: () => Promise<void>;
|
|
79
81
|
refreshSessionList: () => Promise<void>;
|
|
80
82
|
refreshHistoryList: () => Promise<void>;
|
|
81
|
-
sendMessage: (message?: import("@langchain/langgraph-sdk").Message[], extraData?: import("../LangGraphClient.js").SendMessageOptions, withoutCheck?: boolean) => Promise<void>;
|
|
83
|
+
sendMessage: (message?: import("@langchain/langgraph-sdk").Message[], extraData?: import("../LangGraphClient.js").SendMessageOptions, withoutCheck?: boolean, isResume?: boolean) => Promise<void>;
|
|
82
84
|
stopGeneration: () => void;
|
|
83
85
|
setUserInput: (input: string) => void;
|
|
84
86
|
revertChatTo(messageId: string, resend?: boolean, sendOptions?: import("../LangGraphClient.js").SendMessageOptions & import("../time-travel/index.js").RevertChatToOptions): Promise<void>;
|
|
@@ -91,6 +93,7 @@ export declare const useChatProvider: (props: ChatProviderProps) => {
|
|
|
91
93
|
toggleGraphVisible(): void;
|
|
92
94
|
refreshGraph: () => Promise<void>;
|
|
93
95
|
setCurrentAgent(agent: string): Promise<import("../History.js").History>;
|
|
96
|
+
resumeFromInterrupt(data: any): Promise<void>;
|
|
94
97
|
addToHistory: (thread: import("@langchain/langgraph-sdk").Thread<{
|
|
95
98
|
messages: import("@langchain/langgraph-sdk").Message[];
|
|
96
99
|
}>) => void;
|
package/package.json
CHANGED
package/src/LangGraphClient.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { type ILangGraphClient } from "@langgraph-js/pure-graph/dist/types.js";
|
|
|
7
7
|
import { MessageProcessor } from "./MessageProcessor.js";
|
|
8
8
|
import { revertChatTo, RevertChatToOptions } from "./time-travel/index.js";
|
|
9
9
|
import camelcaseKeys from "camelcase-keys";
|
|
10
|
+
import z from "zod";
|
|
10
11
|
export type RenderMessage = Message & {
|
|
11
12
|
/** 对于 AIMessage 来说是节点名称,对于工具节点来说是工具名称 */
|
|
12
13
|
name?: string;
|
|
@@ -91,6 +92,8 @@ export interface LangGraphEvents {
|
|
|
91
92
|
thread: { event: "thread/create"; data: { thread: Thread } };
|
|
92
93
|
/** 流完成事件 */
|
|
93
94
|
done: { event: "done" };
|
|
95
|
+
/** 中断事件 */
|
|
96
|
+
interruptChange: { event: "interruptChange" };
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
/**
|
|
@@ -219,6 +222,8 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
|
|
|
219
222
|
sortBy: options.sortBy || "updated_at",
|
|
220
223
|
offset: options.offset || 0,
|
|
221
224
|
limit: options.limit || 10,
|
|
225
|
+
/** @ts-ignore: 用于删除不需要的字段 */
|
|
226
|
+
without_details: true,
|
|
222
227
|
});
|
|
223
228
|
}
|
|
224
229
|
async deleteThread(threadId: string) {
|
|
@@ -326,6 +331,8 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
|
|
|
326
331
|
}
|
|
327
332
|
public messagesMetadata = {};
|
|
328
333
|
public humanInTheLoop: HumanInTheLoopState | null = null;
|
|
334
|
+
/** 此 interruptData 不包括 humanInTheLoop 的数据 */
|
|
335
|
+
public interruptData: any | null = null;
|
|
329
336
|
/**
|
|
330
337
|
* @zh 发送消息到 LangGraph 后端。
|
|
331
338
|
* @en Sends a message to the LangGraph backend.
|
|
@@ -343,7 +350,12 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
|
|
|
343
350
|
},
|
|
344
351
|
});
|
|
345
352
|
}
|
|
346
|
-
|
|
353
|
+
if (this.interruptData) {
|
|
354
|
+
this.interruptData = null;
|
|
355
|
+
this.emit("interruptChange", {
|
|
356
|
+
event: "interruptChange",
|
|
357
|
+
});
|
|
358
|
+
}
|
|
347
359
|
const messagesToSend = Array.isArray(input)
|
|
348
360
|
? input
|
|
349
361
|
: [
|
|
@@ -451,33 +463,17 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
|
|
|
451
463
|
messages: Message[];
|
|
452
464
|
}
|
|
453
465
|
| undefined;
|
|
454
|
-
|
|
455
466
|
if (data?.__interrupt__) {
|
|
456
467
|
this._status = "interrupted";
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
reviewConfigs: camelcaseKeys(i.value.review_configs as any[], { deep: true }),
|
|
466
|
-
},
|
|
467
|
-
} as InterruptData[number];
|
|
468
|
-
}
|
|
469
|
-
return i;
|
|
470
|
-
});
|
|
471
|
-
humanInTheLoop.forEach((i) => {
|
|
472
|
-
i.value.actionRequests.forEach((j) => {
|
|
473
|
-
j.id = j.id || createActionRequestID(j);
|
|
474
|
-
});
|
|
475
|
-
return i;
|
|
468
|
+
const humanInTheLoopData = this.getHumanInTheLoopData(data?.__interrupt__);
|
|
469
|
+
if (humanInTheLoopData) {
|
|
470
|
+
this.humanInTheLoop = humanInTheLoopData;
|
|
471
|
+
} else {
|
|
472
|
+
this.interruptData = data.__interrupt__;
|
|
473
|
+
}
|
|
474
|
+
this.emit("interruptChange", {
|
|
475
|
+
event: "interruptChange",
|
|
476
476
|
});
|
|
477
|
-
this.humanInTheLoop = {
|
|
478
|
-
interruptData: humanInTheLoop,
|
|
479
|
-
result: {},
|
|
480
|
-
};
|
|
481
477
|
} else if (data?.messages) {
|
|
482
478
|
const isResume = !!command?.resume;
|
|
483
479
|
const isLongerThanLocal = data.messages.length >= this.messageProcessor.getGraphMessages().length;
|
|
@@ -518,6 +514,47 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
|
|
|
518
514
|
return Promise.all(result);
|
|
519
515
|
}
|
|
520
516
|
}
|
|
517
|
+
private getHumanInTheLoopData(interruptData: any) {
|
|
518
|
+
const humanInTheLoopData = z
|
|
519
|
+
.array(
|
|
520
|
+
z.object({
|
|
521
|
+
id: z.string(),
|
|
522
|
+
value: z.union([
|
|
523
|
+
z.object({
|
|
524
|
+
review_configs: z.array(z.any()),
|
|
525
|
+
}),
|
|
526
|
+
z.object({ reviewConfigs: z.array(z.any()) }),
|
|
527
|
+
]),
|
|
528
|
+
})
|
|
529
|
+
)
|
|
530
|
+
.safeParse(interruptData);
|
|
531
|
+
if (humanInTheLoopData.success) {
|
|
532
|
+
const humanInTheLoop: HumanInTheLoopState["interruptData"] = interruptData.map((i: any) => {
|
|
533
|
+
// 修复 python 版本是以下划线命名的,而 js 版本是以驼峰命名的
|
|
534
|
+
if (i && "review_configs" in i.value) {
|
|
535
|
+
return {
|
|
536
|
+
id: i.id,
|
|
537
|
+
value: {
|
|
538
|
+
/** @ts-ignore */
|
|
539
|
+
actionRequests: i.value.action_requests,
|
|
540
|
+
reviewConfigs: camelcaseKeys(i.value.review_configs as any[], { deep: true }),
|
|
541
|
+
},
|
|
542
|
+
} as InterruptData[number];
|
|
543
|
+
}
|
|
544
|
+
return i;
|
|
545
|
+
});
|
|
546
|
+
humanInTheLoop.forEach((i) => {
|
|
547
|
+
i.value.actionRequests.forEach((j) => {
|
|
548
|
+
j.id = j.id || createActionRequestID(j);
|
|
549
|
+
});
|
|
550
|
+
return i;
|
|
551
|
+
});
|
|
552
|
+
return {
|
|
553
|
+
interruptData: humanInTheLoop,
|
|
554
|
+
result: {},
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
}
|
|
521
558
|
private async responseHumanInTheLoop() {
|
|
522
559
|
if (this.humanInTheLoop) {
|
|
523
560
|
const humanInTheLoop = this.humanInTheLoop;
|
|
@@ -2,5 +2,22 @@ import { LangGraphClientConfig } from "../LangGraphClient.js";
|
|
|
2
2
|
import { type ILangGraphClient } from "@langgraph-js/pure-graph/dist/types.js";
|
|
3
3
|
import { Client } from "@langchain/langgraph-sdk";
|
|
4
4
|
export const createLangGraphServerClient = async (config: LangGraphClientConfig): Promise<ILangGraphClient> => {
|
|
5
|
-
|
|
5
|
+
const client = new Client(config) as ILangGraphClient;
|
|
6
|
+
client.threads.search = function (this: any, query: any) {
|
|
7
|
+
return this.fetch("/threads/search", {
|
|
8
|
+
method: "POST",
|
|
9
|
+
json: {
|
|
10
|
+
metadata: (query == null ? void 0 : query.metadata) ?? void 0,
|
|
11
|
+
ids: (query == null ? void 0 : query.ids) ?? void 0,
|
|
12
|
+
limit: (query == null ? void 0 : query.limit) ?? 10,
|
|
13
|
+
offset: (query == null ? void 0 : query.offset) ?? 0,
|
|
14
|
+
status: query == null ? void 0 : query.status,
|
|
15
|
+
sort_by: query == null ? void 0 : query.sortBy,
|
|
16
|
+
sort_order: query == null ? void 0 : query.sortOrder,
|
|
17
|
+
select: (query == null ? void 0 : query.select) ?? void 0,
|
|
18
|
+
without_details: (query == null ? void 0 : query.without_details) ?? false,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}.bind(client.threads);
|
|
22
|
+
return client;
|
|
6
23
|
};
|
|
@@ -8,6 +8,7 @@ import { createLangGraphServerClient } from "../client/LanggraphServer.js";
|
|
|
8
8
|
import { useArtifacts } from "../artifacts/index.js";
|
|
9
9
|
import { RevertChatToOptions } from "../time-travel/index.js";
|
|
10
10
|
import { History, SessionInfo } from "../History.js";
|
|
11
|
+
import { InterruptData, InterruptResponse } from "../humanInTheLoop.js";
|
|
11
12
|
|
|
12
13
|
// ============ 工具函数 ============
|
|
13
14
|
|
|
@@ -68,6 +69,10 @@ export const createChatStore = (initClientName: string, config: Partial<LangGrap
|
|
|
68
69
|
const currentChatId = atom<string | null>(null);
|
|
69
70
|
const currentNodeName = atom<string>("__start__");
|
|
70
71
|
|
|
72
|
+
// Interrupt 状态
|
|
73
|
+
const interruptData = atom<InterruptData | null>(null);
|
|
74
|
+
const isInterrupted = atom<boolean>(false);
|
|
75
|
+
|
|
71
76
|
// 工具和图表
|
|
72
77
|
const tools = atom<UnionTool<any>[]>([]);
|
|
73
78
|
const collapsedTools = atom<string[]>([]);
|
|
@@ -87,7 +92,8 @@ export const createChatStore = (initClientName: string, config: Partial<LangGrap
|
|
|
87
92
|
const updateLoadingFromClientStatus = () => {
|
|
88
93
|
const c = client.get();
|
|
89
94
|
if (c) {
|
|
90
|
-
|
|
95
|
+
// interrupted 状态也应该被视为 loading,因为用户需要处理中断
|
|
96
|
+
loading.set(c.status === "busy" || c.status === "interrupted");
|
|
91
97
|
}
|
|
92
98
|
};
|
|
93
99
|
|
|
@@ -225,12 +231,27 @@ export const createChatStore = (initClientName: string, config: Partial<LangGrap
|
|
|
225
231
|
}
|
|
226
232
|
};
|
|
227
233
|
|
|
234
|
+
const onInterrupted = (event: any) => {
|
|
235
|
+
if (!isActiveClient()) return;
|
|
236
|
+
|
|
237
|
+
const interruptInfo = newClient.interruptData;
|
|
238
|
+
if (interruptInfo) {
|
|
239
|
+
interruptData.set(interruptInfo);
|
|
240
|
+
isInterrupted.set(true);
|
|
241
|
+
updateLoadingFromClientStatus();
|
|
242
|
+
} else {
|
|
243
|
+
interruptData.set(null);
|
|
244
|
+
isInterrupted.set(false);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
228
248
|
newClient.on("start", onStart);
|
|
229
249
|
newClient.on("thread", onThread);
|
|
230
250
|
newClient.on("done", onDone);
|
|
231
251
|
newClient.on("error", onError);
|
|
232
252
|
newClient.on("message", onMessage);
|
|
233
253
|
newClient.on("value", onValue);
|
|
254
|
+
newClient.on("interruptChange", onInterrupted);
|
|
234
255
|
|
|
235
256
|
return () => {
|
|
236
257
|
newClient.off("start", onStart);
|
|
@@ -239,6 +260,7 @@ export const createChatStore = (initClientName: string, config: Partial<LangGrap
|
|
|
239
260
|
newClient.off("error", onError);
|
|
240
261
|
newClient.off("message", onMessage);
|
|
241
262
|
newClient.off("value", onValue);
|
|
263
|
+
newClient.off("interruptChange", onInterrupted);
|
|
242
264
|
};
|
|
243
265
|
}
|
|
244
266
|
|
|
@@ -255,6 +277,8 @@ export const createChatStore = (initClientName: string, config: Partial<LangGrap
|
|
|
255
277
|
}
|
|
256
278
|
|
|
257
279
|
inChatError.set(null);
|
|
280
|
+
interruptData.set(null);
|
|
281
|
+
isInterrupted.set(false);
|
|
258
282
|
|
|
259
283
|
const session = await historyManager.activateSession(sessionId);
|
|
260
284
|
const activeClient = session.client;
|
|
@@ -280,6 +304,15 @@ export const createChatStore = (initClientName: string, config: Partial<LangGrap
|
|
|
280
304
|
if (currentThread && (currentThread.status === "running" || currentThread.status === "pending")) {
|
|
281
305
|
await activeClient.resetStream();
|
|
282
306
|
}
|
|
307
|
+
|
|
308
|
+
// 检查是否处于中断状态
|
|
309
|
+
if (currentThread?.status === "interrupted") {
|
|
310
|
+
const interruptInfo = activeClient.interruptData;
|
|
311
|
+
if (interruptInfo) {
|
|
312
|
+
interruptData.set(interruptInfo);
|
|
313
|
+
isInterrupted.set(true);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
283
316
|
}
|
|
284
317
|
} catch (error) {
|
|
285
318
|
console.error("Failed to activate session:", error);
|
|
@@ -289,7 +322,7 @@ export const createChatStore = (initClientName: string, config: Partial<LangGrap
|
|
|
289
322
|
|
|
290
323
|
// ============ 消息和交互逻辑 ============
|
|
291
324
|
|
|
292
|
-
async function sendMessage(message?: Message[], extraData?: SendMessageOptions, withoutCheck = false) {
|
|
325
|
+
async function sendMessage(message?: Message[], extraData?: SendMessageOptions, withoutCheck = false, isResume = false) {
|
|
293
326
|
const c = client.get();
|
|
294
327
|
if ((!withoutCheck && !userInput.get().trim() && !message?.length) || !c) return;
|
|
295
328
|
|
|
@@ -361,6 +394,10 @@ export const createChatStore = (initClientName: string, config: Partial<LangGrap
|
|
|
361
394
|
currentChatId,
|
|
362
395
|
currentNodeName,
|
|
363
396
|
|
|
397
|
+
// Interrupt 状态
|
|
398
|
+
interruptData,
|
|
399
|
+
isInterrupted,
|
|
400
|
+
|
|
364
401
|
// 工具和图表
|
|
365
402
|
tools,
|
|
366
403
|
collapsedTools,
|
|
@@ -421,7 +458,17 @@ export const createChatStore = (initClientName: string, config: Partial<LangGrap
|
|
|
421
458
|
currentAgent.set(agent);
|
|
422
459
|
return initClient();
|
|
423
460
|
},
|
|
424
|
-
|
|
461
|
+
resumeFromInterrupt(data: any) {
|
|
462
|
+
return sendMessage(
|
|
463
|
+
[],
|
|
464
|
+
{
|
|
465
|
+
command: {
|
|
466
|
+
resume: data,
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
true
|
|
470
|
+
);
|
|
471
|
+
},
|
|
425
472
|
// 历史记录(兼容旧 API)
|
|
426
473
|
addToHistory,
|
|
427
474
|
createNewChat: createNewSession,
|