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
langgraph_api/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.4.1"
1
+ __version__ = "0.7.3"
@@ -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, Mount, Route
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 HTTP_CONFIG, MIGRATIONS_PATH, MOUNT_PREFIX
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 connect, healthcheck
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
- await healthcheck()
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
- await js_healthcheck()
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
- meta_routes: list[BaseRoute] = [
56
- Route("/ok", ok, methods=["GET"]),
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
- if os.path.isfile(path) or path.endswith(".py"):
103
- # Import from file path using a unique module name.
104
- spec = importlib.util.spec_from_file_location("user_router_module", path)
105
- if spec is None or spec.loader is None:
106
- raise ImportError(f"Cannot load spec from {path}")
107
- module = importlib.util.module_from_spec(spec)
108
- spec.loader.exec_module(module)
109
- else:
110
- # Import as a normal module.
111
- module = importlib.import_module(path)
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 router_import := HTTP_CONFIG.get("app"):
131
- user_router = load_custom_app(router_import)
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
- if protected_routes:
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 "inmem" in MIGRATIONS_PATH:
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
- await asyncio.to_thread(Checkpointer().clear)
161
- async with connect() as conn:
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
- routes.insert(0, Route("/internal/truncate", truncate, methods=["POST"]))
223
+ if get_internal_routes is not None:
224
+ for route in get_internal_routes():
225
+ unshadowable_meta_routes.insert(0, route)