langgraph-api 0.3.4__py3-none-any.whl → 0.4.1__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.
@@ -249,6 +249,7 @@ async def create_valid_run(
249
249
  barrier: asyncio.Barrier | None = None,
250
250
  run_id: UUID | None = None,
251
251
  request_start_time: float | None = None,
252
+ temporary: bool = False,
252
253
  ) -> Run:
253
254
  request_id = headers.get("x-request-id") # Will be null in the crons scheduler.
254
255
  (
@@ -262,7 +263,7 @@ async def create_valid_run(
262
263
  run_id=run_id,
263
264
  )
264
265
  if (
265
- thread_id_ is None
266
+ (thread_id_ is None or temporary)
266
267
  and (command := payload.get("command"))
267
268
  and command.get("resume")
268
269
  ):
@@ -270,9 +271,9 @@ async def create_valid_run(
270
271
  status_code=400,
271
272
  detail="You must provide a thread_id when resuming.",
272
273
  )
273
- temporary = (
274
- thread_id_ is None and payload.get("on_completion", "delete") == "delete"
275
- )
274
+ temporary = (temporary or thread_id_ is None) and payload.get(
275
+ "on_completion", "delete"
276
+ ) == "delete"
276
277
  stream_resumable = payload.get("stream_resumable", False)
277
278
  stream_mode, multitask_strategy, prevent_insert_if_inflight = assign_defaults(
278
279
  payload
langgraph_api/stream.py CHANGED
@@ -2,7 +2,7 @@ import uuid
2
2
  from collections.abc import AsyncIterator, Callable
3
3
  from contextlib import AsyncExitStack, aclosing, asynccontextmanager
4
4
  from functools import lru_cache
5
- from typing import Any, Literal, cast
5
+ from typing import Any, cast
6
6
 
7
7
  import langgraph.version
8
8
  import langsmith
@@ -423,7 +423,9 @@ async def consume(
423
423
  stream: AnyStream,
424
424
  run_id: str | uuid.UUID,
425
425
  resumable: bool = False,
426
- stream_modes: set[StreamMode | Literal["metadata"]] | None = None,
426
+ stream_modes: set[StreamMode] | None = None,
427
+ *,
428
+ thread_id: str | uuid.UUID | None = None,
427
429
  ) -> None:
428
430
  stream_modes = stream_modes or set()
429
431
  if "messages-tuple" in stream_modes:
@@ -437,6 +439,7 @@ async def consume(
437
439
  run_id,
438
440
  mode,
439
441
  await run_in_executor(None, json_dumpb, payload),
442
+ thread_id=thread_id,
440
443
  resumable=resumable and mode.split("|")[0] in stream_modes,
441
444
  )
442
445
  except Exception as e:
@@ -446,6 +449,7 @@ async def consume(
446
449
  run_id,
447
450
  "error",
448
451
  await run_in_executor(None, json_dumpb, e),
452
+ thread_id=thread_id,
449
453
  )
450
454
  raise e
451
455
 
@@ -1,4 +1,5 @@
1
1
  import contextvars
2
+ import re
2
3
  import uuid
3
4
  from collections.abc import AsyncIterator
4
5
  from contextlib import asynccontextmanager
@@ -22,6 +23,7 @@ Row: TypeAlias = dict[str, Any]
22
23
  AuthContext = contextvars.ContextVar[Auth.types.BaseAuthContext | None](
23
24
  "AuthContext", default=None
24
25
  )
26
+ STREAM_ID_PATTERN = re.compile(r"^\d+(-(\d+|\*))?$")
25
27
 
26
28
 
27
29
  @asynccontextmanager
@@ -101,6 +103,23 @@ def validate_uuid(uuid_str: str, invalid_uuid_detail: str | None) -> uuid.UUID:
101
103
  raise HTTPException(status_code=422, detail=invalid_uuid_detail) from None
102
104
 
103
105
 
106
+ def validate_stream_id(stream_id: str | None, invalid_stream_id_detail: str | None):
107
+ """
108
+ Validate Redis stream ID format.
109
+ Valid formats:
110
+ - timestamp-sequence (e.g., "1724342400000-0")
111
+ - timestamp-* (e.g., "1724342400000-*")
112
+ - timestamp only (e.g., "1724342400000")
113
+ - "-" (special case, represents the beginning of the stream, use if you want to replay all events)
114
+ """
115
+ if not stream_id or stream_id == "-":
116
+ return stream_id
117
+
118
+ if STREAM_ID_PATTERN.match(stream_id):
119
+ return stream_id
120
+ raise HTTPException(status_code=422, detail=invalid_stream_id_detail)
121
+
122
+
104
123
  def next_cron_date(schedule: str, base_time: datetime) -> datetime:
105
124
  import croniter # type: ignore[unresolved-import]
106
125
 
langgraph_api/worker.py CHANGED
@@ -139,7 +139,9 @@ async def worker(
139
139
  stream_modes: set[StreamMode],
140
140
  ):
141
141
  try:
142
- await consume(stream, run_id, resumable, stream_modes)
142
+ await consume(
143
+ stream, run_id, resumable, stream_modes, thread_id=run["thread_id"]
144
+ )
143
145
  except Exception as e:
144
146
  if not isinstance(e, UserRollback | UserInterrupt):
145
147
  logger.exception(
@@ -151,7 +153,7 @@ async def worker(
151
153
  raise UserTimeout(e) from e
152
154
  raise
153
155
 
154
- async with Runs.enter(run_id, main_loop) as done:
156
+ async with Runs.enter(run_id, run["thread_id"], main_loop) as done:
155
157
  # attempt the run
156
158
  try:
157
159
  if attempt > BG_JOB_MAX_RETRIES:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-api
3
- Version: 0.3.4
3
+ Version: 0.4.1
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.9.0,>=0.8.0
14
+ Requires-Dist: langgraph-runtime-inmem<0.10.0,>=0.9.0
15
15
  Requires-Dist: langgraph-sdk>=0.2.0
16
16
  Requires-Dist: langgraph>=0.4.0
17
17
  Requires-Dist: langsmith>=0.3.45
@@ -1,4 +1,4 @@
1
- langgraph_api/__init__.py,sha256=oYLGMpySamd16KLiaBTfRyrAS7_oyp-TOEHmzmeumwg,22
1
+ langgraph_api/__init__.py,sha256=pMtTmSUht-XtbR_7Doz6bsQqopJJd8rZ8I8zy2HwwoA,22
2
2
  langgraph_api/asgi_transport.py,sha256=XtiLOu4WWsd-xizagBLzT5xUkxc9ZG9YqwvETBPjBFE,5161
3
3
  langgraph_api/asyncio.py,sha256=mZ7G32JjrGxrlH4OMy7AKlBQo5bZt4Sm2rlrBcU-Vj8,9483
4
4
  langgraph_api/cli.py,sha256=-ruIeKi1imvS6GriOfRDZY-waV4SbWiJ0BEFAciPVYI,16330
@@ -22,21 +22,21 @@ langgraph_api/server.py,sha256=uCAqPgCLJ6ckslLs0i_dacSR8mzuR0Y6PkkJYk0O3bE,7196
22
22
  langgraph_api/sse.py,sha256=SLdtZmTdh5D8fbWrQjuY9HYLd2dg8Rmi6ZMmFMVc2iE,4204
23
23
  langgraph_api/state.py,sha256=5RTOShiFVnkx-o6t99_x63CGwXw_8Eb-dSTpYirP8ro,4683
24
24
  langgraph_api/store.py,sha256=NIoNZojs6NbtG3VLBPQEFNttvp7XPkHAfjbQ3gY7aLY,4701
25
- langgraph_api/stream.py,sha256=iRF4Hu6A1n-KvDR4ki1BeOvOnOxGoV2NV8tiBHUWZOs,18428
25
+ langgraph_api/stream.py,sha256=IpGW9vpknI_wWteEmZfQKqCYqbaJAzOpq0FgdFJP60s,18528
26
26
  langgraph_api/thread_ttl.py,sha256=7H3gFlWcUiODPoaEzcwB0LR61uvcuyjD0ew_4BztB7k,1902
27
27
  langgraph_api/traceblock.py,sha256=Qq5CUdefnMDaRDnyvBSWGBClEj-f3oO7NbH6fedxOSE,630
28
28
  langgraph_api/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
29
  langgraph_api/validation.py,sha256=86jftgOsMa7tkeshBw6imYe7zyUXPoVuf5Voh6dFiR8,5285
30
30
  langgraph_api/webhook.py,sha256=SvSM1rdnNtiH4q3JQYmAqJUk2Sable5xAcwOLuRhtlo,1723
31
- langgraph_api/worker.py,sha256=HVGyGVEYcXG-iKVgoBdFgANGxPjSs57JRl5OB4ra4nw,15267
31
+ langgraph_api/worker.py,sha256=M9WQdxEzVGDZqdjz3LHEHhM1g6isPcf3k1V4PEkcSY8,15343
32
32
  langgraph_api/api/__init__.py,sha256=WHy6oNLWtH1K7AxmmsU9RD-Vm6WP-Ov16xS8Ey9YCmQ,6090
33
33
  langgraph_api/api/assistants.py,sha256=5gVvU58Y1-EftBhCHGbEaOi_7cqGMKWhOt_GVfBC0Gg,16836
34
34
  langgraph_api/api/mcp.py,sha256=qe10ZRMN3f-Hli-9TI8nbQyWvMeBb72YB1PZVbyqBQw,14418
35
35
  langgraph_api/api/meta.py,sha256=dFD9ZgykbKARLdVSaJD9vO3CShvEyBmGpkjE8tqii0c,4448
36
36
  langgraph_api/api/openapi.py,sha256=If-z1ckXt-Yu5bwQytK1LWyX_T7G46UtLfixgEP8hwc,11959
37
- langgraph_api/api/runs.py,sha256=AiohGTFLjWCb-oTXoNDvPMod4v6RS_ivlieoiqDmtQM,21812
37
+ langgraph_api/api/runs.py,sha256=1RlD4WA8auCNugFjJSgaM9WPnwEHeWsJLEuKpwSRqcU,22089
38
38
  langgraph_api/api/store.py,sha256=xGcPFx4v-VxlK6HRU9uCjzCQ0v66cvc3o_PB5_g7n0Q,5550
39
- langgraph_api/api/threads.py,sha256=Ap5zUcYqK5GJqwEc-q4QY6qCkmbLxfMmEvQZm0MCFxk,10427
39
+ langgraph_api/api/threads.py,sha256=xjeA6eUp4r19YYvqkfe8c3b_u4yIg6jvjq-EmlGu1kI,11150
40
40
  langgraph_api/api/ui.py,sha256=_genglTUy5BMHlL0lkQypX524yFv6Z5fraIvnrxp7yE,2639
41
41
  langgraph_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
42
  langgraph_api/auth/custom.py,sha256=psETw_GpLWClBbd_ESVPRLUz9GLQ0_XNsuUDSVbtZy0,22522
@@ -55,6 +55,9 @@ langgraph_api/js/client.http.mts,sha256=cvn8JV9go4pUMWkcug8FfSYWsp1wTaT8SgJaskqE
55
55
  langgraph_api/js/client.mts,sha256=gDvYiW7Qfl4re2YhZ5oNqtuvffnW_Sf7DK5aUbKB3vw,32330
56
56
  langgraph_api/js/errors.py,sha256=Cm1TKWlUCwZReDC5AQ6SgNIVGD27Qov2xcgHyf8-GXo,361
57
57
  langgraph_api/js/global.d.ts,sha256=j4GhgtQSZ5_cHzjSPcHgMJ8tfBThxrH-pUOrrJGteOU,196
58
+ langgraph_api/js/isolate-0x130008000-46649-46649-v8.log,sha256=s6h4v7ZtOBK9tKJ0zzPojnob7MBV2j-nyLVPGbgotdg,2641826
59
+ langgraph_api/js/isolate-0x138008000-44681-44681-v8.log,sha256=knAbmQTmz2umDoqMXKuThsmgc4Q6b1HBwZ2ZFUcFu40,2644591
60
+ langgraph_api/js/package-lock.json,sha256=bGM4LYdWJxAb5JE6Tap1-WA_IqRE_CzBjxZTCcMt1_U,117589
58
61
  langgraph_api/js/package.json,sha256=syy2fEcmTxGQVfz4P9MUTgoTxHr1MUcA1rDXemAig2U,1335
59
62
  langgraph_api/js/remote.py,sha256=aeszJ0HGbcL9oExyeWYHb0xLV75U3KgVSxjm3ZK_a48,38403
60
63
  langgraph_api/js/schema.py,sha256=M4fLtr50O1jck8H1hm_0W4cZOGYGdkrB7riLyCes4oY,438
@@ -75,9 +78,9 @@ langgraph_api/middleware/http_logger.py,sha256=2LABfhzTAUtqT8nf1ACy8cYXteatkwraB
75
78
  langgraph_api/middleware/private_network.py,sha256=eYgdyU8AzU2XJu362i1L8aSFoQRiV7_aLBPw7_EgeqI,2111
76
79
  langgraph_api/middleware/request_id.py,sha256=SDj3Yi3WvTbFQ2ewrPQBjAV8sYReOJGeIiuoHeZpR9g,1242
77
80
  langgraph_api/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
- langgraph_api/models/run.py,sha256=q99y57RqUgZgw5kNkUeezo5E9yazg-4-uxKhJR2agag,15479
81
+ langgraph_api/models/run.py,sha256=jPGWcW9fd1KFoXZTSXMMvc0IiXDXk3yoZ5kjdqxZ1mg,15536
79
82
  langgraph_api/tunneling/cloudflare.py,sha256=iKb6tj-VWPlDchHFjuQyep2Dpb-w2NGfJKt-WJG9LH0,3650
80
- langgraph_api/utils/__init__.py,sha256=kj3uCnO2Md9EEhabm331Tg4Jx9qXcxbACMh2T2P-FYw,5028
83
+ langgraph_api/utils/__init__.py,sha256=yCMq7pOMlmeNmi2Fh8U7KLiljBdOMcF0L2SfpobnKKE,5703
81
84
  langgraph_api/utils/cache.py,sha256=SrtIWYibbrNeZzLXLUGBFhJPkMVNQnVxR5giiYGHEfI,1810
82
85
  langgraph_api/utils/config.py,sha256=Tbp4tKDSLKXQJ44EKr885wAQupY-9VWNJ6rgUU2oLOY,4162
83
86
  langgraph_api/utils/future.py,sha256=lXsRQPhJwY7JUbFFZrK-94JjgsToLu-EWU896hvbUxE,7289
@@ -96,9 +99,9 @@ langgraph_runtime/retry.py,sha256=V0duD01fO7GUQ_btQkp1aoXcEOFhXooGVP6q4yMfuyY,11
96
99
  langgraph_runtime/store.py,sha256=7mowndlsIroGHv3NpTSOZDJR0lCuaYMBoTnTrewjslw,114
97
100
  LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
98
101
  logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
99
- openapi.json,sha256=h1LbSeGqr2Oor6vO8d3m67XJ1lHhVYVyt2ULvyhf_Ks,160215
100
- langgraph_api-0.3.4.dist-info/METADATA,sha256=L5VNuwfF9vgK-Pq3KK1AOz1E2_45tosXJlbigcHVd0Q,3890
101
- langgraph_api-0.3.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
102
- langgraph_api-0.3.4.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
103
- langgraph_api-0.3.4.dist-info/licenses/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
104
- langgraph_api-0.3.4.dist-info/RECORD,,
102
+ openapi.json,sha256=kT_Xi6PAI64jLUalbdh0TtIRy1PfqQBxSu-rbyo_ZyA,162474
103
+ langgraph_api-0.4.1.dist-info/METADATA,sha256=v2nNOyHqw8ReH5uNs_tQQeMgnMk969MloE1rsY9P_ow,3891
104
+ langgraph_api-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
105
+ langgraph_api-0.4.1.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
106
+ langgraph_api-0.4.1.dist-info/licenses/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
107
+ langgraph_api-0.4.1.dist-info/RECORD,,
openapi.json CHANGED
@@ -1520,6 +1520,73 @@
1520
1520
  }
1521
1521
  }
1522
1522
  },
1523
+ "/threads/{thread_id}/stream": {
1524
+ "get": {
1525
+ "tags": [
1526
+ "Threads"
1527
+ ],
1528
+ "summary": "Join Thread Stream",
1529
+ "description": "This endpoint streams output in real-time from a thread. The stream will include the output of each run executed sequentially on the thread and will remain open indefinitely. It is the responsibility of the calling client to close the connection.",
1530
+ "operationId": "join_thread_stream_threads__thread_id__stream_get",
1531
+ "parameters": [
1532
+ {
1533
+ "description": "The ID of the thread.",
1534
+ "required": true,
1535
+ "schema": {
1536
+ "type": "string",
1537
+ "format": "uuid",
1538
+ "title": "Thread Id",
1539
+ "description": "The ID of the thread."
1540
+ },
1541
+ "name": "thread_id",
1542
+ "in": "path"
1543
+ },
1544
+ {
1545
+ "required": false,
1546
+ "schema": {
1547
+ "type": "string",
1548
+ "title": "Last Event ID",
1549
+ "description": "The ID of the last event received. Used to resume streaming from a specific point. Pass '-' to resume from the beginning."
1550
+ },
1551
+ "name": "Last-Event-ID",
1552
+ "in": "header"
1553
+ }
1554
+ ],
1555
+ "responses": {
1556
+ "200": {
1557
+ "description": "Success",
1558
+ "content": {
1559
+ "text/event-stream": {
1560
+ "schema": {
1561
+ "type": "string",
1562
+ "description": "The server will send a stream of events in SSE format.\n\n**Example event**:\n\nid: 1\n\nevent: message\n\ndata: {}"
1563
+ }
1564
+ }
1565
+ }
1566
+ },
1567
+ "404": {
1568
+ "description": "Not Found",
1569
+ "content": {
1570
+ "application/json": {
1571
+ "schema": {
1572
+ "$ref": "#/components/schemas/ErrorResponse"
1573
+ }
1574
+ }
1575
+ }
1576
+ },
1577
+ "422": {
1578
+ "description": "Validation Error",
1579
+ "content": {
1580
+ "application/json": {
1581
+ "schema": {
1582
+ "$ref": "#/components/schemas/ErrorResponse"
1583
+ }
1584
+ }
1585
+ }
1586
+ }
1587
+ }
1588
+ }
1589
+ },
1523
1590
  "/threads/{thread_id}/runs": {
1524
1591
  "get": {
1525
1592
  "tags": [