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/api/openapi.py
CHANGED
|
@@ -5,12 +5,7 @@ from functools import lru_cache
|
|
|
5
5
|
|
|
6
6
|
import orjson
|
|
7
7
|
|
|
8
|
-
from langgraph_api
|
|
9
|
-
HTTP_CONFIG,
|
|
10
|
-
LANGGRAPH_AUTH,
|
|
11
|
-
LANGGRAPH_AUTH_TYPE,
|
|
12
|
-
MOUNT_PREFIX,
|
|
13
|
-
)
|
|
8
|
+
from langgraph_api import config
|
|
14
9
|
from langgraph_api.graph import GRAPHS
|
|
15
10
|
from langgraph_api.validation import openapi
|
|
16
11
|
|
|
@@ -39,17 +34,20 @@ def get_openapi_spec() -> bytes:
|
|
|
39
34
|
graph_ids
|
|
40
35
|
)
|
|
41
36
|
# patch the auth schemes
|
|
42
|
-
if LANGGRAPH_AUTH_TYPE == "langsmith":
|
|
37
|
+
if config.LANGGRAPH_AUTH_TYPE == "langsmith":
|
|
43
38
|
openapi["security"] = [
|
|
44
39
|
{"x-api-key": []},
|
|
45
40
|
]
|
|
46
41
|
openapi["components"]["securitySchemes"] = {
|
|
47
42
|
"x-api-key": {"type": "apiKey", "in": "header", "name": "x-api-key"}
|
|
48
43
|
}
|
|
49
|
-
if LANGGRAPH_AUTH:
|
|
44
|
+
if config.LANGGRAPH_AUTH:
|
|
50
45
|
# Allow user to specify OpenAPI security configuration
|
|
51
|
-
if
|
|
52
|
-
|
|
46
|
+
if (
|
|
47
|
+
isinstance(config.LANGGRAPH_AUTH, dict)
|
|
48
|
+
and "openapi" in config.LANGGRAPH_AUTH
|
|
49
|
+
):
|
|
50
|
+
openapi_config = config.LANGGRAPH_AUTH["openapi"]
|
|
53
51
|
if isinstance(openapi_config, dict):
|
|
54
52
|
# Add security schemes
|
|
55
53
|
if "securitySchemes" in openapi_config:
|
|
@@ -82,8 +80,13 @@ def get_openapi_spec() -> bytes:
|
|
|
82
80
|
)
|
|
83
81
|
|
|
84
82
|
# Remove webhook parameters if webhooks are disabled
|
|
85
|
-
if
|
|
86
|
-
webhook_schemas = [
|
|
83
|
+
if config.WEBHOOKS_ENABLED:
|
|
84
|
+
webhook_schemas = [
|
|
85
|
+
"CronCreate",
|
|
86
|
+
"ThreadCronCreate",
|
|
87
|
+
"RunCreateStateful",
|
|
88
|
+
"RunCreateStateless",
|
|
89
|
+
]
|
|
87
90
|
for schema_name in webhook_schemas:
|
|
88
91
|
if schema_name in openapi["components"]["schemas"]:
|
|
89
92
|
schema = openapi["components"]["schemas"][schema_name]
|
|
@@ -96,14 +99,21 @@ def get_openapi_spec() -> bytes:
|
|
|
96
99
|
final = openapi
|
|
97
100
|
if CUSTOM_OPENAPI_SPEC:
|
|
98
101
|
final = merge_openapi_specs(openapi, CUSTOM_OPENAPI_SPEC)
|
|
99
|
-
if MOUNT_PREFIX:
|
|
100
|
-
final["servers"] = [{"url": MOUNT_PREFIX}]
|
|
101
|
-
|
|
102
|
-
MCP_ENABLED = HTTP_CONFIG is None or not HTTP_CONFIG.get("disable_mcp")
|
|
102
|
+
if config.MOUNT_PREFIX:
|
|
103
|
+
final["servers"] = [{"url": config.MOUNT_PREFIX}]
|
|
103
104
|
|
|
104
|
-
if not MCP_ENABLED:
|
|
105
|
+
if not config.MCP_ENABLED:
|
|
105
106
|
# Remove the MCP paths from the OpenAPI spec
|
|
106
107
|
final["paths"].pop("/mcp/", None)
|
|
108
|
+
# Remove the MCP tag definition
|
|
109
|
+
final["tags"] = [t for t in final.get("tags", []) if t.get("name") != "MCP"]
|
|
110
|
+
|
|
111
|
+
if not config.A2A_ENABLED:
|
|
112
|
+
# Remove the A2A paths from the OpenAPI spec
|
|
113
|
+
final["paths"].pop("/a2a/{assistant_id}", None)
|
|
114
|
+
final["paths"].pop("/.well-known/agent-card.json", None)
|
|
115
|
+
# Remove the A2A tag definition
|
|
116
|
+
final["tags"] = [t for t in final.get("tags", []) if t.get("name") != "A2A"]
|
|
107
117
|
|
|
108
118
|
return orjson.dumps(final)
|
|
109
119
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import contextlib
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
import time
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
import structlog
|
|
11
|
+
from starlette.responses import JSONResponse, Response
|
|
12
|
+
from starlette.routing import BaseRoute
|
|
13
|
+
|
|
14
|
+
from langgraph_api import config
|
|
15
|
+
from langgraph_api.route import ApiRequest, ApiRoute
|
|
16
|
+
|
|
17
|
+
logger = structlog.stdlib.get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _clamp_duration(seconds: int) -> int:
|
|
21
|
+
seconds = max(1, seconds)
|
|
22
|
+
return min(seconds, max(1, config.FF_PYSPY_PROFILING_MAX_DURATION_SECS))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def _profile_with_pyspy(seconds: int, fmt: Literal["svg"]) -> Response:
|
|
26
|
+
"""Run py-spy against the current process for N seconds and return SVG."""
|
|
27
|
+
pyspy = shutil.which("py-spy")
|
|
28
|
+
if not pyspy:
|
|
29
|
+
return JSONResponse({"error": "py-spy not found on PATH"}, status_code=501)
|
|
30
|
+
|
|
31
|
+
# py-spy writes to a file; use a temp file then return its contents.
|
|
32
|
+
fd, path = tempfile.mkstemp(suffix=".svg")
|
|
33
|
+
os.close(fd)
|
|
34
|
+
try:
|
|
35
|
+
pid = os.getpid()
|
|
36
|
+
# Example:
|
|
37
|
+
# py-spy record -p <pid> -d <seconds> --format flamegraph -o out.svg
|
|
38
|
+
cmd = [
|
|
39
|
+
pyspy,
|
|
40
|
+
"record",
|
|
41
|
+
"-p",
|
|
42
|
+
str(pid),
|
|
43
|
+
"-d",
|
|
44
|
+
str(seconds),
|
|
45
|
+
"--format",
|
|
46
|
+
"flamegraph",
|
|
47
|
+
"-o",
|
|
48
|
+
path,
|
|
49
|
+
]
|
|
50
|
+
proc = await asyncio.create_subprocess_exec(
|
|
51
|
+
*cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
52
|
+
)
|
|
53
|
+
try:
|
|
54
|
+
_, stderr = await asyncio.wait_for(proc.communicate(), timeout=seconds + 15)
|
|
55
|
+
except TimeoutError:
|
|
56
|
+
with contextlib.suppress(ProcessLookupError):
|
|
57
|
+
proc.kill()
|
|
58
|
+
return JSONResponse(
|
|
59
|
+
{
|
|
60
|
+
"error": "py-spy timed out",
|
|
61
|
+
"hint": "Check ptrace permissions or reduce duration",
|
|
62
|
+
},
|
|
63
|
+
status_code=504,
|
|
64
|
+
)
|
|
65
|
+
if proc.returncode != 0:
|
|
66
|
+
# Common failures: missing ptrace capability in containers.
|
|
67
|
+
msg = stderr.decode("utf-8", errors="ignore") if stderr else "py-spy failed"
|
|
68
|
+
await logger.awarning("py-spy failed", returncode=proc.returncode, msg=msg)
|
|
69
|
+
return JSONResponse(
|
|
70
|
+
{
|
|
71
|
+
"error": "py-spy failed",
|
|
72
|
+
"detail": msg,
|
|
73
|
+
"hint": "Ensure the container has CAP_SYS_PTRACE / seccomp=unconfined",
|
|
74
|
+
},
|
|
75
|
+
status_code=500,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
with open(path, "rb") as f:
|
|
79
|
+
content = f.read()
|
|
80
|
+
ts = int(time.time())
|
|
81
|
+
return Response(
|
|
82
|
+
content,
|
|
83
|
+
media_type="image/svg+xml",
|
|
84
|
+
headers={
|
|
85
|
+
"Content-Disposition": f"inline; filename=pyspy-{ts}.svg",
|
|
86
|
+
"Cache-Control": "no-store",
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
finally:
|
|
90
|
+
with contextlib.suppress(FileNotFoundError):
|
|
91
|
+
os.remove(path)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
async def profile(request: ApiRequest):
|
|
95
|
+
if not config.FF_PYSPY_PROFILING_ENABLED:
|
|
96
|
+
return JSONResponse({"error": "Profiling disabled"}, status_code=403)
|
|
97
|
+
|
|
98
|
+
params = request.query_params
|
|
99
|
+
try:
|
|
100
|
+
seconds = _clamp_duration(int(params.get("seconds", "15")))
|
|
101
|
+
except ValueError:
|
|
102
|
+
return JSONResponse({"error": "Invalid seconds"}, status_code=400)
|
|
103
|
+
return await _profile_with_pyspy(seconds, "svg")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
profile_routes: list[BaseRoute] = [
|
|
107
|
+
ApiRoute("/profile", profile, methods=["GET"]),
|
|
108
|
+
]
|