langgraph-api 0.0.31__py3-none-any.whl → 0.0.33__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/graph.py CHANGED
@@ -21,6 +21,7 @@ from langgraph.pregel import Pregel
21
21
  from langgraph.store.base import BaseStore
22
22
  from starlette.exceptions import HTTPException
23
23
 
24
+ from langgraph_api import asyncio as lg_asyncio
24
25
  from langgraph_api.js.base import BaseRemotePregel
25
26
  from langgraph_api.schema import Config
26
27
 
@@ -60,6 +61,12 @@ async def register_graph(graph_id: str, graph: GraphValue, config: dict | None)
60
61
  )
61
62
 
62
63
 
64
+ def register_graph_sync(
65
+ graph_id: str, graph: GraphValue, config: dict | None = None
66
+ ) -> None:
67
+ lg_asyncio.run_coroutine_threadsafe(register_graph(graph_id, graph, config))
68
+
69
+
63
70
  @asynccontextmanager
64
71
  async def _generate_graph(value: Any) -> AsyncIterator[Any]:
65
72
  """Yield a graph object regardless of its type."""
@@ -187,24 +194,33 @@ async def collect_graphs_from_env(register: bool = False) -> None:
187
194
  config_per_graph = _load_graph_config_from_env() or {}
188
195
 
189
196
  if paths_str:
