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/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.7.3"
|
langgraph_api/api/__init__.py
CHANGED
|
@@ -6,40 +6,81 @@ import os
|
|
|
6
6
|
|
|
7
7
|
import structlog
|
|
8
8
|
from starlette.applications import Starlette
|
|
9
|
+
from starlette.exceptions import HTTPException
|
|
10
|
+
from starlette.middleware import Middleware
|
|
9
11
|
from starlette.requests import Request
|
|
10
12
|
from starlette.responses import HTMLResponse, JSONResponse, Response
|
|
11
|
-
from starlette.routing import BaseRoute,
|
|
13
|
+
from starlette.routing import BaseRoute, Route
|
|
12
14
|
|
|
15
|
+
from langgraph_api import timing
|
|
16
|
+
from langgraph_api.api.a2a import a2a_routes
|
|
13
17
|
from langgraph_api.api.assistants import assistants_routes
|
|
14
18
|
from langgraph_api.api.mcp import mcp_routes
|
|
15
19
|
from langgraph_api.api.meta import meta_info, meta_metrics
|
|
16
20
|
from langgraph_api.api.openapi import get_openapi_spec
|
|
21
|
+
from langgraph_api.api.profile import profile_routes
|
|
17
22
|
from langgraph_api.api.runs import runs_routes
|
|
18
23
|
from langgraph_api.api.store import store_routes
|
|
19
24
|
from langgraph_api.api.threads import threads_routes
|
|
20
25
|
from langgraph_api.api.ui import ui_routes
|
|
21
26
|
from langgraph_api.auth.middleware import auth_middleware
|
|
22
|
-
from langgraph_api.config import
|
|
27
|
+
from langgraph_api.config import (
|
|
28
|
+
FF_PYSPY_PROFILING_ENABLED,
|
|
29
|
+
HTTP_CONFIG,
|
|
30
|
+
LANGGRAPH_ENCRYPTION,
|
|
31
|
+
MIGRATIONS_PATH,
|
|
32
|
+
MOUNT_PREFIX,
|
|
33
|
+
)
|
|
34
|
+
from langgraph_api.feature_flags import IS_POSTGRES_OR_GRPC_BACKEND
|
|
23
35
|
from langgraph_api.graph import js_bg_tasks
|
|
36
|
+
from langgraph_api.grpc.client import get_shared_client
|
|
24
37
|
from langgraph_api.js.base import is_js_path
|
|
38
|
+
from langgraph_api.timing import profiled_import
|
|
25
39
|
from langgraph_api.validation import DOCS_HTML
|
|
26
|
-
from langgraph_runtime.database import
|
|
40
|
+
from langgraph_runtime.database import healthcheck
|
|
27
41
|
|
|
28
42
|
logger = structlog.stdlib.get_logger(__name__)
|
|
29
43
|
|
|
30
44
|
|
|
45
|
+
async def grpc_healthcheck():
|
|
46
|
+
"""Check the health of the gRPC server."""
|
|
47
|
+
try:
|
|
48
|
+
client = await get_shared_client()
|
|
49
|
+
await client.healthcheck()
|
|
50
|
+
except Exception as exc:
|
|
51
|
+
logger.warning(
|
|
52
|
+
"gRPC health check failed. Either the gRPC server is not running or is not responding.",
|
|
53
|
+
error=exc,
|
|
54
|
+
)
|
|
55
|
+
raise HTTPException(
|
|
56
|
+
status_code=500,
|
|
57
|
+
detail="gRPC health check failed. Either the gRPC server is not running or is not responding.",
|
|
58
|
+
) from exc
|
|
59
|
+
|
|
60
|
+
|
|
31
61
|
async def ok(request: Request, *, disabled: bool = False):
|
|
32
62
|
if disabled:
|
|
33
63
|
# We still expose an /ok endpoint even if disable_meta is set so that
|
|
34
64
|
# the operator knows the server started up.
|
|
35
65
|
return JSONResponse({"ok": True})
|
|
36
66
|
check_db = int(request.query_params.get("check_db", "0")) # must be "0" or "1"
|
|
67
|
+
|
|
68
|
+
healthcheck_coroutines = []
|
|
69
|
+
|
|
37
70
|
if check_db:
|
|
38
|
-
|
|
71
|
+
healthcheck_coroutines.append(healthcheck())
|
|
72
|
+
|
|
39
73
|
if js_bg_tasks:
|
|
40
74
|
from langgraph_api.js.remote import js_healthcheck
|
|
41
75
|
|
|
42
|
-
|
|
76
|
+
healthcheck_coroutines.append(js_healthcheck())
|
|
77
|
+
|
|
78
|
+
# Check `core-api` server health
|
|
79
|
+
if IS_POSTGRES_OR_GRPC_BACKEND:
|
|
80
|
+
healthcheck_coroutines.append(grpc_healthcheck())
|
|
81
|
+
|
|
82
|
+
await asyncio.gather(*healthcheck_coroutines)
|
|
83
|
+
|
|
43
84
|
return JSONResponse({"ok": True})
|
|
44
85
|
|
|
45
86
|
|
|
@@ -52,14 +93,25 @@ async def docs(request: Request):
|
|
|
52
93
|
return HTMLResponse(DOCS_HTML.format(mount_prefix=MOUNT_PREFIX or ""))
|
|
53
94
|
|
|
54
95
|
|
|
55
|
-
|
|
56
|
-
Route("/
|
|
57
|
-
Route("/openapi.json", openapi, methods=["GET"]),
|
|
58
|
-
Route("/docs", docs, methods=["GET"]),
|
|
96
|
+
shadowable_meta_routes: list[BaseRoute] = [
|
|
97
|
+
Route("/", ok, methods=["GET"]), # Root health check for load balancers
|
|
59
98
|
Route("/info", meta_info, methods=["GET"]),
|
|
99
|
+
]
|
|
100
|
+
unshadowable_meta_routes: list[BaseRoute] = [
|
|
101
|
+
Route("/ok", ok, methods=["GET"]),
|
|
60
102
|
Route("/metrics", meta_metrics, methods=["GET"]),
|
|
103
|
+
Route("/docs", docs, methods=["GET"]),
|
|
104
|
+
Route("/openapi.json", openapi, methods=["GET"]),
|
|
61
105
|
]
|
|
62
106
|
|
|
107
|
+
middleware_for_protected_routes = [auth_middleware]
|
|
108
|
+
|
|
109
|
+
# Add encryption context middleware if encryption is configured
|
|
110
|
+
if LANGGRAPH_ENCRYPTION:
|
|
111
|
+
from langgraph_api.encryption.middleware import EncryptionContextMiddleware
|
|
112
|
+
|
|
113
|
+
middleware_for_protected_routes.append(Middleware(EncryptionContextMiddleware))
|
|
114
|
+
|
|
63
115
|
protected_routes: list[BaseRoute] = []
|
|
64
116
|
|
|
65
117
|
if HTTP_CONFIG:
|
|
@@ -71,25 +123,43 @@ if HTTP_CONFIG:
|
|
|
71
123
|
protected_routes.extend(threads_routes)
|
|
72
124
|
if not HTTP_CONFIG.get("disable_store"):
|
|
73
125
|
protected_routes.extend(store_routes)
|
|
126
|
+
if FF_PYSPY_PROFILING_ENABLED:
|
|
127
|
+
protected_routes.extend(profile_routes)
|
|
74
128
|
if not HTTP_CONFIG.get("disable_ui"):
|
|
75
129
|
protected_routes.extend(ui_routes)
|
|
76
130
|
if not HTTP_CONFIG.get("disable_mcp"):
|
|
77
131
|
protected_routes.extend(mcp_routes)
|
|
132
|
+
if not HTTP_CONFIG.get("disable_a2a"):
|
|
133
|
+
protected_routes.extend(a2a_routes)
|
|
78
134
|
else:
|
|
79
135
|
protected_routes.extend(assistants_routes)
|
|
80
136
|
protected_routes.extend(runs_routes)
|
|
81
137
|
protected_routes.extend(threads_routes)
|
|
82
138
|
protected_routes.extend(store_routes)
|
|
139
|
+
if FF_PYSPY_PROFILING_ENABLED:
|
|
140
|
+
protected_routes.extend(profile_routes)
|
|
83
141
|
protected_routes.extend(ui_routes)
|
|
84
142
|
protected_routes.extend(mcp_routes)
|
|
143
|
+
protected_routes.extend(a2a_routes)
|
|
85
144
|
|
|
86
|
-
routes: list[BaseRoute] = []
|
|
87
|
-
user_router = None
|
|
88
145
|
|
|
146
|
+
def _metadata_fn(app_import: str) -> dict[str, str]:
|
|
147
|
+
return {"app": app_import}
|
|
89
148
|
|
|
149
|
+
|
|
150
|
+
@timing.timer(
|
|
151
|
+
message="Loaded custom app from {app}",
|
|
152
|
+
metadata_fn=_metadata_fn,
|
|
153
|
+
warn_threshold_secs=3,
|
|
154
|
+
warn_message=(
|
|
155
|
+
"Import for custom app {app} exceeded the expected startup time. "
|
|
156
|
+
"Slow initialization (often due to work executed at import time) can delay readiness, "
|
|
157
|
+
"reduce scale-out capacity, and may cause deployments to be marked unhealthy."
|
|
158
|
+
),
|
|
159
|
+
error_threshold_secs=30,
|
|
160
|
+
)
|
|
90
161
|
def load_custom_app(app_import: str) -> Starlette | None:
|
|
91
162
|
# Expect a string in either "path/to/file.py:my_variable" or "some.module.in:my_variable"
|
|
92
|
-
logger.info(f"Loading custom app from {app_import}")
|
|
93
163
|
path, name = app_import.rsplit(":", 1)
|
|
94
164
|
|
|
95
165
|
# skip loading custom app if it's a js path
|
|
@@ -99,16 +169,19 @@ def load_custom_app(app_import: str) -> Starlette | None:
|
|
|
99
169
|
|
|
100
170
|
try:
|
|
101
171
|
os.environ["__LANGGRAPH_DEFER_LOOPBACK_TRANSPORT"] = "true"
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
172
|
+
with profiled_import(app_import):
|
|
173
|
+
if os.path.isfile(path) or path.endswith(".py"):
|
|
174
|
+
# Import from file path using a unique module name.
|
|
175
|
+
spec = importlib.util.spec_from_file_location(
|
|
176
|
+
"user_router_module", path
|
|
177
|
+
)
|
|
178
|
+
if spec is None or spec.loader is None:
|
|
179
|
+
raise ImportError(f"Cannot load spec from {path}")
|
|
180
|
+
module = importlib.util.module_from_spec(spec)
|
|
181
|
+
spec.loader.exec_module(module)
|
|
182
|
+
else:
|
|
183
|
+
# Import as a normal module.
|
|
184
|
+
module = importlib.import_module(path)
|
|
112
185
|
user_router = getattr(module, name)
|
|
113
186
|
if not isinstance(user_router, Starlette):
|
|
114
187
|
raise TypeError(
|
|
@@ -126,40 +199,27 @@ def load_custom_app(app_import: str) -> Starlette | None:
|
|
|
126
199
|
return user_router
|
|
127
200
|
|
|
128
201
|
|
|
202
|
+
user_router: Starlette | None = None
|
|
129
203
|
if HTTP_CONFIG:
|
|
130
|
-
if
|
|
131
|
-
|
|
132
|
-
if not HTTP_CONFIG.get("disable_meta"):
|
|
133
|
-
routes.extend(meta_routes)
|
|
134
|
-
else:
|
|
135
|
-
# Otherwise the deployment will never be considered healthy
|
|
136
|
-
routes.append(
|
|
204
|
+
if HTTP_CONFIG.get("disable_meta"):
|
|
205
|
+
shadowable_meta_routes = [
|
|
137
206
|
Route(
|
|
138
207
|
"/ok", functools.partial(ok, disabled=True), methods=["GET"], name="ok"
|
|
139
208
|
)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
routes.append(
|
|
143
|
-
Mount(
|
|
144
|
-
"/",
|
|
145
|
-
middleware=[auth_middleware],
|
|
146
|
-
routes=protected_routes,
|
|
147
|
-
),
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
else:
|
|
151
|
-
routes.extend(meta_routes)
|
|
152
|
-
routes.append(Mount("/", middleware=[auth_middleware], routes=protected_routes))
|
|
153
|
-
|
|
209
|
+
]
|
|
210
|
+
unshadowable_meta_routes = []
|
|
154
211
|
|
|
155
|
-
if "
|
|
212
|
+
if router_import := HTTP_CONFIG.get("app"):
|
|
213
|
+
user_router = load_custom_app(router_import)
|
|
214
|
+
if user_router:
|
|
215
|
+
user_router.router.lifespan_context = timing.wrap_lifespan_context_aenter(
|
|
216
|
+
user_router.router.lifespan_context,
|
|
217
|
+
)
|
|
156
218
|
|
|
157
|
-
async def truncate(request: Request):
|
|
158
|
-
from langgraph_runtime.checkpoint import Checkpointer
|
|
159
219
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
await asyncio.to_thread(conn.clear)
|
|
163
|
-
return JSONResponse({"ok": True})
|
|
220
|
+
if "__inmem" in MIGRATIONS_PATH:
|
|
221
|
+
from langgraph_runtime_inmem.routes import get_internal_routes
|
|
164
222
|
|
|
165
|
-
|
|
223
|
+
if get_internal_routes is not None:
|
|
224
|
+
for route in get_internal_routes():
|
|
225
|
+
unshadowable_meta_routes.insert(0, route)
|