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.
Files changed (135) hide show
  1. langgraph_api/__init__.py +1 -1
  2. langgraph_api/api/__init__.py +111 -51
  3. langgraph_api/api/a2a.py +1610 -0
  4. langgraph_api/api/assistants.py +212 -89
  5. langgraph_api/api/mcp.py +3 -3
  6. langgraph_api/api/meta.py +52 -28
  7. langgraph_api/api/openapi.py +27 -17
  8. langgraph_api/api/profile.py +108 -0
  9. langgraph_api/api/runs.py +342 -195
  10. langgraph_api/api/store.py +19 -2
  11. langgraph_api/api/threads.py +209 -27
  12. langgraph_api/asgi_transport.py +14 -9
  13. langgraph_api/asyncio.py +14 -4
  14. langgraph_api/auth/custom.py +52 -37
  15. langgraph_api/auth/langsmith/backend.py +4 -3
  16. langgraph_api/auth/langsmith/client.py +13 -8
  17. langgraph_api/cli.py +230 -133
  18. langgraph_api/command.py +5 -3
  19. langgraph_api/config/__init__.py +532 -0
  20. langgraph_api/config/_parse.py +58 -0
  21. langgraph_api/config/schemas.py +431 -0
  22. langgraph_api/cron_scheduler.py +17 -1
  23. langgraph_api/encryption/__init__.py +15 -0
  24. langgraph_api/encryption/aes_json.py +158 -0
  25. langgraph_api/encryption/context.py +35 -0
  26. langgraph_api/encryption/custom.py +280 -0
  27. langgraph_api/encryption/middleware.py +632 -0
  28. langgraph_api/encryption/shared.py +63 -0
  29. langgraph_api/errors.py +12 -1
  30. langgraph_api/executor_entrypoint.py +11 -6
  31. langgraph_api/feature_flags.py +29 -0
  32. langgraph_api/graph.py +176 -76
  33. langgraph_api/grpc/client.py +313 -0
  34. langgraph_api/grpc/config_conversion.py +231 -0
  35. langgraph_api/grpc/generated/__init__.py +29 -0
  36. langgraph_api/grpc/generated/checkpointer_pb2.py +63 -0
  37. langgraph_api/grpc/generated/checkpointer_pb2.pyi +99 -0
  38. langgraph_api/grpc/generated/checkpointer_pb2_grpc.py +329 -0
  39. langgraph_api/grpc/generated/core_api_pb2.py +216 -0
  40. langgraph_api/grpc/generated/core_api_pb2.pyi +905 -0
  41. langgraph_api/grpc/generated/core_api_pb2_grpc.py +1621 -0
  42. langgraph_api/grpc/generated/engine_common_pb2.py +219 -0
  43. langgraph_api/grpc/generated/engine_common_pb2.pyi +722 -0
  44. langgraph_api/grpc/generated/engine_common_pb2_grpc.py +24 -0
  45. langgraph_api/grpc/generated/enum_cancel_run_action_pb2.py +37 -0
  46. langgraph_api/grpc/generated/enum_cancel_run_action_pb2.pyi +12 -0
  47. langgraph_api/grpc/generated/enum_cancel_run_action_pb2_grpc.py +24 -0
  48. langgraph_api/grpc/generated/enum_control_signal_pb2.py +37 -0
  49. langgraph_api/grpc/generated/enum_control_signal_pb2.pyi +16 -0
  50. langgraph_api/grpc/generated/enum_control_signal_pb2_grpc.py +24 -0
  51. langgraph_api/grpc/generated/enum_durability_pb2.py +37 -0
  52. langgraph_api/grpc/generated/enum_durability_pb2.pyi +16 -0
  53. langgraph_api/grpc/generated/enum_durability_pb2_grpc.py +24 -0
  54. langgraph_api/grpc/generated/enum_multitask_strategy_pb2.py +37 -0
  55. langgraph_api/grpc/generated/enum_multitask_strategy_pb2.pyi +16 -0
  56. langgraph_api/grpc/generated/enum_multitask_strategy_pb2_grpc.py +24 -0
  57. langgraph_api/grpc/generated/enum_run_status_pb2.py +37 -0
  58. langgraph_api/grpc/generated/enum_run_status_pb2.pyi +22 -0
  59. langgraph_api/grpc/generated/enum_run_status_pb2_grpc.py +24 -0
  60. langgraph_api/grpc/generated/enum_stream_mode_pb2.py +37 -0
  61. langgraph_api/grpc/generated/enum_stream_mode_pb2.pyi +28 -0
  62. langgraph_api/grpc/generated/enum_stream_mode_pb2_grpc.py +24 -0
  63. langgraph_api/grpc/generated/enum_thread_status_pb2.py +37 -0
  64. langgraph_api/grpc/generated/enum_thread_status_pb2.pyi +16 -0
  65. langgraph_api/grpc/generated/enum_thread_status_pb2_grpc.py +24 -0
  66. langgraph_api/grpc/generated/enum_thread_stream_mode_pb2.py +37 -0
  67. langgraph_api/grpc/generated/enum_thread_stream_mode_pb2.pyi +16 -0
  68. langgraph_api/grpc/generated/enum_thread_stream_mode_pb2_grpc.py +24 -0
  69. langgraph_api/grpc/generated/errors_pb2.py +39 -0
  70. langgraph_api/grpc/generated/errors_pb2.pyi +21 -0
  71. langgraph_api/grpc/generated/errors_pb2_grpc.py +24 -0
  72. langgraph_api/grpc/ops/__init__.py +370 -0
  73. langgraph_api/grpc/ops/assistants.py +424 -0
  74. langgraph_api/grpc/ops/runs.py +792 -0
  75. langgraph_api/grpc/ops/threads.py +1013 -0
  76. langgraph_api/http.py +16 -5
  77. langgraph_api/http_metrics.py +15 -35
  78. langgraph_api/http_metrics_utils.py +38 -0
  79. langgraph_api/js/build.mts +1 -1
  80. langgraph_api/js/client.http.mts +13 -7
  81. langgraph_api/js/client.mts +2 -5
  82. langgraph_api/js/package.json +29 -28
  83. langgraph_api/js/remote.py +56 -30
  84. langgraph_api/js/src/graph.mts +20 -0
  85. langgraph_api/js/sse.py +2 -2
  86. langgraph_api/js/ui.py +1 -1
  87. langgraph_api/js/yarn.lock +1204 -1006
  88. langgraph_api/logging.py +29 -2
  89. langgraph_api/metadata.py +99 -28
  90. langgraph_api/middleware/http_logger.py +7 -2
  91. langgraph_api/middleware/private_network.py +7 -7
  92. langgraph_api/models/run.py +54 -93
  93. langgraph_api/otel_context.py +205 -0
  94. langgraph_api/patch.py +5 -3
  95. langgraph_api/queue_entrypoint.py +154 -65
  96. langgraph_api/route.py +47 -5
  97. langgraph_api/schema.py +88 -10
  98. langgraph_api/self_hosted_logs.py +124 -0
  99. langgraph_api/self_hosted_metrics.py +450 -0
  100. langgraph_api/serde.py +79 -37
  101. langgraph_api/server.py +138 -60
  102. langgraph_api/state.py +4 -3
  103. langgraph_api/store.py +25 -16
  104. langgraph_api/stream.py +80 -29
  105. langgraph_api/thread_ttl.py +31 -13
  106. langgraph_api/timing/__init__.py +25 -0
  107. langgraph_api/timing/profiler.py +200 -0
  108. langgraph_api/timing/timer.py +318 -0
  109. langgraph_api/utils/__init__.py +53 -8
  110. langgraph_api/utils/cache.py +47 -10
  111. langgraph_api/utils/config.py +2 -1
  112. langgraph_api/utils/errors.py +77 -0
  113. langgraph_api/utils/future.py +10 -6
  114. langgraph_api/utils/headers.py +76 -2
  115. langgraph_api/utils/retriable_client.py +74 -0
  116. langgraph_api/utils/stream_codec.py +315 -0
  117. langgraph_api/utils/uuids.py +29 -62
  118. langgraph_api/validation.py +9 -0
  119. langgraph_api/webhook.py +120 -6
  120. langgraph_api/worker.py +55 -24
  121. {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/METADATA +16 -8
  122. langgraph_api-0.7.3.dist-info/RECORD +168 -0
  123. {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/WHEEL +1 -1
  124. langgraph_runtime/__init__.py +1 -0
  125. langgraph_runtime/routes.py +11 -0
  126. logging.json +1 -3
  127. openapi.json +839 -478
  128. langgraph_api/config.py +0 -387
  129. langgraph_api/js/isolate-0x130008000-46649-46649-v8.log +0 -4430
  130. langgraph_api/js/isolate-0x138008000-44681-44681-v8.log +0 -4430
  131. langgraph_api/js/package-lock.json +0 -3308
  132. langgraph_api-0.4.1.dist-info/RECORD +0 -107
  133. /langgraph_api/{utils.py → grpc/__init__.py} +0 -0
  134. {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/entry_points.txt +0 -0
  135. {langgraph_api-0.4.1.dist-info → langgraph_api-0.7.3.dist-info}/licenses/LICENSE +0 -0
@@ -5,12 +5,7 @@ from functools import lru_cache
5
5
 
6
6
  import orjson
7
7
 
8
- from langgraph_api.config import (
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 isinstance(LANGGRAPH_AUTH, dict) and "openapi" in LANGGRAPH_AUTH:
52
- openapi_config = LANGGRAPH_AUTH["openapi"]
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 HTTP_CONFIG and HTTP_CONFIG.get("disable_webhooks"):
86
- webhook_schemas = ["CronCreate", "RunCreateStateful", "RunCreateStateless"]
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
+ ]