langgraph-api 0.1.15__tar.gz → 0.1.16__tar.gz

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.

Files changed (105) hide show
  1. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/PKG-INFO +1 -1
  2. langgraph_api-0.1.16/langgraph_api/__init__.py +1 -0
  3. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/base.py +3 -0
  4. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/client.mts +98 -42
  5. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/remote.py +17 -11
  6. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/auth.test.mts +1 -1
  7. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/parser.test.mts +18 -18
  8. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/server.py +2 -1
  9. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/sse.py +1 -1
  10. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/stream.py +10 -1
  11. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/pyproject.toml +2 -2
  12. langgraph_api-0.1.15/langgraph_api/__init__.py +0 -1
  13. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/LICENSE +0 -0
  14. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/README.md +0 -0
  15. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/api/__init__.py +0 -0
  16. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/api/assistants.py +0 -0
  17. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/api/mcp.py +0 -0
  18. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/api/meta.py +0 -0
  19. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/api/openapi.py +0 -0
  20. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/api/runs.py +0 -0
  21. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/api/store.py +0 -0
  22. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/api/threads.py +0 -0
  23. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/api/ui.py +0 -0
  24. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/asyncio.py +0 -0
  25. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/auth/__init__.py +0 -0
  26. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/auth/custom.py +0 -0
  27. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/auth/langsmith/__init__.py +0 -0
  28. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/auth/langsmith/backend.py +0 -0
  29. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/auth/langsmith/client.py +0 -0
  30. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/auth/middleware.py +0 -0
  31. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/auth/noop.py +0 -0
  32. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/auth/studio_user.py +0 -0
  33. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/cli.py +0 -0
  34. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/command.py +0 -0
  35. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/config.py +0 -0
  36. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/cron_scheduler.py +0 -0
  37. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/errors.py +0 -0
  38. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/graph.py +0 -0
  39. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/http.py +0 -0
  40. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/.gitignore +0 -0
  41. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/.prettierrc +0 -0
  42. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/__init__.py +0 -0
  43. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/build.mts +0 -0
  44. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/client.http.mts +0 -0
  45. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/errors.py +0 -0
  46. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/global.d.ts +0 -0
  47. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/package.json +0 -0
  48. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/schema.py +0 -0
  49. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/src/graph.mts +0 -0
  50. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/src/hooks.mjs +0 -0
  51. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/src/parser/parser.mts +0 -0
  52. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/src/parser/parser.worker.mjs +0 -0
  53. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/src/schema/types.mts +0 -0
  54. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/src/schema/types.template.mts +0 -0
  55. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/src/utils/files.mts +0 -0
  56. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/src/utils/importMap.mts +0 -0
  57. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
  58. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/src/utils/serde.mts +0 -0
  59. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/sse.py +0 -0
  60. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/api.test.mts +0 -0
  61. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/compose-postgres.auth.yml +0 -0
  62. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/compose-postgres.yml +0 -0
  63. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/.gitignore +0 -0
  64. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/agent.css +0 -0
  65. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/agent.mts +0 -0
  66. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/agent.ui.tsx +0 -0
  67. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/agent_simple.mts +0 -0
  68. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/auth.mts +0 -0
  69. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/command.mts +0 -0
  70. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/delay.mts +0 -0
  71. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/dynamic.mts +0 -0
  72. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/error.mts +0 -0
  73. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/http.mts +0 -0
  74. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/langgraph.json +0 -0
  75. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/nested.mts +0 -0
  76. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/package.json +0 -0
  77. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/weather.mts +0 -0
  78. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/graphs/yarn.lock +0 -0
  79. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/tests/utils.mts +0 -0
  80. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/ui.py +0 -0
  81. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/js/yarn.lock +0 -0
  82. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/logging.py +0 -0
  83. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/metadata.py +0 -0
  84. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/middleware/__init__.py +0 -0
  85. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/middleware/http_logger.py +0 -0
  86. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/middleware/private_network.py +0 -0
  87. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/models/__init__.py +0 -0
  88. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/models/run.py +0 -0
  89. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/patch.py +0 -0
  90. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/queue_entrypoint.py +0 -0
  91. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/route.py +0 -0
  92. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/schema.py +0 -0
  93. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/serde.py +0 -0
  94. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/state.py +0 -0
  95. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/thread_ttl.py +0 -0
  96. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/tunneling/cloudflare.py +0 -0
  97. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/utils.py +0 -0
  98. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/validation.py +0 -0
  99. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/webhook.py +0 -0
  100. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_api/worker.py +0 -0
  101. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_license/__init__.py +0 -0
  102. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_license/validation.py +0 -0
  103. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/langgraph_runtime/__init__.py +0 -0
  104. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/logging.json +0 -0
  105. {langgraph_api-0.1.15 → langgraph_api-0.1.16}/openapi.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: langgraph-api
