langgraph-api 0.4.1__py3-none-any.whl → 0.7.3__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.
- langgraph_api/__init__.py +1 -1
- langgraph_api/api/__init__.py +111 -51
- langgraph_api/api/a2a.py +1610 -0
- langgraph_api/api/assistants.py +212 -89
- langgraph_api/api/mcp.py +3 -3
- langgraph_api/api/meta.py +52 -28
- langgraph_api/api/openapi.py +27 -17
- langgraph_api/api/profile.py +108 -0
- langgraph_api/api/runs.py +342 -195
- langgraph_api/api/store.py +19 -2
- langgraph_api/api/threads.py +209 -27
- langgraph_api/asgi_transport.py +14 -9
- langgraph_api/asyncio.py +14 -4
- langgraph_api/auth/custom.py +52 -37
- langgraph_api/auth/langsmith/backend.py +4 -3
- langgraph_api/auth/langsmith/client.py +13 -8
- langgraph_api/cli.py +230 -133
- langgraph_api/command.py +5 -3
- langgraph_api/config/__init__.py +532 -0
- langgraph_api/config/_parse.py +58 -0
- langgraph_api/config/schemas.py +431 -0
- langgraph_api/cron_scheduler.py +17 -1
- langgraph_api/encryption/__init__.py +15 -0
- langgraph_api/encryption/aes_json.py +158 -0
- langgraph_api/encryption/context.py +35 -0
- langgraph_api/encryption/custom.py +280 -0
- langgraph_api/encryption/middleware.py +632 -0
- langgraph_api/encryption/shared.py +63 -0
- langgraph_api/errors.py +12 -1
- langgraph_api/executor_entrypoint.py +11 -6
- langgraph_api/feature_flags.py +29 -0
- langgraph_api/graph.py +176 -76
- langgraph_api/grpc/client.py +313 -0
- langgraph_api/grpc/config_conversion.py +231 -0
- langgraph_api/grpc/generated/__init__.py +29 -0
- langgraph_api/grpc/generated/checkpointer_pb2.py +63 -0
- langgraph_api/grpc/generated/checkpointer_pb2.pyi +99 -0
- langgraph_api/grpc/generated/checkpointer_pb2_grpc.py +329 -0
- langgraph_api/grpc/generated/core_api_pb2.py +216 -0
- langgraph_api/grpc/generated/core_api_pb2.pyi +905 -0
- langgraph_api/grpc/generated/core_api_pb2_grpc.py +1621 -0
- langgraph_api/grpc/generated/engine_common_pb2.py +219 -0
- langgraph_api/grpc/generated/engine_common_pb2.pyi +722 -0
- langgraph_api/grpc/generated/engine_common_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_cancel_run_action_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_cancel_run_action_pb2.pyi +12 -0
- langgraph_api/grpc/generated/enum_cancel_run_action_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_control_signal_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_control_signal_pb2.pyi +16 -0
- langgraph_api/grpc/generated/enum_control_signal_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_durability_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_durability_pb2.pyi +16 -0
- langgraph_api/grpc/generated/enum_durability_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_multitask_strategy_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_multitask_strategy_pb2.pyi +16 -0
- langgraph_api/grpc/generated/enum_multitask_strategy_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_run_status_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_run_status_pb2.pyi +22 -0
- langgraph_api/grpc/generated/enum_run_status_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_stream_mode_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_stream_mode_pb2.pyi +28 -0
- langgraph_api/grpc/generated/enum_stream_mode_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_thread_status_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_thread_status_pb2.pyi +16 -0
- langgraph_api/grpc/generated/enum_thread_status_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/enum_thread_stream_mode_pb2.py +37 -0
- langgraph_api/grpc/generated/enum_thread_stream_mode_pb2.pyi +16 -0
- langgraph_api/grpc/generated/enum_thread_stream_mode_pb2_grpc.py +24 -0
- langgraph_api/grpc/generated/errors_pb2.py +39 -0
- langgraph_api/grpc/generated/errors_pb2.pyi +21 -0
- langgraph_api/grpc/generated/errors_pb2_grpc.py +24 -0
- langgraph_api/grpc/ops/__init__.py +370 -0
- langgraph_api/grpc/ops/assistants.py +424 -0
- langgraph_api/grpc/ops/runs.py +792 -0
- langgraph_api/grpc/ops/threads.py +1013 -0
- langgraph_api/http.py +16 -5
- langgraph_api/http_metrics.py +15 -35
- langgraph_api/http_metrics_utils.py +38 -0
- langgraph_api/js/build.mts +1 -1
- langgraph_api/js/client.http.mts +13 -7
- langgraph_api/js/client.mts +2 -5
- langgraph_api/js/package.json +29 -28
- langgraph_api/js/remote.py +56 -30
- langgraph_api/js/src/graph.mts +20 -0
- langgraph_api/js/sse.py +2 -2
- langgraph_api/js/ui.py +1 -1
- langgraph_api/js/yarn.lock +1204 -1006
- langgraph_api/logging.py +29 -2
- langgraph_api/metadata.py +99 -28
- langgraph_api/middleware/http_logger.py +7 -2
- langgraph_api/middleware/private_network.py +7 -7
- langgraph_api/models/run.py +54 -93
- langgraph_api/otel_context.py +205 -0
- langgraph_api/patch.py +5 -3
- langgraph_api/queue_entrypoint.py +154 -65
- langgraph_api/route.py +47 -5
- langgraph_api/schema.py +88 -10
- langgraph_api/self_hosted_logs.py +124 -0
- langgraph_api/self_hosted_metrics.py +450 -0
- langgraph_api/serde.py +79 -37
- langgraph_api/server.py +138 -60
- langgraph_api/state.py +4 -3
- langgraph_api/store.py +25 -16
- langgraph_api/stream.py +80 -29
- langgraph_api/thread_ttl.py +31 -13
- langgraph_api/timing/__init__.py +25 -0
- langgraph_api/timing/profiler.py +200 -0
- langgraph_api/timing/timer.py +318 -0
- langgraph_api/utils/__init__.py +53 -8
- langgraph_api/utils/cache.py +47 -10
- langgraph_api/utils/config.py +2 -1
- langgraph_api/utils/errors.py +77 -0
- langgraph_api/utils/future.py +10 -6
- langgraph_api/utils/headers.py +76 -2
- langgraph_api/utils/retriable_client.py +74 -0
- langgraph_api/utils/stream_codec.py +315 -0
- langgraph_api/utils/uuids.py +29 -62
- langgraph_api/validation.py +9 -0
- langgraph_api/webhook.py +120 -6
- langgraph_api/worker.py +55 -24
- {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/METADATA +16 -8
- langgraph_api-0.7.3.dist-info/RECORD +168 -0
- {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/WHEEL +1 -1
- langgraph_runtime/__init__.py +1 -0
- langgraph_runtime/routes.py +11 -0
- logging.json +1 -3
- openapi.json +839 -478
- langgraph_api/config.py +0 -387
- langgraph_api/js/isolate-0x130008000-46649-46649-v8.log +0 -4430
- langgraph_api/js/isolate-0x138008000-44681-44681-v8.log +0 -4430
- langgraph_api/js/package-lock.json +0 -3308
- langgraph_api-0.4.1.dist-info/RECORD +0 -107
- /langgraph_api/{utils.py → grpc/__init__.py} +0 -0
- {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/entry_points.txt +0 -0
- {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/licenses/LICENSE +0 -0
langgraph_api/http.py
CHANGED
|
@@ -70,11 +70,11 @@ class JsonHttpClient:
|
|
|
70
70
|
await res.aclose()
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
_http_client: JsonHttpClient
|
|
73
|
+
_http_client: JsonHttpClient | None = None
|
|
74
74
|
_loopback_client: JsonHttpClient | None = None
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
async def start_http_client() ->
|
|
77
|
+
async def start_http_client() -> JsonHttpClient:
|
|
78
78
|
global _http_client
|
|
79
79
|
_http_client = JsonHttpClient(
|
|
80
80
|
client=httpx.AsyncClient(
|
|
@@ -86,15 +86,26 @@ async def start_http_client() -> None:
|
|
|
86
86
|
),
|
|
87
87
|
),
|
|
88
88
|
)
|
|
89
|
+
return _http_client
|
|
89
90
|
|
|
90
91
|
|
|
91
92
|
async def stop_http_client() -> None:
|
|
92
93
|
global _http_client
|
|
94
|
+
if _http_client is None:
|
|
95
|
+
return
|
|
93
96
|
await _http_client.client.aclose()
|
|
94
|
-
|
|
97
|
+
_http_client = None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_http_client() -> JsonHttpClient | None:
|
|
101
|
+
global _http_client
|
|
102
|
+
return _http_client
|
|
95
103
|
|
|
96
104
|
|
|
97
|
-
def
|
|
105
|
+
async def ensure_http_client() -> JsonHttpClient:
|
|
106
|
+
global _http_client
|
|
107
|
+
if _http_client is None:
|
|
108
|
+
return await start_http_client()
|
|
98
109
|
return _http_client
|
|
99
110
|
|
|
100
111
|
|
|
@@ -168,7 +179,7 @@ async def http_request(
|
|
|
168
179
|
if not path.startswith(("http://", "https://", "/")):
|
|
169
180
|
raise ValueError("path must start with / or http")
|
|
170
181
|
|
|
171
|
-
client = client or
|
|
182
|
+
client = client or (await ensure_http_client())
|
|
172
183
|
|
|
173
184
|
content = None
|
|
174
185
|
if body is not None:
|
langgraph_api/http_metrics.py
CHANGED
|
@@ -1,51 +1,23 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
from langgraph_api import config
|
|
5
|
+
from langgraph_api.http_metrics_utils import (
|
|
6
|
+
HTTP_LATENCY_BUCKETS,
|
|
7
|
+
get_route,
|
|
8
|
+
should_filter_route,
|
|
9
|
+
)
|
|
5
10
|
|
|
6
11
|
MAX_REQUEST_COUNT_ENTRIES = 5000
|
|
7
12
|
MAX_HISTOGRAM_ENTRIES = 1000
|
|
8
13
|
|
|
9
14
|
|
|
10
|
-
def get_route(route: Any) -> str | None:
|
|
11
|
-
try:
|
|
12
|
-
# default lg api routes use the custom APIRoute where scope["route"] is set to a string
|
|
13
|
-
if isinstance(route, str):
|
|
14
|
-
return route
|
|
15
|
-
else:
|
|
16
|
-
# custom FastAPI routes provided by user_router attach an object to scope["route"]
|
|
17
|
-
route_path = getattr(route, "path", None)
|
|
18
|
-
return route_path
|
|
19
|
-
except Exception:
|
|
20
|
-
return None
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def should_filter_route(route_path: str) -> bool:
|
|
24
|
-
# use endswith to honor MOUNT_PREFIX
|
|
25
|
-
return any(route_path.endswith(suffix) for suffix in FILTERED_ROUTES)
|
|
26
|
-
|
|
27
|
-
|
|
28
15
|
class HTTPMetricsCollector:
|
|
29
16
|
def __init__(self):
|
|
30
17
|
# Counter: Key: (method, route, status), Value: count
|
|
31
18
|
self._request_counts: dict[tuple[str, str, int], int] = defaultdict(int)
|
|
32
19
|
|
|
33
|
-
self._histogram_buckets =
|
|
34
|
-
0.01,
|
|
35
|
-
0.1,
|
|
36
|
-
0.5,
|
|
37
|
-
1,
|
|
38
|
-
5,
|
|
39
|
-
15,
|
|
40
|
-
30,
|
|
41
|
-
60,
|
|
42
|
-
120,
|
|
43
|
-
300,
|
|
44
|
-
600,
|
|
45
|
-
1800,
|
|
46
|
-
3600,
|
|
47
|
-
float("inf"),
|
|
48
|
-
]
|
|
20
|
+
self._histogram_buckets = HTTP_LATENCY_BUCKETS
|
|
49
21
|
self._histogram_bucket_labels = [
|
|
50
22
|
"+Inf" if value == float("inf") else str(value)
|
|
51
23
|
for value in self._histogram_buckets
|
|
@@ -97,6 +69,14 @@ class HTTPMetricsCollector:
|
|
|
97
69
|
hist_data["sum"] += latency_seconds
|
|
98
70
|
hist_data["count"] += 1
|
|
99
71
|
|
|
72
|
+
try:
|
|
73
|
+
if config.LANGGRAPH_METRICS_ENABLED:
|
|
74
|
+
from langgraph_api.self_hosted_metrics import record_http_request
|
|
75
|
+
|
|
76
|
+
record_http_request(method, route_path, status, latency_seconds)
|
|
77
|
+
except Exception:
|
|
78
|
+
pass
|
|
79
|
+
|
|
100
80
|
def get_metrics(
|
|
101
81
|
self,
|
|
102
82
|
project_id: str | None,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
FILTERED_ROUTES = {"/ok", "/info", "/metrics", "/docs", "/openapi.json"}
|
|
4
|
+
|
|
5
|
+
HTTP_LATENCY_BUCKETS = [
|
|
6
|
+
0.01,
|
|
7
|
+
0.1,
|
|
8
|
+
0.5,
|
|
9
|
+
1,
|
|
10
|
+
5,
|
|
11
|
+
15,
|
|
12
|
+
30,
|
|
13
|
+
60,
|
|
14
|
+
120,
|
|
15
|
+
300,
|
|
16
|
+
600,
|
|
17
|
+
1800,
|
|
18
|
+
3600,
|
|
19
|
+
float("inf"),
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_route(route: Any) -> str | None:
|
|
24
|
+
try:
|
|
25
|
+
# default lg api routes use the custom APIRoute where scope["route"] is set to a string
|
|
26
|
+
if isinstance(route, str):
|
|
27
|
+
return route
|
|
28
|
+
else:
|
|
29
|
+
# custom FastAPI routes provided by user_router attach an object to scope["route"]
|
|
30
|
+
route_path = getattr(route, "path", None)
|
|
31
|
+
return route_path
|
|
32
|
+
except Exception:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def should_filter_route(route_path: str) -> bool:
|
|
37
|
+
# use endswith to honor MOUNT_PREFIX
|
|
38
|
+
return any(route_path.endswith(suffix) for suffix in FILTERED_ROUTES)
|
langgraph_api/js/build.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="./global.d.ts" />
|
|
2
2
|
import "./src/preload.mjs";
|
|
3
3
|
|
|
4
|
-
import { z } from "zod";
|
|
4
|
+
import { z } from "zod/v3";
|
|
5
5
|
import * as fs from "node:fs/promises";
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import { type GraphSchema, resolveGraph } from "./src/graph.mts";
|
langgraph_api/js/client.http.mts
CHANGED
|
@@ -6,7 +6,7 @@ import * as path from "node:path";
|
|
|
6
6
|
import * as url from "node:url";
|
|
7
7
|
import { createLogger, format, transports } from "winston";
|
|
8
8
|
import { gracefulExit } from "exit-hook";
|
|
9
|
-
import { z } from "zod";
|
|
9
|
+
import { z } from "zod/v3";
|
|
10
10
|
|
|
11
11
|
const logger = createLogger({
|
|
12
12
|
level: "debug",
|
|
@@ -47,7 +47,9 @@ const wrapHonoApp = (app: Hono) => {
|
|
|
47
47
|
// b/c the user's Hono version might be different than ours.
|
|
48
48
|
// See warning here: https://hono.dev/docs/guides/middleware#built-in-middleware
|
|
49
49
|
const newApp = new (Object.getPrototypeOf(app).constructor)() as Hono<{
|
|
50
|
-
Variables: {
|
|
50
|
+
Variables: {
|
|
51
|
+
"langgraph:body": string | ArrayBuffer | ReadableStream | null;
|
|
52
|
+
};
|
|
51
53
|
}>;
|
|
52
54
|
|
|
53
55
|
// This endpoint is used to check if we can yield the routing to the Python server early.
|
|
@@ -71,11 +73,15 @@ const wrapHonoApp = (app: Hono) => {
|
|
|
71
73
|
newApp.notFound(async (c) => {
|
|
72
74
|
// Send the request body back to the Python server
|
|
73
75
|
// Use the cached body in-case the user mutated the body
|
|
74
|
-
let payload:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
let payload: string | ArrayBuffer | ReadableStream | null =
|
|
77
|
+
c.get("langgraph:body");
|
|
78
|
+
|
|
79
|
+
if (payload == null) {
|
|
80
|
+
try {
|
|
81
|
+
payload = JSON.stringify(await c.req.json()) ?? null;
|
|
82
|
+
} catch {
|
|
83
|
+
// pass
|
|
84
|
+
}
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
return c.body(payload, {
|
langgraph_api/js/client.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="./global.d.ts" />
|
|
2
2
|
|
|
3
|
-
import { z } from "zod";
|
|
3
|
+
import { z } from "zod/v3";
|
|
4
4
|
import { Context, Hono } from "hono";
|
|
5
5
|
import { serve } from "@hono/node-server";
|
|
6
6
|
import { zValidator } from "@hono/zod-validator";
|
|
@@ -527,10 +527,7 @@ export class RemoteStore extends BaseStore {
|
|
|
527
527
|
}
|
|
528
528
|
|
|
529
529
|
async get(namespace: string[], key: string): Promise<Item | null> {
|
|
530
|
-
return await sendRecv<Item | null>("store_get", {
|
|
531
|
-
namespace: namespace.join("."),
|
|
532
|
-
key,
|
|
533
|
-
});
|
|
530
|
+
return await sendRecv<Item | null>("store_get", { namespace, key });
|
|
534
531
|
}
|
|
535
532
|
|
|
536
533
|
async search(
|
langgraph_api/js/package.json
CHANGED
|
@@ -7,41 +7,42 @@
|
|
|
7
7
|
"format": "prettier --write ."
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@hono/node-server": "^1.
|
|
11
|
-
"@hono/zod-validator": "^0.
|
|
12
|
-
"@langchain/core": "^
|
|
13
|
-
"@langchain/langgraph": "^
|
|
14
|
-
"@langchain/langgraph-api": "
|
|
15
|
-
"@langchain/langgraph-
|
|
16
|
-
"@langchain/langgraph-
|
|
10
|
+
"@hono/node-server": "^1.19.8",
|
|
11
|
+
"@hono/zod-validator": "^0.7.6",
|
|
12
|
+
"@langchain/core": "^1.1.8",
|
|
13
|
+
"@langchain/langgraph": "^1.1.0",
|
|
14
|
+
"@langchain/langgraph-api": "^1.0.3",
|
|
15
|
+
"@langchain/langgraph-checkpoint": "^1.0.0",
|
|
16
|
+
"@langchain/langgraph-ui": "^1.1.11",
|
|
17
17
|
"@types/json-schema": "^7.0.15",
|
|
18
|
-
"@typescript/vfs": "^1.6.
|
|
19
|
-
"dedent": "^1.
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"p-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
18
|
+
"@typescript/vfs": "^1.6.2",
|
|
19
|
+
"dedent": "^1.7.1",
|
|
20
|
+
"esbuild": "^0.27.2",
|
|
21
|
+
"exit-hook": "^5.0.1",
|
|
22
|
+
"hono": "^4.11.4",
|
|
23
|
+
"p-queue": "^9.0.1",
|
|
24
|
+
"p-retry": "^7.1.1",
|
|
25
|
+
"tsx": "^4.21.0",
|
|
26
|
+
"typescript": "^5.9.3",
|
|
27
|
+
"undici": "^7.18.2",
|
|
28
|
+
"uuid": "^13.0.0",
|
|
29
|
+
"vite": "^7.3.0",
|
|
30
|
+
"winston": "^3.19.0",
|
|
31
|
+
"zod": "^4.2.1"
|
|
31
32
|
},
|
|
32
33
|
"resolutions": {
|
|
33
34
|
"esbuild": "^0.25.0",
|
|
34
35
|
"vite": "^6.1.6"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
|
-
"@langchain/langgraph-sdk": "^
|
|
38
|
-
"@types/node": "^
|
|
39
|
-
"@types/react": "^19.
|
|
40
|
-
"@types/react-dom": "^19.
|
|
41
|
-
"jose": "^6.
|
|
42
|
-
"postgres": "^3.4.
|
|
43
|
-
"prettier": "^3.
|
|
44
|
-
"vitest": "^
|
|
38
|
+
"@langchain/langgraph-sdk": "^1.5.4",
|
|
39
|
+
"@types/node": "^25.0.9",
|
|
40
|
+
"@types/react": "^19.2.7",
|
|
41
|
+
"@types/react-dom": "^19.2.3",
|
|
42
|
+
"jose": "^6.1.3",
|
|
43
|
+
"postgres": "^3.4.7",
|
|
44
|
+
"prettier": "^3.7.4",
|
|
45
|
+
"vitest": "^4.0.16"
|
|
45
46
|
},
|
|
46
47
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
47
48
|
}
|
langgraph_api/js/remote.py
CHANGED
|
@@ -15,7 +15,7 @@ import httpx
|
|
|
15
15
|
import orjson
|
|
16
16
|
import structlog
|
|
17
17
|
import uvicorn
|
|
18
|
-
from langchain_core.runnables.config import RunnableConfig
|
|
18
|
+
from langchain_core.runnables.config import RunnableConfig, merge_configs
|
|
19
19
|
from langchain_core.runnables.graph import Edge, Node
|
|
20
20
|
from langchain_core.runnables.graph import Graph as DrawableGraph
|
|
21
21
|
from langchain_core.runnables.schema import (
|
|
@@ -49,6 +49,7 @@ from langgraph_api.js.sse import SSEDecoder, aiter_lines_raw
|
|
|
49
49
|
from langgraph_api.route import ApiResponse
|
|
50
50
|
from langgraph_api.schema import Config
|
|
51
51
|
from langgraph_api.serde import json_dumpb
|
|
52
|
+
from langgraph_api.utils import get_auth_ctx, get_user_id
|
|
52
53
|
|
|
53
54
|
logger = structlog.stdlib.get_logger(__name__)
|
|
54
55
|
|
|
@@ -153,9 +154,9 @@ class RemotePregel(BaseRemotePregel):
|
|
|
153
154
|
|
|
154
155
|
async for event in _client_stream("streamEvents", data):
|
|
155
156
|
if event["event"] == "on_custom_event":
|
|
156
|
-
yield CustomStreamEvent(**event)
|
|
157
|
+
yield CustomStreamEvent(**event) # type: ignore[missing-typed-dict-key]
|
|
157
158
|
else:
|
|
158
|
-
yield StandardStreamEvent(**event)
|
|
159
|
+
yield StandardStreamEvent(**event) # type: ignore[missing-typed-dict-key]
|
|
159
160
|
|
|
160
161
|
async def fetch_state_schema(self):
|
|
161
162
|
return await _client_invoke("getSchema", {"graph_id": self.graph_id})
|
|
@@ -170,9 +171,9 @@ class RemotePregel(BaseRemotePregel):
|
|
|
170
171
|
"getGraph",
|
|
171
172
|
{
|
|
172
173
|
"graph_id": self.graph_id,
|
|
173
|
-
"graph_config": self.config,
|
|
174
|
+
"graph_config": self._inject_auth_to_config(self.config),
|
|
174
175
|
"graph_name": self.name,
|
|
175
|
-
"config": config,
|
|
176
|
+
"config": self._inject_auth_to_config(config),
|
|
176
177
|
"xray": xray,
|
|
177
178
|
},
|
|
178
179
|
)
|
|
@@ -187,15 +188,17 @@ class RemotePregel(BaseRemotePregel):
|
|
|
187
188
|
)
|
|
188
189
|
for data in nodes
|
|
189
190
|
},
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
191
|
+
list(
|
|
192
|
+
{
|
|
193
|
+
Edge(
|
|
194
|
+
data["source"],
|
|
195
|
+
data["target"],
|
|
196
|
+
data.get("data"),
|
|
197
|
+
data.get("conditional", False),
|
|
198
|
+
)
|
|
199
|
+
for data in edges
|
|
200
|
+
}
|
|
201
|
+
),
|
|
199
202
|
)
|
|
200
203
|
|
|
201
204
|
async def fetch_subgraphs(
|
|
@@ -209,11 +212,11 @@ class RemotePregel(BaseRemotePregel):
|
|
|
209
212
|
"getSubgraphs",
|
|
210
213
|
{
|
|
211
214
|
"graph_id": self.graph_id,
|
|
212
|
-
"graph_config": self.config,
|
|
215
|
+
"graph_config": self._inject_auth_to_config(self.config),
|
|
213
216
|
"graph_name": self.name,
|
|
214
217
|
"namespace": namespace,
|
|
215
218
|
"recurse": recurse,
|
|
216
|
-
"config": config,
|
|
219
|
+
"config": self._inject_auth_to_config(config),
|
|
217
220
|
},
|
|
218
221
|
)
|
|
219
222
|
|
|
@@ -237,7 +240,7 @@ class RemotePregel(BaseRemotePregel):
|
|
|
237
240
|
tuple(task["path"]) if task.get("path") else tuple(),
|
|
238
241
|
# TODO: figure out how to properly deserialise errors
|
|
239
242
|
task.get("error"),
|
|
240
|
-
tuple(interrupts),
|
|
243
|
+
tuple(interrupts), # type: ignore[arg-type]
|
|
241
244
|
state,
|
|
242
245
|
task.get("result"),
|
|
243
246
|
)
|
|
@@ -246,7 +249,7 @@ class RemotePregel(BaseRemotePregel):
|
|
|
246
249
|
|
|
247
250
|
return StateSnapshot( # type: ignore[missing-argument]
|
|
248
251
|
item.get("values"),
|
|
249
|
-
cast(tuple, item.get("next", ())),
|
|
252
|
+
cast("tuple", item.get("next", ())),
|
|
250
253
|
item.get("config"),
|
|
251
254
|
item.get("metadata"),
|
|
252
255
|
item.get("createdAt"),
|
|
@@ -264,9 +267,9 @@ class RemotePregel(BaseRemotePregel):
|
|
|
264
267
|
"getState",
|
|
265
268
|
{
|
|
266
269
|
"graph_id": self.graph_id,
|
|
267
|
-
"graph_config": self.config,
|
|
270
|
+
"graph_config": self._inject_auth_to_config(self.config),
|
|
268
271
|
"graph_name": self.name,
|
|
269
|
-
"config": config,
|
|
272
|
+
"config": self._inject_auth_to_config(config),
|
|
270
273
|
"subgraphs": subgraphs,
|
|
271
274
|
},
|
|
272
275
|
)
|
|
@@ -282,9 +285,9 @@ class RemotePregel(BaseRemotePregel):
|
|
|
282
285
|
"updateState",
|
|
283
286
|
{
|
|
284
287
|
"graph_id": self.graph_id,
|
|
285
|
-
"graph_config": self.config,
|
|
288
|
+
"graph_config": self._inject_auth_to_config(self.config),
|
|
286
289
|
"graph_name": self.name,
|
|
287
|
-
"config": config,
|
|
290
|
+
"config": self._inject_auth_to_config(config),
|
|
288
291
|
"values": values,
|
|
289
292
|
"as_node": as_node,
|
|
290
293
|
},
|
|
@@ -303,9 +306,9 @@ class RemotePregel(BaseRemotePregel):
|
|
|
303
306
|
"getStateHistory",
|
|
304
307
|
{
|
|
305
308
|
"graph_id": self.graph_id,
|
|
306
|
-
"graph_config": self.config,
|
|
309
|
+
"graph_config": self._inject_auth_to_config(self.config),
|
|
307
310
|
"graph_name": self.name,
|
|
308
|
-
"config": config,
|
|
311
|
+
"config": self._inject_auth_to_config(config),
|
|
309
312
|
"limit": limit,
|
|
310
313
|
"filter": filter,
|
|
311
314
|
"before": before,
|
|
@@ -351,6 +354,27 @@ class RemotePregel(BaseRemotePregel):
|
|
|
351
354
|
)
|
|
352
355
|
return result["nodesExecuted"]
|
|
353
356
|
|
|
357
|
+
def _inject_auth_to_config(self, config: RunnableConfig | Config):
|
|
358
|
+
if ctx := get_auth_ctx():
|
|
359
|
+
user_id = get_user_id(cast("BaseUser | None", ctx.user))
|
|
360
|
+
|
|
361
|
+
# Skip if cannot serialize the user to JSON
|
|
362
|
+
if not hasattr(ctx.user, "model_dump"):
|
|
363
|
+
return config
|
|
364
|
+
|
|
365
|
+
return merge_configs(
|
|
366
|
+
config,
|
|
367
|
+
{
|
|
368
|
+
"configurable": {
|
|
369
|
+
"langgraph_auth_user": ctx.user,
|
|
370
|
+
"langgraph_auth_user_id": user_id,
|
|
371
|
+
"langgraph_auth_permissions": list(ctx.permissions),
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
return config
|
|
377
|
+
|
|
354
378
|
|
|
355
379
|
async def run_js_process(paths_str: str | None, watch: bool = False):
|
|
356
380
|
# check if tsx is available
|
|
@@ -630,13 +654,13 @@ async def run_remote_checkpointer():
|
|
|
630
654
|
|
|
631
655
|
async def store_get(payload: dict):
|
|
632
656
|
"""Get store data"""
|
|
633
|
-
|
|
657
|
+
namespaces = payload.get("namespace")
|
|
634
658
|
key = payload.get("key")
|
|
635
659
|
|
|
636
|
-
if not
|
|
660
|
+
if not namespaces or not key:
|
|
637
661
|
raise ValueError("Both namespaces and key are required")
|
|
638
662
|
|
|
639
|
-
namespaces =
|
|
663
|
+
namespaces = tuple(namespaces)
|
|
640
664
|
|
|
641
665
|
store = await _get_passthrough_store()
|
|
642
666
|
result = await store.aget(namespaces, key)
|
|
@@ -646,7 +670,7 @@ async def run_remote_checkpointer():
|
|
|
646
670
|
async def store_put(payload: dict):
|
|
647
671
|
"""Put the new store data"""
|
|
648
672
|
|
|
649
|
-
namespace = tuple(payload["namespace"]
|
|
673
|
+
namespace = tuple(payload["namespace"])
|
|
650
674
|
key = payload["key"]
|
|
651
675
|
value = payload["value"]
|
|
652
676
|
index = payload.get("index")
|
|
@@ -861,6 +885,8 @@ class CustomJsAuthBackend(AuthenticationBackend):
|
|
|
861
885
|
self.ls_auth = LangsmithAuthBackend()
|
|
862
886
|
self.ttl_cache: LRUCache | None = None
|
|
863
887
|
self.cache_keys: list[str] | None = None
|
|
888
|
+
if LANGGRAPH_AUTH is None:
|
|
889
|
+
raise ValueError("LANGGRAPH_AUTH is not set")
|
|
864
890
|
if cache := LANGGRAPH_AUTH.get("cache"):
|
|
865
891
|
keys = cache.get("cache_keys", [])
|
|
866
892
|
if not isinstance(keys, list):
|
|
@@ -891,7 +917,7 @@ class CustomJsAuthBackend(AuthenticationBackend):
|
|
|
891
917
|
if self.cache_keys:
|
|
892
918
|
cache_key = tuple((k, headers[k]) for k in self.cache_keys if k in headers)
|
|
893
919
|
if cache_key and self.ttl_cache is not None:
|
|
894
|
-
cached = self.ttl_cache.get(cache_key)
|
|
920
|
+
cached = await self.ttl_cache.get(cache_key)
|
|
895
921
|
if cached:
|
|
896
922
|
return cached
|
|
897
923
|
|
|
@@ -954,7 +980,7 @@ async def handle_js_auth_event(
|
|
|
954
980
|
|
|
955
981
|
raise HTTPException(status_code=status, detail=message, headers=headers)
|
|
956
982
|
|
|
957
|
-
filters = cast(Auth.types.FilterType | None, response.get("filters"))
|
|
983
|
+
filters = cast("Auth.types.FilterType | None", response.get("filters"))
|
|
958
984
|
|
|
959
985
|
# mutate metadata in value if applicable
|
|
960
986
|
# we need to preserve the identity of the object, so cannot create a new
|
langgraph_api/js/src/graph.mts
CHANGED
|
@@ -62,6 +62,17 @@ export async function resolveGraph(
|
|
|
62
62
|
return "compile" in graph && typeof graph.compile === "function";
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
+
const isCompiledGraph = (
|
|
66
|
+
graph: GraphLike,
|
|
67
|
+
): graph is CompiledGraph<string> => {
|
|
68
|
+
if (typeof graph !== "object" || graph == null) return false;
|
|
69
|
+
return (
|
|
70
|
+
"builder" in graph &&
|
|
71
|
+
typeof graph.builder === "object" &&
|
|
72
|
+
graph.builder != null
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
65
76
|
const graph: GraphUnknown = await import(sourceFile).then(
|
|
66
77
|
(module) => module[exportSymbol || "default"],
|
|
67
78
|
);
|
|
@@ -73,6 +84,15 @@ export async function resolveGraph(
|
|
|
73
84
|
|
|
74
85
|
const afterResolve = (graphLike: GraphLike): CompiledGraph<string> => {
|
|
75
86
|
const graph = isGraph(graphLike) ? graphLike.compile() : graphLike;
|
|
87
|
+
|
|
88
|
+
// TODO: hack, remove once LangChain 1.x createAgent is fixed
|
|
89
|
+
// LangGraph API will assign it's checkpointer by setting it
|
|
90
|
+
// via `graph.checkpointer = ...` and `graph.store = ...`, and the 1.x `createAgent`
|
|
91
|
+
// hides the underlying `StateGraph` instance, so we need to access it directly.
|
|
92
|
+
if (!isCompiledGraph(graph) && "graph" in graph) {
|
|
93
|
+
return (graph as { graph: CompiledGraph<string> }).graph;
|
|
94
|
+
}
|
|
95
|
+
|
|
76
96
|
return graph;
|
|
77
97
|
};
|
|
78
98
|
|
langgraph_api/js/sse.py
CHANGED
|
@@ -51,7 +51,7 @@ class BytesLineDecoder:
|
|
|
51
51
|
# Include any existing buffer in the first portion of the
|
|
52
52
|
# splitlines result.
|
|
53
53
|
self.buffer.extend(lines[0])
|
|
54
|
-
lines = [self.buffer
|
|
54
|
+
lines = [self.buffer, *lines[1:]]
|
|
55
55
|
self.buffer = bytearray()
|
|
56
56
|
|
|
57
57
|
if not trailing_newline:
|
|
@@ -79,7 +79,7 @@ class SSEDecoder:
|
|
|
79
79
|
self._retry: int | None = None
|
|
80
80
|
|
|
81
81
|
def decode(self, line: bytes) -> StreamPart | None:
|
|
82
|
-
# See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation
|
|
82
|
+
# See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation
|
|
83
83
|
|
|
84
84
|
if not line:
|
|
85
85
|
if (
|