langgraph-api 0.4.26__py3-none-any.whl → 0.4.28__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.

Potentially problematic release.


This version of langgraph-api might be problematic. Click here for more details.

langgraph_api/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.4.26"
1
+ __version__ = "0.4.28"
langgraph_api/api/a2a.py CHANGED
@@ -674,12 +674,12 @@ async def handle_post_request(request: ApiRequest, assistant_id: str) -> Respons
674
674
 
675
675
  accept_header = request.headers.get("Accept") or ""
676
676
  if method == "message/stream":
677
- if "text/event-stream" not in accept_header:
677
+ if not _accepts_media_type(accept_header, "text/event-stream"):
678
678
  return create_error_response(
679
679
  "Accept header must include text/event-stream for streaming", 400
680
680
  )
681
681
  else:
682
- if "application/json" not in accept_header:
682
+ if not _accepts_media_type(accept_header, "application/json"):
683
683
  return create_error_response(
684
684
  "Accept header must include application/json", 400
685
685
  )
@@ -720,6 +720,26 @@ def create_error_response(message: str, status_code: int) -> Response:
720
720
  )
721
721
 
722
722
 
723
+ def _accepts_media_type(accept_header: str, media_type: str) -> bool:
724
+ """Return True if the Accept header allows the provided media type."""
725
+ if not accept_header:
726
+ return False
727
+
728
+ target = media_type.lower()
729
+ for media_range in accept_header.split(","):
730
+ value = media_range.strip().lower()
731
+ if not value:
732
+ continue
733
+ candidate = value.split(";", 1)[0].strip()
734
+ if candidate == "*/*" or candidate == target:
735
+ return True
736
+ if candidate.endswith("/*"):
737
+ type_prefix = candidate.split("/", 1)[0]
738
+ if target.startswith(f"{type_prefix}/"):
739
+ return True
740
+ return False
741
+
742
+
723
743
  # ============================================================================
724
744
  # JSON-RPC Message Handlers
725
745
  # ============================================================================
