langgraph-api 0.2.51__py3-none-any.whl → 0.2.56__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/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.2.51"
1
+ __version__ = "0.2.56"
langgraph_api/config.py CHANGED
@@ -1,3 +1,4 @@
1
+ import os
1
2
  from os import environ, getenv
2
3
  from typing import Literal, TypedDict
3
4
 
@@ -149,6 +150,11 @@ STATS_INTERVAL_SECS = env("STATS_INTERVAL_SECS", cast=int, default=60)
149
150
  DATABASE_URI = env("DATABASE_URI", cast=str, default=getenv("POSTGRES_URI", undefined))
150
151
  MIGRATIONS_PATH = env("MIGRATIONS_PATH", cast=str, default="/storage/migrations")
151
152
  POSTGRES_POOL_MAX_SIZE = env("LANGGRAPH_POSTGRES_POOL_MAX_SIZE", cast=int, default=150)
153
+ RESUMABLE_STREAM_TTL_SECONDS = env(
154
+ "RESUMABLE_STREAM_TTL_SECONDS",
155
+ cast=int,
156
+ default=3600, # 1 hour
157
+ )
152
158
 
153
159
 
154
160
  def _get_encryption_key(key_str: str | None):
@@ -239,7 +245,9 @@ BG_JOB_INTERVAL = 30 # seconds
239
245
  BG_JOB_MAX_RETRIES = 3
240
246
  BG_JOB_ISOLATED_LOOPS = env("BG_JOB_ISOLATED_LOOPS", cast=bool, default=False)
241
247
  BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS = env(
242
- "BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS", cast=int, default=3600
248
+ "BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS",
249
+ cast=int,
250
+ default=180, # 3 minutes
243
251
  )
