langgraph-api 0.2.78__py3-none-any.whl → 0.2.83__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.2.78"
1
+ __version__ = "0.2.83"
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import functools
2
3
  import importlib
3
4
  import importlib.util
4
5
  import os
@@ -27,7 +28,11 @@ from langgraph_runtime.database import connect, healthcheck
27
28
  logger = structlog.stdlib.get_logger(__name__)
28
29
 
29
30
 
30
- async def ok(request: Request):
31
+ async def ok(request: Request, *, disabled: bool = False):
32
+ if disabled:
33
+ # We still expose an /ok endpoint even if disable_meta is set so that
34
+ # the operator knows the server started up.
35
+ return JSONResponse({"ok": True})
31
36
  check_db = int(request.query_params.get("check_db", "0")) # must be "0" or "1"
32
37
  if check_db:
33
38
  await healthcheck()
@@ -126,6 +131,13 @@ if HTTP_CONFIG:
126
131
  user_router = load_custom_app(router_import)
127
132
  if not HTTP_CONFIG.get("disable_meta"):
128
133
  routes.extend(meta_routes)
134
+ else:
135
+ # Otherwise the deployment will never be considered healthy
136
+ routes.append(
137
+ Route(
138
+ "/ok", functools.partial(ok, disabled=True), methods=["GET"], name="ok"
139
+ )
140
+ )
129
141
  if protected_routes:
