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.

@@ -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 CHECKPOINTER_SOCKET = "./checkpointer.sock";
106
- const STORE_SOCKET = "./store.sock";
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
- class RemoteCheckpointer extends BaseCheckpointSaver<number | string> {
165
- async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {
166
- const res = await tryFetch("http://checkpointer/get_tuple", {
167
- dispatcher: checkpointerDispatcher,
168
- method: "POST",
169
- headers: { "Content-Type": "application/json" },
170
- body: JSON.stringify({ config }),
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
- const text = await res.text();
174
- const result = (await load(text, {
175
- importMap,
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 res = await tryFetch("http://checkpointer/list", {
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
- } satisfies CheckpointTuple;
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
- const response = await tryFetch("http://checkpointer/put", {
230
- dispatcher: checkpointerDispatcher,
231
- method: "POST",
232
- headers: { "Content-Type": "application/json" },
233
- body: JSON.stringify({
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
- // Implementation of the inherited abstract member 'putWrites'
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 response = await tryFetch("http://store/items/batch", {
350
- dispatcher: storeDispatcher,
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
- const queryParams = new URLSearchParams({
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
- const response = await tryFetch("http://store/items/search", {
394
- dispatcher: storeDispatcher,
395
- method: "POST",
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 tryFetch("http://store/items", {
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 tryFetch("http://store/items", {
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 response = await tryFetch("http://store/list/namespaces", {
432
- dispatcher: storeDispatcher,
433
- method: "POST",
434
- headers: { "Content-Type": "application/json" },
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
- "json",
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
- "json",
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
- "json",
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
- "json",
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
- "json",
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("/:graphId/getSchema", async (c) => {
787
- const schemas = await getOrExtractSchema(c.req.param("graphId"));
788
- const rootGraphId = Object.keys(schemas).find((i) => !i.includes("|"));
789
- if (!rootGraphId) {
790
- throw new HTTPException(500, { message: "Failed to find root graph" });
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
- "json",
799
- z.object({
800
- config: RunnableConfigSchema,
801
- limit: z.number().nullish(),
802
- before: RunnableConfigSchema.nullish(),
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
- return c.text("Internal server error", 500);
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);