190
- specs = [
191
- (
197
+ specs = []
198
+ for key, value in json.loads(paths_str).items():
199
+ try:
200
+ path_or_module, variable = value.rsplit(":", maxsplit=1)
201
+ except ValueError as e:
202
+ raise ValueError(
203
+ f"Invalid path '{value}' for graph '{key}'."
204
+ " Did you miss a variable name?\n"
205
+ " Expected one of the following formats:"
206
+ " 'my.module:variable_name' or '/path/to/file.py:variable_name'"
207
+ ) from e
208
+ specs.append(
192
209
  GraphSpec(
193
210
  key,
194
- module=value.split(":")[0],
195
- variable=value.split(":")[1],
211
+ module=path_or_module,
212
+ variable=variable,
196
213
  config=config_per_graph.get(key),
197
214
  )
198
215
  if "/" not in value
199
216
  else GraphSpec(
200
217
  key,
201
- path=value.split(":")[0],
202
- variable=value.split(":")[1],
218
+ path=path_or_module,
219
+ variable=variable,
203
220
  config=config_per_graph.get(key),
204
221
  )
205
222
  )
206
- for key, value in json.loads(paths_str).items()
207
- ]
223
+
208
224
  else:
209
225
  specs = [
210
226
  GraphSpec(
@@ -1,8 +1,10 @@
1
1
  import asyncio
2
+ import logging
2
3
  import os
3
4
  import shutil
4
5
  import ssl
5
6
  from collections.abc import AsyncIterator
7
+ from contextlib import AbstractContextManager
6
8
  from typing import Any, Literal
7
9
 
8
10
  import certifi
@@ -605,60 +607,79 @@ async def run_remote_checkpointer():
605
607
  await server.serve()
606
608
 
607
609
 
610
+ class DisableHttpxLoggingContextManager(AbstractContextManager):
611
+ """
612
+ Disable HTTP/1.1 200 OK logs spamming stdout.
613
+ """
614
+
615
+ filter: logging.Filter
616
+
617
+ def filter(self, record: logging.LogRecord) -> bool:
618
+ return "200 OK" not in record.getMessage()
619
+
620
+ def __enter__(self):
621
+ logging.getLogger("httpx").addFilter(self.filter)
622
+
623
+ def __exit__(self, exc_type, exc_value, traceback):
624
+ logging.getLogger("httpx").removeFilter(self.filter)
625
+
626
+
608
627
  async def wait_until_js_ready():
609
- async with (
610
- httpx.AsyncClient(
611
- base_url=f"http://localhost:{GRAPH_PORT}",
612
- limits=httpx.Limits(max_connections=1),
613
- transport=httpx.AsyncHTTPTransport(verify=SSL),
614
- ) as graph_client,
615
- httpx.AsyncClient(
616
- base_url=f"http://localhost:{REMOTE_PORT}",
617
- limits=httpx.Limits(max_connections=1),
618
- transport=httpx.AsyncHTTPTransport(verify=SSL),
619
- ) as checkpointer_client,
620
- ):
621
- attempt = 0
622
- while not asyncio.current_task().cancelled():
628
+ with DisableHttpxLoggingContextManager():
629
+ async with (
630
+ httpx.AsyncClient(
631
+ base_url=f"http://localhost:{GRAPH_PORT}",
632
+ limits=httpx.Limits(max_connections=1),
633
+ transport=httpx.AsyncHTTPTransport(verify=SSL),
634
+ ) as graph_client,
635
+ httpx.AsyncClient(
636
+ base_url=f"http://localhost:{REMOTE_PORT}",
637
+ limits=httpx.Limits(max_connections=1),
638
+ transport=httpx.AsyncHTTPTransport(verify=SSL),
639
+ ) as checkpointer_client,
640
+ ):
641
+ attempt = 0
642
+ while not asyncio.current_task().cancelled():
643
+ try:
644
+ res = await graph_client.get("/ok")
645
+ res.raise_for_status()
646
+ res = await checkpointer_client.get("/ok")
647
+ res.raise_for_status()
648
+ return
649
+ except httpx.HTTPError:
650
+ if attempt > 240:
651
+ raise
652
+ else:
653
+ attempt += 1
654
+ await asyncio.sleep(0.5)
655
+
656
+
657
+ async def js_healthcheck():
658
+ with DisableHttpxLoggingContextManager():
659
+ async with (
660
+ httpx.AsyncClient(
661
+ base_url=f"http://localhost:{GRAPH_PORT}",
662
+ limits=httpx.Limits(max_connections=1),
663
+ transport=httpx.AsyncHTTPTransport(verify=SSL),
664
+ ) as graph_client,
665
+ httpx.AsyncClient(
666
+ base_url=f"http://localhost:{REMOTE_PORT}",
667
+ limits=httpx.Limits(max_connections=1),
668
+ transport=httpx.AsyncHTTPTransport(verify=SSL),
669
+ ) as checkpointer_client,
670
+ ):
623
671
  try:
624
672
  res = await graph_client.get("/ok")
625
673
  res.raise_for_status()
626
674
  res = await checkpointer_client.get("/ok")
627
675
  res.raise_for_status()
628
- return
629
- except httpx.HTTPError:
630
- if attempt > 240:
631
- raise
632
- else:
633
- attempt += 1
634
- await asyncio.sleep(0.5)
635
-
636
-
637
- async def js_healthcheck():
638
- async with (
639
- httpx.AsyncClient(
640
- base_url=f"http://localhost:{GRAPH_PORT}",
641
- limits=httpx.Limits(max_connections=1),
642
- transport=httpx.AsyncHTTPTransport(verify=SSL),
643
- ) as graph_client,
644
- httpx.AsyncClient(
645
- base_url=f"http://localhost:{REMOTE_PORT}",
646
- limits=httpx.Limits(max_connections=1),
647
- transport=httpx.AsyncHTTPTransport(verify=SSL),
648
- ) as checkpointer_client,
649
- ):
650
- try:
651
- res = await graph_client.get("/ok")
652
- res.raise_for_status()
653
- res = await checkpointer_client.get("/ok")
654
- res.raise_for_status()
655
- return True
656
- except httpx.HTTPError as exc:
657
- logger.warning(
658
- "JS healthcheck failed. Either the JS server is not running or the event loop is blocked by a CPU-intensive task.",
659
- error=exc,
660
- )
661
- raise HTTPException(
662
- status_code=500,
663
- detail="JS healthcheck failed. Either the JS server is not running or the event loop is blocked by a CPU-intensive task.",
664
- ) from exc
676
+ return True
677
+ except httpx.HTTPError as exc:
678
+ logger.warning(
679
+ "JS healthcheck failed. Either the JS server is not running or the event loop is blocked by a CPU-intensive task.",
680
+ error=exc,
681
+ )
682
+ raise HTTPException(
683
+ status_code=500,
684
+ detail="JS healthcheck failed. Either the JS server is not running or the event loop is blocked by a CPU-intensive task.",
685
+ ) from exc
langgraph_api/lifespan.py CHANGED
@@ -1,10 +1,11 @@
1
1
  import asyncio
2
2
  from contextlib import asynccontextmanager
3
3
 
4
+ import structlog
4
5
  from starlette.applications import Starlette
5
6
 
6
7
  import langgraph_api.config as config
7
- from langgraph_api.asyncio import SimpleTaskGroup
8
+ from langgraph_api.asyncio import SimpleTaskGroup, set_event_loop
8
9
  from langgraph_api.cron_scheduler import cron_scheduler
9
10
  from langgraph_api.graph import collect_graphs_from_env, stop_remote_graphs
10
11
  from langgraph_api.http import start_http_client, stop_http_client
@@ -14,6 +15,8 @@ from langgraph_storage.database import start_pool, stop_pool
14
15
  from langgraph_storage.queue import queue
15
16
  from langgraph_storage.store import Store
16
17
 
18
+ logger = structlog.stdlib.get_logger(__name__)
19
+
17
20
 
18
21
  @asynccontextmanager
19
22
  async def lifespan(
@@ -21,6 +24,12 @@ async def lifespan(
21
24
  with_cron_scheduler: bool = True,
22
25
  taskset: set[asyncio.Task] | None = None,
23
26
  ):
27
+ try:
28
+ current_loop = asyncio.get_running_loop()
29
+ set_event_loop(current_loop)
30
+ except RuntimeError:
31
+ await logger.aerror("Failed to set loop")
32
+
24
33
  if not await get_license_status():
25
34
  raise ValueError(
26
35
  "License verification failed. Please ensure proper configuration:\n"
langgraph_api/logging.py CHANGED
@@ -119,13 +119,14 @@ class Formatter(structlog.stdlib.ProcessorFormatter):
119
119
 
120
120
  # configure structlog
121
121
 
122
- structlog.configure(
123
- processors=[
124
- structlog.stdlib.filter_by_level,
125
- *shared_processors,
126
- structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
127
- ],
128
- logger_factory=structlog.stdlib.LoggerFactory(),
129
- wrapper_class=structlog.stdlib.BoundLogger,
130
- cache_logger_on_first_use=True,
131
- )
122
+ if not structlog.is_configured():
123
+ structlog.configure(
124
+ processors=[
125
+ structlog.stdlib.filter_by_level,
126
+ *shared_processors,
127
+ structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
128
+ ],
129
+ logger_factory=structlog.stdlib.LoggerFactory(),
130
+ wrapper_class=structlog.stdlib.BoundLogger,
131
+ cache_logger_on_first_use=True,
132
+ )
langgraph_api/metadata.py CHANGED
@@ -28,6 +28,7 @@ elif os.getenv("LANGSMITH_HOST_PROJECT_ID"):
28
28
  else:
29
29
  HOST = "self-hosted"
30
30
  PLAN = "enterprise" if plus_features_enabled() else "developer"
31
+ USER_API_URL = os.getenv("LANGGRAPH_API_URL", None)
31
32
 
32
33
  LOGS: list[dict] = []
33
34
  RUN_COUNTER = 0
@@ -241,7 +241,17 @@ async def create_valid_run(
241
241
  # handle multitask strategy
242
242
  inflight_runs = [run async for run in run_]
243
243
  if first["run_id"] == run_id:
244
- logger.info("Created run", run_id=str(run_id), thread_id=str(thread_id))
244
+ logger.info(
245
+ "Created run",
246
+ run_id=str(run_id),
247
+ thread_id=str(thread_id),
248
+ assistant_id=str(assistant_id),
249
+ multitask_strategy=multitask_strategy,
250
+ stream_mode=stream_mode,
251
+ temporary=temporary,
252
+ after_seconds=payload.get("after_seconds", 0),
253
+ if_not_exists=payload.get("if_not_exists", "reject"),
254
+ )
245
255
  # inserted, proceed
246
256
  if multitask_strategy in ("interrupt", "rollback") and inflight_runs:
247
257
  try:
langgraph_api/stream.py CHANGED
@@ -19,15 +19,15 @@ from langgraph.errors import (
19
19
  InvalidUpdateError,
20
20
  )
21
21
  from langgraph.pregel.debug import CheckpointPayload, TaskResultPayload
22
- from langgraph.types import Command, Send
23
22
  from pydantic import ValidationError
24
23
  from pydantic.v1 import ValidationError as ValidationErrorLegacy
25
24
 
26
25
  from langgraph_api.asyncio import ValueEvent, wait_if_not_done
26
+ from langgraph_api.command import map_cmd
27
27
  from langgraph_api.graph import get_graph
28
28
  from langgraph_api.js.base import BaseRemotePregel
29
- from langgraph_api.metadata import HOST, PLAN, incr_nodes
30
- from langgraph_api.schema import Run, RunCommand, StreamMode
29
+ from langgraph_api.metadata import HOST, PLAN, USER_API_URL, incr_nodes
30
+ from langgraph_api.schema import Run, StreamMode
31
31
  from langgraph_api.serde import json_dumpb
32
32
  from langgraph_api.utils import AsyncConnectionProto
33
33
  from langgraph_storage.checkpoint import Checkpointer
@@ -70,32 +70,6 @@ def _preprocess_debug_checkpoint(payload: CheckpointPayload | None) -> dict[str,
70
70
  return payload
71
71
 
72
72
 
73
- def _map_cmd(cmd: RunCommand) -> Command:
74
- goto = cmd.get("goto")
75
- if goto is not None and not isinstance(goto, list):
76
- goto = [cmd.get("goto")]
77
-
78
- update = cmd.get("update")
79
- if isinstance(update, tuple | list) and all(
80
- isinstance(t, tuple | list) and len(t) == 2 and isinstance(t[0], str)
81
- for t in update
82
- ):
83
- update = [tuple(t) for t in update]
84
-
85
- return Command(
86
- update=update,
87
- goto=(
88
- [
89
- it if isinstance(it, str) else Send(it["node"], it["input"])
90
- for it in goto
91
- ]
92
- if goto
93
- else None
94
- ),
95
- resume=cmd.get("resume"),
96
- )
97
-
98
-
99
73
  async def astream_state(
100
74
  stack: AsyncExitStack,
101
75
  conn: AsyncConnectionProto,
@@ -125,7 +99,7 @@ async def astream_state(
125
99
  )
126
100
  input = kwargs.pop("input")
127
101
  if cmd := kwargs.pop("command"):
128
- input = _map_cmd(cmd)
102
+ input = map_cmd(cmd)
129
103
  stream_mode: list[StreamMode] = kwargs.pop("stream_mode")
130
104
  feedback_keys = kwargs.pop("feedback_keys", None)
131
105
  stream_modes_set: set[StreamMode] = set(stream_mode) - {"events"}
@@ -140,6 +114,7 @@ async def astream_state(
140
114
  config["metadata"]["langgraph_version"] = langgraph.version.__version__
141
115
  config["metadata"]["langgraph_plan"] = PLAN
142
116
  config["metadata"]["langgraph_host"] = HOST
117
+ config["metadata"]["langgraph_api_url"] = USER_API_URL
143
118
  # attach node counter
144
119
  if not isinstance(graph, BaseRemotePregel):
145
120
  config["configurable"]["__pregel_node_finished"] = incr_nodes
@@ -27,7 +27,18 @@ AssistantVersionChange = jsonschema_rs.validator_for(
27
27
  openapi["components"]["schemas"]["AssistantVersionChange"]
28
28
  )
29
29
  ThreadCreate = jsonschema_rs.validator_for(
30
- openapi["components"]["schemas"]["ThreadCreate"]
30
+ {
31
+ **openapi["components"]["schemas"]["ThreadCreate"],
32
+ "components": {
33
+ "schemas": {
34
+ "ThreadSuperstepUpdate": openapi["components"]["schemas"][
35
+ "ThreadSuperstepUpdate"
36
+ ],
37
+ "Command": openapi["components"]["schemas"]["Command"],
38
+ "Send": openapi["components"]["schemas"]["Send"],
39
+ }
40
+ },
41
+ }
31
42
  )
32
43
  ThreadPatch = jsonschema_rs.validator_for(
33
44
  openapi["components"]["schemas"]["ThreadPatch"]
@@ -42,6 +53,7 @@ ThreadStateUpdate = jsonschema_rs.validator_for(
42
53
  },
43
54
  }
44
55
  )
56
+
45
57
  ThreadStateCheckpointRequest = jsonschema_rs.validator_for(
46
58
  {
47
59
  **openapi["components"]["schemas"]["ThreadStateCheckpointRequest"],
langgraph_api/worker.py CHANGED
@@ -210,7 +210,7 @@ async def worker(
210
210
  status = "retry"
211
211
  run_ended_at = datetime.now(UTC).isoformat()
212
212
  await logger.awarning(
213
- "Background run failed, will retry",
213
+ f"Background run failed, will retry. Exception: {e}",
214
214
  exc_info=True,
215
215
  run_id=str(run_id),
216
216
  run_attempt=attempt,
@@ -226,7 +226,7 @@ async def worker(
226
226
  status = "error"
227
227
  run_ended_at = datetime.now(UTC).isoformat()
228
228
  await logger.aexception(
229
- "Background run failed",
229
+ f"Background run failed. Exception: {exc}",
230
230
  exc_info=not isinstance(exc, RemoteException),
231
231
  run_id=str(run_id),
232
232
  run_attempt=attempt,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langgraph-api
3
- Version: 0.0.31
3
+ Version: 0.0.33
4
4
  Summary:
5
5
  License: Elastic-2.0
6
6
  Author: Nuno Campos
@@ -16,7 +16,7 @@ Requires-Dist: jsonschema-rs (>=0.20.0,<0.30)
16
16
  Requires-Dist: langchain-core (>=0.2.38,<0.4.0)
17
17
  Requires-Dist: langgraph (>=0.2.56,<0.4.0)
18
18
  Requires-Dist: langgraph-checkpoint (>=2.0.21,<3.0)
19
- Requires-Dist: langgraph-sdk (>=0.1.57,<0.2.0)
19
+ Requires-Dist: langgraph-sdk (>=0.1.58,<0.2.0)
20
20
  Requires-Dist: langsmith (>=0.1.63,<0.4.0)
21
21
  Requires-Dist: orjson (>=3.9.7)
22
22
  Requires-Dist: pyjwt (>=2.9.0,<3.0.0)
@@ -1,14 +1,15 @@
1
1
  LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
2
2
  langgraph_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- langgraph_api/api/__init__.py,sha256=B2kHOBhKjBRdNiWPljYuJFxP__wYPCvgtgTaWYKZGTQ,5129
4
- langgraph_api/api/assistants.py,sha256=2HEIcxaOJJ4_JQ00TKT1ehx7DMbYCGpvhShK0ZiWsSE,14043
3
+ langgraph_api/api/__init__.py,sha256=iDxgg0arqzZ0-roCSuWo3f8zXmKcl4fuU3Jw0-lqtZs,5447
4
+ langgraph_api/api/assistants.py,sha256=nU6tnbgdr_6Utlq0A9nw2a6xxpUM_DNuCFI42_Kcs_o,14233
5
+ langgraph_api/api/mcp.py,sha256=dpKT9DgIoLERTmYZ4sSOPyHbfGbm7hCyb2MrMS_ol18,13593
5
6
  langgraph_api/api/meta.py,sha256=ifJ_Ki0Qf2DYbmY6OKlqKhLGxbt55gm0lEqH1A0cJbw,2790
6
7
  langgraph_api/api/openapi.py,sha256=f9gfmWN2AMKNUpLCpSgZuw_aeOF9jCXPdOtFT5PaTWM,10960
7
8
  langgraph_api/api/runs.py,sha256=_RWKtmjD89ALnTk56dwo2rJwEi2oghk2Tqp0l1aCcZg,16677
8
9
  langgraph_api/api/store.py,sha256=VzAJVOwO0IxosBB7km5TTf2rhlWGyPkVz_LpvbxetVY,5437
9
- langgraph_api/api/threads.py,sha256=taU61XPcCEhBPCYPZcMDsgVDwwWUWJs8p-PrXFXWY48,8661
10
+ langgraph_api/api/threads.py,sha256=meaDGF3R2bYkx0KRa_le4Ka5nOSeqlMDWtLdnEhVYSY,8930
10
11
  langgraph_api/api/ui.py,sha256=LiOZVewKOPbKEykCm30hCEaOA7vuS_Ti5hB32EEy4vw,2082
11
- langgraph_api/asyncio.py,sha256=ipxOGL0CuKZeHw8895ojtfoBU2fj0iJOp48uhiLAmss,7786
12
+ langgraph_api/asyncio.py,sha256=hVuAxWTHoUyNqTzcIEKTkAvh7HFrdGK6WIDowDxORAE,8397
12
13
  langgraph_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
14
  langgraph_api/auth/custom.py,sha256=z69UvpG7wPSXtRmp_UFmA8fY7HeQD4AEIWu3qlpJOBc,21210
14
15
  langgraph_api/auth/langsmith/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -17,11 +18,12 @@ langgraph_api/auth/langsmith/client.py,sha256=eKchvAom7hdkUXauD8vHNceBDDUijrFgdT
17
18
  langgraph_api/auth/middleware.py,sha256=jU8aDSIZHdzCGdifejRF7ndHkSjBtqIHcBwFIuUdHEA,1875
18
19
  langgraph_api/auth/noop.py,sha256=Bk6Nf3p8D_iMVy_OyfPlyiJp_aEwzL-sHrbxoXpCbac,586
19
20
  langgraph_api/auth/studio_user.py,sha256=FzFQRROKDlA9JjtBuwyZvk6Mbwno5M9RVYjDO6FU3F8,186
20
- langgraph_api/cli.py,sha256=3SAwPv8EDKqJx52oD3CB3h0vN7m-LI3lT0RmYFMfkNM,11671
21
- langgraph_api/config.py,sha256=XMJEVfPe1LrmVKGM5sisQs1eZOUDyU3JwscwJOm0q_k,8572
21
+ langgraph_api/cli.py,sha256=17mTzIhAEaNy9THdtsTqaEBsi5h0J0Tu-EAtv4Qbz5U,11787
22
+ langgraph_api/command.py,sha256=3O9v3i0OPa96ARyJ_oJbLXkfO8rPgDhLCswgO9koTFA,768
23
+ langgraph_api/config.py,sha256=52qcOo9dBfynCozLwzO-Dhew-WgXjPVR2-9nNp6Q-nY,8703
22
24
  langgraph_api/cron_scheduler.py,sha256=9yzbbGxzNgJdIg4ZT7yu2oTwT_wRuPxD1c2sbbd52xs,2630
23
25
  langgraph_api/errors.py,sha256=Bu_i5drgNTyJcLiyrwVE_6-XrSU50BHf9TDpttki9wQ,1690
24
- langgraph_api/graph.py,sha256=viFyQa8-BRRNRZqNNUzLB31cB4IypEMkMucQEJQYLWY,16556
26
+ langgraph_api/graph.py,sha256=5Lo9FghptdPIFJoeTDkLjoIQLuNy49RWtAHJmzOCrZI,17209
25
27
  langgraph_api/http.py,sha256=gYbxxjY8aLnsXeJymcJ7G7Nj_yToOGpPYQqmZ1_ggfA,5240
26
28
  langgraph_api/js/.gitignore,sha256=l5yI6G_V6F1600I1IjiUKn87f4uYIrBAYU1MOyBBhg4,59
27
29
  langgraph_api/js/base.py,sha256=BpE8-xkUp8HFPRjSKx1tfUQubvoV4jYl6OwZdre3veI,209
@@ -30,7 +32,7 @@ langgraph_api/js/client.mts,sha256=2dptAX8fMowV9OC4DU4khjpZUgALBLVBTu3jTQbeUJY,2
30
32
  langgraph_api/js/errors.py,sha256=Cm1TKWlUCwZReDC5AQ6SgNIVGD27Qov2xcgHyf8-GXo,361
31
33
  langgraph_api/js/global.d.ts,sha256=cLJRZfYVGmgQ6o_xFevVNNTIi918ZUdxVRnpLVSjiAY,133
32
34
  langgraph_api/js/package.json,sha256=j6DMoVgwRqWqTwdd7R1f-kvmiTUAbO3HaUhM8K64lbE,1224
33
- langgraph_api/js/remote.py,sha256=jUUgdskUbDR_tuhZFrJo4OewbndQ76joczBv-mZHG9k,22637
35
+ langgraph_api/js/remote.py,sha256=g0H2x3W7kejxswhgZfFaljzl4Y7agMCnO5BdNVj1rDY,23474
34
36
  langgraph_api/js/schema.py,sha256=7idnv7URlYUdSNMBXQcw7E4SxaPxCq_Oxwnlml8q5ik,408
35
37
  langgraph_api/js/src/graph.mts,sha256=mRyMUp03Fwd5DlmNIFl3RiUCQuJ5XwmFp1AfAeKDfVc,3169
36
38
  langgraph_api/js/src/hooks.mjs,sha256=XtktgmIHlls_DsknAuwib-z7TqCm0haRoTXvnkgzMuo,601
@@ -58,14 +60,14 @@ langgraph_api/js/tests/graphs/yarn.lock,sha256=i2AAIgXA3XBLM8-oU45wgUefCSG-Tne4g
58
60
  langgraph_api/js/tests/parser.test.mts,sha256=dEC8KTqKygeb1u39ZvpPqCT4HtfPD947nLmITt2buxA,27883
59
61
  langgraph_api/js/tests/utils.mts,sha256=2kTybJ3O7Yfe1q3ehDouqV54ibXkNzsPZ_wBZLJvY-4,421
60
62
  langgraph_api/js/yarn.lock,sha256=W89dVYZMThcec08lJMcYnvEEnQK7VM5cPglvwpIdRv0,82773
61
- langgraph_api/lifespan.py,sha256=u5Fv9I4JqfLk30p5cNqkcEqeB2vTRMGSM5WlFHlE0mU,2219
62
- langgraph_api/logging.py,sha256=KB1ciduIWcMFfG0q9c5_SZemGrscht1RZXLZfeJSS00,3619
63
- langgraph_api/metadata.py,sha256=jPLNIRxHi7taZ0g60UdOEXenkvDwoYdI11tsmHenb28,3443
63
+ langgraph_api/lifespan.py,sha256=sUQnwszj3VaHKUyOLL8E7Rn7D4S_Vde0U69l739CgOQ,2472
64
+ langgraph_api/logging.py,sha256=JJIzbNIgLCN6ClQ3tA-Mm5ffuBGvpRDSZsEvnIlsuu4,3693
65
+ langgraph_api/metadata.py,sha256=5Mu3MUtUc-iIocU3X2SZDoGIqnUmTdT3517MhP94npI,3495
64
66
  langgraph_api/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
67
  langgraph_api/middleware/http_logger.py,sha256=yuFPNFIWwn-4AE1CogBfWlo8KytzywLi_Bd4ccsyVQE,3150
66
68
  langgraph_api/middleware/private_network.py,sha256=eYgdyU8AzU2XJu362i1L8aSFoQRiV7_aLBPw7_EgeqI,2111
67
69
  langgraph_api/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
- langgraph_api/models/run.py,sha256=1TzTmagDXFQD_LhIMRdguZHmrPSzztq1wiMjF63d2fc,9843
70
+ langgraph_api/models/run.py,sha256=J1DpfMwv7W3zX9WtCANyObaCuZWBVfz2JWgdIBa9VzU,10180
69
71
  langgraph_api/patch.py,sha256=82xjuFqY7tgrUm-k1XWHI6k8S6QovSD0zhe--8_xW4o,1296
70
72
  langgraph_api/queue_entrypoint.py,sha256=4xICUxXarNV8DhnaqAMhVi3xCmyVKCL3J5NzHxPA9Xc,1835
71
73
  langgraph_api/route.py,sha256=fM4qYCGbmH0a3_cV8uKocb1sLklehxO6HhdRXqLK6OM,4421
@@ -74,11 +76,11 @@ langgraph_api/serde.py,sha256=VoJ7Z1IuqrQGXFzEP1qijAITtWCrmjtVqlCRuScjXJI,3533
74
76
  langgraph_api/server.py,sha256=CiNK327zTsEpoVGeJK1JOtZHvOBYRoz0CnBTZUmsC7c,4567
75
77
  langgraph_api/sse.py,sha256=2wNodCOP2eg7a9mpSu0S3FQ0CHk2BBV_vv0UtIgJIcc,4034
76
78
  langgraph_api/state.py,sha256=8jx4IoTCOjTJuwzuXJKKFwo1VseHjNnw_CCq4x1SW14,2284
77
- langgraph_api/stream.py,sha256=LKXxol0y9xEsrgnbsDwyeKIPBDE-75uVz01OcLI-_QE,11976
79
+ langgraph_api/stream.py,sha256=lhjnom-T8GbquUZry-KSkajnqYjElaIERhPiXPtpw1E,11354
78
80
  langgraph_api/utils.py,sha256=92mSti9GfGdMRRWyESKQW5yV-75Z9icGHnIrBYvdypU,3619
79
- langgraph_api/validation.py,sha256=McizHlz-Ez8Jhdbc79mbPSde7GIuf2Jlbjx2yv_l6dA,4475
81
+ langgraph_api/validation.py,sha256=LnEdfgID2Z0HP75R_hqWxCn5d5ULGcDJ3r1xaDVv6-w,4845
80
82
  langgraph_api/webhook.py,sha256=1ncwO0rIZcj-Df9sxSnFEzd1gP1bfS4okeZQS8NSRoE,1382
81
- langgraph_api/worker.py,sha256=7yQfZBANt1kgJDOEs5B5c3Xy65lzNMmngVbBqLs-r5s,9802
83
+ langgraph_api/worker.py,sha256=zlkk2yg1tOBQHYwN-WHjc9NJgd3CAvsl8ET0-7tqQIk,9838
82
84
  langgraph_license/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
85
  langgraph_license/middleware.py,sha256=_ODIYzQkymr6W9_Fp9wtf1kAQspnpsmr53xuzyF2GA0,612
84
86
  langgraph_license/validation.py,sha256=Uu_G8UGO_WTlLsBEY0gTVWjRR4czYGfw5YAD3HLZoj0,203
@@ -86,15 +88,15 @@ langgraph_storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
86
88
  langgraph_storage/checkpoint.py,sha256=V4t2GwYEJdPCHbhq_4Udhlv0TWKDzlMu_rlNPdTDc50,3589
87
89
  langgraph_storage/database.py,sha256=I0AgFeJ-NSTT34vxKxQBUf1z2syFP0S8QpKCqTixrzY,5652
88
90
  langgraph_storage/inmem_stream.py,sha256=8bxkILIuFpr7P7RQ37SQAxrpRKvmbHdRB_nbfFiomlk,3263
89
- langgraph_storage/ops.py,sha256=6qj3zh3T7KZco_vz9FW1cI82sbrefo6u2BRmrUDXvZI,69529
91
+ langgraph_storage/ops.py,sha256=0DhtDQllBnV495DPrwhbR_bQUg7ZrGBe0FxSZcicz5g,72469
90
92
  langgraph_storage/queue.py,sha256=UDgsUTtUMfBSRDrQ8Onis-FJO4n7KTsX6sdpbY8Hs0A,5055
91
93
  langgraph_storage/retry.py,sha256=XmldOP4e_H5s264CagJRVnQMDFcEJR_dldVR1Hm5XvM,763
92
94
  langgraph_storage/store.py,sha256=33-J5-Xvobb9ArSa-GezP5KtfXgzWkHUHPyjRYmdw-E,2985
93
95
  langgraph_storage/ttl_dict.py,sha256=FlpEY8EANeXWKo_G5nmIotPquABZGyIJyk6HD9u6vqY,1533
94
96
  logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
95
- openapi.json,sha256=8NhoZn3Dse6Ia0i7fXCB46sdGnEg5qAhov_8of-6m0o,125282
96
- langgraph_api-0.0.31.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
97
- langgraph_api-0.0.31.dist-info/METADATA,sha256=lSR24lckxz9BgmrjjqG7b04Q22m5m1W49dHtggG6BeY,4027
98
- langgraph_api-0.0.31.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
99
- langgraph_api-0.0.31.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
100
- langgraph_api-0.0.31.dist-info/RECORD,,
97
+ openapi.json,sha256=6okfuwPAKB2Hi1tup-EFza49pmN_iD2Yt5JktMALcq4,127984
98
+ langgraph_api-0.0.33.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
99
+ langgraph_api-0.0.33.dist-info/METADATA,sha256=PCu4vrh8uCMFyww-JUdBxsrQFN8NrAX9hckbr3SPem0,4027
100
+ langgraph_api-0.0.33.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
101
+ langgraph_api-0.0.33.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
102
+ langgraph_api-0.0.33.dist-info/RECORD,,
langgraph_storage/ops.py CHANGED
@@ -22,6 +22,7 @@ from starlette.exceptions import HTTPException
22
22
 
23
23
  from langgraph_api.asyncio import SimpleTaskGroup, ValueEvent, create_task
24
24
  from langgraph_api.auth.custom import handle_event
25
+ from langgraph_api.command import map_cmd
25
26
  from langgraph_api.errors import UserInterrupt, UserRollback
26
27
  from langgraph_api.graph import get_graph
27
28
  from langgraph_api.schema import (
@@ -1081,6 +1082,78 @@ class Threads(Authenticated):
1081
1082
  else:
1082
1083
  raise HTTPException(status_code=400, detail="Thread has no graph ID.")
1083
1084
 
1085
+ @staticmethod
1086
+ async def bulk(
1087
+ conn: InMemConnectionProto,
1088
+ *,
1089
+ config: Config,
1090
+ supersteps: Sequence[dict],
1091
+ ctx: Auth.types.BaseAuthContext | None = None,
1092
+ ) -> ThreadUpdateResponse:
1093
+ """Update a thread with a batch of state updates."""
1094
+
1095
+ from langgraph.pregel.types import StateUpdate
1096
+
1097
+ thread_id = _ensure_uuid(config["configurable"]["thread_id"])
1098
+ filters = await Threads.handle_event(
1099
+ ctx,
1100
+ "update",
1101
+ Auth.types.ThreadsUpdate(thread_id=thread_id),
1102
+ )
1103
+
1104
+ thread_iter = await Threads.get(conn, thread_id, ctx=ctx)
1105
+ thread = await fetchone(
1106
+ thread_iter, not_found_detail=f"Thread {thread_id} not found."
1107
+ )
1108
+
1109
+ thread_config = thread["config"]
1110
+ metadata = thread["metadata"]
1111
+
1112
+ if not thread:
1113
+ raise HTTPException(status_code=404, detail="Thread not found")
1114
+
1115
+ if not _check_filter_match(metadata, filters):
1116
+ raise HTTPException(status_code=403, detail="Forbidden")
1117
+
1118
+ if graph_id := metadata.get("graph_id"):
1119
+ config["configurable"].setdefault("graph_id", graph_id)
1120
+ config["configurable"].setdefault("checkpoint_ns", "")
1121
+
1122
+ async with get_graph(
1123
+ graph_id, thread_config, checkpointer=Checkpointer(conn)
1124
+ ) as graph:
1125
+ next_config = await graph.abulk_update_state(
1126
+ config,
1127
+ [
1128
+ [
1129
+ StateUpdate(
1130
+ map_cmd(update.get("command"))
1131
+ if update.get("command")
1132
+ else update.get("values"),
1133
+ update.get("as_node"),
1134
+ )
1135
+ for update in superstep.get("updates", [])
1136
+ ]
1137
+ for superstep in supersteps
1138
+ ],
1139
+ )
1140
+
1141
+ state = await Threads.State.get(
1142
+ conn, config, subgraphs=False, ctx=ctx
1143
+ )
1144
+
1145
+ # update thread values
1146
+ for thread in conn.store["threads"]:
1147
+ if thread["thread_id"] == thread_id:
1148
+ thread["values"] = state.values
1149
+ break
1150
+
1151
+ return ThreadUpdateResponse(
1152
+ checkpoint=next_config["configurable"],
1153
+ )
1154
+ else:
1155
+ raise HTTPException(status_code=400, detail="Thread has no graph ID")
1156
+
1084
1157
  @staticmethod
1085
1158
  async def list(
1086
1159
  conn: InMemConnectionProto,
@@ -1905,7 +1978,8 @@ class Crons:
1905
1978
  conn: InMemConnectionProto,
1906
1979
  ctx: Auth.types.BaseAuthContext | None = None,
1907
1980
  ) -> AsyncIterator[Cron]:
1908
- raise NotImplementedError
1981
+ yield
1982
+ raise NotImplementedError("The in-mem server does not implement Crons.")
1909
1983
 
1910
1984
  @staticmethod
1911
1985
  async def set_next_run_date(