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/cli.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import contextlib
|
|
2
3
|
import json
|
|
3
4
|
import logging
|
|
@@ -8,15 +9,18 @@ import typing
|
|
|
8
9
|
from collections.abc import Mapping, Sequence
|
|
9
10
|
from typing import Literal
|
|
10
11
|
|
|
11
|
-
from typing_extensions import TypedDict
|
|
12
|
-
|
|
13
12
|
if typing.TYPE_CHECKING:
|
|
14
|
-
from
|
|
13
|
+
from packaging.version import Version
|
|
14
|
+
|
|
15
|
+
from langgraph_api.config import AuthConfig, HttpConfig, StoreConfig
|
|
15
16
|
|
|
16
17
|
logging.basicConfig(level=logging.INFO)
|
|
17
18
|
logger = logging.getLogger(__name__)
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
SUPPORT_STATUS = Literal["active", "critical", "eol"]
|
|
22
|
+
|
|
23
|
+
|
|
20
24
|
def _get_ls_origin() -> str | None:
|
|
21
25
|
from langsmith.client import Client
|
|
22
26
|
from langsmith.utils import tracing_is_enabled
|
|
@@ -76,91 +80,6 @@ def patch_environment(**kwargs):
|
|
|
76
80
|
os.environ[key] = value
|
|
77
81
|
|
|
78
82
|
|
|
79
|
-
class SecurityConfig(TypedDict, total=False):
|
|
80
|
-
securitySchemes: dict
|
|
81
|
-
security: list
|
|
82
|
-
# path => {method => security}
|
|
83
|
-
paths: dict[str, dict[str, list]]
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
class CacheConfig(TypedDict, total=False):
|
|
87
|
-
cache_keys: list[str]
|
|
88
|
-
ttl_seconds: int
|
|
89
|
-
max_size: int
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
class AuthConfig(TypedDict, total=False):
|
|
93
|
-
path: str
|
|
94
|
-
"""Path to the authentication function in a Python file."""
|
|
95
|
-
disable_studio_auth: bool
|
|
96
|
-
"""Whether to disable auth when connecting from the LangSmith Studio."""
|
|
97
|
-
openapi: SecurityConfig
|
|
98
|
-
"""The schema to use for updating the openapi spec.
|
|
99
|
-
|
|
100
|
-
Example:
|
|
101
|
-
{
|
|
102
|
-
"securitySchemes": {
|
|
103
|
-
"OAuth2": {
|
|
104
|
-
"type": "oauth2",
|
|
105
|
-
"flows": {
|
|
106
|
-
"password": {
|
|
107
|
-
"tokenUrl": "/token",
|
|
108
|
-
"scopes": {
|
|
109
|
-
"me": "Read information about the current user",
|
|
110
|
-
"items": "Access to create and manage items"
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
"security": [
|
|
117
|
-
{"OAuth2": ["me"]} # Default security requirement for all endpoints
|
|
118
|
-
]
|
|
119
|
-
}
|
|
120
|
-
"""
|
|
121
|
-
cache: CacheConfig | None
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def _check_newer_version(pkg: str, timeout: float = 0.2) -> None:
|
|
125
|
-
"""Log a notice if PyPI reports a newer version."""
|
|
126
|
-
import importlib.metadata as md
|
|
127
|
-
import json
|
|
128
|
-
import urllib.request
|
|
129
|
-
|
|
130
|
-
from packaging.version import Version
|
|
131
|
-
|
|
132
|
-
thread_logger = logging.getLogger("check_version")
|
|
133
|
-
if not thread_logger.handlers:
|
|
134
|
-
handler = logging.StreamHandler()
|
|
135
|
-
handler.setFormatter(logging.Formatter("%(message)s"))
|
|
136
|
-
thread_logger.addHandler(handler)
|
|
137
|
-
|
|
138
|
-
try:
|
|
139
|
-
current = Version(md.version(pkg))
|
|
140
|
-
with urllib.request.urlopen(
|
|
141
|
-
f"https://pypi.org/pypi/{pkg}/json", timeout=timeout
|
|
142
|
-
) as resp:
|
|
143
|
-
latest_str = json.load(resp)["info"]["version"]
|
|
144
|
-
latest = Version(latest_str)
|
|
145
|
-
if latest > current:
|
|
146
|
-
thread_logger.info(
|
|
147
|
-
"🔔 A newer version of %s is available: %s → %s (pip install -U %s)",
|
|
148
|
-
pkg,
|
|
149
|
-
current,
|
|
150
|
-
latest,
|
|
151
|
-
pkg,
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
except Exception:
|
|
155
|
-
pass
|
|
156
|
-
|
|
157
|
-
except RuntimeError:
|
|
158
|
-
thread_logger.info(
|
|
159
|
-
f"Failed to check for newer version of {pkg}."
|
|
160
|
-
" To disable version checks, set LANGGRAPH_NO_VERSION_CHECK=true"
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
|
|
164
83
|
def run_server(
|
|
165
84
|
host: str = "127.0.0.1",
|
|
166
85
|
port: int = 2024,
|
|
@@ -176,15 +95,20 @@ def run_server(
|
|
|
176
95
|
reload_includes: Sequence[str] | None = None,
|
|
177
96
|
reload_excludes: Sequence[str] | None = None,
|
|
178
97
|
store: typing.Optional["StoreConfig"] = None,
|
|
179
|
-
auth: AuthConfig
|
|
98
|
+
auth: typing.Optional["AuthConfig"] = None,
|
|
180
99
|
http: typing.Optional["HttpConfig"] = None,
|
|
181
100
|
ui: dict | None = None,
|
|
101
|
+
webhooks: dict | None = None,
|
|
182
102
|
ui_config: dict | None = None,
|
|
183
103
|
studio_url: str | None = None,
|
|
184
104
|
disable_persistence: bool = False,
|
|
185
105
|
allow_blocking: bool = False,
|
|
186
106
|
runtime_edition: Literal["inmem", "community", "postgres"] = "inmem",
|
|
187
107
|
server_level: str = "WARNING",
|
|
108
|
+
__redis_uri__: str | None = "fake",
|
|
109
|
+
__database_uri__: str | None = ":memory:",
|
|
110
|
+
__migrations_path__: str | None = "__inmem",
|
|
111
|
+
__entrypoint__: Literal["server", "python-executor"] = "server",
|
|
188
112
|
**kwargs: typing.Any,
|
|
189
113
|
):
|
|
190
114
|
"""Run the LangGraph API server."""
|
|
@@ -200,6 +124,8 @@ def run_server(
|
|
|
200
124
|
mount_prefix = None
|
|
201
125
|
if http is not None and http.get("mount_prefix") is not None:
|
|
202
126
|
mount_prefix = http.get("mount_prefix")
|
|
127
|
+
if os.environ.get("MOUNT_PREFIX"):
|
|
128
|
+
mount_prefix = os.environ.get("MOUNT_PREFIX")
|
|
203
129
|
if os.environ.get("LANGGRAPH_MOUNT_PREFIX"):
|
|
204
130
|
mount_prefix = os.environ.get("LANGGRAPH_MOUNT_PREFIX")
|
|
205
131
|
if isinstance(env, str | pathlib.Path):
|
|
@@ -265,16 +191,19 @@ def run_server(
|
|
|
265
191
|
else:
|
|
266
192
|
local_url = upstream_url
|
|
267
193
|
to_patch = dict(
|
|
268
|
-
MIGRATIONS_PATH=
|
|
269
|
-
DATABASE_URI=
|
|
270
|
-
REDIS_URI=
|
|
271
|
-
N_JOBS_PER_WORKER=str(
|
|
194
|
+
MIGRATIONS_PATH=__migrations_path__,
|
|
195
|
+
DATABASE_URI=__database_uri__,
|
|
196
|
+
REDIS_URI=__redis_uri__,
|
|
197
|
+
N_JOBS_PER_WORKER=str(
|
|
198
|
+
n_jobs_per_worker if n_jobs_per_worker is not None else 1
|
|
199
|
+
),
|
|
272
200
|
LANGGRAPH_STORE=json.dumps(store) if store else None,
|
|
273
201
|
LANGSERVE_GRAPHS=json.dumps(graphs) if graphs else None,
|
|
274
202
|
LANGSMITH_LANGGRAPH_API_VARIANT="local_dev",
|
|
275
203
|
LANGGRAPH_AUTH=json.dumps(auth) if auth else None,
|
|
276
204
|
LANGGRAPH_HTTP=json.dumps(http) if http else None,
|
|
277
205
|
LANGGRAPH_UI=json.dumps(ui) if ui else None,
|
|
206
|
+
LANGGRAPH_WEBHOOKS=json.dumps(webhooks) if webhooks else None,
|
|
278
207
|
LANGGRAPH_UI_CONFIG=json.dumps(ui_config) if ui_config else None,
|
|
279
208
|
LANGGRAPH_UI_BUNDLER="true",
|
|
280
209
|
LANGGRAPH_API_URL=local_url,
|
|
@@ -291,7 +220,7 @@ def run_server(
|
|
|
291
220
|
if k in to_patch:
|
|
292
221
|
logger.debug(f"Skipping loaded env var {k}={v}")
|
|
293
222
|
continue
|
|
294
|
-
to_patch[k] = v
|
|
223
|
+
to_patch[k] = v # type: ignore[invalid-assignment]
|
|
295
224
|
with patch_environment(
|
|
296
225
|
**to_patch,
|
|
297
226
|
):
|
|
@@ -325,7 +254,7 @@ def run_server(
|
|
|
325
254
|
full_studio_url = f"{studio_origin}/studio/?baseUrl={local_url}&organizationId={org_id}"
|
|
326
255
|
except TimeoutError as e:
|
|
327
256
|
thread_logger.debug(
|
|
328
|
-
f"Failed to get organization ID: {
|
|
257
|
+
f"Failed to get organization ID: {e!s}"
|
|
329
258
|
)
|
|
330
259
|
pass
|
|
331
260
|
thread_logger.info(
|
|
@@ -354,7 +283,7 @@ def run_server(
|
|
|
354
283
|
- 📚 API Docs: \033[36m{local_url}/docs\033[0m
|
|
355
284
|
|
|
356
285
|
This in-memory server is designed for development and testing.
|
|
357
|
-
For production use, please use
|
|
286
|
+
For production use, please use LangSmith Deployment.
|
|
358
287
|
|
|
359
288
|
"""
|
|
360
289
|
logger.info(welcome)
|
|
@@ -362,8 +291,12 @@ For production use, please use LangGraph Platform.
|
|
|
362
291
|
threading.Thread(target=_open_browser, daemon=True).start()
|
|
363
292
|
nvc = os.getenv("LANGGRAPH_NO_VERSION_CHECK")
|
|
364
293
|
if nvc is None or nvc.lower() not in ("true", "1"):
|
|
294
|
+
from langgraph_api import __version__
|
|
295
|
+
|
|
365
296
|
threading.Thread(
|
|
366
|
-
target=_check_newer_version,
|
|
297
|
+
target=_check_newer_version,
|
|
298
|
+
args=("langgraph-api", __version__),
|
|
299
|
+
daemon=True,
|
|
367
300
|
).start()
|
|
368
301
|
supported_kwargs = {
|
|
369
302
|
k: v
|
|
@@ -371,40 +304,53 @@ For production use, please use LangGraph Platform.
|
|
|
371
304
|
if k in inspect.signature(uvicorn.run).parameters
|
|
372
305
|
}
|
|
373
306
|
server_level = server_level.upper()
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
"
|
|
389
|
-
"
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
"
|
|
394
|
-
"
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
"
|
|
401
|
-
|
|
402
|
-
|
|
307
|
+
if __entrypoint__ == "server":
|
|
308
|
+
uvicorn.run(
|
|
309
|
+
"langgraph_api.server:app",
|
|
310
|
+
host=host,
|
|
311
|
+
port=port,
|
|
312
|
+
reload=reload,
|
|
313
|
+
env_file=env_file,
|
|
314
|
+
access_log=False,
|
|
315
|
+
reload_includes=list(reload_includes) if reload_includes else None,
|
|
316
|
+
reload_excludes=list(reload_excludes) if reload_excludes else None,
|
|
317
|
+
log_config={
|
|
318
|
+
"version": 1,
|
|
319
|
+
"incremental": False,
|
|
320
|
+
"disable_existing_loggers": False,
|
|
321
|
+
"formatters": {
|
|
322
|
+
"simple": {
|
|
323
|
+
"class": "langgraph_api.logging.Formatter",
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
"handlers": {
|
|
327
|
+
"console": {
|
|
328
|
+
"class": "logging.StreamHandler",
|
|
329
|
+
"formatter": "simple",
|
|
330
|
+
"stream": "ext://sys.stdout",
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
"loggers": {
|
|
334
|
+
"uvicorn": {"level": server_level},
|
|
335
|
+
"uvicorn.error": {"level": server_level},
|
|
336
|
+
"langgraph_api.server": {"level": server_level},
|
|
337
|
+
},
|
|
338
|
+
"root": {"handlers": ["console"]},
|
|
403
339
|
},
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
340
|
+
**supported_kwargs,
|
|
341
|
+
)
|
|
342
|
+
elif __entrypoint__ == "python-executor":
|
|
343
|
+
from langgraph_api.executor_entrypoint import (
|
|
344
|
+
main as executor_entrypoint_main,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
asyncio.run(
|
|
348
|
+
executor_entrypoint_main(
|
|
349
|
+
grpc_port=8188,
|
|
350
|
+
)
|
|
351
|
+
)
|
|
352
|
+
else:
|
|
353
|
+
raise ValueError(f"Unknown entrypoint: {__entrypoint__}")
|
|
408
354
|
|
|
409
355
|
|
|
410
356
|
def main():
|
|
@@ -429,7 +375,7 @@ def main():
|
|
|
429
375
|
help="Number of jobs per worker. Default is None (meaning 10)",
|
|
430
376
|
)
|
|
431
377
|
parser.add_argument(
|
|
432
|
-
"--
|
|
378
|
+
"--open-browser", action="store_true", help="Open browser automatically"
|
|
433
379
|
)
|
|
434
380
|
parser.add_argument(
|
|
435
381
|
"--debug-port", type=int, help="Port for debugger to listen on (default: none)"
|
|
@@ -444,7 +390,19 @@ def main():
|
|
|
444
390
|
action="store_true",
|
|
445
391
|
help="Expose the server via Cloudflare Tunnel",
|
|
446
392
|
)
|
|
447
|
-
|
|
393
|
+
parser.add_argument(
|
|
394
|
+
"--runtime-edition",
|
|
395
|
+
type=str,
|
|
396
|
+
default="inmem",
|
|
397
|
+
help="Runtime edition to use",
|
|
398
|
+
)
|
|
399
|
+
parser.add_argument(
|
|
400
|
+
"--entrypoint",
|
|
401
|
+
type=str,
|
|
402
|
+
default="server",
|
|
403
|
+
choices=["server", "python-executor"],
|
|
404
|
+
help="Entry point to use",
|
|
405
|
+
)
|
|
448
406
|
args = parser.parse_args()
|
|
449
407
|
|
|
450
408
|
with open(args.config, encoding="utf-8") as f:
|
|
@@ -453,23 +411,162 @@ def main():
|
|
|
453
411
|
graphs = config_data.get("graphs", {})
|
|
454
412
|
auth = config_data.get("auth")
|
|
455
413
|
ui = config_data.get("ui")
|
|
414
|
+
webhooks = config_data.get("webhooks")
|
|
456
415
|
ui_config = config_data.get("ui_config")
|
|
416
|
+
kwargs = {}
|
|
417
|
+
if args.runtime_edition == "postgres":
|
|
418
|
+
kwargs["__redis_uri__"] = os.getenv("REDIS_URI")
|
|
419
|
+
kwargs["__database_uri__"] = os.getenv("DATABASE_URI")
|
|
420
|
+
kwargs["__migrations_path__"] = os.getenv("MIGRATIONS_PATH")
|
|
421
|
+
if args.entrypoint == "python-executor":
|
|
422
|
+
kwargs["__entrypoint__"] = "python-executor"
|
|
457
423
|
run_server(
|
|
458
424
|
args.host,
|
|
459
425
|
args.port,
|
|
460
426
|
not args.no_reload,
|
|
461
427
|
graphs,
|
|
462
428
|
n_jobs_per_worker=args.n_jobs_per_worker,
|
|
463
|
-
open_browser=
|
|
429
|
+
open_browser=args.open_browser,
|
|
464
430
|
tunnel=args.tunnel,
|
|
465
431
|
debug_port=args.debug_port,
|
|
466
432
|
wait_for_client=args.wait_for_client,
|
|
467
433
|
env=config_data.get("env", None),
|
|
468
434
|
auth=auth,
|
|
469
435
|
ui=ui,
|
|
436
|
+
webhooks=webhooks,
|
|
470
437
|
ui_config=ui_config,
|
|
438
|
+
runtime_edition=args.runtime_edition,
|
|
439
|
+
**kwargs,
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _check_newer_version(pkg: str, current_version: str, timeout: float = 0.5) -> None:
|
|
444
|
+
"""Check PyPI for newer versions and log support status.
|
|
445
|
+
|
|
446
|
+
Critical = one minor behind on same major, OR latest minor of previous major while latest is X.0.*
|
|
447
|
+
EOL = two+ minors behind on same major, OR any previous major after X.1.*
|
|
448
|
+
"""
|
|
449
|
+
import json
|
|
450
|
+
import urllib.request
|
|
451
|
+
|
|
452
|
+
from packaging.version import InvalidVersion, Version
|
|
453
|
+
|
|
454
|
+
log = logging.getLogger("version_check")
|
|
455
|
+
if not log.handlers:
|
|
456
|
+
h = logging.StreamHandler()
|
|
457
|
+
h.setFormatter(logging.Formatter("%(message)s"))
|
|
458
|
+
log.addHandler(h)
|
|
459
|
+
|
|
460
|
+
if os.getenv("LANGGRAPH_NO_VERSION_CHECK", "").lower() in ("true", "1"):
|
|
461
|
+
return
|
|
462
|
+
|
|
463
|
+
def _parse(v: str) -> Version | None:
|
|
464
|
+
try:
|
|
465
|
+
return Version(v)
|
|
466
|
+
except InvalidVersion:
|
|
467
|
+
return None
|
|
468
|
+
|
|
469
|
+
try:
|
|
470
|
+
current = Version(current_version)
|
|
471
|
+
except InvalidVersion:
|
|
472
|
+
log.info(
|
|
473
|
+
f"[version] Could not parse installed version {current_version!r}. Skipping support check."
|
|
474
|
+
)
|
|
475
|
+
return
|
|
476
|
+
|
|
477
|
+
try:
|
|
478
|
+
with urllib.request.urlopen(
|
|
479
|
+
f"https://pypi.org/pypi/{pkg}/json", timeout=timeout
|
|
480
|
+
) as resp:
|
|
481
|
+
payload = json.load(resp)
|
|
482
|
+
latest_str = payload["info"]["version"]
|
|
483
|
+
latest = Version(latest_str)
|
|
484
|
+
releases: dict[str, list[dict]] = payload.get("releases", {})
|
|
485
|
+
except Exception:
|
|
486
|
+
log.debug("Failed to retrieve latest version info for %s", pkg)
|
|
487
|
+
return
|
|
488
|
+
prev_major_latest_minor: Version | None = None
|
|
489
|
+
if latest.major > 0:
|
|
490
|
+
pm = latest.major - 1
|
|
491
|
+
prev_major_versions = [
|
|
492
|
+
v
|
|
493
|
+
for s in releases
|
|
494
|
+
if (v := _parse(s)) is not None and not v.is_prerelease and v.major == pm
|
|
495
|
+
]
|
|
496
|
+
if prev_major_versions:
|
|
497
|
+
prev_major_latest_minor = max(
|
|
498
|
+
prev_major_versions, key=lambda v: (v.major, v.minor, v.micro)
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
if latest > current and not current.is_prerelease:
|
|
502
|
+
log.info(
|
|
503
|
+
"[version] A newer version of %s is available: %s → %s (pip install -U %s)",
|
|
504
|
+
pkg,
|
|
505
|
+
current,
|
|
506
|
+
latest,
|
|
507
|
+
pkg,
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
level = _support_level(current, latest, prev_major_latest_minor)
|
|
511
|
+
changelog = (
|
|
512
|
+
"https://docs.langchain.com/langgraph-platform/langgraph-server-changelog"
|
|
471
513
|
)
|
|
472
514
|
|
|
515
|
+
if level == "critical":
|
|
516
|
+
# Distinguish same-major vs cross-major grace in the wording
|
|
517
|
+
if current.major == latest.major and current.minor == latest.minor - 1:
|
|
518
|
+
tail = "You are one minor version behind the latest (%d.%d.x).\n"
|
|
519
|
+
else:
|
|
520
|
+
tail = "You are on the latest minor of the previous major while a new major (%d.%d.x) just released.\n"
|
|
521
|
+
log.info(
|
|
522
|
+
"⚠️ [support] %s %s is in Critical support.\n"
|
|
523
|
+
"Only critical security and installation fixes are provided.\n"
|
|
524
|
+
+ tail
|
|
525
|
+
+ "Please plan an upgrade soon. See changelog: %s",
|
|
526
|
+
pkg,
|
|
527
|
+
current,
|
|
528
|
+
latest.major,
|
|
529
|
+
latest.minor,
|
|
530
|
+
changelog,
|
|
531
|
+
)
|
|
532
|
+
elif level == "eol":
|
|
533
|
+
log.info(
|
|
534
|
+
"⚠️ [support] %s %s is End of Life.\n"
|
|
535
|
+
"No bug fixes or security updates will be provided.\n"
|
|
536
|
+
"You are two or more minor versions behind the latest (%d.%d.x).\n"
|
|
537
|
+
"You should upgrade immediately. See changelog: %s",
|
|
538
|
+
pkg,
|
|
539
|
+
current,
|
|
540
|
+
latest.major,
|
|
541
|
+
latest.minor,
|
|
542
|
+
changelog,
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def _support_level(
|
|
547
|
+
cur: "Version", lat: "Version", prev_major_latest_minor: "Version | None"
|
|
548
|
+
) -> SUPPORT_STATUS:
|
|
549
|
+
if cur.major > lat.major:
|
|
550
|
+
return "active"
|
|
551
|
+
if cur.major == lat.major:
|
|
552
|
+
if cur.minor == lat.minor:
|
|
553
|
+
return "active"
|
|
554
|
+
if cur.minor == lat.minor - 1:
|
|
555
|
+
return "critical"
|
|
556
|
+
if cur.minor <= lat.minor - 2:
|
|
557
|
+
return "eol"
|
|
558
|
+
return "active"
|
|
559
|
+
|
|
560
|
+
if cur.major == lat.major - 1 and lat.minor == 0:
|
|
561
|
+
if (
|
|
562
|
+
prev_major_latest_minor is not None
|
|
563
|
+
and cur.minor == prev_major_latest_minor.minor
|
|
564
|
+
):
|
|
565
|
+
return "critical"
|
|
566
|
+
return "eol"
|
|
567
|
+
|
|
568
|
+
return "eol"
|
|
569
|
+
|
|
473
570
|
|
|
474
571
|
if __name__ == "__main__":
|
|
475
572
|
main()
|
langgraph_api/command.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import cast
|
|
2
|
+
|
|
1
3
|
from langgraph.types import Command, Send
|
|
2
4
|
|
|
3
5
|
from langgraph_api.schema import RunCommand
|
|
@@ -11,15 +13,15 @@ def map_cmd(cmd: RunCommand) -> Command:
|
|
|
11
13
|
update = cmd.get("update")
|
|
12
14
|
if isinstance(update, tuple | list) and all(
|
|
13
15
|
isinstance(t, tuple | list) and len(t) == 2 and isinstance(t[0], str)
|
|
14
|
-
for t in update
|
|
16
|
+
for t in cast("list", update)
|
|
15
17
|
):
|
|
16
|
-
update = [tuple(t) for t in update]
|
|
18
|
+
update = [tuple(t) for t in cast("list", update)]
|
|
17
19
|
|
|
18
20
|
return Command(
|
|
19
21
|
update=update,
|
|
20
22
|
goto=(
|
|
21
23
|
[
|
|
22
|
-
it if isinstance(it, str) else Send(it["node"], it["input"])
|
|
24
|
+
it if isinstance(it, str) else Send(it["node"], it["input"]) # type: ignore[non-subscriptable]
|
|
23
25
|
for it in goto
|
|
24
26
|
]
|
|
25
27
|
if goto
|