langgraph-api 0.2.14__py3-none-any.whl → 0.2.16__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.14"
1
+ __version__ = "0.2.16"
langgraph_api/config.py CHANGED
@@ -147,7 +147,6 @@ STATS_INTERVAL_SECS = env("STATS_INTERVAL_SECS", cast=int, default=60)
147
147
 
148
148
  DATABASE_URI = env("DATABASE_URI", cast=str, default=getenv("POSTGRES_URI", undefined))
149
149
  MIGRATIONS_PATH = env("MIGRATIONS_PATH", cast=str, default="/storage/migrations")
150
-
151
150
  POSTGRES_POOL_MAX_SIZE = env("LANGGRAPH_POSTGRES_POOL_MAX_SIZE", cast=int, default=150)
152
151
 
153
152
 
@@ -237,6 +236,9 @@ BG_JOB_HEARTBEAT = 120 # seconds
237
236
  BG_JOB_INTERVAL = 30 # seconds
238
237
  BG_JOB_MAX_RETRIES = 3
239
238
  BG_JOB_ISOLATED_LOOPS = env("BG_JOB_ISOLATED_LOOPS", cast=bool, default=False)
239
+ BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS = env(
240
+ "BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS", cast=int, default=3600
241
+ )
240
242
 
241
243
 
242
244
  def _parse_thread_ttl(value: str | None) -> ThreadTTLConfig | None:
langgraph_api/logging.py CHANGED
@@ -1,6 +1,8 @@
1
+ import contextvars
1
2
  import logging
2
3
  import os
3
4
  import threading
5
+ import typing
4
6
 
5
7
  import structlog
6
8
  from starlette.config import Config
@@ -17,6 +19,10 @@ LOG_LEVEL = log_env("LOG_LEVEL", cast=str, default="INFO")
17
19
  logging.getLogger().setLevel(LOG_LEVEL.upper())
18
20
  logging.getLogger("psycopg").setLevel(logging.WARNING)
19
21
 
22
+ worker_config: contextvars.ContextVar[dict[str, typing.Any] | None] = (
23
+ contextvars.ContextVar("worker_config", default=None)
24
+ )
25
+
20
26
  # custom processors
21
27
 
22
28
 
