langgraph-api 0.0.5__tar.gz → 0.0.7__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 (85) hide show
  1. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/PKG-INFO +2 -2
  2. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/api/runs.py +16 -2
  3. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/api/store.py +17 -23
  4. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/cli.py +12 -8
  5. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/graph.py +6 -0
  6. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/client.mts +9 -2
  7. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/package.json +3 -3
  8. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/remote.py +44 -42
  9. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/graph.mts +14 -0
  10. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/hooks.mjs +9 -8
  11. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/api.test.mts +86 -0
  12. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/graphs/agent.mts +9 -4
  13. langgraph_api-0.0.7/langgraph_api/js/tests/graphs/package.json +7 -0
  14. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/graphs/yarn.lock +39 -24
  15. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/yarn.lock +17 -17
  16. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/schema.py +1 -1
  17. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/stream.py +10 -6
  18. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/database.py +2 -3
  19. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/ops.py +10 -0
  20. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/store.py +20 -2
  21. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/openapi.json +34 -6
  22. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/pyproject.toml +3 -3
  23. langgraph_api-0.0.5/langgraph_api/js/tests/graphs/package.json +0 -7
  24. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/LICENSE +0 -0
  25. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/README.md +0 -0
  26. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/__init__.py +0 -0
  27. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/api/__init__.py +0 -0
  28. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/api/assistants.py +0 -0
  29. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/api/meta.py +0 -0
  30. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/api/openapi.py +0 -0
  31. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/api/threads.py +0 -0
  32. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/asyncio.py +0 -0
  33. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/auth/__init__.py +0 -0
  34. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/auth/langsmith/__init__.py +0 -0
  35. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/auth/langsmith/backend.py +0 -0
  36. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/auth/langsmith/client.py +0 -0
  37. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/auth/middleware.py +0 -0
  38. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/auth/noop.py +0 -0
  39. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/config.py +0 -0
  40. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/cron_scheduler.py +0 -0
  41. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/errors.py +0 -0
  42. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/http.py +0 -0
  43. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/http_logger.py +0 -0
  44. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/.gitignore +0 -0
  45. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/build.mts +0 -0
  46. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/global.d.ts +0 -0
  47. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/server_sent_events.py +0 -0
  48. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/parser/parser.mts +0 -0
  49. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/parser/parser.worker.mjs +0 -0
  50. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/schema/types.mts +0 -0
  51. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/schema/types.template.mts +0 -0
  52. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/utils/importMap.mts +0 -0
  53. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
  54. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/utils/serde.mts +0 -0
  55. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/compose-postgres.yml +0 -0
  56. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/graphs/.gitignore +0 -0
  57. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/graphs/error.mts +0 -0
  58. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/graphs/langgraph.json +0 -0
  59. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/graphs/nested.mts +0 -0
  60. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/graphs/weather.mts +0 -0
  61. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/parser.test.mts +0 -0
  62. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/utils.mts +0 -0
  63. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/lifespan.py +0 -0
  64. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/logging.py +0 -0
  65. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/metadata.py +0 -0
  66. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/models/__init__.py +0 -0
  67. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/models/run.py +0 -0
  68. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/patch.py +0 -0
  69. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/queue.py +0 -0
  70. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/route.py +0 -0
  71. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/serde.py +0 -0
  72. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/server.py +0 -0
  73. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/sse.py +0 -0
  74. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/state.py +0 -0
  75. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/utils.py +0 -0
  76. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/validation.py +0 -0
  77. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_license/__init__.py +0 -0
  78. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_license/middleware.py +0 -0
  79. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_license/validation.py +0 -0
  80. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/__init__.py +0 -0
  81. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/checkpoint.py +0 -0
  82. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/queue.py +0 -0
  83. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/retry.py +0 -0
  84. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/ttl_dict.py +0 -0
  85. {langgraph_api-0.0.5 → langgraph_api-0.0.7}/logging.json +0 -0
@@ -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)
@@ -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
 
@@ -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
 
@@ -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)
@@ -0,0 +1,7 @@
1
+ {
2
+ "private": true,
3
+ "dependencies": {
4
+ "@langchain/core": "^0.3.22",
5
+ "@langchain/langgraph": "^0.2.26"
6
+ }
7
+ }