langgraph-api 0.0.48__tar.gz → 0.1.2__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.48 → langgraph_api-0.1.2}/PKG-INFO +1 -2
- langgraph_api-0.1.2/langgraph_api/__init__.py +1 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/__init__.py +2 -2
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/assistants.py +3 -3
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/meta.py +9 -11
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/runs.py +3 -3
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/store.py +2 -2
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/threads.py +3 -3
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/custom.py +25 -4
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/cli.py +3 -1
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/config.py +3 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/cron_scheduler.py +3 -3
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/graph.py +6 -14
- langgraph_api-0.1.2/langgraph_api/js/base.py +29 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/build.mts +3 -3
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/client.mts +64 -3
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/global.d.ts +1 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/package.json +4 -3
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/remote.py +96 -5
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/graph.mts +0 -6
- langgraph_api-0.1.2/langgraph_api/js/src/utils/files.mts +4 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/api.test.mts +80 -80
- langgraph_api-0.1.2/langgraph_api/js/tests/auth.test.mts +648 -0
- langgraph_api-0.1.2/langgraph_api/js/tests/compose-postgres.auth.yml +59 -0
- langgraph_api-0.1.2/langgraph_api/js/tests/graphs/agent_simple.mts +79 -0
- langgraph_api-0.1.2/langgraph_api/js/tests/graphs/auth.mts +106 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/package.json +3 -1
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/yarn.lock +9 -4
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/yarn.lock +18 -23
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/metadata.py +7 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/models/run.py +10 -1
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/queue_entrypoint.py +1 -1
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/server.py +2 -2
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/stream.py +5 -4
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/thread_ttl.py +2 -2
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/worker.py +4 -25
- langgraph_api-0.1.2/langgraph_runtime/__init__.py +39 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/pyproject.toml +3 -3
- langgraph_api-0.0.48/langgraph_api/__init__.py +0 -1
- langgraph_api-0.0.48/langgraph_api/js/base.py +0 -12
- langgraph_api-0.0.48/langgraph_api/lifespan.py +0 -74
- langgraph_api-0.0.48/langgraph_storage/checkpoint.py +0 -123
- langgraph_api-0.0.48/langgraph_storage/database.py +0 -200
- langgraph_api-0.0.48/langgraph_storage/inmem_stream.py +0 -109
- langgraph_api-0.0.48/langgraph_storage/ops.py +0 -2172
- langgraph_api-0.0.48/langgraph_storage/queue.py +0 -186
- langgraph_api-0.0.48/langgraph_storage/retry.py +0 -31
- langgraph_api-0.0.48/langgraph_storage/store.py +0 -100
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/LICENSE +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/README.md +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/mcp.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/openapi.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/api/ui.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/asyncio.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/__init__.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/langsmith/__init__.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/langsmith/backend.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/langsmith/client.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/middleware.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/noop.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/auth/studio_user.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/command.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/errors.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/http.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/.gitignore +0 -0
- {langgraph_api-0.0.48/langgraph_api/middleware → langgraph_api-0.1.2/langgraph_api/js}/__init__.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/errors.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/schema.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/hooks.mjs +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/parser/parser.mts +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/parser/parser.worker.mjs +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/schema/types.mts +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/schema/types.template.mts +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/utils/importMap.mts +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/src/utils/serde.mts +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/sse.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/compose-postgres.yml +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/.gitignore +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/agent.css +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/agent.mts +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/agent.ui.tsx +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/command.mts +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/delay.mts +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/dynamic.mts +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/error.mts +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/langgraph.json +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/nested.mts +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/graphs/weather.mts +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/parser.test.mts +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/tests/utils.mts +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/js/ui.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/logging.py +0 -0
- {langgraph_api-0.0.48/langgraph_api/models → langgraph_api-0.1.2/langgraph_api/middleware}/__init__.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/middleware/http_logger.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/middleware/private_network.py +0 -0
- {langgraph_api-0.0.48/langgraph_license → langgraph_api-0.1.2/langgraph_api/models}/__init__.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/patch.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/route.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/schema.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/serde.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/sse.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/state.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/utils.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/validation.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_api/webhook.py +0 -0
- {langgraph_api-0.0.48/langgraph_storage → langgraph_api-0.1.2/langgraph_license}/__init__.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_license/middleware.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/langgraph_license/validation.py +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/logging.json +0 -0
- {langgraph_api-0.0.48 → langgraph_api-0.1.2}/openapi.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: langgraph-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary:
|
|
5
5
|
License: Elastic-2.0
|
|
6
6
|
Author: Nuno Campos
|
|
@@ -11,7 +11,6 @@ Classifier: Programming Language :: Python :: 3
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
-
Requires-Dist: blockbuster (>=1.5.24,<2.0.0)
|
|
15
14
|
Requires-Dist: cloudpickle (>=3.0.0,<4.0.0)
|
|
16
15
|
Requires-Dist: cryptography (>=42.0.0,<45.0)
|
|
17
16
|
Requires-Dist: httpx (>=0.25.0)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.1"
|
|
@@ -21,7 +21,7 @@ from langgraph_api.auth.middleware import auth_middleware
|
|
|
21
21
|
from langgraph_api.config import HTTP_CONFIG, MIGRATIONS_PATH
|
|
22
22
|
from langgraph_api.graph import js_bg_tasks
|
|
23
23
|
from langgraph_api.validation import DOCS_HTML
|
|
24
|
-
from
|
|
24
|
+
from langgraph_runtime.database import connect, healthcheck
|
|
25
25
|
|
|
26
26
|
logger = structlog.stdlib.get_logger(__name__)
|
|
27
27
|
|
|
@@ -138,7 +138,7 @@ else:
|
|
|
138
138
|
if "inmem" in MIGRATIONS_PATH:
|
|
139
139
|
|
|
140
140
|
async def truncate(request: Request):
|
|
141
|
-
from
|
|
141
|
+
from langgraph_runtime.checkpoint import Checkpointer
|
|
142
142
|
|
|
143
143
|
await asyncio.to_thread(Checkpointer().clear)
|
|
144
144
|
async with connect() as conn:
|
|
@@ -21,9 +21,9 @@ from langgraph_api.validation import (
|
|
|
21
21
|
AssistantVersionChange,
|
|
22
22
|
AssistantVersionsSearchRequest,
|
|
23
23
|
)
|
|
24
|
-
from
|
|
25
|
-
from
|
|
26
|
-
from
|
|
24
|
+
from langgraph_runtime.database import connect
|
|
25
|
+
from langgraph_runtime.ops import Assistants
|
|
26
|
+
from langgraph_runtime.retry import retry_db
|
|
27
27
|
|
|
28
28
|
logger = structlog.stdlib.get_logger(__name__)
|
|
29
29
|
|
|
@@ -5,9 +5,9 @@ from starlette.responses import JSONResponse, PlainTextResponse
|
|
|
5
5
|
from langgraph_api import config
|
|
6
6
|
from langgraph_api.route import ApiRequest
|
|
7
7
|
from langgraph_license.validation import plus_features_enabled
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
from
|
|
8
|
+
from langgraph_runtime.database import connect, pool_stats
|
|
9
|
+
from langgraph_runtime.metrics import get_metrics
|
|
10
|
+
from langgraph_runtime.ops import Runs
|
|
11
11
|
|
|
12
12
|
METRICS_FORMATS = {"prometheus", "json"}
|
|
13
13
|
|
|
@@ -32,20 +32,18 @@ async def meta_metrics(request: ApiRequest):
|
|
|
32
32
|
format = "prometheus"
|
|
33
33
|
|
|
34
34
|
# collect stats
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
metrics = get_metrics()
|
|
36
|
+
worker_metrics = metrics["workers"]
|
|
37
|
+
workers_max = worker_metrics["max"]
|
|
38
|
+
workers_active = worker_metrics["active"]
|
|
39
|
+
workers_available = worker_metrics["available"]
|
|
38
40
|
|
|
39
41
|
if format == "json":
|
|
40
42
|
async with connect() as conn:
|
|
41
43
|
return JSONResponse(
|
|
42
44
|
{
|
|
43
45
|
**pool_stats(),
|
|
44
|
-
"workers":
|
|
45
|
-
"max": workers_max,
|
|
46
|
-
"active": workers_active,
|
|
47
|
-
"available": workers_available,
|
|
48
|
-
},
|
|
46
|
+
"workers": worker_metrics,
|
|
49
47
|
"queue": await Runs.stats(conn),
|
|
50
48
|
}
|
|
51
49
|
)
|
|
@@ -22,9 +22,9 @@ from langgraph_api.validation import (
|
|
|
22
22
|
RunsCancel,
|
|
23
23
|
)
|
|
24
24
|
from langgraph_license.validation import plus_features_enabled
|
|
25
|
-
from
|
|
26
|
-
from
|
|
27
|
-
from
|
|
25
|
+
from langgraph_runtime.database import connect
|
|
26
|
+
from langgraph_runtime.ops import Crons, Runs, Threads
|
|
27
|
+
from langgraph_runtime.retry import retry_db
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
@retry_db
|
|
@@ -13,8 +13,8 @@ from langgraph_api.validation import (
|
|
|
13
13
|
StorePutRequest,
|
|
14
14
|
StoreSearchRequest,
|
|
15
15
|
)
|
|
16
|
-
from
|
|
17
|
-
from
|
|
16
|
+
from langgraph_runtime.retry import retry_db
|
|
17
|
+
from langgraph_runtime.store import Store
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def _validate_namespace(namespace: tuple[str, ...]) -> Response | None:
|
|
@@ -15,9 +15,9 @@ from langgraph_api.validation import (
|
|
|
15
15
|
ThreadStateSearch,
|
|
16
16
|
ThreadStateUpdate,
|
|
17
17
|
)
|
|
18
|
-
from
|
|
19
|
-
from
|
|
20
|
-
from
|
|
18
|
+
from langgraph_runtime.database import connect
|
|
19
|
+
from langgraph_runtime.ops import Threads
|
|
20
|
+
from langgraph_runtime.retry import retry_db
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
@retry_db
|
|
@@ -7,7 +7,7 @@ import os
|
|
|
7
7
|
import sys
|
|
8
8
|
from collections.abc import Awaitable, Callable, Mapping
|
|
9
9
|
from contextlib import AsyncExitStack
|
|
10
|
-
from typing import Any, get_args
|
|
10
|
+
from typing import Any, Literal, get_args
|
|
11
11
|
|
|
12
12
|
import structlog
|
|
13
13
|
from langgraph_sdk import Auth
|
|
@@ -26,6 +26,7 @@ from starlette.responses import Response
|
|
|
26
26
|
from langgraph_api.auth.langsmith.backend import LangsmithAuthBackend
|
|
27
27
|
from langgraph_api.auth.studio_user import StudioUser
|
|
28
28
|
from langgraph_api.config import LANGGRAPH_AUTH, LANGGRAPH_AUTH_TYPE
|
|
29
|
+
from langgraph_api.js.base import is_js_path
|
|
29
30
|
|
|
30
31
|
logger = structlog.stdlib.get_logger(__name__)
|
|
31
32
|
|
|
@@ -56,7 +57,7 @@ def get_custom_auth_middleware() -> AuthenticationBackend:
|
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
@functools.lru_cache(maxsize=1)
|
|
59
|
-
def get_auth_instance() -> Auth | None:
|
|
60
|
+
def get_auth_instance() -> Auth | Literal["js"] | None:
|
|
60
61
|
logger.info(
|
|
61
62
|
f"Getting auth instance: {LANGGRAPH_AUTH}", langgraph_auth=str(LANGGRAPH_AUTH)
|
|
62
63
|
)
|
|
@@ -89,6 +90,12 @@ async def handle_event(
|
|
|
89
90
|
auth = get_auth_instance()
|
|
90
91
|
if auth is None:
|
|
91
92
|
return
|
|
93
|
+
|
|
94
|
+
if auth == "js":
|
|
95
|
+
from langgraph_api.js.remote import handle_js_auth_event
|
|
96
|
+
|
|
97
|
+
return await handle_js_auth_event(ctx, value)
|
|
98
|
+
|
|
92
99
|
handler = _get_handler(auth, ctx)
|
|
93
100
|
if not handler:
|
|
94
101
|
return
|
|
@@ -195,6 +202,7 @@ def _get_custom_auth_middleware(
|
|
|
195
202
|
else:
|
|
196
203
|
path = config.get("path")
|
|
197
204
|
disable_studio_auth = config.get("disable_studio_auth", disable_studio_auth)
|
|
205
|
+
|
|
198
206
|
auth_instance = _get_auth_instance(path)
|
|
199
207
|
if auth_instance is None:
|
|
200
208
|
raise ValueError(
|
|
@@ -204,6 +212,12 @@ def _get_custom_auth_middleware(
|
|
|
204
212
|
"from langgraph_sdk import Auth\n"
|
|
205
213
|
"auth = Auth()"
|
|
206
214
|
)
|
|
215
|
+
|
|
216
|
+
if auth_instance == "js":
|
|
217
|
+
from langgraph_api.js.remote import CustomJsAuthBackend
|
|
218
|
+
|
|
219
|
+
return CustomJsAuthBackend(disable_studio_auth=disable_studio_auth)
|
|
220
|
+
|
|
207
221
|
if auth_instance._authenticate_handler is None:
|
|
208
222
|
raise ValueError(
|
|
209
223
|
f"Custom Auth object at path: {path} does not have an authenticate handler."
|
|
@@ -214,6 +228,7 @@ def _get_custom_auth_middleware(
|
|
|
214
228
|
"async def authenticate(request):\n"
|
|
215
229
|
' return "my-user-id"'
|
|
216
230
|
)
|
|
231
|
+
|
|
217
232
|
result = CustomAuthBackend(
|
|
218
233
|
auth_instance._authenticate_handler,
|
|
219
234
|
disable_studio_auth,
|
|
@@ -223,12 +238,15 @@ def _get_custom_auth_middleware(
|
|
|
223
238
|
|
|
224
239
|
|
|
225
240
|
@functools.lru_cache(maxsize=1)
|
|
226
|
-
def _get_auth_instance(path: str | None = None) -> Auth | None:
|
|
241
|
+
def _get_auth_instance(path: str | None = None) -> Auth | Literal["js"] | None:
|
|
227
242
|
if path is not None:
|
|
228
243
|
auth_instance = _load_auth_obj(path)
|
|
229
244
|
else:
|
|
230
245
|
auth_instance = None
|
|
231
246
|
|
|
247
|
+
if auth_instance == "js":
|
|
248
|
+
return auth_instance
|
|
249
|
+
|
|
232
250
|
if auth_instance is not None and (
|
|
233
251
|
deps := _get_dependencies(auth_instance._authenticate_handler)
|
|
234
252
|
):
|
|
@@ -544,7 +562,7 @@ def normalize_user(user: Any) -> BaseUser:
|
|
|
544
562
|
)
|
|
545
563
|
|
|
546
564
|
|
|
547
|
-
def _load_auth_obj(path: str) -> Auth:
|
|
565
|
+
def _load_auth_obj(path: str) -> Auth | Literal["js"]:
|
|
548
566
|
"""Load an object from a path string."""
|
|
549
567
|
if ":" not in path:
|
|
550
568
|
raise ValueError(
|
|
@@ -555,6 +573,9 @@ def _load_auth_obj(path: str) -> Auth:
|
|
|
555
573
|
module_name, callable_name = path.rsplit(":", 1)
|
|
556
574
|
module_name = module_name.rstrip(":")
|
|
557
575
|
|
|
576
|
+
if is_js_path(module_name):
|
|
577
|
+
return "js"
|
|
578
|
+
|
|
558
579
|
try:
|
|
559
580
|
if "/" in module_name or ".py" in module_name:
|
|
560
581
|
# Load from file path
|
|
@@ -6,6 +6,7 @@ import pathlib
|
|
|
6
6
|
import threading
|
|
7
7
|
import typing
|
|
8
8
|
from collections.abc import Mapping, Sequence
|
|
9
|
+
from typing import Literal
|
|
9
10
|
|
|
10
11
|
from typing_extensions import TypedDict
|
|
11
12
|
|
|
@@ -134,6 +135,7 @@ def run_server(
|
|
|
134
135
|
studio_url: str | None = None,
|
|
135
136
|
disable_persistence: bool = False,
|
|
136
137
|
allow_blocking: bool = False,
|
|
138
|
+
runtime_edition: Literal["inmem", "community", "postgres"] = "inmem",
|
|
137
139
|
**kwargs: typing.Any,
|
|
138
140
|
):
|
|
139
141
|
"""Run the LangGraph API server."""
|
|
@@ -197,6 +199,7 @@ def run_server(
|
|
|
197
199
|
LANGGRAPH_UI_BUNDLER="true",
|
|
198
200
|
LANGGRAPH_API_URL=local_url,
|
|
199
201
|
LANGGRAPH_DISABLE_FILE_PERSISTENCE=str(disable_persistence).lower(),
|
|
202
|
+
LANGGRAPH_RUNTIME_EDITION=runtime_edition,
|
|
200
203
|
# If true, we will not raise on blocking IO calls (via blockbuster)
|
|
201
204
|
LANGGRAPH_ALLOW_BLOCKING=str(allow_blocking).lower(),
|
|
202
205
|
# See https://developer.chrome.com/blog/private-network-access-update-2024-03
|
|
@@ -274,7 +277,6 @@ For production use, please use LangGraph Cloud.
|
|
|
274
277
|
|
|
275
278
|
"""
|
|
276
279
|
logger.info(welcome)
|
|
277
|
-
|
|
278
280
|
if open_browser:
|
|
279
281
|
threading.Thread(target=_open_browser, daemon=True).start()
|
|
280
282
|
supported_kwargs = {
|
|
@@ -323,6 +323,9 @@ USES_INDEXING = (
|
|
|
323
323
|
and STORE_CONFIG.get("index").get("embed")
|
|
324
324
|
)
|
|
325
325
|
USES_CUSTOM_APP = HTTP_CONFIG and HTTP_CONFIG.get("app")
|
|
326
|
+
USES_CUSTOM_AUTH = bool(LANGGRAPH_AUTH)
|
|
327
|
+
USES_THREAD_TTL = bool(THREAD_TTL)
|
|
328
|
+
USES_STORE_TTL = bool(STORE_CONFIG and STORE_CONFIG.get("ttl"))
|
|
326
329
|
|
|
327
330
|
API_VARIANT = env("LANGSMITH_LANGGRAPH_API_VARIANT", cast=str, default="")
|
|
328
331
|
|
|
@@ -7,9 +7,9 @@ from langchain_core.runnables.config import run_in_executor
|
|
|
7
7
|
from langgraph_api.models.run import create_valid_run
|
|
8
8
|
from langgraph_api.utils import next_cron_date
|
|
9
9
|
from langgraph_api.worker import set_auth_ctx_for_run
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
from
|
|
10
|
+
from langgraph_runtime.database import connect
|
|
11
|
+
from langgraph_runtime.ops import Crons
|
|
12
|
+
from langgraph_runtime.retry import retry_db
|
|
13
13
|
|
|
14
14
|
logger = structlog.stdlib.get_logger(__name__)
|
|
15
15
|
|
|
@@ -24,7 +24,7 @@ from starlette.exceptions import HTTPException
|
|
|
24
24
|
|
|
25
25
|
from langgraph_api import asyncio as lg_asyncio
|
|
26
26
|
from langgraph_api import config
|
|
27
|
-
from langgraph_api.js.base import BaseRemotePregel
|
|
27
|
+
from langgraph_api.js.base import BaseRemotePregel, is_js_path
|
|
28
28
|
from langgraph_api.schema import Config
|
|
29
29
|
|
|
30
30
|
if TYPE_CHECKING:
|
|
@@ -50,8 +50,8 @@ async def register_graph(
|
|
|
50
50
|
description: str | None = None,
|
|
51
51
|
) -> None:
|
|
52
52
|
"""Register a graph."""
|
|
53
|
-
from
|
|
54
|
-
from
|
|
53
|
+
from langgraph_runtime.database import connect
|
|
54
|
+
from langgraph_runtime.ops import Assistants
|
|
55
55
|
|
|
56
56
|
await logger.ainfo(f"Registering graph with id '{graph_id}'", graph_id=graph_id)
|
|
57
57
|
GRAPHS[graph_id] = graph
|
|
@@ -181,17 +181,6 @@ class GraphSpec(NamedTuple):
|
|
|
181
181
|
js_bg_tasks: set[asyncio.Task] = set()
|
|
182
182
|
|
|
183
183
|
|
|
184
|
-
def is_js_spec(spec: GraphSpec) -> bool:
|
|
185
|
-
return spec.path is not None and os.path.splitext(spec.path)[1] in (
|
|
186
|
-
".ts",
|
|
187
|
-
".mts",
|
|
188
|
-
".cts",
|
|
189
|
-
".js",
|
|
190
|
-
".mjs",
|
|
191
|
-
".cjs",
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
|
|
195
184
|
def _load_graph_config_from_env() -> dict | None:
|
|
196
185
|
"""Return graph config from env."""
|
|
197
186
|
config_str = os.getenv("LANGGRAPH_CONFIG")
|
|
@@ -283,6 +272,9 @@ async def collect_graphs_from_env(register: bool = False) -> None:
|
|
|
283
272
|
for graph_path in glob.glob("/graphs/*.py")
|
|
284
273
|
]
|
|
285
274
|
|
|
275
|
+
def is_js_spec(x: GraphSpec) -> bool:
|
|
276
|
+
return is_js_path(x.path)
|
|
277
|
+
|
|
286
278
|
js_specs = list(filter(is_js_spec, specs))
|
|
287
279
|
py_specs = list(filterfalse(is_js_spec, specs))
|
|
288
280
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from langchain_core.runnables import Runnable
|
|
4
|
+
|
|
5
|
+
from langgraph_api.schema import Config
|
|
6
|
+
|
|
7
|
+
JS_EXTENSIONS = (
|
|
8
|
+
".ts",
|
|
9
|
+
".mts",
|
|
10
|
+
".cts",
|
|
11
|
+
".js",
|
|
12
|
+
".mjs",
|
|
13
|
+
".cjs",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_js_path(path: str | None) -> bool:
|
|
18
|
+
if path is None:
|
|
19
|
+
return False
|
|
20
|
+
return os.path.splitext(path)[1] in JS_EXTENSIONS
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BaseRemotePregel(Runnable):
|
|
24
|
+
name: str = "LangGraph"
|
|
25
|
+
|
|
26
|
+
graph_id: str
|
|
27
|
+
|
|
28
|
+
# Config passed from get_graph()
|
|
29
|
+
config: Config
|
|
@@ -4,19 +4,19 @@ import { z } from "zod";
|
|
|
4
4
|
import * as fs from "node:fs/promises";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import {
|
|
7
|
-
filterValidGraphSpecs,
|
|
8
7
|
GraphSchema,
|
|
9
8
|
resolveGraph,
|
|
10
9
|
runGraphSchemaWorker,
|
|
11
10
|
} from "./src/graph.mts";
|
|
12
11
|
import { build } from "@langchain/langgraph-ui";
|
|
12
|
+
import { filterValidExportPath } from "./src/utils/files.mts";
|
|
13
13
|
|
|
14
14
|
const __dirname = new URL(".", import.meta.url).pathname;
|
|
15
15
|
|
|
16
16
|
async function main() {
|
|
17
|
-
const specs =
|
|
17
|
+
const specs = Object.entries(
|
|
18
18
|
z.record(z.string()).parse(JSON.parse(process.env.LANGSERVE_GRAPHS))
|
|
19
|
-
);
|
|
19
|
+
).filter(([_, spec]) => filterValidExportPath(spec));
|
|
20
20
|
|
|
21
21
|
const GRAPH_SCHEMAS: Record<string, Record<string, GraphSchema> | false> = {};
|
|
22
22
|
let failed = false;
|
|
@@ -42,11 +42,17 @@ import {
|
|
|
42
42
|
GraphSchema,
|
|
43
43
|
resolveGraph,
|
|
44
44
|
GraphSpec,
|
|
45
|
-
filterValidGraphSpecs,
|
|
46
45
|
type CompiledGraphFactory,
|
|
47
46
|
} from "./src/graph.mts";
|
|
48
47
|
import { asyncExitHook, gracefulExit } from "exit-hook";
|
|
49
48
|
import { awaitAllCallbacks } from "@langchain/core/callbacks/promises";
|
|
49
|
+
import { StatusCode } from "hono/utils/http-status";
|
|
50
|
+
import {
|
|
51
|
+
authenticate,
|
|
52
|
+
authorize,
|
|
53
|
+
registerAuth,
|
|
54
|
+
} from "@langchain/langgraph-api/auth";
|
|
55
|
+
import { filterValidExportPath } from "./src/utils/files.mts";
|
|
50
56
|
|
|
51
57
|
const logger = createLogger({
|
|
52
58
|
level: "debug",
|
|
@@ -872,9 +878,9 @@ async function main() {
|
|
|
872
878
|
store: new RemoteStore(),
|
|
873
879
|
};
|
|
874
880
|
|
|
875
|
-
const specs =
|
|
881
|
+
const specs = Object.entries(
|
|
876
882
|
z.record(z.string()).parse(JSON.parse(process.env.LANGSERVE_GRAPHS ?? "{}"))
|
|
877
|
-
);
|
|
883
|
+
).filter(([_, spec]) => filterValidExportPath(spec));
|
|
878
884
|
|
|
879
885
|
if (!process.argv.includes("--skip-schema-cache")) {
|
|
880
886
|
try {
|
|
@@ -944,6 +950,61 @@ async function main() {
|
|
|
944
950
|
)
|
|
945
951
|
);
|
|
946
952
|
|
|
953
|
+
// Load LANGGRAPH_AUTH
|
|
954
|
+
const auth = z
|
|
955
|
+
.object({
|
|
956
|
+
path: z.string().optional(),
|
|
957
|
+
disable_studio_auth: z.boolean().optional(),
|
|
958
|
+
})
|
|
959
|
+
.parse(JSON.parse(process.env.LANGGRAPH_AUTH ?? "{}"));
|
|
960
|
+
|
|
961
|
+
if (filterValidExportPath(auth.path)) {
|
|
962
|
+
await registerAuth(auth, { cwd: process.cwd() });
|
|
963
|
+
|
|
964
|
+
app.post("/auth/authenticate", async (c) => {
|
|
965
|
+
try {
|
|
966
|
+
const rawHeaders = c.req.raw.headers;
|
|
967
|
+
const authUrl = rawHeaders.get("x-langgraph-auth-url") as string;
|
|
968
|
+
const method = rawHeaders.get("x-langgraph-auth-method") as string;
|
|
969
|
+
|
|
970
|
+
const headers = new Headers(rawHeaders);
|
|
971
|
+
headers.delete("x-langgraph-auth-url");
|
|
972
|
+
headers.delete("x-langgraph-auth-method");
|
|
973
|
+
|
|
974
|
+
const context = await authenticate(
|
|
975
|
+
new Request(authUrl, { headers, method })
|
|
976
|
+
);
|
|
977
|
+
|
|
978
|
+
return c.json(context);
|
|
979
|
+
} catch (error) {
|
|
980
|
+
if (error instanceof HTTPException) {
|
|
981
|
+
return c.json(
|
|
982
|
+
{
|
|
983
|
+
...serializeError(error),
|
|
984
|
+
status: error.res?.status ?? error.status,
|
|
985
|
+
headers: error.res?.headers,
|
|
986
|
+
},
|
|
987
|
+
error.status as StatusCode
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
return c.json(serializeError(error), 403);
|
|
992
|
+
}
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
app.post("/auth/authorize", async (c) => {
|
|
996
|
+
try {
|
|
997
|
+
return c.json(await authorize(await c.req.json()));
|
|
998
|
+
} catch (error) {
|
|
999
|
+
if (error instanceof HTTPException) {
|
|
1000
|
+
return c.json(serializeError(error), error.status);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
return c.json(serializeError(error), 500);
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
|
|
947
1008
|
app.get("/ok", (c) => c.json({ ok: true }));
|
|
948
1009
|
|
|
949
1010
|
app.onError((err, c) => {
|
|
@@ -24,15 +24,16 @@
|
|
|
24
24
|
"undici": "^6.21.1",
|
|
25
25
|
"uuid": "^10.0.0",
|
|
26
26
|
"winston": "^3.17.0",
|
|
27
|
-
"@langchain/langgraph-api": "~0.0.
|
|
28
|
-
"@langchain/langgraph-ui": "~0.0.
|
|
27
|
+
"@langchain/langgraph-api": "~0.0.21",
|
|
28
|
+
"@langchain/langgraph-ui": "~0.0.21",
|
|
29
29
|
"zod": "^3.23.8"
|
|
30
30
|
},
|
|
31
31
|
"resolutions": {
|
|
32
32
|
"esbuild": "^0.25.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"
|
|
35
|
+
"jose": "^6.0.10",
|
|
36
|
+
"@langchain/langgraph-sdk": "^0.0.66",
|
|
36
37
|
"@types/react": "^19.0.8",
|
|
37
38
|
"@types/react-dom": "^19.0.3",
|
|
38
39
|
"@types/node": "^22.2.0",
|
|
@@ -5,7 +5,7 @@ import shutil
|
|
|
5
5
|
import ssl
|
|
6
6
|
from collections.abc import AsyncIterator
|
|
7
7
|
from contextlib import AbstractContextManager
|
|
8
|
-
from typing import Any, Literal, Self
|
|
8
|
+
from typing import Any, Literal, Self, cast
|
|
9
9
|
|
|
10
10
|
import certifi
|
|
11
11
|
import httpx
|
|
@@ -24,12 +24,20 @@ from langgraph.checkpoint.serde.base import SerializerProtocol
|
|
|
24
24
|
from langgraph.pregel.types import PregelTask, StateSnapshot
|
|
25
25
|
from langgraph.store.base import GetOp, Item, ListNamespacesOp, PutOp, SearchOp
|
|
26
26
|
from langgraph.types import Command, Interrupt, Send
|
|
27
|
+
from langgraph_sdk import Auth
|
|
27
28
|
from pydantic import BaseModel
|
|
28
29
|
from starlette.applications import Starlette
|
|
30
|
+
from starlette.authentication import (
|
|
31
|
+
AuthCredentials,
|
|
32
|
+
AuthenticationBackend,
|
|
33
|
+
BaseUser,
|
|
34
|
+
)
|
|
29
35
|
from starlette.exceptions import HTTPException
|
|
30
|
-
from starlette.requests import Request
|
|
36
|
+
from starlette.requests import HTTPConnection, Request
|
|
31
37
|
from starlette.routing import Route
|
|
32
38
|
|
|
39
|
+
from langgraph_api.auth.custom import DotDict, ProxyUser
|
|
40
|
+
from langgraph_api.config import LANGGRAPH_AUTH_TYPE
|
|
33
41
|
from langgraph_api.js.base import BaseRemotePregel
|
|
34
42
|
from langgraph_api.js.errors import RemoteException
|
|
35
43
|
from langgraph_api.js.sse import SSEDecoder, aiter_lines_raw
|
|
@@ -365,7 +373,7 @@ async def run_js_process(paths_str: str, watch: bool = False):
|
|
|
365
373
|
|
|
366
374
|
|
|
367
375
|
def _get_passthrough_checkpointer(conn: AsyncConnectionProto):
|
|
368
|
-
from
|
|
376
|
+
from langgraph_runtime.checkpoint import Checkpointer
|
|
369
377
|
|
|
370
378
|
class PassthroughSerialiser(SerializerProtocol):
|
|
371
379
|
def dumps(self, obj: Any) -> bytes:
|
|
@@ -393,7 +401,7 @@ def _get_passthrough_checkpointer(conn: AsyncConnectionProto):
|
|
|
393
401
|
|
|
394
402
|
|
|
395
403
|
def _get_passthrough_store():
|
|
396
|
-
from
|
|
404
|
+
from langgraph_runtime.store import Store
|
|
397
405
|
|
|
398
406
|
return Store()
|
|
399
407
|
|
|
@@ -401,7 +409,7 @@ def _get_passthrough_store():
|
|
|
401
409
|
# Setup a HTTP server on top of CHECKPOINTER_SOCKET unix socket
|
|
402
410
|
# used by `client.mts` to communicate with the Python checkpointer
|
|
403
411
|
async def run_remote_checkpointer():
|
|
404
|
-
from
|
|
412
|
+
from langgraph_runtime.database import connect
|
|
405
413
|
|
|
406
414
|
async def checkpointer_list(payload: dict):
|
|
407
415
|
"""Search checkpoints"""
|
|
@@ -729,3 +737,86 @@ async def js_healthcheck():
|
|
|
729
737
|
status_code=500,
|
|
730
738
|
detail="JS healthcheck failed. Either the JS server is not running or the event loop is blocked by a CPU-intensive task.",
|
|
731
739
|
) from exc
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
class CustomJsAuthBackend(AuthenticationBackend):
|
|
743
|
+
ls_auth: AuthenticationBackend | None
|
|
744
|
+
|
|
745
|
+
def __init__(self, disable_studio_auth: bool = False):
|
|
746
|
+
self.ls_auth = None
|
|
747
|
+
if not disable_studio_auth and LANGGRAPH_AUTH_TYPE == "langsmith":
|
|
748
|
+
from langgraph_api.auth.langsmith.backend import LangsmithAuthBackend
|
|
749
|
+
|
|
750
|
+
self.ls_auth = LangsmithAuthBackend()
|
|
751
|
+
|
|
752
|
+
async def authenticate(
|
|
753
|
+
self, conn: HTTPConnection
|
|
754
|
+
) -> tuple[AuthCredentials, BaseUser] | None:
|
|
755
|
+
if self.ls_auth is not None and (
|
|
756
|
+
(auth_scheme := conn.headers.get("x-auth-scheme"))
|
|
757
|
+
and auth_scheme == "langsmith"
|
|
758
|
+
):
|
|
759
|
+
return await self.ls_auth.authenticate(conn)
|
|
760
|
+
|
|
761
|
+
headers = dict(conn.headers)
|
|
762
|
+
# need to remove content-length to prevent confusing the HTTP client
|
|
763
|
+
headers.pop("content-length", None)
|
|
764
|
+
headers["x-langgraph-auth-url"] = str(conn.url)
|
|
765
|
+
headers["x-langgraph-auth-method"] = conn.scope.get("method")
|
|
766
|
+
|
|
767
|
+
res = await _client.post("/auth/authenticate", headers=headers)
|
|
768
|
+
data = res.json()
|
|
769
|
+
|
|
770
|
+
if data.get("error"):
|
|
771
|
+
status = data.get("status") or 403
|
|
772
|
+
headers = data.get("headers")
|
|
773
|
+
message = data.get("message") or "Unauthorized"
|
|
774
|
+
|
|
775
|
+
raise HTTPException(status_code=status, detail=message, headers=headers)
|
|
776
|
+
|
|
777
|
+
return AuthCredentials(data["scopes"]), ProxyUser(DotDict(data["user"]))
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
async def handle_js_auth_event(
|
|
781
|
+
ctx: Auth.types.AuthContext | None,
|
|
782
|
+
value: dict,
|
|
783
|
+
) -> Auth.types.FilterType | None:
|
|
784
|
+
res = await _client.post(
|
|
785
|
+
"/auth/authorize",
|
|
786
|
+
headers={"Content-Type": "application/json"},
|
|
787
|
+
data=json_dumpb(
|
|
788
|
+
{
|
|
789
|
+
"resource": ctx.resource,
|
|
790
|
+
"action": ctx.action,
|
|
791
|
+
"value": value,
|
|
792
|
+
"context": {
|
|
793
|
+
"user": cast(DotDict, ctx.user).dict(),
|
|
794
|
+
"scopes": ctx.permissions,
|
|
795
|
+
}
|
|
796
|
+
if ctx
|
|
797
|
+
else None,
|
|
798
|
+
}
|
|
799
|
+
),
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
response = res.json()
|
|
803
|
+
|
|
804
|
+
if response.get("error"):
|
|
805
|
+
status = response.get("status") or 403
|
|
806
|
+
headers = response.get("headers")
|
|
807
|
+
message = response.get("message") or "Unauthorized"
|
|
808
|
+
|
|
809
|
+
raise HTTPException(status_code=status, detail=message, headers=headers)
|
|
810
|
+
|
|
811
|
+
filters = cast(Auth.types.FilterType | None, response.get("filters"))
|
|
812
|
+
|
|
813
|
+
# mutate metadata in value if applicable
|
|
814
|
+
# we need to preserve the identity of the object, so cannot create a new
|
|
815
|
+
# dictionary, otherwise the changes will not persist
|
|
816
|
+
if isinstance(value, dict) and (updated_value := response.get("value")):
|
|
817
|
+
if isinstance(value.get("metadata"), dict) and (
|
|
818
|
+
metadata := updated_value.get("metadata")
|
|
819
|
+
):
|
|
820
|
+
value["metadata"].update(metadata)
|
|
821
|
+
|
|
822
|
+
return filters
|
|
@@ -20,12 +20,6 @@ export interface GraphSpec {
|
|
|
20
20
|
exportSymbol: string;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export function filterValidGraphSpecs(specs: Record<string, string>) {
|
|
24
|
-
return Object.entries(specs).filter(
|
|
25
|
-
([_, spec]) => !spec.split(":")[0].endsWith(".py")
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
23
|
export type CompiledGraphFactory<T extends string> = (config: {
|
|
30
24
|
configurable?: Record<string, unknown>;
|
|
31
25
|
}) => Promise<CompiledGraph<T>>;
|