langgraph-runtime-inmem 0.38.0.dev6__tar.gz → 0.38.0.dev7__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.
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/PKG-INFO +1 -1
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/langgraph_runtime_inmem/__init__.py +1 -1
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/langgraph_runtime_inmem/ops.py +145 -26
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/uv.lock +9 -9
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/.gitignore +0 -0
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/Makefile +0 -0
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/README.md +0 -0
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/langgraph_runtime_inmem/_persistence.py +0 -0
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/langgraph_runtime_inmem/checkpoint.py +0 -0
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/langgraph_runtime_inmem/database.py +0 -0
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/langgraph_runtime_inmem/encryption.py +0 -0
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/langgraph_runtime_inmem/inmem_stream.py +0 -0
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/langgraph_runtime_inmem/lifespan.py +0 -0
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/langgraph_runtime_inmem/metrics.py +0 -0
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/langgraph_runtime_inmem/queue.py +0 -0
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/langgraph_runtime_inmem/retry.py +0 -0
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/langgraph_runtime_inmem/routes.py +0 -0
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/langgraph_runtime_inmem/store.py +0 -0
- {langgraph_runtime_inmem-0.38.0.dev6 → langgraph_runtime_inmem-0.38.0.dev7}/pyproject.toml +0 -0
|
@@ -10,7 +10,7 @@ import typing
|
|
|
10
10
|
import uuid
|
|
11
11
|
from collections import defaultdict
|
|
12
12
|
from collections.abc import AsyncIterator, Sequence
|
|
13
|
-
from contextlib import AsyncExitStack, asynccontextmanager
|
|
13
|
+
from contextlib import AsyncExitStack, aclosing, asynccontextmanager
|
|
14
14
|
from datetime import UTC, datetime, timedelta
|
|
15
15
|
from typing import Any, Literal, cast
|
|
16
16
|
from uuid import UUID, uuid4
|
|
@@ -54,6 +54,7 @@ if typing.TYPE_CHECKING:
|
|
|
54
54
|
PoolStats,
|
|
55
55
|
QueueStats,
|
|
56
56
|
Run,
|
|
57
|
+
RunCancelProgress,
|
|
57
58
|
RunSelectField,
|
|
58
59
|
RunStatus,
|
|
59
60
|
StreamMode,
|
|
@@ -263,6 +264,18 @@ def _ensure_uuid(id_: str | uuid.UUID | None) -> uuid.UUID:
|
|
|
263
264
|
return id_
|
|
264
265
|
|
|
265
266
|
|
|
267
|
+
_EPOCH = datetime(1970, 1, 1, tzinfo=UTC)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _accepted_at_or_before(run_id: uuid.UUID, boundary_ms: int) -> bool:
|
|
271
|
+
"""Whether a run was accepted at or before a Unix-millisecond boundary.
|
|
272
|
+
|
|
273
|
+
Run IDs are UUIDv7, whose first 48 bits are the millisecond they were
|
|
274
|
+
generated. A run whose ID isn't a UUIDv7 counts as accepted before.
|
|
275
|
+
"""
|
|
276
|
+
return run_id.version != 7 or run_id.int >> 80 <= boundary_ms
|
|
277
|
+
|
|
278
|
+
|
|
266
279
|
def _ensure_datetime(value: datetime | str | None) -> datetime | None:
|
|
267
280
|
if value is None or isinstance(value, datetime):
|
|
268
281
|
return value
|
|
@@ -2177,7 +2190,38 @@ class Threads(Authenticated):
|
|
|
2177
2190
|
metadata: MetadataInput = None,
|
|
2178
2191
|
ctx: Auth.types.BaseAuthContext | None = None,
|
|
2179
2192
|
) -> list[StateSnapshot]:
|
|
2180
|
-
"""Get the history of a thread.
|
|
2193
|
+
"""Get the history of a thread.
|
|
2194
|
+
|
|
2195
|
+
Kept for langgraph-api versions that predate ``iterate``.
|
|
2196
|
+
"""
|
|
2197
|
+
return [
|
|
2198
|
+
state
|
|
2199
|
+
async for state in Threads.State.iterate(
|
|
2200
|
+
conn,
|
|
2201
|
+
config=config,
|
|
2202
|
+
limit=limit,
|
|
2203
|
+
before=before,
|
|
2204
|
+
metadata=metadata,
|
|
2205
|
+
ctx=ctx,
|
|
2206
|
+
)
|
|
2207
|
+
]
|
|
2208
|
+
|
|
2209
|
+
@staticmethod
|
|
2210
|
+
async def iterate(
|
|
2211
|
+
conn: InMemConnectionProto,
|
|
2212
|
+
*,
|
|
2213
|
+
config: Config,
|
|
2214
|
+
limit: int = 1,
|
|
2215
|
+
before: str | Checkpoint | None = None,
|
|
2216
|
+
metadata: MetadataInput = None,
|
|
2217
|
+
ctx: Auth.types.BaseAuthContext | None = None,
|
|
2218
|
+
incremental: bool = False,
|
|
2219
|
+
) -> AsyncIterator[StateSnapshot]:
|
|
2220
|
+
"""Yield the history of a thread.
|
|
2221
|
+
|
|
2222
|
+
With ``incremental``, checkpoints load in growing pages, so a
|
|
2223
|
+
caller that stops early does not load the rest of ``limit``.
|
|
2224
|
+
"""
|
|
2181
2225
|
from langgraph_api.graph import get_graph # noqa: PLC0415
|
|
2182
2226
|
from langgraph_api.store import get_store # noqa: PLC0415
|
|
2183
2227
|
from langgraph_api.utils import fetchone # noqa: PLC0415
|
|
@@ -2196,7 +2240,7 @@ class Threads(Authenticated):
|
|
|
2196
2240
|
# Parse thread metadata and config
|
|
2197
2241
|
thread_metadata = thread["metadata"]
|
|
2198
2242
|
if not _check_filter_match(thread_metadata, filters):
|
|
2199
|
-
return
|
|
2243
|
+
return
|
|
2200
2244
|
|
|
2201
2245
|
thread_config = cast(dict[str, Any], thread["config"])
|
|
2202
2246
|
thread_config = {
|
|
@@ -2225,16 +2269,27 @@ class Threads(Authenticated):
|
|
|
2225
2269
|
else before
|
|
2226
2270
|
)
|
|
2227
2271
|
|
|
2228
|
-
|
|
2229
|
-
state
|
|
2230
|
-
|
|
2231
|
-
config, limit=limit, filter=metadata, before=before_param
|
|
2272
|
+
if incremental:
|
|
2273
|
+
from langgraph_api.state import ( # noqa: PLC0415
|
|
2274
|
+
iter_state_history,
|
|
2232
2275
|
)
|
|
2233
|
-
]
|
|
2234
|
-
|
|
2235
|
-
return states
|
|
2236
2276
|
|
|
2237
|
-
|
|
2277
|
+
history = iter_state_history(
|
|
2278
|
+
graph,
|
|
2279
|
+
config,
|
|
2280
|
+
limit=limit,
|
|
2281
|
+
filter=metadata,
|
|
2282
|
+
before=before_param,
|
|
2283
|
+
)
|
|
2284
|
+
else:
|
|
2285
|
+
history = graph.aget_state_history(
|
|
2286
|
+
config, limit=limit, filter=metadata, before=before_param
|
|
2287
|
+
)
|
|
2288
|
+
async with aclosing(history) as states:
|
|
2289
|
+
async for state in states:
|
|
2290
|
+
yield state
|
|
2291
|
+
# Drop the entry before the next page loads.
|
|
2292
|
+
del state
|
|
2238
2293
|
|
|
2239
2294
|
class Stream(Authenticated):
|
|
2240
2295
|
resource = "threads"
|
|
@@ -3248,12 +3303,88 @@ class Runs(Authenticated):
|
|
|
3248
3303
|
return
|
|
3249
3304
|
raise HTTPException(status_code=404, detail="No runs found to cancel.")
|
|
3250
3305
|
|
|
3306
|
+
cancelable_runs = await Runs._cancel_runs(
|
|
3307
|
+
conn, candidate_runs, action=action, reason=reason
|
|
3308
|
+
)
|
|
3309
|
+
if not cancelable_runs:
|
|
3310
|
+
# When cancelling by assistant_id, it's valid to have no cancelable runs.
|
|
3311
|
+
# An interrupt that finds a rollback under way leaves it alone and
|
|
3312
|
+
# succeeds, as in the Go runtime.
|
|
3313
|
+
if assistant_id is not None or any(
|
|
3314
|
+
run["status"] == "running" and run.get("cancel_action") == "rollback"
|
|
3315
|
+
for run in candidate_runs
|
|
3316
|
+
):
|
|
3317
|
+
return
|
|
3318
|
+
raise HTTPException(
|
|
3319
|
+
status_code=404,
|
|
3320
|
+
detail="No matching runs to cancel. Please verify the thread ID and run IDs are correct, and the runs haven't been deleted or completed.",
|
|
3321
|
+
)
|
|
3322
|
+
|
|
3323
|
+
await logger.ainfo(
|
|
3324
|
+
"Cancelled runs",
|
|
3325
|
+
run_ids=[str(r["run_id"]) for r in cancelable_runs],
|
|
3326
|
+
thread_id=str(thread_id) if thread_id else None,
|
|
3327
|
+
status=status,
|
|
3328
|
+
action=action,
|
|
3329
|
+
)
|
|
3330
|
+
|
|
3331
|
+
@staticmethod
|
|
3332
|
+
async def cancel_accepted_before(
|
|
3333
|
+
conn: InMemConnectionProto,
|
|
3334
|
+
accepted_before: datetime,
|
|
3335
|
+
*,
|
|
3336
|
+
reason: str,
|
|
3337
|
+
) -> RunCancelProgress:
|
|
3338
|
+
"""Interrupt every in-flight run accepted at or before `accepted_before`.
|
|
3339
|
+
|
|
3340
|
+
Applies no auth filters, so only the operator route may call it. As with
|
|
3341
|
+
`cancel`, running runs are marked interrupted when they're signalled, so
|
|
3342
|
+
`finishing` only counts runs whose worker is still applying a rollback.
|
|
3343
|
+
"""
|
|
3344
|
+
boundary_ms = (accepted_before - _EPOCH) // timedelta(milliseconds=1)
|
|
3345
|
+
|
|
3346
|
+
def in_flight_targets() -> list[dict]:
|
|
3347
|
+
return [
|
|
3348
|
+
run
|
|
3349
|
+
for run in conn.store["runs"]
|
|
3350
|
+
if run["status"] in ("pending", "running")
|
|
3351
|
+
and _accepted_at_or_before(_ensure_uuid(run["run_id"]), boundary_ms)
|
|
3352
|
+
]
|
|
3353
|
+
|
|
3354
|
+
cancelled = await Runs._cancel_runs(
|
|
3355
|
+
conn, in_flight_targets(), action="interrupt", reason=reason
|
|
3356
|
+
)
|
|
3357
|
+
if cancelled:
|
|
3358
|
+
await logger.ainfo(
|
|
3359
|
+
"Cancelled runs accepted before boundary",
|
|
3360
|
+
run_ids=[str(r["run_id"]) for r in cancelled],
|
|
3361
|
+
accepted_before=accepted_before.isoformat(),
|
|
3362
|
+
)
|
|
3363
|
+
remaining = in_flight_targets()
|
|
3364
|
+
finishing = sum(1 for run in remaining if run.get("cancel_action"))
|
|
3365
|
+
return {"not_yet_reached": len(remaining) - finishing, "finishing": finishing}
|
|
3366
|
+
|
|
3367
|
+
@staticmethod
|
|
3368
|
+
async def _cancel_runs(
|
|
3369
|
+
conn: InMemConnectionProto,
|
|
3370
|
+
candidate_runs: list[dict],
|
|
3371
|
+
*,
|
|
3372
|
+
action: Literal["interrupt", "rollback"],
|
|
3373
|
+
reason: str | None,
|
|
3374
|
+
) -> list[dict]:
|
|
3375
|
+
"""Signal each candidate run and cancel the ones still in flight.
|
|
3376
|
+
|
|
3377
|
+
Returns the runs that were in flight. When none were, nothing is changed.
|
|
3378
|
+
"""
|
|
3251
3379
|
stream_manager = get_stream_manager()
|
|
3252
3380
|
coros = []
|
|
3253
3381
|
cancelable_runs = []
|
|
3254
3382
|
pending_thread_ids: set[UUID] = set()
|
|
3255
3383
|
|
|
3256
3384
|
for run in candidate_runs:
|
|
3385
|
+
if action == "interrupt" and run.get("cancel_action") == "rollback":
|
|
3386
|
+
# A rollback already under way wins; its worker finalizes it.
|
|
3387
|
+
continue
|
|
3257
3388
|
run_id = run["run_id"]
|
|
3258
3389
|
control_message = Message(
|
|
3259
3390
|
topic=f"run:{run_id}:control".encode(),
|
|
@@ -3271,7 +3402,7 @@ class Runs(Authenticated):
|
|
|
3271
3402
|
pending_thread_ids.add(run["thread_id"])
|
|
3272
3403
|
if action == "rollback" and run["status"] == "running":
|
|
3273
3404
|
# Its worker finalizes the rollback; staying "running" holds the thread's next run until then.
|
|
3274
|
-
|
|
3405
|
+
run["cancel_action"] = "rollback"
|
|
3275
3406
|
elif queues or action != "rollback":
|
|
3276
3407
|
run["status"] = "interrupted"
|
|
3277
3408
|
run["cancel_reason"] = reason
|
|
@@ -3290,13 +3421,7 @@ class Runs(Authenticated):
|
|
|
3290
3421
|
)
|
|
3291
3422
|
|
|
3292
3423
|
if not cancelable_runs:
|
|
3293
|
-
|
|
3294
|
-
if assistant_id is not None:
|
|
3295
|
-
return
|
|
3296
|
-
raise HTTPException(
|
|
3297
|
-
status_code=404,
|
|
3298
|
-
detail="No matching runs to cancel. Please verify the thread ID and run IDs are correct, and the runs haven't been deleted or completed.",
|
|
3299
|
-
)
|
|
3424
|
+
return []
|
|
3300
3425
|
|
|
3301
3426
|
if coros:
|
|
3302
3427
|
await asyncio.gather(*coros)
|
|
@@ -3312,13 +3437,7 @@ class Runs(Authenticated):
|
|
|
3312
3437
|
)
|
|
3313
3438
|
thread["updated_at"] = datetime.now(tz=UTC)
|
|
3314
3439
|
|
|
3315
|
-
|
|
3316
|
-
"Cancelled runs",
|
|
3317
|
-
run_ids=[str(r["run_id"]) for r in cancelable_runs],
|
|
3318
|
-
thread_id=str(thread_id) if thread_id else None,
|
|
3319
|
-
status=status,
|
|
3320
|
-
action=action,
|
|
3321
|
-
)
|
|
3440
|
+
return cancelable_runs
|
|
3322
3441
|
|
|
3323
3442
|
@staticmethod
|
|
3324
3443
|
async def search(
|
|
@@ -300,15 +300,15 @@ wheels = [
|
|
|
300
300
|
|
|
301
301
|
[[package]]
|
|
302
302
|
name = "langgraph-checkpoint"
|
|
303
|
-
version = "4.
|
|
303
|
+
version = "4.3.0"
|
|
304
304
|
source = { registry = "https://pypi.org/simple" }
|
|
305
305
|
dependencies = [
|
|
306
306
|
{ name = "langchain-core" },
|
|
307
307
|
{ name = "ormsgpack" },
|
|
308
308
|
]
|
|
309
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
309
|
+
sdist = { url = "https://files.pythonhosted.org/packages/0f/69/31fdbdc65a85bbd6178afa193c772bb926620f47b4869638bc2bc80afaaa/langgraph_checkpoint-4.3.0.tar.gz", hash = "sha256:c75965d84cc2c1d549163e910a15bcb577758001b141619d05297c463280b018", size = 182652, upload-time = "2026-10-12T22:26:31.478Z" }
|
|
310
310
|
wheels = [
|
|
311
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
311
|
+
{ url = "https://files.pythonhosted.org/packages/1f/0c/84747e340bf4f29291c84cdd5733fc8d0a822f3d33bb24e664a18afa4a7c/langgraph_checkpoint-4.3.0-py3-none-any.whl", hash = "sha256:bedfafe2f997ded60e4fa593e79f56f436a6e45586392dc382aa810d0c751c64", size = 58063, upload-time = "2026-10-12T22:26:30.429Z" },
|
|
312
312
|
]
|
|
313
313
|
|
|
314
314
|
[[package]]
|
|
@@ -388,7 +388,7 @@ dev = [
|
|
|
388
388
|
|
|
389
389
|
[[package]]
|
|
390
390
|
name = "langgraph-sdk"
|
|
391
|
-
version = "0.4.
|
|
391
|
+
version = "0.4.4"
|
|
392
392
|
source = { registry = "https://pypi.org/simple" }
|
|
393
393
|
dependencies = [
|
|
394
394
|
{ name = "httpx" },
|
|
@@ -397,9 +397,9 @@ dependencies = [
|
|
|
397
397
|
{ name = "orjson" },
|
|
398
398
|
{ name = "websockets" },
|
|
399
399
|
]
|
|
400
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
400
|
+
sdist = { url = "https://files.pythonhosted.org/packages/3f/ae/91446c1fffa04a2dc1f81afbfb5bfff3590452891a72bd9098a656ab2657/langgraph_sdk-0.4.4.tar.gz", hash = "sha256:4e651ffa09de695681579396375377bdde23bedbc8e35b070e615cbd5af7da8b", size = 345789, upload-time = "2026-08-27T21:25:22.593Z" }
|
|
401
401
|
wheels = [
|
|
402
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
402
|
+
{ url = "https://files.pythonhosted.org/packages/43/d5/2ad7f3a835c6fcebf58b6669552536ecd52d2dfe4cf49c6c36648fd83572/langgraph_sdk-0.4.4-py3-none-any.whl", hash = "sha256:39afe416c91742925e6f8a93715f566d499b36e1b636b804a4ffe3190e4f4e64", size = 162270, upload-time = "2026-08-27T21:25:21.072Z" },
|
|
403
403
|
]
|
|
404
404
|
|
|
405
405
|
[[package]]
|
|
@@ -1199,14 +1199,14 @@ wheels = [
|
|
|
1199
1199
|
|
|
1200
1200
|
[[package]]
|
|
1201
1201
|
name = "werkzeug"
|
|
1202
|
-
version = "3.1.
|
|
1202
|
+
version = "3.1.9"
|
|
1203
1203
|
source = { registry = "https://pypi.org/simple" }
|
|
1204
1204
|
dependencies = [
|
|
1205
1205
|
{ name = "markupsafe" },
|
|
1206
1206
|
]
|
|
1207
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1207
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a4/34/4dd12fc8bb7d61c91467ec3efe415ffa7d5456f799954b40c5bbaeae470e/werkzeug-3.1.9.tar.gz", hash = "sha256:55ca7c70a75689be937aa27f8ff4b018f06ff4838fc73045560bf0f5a1291060", size = 940188, upload-time = "2026-09-27T18:33:41.637Z" }
|
|
1208
1208
|
wheels = [
|
|
1209
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1209
|
+
{ url = "https://files.pythonhosted.org/packages/a1/38/df03f564f43cec2684823f3cccae1a652ee7face1cbaa76fb223096e64d7/werkzeug-3.1.9-py3-none-any.whl", hash = "sha256:6392e50c78460ba618e5b21f08a71f59c99ce99cdc6cf6e3dd7e6ccca8754fab", size = 228700, upload-time = "2026-09-27T18:33:39.685Z" },
|
|
1210
1210
|
]
|
|
1211
1211
|
|
|
1212
1212
|
[[package]]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|