langgraph_api/cli.py CHANGED
@@ -165,6 +165,8 @@ def run_server(
165
165
  mount_prefix = None
166
166
  if http is not None and http.get("mount_prefix") is not None:
167
167
  mount_prefix = http.get("mount_prefix")
168
+ if os.environ.get("MOUNT_PREFIX"):
169
+ mount_prefix = os.environ.get("MOUNT_PREFIX")
168
170
  if os.environ.get("LANGGRAPH_MOUNT_PREFIX"):
169
171
  mount_prefix = os.environ.get("LANGGRAPH_MOUNT_PREFIX")
170
172
  if isinstance(env, str | pathlib.Path):
langgraph_api/config.py CHANGED
@@ -179,8 +179,6 @@ REDIS_URI = env("REDIS_URI", cast=str)
179
179
  REDIS_CLUSTER = env("REDIS_CLUSTER", cast=bool, default=False)
180
180
  REDIS_MAX_CONNECTIONS = env("REDIS_MAX_CONNECTIONS", cast=int, default=2000)
181
181
  REDIS_CONNECT_TIMEOUT = env("REDIS_CONNECT_TIMEOUT", cast=float, default=10.0)
182
- REDIS_MAX_IDLE_TIME = env("REDIS_MAX_IDLE_TIME", cast=float, default=120.0)
183
- REDIS_STREAM_TIMEOUT = env("REDIS_STREAM_TIMEOUT", cast=float, default=30.0)
184
182
  REDIS_KEY_PREFIX = env("REDIS_KEY_PREFIX", cast=str, default="")
185
183
  RUN_STATS_CACHE_SECONDS = env("RUN_STATS_CACHE_SECONDS", cast=int, default=60)
186
184
 
@@ -290,8 +288,10 @@ if THREAD_TTL is None and CHECKPOINTER_CONFIG is not None:
290
288
 
291
289
  N_JOBS_PER_WORKER = env("N_JOBS_PER_WORKER", cast=int, default=10)
292
290
  BG_JOB_TIMEOUT_SECS = env("BG_JOB_TIMEOUT_SECS", cast=float, default=3600)
291
+
293
292
  FF_CRONS_ENABLED = env("FF_CRONS_ENABLED", cast=bool, default=True)
294
293
  FF_RICH_THREADS = env("FF_RICH_THREADS", cast=bool, default=True)
294
+ FF_LOG_DROPPED_EVENTS = env("FF_LOG_DROPPED_EVENTS", cast=bool, default=False)
295
295
 
296
296
  # auth
297
297
 
@@ -1,6 +1,8 @@
1
1
  # ruff: noqa: E402
2
2
  import os
3
3
 
4
+ from langgraph_api.api.meta import METRICS_FORMATS
5
+
4
6
  if not (
5
7
  (disable_truststore := os.getenv("DISABLE_TRUSTSTORE"))
6
8
  and disable_truststore.lower() == "true"
@@ -30,6 +32,7 @@ logger = structlog.stdlib.get_logger(__name__)
30
32
  async def health_and_metrics_server():
31
33
  import uvicorn
32
34
  from starlette.applications import Starlette
35
+ from starlette.requests import Request
33
36
  from starlette.responses import JSONResponse, PlainTextResponse
34
37
  from starlette.routing import Route
35
38
 
@@ -38,7 +41,14 @@ async def health_and_metrics_server():
38
41
  async def health_endpoint(request):
39
42
  return JSONResponse({"status": "ok"})
40
43
 
41
- async def metrics_endpoint(request):
44
+ async def metrics_endpoint(request: Request):
45
+ metrics_format = request.query_params.get("format", "prometheus")
46
+ if metrics_format not in METRICS_FORMATS:
47
+ await logger.awarning(
48
+ f"metrics format {metrics_format} not supported, falling back to prometheus"
49
+ )
50
+ metrics_format = "prometheus"
51
+
42
52
  metrics = get_metrics()
43
53
  worker_metrics = cast(dict[str, int], metrics["workers"])
44
54
  workers_max = worker_metrics["max"]
@@ -48,29 +58,37 @@ async def health_and_metrics_server():
48
58
  project_id = os.getenv("LANGSMITH_HOST_PROJECT_ID")
49
59
  revision_id = os.getenv("LANGSMITH_HOST_REVISION_ID")
50
60
 
51
- metrics_lines = [
52
- "# HELP lg_api_workers_max The maximum number of workers available.",
53
- "# TYPE lg_api_workers_max gauge",
54
- f'lg_api_workers_max{{project_id="{project_id}", revision_id="{revision_id}"}} {workers_max}',
55
- "# HELP lg_api_workers_active The number of currently active workers.",
56
- "# TYPE lg_api_workers_active gauge",
57
- f'lg_api_workers_active{{project_id="{project_id}", revision_id="{revision_id}"}} {workers_active}',
58
- "# HELP lg_api_workers_available The number of available (idle) workers.",
59
- "# TYPE lg_api_workers_available gauge",
60
- f'lg_api_workers_available{{project_id="{project_id}", revision_id="{revision_id}"}} {workers_available}',
61
- ]
62
-
63
- metrics_lines.extend(
64
- pool_stats(
65
- project_id=project_id,
66
- revision_id=revision_id,
67
- )
61
+ pg_redis_stats = pool_stats(
62
+ project_id=project_id,
63
+ revision_id=revision_id,
64
+ format=metrics_format,
68
65
  )
69
66
 
70
- return PlainTextResponse(
71
- "\n".join(metrics_lines),
72
- media_type="text/plain; version=0.0.4; charset=utf-8",
73
- )
67
+ if metrics_format == "json":
68
+ resp = {
69
+ **pg_redis_stats,
70
+ "workers": worker_metrics,
71
+ }
72
+ return JSONResponse(resp)
73
+ elif metrics_format == "prometheus":
74
+ metrics_lines = [
75
+ "# HELP lg_api_workers_max The maximum number of workers available.",
76
+ "# TYPE lg_api_workers_max gauge",
77
+ f'lg_api_workers_max{{project_id="{project_id}", revision_id="{revision_id}"}} {workers_max}',
78
+ "# HELP lg_api_workers_active The number of currently active workers.",
79
+ "# TYPE lg_api_workers_active gauge",
80
+ f'lg_api_workers_active{{project_id="{project_id}", revision_id="{revision_id}"}} {workers_active}',
81
+ "# HELP lg_api_workers_available The number of available (idle) workers.",
82
+ "# TYPE lg_api_workers_available gauge",
83
+ f'lg_api_workers_available{{project_id="{project_id}", revision_id="{revision_id}"}} {workers_available}',
84
+ ]
85
+
86
+ metrics_lines.extend(pg_redis_stats)
87
+
88
+ return PlainTextResponse(
89
+ "\n".join(metrics_lines),
90
+ media_type="text/plain; version=0.0.4; charset=utf-8",
91
+ )
74
92
 
75
93
  app = Starlette(
76
94
  routes=[
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-api
3
- Version: 0.4.26
3
+ Version: 0.4.28
4
4
  Author-email: Nuno Campos <nuno@langchain.dev>, Will Fu-Hinthorn <will@langchain.dev>
5
5
  License: Elastic-2.0
6
6
  License-File: LICENSE
@@ -1,9 +1,9 @@
1
- langgraph_api/__init__.py,sha256=lR0PxzTAjaaJQ_3Wm5vE1PfTgOS8pdhmsmn4OrIioB0,23
1
+ langgraph_api/__init__.py,sha256=QZ0p7EqvqxpClz_YH3XtlxgPmyXKcK-CGg_B_p5Z7w0,23
2
2
  langgraph_api/asgi_transport.py,sha256=XtiLOu4WWsd-xizagBLzT5xUkxc9ZG9YqwvETBPjBFE,5161
3
3
  langgraph_api/asyncio.py,sha256=FEEkLm_N-15cbElo4vQ309MkDKBZuRqAYV8VJ1DocNw,9860
4
- langgraph_api/cli.py,sha256=DrTkO5JSX6jpv-aFXZfRP5Fa9j121nvnrjDgQQzqlHs,19576
4
+ langgraph_api/cli.py,sha256=o_zD2vkky06dzW87HQgkIR1_h3ZCSZ8tgNvFCK9rKVo,19669
5
5
  langgraph_api/command.py,sha256=Bh-rvuTLwdHCqFWryCjB1M8oWxPBwRBUjMNj_04KPxM,852
6
- langgraph_api/config.py,sha256=r9mmbyZlhBuJLpnTkaOLcNH6ufFNqm_2eCiuOmhqRl0,12241
6
+ langgraph_api/config.py,sha256=eS0lIOMx-UhqEw9zDXcz3W5aOLX7laqxZUQcasuXoAs,12168
7
7
  langgraph_api/cron_scheduler.py,sha256=25wYzEQrhPEivZrAPYOmzLPDOQa-aFogU37mTXc9TJk,2566
8
8
  langgraph_api/errors.py,sha256=zlnl3xXIwVG0oGNKKpXf1an9Rn_SBDHSyhe53hU6aLw,1858
9
9
  langgraph_api/executor_entrypoint.py,sha256=CaX813ygtf9CpOaBkfkQXJAHjFtmlScCkrOvTDmu4Aw,750
@@ -14,7 +14,7 @@ langgraph_api/http_metrics.py,sha256=MU9ccXt7aBb0AJ2SWEjwtbtbJEWmeqSdx7-CI51e32o
14
14
  langgraph_api/logging.py,sha256=qB6q_cUba31edE4_D6dBGhdiUTpW7sXAOepUjYb_R50,5216
15
15
  langgraph_api/metadata.py,sha256=0eGYhXOW6UIVDj2Y5mOdSJz_RadgJG8xmUsC9WqwsiE,8342
16
16
  langgraph_api/patch.py,sha256=J0MmcfpZG15SUVaVcI0Z4x_c0-0rbbT7Pwh9fDAQOpA,1566
17
- langgraph_api/queue_entrypoint.py,sha256=4yvvE142Jso8nbhiatvUEQxSfrBu_hr4wNHX2Apq4bM,5667
17
+ langgraph_api/queue_entrypoint.py,sha256=k-Lz-HdaM2ICJacf9yCQw21GlJp00dPoHKuhe1PSrSs,6418
18
18
  langgraph_api/route.py,sha256=EBhELuJ1He-ZYcAnR5YTImcIeDtWthDae5CHELBxPkM,5056
19
19
  langgraph_api/schema.py,sha256=AsgF0dIjBvDd_PDy20mGqB_IkBLgVvSj8qRKG_lPlec,8440
20
20
  langgraph_api/serde.py,sha256=Jkww6ixP5o2YZmnXtM7ihuAYC6YSuNDNPvE-8ILoqVo,5499
@@ -29,7 +29,7 @@ langgraph_api/validation.py,sha256=86jftgOsMa7tkeshBw6imYe7zyUXPoVuf5Voh6dFiR8,5
29
29
  langgraph_api/webhook.py,sha256=SvSM1rdnNtiH4q3JQYmAqJUk2Sable5xAcwOLuRhtlo,1723
30
30
  langgraph_api/worker.py,sha256=FQRw3kL9ynDv_LNgY_OjjPZQBuAvSQpsW6nECnABvDg,15354
31
31
  langgraph_api/api/__init__.py,sha256=raFkYH50tsO-KjRmDbGVoHCuxuH58u1lrZbr-MlITIY,6262
32
- langgraph_api/api/a2a.py,sha256=fxh4FlJNWBkZLc_nqd-gUJV9Ha9hkJwWUWvqZi3CkLM,53277
32
+ langgraph_api/api/a2a.py,sha256=HIHZkLnIcM1u1FJti-L2NH-h1I9BZ_d-QW9z3gFonn8,53995
33
33
  langgraph_api/api/assistants.py,sha256=OX83GCWwGR8MuEJKIzAPEC4LC3Aghs5vD3NGLNnijaU,17268
34
34
  langgraph_api/api/mcp.py,sha256=qe10ZRMN3f-Hli-9TI8nbQyWvMeBb72YB1PZVbyqBQw,14418
35
35
  langgraph_api/api/meta.py,sha256=Qyj6r5czkVJ81tpD6liFY7tlrmFDsiSfBr-4X8HJpRc,4834
@@ -99,8 +99,8 @@ langgraph_runtime/store.py,sha256=7mowndlsIroGHv3NpTSOZDJR0lCuaYMBoTnTrewjslw,11
99
99
  LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
100
100
  logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
101
101
  openapi.json,sha256=21wu-NxdxyTQwZctNcEfRkLMnSBi0QhGAfwq5kg8XNU,172618
102
- langgraph_api-0.4.26.dist-info/METADATA,sha256=o47Y4vD8kXdguVr5XRhq7OrkYD13q61G2k43QYmIx7Y,3893
103
- langgraph_api-0.4.26.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
104
- langgraph_api-0.4.26.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
105
- langgraph_api-0.4.26.dist-info/licenses/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
106
- langgraph_api-0.4.26.dist-info/RECORD,,
102
+ langgraph_api-0.4.28.dist-info/METADATA,sha256=qTje00tzH8lBtQcOUOFLqvnoB8siS_uFb7bUVdNzzmk,3893
103
+ langgraph_api-0.4.28.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
104
+ langgraph_api-0.4.28.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
105
+ langgraph_api-0.4.28.dist-info/licenses/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
106
+ langgraph_api-0.4.28.dist-info/RECORD,,