3
- Version: 0.1.15
3
+ Version: 0.1.16
4
4
  Summary:
5
5
  License: Elastic-2.0
6
6
  Author: Nuno Campos
@@ -0,0 +1 @@
1
+ __version__ = "0.1.16"
@@ -27,3 +27,6 @@ class BaseRemotePregel(Runnable):
27
27
 
28
28
  # Config passed from get_graph()
29
29
  config: Config
30
+
31
+ async def get_nodes_executed(self) -> int:
32
+ return 0
@@ -76,7 +76,7 @@ const logger = createLogger({
76
76
  }
77
77
 
78
78
  return JSON.stringify({ timestamp, level, event, ...rest });
79
- })
79
+ }),
80
80
  ),
81
81
  transports: [
82
82
  new transports.Console({
@@ -91,6 +91,20 @@ let GRAPH_OPTIONS: {
91
91
  checkpointer?: BaseCheckpointSaver<string | number>;
92
92
  store?: BaseStore;
93
93
  } = {};
94
+ let nodesExecuted = 0;
95
+ function incrementNodes() {
96
+ nodesExecuted++;
97
+ }
98
+
99
+ const version = await (async () => {
100
+ try {
101
+ const packageJson = await import("@langchain/langgraph/package.json");
102
+ return packageJson["version"];
103
+ } catch (error) {
104
+ logger.error(error);
105
+ }
106
+ return undefined;
107
+ })();
94
108
 
95
109
  const GRAPH_RESOLVED: Record<
96
110
  string,
@@ -101,7 +115,7 @@ const GRAPH_SPEC: Record<string, GraphSpec> = {};
101
115
  async function getGraph(
102
116
  graphId: string,
103
117
  config: { configurable?: Record<string, unknown> },
104
- name: string | null | undefined
118
+ name: string | null | undefined,
105
119
  ) {
106
120
  if (!GRAPH_RESOLVED[graphId])
107
121
  throw new HTTPException(404, { message: `Graph "${graphId}" not found` });
@@ -145,7 +159,7 @@ async function getOrExtractSchema(graphId: string) {
145
159
  try {
146
160
  timeoutMs = Number.parseInt(
147
161
  process.env.LANGGRAPH_SCHEMA_RESOLVE_TIMEOUT_MS || "30000",
148
- 10
162
+ 10,
149
163
  );
150
164
  if (Number.isNaN(timeoutMs) || timeoutMs <= 0) timeoutMs = undefined;
151
165
  } catch {
@@ -178,7 +192,7 @@ const RunnableConfigSchema = z.object({
178
192
  });
179
193
 
180
194
  const getRunnableConfig = (
181
- userConfig: z.infer<typeof RunnableConfigSchema> | null | undefined
195
+ userConfig: z.infer<typeof RunnableConfigSchema> | null | undefined,
182
196
  ) => {
183
197
  if (!userConfig) return {};
184
198
  return {
@@ -214,13 +228,13 @@ function tryFetch(...args: Parameters<typeof fetch>) {
214
228
  factor: 2,
215
229
  minTimeout: 1000,
216
230
  onFailedAttempt: (error) => void logger.error(error),
217
- }
231
+ },
218
232
  );
219
233
  }
220
234
 
221
235
  async function sendRecv<T = any>(
222
236
  method: `${"checkpointer" | "store"}_${string}`,
223
- data: unknown
237
+ data: unknown,
224
238
  ): Promise<T> {
225
239
  const res = await tryFetch(`http://localhost:${REMOTE_PORT}/${method}`, {
226
240
  method: "POST",
@@ -240,7 +254,7 @@ const HEARTBEAT_MS = 5_000;
240
254
  const handleInvoke = <T extends z.ZodType<any>>(
241
255
  name: string,
242
256
  _schema: T,
243
- handler: (rawPayload: z.infer<T>) => Promise<any>
257
+ handler: (rawPayload: z.infer<T>) => Promise<any>,
244
258
  ) => {
245
259
  return async (c: Context<any, any, { in: z.infer<T>; out: any }>) => {
246
260
  const graphId = c.req.param("graphId");
@@ -262,7 +276,7 @@ const handleInvoke = <T extends z.ZodType<any>>(
262
276
  let interval = setInterval(() => enqueueWrite(" "), HEARTBEAT_MS);
263
277
 
264
278
  const response = JSON.stringify(
265
- await handler({ graph_id: graphId, ...body })
279
+ await handler({ graph_id: graphId, ...body }),
266
280
  );
267
281
 
268
282
  clearInterval(interval);
@@ -274,7 +288,7 @@ const handleInvoke = <T extends z.ZodType<any>>(
274
288
  const handleStream = <T extends z.ZodType<any>>(
275
289
  name: string,
276
290
  _schema: T,
277
- handler: (rawPayload: z.infer<T>) => AsyncGenerator<any, void, unknown>
291
+ handler: (rawPayload: z.infer<T>) => AsyncGenerator<any, void, unknown>,
278
292
  ) => {
279
293
  return (c: Context<any, any, { in: z.infer<T>; out: any }>) => {
280
294
  const graphId = c.req.param("graphId");
@@ -333,7 +347,7 @@ class RemoteCheckpointer extends BaseCheckpointSaver<number | string> {
333
347
  limit?: number;
334
348
  before?: RunnableConfig;
335
349
  filter?: Record<string, any>;
336
- }
350
+ },
337
351
  ): AsyncGenerator<CheckpointTuple> {
338
352
  const result = await sendRecv("checkpointer_list", { config, ...options });
339
353
 
@@ -352,7 +366,7 @@ class RemoteCheckpointer extends BaseCheckpointSaver<number | string> {
352
366
  config: RunnableConfig,
353
367
  checkpoint: Checkpoint,
354
368
  metadata: CheckpointMetadata,
355
- newVersions: ChannelVersions
369
+ newVersions: ChannelVersions,
356
370
  ): Promise<RunnableConfig> {
357
371
  return await sendRecv<RunnableConfig>("checkpointer_put", {
358
372
  config,
@@ -365,14 +379,14 @@ class RemoteCheckpointer extends BaseCheckpointSaver<number | string> {
365
379
  async putWrites(
366
380
  config: RunnableConfig,
367
381
  writes: [string, unknown][],
368
- taskId: string
382
+ taskId: string,
369
383
  ): Promise<void> {
370
384
  await sendRecv("checkpointer_put_writes", { config, writes, taskId });
371
385
  }
372
386
 
373
387
  getNextVersion(
374
388
  current: number | string | undefined,
375
- _channel: ChannelProtocol
389
+ _channel: ChannelProtocol,
376
390
  ): string {
377
391
  let currentVersion = 0;
378
392
 
@@ -402,7 +416,7 @@ function camelToSnake(operation: Operation) {
402
416
  Object.entries(obj).map(([key, value]) => {
403
417
  const snakeKey = key.replace(
404
418
  /[A-Z]/g,
405
- (letter) => `_${letter.toLowerCase()}`
419
+ (letter) => `_${letter.toLowerCase()}`,
406
420
  );
407
421
  if (
408
422
  typeof value === "object" &&
@@ -412,7 +426,7 @@ function camelToSnake(operation: Operation) {
412
426
  return [snakeKey, snakeCaseKeys(value)];
413
427
  }
414
428
  return [snakeKey, value];
415
- })
429
+ }),
416
430
  );
417
431
  };
418
432
 
@@ -459,7 +473,7 @@ function pyItemToJs(item?: PyItem): Item | undefined {
459
473
 
460
474
  export class RemoteStore extends BaseStore {
461
475
  async batch<Op extends Operation[]>(
462
- operations: Op
476
+ operations: Op,
463
477
  ): Promise<OperationResults<Op>> {
464
478
  const results = await sendRecv<PyResult[]>("store_batch", {
465
479
  operations: operations.map(camelToSnake),
@@ -493,7 +507,7 @@ export class RemoteStore extends BaseStore {
493
507
  filter?: Record<string, any>;
494
508
  limit?: number;
495
509
  offset?: number;
496
- }
510
+ },
497
511
  ): Promise<Item[]> {
498
512
  return await sendRecv<Item[]>("store_search", {
499
513
  namespace_prefix: namespacePrefix,
@@ -504,7 +518,7 @@ export class RemoteStore extends BaseStore {
504
518
  async put(
505
519
  namespace: string[],
506
520
  key: string,
507
- value: Record<string, any>
521
+ value: Record<string, any>,
508
522
  ): Promise<void> {
509
523
  await sendRecv("store_put", { namespace, key, value });
510
524
  }
@@ -522,7 +536,7 @@ export class RemoteStore extends BaseStore {
522
536
  }): Promise<string[][]> {
523
537
  const data = await sendRecv<{ namespaces: string[][] }>(
524
538
  "store_list_namespaces",
525
- { max_depth: options?.maxDepth, ...options }
539
+ { max_depth: options?.maxDepth, ...options },
526
540
  );
527
541
  return data.namespaces;
528
542
  }
@@ -569,7 +583,7 @@ const StreamEventsPayload = z.object({
569
583
  });
570
584
 
571
585
  function reviveCommand(
572
- command: z.infer<typeof StreamEventsPayload>["command"]
586
+ command: z.infer<typeof StreamEventsPayload>["command"],
573
587
  ): Command | undefined {
574
588
  if (command == null) return undefined;
575
589
  let { goto, update, resume, graph } = command;
@@ -589,12 +603,17 @@ function reviveCommand(
589
603
  }
590
604
 
591
605
  async function* streamEventsRequest(
592
- rawPayload: z.infer<typeof StreamEventsPayload>
606
+ rawPayload: z.infer<typeof StreamEventsPayload>,
593
607
  ) {
594
608
  const { graph_id: graphId, ...payload } = rawPayload;
595
609
  const config = getRunnableConfig(payload.config);
596
610
  const graph = await getGraph(graphId, config, payload.graph_name);
597
611
  const input = reviveCommand(payload.command) ?? payload.input;
612
+ // TODO Check if it's a remote graph and don't set in that case
613
+ config.configurable = {
614
+ ...config.configurable,
615
+ ["__pregel_node_finished"]: incrementNodes,
616
+ };
598
617
 
599
618
  const userStreamMode =
600
619
  payload.stream_mode == null
@@ -635,6 +654,11 @@ async function* streamEventsRequest(
635
654
 
636
655
  const streamMode = [...graphStreamMode];
637
656
 
657
+ if (version != null) {
658
+ config.metadata ??= {};
659
+ config.metadata.langgraph_version = version;
660
+ }
661
+
638
662
  for await (const data of graph.streamEvents(input, {
639
663
  ...config,
640
664
  version: "v2",
@@ -731,7 +755,7 @@ async function getGraphRequest(rawPayload: z.infer<typeof GetGraphPayload>) {
731
755
  const graph = await getGraph(
732
756
  graphId,
733
757
  getRunnableConfig(payload.graph_config),
734
- payload.graph_name
758
+ payload.graph_name,
735
759
  );
736
760
 
737
761
  const drawable = await graph.getGraphAsync({
@@ -750,7 +774,7 @@ const GetSubgraphsPayload = z.object({
750
774
  });
751
775
 
752
776
  async function getSubgraphsRequest(
753
- rawPayload: z.infer<typeof GetSubgraphsPayload>
777
+ rawPayload: z.infer<typeof GetSubgraphsPayload>,
754
778
  ) {
755
779
  const { graph_id: graphId, ...payload } = rawPayload;
756
780
  const graphConfig = getRunnableConfig(payload.graph_config);
@@ -764,7 +788,7 @@ async function getSubgraphsRequest(
764
788
 
765
789
  for await (const [name] of graph.getSubgraphsAsync(
766
790
  payload.namespace ?? undefined,
767
- payload.recurse ?? undefined
791
+ payload.recurse ?? undefined,
768
792
  )) {
769
793
  const schema =
770
794
  graphSchema[`${rootGraphId}|${name}`] || graphSchema[rootGraphId];
@@ -806,7 +830,7 @@ const UpdateStatePayload = z.object({
806
830
  });
807
831
 
808
832
  async function updateStateRequest(
809
- rawPayload: z.infer<typeof UpdateStatePayload>
833
+ rawPayload: z.infer<typeof UpdateStatePayload>,
810
834
  ) {
811
835
  const { graph_id: graphId, ...payload } = rawPayload;
812
836
  const graphConfig = getRunnableConfig(payload.graph_config);
@@ -815,7 +839,7 @@ async function updateStateRequest(
815
839
  const config = await graph.updateState(
816
840
  getRunnableConfig(payload.config),
817
841
  payload.values,
818
- payload.as_node ?? undefined
842
+ payload.as_node ?? undefined,
819
843
  );
820
844
 
821
845
  return config;
@@ -850,7 +874,7 @@ const GetStateHistoryPayload = z.object({
850
874
  });
851
875
 
852
876
  async function* getStateHistoryRequest(
853
- rawPayload: z.infer<typeof GetStateHistoryPayload>
877
+ rawPayload: z.infer<typeof GetStateHistoryPayload>,
854
878
  ) {
855
879
  const { graph_id: graphId, ...payload } = rawPayload;
856
880
  const config = getRunnableConfig(payload.graph_config);
@@ -862,7 +886,7 @@ async function* getStateHistoryRequest(
862
886
  limit: payload.limit ?? undefined,
863
887
  before: payload.before ? getRunnableConfig(payload.before) : undefined,
864
888
  filter: payload.filter ?? undefined,
865
- }
889
+ },
866
890
  )) {
867
891
  yield item;
868
892
  }
@@ -879,7 +903,9 @@ async function main() {
879
903
  };
880
904
 
881
905
  const specs = Object.entries(
882
- z.record(z.string()).parse(JSON.parse(process.env.LANGSERVE_GRAPHS ?? "{}"))
906
+ z
907
+ .record(z.string())
908
+ .parse(JSON.parse(process.env.LANGSERVE_GRAPHS ?? "{}")),
883
909
  ).filter(([_, spec]) => filterValidExportPath(spec));
884
910
 
885
911
  if (!process.argv.includes("--skip-schema-cache")) {
@@ -887,7 +913,7 @@ async function main() {
887
913
  GRAPH_SCHEMA = JSON.parse(
888
914
  await fs.readFile(path.resolve(__dirname, "client.schemas.json"), {
889
915
  encoding: "utf-8",
890
- })
916
+ }),
891
917
  );
892
918
  } catch {
893
919
  // pass
@@ -901,43 +927,43 @@ async function main() {
901
927
 
902
928
  GRAPH_RESOLVED[graphId] = resolved;
903
929
  GRAPH_SPEC[graphId] = spec;
904
- })
930
+ }),
905
931
  );
906
932
 
907
933
  app.post(
908
934
  "/:graphId/streamEvents",
909
935
  zValidator("json", StreamEventsPayload),
910
- handleStream("streamEvents", StreamEventsPayload, streamEventsRequest)
936
+ handleStream("streamEvents", StreamEventsPayload, streamEventsRequest),
911
937
  );
912
938
 
913
939
  app.post(
914
940
  "/:graphId/getGraph",
915
941
  zValidator("json", GetGraphPayload),
916
- handleInvoke("getGraph", GetGraphPayload, getGraphRequest)
942
+ handleInvoke("getGraph", GetGraphPayload, getGraphRequest),
917
943
  );
918
944
 
919
945
  app.post(
920
946
  "/:graphId/getSubgraphs",
921
947
  zValidator("json", GetSubgraphsPayload),
922
- handleInvoke("getSubgraphs", GetSubgraphsPayload, getSubgraphsRequest)
948
+ handleInvoke("getSubgraphs", GetSubgraphsPayload, getSubgraphsRequest),
923
949
  );
924
950
 
925
951
  app.post(
926
952
  "/:graphId/getState",
927
953
  zValidator("json", GetStatePayload),
928
- handleInvoke("getState", GetStatePayload, getStateRequest)
954
+ handleInvoke("getState", GetStatePayload, getStateRequest),
929
955
  );
930
956
 
931
957
  app.post(
932
958
  "/:graphId/updateState",
933
959
  zValidator("json", UpdateStatePayload),
934
- handleInvoke("updateState", UpdateStatePayload, updateStateRequest)
960
+ handleInvoke("updateState", UpdateStatePayload, updateStateRequest),
935
961
  );
936
962
 
937
963
  app.post(
938
964
  "/:graphId/getSchema",
939
965
  zValidator("json", GetSchemaPayload),
940
- handleInvoke("getSchema", GetSchemaPayload, getSchemaRequest)
966
+ handleInvoke("getSchema", GetSchemaPayload, getSchemaRequest),
941
967
  );
942
968
 
943
969
  app.post(
@@ -946,8 +972,26 @@ async function main() {
946
972
  handleStream(
947
973
  "getStateHistory",
948
974
  GetStateHistoryPayload,
949
- getStateHistoryRequest
950
- )
975
+ getStateHistoryRequest,
976
+ ),
977
+ );
978
+ app.post(
979
+ "/:graphId/getNodesExecuted",
980
+ zValidator("json", GetNodesExecutedPayload),
981
+ handleInvoke(
982
+ "getNodesExecuted",
983
+ GetNodesExecutedPayload,
984
+ getNodesExecutedRequest,
985
+ ),
986
+ );
987
+ app.post(
988
+ "/:graphId/getNodesExecuted",
989
+ zValidator("json", GetNodesExecutedPayload),
990
+ handleInvoke(
991
+ "getNodesExecuted",
992
+ GetNodesExecutedPayload,
993
+ getNodesExecutedRequest,
994
+ ),
951
995
  );
952
996
 
953
997
  // Load LANGGRAPH_AUTH
@@ -972,7 +1016,7 @@ async function main() {
972
1016
  headers.delete("x-langgraph-auth-method");
973
1017
 
974
1018
  const context = await authenticate(
975
- new Request(authUrl, { headers, method })
1019
+ new Request(authUrl, { headers, method }),
976
1020
  );
977
1021
 
978
1022
  return c.json(context);
@@ -984,7 +1028,7 @@ async function main() {
984
1028
  status: error.res?.status ?? error.status,
985
1029
  headers: error.res?.headers,
986
1030
  },
987
- error.status as StatusCode
1031
+ error.status as StatusCode,
988
1032
  );
989
1033
  }
990
1034
 
@@ -1018,7 +1062,7 @@ async function main() {
1018
1062
  });
1019
1063
 
1020
1064
  serve({ fetch: app.fetch, hostname: "localhost", port: GRAPH_PORT }, (c) =>
1021
- logger.info(`Listening to ${c.address}:${c.port}`)
1065
+ logger.info(`Listening to ${c.address}:${c.port}`),
1022
1066
  );
1023
1067
  }
1024
1068
 
@@ -1027,5 +1071,17 @@ process.on("uncaughtExceptionMonitor", (error) => {
1027
1071
  gracefulExit();
1028
1072
  });
1029
1073
 
1074
+ const GetNodesExecutedPayload = z.object({
1075
+ graph_id: z.string(),
1076
+ });
1077
+
1078
+ async function getNodesExecutedRequest(
1079
+ _payload: z.infer<typeof GetNodesExecutedPayload>,
1080
+ ) {
1081
+ const value = nodesExecuted;
1082
+ nodesExecuted = 0;
1083
+ return { nodesExecuted: value };
1084
+ }
1085
+
1030
1086
  asyncExitHook(() => awaitAllCallbacks(), { wait: 3_000 });
1031
1087
  main();
@@ -315,26 +315,30 @@ class RemotePregel(BaseRemotePregel):
315
315
  *,
316
316
  xray: int | bool = False,
317
317
  ) -> dict[str, Any]:
318
- raise Exception("Not implemented")
318
+ raise NotImplementedError()
319
319
 
320
320
  def get_input_schema(self, config: RunnableConfig | None = None) -> type[BaseModel]:
321
- raise Exception("Not implemented")
321
+ raise NotImplementedError()
322
322
 
323
323
  def get_output_schema(
324
324
  self, config: RunnableConfig | None = None
325
325
  ) -> type[BaseModel]:
326
- raise Exception("Not implemented")
326
+ raise NotImplementedError()
327
327
 
328
328
  def config_schema(self) -> type[BaseModel]:
329
- raise Exception("Not implemented")
329
+ raise NotImplementedError()
330
330
 
331
331
  async def invoke(self, input: Any, config: RunnableConfig | None = None):
332
- raise Exception("Not implemented")
332
+ raise NotImplementedError()
333
333
 
334
334
  def copy(self, update: dict[str, Any] | None = None) -> Self:
335
335
  attrs = {**self.__dict__, **(update or {})}
336
336
  return self.__class__(**attrs)
337
337
 
338
+ async def fetch_nodes_executed(self):
339
+ result = await _client_invoke("getNodesExecuted", {"graph_id": self.graph_id})
340
+ return result["nodesExecuted"]
341
+
338
342
 
339
343
  async def run_js_process(paths_str: str, watch: bool = False):
340
344
  # check if tsx is available
@@ -843,12 +847,14 @@ async def handle_js_auth_event(
843
847
  "resource": ctx.resource,
844
848
  "action": ctx.action,
845
849
  "value": value,
846
- "context": {
847
- "user": cast(DotDict, ctx.user).dict(),
848
- "scopes": ctx.permissions,
849
- }
850
- if ctx
851
- else None,
850
+ "context": (
851
+ {
852
+ "user": cast(DotDict, ctx.user).dict(),
853
+ "scopes": ctx.permissions,
854
+ }
855
+ if ctx
856
+ else None
857
+ ),
852
858
  }
853
859
  ),
854
860
  )
@@ -458,7 +458,7 @@ it("stream run other user thread", async () => {
458
458
  ]);
459
459
  });
460
460
 
461
- it("cancel run other user thread", async () => {
461
+ it("cancel run other user thread", { retry: 3 }, async () => {
462
462
  const owner = await createJwtClient("johndoe", ["me"]);
463
463
  const otherUser = await createJwtClient("alice", ["me"]);
464
464
 
@@ -61,7 +61,7 @@ describe
61
61
  ])("%s", { timeout: 10_000 }, ([prop]) => {
62
62
  const schemas = SubgraphExtractor.extractSchemas(
63
63
  { contents: `${common}\n\nexport const graph = ${prop};` },
64
- "graph"
64
+ "graph",
65
65
  );
66
66
 
67
67
  expect(schemas.graph.input).toMatchObject(MessagesSchema);
@@ -114,7 +114,7 @@ describe
114
114
  .compile();
115
115
  `,
116
116
  },
117
- "graph"
117
+ "graph",
118
118
  );
119
119
  expect(schemas["graph|child"].input).toMatchObject({
120
120
  type: "object",
@@ -233,7 +233,7 @@ describe
233
233
  .compile();
234
234
  `,
235
235
  },
236
- "parent"
236
+ "parent",
237
237
  );
238
238
 
239
239
  expect(Object.keys(schemas)).toEqual(
@@ -241,7 +241,7 @@ describe
241
241
  "parent",
242
242
  "parent|parent_two",
243
243
  "parent|parent_two|child_two",
244
- ])
244
+ ]),
245
245
  );
246
246
 
247
247
  expect(schemas.parent.state).toMatchObject({
@@ -362,12 +362,12 @@ describe
362
362
  `,
363
363
  },
364
364
  "parent",
365
- { strict: true }
365
+ { strict: true },
366
366
  );
367
367
  }).toThrowError(
368
- `Multiple unique subgraph invocations found for "parent|parent_one"`
368
+ `Multiple unique subgraph invocations found for "parent|parent_one"`,
369
369
  );
370
- }
370
+ },
371
371
  );
372
372
 
373
373
  test.concurrent("imported subgraphs", { timeout: 10_000 }, () => {
@@ -425,11 +425,11 @@ describe
425
425
  ],
426
426
  ],
427
427
  },
428
- "graph"
428
+ "graph",
429
429
  );
430
430
 
431
431
  expect(Object.keys(schemas)).toEqual(
432
- expect.arrayContaining(["graph", "graph|child"])
432
+ expect.arrayContaining(["graph", "graph|child"]),
433
433
  );
434
434
 
435
435
  expect(schemas["graph|child"].input).toMatchObject({
@@ -561,11 +561,11 @@ describe
561
561
  ],
562
562
  ],
563
563
  },
564
- "graph"
564
+ "graph",
565
565
  );
566
566
 
567
567
  expect(Object.keys(schemas)).toEqual(
568
- expect.arrayContaining(["graph", "graph|child"])
568
+ expect.arrayContaining(["graph", "graph|child"]),
569
569
  );
570
570
 
571
571
  expect(schemas["graph|child"].input).toMatchObject({
@@ -638,7 +638,7 @@ describe
638
638
  type: "object",
639
639
  $schema: "http://json-schema.org/draft-07/schema#",
640
640
  });
641
- }
641
+ },
642
642
  );
643
643
 
644
644
  test.concurrent("indirect", { timeout: 10_000 }, () => {
@@ -680,7 +680,7 @@ describe
680
680
  export const graph = parent.compile()
681
681
  `,
682
682
  },
683
- "graph"
683
+ "graph",
684
684
  );
685
685
  expect(schemas["graph|child"].input).toMatchObject({
686
686
  type: "object",
@@ -755,7 +755,7 @@ describe
755
755
  });
756
756
  });
757
757
 
758
- test.skipIf(isParserSkipped).concurrent("weather", { timeout: 10_000 }, () => {
758
+ test.skipIf(isParserSkipped).concurrent("weather", { timeout: 20_000 }, () => {
759
759
  const schemas = SubgraphExtractor.extractSchemas(
760
760
  {
761
761
  contents: dedent`
@@ -818,11 +818,11 @@ test.skipIf(isParserSkipped).concurrent("weather", { timeout: 10_000 }, () => {
818
818
  export const graph = router.compile();
819
819
  `,
820
820
  },
821
- "graph"
821
+ "graph",
822
822
  );
823
823
 
824
824
  expect(Object.keys(schemas)).toEqual(
825
- expect.arrayContaining(["graph", "graph|weather_graph"])
825
+ expect.arrayContaining(["graph", "graph|weather_graph"]),
826
826
  );
827
827
  });
828
828
 
@@ -876,10 +876,10 @@ test.skipIf(isParserSkipped).concurrent("nested", { timeout: 10_000 }, () => {
876
876
  export const graph = grandParent.compile();
877
877
  `,
878
878
  },
879
- "graph"
879
+ "graph",
880
880
  );
881
881
 
882
882
  expect(Object.keys(schemas)).toEqual(
883
- expect.arrayContaining(["graph", "graph|gp_two", "graph|gp_two|p_two"])
883
+ expect.arrayContaining(["graph", "graph|gp_two", "graph|gp_two|p_two"]),
884
884
  );
885
885
  });
@@ -60,6 +60,7 @@ middleware.extend(
60
60
  allow_credentials=True,
61
61
  allow_methods=["*"],
62
62
  allow_headers=["*"],
63
+ expose_headers=["x-pagination-total"],
63
64
  )
64
65
  if config.CORS_CONFIG is None
65
66
  else Middleware(
@@ -184,7 +185,7 @@ if config.MOUNT_PREFIX:
184
185
  # The SDK initialized with None is trying to connect via
185
186
  # ASGITransport. Ensure that it has the correct subpath prefixes
186
187
  # so the regular router can handle it.
187
- scope["path"] = f'/noauth{prefix}{scope["path"]}'
188
+ scope["path"] = f"/noauth{prefix}{scope['path']}"
188
189
  scope["raw_path"] = scope["path"].encode("utf-8")
189
190
 
190
191
  return await self.app(scope, receive, send)
@@ -77,7 +77,7 @@ class EventSourceResponse(sse_starlette.EventSourceResponse):
77
77
  {
78
78
  "type": "http.response.body",
79
79
  "body": json_to_sse(b"error", exc),
80
- "more_body": False,
80
+ "more_body": True,
81
81
  }
82
82
  )
83
83
 
@@ -116,7 +116,8 @@ async def astream_state(
116
116
  config["metadata"]["langgraph_host"] = HOST
117
117
  config["metadata"]["langgraph_api_url"] = USER_API_URL
118
118
  # attach node counter
119
- if not isinstance(graph, BaseRemotePregel):
119
+ is_remote_pregel = isinstance(graph, BaseRemotePregel)
120
+ if not is_remote_pregel:
120
121
  config["configurable"]["__pregel_node_finished"] = incr_nodes
121
122
  # TODO add node tracking for JS graphs
122
123
  # attach run_id to config
@@ -244,6 +245,14 @@ async def astream_state(
244
245
  else:
245
246
  yield mode, chunk
246
247
  # --- end shared logic with astream_events ---
248
+ if is_remote_pregel:
249
+ # increament the remote runs
250
+ try:
251
+ nodes_executed = await graph.fetch_nodes_executed()
252
+ incr_nodes(None, incr=nodes_executed)
253
+ except Exception as e:
254
+ logger.warning(f"Failed to fetch nodes executed for {graph.graph_id}: {e}")
255
+
247
256
  # Get feedback URLs
248
257
  if feedback_keys:
249
258
  feedback_urls = await run_in_executor(
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langgraph-api"
3
- version = "0.1.15"
3
+ version = "0.1.16"
4
4
  description = ""
5
5
  authors = [
6
6
  "Nuno Campos <nuno@langchain.dev>",
@@ -63,7 +63,7 @@ pytest-repeat = "^0.9.3"
63
63
  pytest-retry = "^1.6.3"
64
64
  pytest-httpserver = "^1.1.0"
65
65
  fastapi = "^0.115.8"
66
- langgraph = ">=0.3.17"
66
+ langgraph = ">=0.3.32"
67
67
  pycryptodome = "^3.22.0"
68
68
  langgraph-runtime-inmem = { path = "../runtime_inmem", develop = true, python = ">=3.11,<4.0" }
69
69
  blockbuster = "^1.5.24"
@@ -1 +0,0 @@
1
- __version__ = "0.1.15"
File without changes
File without changes