langgraph-api 0.0.31__tar.gz → 0.0.32__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.
Potentially problematic release.
This version of langgraph-api might be problematic. Click here for more details.
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/PKG-INFO +2 -2
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/api/assistants.py +7 -1
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/api/threads.py +10 -1
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/cli.py +8 -7
- langgraph_api-0.0.32/langgraph_api/command.py +29 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/remote.py +72 -51
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/stream.py +3 -29
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/validation.py +13 -1
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_storage/ops.py +75 -1
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/openapi.json +102 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/pyproject.toml +3 -3
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/LICENSE +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/README.md +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/__init__.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/api/__init__.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/api/meta.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/api/openapi.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/api/runs.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/api/store.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/api/ui.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/asyncio.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/auth/__init__.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/auth/custom.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/auth/langsmith/__init__.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/auth/langsmith/backend.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/auth/langsmith/client.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/auth/middleware.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/auth/noop.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/auth/studio_user.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/config.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/cron_scheduler.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/errors.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/graph.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/http.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/.gitignore +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/base.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/build.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/client.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/errors.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/global.d.ts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/package.json +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/schema.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/src/graph.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/src/hooks.mjs +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/src/parser/parser.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/src/parser/parser.worker.mjs +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/src/schema/types.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/src/schema/types.template.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/src/utils/importMap.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/src/utils/serde.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/sse.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/tests/api.test.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/tests/compose-postgres.yml +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/tests/graphs/.gitignore +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/tests/graphs/agent.css +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/tests/graphs/agent.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/tests/graphs/agent.ui.tsx +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/tests/graphs/delay.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/tests/graphs/error.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/tests/graphs/langgraph.json +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/tests/graphs/nested.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/tests/graphs/package.json +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/tests/graphs/weather.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/tests/graphs/yarn.lock +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/tests/parser.test.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/tests/utils.mts +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/yarn.lock +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/lifespan.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/logging.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/metadata.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/middleware/__init__.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/middleware/http_logger.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/middleware/private_network.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/models/__init__.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/models/run.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/patch.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/queue_entrypoint.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/route.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/schema.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/serde.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/server.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/sse.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/state.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/utils.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/webhook.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/worker.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_license/__init__.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_license/middleware.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_license/validation.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_storage/__init__.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_storage/checkpoint.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_storage/database.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_storage/inmem_stream.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_storage/queue.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_storage/retry.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_storage/store.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_storage/ttl_dict.py +0 -0
- {langgraph_api-0.0.31 → langgraph_api-0.0.32}/logging.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: langgraph-api
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.32
|
|
4
4
|
Summary:
|
|
5
5
|
License: Elastic-2.0
|
|
6
6
|
Author: Nuno Campos
|
|
@@ -16,7 +16,7 @@ Requires-Dist: jsonschema-rs (>=0.20.0,<0.30)
|
|
|
16
16
|
Requires-Dist: langchain-core (>=0.2.38,<0.4.0)
|
|
17
17
|
Requires-Dist: langgraph (>=0.2.56,<0.4.0)
|
|
18
18
|
Requires-Dist: langgraph-checkpoint (>=2.0.21,<3.0)
|
|
19
|
-
Requires-Dist: langgraph-sdk (>=0.1.
|
|
19
|
+
Requires-Dist: langgraph-sdk (>=0.1.58,<0.2.0)
|
|
20
20
|
Requires-Dist: langsmith (>=0.1.63,<0.4.0)
|
|
21
21
|
Requires-Dist: orjson (>=3.9.7)
|
|
22
22
|
Requires-Dist: pyjwt (>=2.9.0,<3.0.0)
|
|
@@ -87,7 +87,13 @@ def _graph_schemas(graph: Pregel) -> dict:
|
|
|
87
87
|
f"Failed to get output schema for graph {graph.name} with error: `{str(e)}`"
|
|
88
88
|
)
|
|
89
89
|
output_schema = None
|
|
90
|
-
|
|
90
|
+
try:
|
|
91
|
+
state_schema = _state_jsonschema(graph)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.warning(
|
|
94
|
+
f"Failed to get state schema for graph {graph.name} with error: `{str(e)}`"
|
|
95
|
+
)
|
|
96
|
+
state_schema = None
|
|
91
97
|
try:
|
|
92
98
|
config_schema = _get_configurable_jsonschema(graph)
|
|
93
99
|
except Exception as e:
|
|
@@ -28,12 +28,21 @@ async def create_thread(
|
|
|
28
28
|
if thread_id := payload.get("thread_id"):
|
|
29
29
|
validate_uuid(thread_id, "Invalid thread ID: must be a UUID")
|
|
30
30
|
async with connect() as conn:
|
|
31
|
+
thread_id = thread_id or str(uuid4())
|
|
31
32
|
iter = await Threads.put(
|
|
32
33
|
conn,
|
|
33
|
-
thread_id
|
|
34
|
+
thread_id,
|
|
34
35
|
metadata=payload.get("metadata"),
|
|
35
36
|
if_exists=payload.get("if_exists") or "raise",
|
|
36
37
|
)
|
|
38
|
+
|
|
39
|
+
if supersteps := payload.get("supersteps"):
|
|
40
|
+
await Threads.State.bulk(
|
|
41
|
+
conn,
|
|
42
|
+
config={"configurable": {"thread_id": thread_id}},
|
|
43
|
+
supersteps=supersteps,
|
|
44
|
+
)
|
|
45
|
+
|
|
37
46
|
return ApiResponse(await fetchone(iter, not_found_code=409))
|
|
38
47
|
|
|
39
48
|
|
|
@@ -129,6 +129,7 @@ def run_server(
|
|
|
129
129
|
store: typing.Optional["StoreConfig"] = None,
|
|
130
130
|
auth: AuthConfig | None = None,
|
|
131
131
|
http: typing.Optional["HttpConfig"] = None,
|
|
132
|
+
studio_url: str | None = None,
|
|
132
133
|
**kwargs: typing.Any,
|
|
133
134
|
):
|
|
134
135
|
"""Run the LangGraph API server."""
|
|
@@ -192,11 +193,11 @@ def run_server(
|
|
|
192
193
|
ALLOW_PRIVATE_NETWORK="true",
|
|
193
194
|
**(env_vars or {}),
|
|
194
195
|
):
|
|
195
|
-
studio_origin = _get_ls_origin() or "https://smith.langchain.com"
|
|
196
|
-
|
|
196
|
+
studio_origin = studio_url or _get_ls_origin() or "https://smith.langchain.com"
|
|
197
|
+
full_studio_url = f"{studio_origin}/studio/?baseUrl={local_url}"
|
|
197
198
|
|
|
198
199
|
def _open_browser():
|
|
199
|
-
nonlocal studio_origin,
|
|
200
|
+
nonlocal studio_origin, full_studio_url
|
|
200
201
|
import time
|
|
201
202
|
import urllib.request
|
|
202
203
|
import webbrowser
|
|
@@ -218,7 +219,7 @@ def run_server(
|
|
|
218
219
|
try:
|
|
219
220
|
org_id = org_id_future.result(timeout=3.0)
|
|
220
221
|
if org_id:
|
|
221
|
-
|
|
222
|
+
full_studio_url = f"{studio_origin}/studio/?baseUrl={local_url}&organizationId={org_id}"
|
|
222
223
|
except TimeoutError as e:
|
|
223
224
|
thread_logger.debug(
|
|
224
225
|
f"Failed to get organization ID: {str(e)}"
|
|
@@ -230,8 +231,8 @@ def run_server(
|
|
|
230
231
|
thread_logger.info(
|
|
231
232
|
"🎨 Opening Studio in your browser..."
|
|
232
233
|
)
|
|
233
|
-
thread_logger.info("URL: " +
|
|
234
|
-
webbrowser.open(
|
|
234
|
+
thread_logger.info("URL: " + full_studio_url)
|
|
235
|
+
webbrowser.open(full_studio_url)
|
|
235
236
|
return
|
|
236
237
|
except urllib.error.URLError:
|
|
237
238
|
pass
|
|
@@ -246,7 +247,7 @@ def run_server(
|
|
|
246
247
|
╩═╝┴ ┴┘└┘└─┘╚═╝┴└─┴ ┴┴ ┴ ┴
|
|
247
248
|
|
|
248
249
|
- 🚀 API: \033[36m{local_url}\033[0m
|
|
249
|
-
- 🎨 Studio UI: \033[36m{
|
|
250
|
+
- 🎨 Studio UI: \033[36m{full_studio_url}\033[0m
|
|
250
251
|
- 📚 API Docs: \033[36m{local_url}/docs\033[0m
|
|
251
252
|
|
|
252
253
|
This in-memory server is designed for development and testing.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from langgraph.types import Command, Send
|
|
2
|
+
|
|
3
|
+
from langgraph_api.schema import RunCommand
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def map_cmd(cmd: RunCommand) -> Command:
|
|
7
|
+
goto = cmd.get("goto")
|
|
8
|
+
if goto is not None and not isinstance(goto, list):
|
|
9
|
+
goto = [cmd.get("goto")]
|
|
10
|
+
|
|
11
|
+
update = cmd.get("update")
|
|
12
|
+
if isinstance(update, tuple | list) and all(
|
|
13
|
+
isinstance(t, tuple | list) and len(t) == 2 and isinstance(t[0], str)
|
|
14
|
+
for t in update
|
|
15
|
+
):
|
|
16
|
+
update = [tuple(t) for t in update]
|
|
17
|
+
|
|
18
|
+
return Command(
|
|
19
|
+
update=update,
|
|
20
|
+
goto=(
|
|
21
|
+
[
|
|
22
|
+
it if isinstance(it, str) else Send(it["node"], it["input"])
|
|
23
|
+
for it in goto
|
|
24
|
+
]
|
|
25
|
+
if goto
|
|
26
|
+
else None
|
|
27
|
+
),
|
|
28
|
+
resume=cmd.get("resume"),
|
|
29
|
+
)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import logging
|
|
2
3
|
import os
|
|
3
4
|
import shutil
|
|
4
5
|
import ssl
|
|
5
6
|
from collections.abc import AsyncIterator
|
|
7
|
+
from contextlib import AbstractContextManager
|
|
6
8
|
from typing import Any, Literal
|
|
7
9
|
|
|
8
10
|
import certifi
|
|
@@ -605,60 +607,79 @@ async def run_remote_checkpointer():
|
|
|
605
607
|
await server.serve()
|
|
606
608
|
|
|
607
609
|
|
|
610
|
+
class DisableHttpxLoggingContextManager(AbstractContextManager):
|
|
611
|
+
"""
|
|
612
|
+
Disable HTTP/1.1 200 OK logs spamming stdout.
|
|
613
|
+
"""
|
|
614
|
+
|
|
615
|
+
filter: logging.Filter
|
|
616
|
+
|
|
617
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
618
|
+
return "200 OK" not in record.getMessage()
|
|
619
|
+
|
|
620
|
+
def __enter__(self):
|
|
621
|
+
logging.getLogger("httpx").addFilter(self.filter)
|
|
622
|
+
|
|
623
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
624
|
+
logging.getLogger("httpx").removeFilter(self.filter)
|
|
625
|
+
|
|
626
|
+
|
|
608
627
|
async def wait_until_js_ready():
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
628
|
+
with DisableHttpxLoggingContextManager():
|
|
629
|
+
async with (
|
|
630
|
+
httpx.AsyncClient(
|
|
631
|
+
base_url=f"http://localhost:{GRAPH_PORT}",
|
|
632
|
+
limits=httpx.Limits(max_connections=1),
|
|
633
|
+
transport=httpx.AsyncHTTPTransport(verify=SSL),
|
|
634
|
+
) as graph_client,
|
|
635
|
+
httpx.AsyncClient(
|
|
636
|
+
base_url=f"http://localhost:{REMOTE_PORT}",
|
|
637
|
+
limits=httpx.Limits(max_connections=1),
|
|
638
|
+
transport=httpx.AsyncHTTPTransport(verify=SSL),
|
|
639
|
+
) as checkpointer_client,
|
|
640
|
+
):
|
|
641
|
+
attempt = 0
|
|
642
|
+
while not asyncio.current_task().cancelled():
|
|
643
|
+
try:
|
|
644
|
+
res = await graph_client.get("/ok")
|
|
645
|
+
res.raise_for_status()
|
|
646
|
+
res = await checkpointer_client.get("/ok")
|
|
647
|
+
res.raise_for_status()
|
|
648
|
+
return
|
|
649
|
+
except httpx.HTTPError:
|
|
650
|
+
if attempt > 240:
|
|
651
|
+
raise
|
|
652
|
+
else:
|
|
653
|
+
attempt += 1
|
|
654
|
+
await asyncio.sleep(0.5)
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
async def js_healthcheck():
|
|
658
|
+
with DisableHttpxLoggingContextManager():
|
|
659
|
+
async with (
|
|
660
|
+
httpx.AsyncClient(
|
|
661
|
+
base_url=f"http://localhost:{GRAPH_PORT}",
|
|
662
|
+
limits=httpx.Limits(max_connections=1),
|
|
663
|
+
transport=httpx.AsyncHTTPTransport(verify=SSL),
|
|
664
|
+
) as graph_client,
|
|
665
|
+
httpx.AsyncClient(
|
|
666
|
+
base_url=f"http://localhost:{REMOTE_PORT}",
|
|
667
|
+
limits=httpx.Limits(max_connections=1),
|
|
668
|
+
transport=httpx.AsyncHTTPTransport(verify=SSL),
|
|
669
|
+
) as checkpointer_client,
|
|
670
|
+
):
|
|
623
671
|
try:
|
|
624
672
|
res = await graph_client.get("/ok")
|
|
625
673
|
res.raise_for_status()
|
|
626
674
|
res = await checkpointer_client.get("/ok")
|
|
627
675
|
res.raise_for_status()
|
|
628
|
-
return
|
|
629
|
-
except httpx.HTTPError:
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
async with (
|
|
639
|
-
httpx.AsyncClient(
|
|
640
|
-
base_url=f"http://localhost:{GRAPH_PORT}",
|
|
641
|
-
limits=httpx.Limits(max_connections=1),
|
|
642
|
-
transport=httpx.AsyncHTTPTransport(verify=SSL),
|
|
643
|
-
) as graph_client,
|
|
644
|
-
httpx.AsyncClient(
|
|
645
|
-
base_url=f"http://localhost:{REMOTE_PORT}",
|
|
646
|
-
limits=httpx.Limits(max_connections=1),
|
|
647
|
-
transport=httpx.AsyncHTTPTransport(verify=SSL),
|
|
648
|
-
) as checkpointer_client,
|
|
649
|
-
):
|
|
650
|
-
try:
|
|
651
|
-
res = await graph_client.get("/ok")
|
|
652
|
-
res.raise_for_status()
|
|
653
|
-
res = await checkpointer_client.get("/ok")
|
|
654
|
-
res.raise_for_status()
|
|
655
|
-
return True
|
|
656
|
-
except httpx.HTTPError as exc:
|
|
657
|
-
logger.warning(
|
|
658
|
-
"JS healthcheck failed. Either the JS server is not running or the event loop is blocked by a CPU-intensive task.",
|
|
659
|
-
error=exc,
|
|
660
|
-
)
|
|
661
|
-
raise HTTPException(
|
|
662
|
-
status_code=500,
|
|
663
|
-
detail="JS healthcheck failed. Either the JS server is not running or the event loop is blocked by a CPU-intensive task.",
|
|
664
|
-
) from exc
|
|
676
|
+
return True
|
|
677
|
+
except httpx.HTTPError as exc:
|
|
678
|
+
logger.warning(
|
|
679
|
+
"JS healthcheck failed. Either the JS server is not running or the event loop is blocked by a CPU-intensive task.",
|
|
680
|
+
error=exc,
|
|
681
|
+
)
|
|
682
|
+
raise HTTPException(
|
|
683
|
+
status_code=500,
|
|
684
|
+
detail="JS healthcheck failed. Either the JS server is not running or the event loop is blocked by a CPU-intensive task.",
|
|
685
|
+
) from exc
|
|
@@ -19,15 +19,15 @@ from langgraph.errors import (
|
|
|
19
19
|
InvalidUpdateError,
|
|
20
20
|
)
|
|
21
21
|
from langgraph.pregel.debug import CheckpointPayload, TaskResultPayload
|
|
22
|
-
from langgraph.types import Command, Send
|
|
23
22
|
from pydantic import ValidationError
|
|
24
23
|
from pydantic.v1 import ValidationError as ValidationErrorLegacy
|
|
25
24
|
|
|
26
25
|
from langgraph_api.asyncio import ValueEvent, wait_if_not_done
|
|
26
|
+
from langgraph_api.command import map_cmd
|
|
27
27
|
from langgraph_api.graph import get_graph
|
|
28
28
|
from langgraph_api.js.base import BaseRemotePregel
|
|
29
29
|
from langgraph_api.metadata import HOST, PLAN, incr_nodes
|
|
30
|
-
from langgraph_api.schema import Run,
|
|
30
|
+
from langgraph_api.schema import Run, StreamMode
|
|
31
31
|
from langgraph_api.serde import json_dumpb
|
|
32
32
|
from langgraph_api.utils import AsyncConnectionProto
|
|
33
33
|
from langgraph_storage.checkpoint import Checkpointer
|
|
@@ -70,32 +70,6 @@ def _preprocess_debug_checkpoint(payload: CheckpointPayload | None) -> dict[str,
|
|
|
70
70
|
return payload
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
def _map_cmd(cmd: RunCommand) -> Command:
|
|
74
|
-
goto = cmd.get("goto")
|
|
75
|
-
if goto is not None and not isinstance(goto, list):
|
|
76
|
-
goto = [cmd.get("goto")]
|
|
77
|
-
|
|
78
|
-
update = cmd.get("update")
|
|
79
|
-
if isinstance(update, tuple | list) and all(
|
|
80
|
-
isinstance(t, tuple | list) and len(t) == 2 and isinstance(t[0], str)
|
|
81
|
-
for t in update
|
|
82
|
-
):
|
|
83
|
-
update = [tuple(t) for t in update]
|
|
84
|
-
|
|
85
|
-
return Command(
|
|
86
|
-
update=update,
|
|
87
|
-
goto=(
|
|
88
|
-
[
|
|
89
|
-
it if isinstance(it, str) else Send(it["node"], it["input"])
|
|
90
|
-
for it in goto
|
|
91
|
-
]
|
|
92
|
-
if goto
|
|
93
|
-
else None
|
|
94
|
-
),
|
|
95
|
-
resume=cmd.get("resume"),
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
|
|
99
73
|
async def astream_state(
|
|
100
74
|
stack: AsyncExitStack,
|
|
101
75
|
conn: AsyncConnectionProto,
|
|
@@ -125,7 +99,7 @@ async def astream_state(
|
|
|
125
99
|
)
|
|
126
100
|
input = kwargs.pop("input")
|
|
127
101
|
if cmd := kwargs.pop("command"):
|
|
128
|
-
input =
|
|
102
|
+
input = map_cmd(cmd)
|
|
129
103
|
stream_mode: list[StreamMode] = kwargs.pop("stream_mode")
|
|
130
104
|
feedback_keys = kwargs.pop("feedback_keys", None)
|
|
131
105
|
stream_modes_set: set[StreamMode] = set(stream_mode) - {"events"}
|
|
@@ -27,7 +27,18 @@ AssistantVersionChange = jsonschema_rs.validator_for(
|
|
|
27
27
|
openapi["components"]["schemas"]["AssistantVersionChange"]
|
|
28
28
|
)
|
|
29
29
|
ThreadCreate = jsonschema_rs.validator_for(
|
|
30
|
-
|
|
30
|
+
{
|
|
31
|
+
**openapi["components"]["schemas"]["ThreadCreate"],
|
|
32
|
+
"components": {
|
|
33
|
+
"schemas": {
|
|
34
|
+
"ThreadSuperstepUpdate": openapi["components"]["schemas"][
|
|
35
|
+
"ThreadSuperstepUpdate"
|
|
36
|
+
],
|
|
37
|
+
"Command": openapi["components"]["schemas"]["Command"],
|
|
38
|
+
"Send": openapi["components"]["schemas"]["Send"],
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
}
|
|
31
42
|
)
|
|
32
43
|
ThreadPatch = jsonschema_rs.validator_for(
|
|
33
44
|
openapi["components"]["schemas"]["ThreadPatch"]
|
|
@@ -42,6 +53,7 @@ ThreadStateUpdate = jsonschema_rs.validator_for(
|
|
|
42
53
|
},
|
|
43
54
|
}
|
|
44
55
|
)
|
|
56
|
+
|
|
45
57
|
ThreadStateCheckpointRequest = jsonschema_rs.validator_for(
|
|
46
58
|
{
|
|
47
59
|
**openapi["components"]["schemas"]["ThreadStateCheckpointRequest"],
|
|
@@ -22,6 +22,7 @@ from starlette.exceptions import HTTPException
|
|
|
22
22
|
|
|
23
23
|
from langgraph_api.asyncio import SimpleTaskGroup, ValueEvent, create_task
|
|
24
24
|
from langgraph_api.auth.custom import handle_event
|
|
25
|
+
from langgraph_api.command import map_cmd
|
|
25
26
|
from langgraph_api.errors import UserInterrupt, UserRollback
|
|
26
27
|
from langgraph_api.graph import get_graph
|
|
27
28
|
from langgraph_api.schema import (
|
|
@@ -1081,6 +1082,78 @@ class Threads(Authenticated):
|
|
|
1081
1082
|
else:
|
|
1082
1083
|
raise HTTPException(status_code=400, detail="Thread has no graph ID.")
|
|
1083
1084
|
|
|
1085
|
+
@staticmethod
|
|
1086
|
+
async def bulk(
|
|
1087
|
+
conn: InMemConnectionProto,
|
|
1088
|
+
*,
|
|
1089
|
+
config: Config,
|
|
1090
|
+
supersteps: Sequence[dict],
|
|
1091
|
+
ctx: Auth.types.BaseAuthContext | None = None,
|
|
1092
|
+
) -> ThreadUpdateResponse:
|
|
1093
|
+
"""Update a thread with a batch of state updates."""
|
|
1094
|
+
|
|
1095
|
+
from langgraph.pregel.types import StateUpdate
|
|
1096
|
+
|
|
1097
|
+
thread_id = _ensure_uuid(config["configurable"]["thread_id"])
|
|
1098
|
+
filters = await Threads.handle_event(
|
|
1099
|
+
ctx,
|
|
1100
|
+
"update",
|
|
1101
|
+
Auth.types.ThreadsUpdate(thread_id=thread_id),
|
|
1102
|
+
)
|
|
1103
|
+
|
|
1104
|
+
thread_iter = await Threads.get(conn, thread_id, ctx=ctx)
|
|
1105
|
+
thread = await fetchone(
|
|
1106
|
+
thread_iter, not_found_detail=f"Thread {thread_id} not found."
|
|
1107
|
+
)
|
|
1108
|
+
|
|
1109
|
+
thread_config = thread["config"]
|
|
1110
|
+
metadata = thread["metadata"]
|
|
1111
|
+
|
|
1112
|
+
if not thread:
|
|
1113
|
+
raise HTTPException(status_code=404, detail="Thread not found")
|
|
1114
|
+
|
|
1115
|
+
if not _check_filter_match(metadata, filters):
|
|
1116
|
+
raise HTTPException(status_code=403, detail="Forbidden")
|
|
1117
|
+
|
|
1118
|
+
if graph_id := metadata.get("graph_id"):
|
|
1119
|
+
config["configurable"].setdefault("graph_id", graph_id)
|
|
1120
|
+
config["configurable"].setdefault("checkpoint_ns", "")
|
|
1121
|
+
|
|
1122
|
+
async with get_graph(
|
|
1123
|
+
graph_id, thread_config, checkpointer=Checkpointer(conn)
|
|
1124
|
+
) as graph:
|
|
1125
|
+
next_config = await graph.abulk_update_state(
|
|
1126
|
+
config,
|
|
1127
|
+
[
|
|
1128
|
+
[
|
|
1129
|
+
StateUpdate(
|
|
1130
|
+
map_cmd(update.get("command"))
|
|
1131
|
+
if update.get("command")
|
|
1132
|
+
else update.get("values"),
|
|
1133
|
+
update.get("as_node"),
|
|
1134
|
+
)
|
|
1135
|
+
for update in superstep.get("updates", [])
|
|
1136
|
+
]
|
|
1137
|
+
for superstep in supersteps
|
|
1138
|
+
],
|
|
1139
|
+
)
|
|
1140
|
+
|
|
1141
|
+
state = await Threads.State.get(
|
|
1142
|
+
conn, config, subgraphs=False, ctx=ctx
|
|
1143
|
+
)
|
|
1144
|
+
|
|
1145
|
+
# update thread values
|
|
1146
|
+
for thread in conn.store["threads"]:
|
|
1147
|
+
if thread["thread_id"] == thread_id:
|
|
1148
|
+
thread["values"] = state.values
|
|
1149
|
+
break
|
|
1150
|
+
|
|
1151
|
+
return ThreadUpdateResponse(
|
|
1152
|
+
checkpoint=next_config["configurable"],
|
|
1153
|
+
)
|
|
1154
|
+
else:
|
|
1155
|
+
raise HTTPException(status_code=400, detail="Thread has no graph ID")
|
|
1156
|
+
|
|
1084
1157
|
@staticmethod
|
|
1085
1158
|
async def list(
|
|
1086
1159
|
conn: InMemConnectionProto,
|
|
@@ -1905,7 +1978,8 @@ class Crons:
|
|
|
1905
1978
|
conn: InMemConnectionProto,
|
|
1906
1979
|
ctx: Auth.types.BaseAuthContext | None = None,
|
|
1907
1980
|
) -> AsyncIterator[Cron]:
|
|
1908
|
-
|
|
1981
|
+
yield
|
|
1982
|
+
raise NotImplementedError("The in-mem server does not implement Crons.")
|
|
1909
1983
|
|
|
1910
1984
|
@staticmethod
|
|
1911
1985
|
async def set_next_run_date(
|
|
@@ -805,6 +805,58 @@
|
|
|
805
805
|
}
|
|
806
806
|
}
|
|
807
807
|
},
|
|
808
|
+
"/threads/state/bulk": {
|
|
809
|
+
"post": {
|
|
810
|
+
"tags": [
|
|
811
|
+
"Threads"
|
|
812
|
+
],
|
|
813
|
+
"summary": "Bulk Update Thread State",
|
|
814
|
+
"description": "Create a new thread from a batch of state updates.",
|
|
815
|
+
"operationId": "bulk_update_thread_state_post",
|
|
816
|
+
"requestBody": {
|
|
817
|
+
"content": {
|
|
818
|
+
"application/json": {
|
|
819
|
+
"schema": {
|
|
820
|
+
"$ref": "#/components/schemas/ThreadStateBulkUpdate"
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
"required": true
|
|
825
|
+
},
|
|
826
|
+
"responses": {
|
|
827
|
+
"200": {
|
|
828
|
+
"description": "Success",
|
|
829
|
+
"content": {
|
|
830
|
+
"application/json": {
|
|
831
|
+
"schema": {
|
|
832
|
+
"$ref": "#/components/schemas/Thread"
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
},
|
|
837
|
+
"409": {
|
|
838
|
+
"description": "Conflict",
|
|
839
|
+
"content": {
|
|
840
|
+
"application/json": {
|
|
841
|
+
"schema": {
|
|
842
|
+
"$ref": "#/components/schemas/ErrorResponse"
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
},
|
|
847
|
+
"422": {
|
|
848
|
+
"description": "Validation Error",
|
|
849
|
+
"content": {
|
|
850
|
+
"application/json": {
|
|
851
|
+
"schema": {
|
|
852
|
+
"$ref": "#/components/schemas/ErrorResponse"
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
},
|
|
808
860
|
"/threads/{thread_id}/state": {
|
|
809
861
|
"get": {
|
|
810
862
|
"tags": [
|
|
@@ -3906,6 +3958,19 @@
|
|
|
3906
3958
|
"title": "If Exists",
|
|
3907
3959
|
"description": "How to handle duplicate creation. Must be either 'raise' (raise error if duplicate), or 'do_nothing' (return existing thread).",
|
|
3908
3960
|
"default": "raise"
|
|
3961
|
+
},
|
|
3962
|
+
"supersteps": {
|
|
3963
|
+
"type": "array",
|
|
3964
|
+
"items": {
|
|
3965
|
+
"type": "object",
|
|
3966
|
+
"properties": {
|
|
3967
|
+
"updates": {
|
|
3968
|
+
"type": "array",
|
|
3969
|
+
"items": { "$ref": "#/components/schemas/ThreadSuperstepUpdate" }
|
|
3970
|
+
}
|
|
3971
|
+
},
|
|
3972
|
+
"required": ["updates"]
|
|
3973
|
+
}
|
|
3909
3974
|
}
|
|
3910
3975
|
},
|
|
3911
3976
|
"type": "object",
|
|
@@ -4094,6 +4159,43 @@
|
|
|
4094
4159
|
"title": "ThreadStateUpdate",
|
|
4095
4160
|
"description": "Payload for updating the state of a thread."
|
|
4096
4161
|
},
|
|
4162
|
+
"ThreadSuperstepUpdate": {
|
|
4163
|
+
"properties": {
|
|
4164
|
+
"values": {
|
|
4165
|
+
"anyOf": [
|
|
4166
|
+
{
|
|
4167
|
+
"type": "array",
|
|
4168
|
+
"items": {
|
|
4169
|
+
"type": "object"
|
|
4170
|
+
}
|
|
4171
|
+
},
|
|
4172
|
+
{
|
|
4173
|
+
"type": "object"
|
|
4174
|
+
},
|
|
4175
|
+
{
|
|
4176
|
+
"type": "null"
|
|
4177
|
+
}
|
|
4178
|
+
]
|
|
4179
|
+
},
|
|
4180
|
+
"command": {
|
|
4181
|
+
"anyOf": [
|
|
4182
|
+
{
|
|
4183
|
+
"$ref": "#/components/schemas/Command"
|
|
4184
|
+
},
|
|
4185
|
+
{
|
|
4186
|
+
"type": "null"
|
|
4187
|
+
}
|
|
4188
|
+
],
|
|
4189
|
+
"description": "The command associated with the update."
|
|
4190
|
+
},
|
|
4191
|
+
"as_node": {
|
|
4192
|
+
"type": "string",
|
|
4193
|
+
"description": "Update the state as if this node had just executed."
|
|
4194
|
+
}
|
|
4195
|
+
},
|
|
4196
|
+
"required": ["as_node"],
|
|
4197
|
+
"type": "object"
|
|
4198
|
+
},
|
|
4097
4199
|
"ThreadStateUpdateResponse": {
|
|
4098
4200
|
"properties": {
|
|
4099
4201
|
"checkpoint": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "langgraph-api"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.32"
|
|
4
4
|
description = ""
|
|
5
5
|
authors = [
|
|
6
6
|
"Nuno Campos <nuno@langchain.dev>",
|
|
@@ -35,7 +35,7 @@ jsonschema-rs = ">=0.20.0,<0.30"
|
|
|
35
35
|
structlog = ">=24.1.0,<26"
|
|
36
36
|
pyjwt = "^2.9.0"
|
|
37
37
|
cryptography = "^43.0.3"
|
|
38
|
-
langgraph-sdk = "^0.1.
|
|
38
|
+
langgraph-sdk = "^0.1.58"
|
|
39
39
|
|
|
40
40
|
[tool.poetry.group.dev.dependencies]
|
|
41
41
|
ruff = "^0.6.2"
|
|
@@ -48,7 +48,7 @@ pytest-repeat = "^0.9.3"
|
|
|
48
48
|
pytest-retry = "^1.6.3"
|
|
49
49
|
pytest-httpserver = "^1.1.0"
|
|
50
50
|
fastapi = "^0.115.8"
|
|
51
|
-
langgraph = ">=0.3.
|
|
51
|
+
langgraph = ">=0.3.17"
|
|
52
52
|
pycryptodome = "^3.22.0"
|
|
53
53
|
|
|
54
54
|
[tool.pytest.ini_options]
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langgraph_api-0.0.31 → langgraph_api-0.0.32}/langgraph_api/js/src/schema/types.template.mts
RENAMED
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|