244
252
  MAX_STREAM_CHUNK_SIZE_BYTES = env(
245
253
  "MAX_STREAM_CHUNK_SIZE_BYTES", cast=int, default=1024 * 1024 * 128
@@ -353,3 +361,10 @@ API_VARIANT = env("LANGSMITH_LANGGRAPH_API_VARIANT", cast=str, default="")
353
361
 
354
362
  # UI
355
363
  UI_USE_BUNDLER = env("LANGGRAPH_UI_BUNDLER", cast=bool, default=False)
364
+
365
+ if not os.getenv("LANGCHAIN_REVISION_ID") and (
366
+ ref_sha := os.getenv("LANGSMITH_LANGGRAPH_GIT_REF_SHA")
367
+ ):
368
+ # This is respected by the langsmith SDK env inference
369
+ # https://github.com/langchain-ai/langsmith-sdk/blob/1b93e4c13b8369d92db891ae3babc3e2254f0e56/python/langsmith/env/_runtime_env.py#L190
370
+ os.environ["LANGCHAIN_REVISION_ID"] = ref_sha
langgraph_api/graph.py CHANGED
@@ -117,7 +117,6 @@ async def get_graph(
117
117
  """Return the runnable."""
118
118
  assert_graph_exists(graph_id)
119
119
  value = GRAPHS[graph_id]
120
- token = None
121
120
  if graph_id in FACTORY_ACCEPTS_CONFIG:
122
121
  config = ensure_config(config)
123
122
  if store is not None and not config["configurable"].get(CONFIG_KEY_STORE):
@@ -126,7 +125,7 @@ async def get_graph(
126
125
  CONFIG_KEY_CHECKPOINTER
127
126
  ):
128
127
  config["configurable"][CONFIG_KEY_CHECKPOINTER] = checkpointer
129
- token = var_child_runnable_config.set(config)
128
+ var_child_runnable_config.set(config)
130
129
  value = value(config) if FACTORY_ACCEPTS_CONFIG[graph_id] else value()
131
130
  try:
132
131
  async with _generate_graph(value) as graph_obj:
@@ -147,8 +146,7 @@ async def get_graph(
147
146
  update["config"] = config
148
147
  yield graph_obj.copy(update=update)
149
148
  finally:
150
- if token is not None:
151
- var_child_runnable_config.reset(token)
149
+ var_child_runnable_config.set(None)
152
150
 
153
151
 
154
152
  def graph_exists(graph_id: str) -> bool:
@@ -159,7 +157,10 @@ def graph_exists(graph_id: str) -> bool:
159
157
  def assert_graph_exists(graph_id: str) -> None:
160
158
  """Assert that a graph exists."""
161
159
  if not graph_exists(graph_id):
162
- raise HTTPException(status_code=404, detail=f"Graph '{graph_id}' not found")
160
+ raise HTTPException(
161
+ status_code=404,
162
+ detail=f"Graph '{graph_id}' not found. Expected one of: {sorted(GRAPHS.keys())}",
163
+ )
163
164
 
164
165
 
165
166
  def get_assistant_id(assistant_id: str) -> str:
langgraph_api/metadata.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import os
3
+ import uuid
3
4
  from datetime import UTC, datetime
4
5
 
5
6
  import langgraph.version
@@ -26,6 +27,20 @@ REVISION = os.getenv("LANGSMITH_LANGGRAPH_API_REVISION")
26
27
  VARIANT = os.getenv("LANGSMITH_LANGGRAPH_API_VARIANT")
27
28
  PROJECT_ID = os.getenv("LANGSMITH_HOST_PROJECT_ID")
28
29
  TENANT_ID = os.getenv("LANGSMITH_TENANT_ID")
30
+ if PROJECT_ID:
31
+ try:
32
+ uuid.UUID(PROJECT_ID)
33
+ except ValueError:
34
+ raise ValueError(
35
+ f"Invalid project ID: {PROJECT_ID}. Must be a valid UUID"
36
+ ) from None
37
+ if TENANT_ID:
38
+ try:
39
+ uuid.UUID(TENANT_ID)
40
+ except ValueError:
41
+ raise ValueError(
42
+ f"Invalid tenant ID: {TENANT_ID}. Must be a valid UUID"
43
+ ) from None
29
44
  if VARIANT == "cloud":
30
45
  HOST = "saas"
31
46
  elif PROJECT_ID:
@@ -10,11 +10,13 @@ if not (
10
10
  truststore.inject_into_ssl() # noqa: F401
11
11
 
12
12
  import asyncio
13
+ import contextlib
13
14
  import http.server
14
15
  import json
15
16
  import logging.config
16
17
  import os
17
18
  import pathlib
19
+ import signal
18
20
 
19
21
  import structlog
20
22
  import uvloop
@@ -56,26 +58,43 @@ async def healthcheck_server():
56
58
 
57
59
 
58
60
  async def entrypoint():
61
+ from langgraph_api import logging as lg_logging
62
+
63
+ lg_logging.set_logging_context({"entrypoint": "python-queue"})
59
64
  tasks: set[asyncio.Task] = set()
60
- # start simple http server for health checks
61
65
  tasks.add(asyncio.create_task(healthcheck_server()))
62
- # start queue and associated tasks
63
66
  async with lifespan(None, with_cron_scheduler=False, taskset=tasks):
64
- # run forever, error if any tasks fail
65
- try:
66
- await asyncio.gather(*tasks)
67
- except asyncio.CancelledError:
68
- pass
67
+ await asyncio.gather(*tasks)
68
+
69
+
70
+ async def main():
71
+ """Run the queue entrypoint and shut down gracefully on SIGTERM/SIGINT."""
72
+ loop = asyncio.get_running_loop()
73
+ stop_event = asyncio.Event()
74
+
75
+ def _handle_signal() -> None:
76
+ logger.warning("Received termination signal, initiating graceful shutdown")
77
+ stop_event.set()
78
+
79
+ try:
80
+ loop.add_signal_handler(signal.SIGTERM, _handle_signal)
81
+ except (NotImplementedError, RuntimeError):
82
+ signal.signal(signal.SIGTERM, lambda *_: _handle_signal())
83
+
84
+ entry_task = asyncio.create_task(entrypoint())
85
+ await stop_event.wait()
86
+
87
+ logger.warning("Cancelling queue entrypoint task")
88
+ entry_task.cancel()
89
+ with contextlib.suppress(asyncio.CancelledError):
90
+ await entry_task
69
91
 
70
92
 
71
93
  if __name__ == "__main__":
72
- # set up logging
73
94
  with open(pathlib.Path(__file__).parent.parent / "logging.json") as file:
74
95
  loaded_config = json.load(file)
75
96
  logging.config.dictConfig(loaded_config)
76
97
 
77
- # set up uvloop
78
98
  uvloop.install()
79
99
 
80
- # run the entrypoint
81
- asyncio.run(entrypoint())
100
+ asyncio.run(main())
langgraph_api/worker.py CHANGED
@@ -193,7 +193,6 @@ async def worker(
193
193
  async with connect() as conn:
194
194
  if exception is None:
195
195
  status = "success"
196
- await Runs.set_status(conn, run_id, "success")
197
196
  # If a stateful run succeeded but no checkpoint was returned, likely
198
197
  # there was a retriable exception that resumed right at the end
199
198
  if checkpoint is None and not temporary:
@@ -214,6 +213,9 @@ async def worker(
214
213
  run_id=str(run_id),
215
214
  run_attempt=attempt,
216
215
  )
216
+ await Threads.set_joint_status(
217
+ conn, run["thread_id"], run_id, status, checkpoint=checkpoint
218
+ )
217
219
  elif isinstance(exception, TimeoutError):
218
220
  status = "timeout"
219
221
  run_ended_at = datetime.now(UTC).isoformat()
@@ -231,13 +233,17 @@ async def worker(
231
233
  else None
232
234
  ),
233
235
  )
234
- await Runs.set_status(conn, run_id, "timeout")
236
+ await Threads.set_joint_status(
237
+ conn, run["thread_id"], run_id, status, checkpoint=checkpoint
238
+ )
235
239
  elif isinstance(exception, UserRollback):
236
240
  status = "rollback"
237
241
  run_ended_at_dt = datetime.now(UTC)
238
242
  run_ended_at = run_ended_at_dt.isoformat()
239
243
  try:
240
- await Runs.delete(conn, run_id, thread_id=run["thread_id"])
244
+ await Threads.set_joint_status(
245
+ conn, run["thread_id"], run_id, status, checkpoint=checkpoint
246
+ )
241
247
  await logger.ainfo(
242
248
  "Background run rolled back",
243
249
  run_id=str(run_id),
@@ -285,7 +291,9 @@ async def worker(
285
291
  else None
286
292
  ),
287
293
  )
288
- await Runs.set_status(conn, run_id, "interrupted")
294
+ await Threads.set_joint_status(
295
+ conn, run["thread_id"], run_id, status, checkpoint, exception
296
+ )
289
297
  elif isinstance(exception, RETRIABLE_EXCEPTIONS):
290
298
  status = "retry"
291
299
  run_ended_at_dt = datetime.now(UTC)
@@ -300,6 +308,7 @@ async def worker(
300
308
  run_ended_at=run_ended_at,
301
309
  run_exec_ms=ms(run_ended_at_dt, run_started_at),
302
310
  )
311
+ # Don't update thread status yet.
303
312
  await Runs.set_status(conn, run_id, "pending")
304
313
  else:
305
314
  status = "error"
@@ -315,7 +324,9 @@ async def worker(
315
324
  run_ended_at=run_ended_at,
316
325
  run_exec_ms=ms(run_ended_at_dt, run_started_at),
317
326
  )
318
- await Runs.set_status(conn, run_id, "error")
327
+ await Threads.set_joint_status(
328
+ conn, run["thread_id"], run_id, status, checkpoint, exception
329
+ )
319
330
 
320
331
  # delete or set status of thread
321
332
  if not isinstance(exception, RETRIABLE_EXCEPTIONS):
@@ -336,6 +347,7 @@ async def worker(
336
347
  raise
337
348
 
338
349
  if isinstance(exception, RETRIABLE_EXCEPTIONS):
350
+ await logger.awarning("RETRYING", exc_info=exception)
339
351
  # re-raise so Runs.enter knows not to mark as done
340
352
  # Runs.enter will catch the exception, but what triggers the retry
341
353
  # is setting the status to "pending"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-api
3
- Version: 0.2.51
3
+ Version: 0.2.56
4
4
  Author-email: Nuno Campos <nuno@langchain.dev>, Will Fu-Hinthorn <will@langchain.dev>
5
5
  License: Elastic-2.0
6
6
  License-File: LICENSE
@@ -11,7 +11,7 @@ Requires-Dist: httpx>=0.25.0
11
11
  Requires-Dist: jsonschema-rs<0.30,>=0.20.0
12
12
  Requires-Dist: langchain-core>=0.3.64
13
13
  Requires-Dist: langgraph-checkpoint>=2.0.23
14
- Requires-Dist: langgraph-runtime-inmem<0.3,>=0.2.0
14
+ Requires-Dist: langgraph-runtime-inmem<0.4,>=0.3.0
15
15
  Requires-Dist: langgraph-sdk>=0.1.66
16
16
  Requires-Dist: langgraph>=0.3.27
17
17
  Requires-Dist: langsmith>=0.3.45
@@ -1,17 +1,17 @@
1
- langgraph_api/__init__.py,sha256=IVfgL24_F5j7rUHf7ys_SvphooWjDzcdlDH4HeGwa-E,23
1
+ langgraph_api/__init__.py,sha256=yuhEwVPZG_uwEXVduMxOuyth-d2aFtmKNM7h6XPNHtM,23
2
2
  langgraph_api/asgi_transport.py,sha256=eqifhHxNnxvI7jJqrY1_8RjL4Fp9NdN4prEub2FWBt8,5091
3
3
  langgraph_api/asyncio.py,sha256=Odnc6mAJIGF3eFWT8Xcrg2Zam7FwzXkfCWEHaXfrzQQ,9371
4
4
  langgraph_api/cli.py,sha256=9Ou3tGDDY_VVLt5DFle8UviJdpI4ZigC5hElYvq2-To,14519
5
5
  langgraph_api/command.py,sha256=3O9v3i0OPa96ARyJ_oJbLXkfO8rPgDhLCswgO9koTFA,768
6
- langgraph_api/config.py,sha256=pFIZb4t2Vo7HbX0ZMjUNDR7Q7Bpj-stp_TmSmI026yo,11115
6
+ langgraph_api/config.py,sha256=do03SoO93rfL7PKuxviLZbYuVlzlZJayXnodkO-nxv0,11623
7
7
  langgraph_api/cron_scheduler.py,sha256=i87j4pJrcsmsqMKeKUs69gaAjrGaSM3pM3jnXdN5JDQ,2630
8
8
  langgraph_api/errors.py,sha256=Bu_i5drgNTyJcLiyrwVE_6-XrSU50BHf9TDpttki9wQ,1690
9
- langgraph_api/graph.py,sha256=MPm8DvInBQsq2em45c2YD5bW6T_G1LlDkAuWq-19gCQ,23240
9
+ langgraph_api/graph.py,sha256=JUJgmYF4Bfj3zKH9DZ2c4fgCOaZZ4QA3rbeC5Gtp4Bg,23255
10
10
  langgraph_api/http.py,sha256=gYbxxjY8aLnsXeJymcJ7G7Nj_yToOGpPYQqmZ1_ggfA,5240
11
11
  langgraph_api/logging.py,sha256=1BXELwUBY8gZeOWCYElbBu_GyHLM2jjlDVJznlekqvQ,4268
12
- langgraph_api/metadata.py,sha256=2sz9ECnbnQtgqN6ptDkRmymaVKfQPgaX-JuDJDJB47c,4254
12
+ langgraph_api/metadata.py,sha256=Gx0b6YszLRjdWLDVN8OcVgC_YYQG_nQitPfUfgQx1w8,4648
13
13
  langgraph_api/patch.py,sha256=Dgs0PXHytekX4SUL6KsjjN0hHcOtGLvv1GRGbh6PswU,1408
14
- langgraph_api/queue_entrypoint.py,sha256=_41ZveMDdn9bapjA7Ik9FG3r4hyIwXESUM5F1PdlieE,2309
14
+ langgraph_api/queue_entrypoint.py,sha256=rW4-OEsWwBsLLp6U0P-de-I0tR3gPZaeBKWB3LRrTr0,2923
15
15
  langgraph_api/route.py,sha256=4VBkJMeusfiZtLzyUaKm1HwLHTq0g15y2CRiRhM6xyA,4773
16
16
  langgraph_api/schema.py,sha256=2711t4PIBk5dky4gmMndrTRC9CVvAgH47C9FKDxhkBo,5444
17
17
  langgraph_api/serde.py,sha256=8fQXg7T7RVUqj_jgOoSOJrWVpQDW0qJKjAjSsEhPHo4,4803
@@ -24,7 +24,7 @@ langgraph_api/thread_ttl.py,sha256=-Ox8NFHqUH3wGNdEKMIfAXUubY5WGifIgCaJ7npqLgw,1
24
24
  langgraph_api/utils.py,sha256=92mSti9GfGdMRRWyESKQW5yV-75Z9icGHnIrBYvdypU,3619
25
25
  langgraph_api/validation.py,sha256=zMuKmwUEBjBgFMwAaeLZmatwGVijKv2sOYtYg7gfRtc,4950
26
26
  langgraph_api/webhook.py,sha256=1ncwO0rIZcj-Df9sxSnFEzd1gP1bfS4okeZQS8NSRoE,1382
27
- langgraph_api/worker.py,sha256=hg15i1UYRwuHWIzbOG_8HJk1FtUO8-Amcp1c2jIqiqg,15278
27
+ langgraph_api/worker.py,sha256=FgTYpOA4Unt3xcqWpOKPqSZpFqND5GcqLk6O0fi4MHU,15812
28
28
  langgraph_api/api/__init__.py,sha256=YVzpbn5IQotvuuLG9fhS9QMrxXfP4s4EpEMG0n4q3Nw,5625
29
29
  langgraph_api/api/assistants.py,sha256=6IPVKQBlI95-Z4nYdqBY9st9oynGJAocL67cwnDaZCk,15744
30
30
  langgraph_api/api/mcp.py,sha256=RvRYgANqRzNQzSmgjNkq4RlKTtoEJYil04ot9lsmEtE,14352
@@ -86,8 +86,8 @@ langgraph_runtime/store.py,sha256=7mowndlsIroGHv3NpTSOZDJR0lCuaYMBoTnTrewjslw,11
86
86
  LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
87
87
  logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
88
88
  openapi.json,sha256=wrJup7sCRlZXTRagjzGZ7474U1wma4ZzYTkkninrT6M,141875
89
- langgraph_api-0.2.51.dist-info/METADATA,sha256=GME1y9H2-Xw8LoqNV6R0RW5VwF-ypHOCNKYlAmrNfXE,3891
90
- langgraph_api-0.2.51.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
91
- langgraph_api-0.2.51.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
92
- langgraph_api-0.2.51.dist-info/licenses/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
93
- langgraph_api-0.2.51.dist-info/RECORD,,
89
+ langgraph_api-0.2.56.dist-info/METADATA,sha256=wfCknYuOUBNVrO4AM6m33ekJis6bPutLf-EWFDWpPTw,3891
90
+ langgraph_api-0.2.56.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
91
+ langgraph_api-0.2.56.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
92
+ langgraph_api-0.2.56.dist-info/licenses/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
93
+ langgraph_api-0.2.56.dist-info/RECORD,,