langgraph-api 0.0.5__py3-none-any.whl → 0.0.7__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/runs.py CHANGED
@@ -283,10 +283,18 @@ async def list_runs_http(
283
283
  validate_uuid(thread_id, "Invalid thread ID: must be a UUID")
284
284
  limit = int(request.query_params.get("limit", 10))
285
285
  offset = int(request.query_params.get("offset", 0))
286
+ status = request.query_params.get("status")
286
287
  async with connect() as conn, conn.pipeline():
287
288
  thread, runs = await asyncio.gather(
288
289
  Threads.get(conn, thread_id),
289
- Runs.search(conn, thread_id, limit=limit, offset=offset, metadata=None),
290
+ Runs.search(
291
+ conn,
292
+ thread_id,
293
+ limit=limit,
294
+ offset=offset,
295
+ status=status,
296
+ metadata=None,
297
+ ),
290
298
  )
291
299
  await fetchone(thread)
292
300
  return ApiResponse([run async for run in runs])
@@ -323,9 +331,15 @@ async def join_run_stream_endpoint(request: ApiRequest):
323
331
  """Wait for a run to finish."""
324
332
  thread_id = request.path_params["thread_id"]
325
333
  run_id = request.path_params["run_id"]
334
+ cancel_on_disconnect_str = request.query_params.get("cancel_on_disconnect", "false")
335
+ cancel_on_disconnect = cancel_on_disconnect_str.lower() in {"true", "yes", "1"}
326
336
  validate_uuid(thread_id, "Invalid thread ID: must be a UUID")
327
337
  validate_uuid(run_id, "Invalid run ID: must be a UUID")
328
- return EventSourceResponse(Runs.Stream.join(run_id, thread_id=thread_id))
338
+ return EventSourceResponse(
339
+ Runs.Stream.join(
340
+ run_id, thread_id=thread_id, cancel_on_disconnect=cancel_on_disconnect
341
+ )
342
+ )
329
343
 
330
344
 
331
345
  @retry_db
@@ -8,7 +8,6 @@ from langgraph_api.validation import (
8
8
  StorePutRequest,
9
9
  StoreSearchRequest,
10
10
  )
11
- from langgraph_storage.database import connect
12
11
  from langgraph_storage.retry import retry_db
13
12
  from langgraph_storage.store import Store
14
13
 
@@ -31,8 +30,7 @@ async def put_item(request: ApiRequest):
31
30
  return err
32
31
  key = payload["key"]
33
32
  value = payload["value"]
34
- async with connect() as conn:
35
- await Store(conn).aput(namespace, key, value)
33
+ await Store().aput(namespace, key, value)
36
34
  return Response(status_code=204)
37
35
 
38
36
 
@@ -45,8 +43,7 @@ async def get_item(request: ApiRequest):
45
43
  key = request.query_params.get("key")
46
44
  if not key:
47
45
  return ApiResponse({"error": "Key is required"}, status_code=400)
48
- async with connect() as conn:
49
- result = await Store(conn).aget(namespace, key)
46
+ result = await Store().aget(namespace, key)
50
47
  return ApiResponse(result.dict() if result is not None else None)
51
48
 
52
49
 
@@ -58,8 +55,7 @@ async def delete_item(request: ApiRequest):
58
55
  if err := _validate_namespace(namespace):
59
56
  return err
60
57
  key = payload["key"]
61
- async with connect() as conn:
62
- await Store(conn).adelete(namespace, key)
58
+ await Store().adelete(namespace, key)
63
59
  return Response(status_code=204)
64
60
 
65
61
 
@@ -74,14 +70,13 @@ async def search_items(request: ApiRequest):
74
70
  limit = payload.get("limit") or 10
75
71
  offset = payload.get("offset") or 0
76
72
  query = payload.get("query")
77
- async with connect() as conn:
78
- items = await Store(conn).asearch(
79
- namespace_prefix,
80
- filter=filter,
81
- limit=limit,
82
- offset=offset,
83
- query=query,
84
- )
73
+ items = await Store().asearch(
74
+ namespace_prefix,
75
+ filter=filter,
76
+ limit=limit,
77
+ offset=offset,
78
+ query=query,
79
+ )
85
80
  return ApiResponse({"items": [item.dict() for item in items]})
86
81
 
87
82
 
@@ -98,14 +93,13 @@ async def list_namespaces(request: ApiRequest):
98
93
  max_depth = payload.get("max_depth")
99
94
  limit = payload.get("limit", 100)
100
95
  offset = payload.get("offset", 0)
101
- async with connect() as conn:
102
- result = await Store(conn).alist_namespaces(
103
- prefix=prefix,
104
- suffix=suffix,
105
- max_depth=max_depth,
106
- limit=limit,
107
- offset=offset,
108
- )
96
+ result = await Store().alist_namespaces(
97
+ prefix=prefix,
98
+ suffix=suffix,
99
+ max_depth=max_depth,
100
+ limit=limit,
101
+ offset=offset,
102
+ )
109
103
  return ApiResponse({"namespaces": result})
110
104
 
111
105
 
langgraph_api/cli.py CHANGED
@@ -94,10 +94,6 @@ class StoreConfig(TypedDict, total=False):
94
94
  index: IndexConfig
95
95
 
96
96
 
97
- class Config(TypedDict, total=False):
98
- store: StoreConfig | None
99
-
100
-
101
97
  def run_server(
102
98
  host: str = "127.0.0.1",
103
99
  port: int = 2024,
@@ -107,10 +103,11 @@ def run_server(
107
103
  env_file: str | None = None,
108
104
  open_browser: bool = False,
109
105
  debug_port: int | None = None,
106
+ wait_for_client: bool = False,
110
107
  env: str | pathlib.Path | Mapping[str, str] | None = None,
111
108
  reload_includes: Sequence[str] | None = None,
112
109
  reload_excludes: Sequence[str] | None = None,
113
- config: Config | None = None,
110
+ store: StoreConfig | None = None,
114
111
  **kwargs: Any,
115
112
  ):
116
113
  """Run the LangGraph API server."""
@@ -148,8 +145,9 @@ def run_server(
148
145
  logger.info(" - Host: 0.0.0.0")
149
146
  logger.info(f" - Port: {debug_port}")
150
147
  logger.info("3. Start the debugger to connect to the server.")
151
- debugpy.wait_for_client()
152
- logger.info("Debugger attached. Starting server...")
148
+ if wait_for_client:
149
+ debugpy.wait_for_client()
150
+ logger.info("Debugger attached. Starting server...")
153
151
 
154
152
  local_url = f"http://{host}:{port}"
155
153
  studio_url = f"https://smith.langchain.com/studio/?baseUrl={local_url}"
@@ -213,7 +211,7 @@ For production use, please use LangGraph Cloud.
213
211
  DATABASE_URI=":memory:",
214
212
  REDIS_URI="fake",
215
213
  N_JOBS_PER_WORKER=str(n_jobs_per_worker if n_jobs_per_worker else 1),
216
- LANGGRAPH_CONFIG=json.dumps(config) if config else None,
214
+ LANGGRAPH_STORE=json.dumps(store) if store else None,
217
215
  LANGSERVE_GRAPHS=json.dumps(graphs) if graphs else None,
218
216
  LANGSMITH_LANGGRAPH_API_VARIANT="local_dev",
219
217
  **(env_vars or {}),
@@ -274,6 +272,11 @@ def main():
274
272
  parser.add_argument(
275
273
  "--debug-port", type=int, help="Port for debugger to listen on (default: none)"
276
274
  )
275
+ parser.add_argument(
276
+ "--wait-for-client",
277
+ action="store_true",
278
+ help="Whether to break and wait for a debugger to attach",
279
+ )
277
280
 
278
281
  args = parser.parse_args()
279
282
 
@@ -289,6 +292,7 @@ def main():
289
292
  n_jobs_per_worker=args.n_jobs_per_worker,
290
293
  open_browser=not args.no_browser,
291
294
  debug_port=args.debug_port,
295
+ wait_for_client=args.wait_for_client,
292
296
  env=config_data.get("env", None),
293
297
  )
294
298
 
langgraph_api/graph.py CHANGED
@@ -196,6 +196,12 @@ async def collect_graphs_from_env(register: bool = False) -> None:
196
196
  py_specs = list(filterfalse(is_js_spec, specs))
197
197
 
198
198
  if js_specs:
199
+ if os.environ.get("LANGSMITH_LANGGRAPH_API_VARIANT") == "local_dev":
200
+ raise NotImplementedError(
201
+ "LangGraph.JS graphs are not yet supported in local development mode. "
202
+ "To run your JS graphs, either use the LangGraph Studio application "
203
+ "or run `langgraph up` to start the server in a Docker container."
204
+ )
199
205
  import sys
200
206
 
201
207
  from langgraph_api.js.remote import (
@@ -9,6 +9,7 @@ import {
9
9
  BaseStore,
10
10
  Item,
11
11
  Operation,
12
+ Command,
12
13
  OperationResults,
13
14
  type Checkpoint,
14
15
  type CheckpointMetadata,
@@ -459,7 +460,7 @@ async function main() {
459
460
 
460
461
  const specs = z
461
462
  .record(z.string())
462
- .parse(JSON.parse(process.env.LANGSERVE_GRAPHS));
463
+ .parse(JSON.parse(process.env.LANGSERVE_GRAPHS ?? "{}"));
463
464
 
464
465
  if (!process.argv.includes("--skip-schema-cache")) {
465
466
  try {
@@ -495,6 +496,7 @@ async function main() {
495
496
  "json",
496
497
  z.object({
497
498
  input: z.unknown(),
499
+ command: z.object({ resume: z.unknown() }).nullish(),
498
500
  stream_mode: z
499
501
  .union([ExtraStreamModeSchema, z.array(ExtraStreamModeSchema)])
500
502
  .optional(),
@@ -512,6 +514,11 @@ async function main() {
512
514
  const graph = getGraph(c.req.param("graphId"));
513
515
  const payload = c.req.valid("json");
514
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
+
515
522
  const userStreamMode =
516
523
  payload.stream_mode == null
517
524
  ? []
@@ -551,7 +558,7 @@ async function main() {
551
558
  const streamMode = [...graphStreamMode];
552
559
 
553
560
  try {
554
- for await (const data of graph.streamEvents(payload.input, {
561
+ for await (const data of graph.streamEvents(input, {
555
562
  ...config,
556
563
  version: "v2",
557
564
  streamMode,
@@ -9,8 +9,8 @@
9
9
  "dependencies": {
10
10
  "@hono/node-server": "^1.12.0",
11
11
  "@hono/zod-validator": "^0.2.2",
12
- "@langchain/core": "^0.3.17",
13
- "@langchain/langgraph": "^0.2.23",
12
+ "@langchain/core": "^0.3.22",
13
+ "@langchain/langgraph": "^0.2.26",
14
14
  "@types/json-schema": "^7.0.15",
15
15
  "@typescript/vfs": "^1.6.0",
16
16
  "dedent": "^1.5.3",
@@ -24,7 +24,7 @@
24
24
  "zod": "^3.23.8"
25
25
  },
26
26
  "devDependencies": {
27
- "@langchain/langgraph-sdk": "^0.0.19",
27
+ "@langchain/langgraph-sdk": "^0.0.31",
28
28
  "@types/node": "^22.2.0",
29
29
  "postgres": "^3.4.4",
30
30
  "prettier": "^3.3.3",
@@ -20,7 +20,7 @@ from langchain_core.runnables.schema import (
20
20
  from langgraph.checkpoint.serde.base import SerializerProtocol
21
21
  from langgraph.pregel.types import PregelTask, StateSnapshot
22
22
  from langgraph.store.base import GetOp, Item, ListNamespacesOp, PutOp, SearchOp
23
- from langgraph.types import Interrupt
23
+ from langgraph.types import Command, Interrupt
24
24
  from pydantic import BaseModel
25
25
  from starlette.applications import Starlette
26
26
  from starlette.requests import Request
@@ -91,12 +91,18 @@ class RemotePregel(Runnable):
91
91
  if version != "v2":
92
92
  raise ValueError("Only v2 of astream_events is supported")
93
93
 
94
+ data = {
95
+ "command" if isinstance(input, Command) else "input": input,
96
+ "config": config,
97
+ **kwargs,
98
+ }
99
+
94
100
  async with aconnect_sse(
95
101
  self._async_client,
96
102
  "POST",
97
103
  f"/{self.graph_id}/streamEvents",
98
104
  headers={"Content-Type": "application/json"},
99
- data=orjson.dumps({"input": input, "config": config, **kwargs}),
105
+ data=orjson.dumps(data),
100
106
  ) as event_source:
101
107
  async for sse in event_source.aiter_sse():
102
108
  event = orjson.loads(sse["data"])
@@ -173,15 +179,19 @@ class RemotePregel(Runnable):
173
179
  tuple(task["path"]) if task.get("path") else tuple(),
174
180
  # TODO: figure out how to properly deserialise errors
175
181
  task.get("error"),
176
- tuple(
177
- Interrupt(
178
- value=interrupt["value"],
179
- when=interrupt["when"],
182
+ (
183
+ tuple(
184
+ Interrupt(
185
+ value=interrupt["value"],
186
+ when=interrupt["when"],
187
+ resumable=interrupt.get("resumable", True),
188
+ ns=interrupt.get("ns"),
189
+ )
190
+ for interrupt in task.get("interrupts")
180
191
  )
181
- for interrupt in task.get("interrupts")
182
- )
183
- if task.get("interrupts")
184
- else [],
192
+ if task.get("interrupts")
193
+ else []
194
+ ),
185
195
  state,
186
196
  )
187
197
  )
@@ -413,17 +423,13 @@ async def run_remote_checkpointer():
413
423
  await server.serve()
414
424
 
415
425
 
416
- def _get_passthrough_store(conn: AsyncConnectionProto):
426
+ def _get_passthrough_store():
417
427
  from langgraph_storage.store import Store
418
428
 
419
- store = Store(conn)
420
-
421
- return store
429
+ return Store()
422
430
 
423
431
 
424
432
  async def run_remote_store():
425
- from langgraph_storage.database import connect
426
-
427
433
  async def abatch(request: Request):
428
434
  payload = orjson.loads(await request.body())
429
435
  operations = payload.get("operations", [])
@@ -470,9 +476,8 @@ async def run_remote_store():
470
476
  {"error": f"Unknown operation type: {op}"}, status_code=400
471
477
  )
472
478
 
473
- async with connect() as conn:
474
- store = _get_passthrough_store(conn)
475
- results = await store.abatch(processed_operations)
479
+ store = _get_passthrough_store()
480
+ results = await store.abatch(processed_operations)
476
481
 
477
482
  # Handle potentially undefined or non-dict results
478
483
  processed_results = []
@@ -512,9 +517,8 @@ async def run_remote_store():
512
517
 
513
518
  namespaces = namespaces_str.split(".")
514
519
 
515
- async with connect() as conn:
516
- store = _get_passthrough_store(conn)
517
- result = await store.aget(namespaces, key)
520
+ store = _get_passthrough_store()
521
+ result = await store.aget(namespaces, key)
518
522
 
519
523
  return ApiResponse(result)
520
524
 
@@ -524,10 +528,10 @@ async def run_remote_store():
524
528
  namespace = tuple(payload["namespace"].split("."))
525
529
  key = payload["key"]
526
530
  value = payload["value"]
531
+ index = payload.get("index")
527
532
 
528
- async with connect() as conn:
529
- store = _get_passthrough_store(conn)
530
- await store.aput(namespace, key, value)
533
+ store = _get_passthrough_store()
534
+ await store.aput(namespace, key, value, index=index)
531
535
 
532
536
  return ApiResponse({"success": True})
533
537
 
@@ -538,12 +542,12 @@ async def run_remote_store():
538
542
  filter = payload.get("filter")
539
543
  limit = payload.get("limit", 10)
540
544
  offset = payload.get("offset", 0)
545
+ query = payload.get("query")
541
546
 
542
- async with connect() as conn:
543
- store = _get_passthrough_store(conn)
544
- result = await store.asearch(
545
- namespace_prefix, filter=filter, limit=limit, offset=offset
546
- )
547
+ store = _get_passthrough_store()
548
+ result = await store.asearch(
549
+ namespace_prefix, filter=filter, limit=limit, offset=offset, query=query
550
+ )
547
551
 
548
552
  return ApiResponse([item.dict() for item in result])
549
553
 
@@ -553,9 +557,8 @@ async def run_remote_store():
553
557
  namespace = tuple(payload["namespace"])
554
558
  key = payload["key"]
555
559
 
556
- async with connect() as conn:
557
- store = _get_passthrough_store(conn)
558
- await store.adelete(namespace, key)
560
+ store = _get_passthrough_store()
561
+ await store.adelete(namespace, key)
559
562
 
560
563
  return ApiResponse({"success": True})
561
564
 
@@ -568,15 +571,14 @@ async def run_remote_store():
568
571
  limit = payload.get("limit", 100)
569
572
  offset = payload.get("offset", 0)
570
573
 
571
- async with connect() as conn:
572
- store = _get_passthrough_store(conn)
573
- result = await store.alist_namespaces(
574
- prefix=prefix,
575
- suffix=suffix,
576
- max_depth=max_depth,
577
- limit=limit,
578
- offset=offset,
579
- )
574
+ store = _get_passthrough_store()
575
+ result = await store.alist_namespaces(
576
+ prefix=prefix,
577
+ suffix=suffix,
578
+ max_depth=max_depth,
579
+ limit=limit,
580
+ offset=offset,
581
+ )
580
582
 
581
583
  return ApiResponse([list(ns) for ns in result])
582
584
 
@@ -20,6 +20,20 @@ export interface GraphSpec {
20
20
  exportSymbol: string;
21
21
  }
22
22
 
23
+ export async function resolveGraph(
24
+ spec: string,
25
+ options?: { onlyFilePresence?: false }
26
+ ): Promise<{
27
+ sourceFile: string;
28
+ exportSymbol: string;
29
+ resolved: CompiledGraph<string>;
30
+ }>;
31
+
32
+ export async function resolveGraph(
33
+ spec: string,
34
+ options: { onlyFilePresence: true }
35
+ ): Promise<{ sourceFile: string; exportSymbol: string; resolved: undefined }>;
36
+
23
37
  export async function resolveGraph(
24
38
  spec: string,
25
39
  options?: { onlyFilePresence?: boolean }
@@ -1,16 +1,17 @@
1
1
  // This hook is to ensure that @langchain/langgraph package
2
2
  // found in /api folder has precendence compared to user-provided package
3
3
  // found in /deps. Does not attempt to semver check for too old packages.
4
- export async function resolve(specifier, context, nextResolve) {
4
+ const OVERRIDE_RESOLVE = [
5
+ "@langchain/langgraph",
6
+ "@langchain/langgraph-checkpoint",
7
+ ];
8
+
9
+ export const resolve = async (specifier, context, nextResolve) => {
5
10
  const parentURL = new URL("./graph.mts", import.meta.url).toString();
6
11
 
7
- if (specifier.startsWith("@langchain/langgraph")) {
8
- try {
9
- return nextResolve(specifier, { ...context, parentURL });
10
- } catch (error) {
11
- return nextResolve(specifier, context);
12
- }
12
+ if (OVERRIDE_RESOLVE.includes(specifier)) {
13
+ return nextResolve(specifier, { ...context, parentURL });
13
14
  }
14
15
 
15
16
  return nextResolve(specifier, context);
16
- }
17
+ };
@@ -447,6 +447,35 @@ describe("runs", () => {
447
447
  await sql`DELETE FROM store`;
448
448
  });
449
449
 
450
+ it.skip.concurrent("list runs", async () => {
451
+ const assistant = await client.assistants.create({ graphId: "agent" });
452
+ const thread = await client.threads.create();
453
+ await client.runs.wait(thread.thread_id, assistant.assistant_id, {
454
+ input: { messages: [{ type: "human", content: "foo" }] },
455
+ config: globalConfig,
456
+ });
457
+
458
+ const pendingRun = await client.runs.create(
459
+ thread.thread_id,
460
+ assistant.assistant_id,
461
+ {
462
+ input: { messages: [{ type: "human", content: "bar" }] },
463
+ config: globalConfig,
464
+ }
465
+ );
466
+
467
+ let runs = await client.runs.list(thread.thread_id);
468
+ expect(runs.length).toBe(2);
469
+
470
+ runs = await client.runs.list(thread.thread_id, { status: "pending" });
471
+ expect(runs.length).toBe(1);
472
+
473
+ await client.runs.cancel(thread.thread_id, pendingRun.run_id);
474
+
475
+ runs = await client.runs.list(thread.thread_id, { status: "interrupted" });
476
+ expect(runs.length).toBe(1);
477
+ });
478
+
450
479
  it.concurrent("stream values", async () => {
451
480
  const assistant = await client.assistants.create({ graphId: "agent" });
452
481
  const thread = await client.threads.create();
@@ -1500,6 +1529,63 @@ describe("subgraphs", () => {
1500
1529
  expect(innerHistory[0].values.messages.length).toBe(2);
1501
1530
  expect(innerHistory[innerHistory.length - 1].next).toEqual(["__start__"]);
1502
1531
  });
1532
+
1533
+ it.concurrent("interrupt inside node", async () => {
1534
+ const assistant = await client.assistants.create({ graphId: "agent" });
1535
+
1536
+ let thread = await client.threads.create();
1537
+ await client.runs.wait(thread.thread_id, assistant.assistant_id, {
1538
+ input: {
1539
+ messages: [{ role: "human", content: "SF", id: "initial-message" }],
1540
+ interrupt: true,
1541
+ },
1542
+ config: globalConfig,
1543
+ });
1544
+
1545
+ const state = await client.threads.getState(thread.thread_id);
1546
+ expect(state.next).toEqual(["agent"]);
1547
+ expect(state.tasks).toMatchObject([
1548
+ {
1549
+ id: expect.any(String),
1550
+ name: "agent",
1551
+ path: ["__pregel_pull", "agent"],
1552
+ error: null,
1553
+ interrupts: [
1554
+ {
1555
+ value: "i want to interrupt",
1556
+ when: "during",
1557
+ resumable: true,
1558
+ ns: [expect.stringMatching(/^agent:/)],
1559
+ },
1560
+ ],
1561
+ checkpoint: null,
1562
+ state: null,
1563
+ result: null,
1564
+ },
1565
+ ]);
1566
+
1567
+ thread = await client.threads.get(thread.thread_id);
1568
+ expect(thread.status).toBe("interrupted");
1569
+ expect(thread.interrupts).toMatchObject({
1570
+ [state.tasks[0].id]: [
1571
+ {
1572
+ value: "i want to interrupt",
1573
+ when: "during",
1574
+ resumable: true,
1575
+ ns: [expect.stringMatching(/^agent:/)],
1576
+ },
1577
+ ],
1578
+ });
1579
+
1580
+ const stream = await gatherIterator(
1581
+ client.runs.stream(thread.thread_id, assistant.assistant_id, {
1582
+ command: { resume: "i want to resume" },
1583
+ })
1584
+ );
1585
+
1586
+ expect(stream.at(-1)?.event).toBe("values");
1587
+ expect(stream.at(-1)?.data.messages.length).toBe(4);
1588
+ });
1503
1589
  });
1504
1590
 
1505
1591
  describe("errors", () => {
@@ -7,6 +7,7 @@ import {
7
7
  messagesStateReducer,
8
8
  SharedValue,
9
9
  LangGraphRunnableConfig,
10
+ interrupt,
10
11
  } from "@langchain/langgraph";
11
12
  import { FakeListChatModel } from "@langchain/core/utils/testing";
12
13
 
@@ -16,6 +17,7 @@ const GraphAnnotationOutput = Annotation.Root({
16
17
  default: () => [],
17
18
  }),
18
19
  sharedStateValue: Annotation<string | null>(),
20
+ interrupt: Annotation<boolean>(),
19
21
  });
20
22
 
21
23
  const GraphAnnotationInput = Annotation.Root({
@@ -36,8 +38,10 @@ const getModel = (threadId: string) => {
36
38
 
37
39
  const agentNode = async (
38
40
  state: typeof GraphAnnotationInput.State,
39
- config: LangGraphRunnableConfig,
41
+ config: LangGraphRunnableConfig
40
42
  ) => {
43
+ if (state.interrupt) interrupt("i want to interrupt");
44
+
41
45
  const model = getModel(config.configurable?.thread_id ?? "$");
42
46
  const response = await model.invoke(state.messages);
43
47
  const sharedStateValue = state.sharedState?.data?.user_id ?? null;
@@ -52,6 +56,7 @@ const agentNode = async (
52
56
  }
53
57
 
54
58
  return {
59
+ interrupt: false,
55
60
  messages: [response],
56
61
  sharedState: { data: { user_id: config?.configurable?.user_id } },
57
62
  sharedStateValue,
@@ -60,7 +65,7 @@ const agentNode = async (
60
65
 
61
66
  const toolNode = async (
62
67
  state: typeof GraphAnnotationInput.State,
63
- config: LangGraphRunnableConfig,
68
+ config: LangGraphRunnableConfig
64
69
  ) => {
65
70
  const store = config.store;
66
71
  let sharedStateFromStoreConfig: Record<string, any> | null = null;
@@ -84,7 +89,7 @@ const toolNode = async (
84
89
 
85
90
  const checkSharedStateNode = async (
86
91
  _: typeof GraphAnnotationInput.State,
87
- config: LangGraphRunnableConfig,
92
+ config: LangGraphRunnableConfig
88
93
  ): Promise<Partial<typeof GraphAnnotationInput.State>> => {
89
94
  const store = config.store;
90
95
  const namespace = ["inputtedState", "data"];
@@ -114,7 +119,7 @@ const workflow = new StateGraph(
114
119
  input: GraphAnnotationInput,
115
120
  output: GraphAnnotationOutput,
116
121
  },
117
- Annotation.Root({ model_name: Annotation<string> }),
122
+ Annotation.Root({ model_name: Annotation<string> })
118
123
  )
119
124
  .addNode("agent", agentNode)
120
125
  .addNode("tool", toolNode)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": true,
3
3
  "dependencies": {
4
- "@langchain/core": "^0.3.9",
5
- "@langchain/langgraph": "^0.2.14"
4
+ "@langchain/core": "^0.3.22",
5
+ "@langchain/langgraph": "^0.2.26"
6
6
  }
7
7
  }
@@ -2,16 +2,16 @@
2
2
  # yarn lockfile v1
3
3
 
4
4
 
5
- "@langchain/core@^0.3.9":
6
- version "0.3.9"
7
- resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.3.9.tgz#1fa75be84a22d951946277bc4ad3ea656c77fafc"
8
- integrity sha512-Rttr9FuFwU+CWIoEyuLqQUPYg+3pKL1YpDgo3nvoDVhinoHqwGQ7aNGzZ/Sf+qASMi76sPSLm+75pHMJwwOiWg==
5
+ "@langchain/core@^0.3.22":
6
+ version "0.3.22"
7
+ resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.3.22.tgz#22064eca45a1f506e554c30537de62dc742c67f8"
8
+ integrity sha512-9rwEbxJi3Fgs8XuealNYxB6s0FCOnvXLnpiV5/oKgmEJtCRS91IqgJCWA8d59s4YkaEply/EsZVc2azNPK6Wjw==
9
9
  dependencies:
10
10
  ansi-styles "^5.0.0"
11
11
  camelcase "6"
12
12
  decamelize "1.2.0"
13
13
  js-tiktoken "^1.0.12"
14
- langsmith "^0.1.56"
14
+ langsmith "^0.2.8"
15
15
  mustache "^4.2.0"
16
16
  p-queue "^6.6.2"
17
17
  p-retry "4"
@@ -19,23 +19,38 @@
19
19
  zod "^3.22.4"
20
20
  zod-to-json-schema "^3.22.3"
21
21
 
22
- "@langchain/langgraph-checkpoint@~0.0.10":
23
- version "0.0.10"
24
- resolved "https://registry.yarnpkg.com/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-0.0.10.tgz#30d896ff48a9431ece0afa7e60e420ad2234b338"
25
- integrity sha512-BMfJD5Eg39pM0iJmEv50qJL5dJJI5U2oHuNXixWlQ1BKsvtbSs713+EHc21uuvcJUct1MPiv7RdfvwXycLM/aQ==
22
+ "@langchain/langgraph-checkpoint@~0.0.12":
23
+ version "0.0.12"
24
+ resolved "https://registry.yarnpkg.com/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-0.0.12.tgz#672f2dd5ea3a33054d9792ff5c151faebd75d852"
25
+ integrity sha512-XySxUqpt7X3k02UyncpKupZMOHqLbkjOpkGWFdwBTueT1+kVeS2+DTwZK80QT/BaWH6jEUkMk14oU9/D63R0tg==
26
26
  dependencies:
27
27
  uuid "^10.0.0"
28
28
 
29
- "@langchain/langgraph@^0.2.14":
30
- version "0.2.14"
31
- resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.14.tgz#f698e948b923be666ffe61601da0cfbd363f8968"
32
- integrity sha512-gvneCZDzYzpt+P6ye7pveiRZtlGKWFKk3XAck31yxSf5D/++lP8s6ocMY1x+UaFEfAYd5Qj6jNPI9aPp9Y75jQ==
29
+ "@langchain/langgraph-sdk@~0.0.21":
30
+ version "0.0.31"
31
+ resolved "https://registry.yarnpkg.com/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.31.tgz#186cc2d31bb504d301e227dc7448dd22a8ee1933"
32
+ integrity sha512-oYZWoC3x7vH9bAL1Y30XjtuWnic1j3knXD4BbldsY0chFLxwIT5i6/GMThNy3Oiwb4SB+c6gvaSuxBNDkp7dkw==
33
33
  dependencies:
34
- "@langchain/langgraph-checkpoint" "~0.0.10"
35
- double-ended-queue "^2.1.0-0"
34
+ "@types/json-schema" "^7.0.15"
35
+ p-queue "^6.6.2"
36
+ p-retry "4"
37
+ uuid "^9.0.0"
38
+
39
+ "@langchain/langgraph@^0.2.26":
40
+ version "0.2.26"
41
+ resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.26.tgz#8a9f771e4e356de616f7e4a90c61f566cd267a92"
42
+ integrity sha512-79nq4N5gUCW3RJQSTGdqRg28Rrdd6V493oxCW7dKST1qwS/CFf+Y/nU0nDzmwKYUekLZ3Fkh7WtyYrgonZhjjg==
43
+ dependencies:
44
+ "@langchain/langgraph-checkpoint" "~0.0.12"
45
+ "@langchain/langgraph-sdk" "~0.0.21"
36
46
  uuid "^10.0.0"
37
47
  zod "^3.23.8"
38
48
 
49
+ "@types/json-schema@^7.0.15":
50
+ version "7.0.15"
51
+ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
52
+ integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
53
+
39
54
  "@types/retry@0.12.0":
40
55
  version "0.12.0"
41
56
  resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
@@ -71,11 +86,6 @@ decamelize@1.2.0:
71
86
  resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
72
87
  integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
73
88
 
74
- double-ended-queue@^2.1.0-0:
75
- version "2.1.0-0"
76
- resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
77
- integrity sha512-+BNfZ+deCo8hMNpDqDnvT+c0XpJ5cUa6mqYq89bho2Ifze4URTqRkcwR399hWoTrTkbZ/XJYDgP6rc7pRgffEQ==
78
-
79
89
  eventemitter3@^4.0.4:
80
90
  version "4.0.7"
81
91
  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
@@ -88,10 +98,10 @@ js-tiktoken@^1.0.12:
88
98
  dependencies:
89
99
  base64-js "^1.5.1"
90
100
 
91
- langsmith@^0.1.56:
92
- version "0.1.64"
93
- resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.1.64.tgz#d10cc8bb0e1aa38f67f385c5835eecbfd7b7d154"
94
- integrity sha512-fA827iH34G6zFzZx5kzbZcevtulqq8qo1e9g/Rn+L0TXuOJSKLMEfVbDbX6WD9laus9P8ItZcp27E5/DngOFHQ==
101
+ langsmith@^0.2.8:
102
+ version "0.2.9"
103
+ resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.2.9.tgz#07366136832863b12e284ba0567bc836c1a73ead"
104
+ integrity sha512-bRAgdFgUeCU82eDcMTyTBuiadekDIv7oiNW1G5phxeuynTqXKjUII3mjpeak291bC00ow2q5kauWT0CTVEs+4Q==
95
105
  dependencies:
96
106
  "@types/uuid" "^10.0.0"
97
107
  commander "^10.0.1"
@@ -148,6 +158,11 @@ uuid@^10.0.0:
148
158
  resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294"
149
159
  integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==
150
160
 
161
+ uuid@^9.0.0:
162
+ version "9.0.1"
163
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
164
+ integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
165
+
151
166
  zod-to-json-schema@^3.22.3:
152
167
  version "3.23.2"
153
168
  resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.23.2.tgz#bc7e379c8050462538383e382964c03d8fe008f9"
@@ -301,16 +301,16 @@
301
301
  "@jridgewell/resolve-uri" "^3.1.0"
302
302
  "@jridgewell/sourcemap-codec" "^1.4.14"
303
303
 
304
- "@langchain/core@^0.3.17":
305
- version "0.3.18"
306
- resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.3.18.tgz#bbe3c518f3b48b0cecd426b36d22bac88486a214"
307
- integrity sha512-IEZCrFs1Xd0J2FTH1D3Lnm3/Yk2r8LSpwDeLYwcCom3rNAK5k4mKQ2rwIpNq3YuqBdrTNMKRO+PopjkP1SB17A==
304
+ "@langchain/core@^0.3.22":
305
+ version "0.3.22"
306
+ resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.3.22.tgz#22064eca45a1f506e554c30537de62dc742c67f8"
307
+ integrity sha512-9rwEbxJi3Fgs8XuealNYxB6s0FCOnvXLnpiV5/oKgmEJtCRS91IqgJCWA8d59s4YkaEply/EsZVc2azNPK6Wjw==
308
308
  dependencies:
309
309
  ansi-styles "^5.0.0"
310
310
  camelcase "6"
311
311
  decamelize "1.2.0"
312
312
  js-tiktoken "^1.0.12"
313
- langsmith "^0.2.0"
313
+ langsmith "^0.2.8"
314
314
  mustache "^4.2.0"
315
315
  p-queue "^6.6.2"
316
316
  p-retry "4"
@@ -325,10 +325,10 @@
325
325
  dependencies:
326
326
  uuid "^10.0.0"
327
327
 
328
- "@langchain/langgraph-sdk@^0.0.19":
329
- version "0.0.19"
330
- resolved "https://registry.yarnpkg.com/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.19.tgz#c36088e2422875deb652fdfbdf135f1a0d31a290"
331
- integrity sha512-ItMoCcWIwWSFXViiiMX5WUKXodurWNYNC5IyIByFpiIkpDyysSs+vSDx3TFtR7QDxEOrivzc4ZIbvpVl8yZk3A==
328
+ "@langchain/langgraph-sdk@^0.0.31":
329
+ version "0.0.31"
330
+ resolved "https://registry.yarnpkg.com/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.31.tgz#186cc2d31bb504d301e227dc7448dd22a8ee1933"
331
+ integrity sha512-oYZWoC3x7vH9bAL1Y30XjtuWnic1j3knXD4BbldsY0chFLxwIT5i6/GMThNy3Oiwb4SB+c6gvaSuxBNDkp7dkw==
332
332
  dependencies:
333
333
  "@types/json-schema" "^7.0.15"
334
334
  p-queue "^6.6.2"
@@ -345,10 +345,10 @@
345
345
  p-retry "4"
346
346
  uuid "^9.0.0"
347
347
 
348
- "@langchain/langgraph@^0.2.23":
349
- version "0.2.23"
350
- resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.23.tgz#b892ae642c2ac49a44599ce18ab1897b14554f9f"
351
- integrity sha512-aUD3G2cUSmrOb6xtJkd0AUctmHJXuyNPIzMR86JMEN591AtkHTVe2OhTxo15S8PPAQxJ7HrCJyMFd0EQECRASw==
348
+ "@langchain/langgraph@^0.2.26":
349
+ version "0.2.26"
350
+ resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.26.tgz#8a9f771e4e356de616f7e4a90c61f566cd267a92"
351
+ integrity sha512-79nq4N5gUCW3RJQSTGdqRg28Rrdd6V493oxCW7dKST1qwS/CFf+Y/nU0nDzmwKYUekLZ3Fkh7WtyYrgonZhjjg==
352
352
  dependencies:
353
353
  "@langchain/langgraph-checkpoint" "~0.0.12"
354
354
  "@langchain/langgraph-sdk" "~0.0.21"
@@ -865,10 +865,10 @@ kuler@^2.0.0:
865
865
  resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3"
866
866
  integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==
867
867
 
868
- langsmith@^0.2.0:
869
- version "0.2.5"
870
- resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.2.5.tgz#0e58900bcdfb47409222dd6c8c122c50f5015337"
871
- integrity sha512-dA+l7ZEh1Q9Q9FcE39PUSSEMfsFo73R2V81fRo5KSlGNcypOEhoQvv6lbjyZP7MHmt3/9pPcfpuRd5Y4RbFYqQ==
868
+ langsmith@^0.2.8:
869
+ version "0.2.9"
870
+ resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.2.9.tgz#07366136832863b12e284ba0567bc836c1a73ead"
871
+ integrity sha512-bRAgdFgUeCU82eDcMTyTBuiadekDIv7oiNW1G5phxeuynTqXKjUII3mjpeak291bC00ow2q5kauWT0CTVEs+4Q==
872
872
  dependencies:
873
873
  "@types/uuid" "^10.0.0"
874
874
  commander "^10.0.1"
langgraph_api/schema.py CHANGED
@@ -156,7 +156,7 @@ class RunSend(TypedDict):
156
156
 
157
157
 
158
158
  class RunCommand(TypedDict):
159
- send: RunSend | Sequence[RunSend] | None
159
+ goto: str | RunSend | Sequence[RunSend | str] | None
160
160
  update: dict[str, Any] | None
161
161
  resume: Any | None
162
162
 
langgraph_api/stream.py CHANGED
@@ -71,13 +71,17 @@ def _preprocess_debug_checkpoint(payload: CheckpointPayload | None) -> dict[str,
71
71
 
72
72
 
73
73
  def _map_cmd(cmd: RunCommand) -> Command:
74
- send = cmd.get("send")
75
- if send is not None and not isinstance(send, list):
76
- send = [cmd.get("send")]
74
+ goto = cmd.get("goto")
75
+ if goto is not None and not isinstance(goto, list):
76
+ goto = [cmd.get("goto")]
77
77
 
78
78
  return Command(
79
79
  update=cmd.get("update"),
80
- send=[Send(send["node"], send["input"]) for send in send] if send else None,
80
+ goto=[
81
+ it if isinstance(it, str) else Send(it["node"], it["input"]) for it in goto
82
+ ]
83
+ if goto
84
+ else None,
81
85
  resume=cmd.get("resume"),
82
86
  )
83
87
 
@@ -94,7 +98,7 @@ async def astream_state(
94
98
  ) -> AnyStream:
95
99
  """Stream messages from the runnable."""
96
100
  run_id = str(run["run_id"])
97
- pipe = await stack.enter_async_context(conn.pipeline())
101
+ await stack.enter_async_context(conn.pipeline())
98
102
  # extract args from run
99
103
  kwargs = run["kwargs"].copy()
100
104
  subgraphs = kwargs.get("subgraphs", False)
@@ -103,7 +107,7 @@ async def astream_state(
103
107
  graph = await get_graph(
104
108
  config["configurable"]["graph_id"],
105
109
  config,
106
- store=None if not conn else Store(conn, pipe=pipe),
110
+ store=Store(),
107
111
  checkpointer=None if temporary else Checkpointer(conn),
108
112
  )
109
113
  input = kwargs.pop("input")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langgraph-api
3
- Version: 0.0.5
3
+ Version: 0.0.7
4
4
  Summary:
5
5
  License: Elastic-2.0
6
6
  Author: Nuno Campos
@@ -14,7 +14,7 @@ Requires-Dist: cryptography (>=43.0.3,<44.0.0)
14
14
  Requires-Dist: httpx (>=0.27.0)
15
15
  Requires-Dist: jsonschema-rs (>=0.25.0,<0.26.0)
16
16
  Requires-Dist: langchain-core (>=0.2.38,<0.4.0)
17
- Requires-Dist: langgraph (>=0.2.52,<0.3.0)
17
+ Requires-Dist: langgraph (>=0.2.56,<0.3.0)
18
18
  Requires-Dist: langgraph-checkpoint (>=2.0.7,<3.0)
19
19
  Requires-Dist: langsmith (>=0.1.63,<0.2.0)
20
20
  Requires-Dist: orjson (>=3.10.1)
@@ -4,8 +4,8 @@ langgraph_api/api/__init__.py,sha256=tlMXuqnyJt99aSlUXwR-dS3w5X6sDDczJu4hbm2LP30
4
4
  langgraph_api/api/assistants.py,sha256=3v4v7kLmlb2meSAnNt73toijnaSikbJgH9Jhb6IL04g,11270
5
5
  langgraph_api/api/meta.py,sha256=hueasWpTDQ6xYLo9Bzt2jhNH8XQRzreH8FTeFfnRoxQ,2700
6
6
  langgraph_api/api/openapi.py,sha256=zvKMMebBP28hLPMNt1kTtFK17aV9Tz-ok2okEM45XjA,906
7
- langgraph_api/api/runs.py,sha256=Z4PyBk11zvrbYxHeXy8uZimpc3LoAdpku3DEQa79IWU,15535
8
- langgraph_api/api/store.py,sha256=QLiMljCez50lyOvUd3HMXUvjUxyVowB6Cx1Dc1NNTQU,4154
7
+ langgraph_api/api/runs.py,sha256=X3fPWVjj4IUGWMIkjVkddVSqBaNh2f9HqtXNVLol8-Q,15961
8
+ langgraph_api/api/store.py,sha256=y7VIejpsE7rpPF-tiMGBqqBwWPZ1wb3o48th6NUvb5I,3849
9
9
  langgraph_api/api/threads.py,sha256=taU61XPcCEhBPCYPZcMDsgVDwwWUWJs8p-PrXFXWY48,8661
10
10
  langgraph_api/asyncio.py,sha256=XiFEllu-Kg4zAO084npHPYOPnLQRire3V75XrVQYMxE,6023
11
11
  langgraph_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -14,22 +14,22 @@ langgraph_api/auth/langsmith/backend.py,sha256=uHeb5-h13NIjrX_LDAvfWYr3zpbJvlvbd
14
14
  langgraph_api/auth/langsmith/client.py,sha256=eKchvAom7hdkUXauD8vHNceBDDUijrFgdTV8bKd7x4Q,3998
15
15
  langgraph_api/auth/middleware.py,sha256=_gJTOskEaln4RUT2rVYdQGPJVAyAiq-orsL_eQ3CynE,1369
16
16
  langgraph_api/auth/noop.py,sha256=vDJmzG2vArJxVzdHePvrJWahEa0dvGnhc2LEMMeiFz0,391
17
- langgraph_api/cli.py,sha256=m398-sIHv3xglVda0IoRnLodswMUXBtqkjzmuNWn2gM,9889
17
+ langgraph_api/cli.py,sha256=GjFMWQM4nXV6npc-A2AyLfEFuNIxj2EuQwy2LFrQVEw,10093
18
18
  langgraph_api/config.py,sha256=cG6eO4P_SZ2pKedb2b4n4vnBHRQr0aiECvGvOA8ZlJA,2259
19
19
  langgraph_api/cron_scheduler.py,sha256=DAzY2DsADzEpPVbG2BOSLTIufI93yeRswd71Aby_lV0,2257
20
20
  langgraph_api/errors.py,sha256=Bu_i5drgNTyJcLiyrwVE_6-XrSU50BHf9TDpttki9wQ,1690
21
- langgraph_api/graph.py,sha256=xf8jiirayV9mRamT3PcY8ADevxbj-7Wui35HyPExv-s,16118
21
+ langgraph_api/graph.py,sha256=uHAc5DP0aUN-ecgYjXGOYp0IJBC1k3JWlYhExL4jYaE,16505
22
22
  langgraph_api/http.py,sha256=XrbyxpjtfSvnaWWh5ZLGpgZmY83WoDCrP_1GPguNiXI,4712
23
23
  langgraph_api/http_logger.py,sha256=Sxo_q-65tElauRvkzVLt9lJojgNdgtcHGBYD0IRyX7M,3146
24
24
  langgraph_api/js/.gitignore,sha256=qAah3Fq0HWAlfRj5ktZyC6QRQIsAolGLRGcRukA1XJI,33
25
25
  langgraph_api/js/build.mts,sha256=v4ZJFnfBJBuLn8g0q-Uab9sgNtcssXcFEI-CmMoiOBc,1301
26
- langgraph_api/js/client.mts,sha256=nmWjL3Zs9jm8JoKPp8nrNNeRzwISTMqcZqqsDGx0SYE,24189
26
+ langgraph_api/js/client.mts,sha256=GF34EYtjIKvl9u44PeWhWD_QCbTbrxFQ-BL-7v6P23c,24434
27
27
  langgraph_api/js/global.d.ts,sha256=zR_zLYfpzyPfxpEFth5RgZoyfGulIXyZYPRf7cU0K0Y,106
28
- langgraph_api/js/package.json,sha256=-uXENFwS4rqlsqHirrV4kMSiRJRC1Su5RwkPIOpCoZc,792
29
- langgraph_api/js/remote.py,sha256=n34B4X1sZW70vEF1hMQ4vn28BroZJt7_7BGaJ0TNeBU,23306
28
+ langgraph_api/js/package.json,sha256=MuXjwh1Y-vLZFQnBAMgMABBJt2hDORvVLiS1xniyZnA,792
29
+ langgraph_api/js/remote.py,sha256=rudGN-8kHVuEbnGeDMEZbn__edtqkjxXlrxIuO1xOo0,23323
30
30
  langgraph_api/js/server_sent_events.py,sha256=DLgXOHauemt7706vnfDUCG1GI3TidKycSizccdz9KgA,3702
31
- langgraph_api/js/src/graph.mts,sha256=Gh2-TcCepeRhqMk8Bm1tWm5sv53y2FxgSclUTamnLNw,2636
32
- langgraph_api/js/src/hooks.mjs,sha256=rdAEcBmqtIwzTPzqxJ2e7TJq6FWnhh5Edpy6os8hmlk,597
31
+ langgraph_api/js/src/graph.mts,sha256=EO1ITYoKiUykzO_8V8mnQb6NYybooR1VXIovThZzywc,2998
32
+ langgraph_api/js/src/hooks.mjs,sha256=XtktgmIHlls_DsknAuwib-z7TqCm0haRoTXvnkgzMuo,601
33
33
  langgraph_api/js/src/parser/parser.mts,sha256=wXre7teh8N8RYmGcyhZp4vMJd0kNnnFgoSGEyMVPzpQ,13007
34
34
  langgraph_api/js/src/parser/parser.worker.mjs,sha256=2K6D0GlUmkk7LE39I8mryB8VZVE3-N9Cblji-ArPhFo,386
35
35
  langgraph_api/js/src/schema/types.mts,sha256=SUj0vpvWVbID1mnGr2SUtumDBQkJ9zjfvJdoFP7DIzk,66536
@@ -37,19 +37,19 @@ langgraph_api/js/src/schema/types.template.mts,sha256=c-FA0Ykzp4KvPyYA6a-hDf60Kd
37
37
  langgraph_api/js/src/utils/importMap.mts,sha256=pX4TGOyUpuuWF82kXcxcv3-8mgusRezOGe6Uklm2O5A,1644
38
38
  langgraph_api/js/src/utils/pythonSchemas.mts,sha256=98IW7Z_VP7L_CHNRMb3_MsiV3BgLE2JsWQY_PQcRR3o,685
39
39
  langgraph_api/js/src/utils/serde.mts,sha256=5SO-wYPnPa8f-D6HQX5Oy3NX3nuziZM8vQMqc2tuxbk,531
40
- langgraph_api/js/tests/api.test.mts,sha256=tuOZFGYgPld4kfm-S_SNXBwm3Ugp7h2PPuGLIyCjn24,49654
40
+ langgraph_api/js/tests/api.test.mts,sha256=ZfwWmu6RgxMBELwR5PUpGOdVFkeDWlONPx-f_huSRok,52248
41
41
  langgraph_api/js/tests/compose-postgres.yml,sha256=7VCpWUgPjCzybf6Gu6pT788sZsxUnTjhbmji6c33cVc,1639
42
42
  langgraph_api/js/tests/graphs/.gitignore,sha256=26J8MarZNXh7snXD5eTpV3CPFTht5Znv8dtHYCLNfkw,12
43
- langgraph_api/js/tests/graphs/agent.mts,sha256=_E0JUf1U_yXJeqOMEhl2QqbbdADZR4zpLhiZk8SEWRc,3686
43
+ langgraph_api/js/tests/graphs/agent.mts,sha256=i2s0GOnydW88laDGBatYkQnjUe9Q44RNHDhdEGIcT8w,3811
44
44
  langgraph_api/js/tests/graphs/error.mts,sha256=l4tk89449dj1BnEF_0ZcfPt0Ikk1gl8L1RaSnRfr3xo,487
45
45
  langgraph_api/js/tests/graphs/langgraph.json,sha256=-wSU_9H2fraq7Tijq_dTuK8SBDYLHsnfrYgi2zYUK2o,155
46
46
  langgraph_api/js/tests/graphs/nested.mts,sha256=4G7jSOSaFVQAza-_ARbK-Iai1biLlF2DIPDZXf7PLIY,1245
47
- langgraph_api/js/tests/graphs/package.json,sha256=g6GuOmLghaTYMSFoAoYa7lrIZyPwOETKaJNec-yT9n8,118
47
+ langgraph_api/js/tests/graphs/package.json,sha256=y8ZYXtFv3cl-qQHNmZtgsFXvsAlxKdlpvbyFqv8ReSY,119
48
48
  langgraph_api/js/tests/graphs/weather.mts,sha256=A7mLK3xW8h5B-ZyJNAyX2M2fJJwzPJzXs4DYesJwreQ,1655
49
- langgraph_api/js/tests/graphs/yarn.lock,sha256=6GvNkJgzMDmLiw13ilBXOyelM3YHwUFySTdSbtNBpY8,7255
49
+ langgraph_api/js/tests/graphs/yarn.lock,sha256=H66dRY5yBM2zzKnszqeprc8eN58B0mEgXseX2_H4lRM,7903
50
50
  langgraph_api/js/tests/parser.test.mts,sha256=3zAbboUNhI-cY3hj4Ssr7J-sQXCBTeeI1ItrkG0Ftuk,26257
51
51
  langgraph_api/js/tests/utils.mts,sha256=2kTybJ3O7Yfe1q3ehDouqV54ibXkNzsPZ_wBZLJvY-4,421
52
- langgraph_api/js/yarn.lock,sha256=CnKtoZq08ICgCvsB4h8SivPeVPWf5Oxl2JZlZIKgdEo,65156
52
+ langgraph_api/js/yarn.lock,sha256=WAgI5IVsaZrATwEEs6uCvyB6P_iHJ6ghk39Cj1dyLtc,65156
53
53
  langgraph_api/lifespan.py,sha256=Uj7NV-NqxxD1fgx_umM9pVqclcy-VlqrIxDljyj2he0,1820
54
54
  langgraph_api/logging.py,sha256=tiDNrEFwqaIdL5ywZv908OXlzzfXsPCws9GXeoFtBV8,3367
55
55
  langgraph_api/metadata.py,sha256=mih2G7ScQxiqyUlbksVXkqR3Oo-pM1b6lXtzOsgR1sw,3044
@@ -58,12 +58,12 @@ langgraph_api/models/run.py,sha256=nNhlG-l2P-DThFWYmCaar0UuPctadB9sH3CCWJfLqVc,8
58
58
  langgraph_api/patch.py,sha256=94ddcTSZJe22JcpjxiSNjFZdYVnmeoWjk4IX4iBSoyk,1249
59
59
  langgraph_api/queue.py,sha256=7tsbDgv4GlUYieJsrvIJDMQUEok4Eu-n_PIQ93rwKjk,9810
60
60
  langgraph_api/route.py,sha256=Dzje_dSigJramglqkt4ERT9-cb2xFli7dx25ZV6B6mI,4147
61
- langgraph_api/schema.py,sha256=yHHQS_4KQjiENF21JexzdJ_RF_HSxMEQX2aP6yPVTo4,5251
61
+ langgraph_api/schema.py,sha256=EiCWRR2GmGrBrOYcuK9SeVQS5b98SdaJlKaqOL7t-WQ,5263
62
62
  langgraph_api/serde.py,sha256=VoJ7Z1IuqrQGXFzEP1qijAITtWCrmjtVqlCRuScjXJI,3533
63
63
  langgraph_api/server.py,sha256=afHDnL6b_fAIu_q4icnK60a74lHTTZOMIe1egdhRXIk,1522
64
64
  langgraph_api/sse.py,sha256=2wNodCOP2eg7a9mpSu0S3FQ0CHk2BBV_vv0UtIgJIcc,4034
65
65
  langgraph_api/state.py,sha256=8jx4IoTCOjTJuwzuXJKKFwo1VseHjNnw_CCq4x1SW14,2284
66
- langgraph_api/stream.py,sha256=at4M1aePiBcsjXyg8ljE_aIvPkiAphoWpjBpy9eAweU,11527
66
+ langgraph_api/stream.py,sha256=u0gjbCrmYvVw8Ux6DgsYTojLCHSwM4Pi-0LhSLGY4HM,11546
67
67
  langgraph_api/utils.py,sha256=FI50tOFMVidV4-1TefouL1N-OJX41qD_fSEoWigTtf0,1575
68
68
  langgraph_api/validation.py,sha256=McizHlz-Ez8Jhdbc79mbPSde7GIuf2Jlbjx2yv_l6dA,4475
69
69
  langgraph_license/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -71,16 +71,16 @@ langgraph_license/middleware.py,sha256=_ODIYzQkymr6W9_Fp9wtf1kAQspnpsmr53xuzyF2G
71
71
  langgraph_license/validation.py,sha256=Uu_G8UGO_WTlLsBEY0gTVWjRR4czYGfw5YAD3HLZoj0,203
72
72
  langgraph_storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
73
  langgraph_storage/checkpoint.py,sha256=55J7W0s5Z7svqqan9wphubSgCphWUItq98j-iWVwbH8,2774
74
- langgraph_storage/database.py,sha256=0Cm4Vdm8YKRwKROs3dDPdFF6EgFgNuPsR-m3vx_htPQ,5235
75
- langgraph_storage/ops.py,sha256=DV-j1datJ0hhN7rx1PiSl8e3R5K3Siceihvz5QzScl0,52280
74
+ langgraph_storage/database.py,sha256=jYAC1tDijO7jTPABxAKB2GutTKLf3EkbMI9VAIM3dT8,5186
75
+ langgraph_storage/ops.py,sha256=Uev3bLTrEP53vX9POHIEo0eYEVH0pyZiLmliE04VCIM,52708
76
76
  langgraph_storage/queue.py,sha256=6cTZ0ubHu3S1T43yxHMVOwsQsDaJupByiU0sTUFFls8,3261
77
77
  langgraph_storage/retry.py,sha256=uvYFuXJ-T6S1QY1ZwkZHyZQbsvS-Ab68LSbzbUUSI2E,696
78
- langgraph_storage/store.py,sha256=lHK4epfxaH7ERaVOgcbjNLdKl8VFSuhGmW-ur8weFQY,2125
78
+ langgraph_storage/store.py,sha256=D-p3cWc_umamkKp-6Cz3cAriSACpvM5nxUIvND6PuxE,2710
79
79
  langgraph_storage/ttl_dict.py,sha256=FlpEY8EANeXWKo_G5nmIotPquABZGyIJyk6HD9u6vqY,1533
80
80
  logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
81
- openapi.json,sha256=eNphz--BboaQ_BXBQon3GYC2yLT_9Tg6omZhIPaIjqk,123565
82
- langgraph_api-0.0.5.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
83
- langgraph_api-0.0.5.dist-info/METADATA,sha256=ajyvQTklUvN3VbpVVciPsrQzj_Yd7LJoBXq9hM4wANk,3993
84
- langgraph_api-0.0.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
85
- langgraph_api-0.0.5.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
86
- langgraph_api-0.0.5.dist-info/RECORD,,
81
+ openapi.json,sha256=UxAGHZYM4PgNd48TSZt7f2lVuyPUkDadxBBhRy5jcmk,124512
82
+ langgraph_api-0.0.7.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
83
+ langgraph_api-0.0.7.dist-info/METADATA,sha256=2-Fe-_N61HLzOGA8evWq8yOoMEV2mRoXhxJIdr5Nxa0,3993
84
+ langgraph_api-0.0.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
85
+ langgraph_api-0.0.7.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
86
+ langgraph_api-0.0.7.dist-info/RECORD,,
@@ -157,10 +157,9 @@ async def connect(*, __test__: bool = False) -> AsyncIterator[AsyncConnectionPro
157
157
 
158
158
  async def start_pool() -> None:
159
159
  if store._STORE_CONFIG is None:
160
- if (config_val := os.getenv("LANGGRAPH_CONFIG")) and config_val.strip():
160
+ if (config_val := os.getenv("LANGGRAPH_STORE")) and config_val.strip():
161
161
  config_ = json.loads(config_val.strip())
162
- if "store" in config_:
163
- store.set_store_config(config_["store"])
162
+ store.set_store_config(config_)
164
163
 
165
164
  if not os.path.exists(".langgraph_api"):
166
165
  os.mkdir(".langgraph_api")
langgraph_storage/ops.py CHANGED
@@ -1089,6 +1089,9 @@ class Runs:
1089
1089
  # Create new run
1090
1090
  configurable = Runs._merge_jsonb(
1091
1091
  Runs._get_configurable(assistant["config"]),
1092
+ Runs._get_configurable(existing_thread["config"])
1093
+ if existing_thread
1094
+ else {},
1092
1095
  Runs._get_configurable(config),
1093
1096
  {
1094
1097
  "run_id": str(run_id),
@@ -1097,6 +1100,11 @@ class Runs:
1097
1100
  "assistant_id": str(assistant_id),
1098
1101
  "user_id": (
1099
1102
  config.get("configurable", {}).get("user_id")
1103
+ or (
1104
+ existing_thread["config"].get("configurable", {}).get("user_id")
1105
+ if existing_thread
1106
+ else None
1107
+ )
1100
1108
  or assistant["config"].get("configurable", {}).get("user_id")
1101
1109
  or user_id
1102
1110
  ),
@@ -1287,6 +1295,7 @@ class Runs:
1287
1295
  limit: int = 10,
1288
1296
  offset: int = 0,
1289
1297
  metadata: MetadataInput,
1298
+ status: RunStatus | None = None,
1290
1299
  ) -> AsyncIterator[Run]:
1291
1300
  """List all runs by thread."""
1292
1301
  runs = conn.store["runs"]
@@ -1297,6 +1306,7 @@ class Runs:
1297
1306
  for run in runs
1298
1307
  if run["thread_id"] == thread_id
1299
1308
  and is_jsonb_contained(run["metadata"], metadata)
1309
+ and (status is None or run["status"] == status)
1300
1310
  ]
1301
1311
  sorted_runs = sorted(filtered_runs, key=lambda x: x["created_at"], reverse=True)
1302
1312
  sliced_runs = sorted_runs[offset : offset + limit]
@@ -1,8 +1,11 @@
1
1
  import os
2
2
  from collections import defaultdict
3
+ from collections.abc import Iterable
3
4
  from typing import Any
4
5
 
5
6
  from langgraph.checkpoint.memory import PersistentDict
7
+ from langgraph.store.base import BaseStore, Op, Result
8
+ from langgraph.store.base.batch import AsyncBatchedBaseStore
6
9
  from langgraph.store.memory import InMemoryStore
7
10
 
8
11
  from langgraph_api.graph import resolve_embeddings
@@ -43,13 +46,28 @@ class DiskBackedInMemStore(InMemoryStore):
43
46
  self._vectors.close()
44
47
 
45
48
 
49
+ class BatchedStore(AsyncBatchedBaseStore):
50
+ def __init__(self, store: BaseStore) -> None:
51
+ super().__init__()
52
+ self._store = store
53
+
54
+ def batch(self, ops: Iterable[Op]) -> list[Result]:
55
+ return self._store.batch(ops)
56
+
57
+ async def abatch(self, ops: Iterable[Op]) -> list[Result]:
58
+ return await self._store.abatch(ops)
59
+
60
+ def close(self) -> None:
61
+ self._store.close()
62
+
63
+
46
64
  _STORE_FILE = os.path.join(".langgraph_api", "store.pckl")
47
65
  _VECTOR_FILE = os.path.join(".langgraph_api", "store.vectors.pckl")
48
66
  os.makedirs(".langgraph_api", exist_ok=True)
49
67
  STORE = DiskBackedInMemStore()
50
68
 
51
69
 
52
- def set_store_config(config) -> None:
70
+ def set_store_config(config: dict) -> None:
53
71
  global _STORE_CONFIG, STORE
54
72
  _STORE_CONFIG = config.copy()
55
73
  _STORE_CONFIG["index"]["embed"] = resolve_embeddings(_STORE_CONFIG.get("index", {}))
@@ -59,4 +77,4 @@ def set_store_config(config) -> None:
59
77
 
60
78
 
61
79
  def Store(*args: Any, **kwargs: Any) -> DiskBackedInMemStore:
62
- return STORE
80
+ return BatchedStore(STORE)
openapi.json CHANGED
@@ -1342,6 +1342,21 @@
1342
1342
  },
1343
1343
  "name": "offset",
1344
1344
  "in": "query"
1345
+ },
1346
+ {
1347
+ "required": false,
1348
+ "schema": {
1349
+ "type": "string",
1350
+ "enum": [
1351
+ "pending",
1352
+ "error",
1353
+ "success",
1354
+ "timeout",
1355
+ "interrupted"
1356
+ ]
1357
+ },
1358
+ "name": "status",
1359
+ "in": "query"
1345
1360
  }
1346
1361
  ],
1347
1362
  "responses": {
@@ -1836,6 +1851,17 @@
1836
1851
  },
1837
1852
  "name": "run_id",
1838
1853
  "in": "path"
1854
+ },
1855
+ {
1856
+ "required": false,
1857
+ "schema": {
1858
+ "type": "boolean",
1859
+ "title": "Cancel on Disconnect",
1860
+ "description": "If true, the run will be cancelled if the client disconnects.",
1861
+ "default": false
1862
+ },
1863
+ "name": "cancel_on_disconnect",
1864
+ "in": "query"
1839
1865
  }
1840
1866
  ],
1841
1867
  "responses": {
@@ -3231,7 +3257,7 @@
3231
3257
  "title": "Resume",
3232
3258
  "description": "A value to pass to an interrupted node."
3233
3259
  },
3234
- "send": {
3260
+ "goto": {
3235
3261
  "anyOf": [
3236
3262
  {
3237
3263
  "$ref": "#/components/schemas/Send"
@@ -3242,11 +3268,13 @@
3242
3268
  "$ref": "#/components/schemas/Send"
3243
3269
  }
3244
3270
  },
3245
- {
3246
- "type": "null"
3247
- }
3248
- ]
3249
- }
3271
+ { "type": "string" },
3272
+ { "type": "array", "items": { "type": "string" } },
3273
+ { "type": "null" }
3274
+ ],
3275
+ "title": "Goto",
3276
+ "description": "Name of the node(s) to navigate to next or node(s) to be executed with a provided input."
3277
+ }
3250
3278
  }
3251
3279
  },
3252
3280
  "RunCreateStateful": {