@@ -27,6 +33,15 @@ def add_thread_name(
27
33
  return event_dict
28
34
 
29
35
 
36
+ def set_logging_context(val: dict[str, typing.Any] | None) -> contextvars.Token:
37
+ if val is None:
38
+ return worker_config.set(None)
39
+ current = worker_config.get()
40
+ if current is None:
41
+ return worker_config.set(val)
42
+ return worker_config.set({**current, **val})
43
+
44
+
30
45
  class AddPrefixedEnvVars:
31
46
  def __init__(self, prefix: str) -> None:
32
47
  self.kv = {
@@ -42,6 +57,15 @@ class AddPrefixedEnvVars:
42
57
  return event_dict
43
58
 
44
59
 
60
+ class AddLoggingContext:
61
+ def __call__(
62
+ self, logger: logging.Logger, method_name: str, event_dict: EventDict
63
+ ) -> EventDict:
64
+ if (ctx := worker_config.get()) is not None:
65
+ event_dict.update(ctx)
66
+ return event_dict
67
+
68
+
45
69
  class JSONRenderer:
46
70
  def __call__(
47
71
  self, logger: logging.Logger, method_name: str, event_dict: EventDict
@@ -87,6 +111,7 @@ shared_processors = [
87
111
  structlog.processors.StackInfoRenderer(),
88
112
  structlog.processors.format_exc_info,
89
113
  structlog.processors.UnicodeDecoder(),
114
+ AddLoggingContext(),
90
115
  ]
91
116
 
92
117
 
@@ -0,0 +1,30 @@
1
+ """Middleware to handle setting request IDs for logging."""
2
+
3
+ import re
4
+ import uuid
5
+
6
+ from starlette.types import ASGIApp, Receive, Scope, Send
7
+
8
+ PATHS_INCLUDE = ("/runs", "/threads")
9
+
10
+
11
+ class RequestIdMiddleware:
12
+ def __init__(self, app: ASGIApp, mount_prefix: str = ""):
13
+ self.app = app
14
+ paths = (
15
+ (mount_prefix + p for p in ("/runs", "/threads"))
16
+ if mount_prefix
17
+ else ("/runs", "/threads")
18
+ )
19
+ self.pattern = re.compile(r"^(" + "|".join(paths) + r")(/.*)?$")
20
+
21
+ async def __call__(self, scope: Scope, receive: Receive, send: Send):
22
+ if scope["type"] == "http" and self.pattern.match(scope["path"]):
23
+ request_id = next(
24
+ (h[1] for h in scope["headers"] if h[0] == b"x-request-id"),
25
+ None,
26
+ )
27
+ if request_id is None:
28
+ request_id = str(uuid.uuid4()).encode()
29
+ scope["headers"].append((b"x-request-id", request_id))
30
+ await self.app(scope, receive, send)
@@ -248,6 +248,7 @@ async def create_valid_run(
248
248
  barrier: asyncio.Barrier | None = None,
249
249
  run_id: UUID | None = None,
250
250
  ) -> Run:
251
+ request_id = headers.get("x-request-id") # Will be null in the crons scheduler.
251
252
  (
252
253
  assistant_id,
253
254
  thread_id,
@@ -288,6 +289,8 @@ async def create_valid_run(
288
289
  configurable["langgraph_auth_permissions"] = ctx.permissions
289
290
  else:
290
291
  user_id = None
292
+ if not configurable.get("langgraph_request_id"):
293
+ configurable["langgraph_request_id"] = request_id
291
294
  run_coro = Runs.put(
292
295
  conn,
293
296
  assistant_id,
langgraph_api/server.py CHANGED
@@ -29,6 +29,7 @@ from langgraph_api.errors import (
29
29
  from langgraph_runtime.lifespan import lifespan
30
30
  from langgraph_api.middleware.http_logger import AccessLoggerMiddleware
31
31
  from langgraph_api.middleware.private_network import PrivateNetworkMiddleware
32
+ from langgraph_api.middleware.request_id import RequestIdMiddleware
32
33
  from langgraph_api.utils import SchemaGenerator
33
34
  from langgraph_runtime.retry import OVERLOADED_EXCEPTIONS
34
35
  from langgraph_api.js.base import is_js_path
@@ -69,6 +70,7 @@ middleware.extend(
69
70
  )
70
71
  ),
71
72
  Middleware(AccessLoggerMiddleware, logger=logger),
73
+ Middleware(RequestIdMiddleware, mount_prefix=config.MOUNT_PREFIX),
72
74
  ]
73
75
  )
74
76
  exception_handlers = {
langgraph_api/worker.py CHANGED
@@ -8,23 +8,18 @@ import structlog
8
8
  from langgraph.pregel.debug import CheckpointPayload, TaskResultPayload
9
9
  from starlette.exceptions import HTTPException
10
10
 
11
+ import langgraph_api.logging as lg_logging
11
12
  from langgraph_api.auth.custom import SimpleUser, normalize_user
12
13
  from langgraph_api.config import (
13
14
  BG_JOB_ISOLATED_LOOPS,
14
15
  BG_JOB_MAX_RETRIES,
15
16
  BG_JOB_TIMEOUT_SECS,
16
17
  )
17
- from langgraph_api.errors import (
18
- UserInterrupt,
19
- UserRollback,
20
- )
18
+ from langgraph_api.errors import UserInterrupt, UserRollback
21
19
  from langgraph_api.js.errors import RemoteException
22
20
  from langgraph_api.metadata import incr_runs
23
21
  from langgraph_api.schema import Run
24
- from langgraph_api.stream import (
25
- astream_state,
26
- consume,
27
- )
22
+ from langgraph_api.stream import astream_state, consume
28
23
  from langgraph_api.utils import set_auth_ctx, with_user
29
24
  from langgraph_runtime.database import connect
30
25
  from langgraph_runtime.ops import Runs, Threads
@@ -89,15 +84,22 @@ async def worker(
89
84
  ):
90
85
  temporary = run["kwargs"].get("temporary", False)
91
86
  run_created_at = run["created_at"].isoformat()
87
+ run["kwargs"]
88
+ lg_logging.set_logging_context(
89
+ {
90
+ "run_id": str(run_id),
91
+ "run_attempt": attempt,
92
+ "thread_id": str(run.get("thread_id")),
93
+ "assistant_id": str(run.get("assistant_id")),
94
+ "graph_id": _get_graph_id(run),
95
+ "request_id": _get_request_id(run),
96
+ }
97
+ )
98
+
92
99
  await logger.ainfo(
93
100
  "Starting background run",
94
- run_id=str(run_id),
95
- run_attempt=attempt,
96
- run_created_at=run_created_at,
97
101
  run_started_at=run_started_at.isoformat(),
98
102
  run_queue_ms=ms(run_started_at, run["created_at"]),
99
- thread_id=run.get("thread_id"),
100
- assistant_id=run.get("assistant_id"),
101
103
  )
102
104
 
103
105
  def on_checkpoint(checkpoint_arg: CheckpointPayload):
@@ -168,7 +170,6 @@ async def worker(
168
170
  run_started_at=run_started_at.isoformat(),
169
171
  run_ended_at=run_ended_at,
170
172
  run_exec_ms=ms(datetime.now(UTC), run_started_at),
171
- graph_id=_get_graph_id(run),
172
173
  )
173
174
  await Runs.set_status(conn, run_id, "timeout")
174
175
  except UserRollback as e:
@@ -185,7 +186,6 @@ async def worker(
185
186
  run_started_at=run_started_at.isoformat(),
186
187
  run_ended_at=run_ended_at,
187
188
  run_exec_ms=ms(datetime.now(UTC), run_started_at),
188
- graph_id=_get_graph_id(run),
189
189
  )
190
190
 
191
191
  except InFailedSqlTransaction as e:
@@ -223,7 +223,6 @@ async def worker(
223
223
  run_started_at=run_started_at.isoformat(),
224
224
  run_ended_at=run_ended_at,
225
225
  run_exec_ms=ms(datetime.now(UTC), run_started_at),
226
- graph_id=_get_graph_id(run),
227
226
  )
228
227
  await Runs.set_status(conn, run_id, "interrupted")
229
228
  except RETRIABLE_EXCEPTIONS as e:
@@ -239,7 +238,6 @@ async def worker(
239
238
  run_started_at=run_started_at.isoformat(),
240
239
  run_ended_at=run_ended_at,
241
240
  run_exec_ms=ms(datetime.now(UTC), run_started_at),
242
- graph_id=_get_graph_id(run),
243
241
  )
244
242
  await Runs.set_status(conn, run_id, "pending")
245
243
  raise
@@ -256,7 +254,6 @@ async def worker(
256
254
  run_started_at=run_started_at.isoformat(),
257
255
  run_ended_at=run_ended_at,
258
256
  run_exec_ms=ms(datetime.now(UTC), run_started_at),
259
- graph_id=_get_graph_id(run),
260
257
  )
261
258
  await Runs.set_status(conn, run_id, "error")
262
259
  set_auth_ctx(None, None)
@@ -313,6 +310,13 @@ def ms(after: datetime, before: datetime) -> int:
313
310
  return int((after - before).total_seconds() * 1000)
314
311
 
315
312
 
313
+ def _get_request_id(run: Run) -> str | None:
314
+ try:
315
+ return run["kwargs"]["config"]["configurable"]["langgraph_request_id"]
316
+ except Exception:
317
+ return None
318
+
319
+
316
320
  def _get_graph_id(run: Run) -> str | None:
317
321
  try:
318
322
  return run["kwargs"]["config"]["configurable"]["graph_id"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: langgraph-api
3
- Version: 0.2.14
3
+ Version: 0.2.16
4
4
  Summary:
5
5
  License: Elastic-2.0
6
6
  Author: Nuno Campos
@@ -1,5 +1,5 @@
1
1
  LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
2
- langgraph_api/__init__.py,sha256=3fSLgeJpZq4cUgzAH_CdFzXwJEO3NH_VVDv2pQnmwN0,23
2
+ langgraph_api/__init__.py,sha256=nS1Qf8CA6pxMzqGCeAMZHTKK2igCSNakZ3o7kDK0JIQ,23
3
3
  langgraph_api/api/__init__.py,sha256=YVzpbn5IQotvuuLG9fhS9QMrxXfP4s4EpEMG0n4q3Nw,5625
4
4
  langgraph_api/api/assistants.py,sha256=mcKaVeNG8hQAV4IQDhaukS7FgVqTIVQNTyti3GfK2KI,14649
5
5
  langgraph_api/api/mcp.py,sha256=RvRYgANqRzNQzSmgjNkq4RlKTtoEJYil04ot9lsmEtE,14352
@@ -20,7 +20,7 @@ langgraph_api/auth/noop.py,sha256=Bk6Nf3p8D_iMVy_OyfPlyiJp_aEwzL-sHrbxoXpCbac,58
20
20
  langgraph_api/auth/studio_user.py,sha256=FzFQRROKDlA9JjtBuwyZvk6Mbwno5M9RVYjDO6FU3F8,186
21
21
  langgraph_api/cli.py,sha256=9Ou3tGDDY_VVLt5DFle8UviJdpI4ZigC5hElYvq2-To,14519
22
22
  langgraph_api/command.py,sha256=3O9v3i0OPa96ARyJ_oJbLXkfO8rPgDhLCswgO9koTFA,768
23
- langgraph_api/config.py,sha256=Jsv1Qw1esmO1hllo2_0UhDtcIxOAmF38-8xutpxDcw4,10862
23
+ langgraph_api/config.py,sha256=AsEyWCZXl_x-NvFhbhM7wagspA7DRyoL-IY6f2dBpUo,10968
24
24
  langgraph_api/cron_scheduler.py,sha256=i87j4pJrcsmsqMKeKUs69gaAjrGaSM3pM3jnXdN5JDQ,2630
25
25
  langgraph_api/errors.py,sha256=Bu_i5drgNTyJcLiyrwVE_6-XrSU50BHf9TDpttki9wQ,1690
26
26
  langgraph_api/graph.py,sha256=YyWCPtoI9VDV0knjCMUFoH4r9OFVsAiv5K8FzbziMqs,21488
@@ -69,19 +69,20 @@ langgraph_api/js/tests/utils.mts,sha256=Jk1ZZmllNgSS6FJlSs9VaQxHqCEUzkqB5rRQwTSA
69
69
  langgraph_api/js/tsconfig.json,sha256=imCYqVnqFpaBoZPx8k1nO4slHIWBFsSlmCYhO73cpBs,341
70
70
  langgraph_api/js/ui.py,sha256=XNT8iBcyT8XmbIqSQUWd-j_00HsaWB2vRTVabwFBkik,2439
71
71
  langgraph_api/js/yarn.lock,sha256=qtnJQjwX8OUQVzsh51N1imtjYGg5RCI9xkg5n5VZMKI,84019
72
- langgraph_api/logging.py,sha256=JJIzbNIgLCN6ClQ3tA-Mm5ffuBGvpRDSZsEvnIlsuu4,3693
72
+ langgraph_api/logging.py,sha256=3GSbvmXi8yWxWxJ558RE81xUEdklrPHJ4PpkxAb-35w,4428
73
73
  langgraph_api/metadata.py,sha256=ptaxwmzdx2bUBSc1KRhqgF-Xnm-Zh2gqwSiHpl8LD9c,4482
74
74
  langgraph_api/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
75
  langgraph_api/middleware/http_logger.py,sha256=aj4mdisRobFePkD3Iy6-w_Mujwx4TQRaEhPvSd6HgLk,3284
76
76
  langgraph_api/middleware/private_network.py,sha256=eYgdyU8AzU2XJu362i1L8aSFoQRiV7_aLBPw7_EgeqI,2111
77
+ langgraph_api/middleware/request_id.py,sha256=jBaL-zRLIYuGkYBz1poSH35ld_GDRuiMR5CyIpzKJt8,1025
77
78
  langgraph_api/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
- langgraph_api/models/run.py,sha256=hPiiP6jgJX-jsg4Aihwi0tpC-nkTabV9YE0WNlD4EjI,13166
79
+ langgraph_api/models/run.py,sha256=kVdxF239sVmdvXUZNBL-GV0-euZO-Ca2Jz8qseNycKM,13362
79
80
  langgraph_api/patch.py,sha256=Dgs0PXHytekX4SUL6KsjjN0hHcOtGLvv1GRGbh6PswU,1408
80
81
  langgraph_api/queue_entrypoint.py,sha256=gjtajZfnDXhsi7JElfmkY-p0ENBiKBDJ4Ugiw-exapw,1839
81
82
  langgraph_api/route.py,sha256=uN311KjIugyNHG3rmVw_ms61QO1W1l16jJx03rf0R_s,4630
82
83
  langgraph_api/schema.py,sha256=2711t4PIBk5dky4gmMndrTRC9CVvAgH47C9FKDxhkBo,5444
83
84
  langgraph_api/serde.py,sha256=TVsx2QQtepf8Wsgsabcku1NV4Vbugu4Oujmdnq4qMS0,3964
84
- langgraph_api/server.py,sha256=lCv_ZHLbMnhKRhdaSJi1edk-O9We-MnQafkGzdWlngw,6599
85
+ langgraph_api/server.py,sha256=CzBeOr8fjaVSbdmBNysx3zACSxTV8DrmeU7EQvZrlDE,6742
85
86
  langgraph_api/sse.py,sha256=3jG_FZj8FI9r7xGWTqaAyDkmqf6P1NOu0EzGrcSOGYc,4033
86
87
  langgraph_api/state.py,sha256=8jx4IoTCOjTJuwzuXJKKFwo1VseHjNnw_CCq4x1SW14,2284
87
88
  langgraph_api/stream.py,sha256=a4sjBm3XhHK6NC4OD1dHey53t6czKodvrlxh9rfjfSA,11718
@@ -90,14 +91,14 @@ langgraph_api/tunneling/cloudflare.py,sha256=iKb6tj-VWPlDchHFjuQyep2Dpb-w2NGfJKt
90
91
  langgraph_api/utils.py,sha256=92mSti9GfGdMRRWyESKQW5yV-75Z9icGHnIrBYvdypU,3619
91
92
  langgraph_api/validation.py,sha256=zMuKmwUEBjBgFMwAaeLZmatwGVijKv2sOYtYg7gfRtc,4950
92
93
  langgraph_api/webhook.py,sha256=1ncwO0rIZcj-Df9sxSnFEzd1gP1bfS4okeZQS8NSRoE,1382
93
- langgraph_api/worker.py,sha256=X2noF7KqsG2UOn8gRPgoHxvJmaC2jDzTDy8Fen2jtJc,12230
94
+ langgraph_api/worker.py,sha256=uuQ6i9iKvSeF4CD_w1nGgpQ4FXLZ2wpn3IvHOLfphTY,12391
94
95
  langgraph_license/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
96
  langgraph_license/validation.py,sha256=ZKraAVJArAABKqrmHN-EN18ncoNUmRm500Yt1Sc7tUA,537
96
97
  langgraph_runtime/__init__.py,sha256=O4GgSmu33c-Pr8Xzxj_brcK5vkm70iNTcyxEjICFZxA,1075
97
98
  logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
98
99
  openapi.json,sha256=GefWJwBrbrN_jNDAEFlwNEXx1ottRtvjOmKBi965KLU,133756
99
- langgraph_api-0.2.14.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
100
- langgraph_api-0.2.14.dist-info/METADATA,sha256=hhrsZXGXx-UCWbSi20eVnJpp9BZT3-eW5mxVCnN6PFA,4241
101
- langgraph_api-0.2.14.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
102
- langgraph_api-0.2.14.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
103
- langgraph_api-0.2.14.dist-info/RECORD,,
100
+ langgraph_api-0.2.16.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
101
+ langgraph_api-0.2.16.dist-info/METADATA,sha256=o69ePDILT_btM9GX96mzw9PGHKanX5JKDP6VHKrjiz4,4241
102
+ langgraph_api-0.2.16.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
103
+ langgraph_api-0.2.16.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
104
+ langgraph_api-0.2.16.dist-info/RECORD,,