130
142
  routes.append(
131
143
  Mount(
langgraph_api/cli.py CHANGED
@@ -346,7 +346,7 @@ def run_server(
346
346
  - 📚 API Docs: \033[36m{local_url}/docs\033[0m
347
347
 
348
348
  This in-memory server is designed for development and testing.
349
- For production use, please use LangGraph Cloud.
349
+ For production use, please use LangGraph Platform.
350
350
 
351
351
  """
352
352
  logger.info(welcome)
langgraph_api/config.py CHANGED
@@ -155,7 +155,7 @@ POSTGRES_POOL_MAX_SIZE = env("LANGGRAPH_POSTGRES_POOL_MAX_SIZE", cast=int, defau
155
155
  RESUMABLE_STREAM_TTL_SECONDS = env(
156
156
  "RESUMABLE_STREAM_TTL_SECONDS",
157
157
  cast=int,
158
- default=3600, # 1 hour
158
+ default=120, # 2 minutes
159
159
  )
160
160
 
161
161
 
@@ -1,6 +1,8 @@
1
1
  import asyncio
2
+ import json
2
3
  import logging
3
4
  import os
5
+ import re
4
6
  import shutil
5
7
  import ssl
6
8
  from collections import deque
@@ -452,6 +454,26 @@ async def run_js_http_process(paths_str: str, http_config: dict, watch: bool = F
452
454
  attempt += 1
453
455
 
454
456
 
457
+ _BAD_SURROGATE_RE = re.compile(r"\\u[dD][89a-fA-F][0-9a-fA-F]{2}")
458
+ _BAD_ESCAPE_RE = re.compile(r"\\(?![\"\\/bfnrtu])")
459
+
460
+
461
+ def _safe_json_loads(data: bytes):
462
+ """Attempt *orjson.loads* first; if it fails, repair common escape issues.
463
+
464
+ For a time, we had a bug in our surrogate cleanup in serde.py, which
465
+ allowed sequences containing a stray backslash to be stored which would
466
+ then fail upon loading. This function attempts to repair those sequences.
467
+ """
468
+ try:
469
+ return orjson.loads(data)
470
+ except orjson.JSONDecodeError:
471
+ txt = data.decode("utf-8", "replace")
472
+ txt = _BAD_ESCAPE_RE.sub(r"\\\\", txt)
473
+ txt = _BAD_SURROGATE_RE.sub("", txt)
474
+ return json.loads(txt)
475
+
476
+
455
477
  class PassthroughSerialiser(SerializerProtocol):
456
478
  def dumps(self, obj: Any) -> bytes:
457
479
  return json_dumpb(obj)
@@ -460,13 +482,13 @@ class PassthroughSerialiser(SerializerProtocol):
460
482
  return "json", json_dumpb(obj)
461
483
 
462
484
  def loads(self, data: bytes) -> Any:
463
- return orjson.loads(data)
485
+ return _safe_json_loads(data)
464
486
 
465
487
  def loads_typed(self, data: tuple[str, bytes]) -> Any:
466
488
  type, payload = data
467
489
  if type != "json":
468
490
  raise ValueError(f"Unsupported type {type}")
469
- return orjson.loads(payload)
491
+ return _safe_json_loads(payload)
470
492
 
471
493
 
472
494
  def _get_passthrough_checkpointer():
langgraph_api/metadata.py CHANGED
@@ -55,13 +55,15 @@ RUN_COUNTER = 0
55
55
  NODE_COUNTER = 0
56
56
  FROM_TIMESTAMP = datetime.now(UTC).isoformat()
57
57
 
58
- if (
59
- "api.smith.langchain.com" in LANGSMITH_AUTH_ENDPOINT
60
- and not LANGGRAPH_CLOUD_LICENSE_KEY
61
- ):
62
- METADATA_ENDPOINT = LANGSMITH_AUTH_ENDPOINT.rstrip("/") + "/v1/metadata/submit"
63
- else:
64
- METADATA_ENDPOINT = "https://api.smith.langchain.com/v1/metadata/submit"
58
+ # Beacon endpoint for license key submissions
59
+ BEACON_ENDPOINT = "https://api.smith.langchain.com/v1/metadata/submit"
60
+
61
+ # LangChain auth endpoint for API key submissions
62
+ LANGCHAIN_METADATA_ENDPOINT = None
63
+ if LANGSMITH_AUTH_ENDPOINT:
64
+ LANGCHAIN_METADATA_ENDPOINT = (
65
+ LANGSMITH_AUTH_ENDPOINT.rstrip("/") + "/v1/metadata/submit"
66
+ )
65
67
 
66
68
 
67
69
  def incr_runs(*, incr: int = 1) -> None:
@@ -82,8 +84,10 @@ async def metadata_loop() -> None:
82
84
  if not LANGGRAPH_CLOUD_LICENSE_KEY and not LANGSMITH_API_KEY:
83
85
  return
84
86
 
85
- if LANGGRAPH_CLOUD_LICENSE_KEY and not LANGGRAPH_CLOUD_LICENSE_KEY.startswith(
86
- "lcl_"
87
+ if (
88
+ LANGGRAPH_CLOUD_LICENSE_KEY
89
+ and not LANGGRAPH_CLOUD_LICENSE_KEY.startswith("lcl_")
90
+ and not LANGSMITH_API_KEY
87
91
  ):
88
92
  logger.info("Running in air-gapped mode, skipping metadata loop")
89
93
  return
@@ -102,9 +106,7 @@ async def metadata_loop() -> None:
102
106
  NODE_COUNTER = 0
103
107
  FROM_TIMESTAMP = to_timestamp
104
108
 
105
- payload = {
106
- "license_key": LANGGRAPH_CLOUD_LICENSE_KEY,
107
- "api_key": LANGSMITH_API_KEY,
109
+ base_payload = {
108
110
  "from_timestamp": from_timestamp,
109
111
  "to_timestamp": to_timestamp,
110
112
  "tags": {
@@ -130,17 +132,66 @@ async def metadata_loop() -> None:
130
132
  },
131
133
  "logs": [],
132
134
  }
133
- try:
134
- await http_request(
135
- "POST",
136
- METADATA_ENDPOINT,
137
- body=orjson.dumps(payload),
138
- headers={"Content-Type": "application/json"},
139
- )
140
- except Exception as e:
135
+
136
+ # Track successful submissions
137
+ submissions_attempted = []
138
+ submissions_failed = []
139
+
140
+ # 1. Send to beacon endpoint if license key starts with lcl_
141
+ if LANGGRAPH_CLOUD_LICENSE_KEY and LANGGRAPH_CLOUD_LICENSE_KEY.startswith(
142
+ "lcl_"
143
+ ):
144
+ beacon_payload = {
145
+ **base_payload,
146
+ "license_key": LANGGRAPH_CLOUD_LICENSE_KEY,
147
+ }
148
+ submissions_attempted.append("beacon")
149
+ try:
150
+ await http_request(
151
+ "POST",
152
+ BEACON_ENDPOINT,
153
+ body=orjson.dumps(beacon_payload),
154
+ headers={"Content-Type": "application/json"},
155
+ )
156
+ await logger.ainfo("Successfully submitted metadata to beacon endpoint")
157
+ except Exception as e:
158
+ submissions_failed.append("beacon")
159
+ await logger.awarning(
160
+ "Beacon metadata submission failed.", error=str(e)
161
+ )
162
+
163
+ # 2. Send to langchain auth endpoint if API key is set
164
+ if LANGSMITH_API_KEY and LANGCHAIN_METADATA_ENDPOINT:
165
+ langchain_payload = {
166
+ **base_payload,
167
+ "api_key": LANGSMITH_API_KEY,
168
+ }
169
+ submissions_attempted.append("langchain")
170
+ try:
171
+ await http_request(
172
+ "POST",
173
+ LANGCHAIN_METADATA_ENDPOINT,
174
+ body=orjson.dumps(langchain_payload),
175
+ headers={"Content-Type": "application/json"},
176
+ )
177
+ logger.info("Successfully submitted metadata to LangSmith instance")
178
+ except Exception as e:
179
+ submissions_failed.append("langchain")
180
+ await logger.awarning(
181
+ "LangChain metadata submission failed.", error=str(e)
182
+ )
183
+
184
+ if submissions_attempted and len(submissions_failed) == len(
185
+ submissions_attempted
186
+ ):
141
187
  # retry on next iteration
142
188
  incr_runs(incr=runs)
143
189
  incr_nodes("", incr=nodes)
144
190
  FROM_TIMESTAMP = from_timestamp
145
- await logger.ainfo("Metadata submission skipped.", error=str(e))
191
+ await logger.awarning(
192
+ "All metadata submissions failed, will retry",
193
+ attempted=submissions_attempted,
194
+ failed=submissions_failed,
195
+ )
196
+
146
197
  await asyncio.sleep(INTERVAL)
langgraph_api/serde.py CHANGED
@@ -123,16 +123,18 @@ def _sanitise(o: Any) -> Any:
123
123
 
124
124
  def json_dumpb(obj) -> bytes:
125
125
  try:
126
- return orjson.dumps(obj, default=default, option=_option).replace(
127
- rb"\u0000", b""
128
- ) # null unicode char not allowed in json
126
+ dumped = orjson.dumps(obj, default=default, option=_option)
129
127
  except TypeError as e:
130
128
  if "surrogates not allowed" not in str(e):
131
129
  raise
132
- surrogate_sanitized = _sanitise(obj)
133
- return orjson.dumps(
134
- surrogate_sanitized, default=default, option=_option
135
- ).replace(rb"\u0000", b"")
130
+ dumped = orjson.dumps(_sanitise(obj), default=default, option=_option)
131
+ return (
132
+ # Unfortunately simply doing ``.replace(rb"\\u0000", b"")`` on
133
+ # the dumped bytes can leave an **orphaned back-slash** (e.g. ``\\q``)
134
+ # which makes the resulting JSON invalid. The fix is to delete the *double*
135
+ # back-slash form **first**, then (optionally) the single-escapes.
136
+ dumped.replace(rb"\\u0000", b"").replace(rb"\u0000", b"")
137
+ )
136
138
 
137
139
 
138
140
  def json_loads(content: bytes | Fragment | dict) -> Any:
@@ -154,6 +156,10 @@ class Serializer(JsonPlusSerializer):
154
156
  except TypeError:
155
157
  return "pickle", cloudpickle.dumps(obj)
156
158
 
159
+ def dumps(self, obj: Any) -> bytes:
160
+ # See comment above (in json_dumpb)
161
+ return super().dumps(obj).replace(rb"\\u0000", b"").replace(rb"\u0000", b"")
162
+
157
163
  def loads_typed(self, data: tuple[str, bytes]) -> Any:
158
164
  if data[0] == "pickle":
159
165
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-api
3
- Version: 0.2.78
3
+ Version: 0.2.83
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,21 +1,21 @@
1
- langgraph_api/__init__.py,sha256=bKvBkq9XZLzYHkMc6R4SvKjEY40WZWKPZT_0VAK5MRg,23
1
+ langgraph_api/__init__.py,sha256=pjPizgc1F0inosPx3drrKz6OVCqyJA5RpGxHjw1BzHI,23
2
2
  langgraph_api/asgi_transport.py,sha256=eqifhHxNnxvI7jJqrY1_8RjL4Fp9NdN4prEub2FWBt8,5091
3
3
  langgraph_api/asyncio.py,sha256=qrYEqPRrqtGq7E7KjcMC-ALyN79HkRnmp9rM2TAw9L8,9404
4
- langgraph_api/cli.py,sha256=13mKb-WT7fGx_yqcbWITPB9ICEHCrPzIP1ddZ5RbXbY,16015
4
+ langgraph_api/cli.py,sha256=-R0fvxg4KNxTkSe7xvDZruF24UMhStJYjpAYlUx3PBk,16018
5
5
  langgraph_api/command.py,sha256=3O9v3i0OPa96ARyJ_oJbLXkfO8rPgDhLCswgO9koTFA,768
6
- langgraph_api/config.py,sha256=DQxZgt1GkQLMYS_Kd8sodSI9znYzXn-XTfZdzdblPYE,11819
6
+ langgraph_api/config.py,sha256=-Ij90EaSCW4woYQug0aUp6zVTR0W1oJyYS6jY6hP2ao,11821
7
7
  langgraph_api/cron_scheduler.py,sha256=CiwZ-U4gDOdG9zl9dlr7mH50USUgNB2Fvb8YTKVRBN4,2625
8
8
  langgraph_api/errors.py,sha256=zlnl3xXIwVG0oGNKKpXf1an9Rn_SBDHSyhe53hU6aLw,1858
9
9
  langgraph_api/graph.py,sha256=pw_3jVZNe0stO5-Y8kLUuC8EJ5tFqdLu9fLpwUz4Hc4,23574
10
10
  langgraph_api/http.py,sha256=WsO9u4s2RHMBaM2No05e5h1Ke3TT7u-_-RP1ITxQ410,5571
11
11
  langgraph_api/http_metrics.py,sha256=VgM45yU1FkXuI9CIOE_astxAAu2G-OJ42BRbkcos_CQ,5555
12
12
  langgraph_api/logging.py,sha256=LL2LNuMYFrqDhG_KbyKy9AoAPghcdlFj2T50zMyPddk,4182
13
- langgraph_api/metadata.py,sha256=WFrqK32h0LHjGFOs_f7vEbu2RQVHCXuxwGSYxFOJMs0,4707
13
+ langgraph_api/metadata.py,sha256=lfovneEMLA5vTNa61weMkQkiZCtwo-qdwFwqNSj5qVs,6638
14
14
  langgraph_api/patch.py,sha256=Dgs0PXHytekX4SUL6KsjjN0hHcOtGLvv1GRGbh6PswU,1408
15
15
  langgraph_api/queue_entrypoint.py,sha256=hC8j-A4cUxibusiiPJBlK0mkmChNZxNcXn5GVwL0yic,4889
16
16
  langgraph_api/route.py,sha256=4VBkJMeusfiZtLzyUaKm1HwLHTq0g15y2CRiRhM6xyA,4773
17
17
  langgraph_api/schema.py,sha256=a6it0h9ku4jrTXiW9MhnGok_wignyQ4cXBra67FiryM,5678
18
- langgraph_api/serde.py,sha256=8fQXg7T7RVUqj_jgOoSOJrWVpQDW0qJKjAjSsEhPHo4,4803
18
+ langgraph_api/serde.py,sha256=0ALETUn582vNF-m0l_WOZGF_scL1VPA39fDkwMJQPrg,5187
19
19
  langgraph_api/server.py,sha256=Z_VL-kIphybTRDWBIqHMfRhgCmAFyTRqAGlgnHQF0Zg,6973
20
20
  langgraph_api/sse.py,sha256=SLdtZmTdh5D8fbWrQjuY9HYLd2dg8Rmi6ZMmFMVc2iE,4204
21
21
  langgraph_api/state.py,sha256=NLl5YgLKppHJ7zfF0bXjsroXmIGCZez0IlDAKNGVy0g,2365
@@ -26,7 +26,7 @@ langgraph_api/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  langgraph_api/validation.py,sha256=zMuKmwUEBjBgFMwAaeLZmatwGVijKv2sOYtYg7gfRtc,4950
27
27
  langgraph_api/webhook.py,sha256=VCJp4dI5E1oSJ15XP34cnPiOi8Ya8Q1BnBwVGadOpLI,1636
28
28
  langgraph_api/worker.py,sha256=fL0pNEW9FaldEREq_4L-ivkxTr2R_rEpmVMi-zRx__U,14226
29
- langgraph_api/api/__init__.py,sha256=YVzpbn5IQotvuuLG9fhS9QMrxXfP4s4EpEMG0n4q3Nw,5625
29
+ langgraph_api/api/__init__.py,sha256=WHy6oNLWtH1K7AxmmsU9RD-Vm6WP-Ov16xS8Ey9YCmQ,6090
30
30
  langgraph_api/api/assistants.py,sha256=w7nXjEknDVHSuP228S8ZLh4bG0nRGnSwVP9pECQOK90,16247
31
31
  langgraph_api/api/mcp.py,sha256=RvRYgANqRzNQzSmgjNkq4RlKTtoEJYil04ot9lsmEtE,14352
32
32
  langgraph_api/api/meta.py,sha256=LnDyfO-YJx8WbFS7VR6LCeB8zL5vxH6vFHd87aqHnjk,4106
@@ -53,7 +53,7 @@ langgraph_api/js/client.mts,sha256=N9CTH7mbXGSD-gpv-XyruYsHI-rgrObL8cQoAp5s3_U,3
53
53
  langgraph_api/js/errors.py,sha256=Cm1TKWlUCwZReDC5AQ6SgNIVGD27Qov2xcgHyf8-GXo,361
54
54
  langgraph_api/js/global.d.ts,sha256=j4GhgtQSZ5_cHzjSPcHgMJ8tfBThxrH-pUOrrJGteOU,196
55
55
  langgraph_api/js/package.json,sha256=BpNAO88mbE-Gv4WzQfj1TLktCWGqm6XBqI892ObuOUw,1333
56
- langgraph_api/js/remote.py,sha256=DmqT2BtkL5cDkyC9YBQo-ekyYhSdc22_Dv7e61UdUtw,36130
56
+ langgraph_api/js/remote.py,sha256=B_0cP34AGTW9rL_hJyKIh3P6Z4TvNYNNyc7S2_SOWt4,36880
57
57
  langgraph_api/js/schema.py,sha256=7idnv7URlYUdSNMBXQcw7E4SxaPxCq_Oxwnlml8q5ik,408
58
58
  langgraph_api/js/sse.py,sha256=lsfp4nyJyA1COmlKG9e2gJnTttf_HGCB5wyH8OZBER8,4105
59
59
  langgraph_api/js/tsconfig.json,sha256=imCYqVnqFpaBoZPx8k1nO4slHIWBFsSlmCYhO73cpBs,341
@@ -90,8 +90,8 @@ langgraph_runtime/store.py,sha256=7mowndlsIroGHv3NpTSOZDJR0lCuaYMBoTnTrewjslw,11
90
90
  LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
91
91
  logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
92
92
  openapi.json,sha256=p5tn_cNRiFA0HN3L6JfC9Nm16Hgv-BxvAQcJymKhVWI,143296
93
- langgraph_api-0.2.78.dist-info/METADATA,sha256=_kuDNqhROe_phgaf29aHoymKoMCqRhfasQde2rwbASI,3891
94
- langgraph_api-0.2.78.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
95
- langgraph_api-0.2.78.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
96
- langgraph_api-0.2.78.dist-info/licenses/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
97
- langgraph_api-0.2.78.dist-info/RECORD,,
93
+ langgraph_api-0.2.83.dist-info/METADATA,sha256=KgKghdie5R6f4M5GUiDpMumPq7bShgf7TmuSpRRZzps,3891
94
+ langgraph_api-0.2.83.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
95
+ langgraph_api-0.2.83.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
96
+ langgraph_api-0.2.83.dist-info/licenses/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
97
+ langgraph_api-0.2.83.dist-info/RECORD,,