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 +1 -1
- langgraph_api/config.py +16 -1
- langgraph_api/graph.py +6 -5
- langgraph_api/metadata.py +15 -0
- langgraph_api/queue_entrypoint.py +30 -11
- langgraph_api/worker.py +17 -5
- {langgraph_api-0.2.51.dist-info → langgraph_api-0.2.56.dist-info}/METADATA +2 -2
- {langgraph_api-0.2.51.dist-info → langgraph_api-0.2.56.dist-info}/RECORD +11 -11
- {langgraph_api-0.2.51.dist-info → langgraph_api-0.2.56.dist-info}/WHEEL +0 -0
- {langgraph_api-0.2.51.dist-info → langgraph_api-0.2.56.dist-info}/entry_points.txt +0 -0
- {langgraph_api-0.2.51.dist-info → langgraph_api-0.2.56.dist-info}/licenses/LICENSE +0 -0
langgraph_api/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.2.
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
90
|
-
langgraph_api-0.2.
|
|
91
|
-
langgraph_api-0.2.
|
|
92
|
-
langgraph_api-0.2.
|
|
93
|
-
langgraph_api-0.2.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|