langgraph-api 0.0.13__py3-none-any.whl → 0.0.15__py3-none-any.whl
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.
Potentially problematic release.
This version of langgraph-api might be problematic. Click here for more details.
- langgraph_api/api/__init__.py +2 -1
- langgraph_api/api/assistants.py +4 -4
- langgraph_api/config.py +1 -0
- langgraph_api/graph.py +6 -13
- langgraph_api/js/base.py +9 -0
- langgraph_api/js/build.mts +2 -0
- langgraph_api/js/client.mts +383 -409
- langgraph_api/js/client.new.mts +856 -0
- langgraph_api/js/errors.py +11 -0
- langgraph_api/js/package.json +2 -0
- langgraph_api/js/remote.py +16 -673
- langgraph_api/js/remote_new.py +685 -0
- langgraph_api/js/remote_old.py +657 -0
- langgraph_api/js/schema.py +29 -0
- langgraph_api/js/src/utils/serde.mts +7 -0
- langgraph_api/js/tests/api.test.mts +35 -1
- langgraph_api/js/tests/compose-postgres.yml +2 -1
- langgraph_api/js/tests/graphs/delay.mts +25 -0
- langgraph_api/js/tests/graphs/langgraph.json +2 -1
- langgraph_api/js/yarn.lock +866 -14
- langgraph_api/queue.py +85 -27
- langgraph_api/stream.py +4 -4
- {langgraph_api-0.0.13.dist-info → langgraph_api-0.0.15.dist-info}/METADATA +2 -2
- {langgraph_api-0.0.13.dist-info → langgraph_api-0.0.15.dist-info}/RECORD +28 -21
- langgraph_storage/ops.py +2 -2
- {langgraph_api-0.0.13.dist-info → langgraph_api-0.0.15.dist-info}/LICENSE +0 -0
- {langgraph_api-0.0.13.dist-info → langgraph_api-0.0.15.dist-info}/WHEEL +0 -0
- {langgraph_api-0.0.13.dist-info → langgraph_api-0.0.15.dist-info}/entry_points.txt +0 -0
langgraph_api/js/client.mts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
/// <reference types="./global.d.ts" />
|
|
2
|
+
|
|
1
3
|
import { z } from "zod";
|
|
2
|
-
import { Hono } from "hono";
|
|
4
|
+
import { Context, Hono } from "hono";
|
|
3
5
|
import { serve } from "@hono/node-server";
|
|
4
6
|
import { zValidator } from "@hono/zod-validator";
|
|
5
7
|
import { streamSSE } from "hono/streaming";
|
|
6
8
|
import { HTTPException } from "hono/http-exception";
|
|
9
|
+
import { Agent, fetch } from "undici";
|
|
7
10
|
import pRetry from "p-retry";
|
|
8
11
|
import {
|
|
9
12
|
BaseStore,
|
|
@@ -24,11 +27,10 @@ import {
|
|
|
24
27
|
import { createHash } from "node:crypto";
|
|
25
28
|
import * as fs from "node:fs/promises";
|
|
26
29
|
import * as path from "node:path";
|
|
27
|
-
import { serialiseAsDict } from "./src/utils/serde.mjs";
|
|
30
|
+
import { serialiseAsDict, serializeError } from "./src/utils/serde.mjs";
|
|
28
31
|
import * as importMap from "./src/utils/importMap.mjs";
|
|
29
32
|
|
|
30
33
|
import { createLogger, format, transports } from "winston";
|
|
31
|
-
import { Agent, fetch } from "undici";
|
|
32
34
|
|
|
33
35
|
import { load } from "@langchain/core/load";
|
|
34
36
|
import { BaseMessageChunk, isBaseMessage } from "@langchain/core/messages";
|
|
@@ -102,13 +104,10 @@ async function getOrExtractSchema(graphId: string) {
|
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
const GRAPH_SOCKET = "./graph.sock";
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
const checkpointerDispatcher = new Agent({
|
|
109
|
-
connect: { socketPath: CHECKPOINTER_SOCKET },
|
|
107
|
+
const REMOTE_SOCKET = "./checkpointer.sock";
|
|
108
|
+
const remoteDispatcher = new Agent({
|
|
109
|
+
connect: { socketPath: REMOTE_SOCKET },
|
|
110
110
|
});
|
|
111
|
-
const storeDispatcher = new Agent({ connect: { socketPath: STORE_SOCKET } });
|
|
112
111
|
|
|
113
112
|
const RunnableConfigSchema = z.object({
|
|
114
113
|
tags: z.array(z.string()).optional(),
|
|
@@ -161,22 +160,71 @@ function tryFetch(...args: Parameters<typeof fetch>) {
|
|
|
161
160
|
);
|
|
162
161
|
}
|
|
163
162
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
163
|
+
async function sendRecv<T = any>(
|
|
164
|
+
method: `${"checkpointer" | "store"}_${string}`,
|
|
165
|
+
data: unknown
|
|
166
|
+
): Promise<T> {
|
|
167
|
+
const res = await tryFetch(`http://remote/${method}`, {
|
|
168
|
+
dispatcher: remoteDispatcher,
|
|
169
|
+
method: "POST",
|
|
170
|
+
headers: { "Content-Type": "application/json" },
|
|
171
|
+
body: JSON.stringify(data),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return (await load(await res.text(), {
|
|
175
|
+
importMap,
|
|
176
|
+
optionalImportEntrypoints: [],
|
|
177
|
+
optionalImportsMap: {},
|
|
178
|
+
secretsMap: {},
|
|
179
|
+
})) as T;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const handleInvoke = <T extends z.ZodType<any>>(
|
|
183
|
+
name: string,
|
|
184
|
+
_schema: T,
|
|
185
|
+
handler: (rawPayload: z.infer<T>) => Promise<any>
|
|
186
|
+
) => {
|
|
187
|
+
return async (c: Context<any, any, { in: z.infer<T>; out: any }>) => {
|
|
188
|
+
const graphId = c.req.param("graphId");
|
|
189
|
+
const body = c.req.valid("json") as any;
|
|
190
|
+
const response = await handler({ graph_id: graphId, ...body });
|
|
191
|
+
return c.json(response);
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const handleStream = <T extends z.ZodType<any>>(
|
|
196
|
+
name: string,
|
|
197
|
+
_schema: T,
|
|
198
|
+
handler: (rawPayload: z.infer<T>) => AsyncGenerator<any, void, unknown>
|
|
199
|
+
) => {
|
|
200
|
+
return (c: Context<any, any, { in: z.infer<T>; out: any }>) => {
|
|
201
|
+
const graphId = c.req.param("graphId");
|
|
202
|
+
const body = c.req.valid("json") as any;
|
|
203
|
+
return streamSSE(c, async (stream) => {
|
|
204
|
+
try {
|
|
205
|
+
for await (const data of handler({ graph_id: graphId, ...body })) {
|
|
206
|
+
await stream.writeSSE({
|
|
207
|
+
data: serialiseAsDict(data),
|
|
208
|
+
event: name,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
} catch (error) {
|
|
212
|
+
// Still print out the error, as the stack
|
|
213
|
+
// trace is not carried over in Python
|
|
214
|
+
logger.error(error);
|
|
215
|
+
|
|
216
|
+
await stream.writeSSE({
|
|
217
|
+
event: "error",
|
|
218
|
+
data: serialiseAsDict(serializeError(error)),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
171
221
|
});
|
|
222
|
+
};
|
|
223
|
+
};
|
|
172
224
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
optionalImportEntrypoints: [],
|
|
177
|
-
optionalImportsMap: {},
|
|
178
|
-
secretsMap: {},
|
|
179
|
-
})) as any;
|
|
225
|
+
class RemoteCheckpointer extends BaseCheckpointSaver<number | string> {
|
|
226
|
+
async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {
|
|
227
|
+
const result = await sendRecv("checkpointer_get_tuple", { config });
|
|
180
228
|
|
|
181
229
|
if (!result) return undefined;
|
|
182
230
|
return {
|
|
@@ -187,6 +235,7 @@ class RemoteCheckpointer extends BaseCheckpointSaver<number | string> {
|
|
|
187
235
|
pendingWrites: result.pending_writes,
|
|
188
236
|
};
|
|
189
237
|
}
|
|
238
|
+
|
|
190
239
|
async *list(
|
|
191
240
|
config: RunnableConfig,
|
|
192
241
|
options?: {
|
|
@@ -195,20 +244,7 @@ class RemoteCheckpointer extends BaseCheckpointSaver<number | string> {
|
|
|
195
244
|
filter?: Record<string, any>;
|
|
196
245
|
}
|
|
197
246
|
): AsyncGenerator<CheckpointTuple> {
|
|
198
|
-
const
|
|
199
|
-
dispatcher: checkpointerDispatcher,
|
|
200
|
-
method: "POST",
|
|
201
|
-
headers: { "Content-Type": "application/json" },
|
|
202
|
-
body: JSON.stringify({ config, ...options }),
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
const text = await res.text();
|
|
206
|
-
const result = (await load(text, {
|
|
207
|
-
importMap,
|
|
208
|
-
optionalImportEntrypoints: [],
|
|
209
|
-
optionalImportsMap: {},
|
|
210
|
-
secretsMap: {},
|
|
211
|
-
})) as any;
|
|
247
|
+
const result = await sendRecv("checkpointer_list", { config, ...options });
|
|
212
248
|
|
|
213
249
|
for (const item of result) {
|
|
214
250
|
yield {
|
|
@@ -217,28 +253,22 @@ class RemoteCheckpointer extends BaseCheckpointSaver<number | string> {
|
|
|
217
253
|
metadata: item.metadata,
|
|
218
254
|
parentConfig: item.parent_config,
|
|
219
255
|
pendingWrites: item.pending_writes,
|
|
220
|
-
}
|
|
256
|
+
};
|
|
221
257
|
}
|
|
222
258
|
}
|
|
259
|
+
|
|
223
260
|
async put(
|
|
224
261
|
config: RunnableConfig,
|
|
225
262
|
checkpoint: Checkpoint,
|
|
226
263
|
metadata: CheckpointMetadata,
|
|
227
264
|
newVersions: ChannelVersions
|
|
228
265
|
): Promise<RunnableConfig> {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
config,
|
|
235
|
-
checkpoint,
|
|
236
|
-
metadata,
|
|
237
|
-
new_versions: newVersions,
|
|
238
|
-
}),
|
|
266
|
+
return await sendRecv<RunnableConfig>("checkpointer_put", {
|
|
267
|
+
config,
|
|
268
|
+
checkpoint,
|
|
269
|
+
metadata,
|
|
270
|
+
new_versions: newVersions,
|
|
239
271
|
});
|
|
240
|
-
|
|
241
|
-
return (await response.json()) as RunnableConfig;
|
|
242
272
|
}
|
|
243
273
|
|
|
244
274
|
async putWrites(
|
|
@@ -246,13 +276,7 @@ class RemoteCheckpointer extends BaseCheckpointSaver<number | string> {
|
|
|
246
276
|
writes: [string, unknown][],
|
|
247
277
|
taskId: string
|
|
248
278
|
): Promise<void> {
|
|
249
|
-
|
|
250
|
-
await tryFetch("http://checkpointer/put_writes", {
|
|
251
|
-
dispatcher: checkpointerDispatcher,
|
|
252
|
-
method: "POST",
|
|
253
|
-
headers: { "Content-Type": "application/json" },
|
|
254
|
-
body: JSON.stringify({ config, writes, taskId }),
|
|
255
|
-
});
|
|
279
|
+
await sendRecv("checkpointer_put_writes", { config, writes, taskId });
|
|
256
280
|
}
|
|
257
281
|
|
|
258
282
|
getNextVersion(
|
|
@@ -346,14 +370,10 @@ export class RemoteStore extends BaseStore {
|
|
|
346
370
|
async batch<Op extends Operation[]>(
|
|
347
371
|
operations: Op
|
|
348
372
|
): Promise<OperationResults<Op>> {
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
method: "POST",
|
|
352
|
-
headers: { "Content-Type": "application/json" },
|
|
353
|
-
body: JSON.stringify({ operations: operations.map(camelToSnake) }),
|
|
373
|
+
const results = await sendRecv<PyResult[]>("store_batch", {
|
|
374
|
+
operations: operations.map(camelToSnake),
|
|
354
375
|
});
|
|
355
376
|
|
|
356
|
-
const results = (await response.json()) as PyResult[];
|
|
357
377
|
return results.map((result) => {
|
|
358
378
|
if (Array.isArray(result)) {
|
|
359
379
|
return result.map((item) => pyItemToJs(item));
|
|
@@ -370,16 +390,10 @@ export class RemoteStore extends BaseStore {
|
|
|
370
390
|
}
|
|
371
391
|
|
|
372
392
|
async get(namespace: string[], key: string): Promise<Item | null> {
|
|
373
|
-
|
|
393
|
+
return await sendRecv<Item | null>("store_get", {
|
|
374
394
|
namespace: namespace.join("."),
|
|
375
395
|
key,
|
|
376
396
|
});
|
|
377
|
-
const urlWithParams = `http://store/items?${queryParams.toString()}`;
|
|
378
|
-
const response = await tryFetch(urlWithParams, {
|
|
379
|
-
dispatcher: storeDispatcher,
|
|
380
|
-
method: "GET",
|
|
381
|
-
});
|
|
382
|
-
return (await response.json()) as Item | null;
|
|
383
397
|
}
|
|
384
398
|
|
|
385
399
|
async search(
|
|
@@ -390,13 +404,10 @@ export class RemoteStore extends BaseStore {
|
|
|
390
404
|
offset?: number;
|
|
391
405
|
}
|
|
392
406
|
): Promise<Item[]> {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
headers: { "Content-Type": "application/json" },
|
|
397
|
-
body: JSON.stringify({ namespace_prefix: namespacePrefix, ...options }),
|
|
407
|
+
return await sendRecv<Item[]>("store_search", {
|
|
408
|
+
namespace_prefix: namespacePrefix,
|
|
409
|
+
...options,
|
|
398
410
|
});
|
|
399
|
-
return (await response.json()) as Item[];
|
|
400
411
|
}
|
|
401
412
|
|
|
402
413
|
async put(
|
|
@@ -404,21 +415,11 @@ export class RemoteStore extends BaseStore {
|
|
|
404
415
|
key: string,
|
|
405
416
|
value: Record<string, any>
|
|
406
417
|
): Promise<void> {
|
|
407
|
-
await
|
|
408
|
-
dispatcher: storeDispatcher,
|
|
409
|
-
method: "PUT",
|
|
410
|
-
headers: { "Content-Type": "application/json" },
|
|
411
|
-
body: JSON.stringify({ namespace, key, value }),
|
|
412
|
-
});
|
|
418
|
+
await sendRecv("store_put", { namespace, key, value });
|
|
413
419
|
}
|
|
414
420
|
|
|
415
421
|
async delete(namespace: string[], key: string): Promise<void> {
|
|
416
|
-
await
|
|
417
|
-
dispatcher: storeDispatcher,
|
|
418
|
-
method: "DELETE",
|
|
419
|
-
headers: { "Content-Type": "application/json" },
|
|
420
|
-
body: JSON.stringify({ namespace, key }),
|
|
421
|
-
});
|
|
422
|
+
await sendRecv("store_delete", { namespace, key });
|
|
422
423
|
}
|
|
423
424
|
|
|
424
425
|
async listNamespaces(options: {
|
|
@@ -428,14 +429,10 @@ export class RemoteStore extends BaseStore {
|
|
|
428
429
|
limit?: number;
|
|
429
430
|
offset?: number;
|
|
430
431
|
}): Promise<string[][]> {
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
body: JSON.stringify({ max_depth: options?.maxDepth, ...options }),
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
const data = (await response.json()) as { namespaces: string[][] };
|
|
432
|
+
const data = await sendRecv<{ namespaces: string[][] }>(
|
|
433
|
+
"store_list_namespaces",
|
|
434
|
+
{ max_depth: options?.maxDepth, ...options }
|
|
435
|
+
);
|
|
439
436
|
return data.namespaces;
|
|
440
437
|
}
|
|
441
438
|
}
|
|
@@ -451,6 +448,272 @@ const ExtraStreamModeSchema = z.union([
|
|
|
451
448
|
z.literal("messages"),
|
|
452
449
|
]);
|
|
453
450
|
|
|
451
|
+
const StreamEventsPayload = z.object({
|
|
452
|
+
graph_id: z.string(),
|
|
453
|
+
input: z.unknown(),
|
|
454
|
+
command: z.object({ resume: z.unknown() }).nullish(),
|
|
455
|
+
stream_mode: z
|
|
456
|
+
.union([ExtraStreamModeSchema, z.array(ExtraStreamModeSchema)])
|
|
457
|
+
.optional(),
|
|
458
|
+
config: RunnableConfigSchema.nullish(),
|
|
459
|
+
interrupt_before: z.union([z.array(z.string()), z.literal("*")]).nullish(),
|
|
460
|
+
interrupt_after: z.union([z.array(z.string()), z.literal("*")]).nullish(),
|
|
461
|
+
subgraphs: z.boolean().optional(),
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
async function* streamEventsRequest(
|
|
465
|
+
rawPayload: z.infer<typeof StreamEventsPayload>
|
|
466
|
+
) {
|
|
467
|
+
const { graph_id: graphId, ...payload } = rawPayload;
|
|
468
|
+
const graph = getGraph(graphId);
|
|
469
|
+
|
|
470
|
+
const input = payload.command ? new Command(payload.command) : payload.input;
|
|
471
|
+
|
|
472
|
+
const userStreamMode =
|
|
473
|
+
payload.stream_mode == null
|
|
474
|
+
? []
|
|
475
|
+
: Array.isArray(payload.stream_mode)
|
|
476
|
+
? payload.stream_mode
|
|
477
|
+
: [payload.stream_mode];
|
|
478
|
+
|
|
479
|
+
const graphStreamMode: Set<"updates" | "debug" | "values"> = new Set();
|
|
480
|
+
if (payload.stream_mode) {
|
|
481
|
+
for (const mode of userStreamMode) {
|
|
482
|
+
if (mode === "messages") {
|
|
483
|
+
graphStreamMode.add("values");
|
|
484
|
+
} else {
|
|
485
|
+
graphStreamMode.add(mode);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const config = getRunnableConfig(payload.config);
|
|
491
|
+
|
|
492
|
+
const messages: Record<string, BaseMessageChunk> = {};
|
|
493
|
+
const completedIds = new Set<string>();
|
|
494
|
+
|
|
495
|
+
let interruptBefore: typeof payload.interrupt_before =
|
|
496
|
+
payload.interrupt_before ?? undefined;
|
|
497
|
+
|
|
498
|
+
if (Array.isArray(interruptBefore) && interruptBefore.length === 0)
|
|
499
|
+
interruptBefore = undefined;
|
|
500
|
+
|
|
501
|
+
let interruptAfter: typeof payload.interrupt_after =
|
|
502
|
+
payload.interrupt_after ?? undefined;
|
|
503
|
+
|
|
504
|
+
if (Array.isArray(interruptAfter) && interruptAfter.length === 0)
|
|
505
|
+
interruptAfter = undefined;
|
|
506
|
+
|
|
507
|
+
const streamMode = [...graphStreamMode];
|
|
508
|
+
|
|
509
|
+
for await (const data of graph.streamEvents(input, {
|
|
510
|
+
...config,
|
|
511
|
+
version: "v2",
|
|
512
|
+
streamMode,
|
|
513
|
+
subgraphs: payload.subgraphs,
|
|
514
|
+
interruptBefore,
|
|
515
|
+
interruptAfter,
|
|
516
|
+
})) {
|
|
517
|
+
// TODO: upstream this fix to LangGraphJS
|
|
518
|
+
if (streamMode.length === 1 && !Array.isArray(data.data.chunk)) {
|
|
519
|
+
data.data.chunk = [streamMode[0], data.data.chunk];
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (payload.subgraphs) {
|
|
523
|
+
if (Array.isArray(data.data.chunk) && data.data.chunk.length === 2) {
|
|
524
|
+
data.data.chunk = [[], ...data.data.chunk];
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
yield data;
|
|
529
|
+
|
|
530
|
+
if (userStreamMode.includes("messages")) {
|
|
531
|
+
if (data.event === "on_chain_stream" && data.run_id === config.runId) {
|
|
532
|
+
const newMessages: Array<BaseMessageChunk> = [];
|
|
533
|
+
const [_, chunk]: [string, any] = data.data.chunk;
|
|
534
|
+
|
|
535
|
+
let chunkMessages: Array<BaseMessageChunk> = [];
|
|
536
|
+
if (
|
|
537
|
+
typeof chunk === "object" &&
|
|
538
|
+
chunk != null &&
|
|
539
|
+
"messages" in chunk &&
|
|
540
|
+
!isBaseMessage(chunk)
|
|
541
|
+
) {
|
|
542
|
+
chunkMessages = chunk?.messages;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (!Array.isArray(chunkMessages)) {
|
|
546
|
+
chunkMessages = [chunkMessages];
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
for (const message of chunkMessages) {
|
|
550
|
+
if (!message.id || completedIds.has(message.id)) continue;
|
|
551
|
+
completedIds.add(message.id);
|
|
552
|
+
newMessages.push(message);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (newMessages.length > 0) {
|
|
556
|
+
yield {
|
|
557
|
+
event: "on_custom_event",
|
|
558
|
+
name: "messages/complete",
|
|
559
|
+
data: newMessages,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
} else if (
|
|
563
|
+
data.event === "on_chat_model_stream" &&
|
|
564
|
+
!data.tags?.includes("nostream")
|
|
565
|
+
) {
|
|
566
|
+
const message: BaseMessageChunk = data.data.chunk;
|
|
567
|
+
|
|
568
|
+
if (!message.id) continue;
|
|
569
|
+
|
|
570
|
+
if (messages[message.id] == null) {
|
|
571
|
+
messages[message.id] = message;
|
|
572
|
+
yield {
|
|
573
|
+
event: "on_custom_event",
|
|
574
|
+
name: "messages/metadata",
|
|
575
|
+
data: { [message.id]: { metadata: data.metadata } },
|
|
576
|
+
};
|
|
577
|
+
} else {
|
|
578
|
+
messages[message.id] = messages[message.id].concat(message);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
yield {
|
|
582
|
+
event: "on_custom_event",
|
|
583
|
+
name: "messages/partial",
|
|
584
|
+
data: [messages[message.id]],
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const GetGraphPayload = z.object({
|
|
592
|
+
graph_id: z.string(),
|
|
593
|
+
config: RunnableConfigSchema.nullish(),
|
|
594
|
+
xray: z.union([z.number(), z.boolean()]).nullish(),
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
async function getGraphRequest(rawPayload: z.infer<typeof GetGraphPayload>) {
|
|
598
|
+
const { graph_id: graphId, ...payload } = rawPayload;
|
|
599
|
+
const graph = getGraph(graphId);
|
|
600
|
+
return graph
|
|
601
|
+
.getGraph({
|
|
602
|
+
...getRunnableConfig(payload.config),
|
|
603
|
+
xray: payload.xray ?? undefined,
|
|
604
|
+
})
|
|
605
|
+
.toJSON();
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const GetSubgraphsPayload = z.object({
|
|
609
|
+
graph_id: z.string(),
|
|
610
|
+
namespace: z.string().nullish(),
|
|
611
|
+
recurse: z.boolean().nullish(),
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
async function getSubgraphsRequest(
|
|
615
|
+
rawPayload: z.infer<typeof GetSubgraphsPayload>
|
|
616
|
+
) {
|
|
617
|
+
const { graph_id: graphId, ...payload } = rawPayload;
|
|
618
|
+
const graph = getGraph(graphId);
|
|
619
|
+
const result: Array<[name: string, Record<string, any>]> = [];
|
|
620
|
+
|
|
621
|
+
const graphSchema = await getOrExtractSchema(graphId);
|
|
622
|
+
const rootGraphId = Object.keys(graphSchema).find((i) => !i.includes("|"));
|
|
623
|
+
|
|
624
|
+
if (!rootGraphId) throw new Error("Failed to find root graph");
|
|
625
|
+
|
|
626
|
+
for (const [name] of graph.getSubgraphs(
|
|
627
|
+
payload.namespace ?? undefined,
|
|
628
|
+
payload.recurse ?? undefined
|
|
629
|
+
)) {
|
|
630
|
+
const schema =
|
|
631
|
+
graphSchema[`${rootGraphId}|${name}`] || graphSchema[rootGraphId];
|
|
632
|
+
result.push([name, schema]);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// TODO: make this a stream
|
|
636
|
+
return Object.fromEntries(result);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const GetStatePayload = z.object({
|
|
640
|
+
graph_id: z.string(),
|
|
641
|
+
config: RunnableConfigSchema,
|
|
642
|
+
subgraphs: z.boolean().nullish(),
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
async function getStateRequest(rawPayload: z.infer<typeof GetStatePayload>) {
|
|
646
|
+
const { graph_id: graphId, ...payload } = rawPayload;
|
|
647
|
+
const graph = getGraph(graphId);
|
|
648
|
+
|
|
649
|
+
const state = await graph.getState(getRunnableConfig(payload.config), {
|
|
650
|
+
subgraphs: payload.subgraphs ?? undefined,
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// TODO: just send the JSON directly, don't ser/de twice
|
|
654
|
+
return JSON.parse(serialiseAsDict(state));
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const UpdateStatePayload = z.object({
|
|
658
|
+
graph_id: z.string(),
|
|
659
|
+
config: RunnableConfigSchema,
|
|
660
|
+
values: z.unknown(),
|
|
661
|
+
as_node: z.string().nullish(),
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
async function updateStateRequest(
|
|
665
|
+
rawPayload: z.infer<typeof UpdateStatePayload>
|
|
666
|
+
) {
|
|
667
|
+
const { graph_id: graphId, ...payload } = rawPayload;
|
|
668
|
+
const graph = getGraph(graphId);
|
|
669
|
+
|
|
670
|
+
const config = await graph.updateState(
|
|
671
|
+
getRunnableConfig(payload.config),
|
|
672
|
+
payload.values,
|
|
673
|
+
payload.as_node ?? undefined
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
return config;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const GetSchemaPayload = z.object({ graph_id: z.string() });
|
|
680
|
+
|
|
681
|
+
async function getSchemaRequest(payload: z.infer<typeof GetSchemaPayload>) {
|
|
682
|
+
const { graph_id: graphId } = payload;
|
|
683
|
+
const schemas = await getOrExtractSchema(graphId);
|
|
684
|
+
const rootGraphId = Object.keys(schemas).find((i) => !i.includes("|"));
|
|
685
|
+
if (!rootGraphId) {
|
|
686
|
+
throw new Error("Failed to find root graph");
|
|
687
|
+
}
|
|
688
|
+
return schemas[rootGraphId];
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const GetStateHistoryPayload = z.object({
|
|
692
|
+
graph_id: z.string(),
|
|
693
|
+
config: RunnableConfigSchema,
|
|
694
|
+
limit: z.number().nullish(),
|
|
695
|
+
before: RunnableConfigSchema.nullish(),
|
|
696
|
+
filter: z.record(z.unknown()).nullish(),
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
async function* getStateHistoryRequest(
|
|
700
|
+
rawPayload: z.infer<typeof GetStateHistoryPayload>
|
|
701
|
+
) {
|
|
702
|
+
const { graph_id: graphId, ...payload } = rawPayload;
|
|
703
|
+
const graph = getGraph(graphId);
|
|
704
|
+
|
|
705
|
+
for await (const item of graph.getStateHistory(
|
|
706
|
+
getRunnableConfig(payload.config),
|
|
707
|
+
{
|
|
708
|
+
limit: payload.limit ?? undefined,
|
|
709
|
+
before: payload.before ? getRunnableConfig(payload.before) : undefined,
|
|
710
|
+
filter: payload.filter ?? undefined,
|
|
711
|
+
}
|
|
712
|
+
)) {
|
|
713
|
+
yield item;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
454
717
|
const __dirname = new URL(".", import.meta.url).pathname;
|
|
455
718
|
|
|
456
719
|
async function main() {
|
|
@@ -492,339 +755,48 @@ async function main() {
|
|
|
492
755
|
|
|
493
756
|
app.post(
|
|
494
757
|
"/:graphId/streamEvents",
|
|
495
|
-
zValidator(
|
|
496
|
-
|
|
497
|
-
z.object({
|
|
498
|
-
input: z.unknown(),
|
|
499
|
-
command: z.object({ resume: z.unknown() }).nullish(),
|
|
500
|
-
stream_mode: z
|
|
501
|
-
.union([ExtraStreamModeSchema, z.array(ExtraStreamModeSchema)])
|
|
502
|
-
.optional(),
|
|
503
|
-
config: RunnableConfigSchema.nullish(),
|
|
504
|
-
interrupt_before: z
|
|
505
|
-
.union([z.array(z.string()), z.literal("*")])
|
|
506
|
-
.nullish(),
|
|
507
|
-
interrupt_after: z
|
|
508
|
-
.union([z.array(z.string()), z.literal("*")])
|
|
509
|
-
.nullish(),
|
|
510
|
-
subgraphs: z.boolean().optional(),
|
|
511
|
-
})
|
|
512
|
-
),
|
|
513
|
-
async (c) => {
|
|
514
|
-
const graph = getGraph(c.req.param("graphId"));
|
|
515
|
-
const payload = c.req.valid("json");
|
|
516
|
-
|
|
517
|
-
const input = payload.command
|
|
518
|
-
? // @ts-expect-error Update LG.js to mark `resume as optional
|
|
519
|
-
new Command(payload.command)
|
|
520
|
-
: payload.input;
|
|
521
|
-
|
|
522
|
-
const userStreamMode =
|
|
523
|
-
payload.stream_mode == null
|
|
524
|
-
? []
|
|
525
|
-
: Array.isArray(payload.stream_mode)
|
|
526
|
-
? payload.stream_mode
|
|
527
|
-
: [payload.stream_mode];
|
|
528
|
-
|
|
529
|
-
const graphStreamMode: Set<"updates" | "debug" | "values"> = new Set();
|
|
530
|
-
if (payload.stream_mode) {
|
|
531
|
-
for (const mode of userStreamMode) {
|
|
532
|
-
if (mode === "messages") {
|
|
533
|
-
graphStreamMode.add("values");
|
|
534
|
-
} else {
|
|
535
|
-
graphStreamMode.add(mode);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
const config = getRunnableConfig(payload.config);
|
|
541
|
-
|
|
542
|
-
return streamSSE(c, async (stream) => {
|
|
543
|
-
const messages: Record<string, BaseMessageChunk> = {};
|
|
544
|
-
const completedIds = new Set<string>();
|
|
545
|
-
|
|
546
|
-
let interruptBefore: typeof payload.interrupt_before =
|
|
547
|
-
payload.interrupt_before ?? undefined;
|
|
548
|
-
|
|
549
|
-
if (Array.isArray(interruptBefore) && interruptBefore.length === 0)
|
|
550
|
-
interruptBefore = undefined;
|
|
551
|
-
|
|
552
|
-
let interruptAfter: typeof payload.interrupt_after =
|
|
553
|
-
payload.interrupt_after ?? undefined;
|
|
554
|
-
|
|
555
|
-
if (Array.isArray(interruptAfter) && interruptAfter.length === 0)
|
|
556
|
-
interruptAfter = undefined;
|
|
557
|
-
|
|
558
|
-
const streamMode = [...graphStreamMode];
|
|
559
|
-
|
|
560
|
-
try {
|
|
561
|
-
for await (const data of graph.streamEvents(input, {
|
|
562
|
-
...config,
|
|
563
|
-
version: "v2",
|
|
564
|
-
streamMode,
|
|
565
|
-
subgraphs: payload.subgraphs,
|
|
566
|
-
interruptBefore,
|
|
567
|
-
interruptAfter,
|
|
568
|
-
})) {
|
|
569
|
-
// TODO: upstream this fix to LangGraphJS
|
|
570
|
-
if (streamMode.length === 1 && !Array.isArray(data.data.chunk)) {
|
|
571
|
-
data.data.chunk = [streamMode[0], data.data.chunk];
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
if (payload.subgraphs) {
|
|
575
|
-
if (
|
|
576
|
-
Array.isArray(data.data.chunk) &&
|
|
577
|
-
data.data.chunk.length === 2
|
|
578
|
-
) {
|
|
579
|
-
data.data.chunk = [[], ...data.data.chunk];
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
await stream.writeSSE({
|
|
584
|
-
event: "streamLog",
|
|
585
|
-
data: serialiseAsDict(data),
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
if (userStreamMode.includes("messages")) {
|
|
589
|
-
if (
|
|
590
|
-
data.event === "on_chain_stream" &&
|
|
591
|
-
data.run_id === config.runId
|
|
592
|
-
) {
|
|
593
|
-
const newMessages: Array<BaseMessageChunk> = [];
|
|
594
|
-
const [_, chunk]: [string, any] = data.data.chunk;
|
|
595
|
-
|
|
596
|
-
let chunkMessages: Array<BaseMessageChunk> = [];
|
|
597
|
-
if (
|
|
598
|
-
typeof chunk === "object" &&
|
|
599
|
-
chunk != null &&
|
|
600
|
-
"messages" in chunk &&
|
|
601
|
-
!isBaseMessage(chunk)
|
|
602
|
-
) {
|
|
603
|
-
chunkMessages = chunk?.messages;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
if (!Array.isArray(chunkMessages)) {
|
|
607
|
-
chunkMessages = [chunkMessages];
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
for (const message of chunkMessages) {
|
|
611
|
-
if (!message.id || completedIds.has(message.id)) continue;
|
|
612
|
-
completedIds.add(message.id);
|
|
613
|
-
newMessages.push(message);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
if (newMessages.length > 0) {
|
|
617
|
-
await stream.writeSSE({
|
|
618
|
-
event: "streamLog",
|
|
619
|
-
data: serialiseAsDict({
|
|
620
|
-
event: "on_custom_event",
|
|
621
|
-
name: "messages/complete",
|
|
622
|
-
data: newMessages,
|
|
623
|
-
}),
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
} else if (
|
|
627
|
-
data.event === "on_chat_model_stream" &&
|
|
628
|
-
!data.tags?.includes("nostream")
|
|
629
|
-
) {
|
|
630
|
-
const message: BaseMessageChunk = data.data.chunk;
|
|
631
|
-
|
|
632
|
-
if (!message.id) continue;
|
|
633
|
-
|
|
634
|
-
if (messages[message.id] == null) {
|
|
635
|
-
messages[message.id] = message;
|
|
636
|
-
await stream.writeSSE({
|
|
637
|
-
event: "streamLog",
|
|
638
|
-
data: serialiseAsDict({
|
|
639
|
-
event: "on_custom_event",
|
|
640
|
-
name: "messages/metadata",
|
|
641
|
-
data: { [message.id]: { metadata: data.metadata } },
|
|
642
|
-
}),
|
|
643
|
-
});
|
|
644
|
-
} else {
|
|
645
|
-
messages[message.id] = messages[message.id].concat(message);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
await stream.writeSSE({
|
|
649
|
-
event: "streamLog",
|
|
650
|
-
data: serialiseAsDict({
|
|
651
|
-
event: "on_custom_event",
|
|
652
|
-
name: "messages/partial",
|
|
653
|
-
data: [messages[message.id]],
|
|
654
|
-
}),
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
} catch (error) {
|
|
660
|
-
const errorName = error instanceof Error ? error.name : "Error";
|
|
661
|
-
const errorMessage =
|
|
662
|
-
error instanceof Error ? error.message : JSON.stringify(error);
|
|
663
|
-
|
|
664
|
-
await stream.writeSSE({
|
|
665
|
-
event: "error",
|
|
666
|
-
data: serialiseAsDict({
|
|
667
|
-
error: errorName,
|
|
668
|
-
message: errorMessage,
|
|
669
|
-
}),
|
|
670
|
-
});
|
|
671
|
-
|
|
672
|
-
// Still print out the error, as the stack
|
|
673
|
-
// trace is not carried over in Python
|
|
674
|
-
logger.error(error);
|
|
675
|
-
}
|
|
676
|
-
});
|
|
677
|
-
}
|
|
758
|
+
zValidator("json", StreamEventsPayload),
|
|
759
|
+
handleStream("streamEvents", StreamEventsPayload, streamEventsRequest)
|
|
678
760
|
);
|
|
679
761
|
|
|
680
762
|
app.post(
|
|
681
763
|
"/:graphId/getGraph",
|
|
682
|
-
zValidator(
|
|
683
|
-
|
|
684
|
-
z.object({
|
|
685
|
-
config: RunnableConfigSchema.nullish(),
|
|
686
|
-
xray: z.union([z.number(), z.boolean()]).nullish(),
|
|
687
|
-
})
|
|
688
|
-
),
|
|
689
|
-
async (c) => {
|
|
690
|
-
const graphId = c.req.param("graphId");
|
|
691
|
-
const graph = getGraph(graphId);
|
|
692
|
-
return c.json(
|
|
693
|
-
graph
|
|
694
|
-
.getGraph({
|
|
695
|
-
...getRunnableConfig(c.req.valid("json").config),
|
|
696
|
-
xray: c.req.valid("json").xray ?? undefined,
|
|
697
|
-
})
|
|
698
|
-
.toJSON()
|
|
699
|
-
);
|
|
700
|
-
}
|
|
764
|
+
zValidator("json", GetGraphPayload),
|
|
765
|
+
handleInvoke("getGraph", GetGraphPayload, getGraphRequest)
|
|
701
766
|
);
|
|
702
767
|
|
|
703
768
|
app.post(
|
|
704
769
|
"/:graphId/getSubgraphs",
|
|
705
|
-
zValidator(
|
|
706
|
-
|
|
707
|
-
z.object({
|
|
708
|
-
namespace: z.string().nullish(),
|
|
709
|
-
recurse: z.boolean().nullish(),
|
|
710
|
-
})
|
|
711
|
-
),
|
|
712
|
-
|
|
713
|
-
async (c) => {
|
|
714
|
-
const graphId = c.req.param("graphId");
|
|
715
|
-
const graph = getGraph(graphId);
|
|
716
|
-
|
|
717
|
-
const payload = c.req.valid("json");
|
|
718
|
-
const result: Array<[name: string, Record<string, any>]> = [];
|
|
719
|
-
|
|
720
|
-
const graphSchema = await getOrExtractSchema(graphId);
|
|
721
|
-
const rootGraphId = Object.keys(graphSchema).find(
|
|
722
|
-
(i) => !i.includes("|")
|
|
723
|
-
);
|
|
724
|
-
|
|
725
|
-
if (!rootGraphId)
|
|
726
|
-
throw new HTTPException(500, { message: "Failed to find root graph" });
|
|
727
|
-
|
|
728
|
-
for (const [name] of graph.getSubgraphs(
|
|
729
|
-
payload.namespace ?? undefined,
|
|
730
|
-
payload.recurse ?? undefined
|
|
731
|
-
)) {
|
|
732
|
-
const schema =
|
|
733
|
-
graphSchema[`${rootGraphId}|${name}`] || graphSchema[rootGraphId];
|
|
734
|
-
result.push([name, schema]);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
return c.json(Object.fromEntries(result));
|
|
738
|
-
}
|
|
770
|
+
zValidator("json", GetSubgraphsPayload),
|
|
771
|
+
handleInvoke("getSubgraphs", GetSubgraphsPayload, getSubgraphsRequest)
|
|
739
772
|
);
|
|
740
773
|
|
|
741
774
|
app.post(
|
|
742
775
|
"/:graphId/getState",
|
|
743
|
-
zValidator(
|
|
744
|
-
|
|
745
|
-
z.object({
|
|
746
|
-
config: RunnableConfigSchema,
|
|
747
|
-
subgraphs: z.boolean().nullish(),
|
|
748
|
-
})
|
|
749
|
-
),
|
|
750
|
-
async (c) => {
|
|
751
|
-
const graph = getGraph(c.req.param("graphId"));
|
|
752
|
-
const payload = c.req.valid("json");
|
|
753
|
-
|
|
754
|
-
const state = await graph.getState(getRunnableConfig(payload.config), {
|
|
755
|
-
subgraphs: payload.subgraphs ?? undefined,
|
|
756
|
-
});
|
|
757
|
-
// TODO: just send the JSON directly, don't ser/de twice
|
|
758
|
-
return c.json(JSON.parse(serialiseAsDict(state)));
|
|
759
|
-
}
|
|
776
|
+
zValidator("json", GetStatePayload),
|
|
777
|
+
handleInvoke("getState", GetStatePayload, getStateRequest)
|
|
760
778
|
);
|
|
761
779
|
|
|
762
780
|
app.post(
|
|
763
781
|
"/:graphId/updateState",
|
|
764
|
-
zValidator(
|
|
765
|
-
|
|
766
|
-
z.object({
|
|
767
|
-
config: RunnableConfigSchema,
|
|
768
|
-
values: z.unknown(),
|
|
769
|
-
as_node: z.string().nullish(),
|
|
770
|
-
})
|
|
771
|
-
),
|
|
772
|
-
async (c) => {
|
|
773
|
-
const graph = getGraph(c.req.param("graphId"));
|
|
774
|
-
const payload = c.req.valid("json");
|
|
775
|
-
|
|
776
|
-
const config = await graph.updateState(
|
|
777
|
-
getRunnableConfig(payload.config),
|
|
778
|
-
payload.values,
|
|
779
|
-
payload.as_node ?? undefined
|
|
780
|
-
);
|
|
781
|
-
|
|
782
|
-
return c.json(config);
|
|
783
|
-
}
|
|
782
|
+
zValidator("json", UpdateStatePayload),
|
|
783
|
+
handleInvoke("updateState", UpdateStatePayload, updateStateRequest)
|
|
784
784
|
);
|
|
785
785
|
|
|
786
|
-
app.post(
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
}
|
|
792
|
-
return c.json(schemas[rootGraphId]);
|
|
793
|
-
});
|
|
786
|
+
app.post(
|
|
787
|
+
"/:graphId/getSchema",
|
|
788
|
+
zValidator("json", GetSchemaPayload),
|
|
789
|
+
handleInvoke("getSchema", GetSchemaPayload, getSchemaRequest)
|
|
790
|
+
);
|
|
794
791
|
|
|
795
792
|
app.post(
|
|
796
793
|
"/:graphId/getStateHistory",
|
|
797
|
-
zValidator(
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
filter: z.record(z.unknown()).nullish(),
|
|
804
|
-
})
|
|
805
|
-
),
|
|
806
|
-
async (c) => {
|
|
807
|
-
const graph = getGraph(c.req.param("graphId"));
|
|
808
|
-
const payload = c.req.valid("json");
|
|
809
|
-
|
|
810
|
-
return streamSSE(c, async (stream) => {
|
|
811
|
-
for await (const item of graph.getStateHistory(
|
|
812
|
-
getRunnableConfig(payload.config),
|
|
813
|
-
{
|
|
814
|
-
limit: payload.limit ?? undefined,
|
|
815
|
-
before: payload.before
|
|
816
|
-
? getRunnableConfig(payload.before)
|
|
817
|
-
: undefined,
|
|
818
|
-
filter: payload.filter ?? undefined,
|
|
819
|
-
}
|
|
820
|
-
)) {
|
|
821
|
-
await stream.writeSSE({
|
|
822
|
-
data: serialiseAsDict(item),
|
|
823
|
-
event: "getStateHistory",
|
|
824
|
-
});
|
|
825
|
-
}
|
|
826
|
-
});
|
|
827
|
-
}
|
|
794
|
+
zValidator("json", GetStateHistoryPayload),
|
|
795
|
+
handleStream(
|
|
796
|
+
"getStateHistory",
|
|
797
|
+
GetStateHistoryPayload,
|
|
798
|
+
getStateHistoryRequest
|
|
799
|
+
)
|
|
828
800
|
);
|
|
829
801
|
|
|
830
802
|
app.get("/ok", (c) => c.json({ ok: true }));
|
|
@@ -834,7 +806,9 @@ async function main() {
|
|
|
834
806
|
if (err instanceof HTTPException && err.status === 401) {
|
|
835
807
|
return err.getResponse();
|
|
836
808
|
}
|
|
837
|
-
|
|
809
|
+
|
|
810
|
+
const { message } = serializeError(err);
|
|
811
|
+
return c.text(message || "Internal server error", 500);
|
|
838
812
|
});
|
|
839
813
|
|
|
840
814
|
await fs.unlink(GRAPH_SOCKET).catch(() => void 0);
|