langgraph-api 0.0.25__tar.gz → 0.0.27__tar.gz

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.

Files changed (95) hide show
  1. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/PKG-INFO +9 -9
  2. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/README.md +1 -1
  3. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/api/__init__.py +3 -0
  4. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/api/meta.py +1 -0
  5. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/cli.py +72 -57
  6. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/config.py +9 -0
  7. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/build.mts +5 -2
  8. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/client.mts +17 -9
  9. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/client.new.mts +18 -4
  10. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/package.json +4 -2
  11. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/remote_old.py +9 -6
  12. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/src/graph.mts +5 -4
  13. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/yarn.lock +5 -0
  14. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/server.py +4 -1
  15. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/pyproject.toml +8 -8
  16. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/LICENSE +0 -0
  17. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/__init__.py +0 -0
  18. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/api/assistants.py +0 -0
  19. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/api/openapi.py +0 -0
  20. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/api/runs.py +0 -0
  21. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/api/store.py +0 -0
  22. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/api/threads.py +0 -0
  23. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/asyncio.py +0 -0
  24. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/auth/__init__.py +0 -0
  25. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/auth/custom.py +0 -0
  26. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/auth/langsmith/__init__.py +0 -0
  27. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/auth/langsmith/backend.py +0 -0
  28. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/auth/langsmith/client.py +0 -0
  29. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/auth/middleware.py +0 -0
  30. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/auth/noop.py +0 -0
  31. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/auth/studio_user.py +0 -0
  32. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/cron_scheduler.py +0 -0
  33. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/errors.py +0 -0
  34. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/graph.py +0 -0
  35. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/http.py +0 -0
  36. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/.gitignore +0 -0
  37. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/base.py +0 -0
  38. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/errors.py +0 -0
  39. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/global.d.ts +0 -0
  40. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/remote.py +0 -0
  41. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/remote_new.py +0 -0
  42. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/schema.py +0 -0
  43. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/server_sent_events.py +0 -0
  44. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/src/hooks.mjs +0 -0
  45. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/src/parser/parser.mts +0 -0
  46. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/src/parser/parser.worker.mjs +0 -0
  47. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/src/schema/types.mts +0 -0
  48. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/src/schema/types.template.mts +0 -0
  49. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/src/utils/importMap.mts +0 -0
  50. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
  51. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/src/utils/serde.mts +0 -0
  52. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/tests/api.test.mts +0 -0
  53. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/tests/compose-postgres.yml +0 -0
  54. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/tests/graphs/.gitignore +0 -0
  55. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/tests/graphs/agent.mts +0 -0
  56. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/tests/graphs/delay.mts +0 -0
  57. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/tests/graphs/error.mts +0 -0
  58. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/tests/graphs/langgraph.json +0 -0
  59. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/tests/graphs/nested.mts +0 -0
  60. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/tests/graphs/package.json +0 -0
  61. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/tests/graphs/weather.mts +0 -0
  62. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/tests/graphs/yarn.lock +0 -0
  63. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/tests/parser.test.mts +0 -0
  64. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/js/tests/utils.mts +0 -0
  65. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/lifespan.py +0 -0
  66. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/logging.py +0 -0
  67. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/metadata.py +0 -0
  68. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/middleware/__init__.py +0 -0
  69. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/middleware/http_logger.py +0 -0
  70. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/middleware/private_network.py +0 -0
  71. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/models/__init__.py +0 -0
  72. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/models/run.py +0 -0
  73. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/patch.py +0 -0
  74. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/queue.py +0 -0
  75. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/route.py +0 -0
  76. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/schema.py +0 -0
  77. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/serde.py +0 -0
  78. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/sse.py +0 -0
  79. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/state.py +0 -0
  80. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/stream.py +0 -0
  81. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/utils.py +0 -0
  82. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_api/validation.py +0 -0
  83. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_license/__init__.py +0 -0
  84. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_license/middleware.py +0 -0
  85. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_license/validation.py +0 -0
  86. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_storage/__init__.py +0 -0
  87. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_storage/checkpoint.py +0 -0
  88. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_storage/database.py +0 -0
  89. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_storage/ops.py +0 -0
  90. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_storage/queue.py +0 -0
  91. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_storage/retry.py +0 -0
  92. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_storage/store.py +0 -0
  93. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/langgraph_storage/ttl_dict.py +0 -0
  94. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/logging.json +0 -0
  95. {langgraph_api-0.0.25 → langgraph_api-0.0.27}/openapi.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langgraph-api
3
- Version: 0.0.25
3
+ Version: 0.0.27
4
4
  Summary:
5
5
  License: Elastic-2.0
6
6
  Author: Nuno Campos
@@ -11,26 +11,26 @@ Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Requires-Dist: cryptography (>=43.0.3,<44.0.0)
14
- Requires-Dist: httpx (>=0.27.0)
15
- Requires-Dist: jsonschema-rs (>=0.25.0,<0.26.0)
14
+ Requires-Dist: httpx (>=0.25.0)
15
+ Requires-Dist: jsonschema-rs (>=0.20.0,<0.21.0)
16
16
  Requires-Dist: langchain-core (>=0.2.38,<0.4.0)
17
- Requires-Dist: langgraph (>=0.2.56,<0.3.0)
17
+ Requires-Dist: langgraph (>=0.2.56,<0.4.0)
18
18
  Requires-Dist: langgraph-checkpoint (>=2.0.15,<3.0)
19
- Requires-Dist: langgraph-sdk (>=0.1.51,<0.2.0)
19
+ Requires-Dist: langgraph-sdk (>=0.1.53,<0.2.0)
20
20
  Requires-Dist: langsmith (>=0.1.63,<0.4.0)
21
- Requires-Dist: orjson (>=3.10.1)
21
+ Requires-Dist: orjson (>=3.9.7)
22
22
  Requires-Dist: pyjwt (>=2.9.0,<3.0.0)
23
23
  Requires-Dist: sse-starlette (>=2.1.0,<2.2.0)
24
24
  Requires-Dist: starlette (>=0.38.6)
25
- Requires-Dist: structlog (>=24.4.0,<25.0.0)
26
- Requires-Dist: tenacity (>=8.3.0,<10)
25
+ Requires-Dist: structlog (>=23.1.0,<24.0.0)
26
+ Requires-Dist: tenacity (>=8.0.0)
27
27
  Requires-Dist: uvicorn (>=0.26.0)
28
28
  Requires-Dist: watchfiles (>=0.13)
29
29
  Description-Content-Type: text/markdown
30
30
 
31
31
  # LangGraph API
32
32
 
33
- This package implements the LangGraph API for rapid development and testing. Build and iterate on LangGraph agents with a tight feedback loop. The sesrver is backed by a predominently in-memory data store that is persisted to local disk when the server is restarted.
33
+ This package implements the LangGraph API for rapid development and testing. Build and iterate on LangGraph agents with a tight feedback loop. The server is backed by a predominently in-memory data store that is persisted to local disk when the server is restarted.
34
34
 
35
35
  For production use, see the various [deployment options](https://langchain-ai.github.io/langgraph/concepts/deployment_options/) for the LangGraph API, which are backed by a production-grade database.
36
36
 
@@ -1,6 +1,6 @@
1
1
  # LangGraph API
2
2
 
3
- This package implements the LangGraph API for rapid development and testing. Build and iterate on LangGraph agents with a tight feedback loop. The sesrver is backed by a predominently in-memory data store that is persisted to local disk when the server is restarted.
3
+ This package implements the LangGraph API for rapid development and testing. Build and iterate on LangGraph agents with a tight feedback loop. The server is backed by a predominently in-memory data store that is persisted to local disk when the server is restarted.
4
4
 
5
5
  For production use, see the various [deployment options](https://langchain-ai.github.io/langgraph/concepts/deployment_options/) for the LangGraph API, which are backed by a production-grade database.
6
6
 
@@ -78,6 +78,7 @@ def load_custom_app(app_import: str) -> Starlette | None:
78
78
  logger.info(f"Loading custom app from {app_import}")
79
79
  path, name = app_import.rsplit(":", 1)
80
80
  try:
81
+ os.environ["__LANGGRAPH_DEFER_LOOPBACK_TRANSPORT"] = "true"
81
82
  if os.path.isfile(path) or path.endswith(".py"):
82
83
  # Import from file path using a unique module name.
83
84
  spec = importlib.util.spec_from_file_location("user_router_module", path)
@@ -100,6 +101,8 @@ def load_custom_app(app_import: str) -> Starlette | None:
100
101
  raise ImportError(f"Failed to import app module '{path}'") from e
101
102
  except AttributeError as e:
102
103
  raise AttributeError(f"App '{name}' not found in module '{path}'") from e
104
+ finally:
105
+ os.environ.pop("__LANGGRAPH_DEFER_LOOPBACK_TRANSPORT", None)
103
106
  return user_router
104
107
 
105
108
 
@@ -19,6 +19,7 @@ async def meta_info(request: ApiRequest):
19
19
  "flags": {
20
20
  "assistants": True,
21
21
  "crons": plus and config.FF_CRONS_ENABLED,
22
+ "langsmith": bool(config.LANGSMITH_API_KEY) and bool(config.TRACING),
22
23
  }
23
24
  }
24
25
  )
@@ -16,6 +16,16 @@ logging.basicConfig(level=logging.INFO)
16
16
  logger = logging.getLogger(__name__)
17
17
 
18
18
 
19
+ def _get_ls_origin() -> str | None:
20
+ from langsmith.client import Client
21
+ from langsmith.utils import tracing_is_enabled
22
+
23
+ if not tracing_is_enabled():
24
+ return
25
+ client = Client()
26
+ return client._host_url
27
+
28
+
19
29
  def _get_org_id() -> str | None:
20
30
  from langsmith.client import Client
21
31
  from langsmith.utils import tracing_is_enabled
@@ -201,49 +211,67 @@ def run_server(
201
211
  logger.info("Debugger attached. Starting server...")
202
212
 
203
213
  local_url = f"http://{host}:{port}"
204
- studio_url = f"https://smith.langchain.com/studio/?baseUrl={local_url}"
205
-
206
- def _open_browser():
207
- nonlocal studio_url
208
- import time
209
- import urllib.request
210
- import webbrowser
211
- from concurrent.futures import ThreadPoolExecutor
212
-
213
- thread_logger = logging.getLogger("browser_opener")
214
- if not thread_logger.handlers:
215
- handler = logging.StreamHandler()
216
- handler.setFormatter(logging.Formatter("%(message)s"))
217
- thread_logger.addHandler(handler)
218
-
219
- with ThreadPoolExecutor(max_workers=1) as executor:
220
- org_id_future = executor.submit(_get_org_id)
221
-
222
- while True:
223
- try:
224
- with urllib.request.urlopen(f"{local_url}/ok") as response:
225
- if response.status == 200:
226
- try:
227
- org_id = org_id_future.result(timeout=3.0)
228
- if org_id:
229
- studio_url = f"https://smith.langchain.com/studio/?baseUrl={local_url}&organizationId={org_id}"
230
- except TimeoutError as e:
231
- thread_logger.debug(
232
- f"Failed to get organization ID: {str(e)}"
214
+
215
+ with patch_environment(
216
+ MIGRATIONS_PATH="__inmem",
217
+ DATABASE_URI=":memory:",
218
+ REDIS_URI="fake",
219
+ N_JOBS_PER_WORKER=str(n_jobs_per_worker if n_jobs_per_worker else 1),
220
+ LANGGRAPH_STORE=json.dumps(store) if store else None,
221
+ LANGSERVE_GRAPHS=json.dumps(graphs) if graphs else None,
222
+ LANGSMITH_LANGGRAPH_API_VARIANT="local_dev",
223
+ LANGGRAPH_AUTH=json.dumps(auth) if auth else None,
224
+ LANGGRAPH_HTTP=json.dumps(http) if http else None,
225
+ # See https://developer.chrome.com/blog/private-network-access-update-2024-03
226
+ ALLOW_PRIVATE_NETWORK="true",
227
+ **(env_vars or {}),
228
+ ):
229
+ studio_origin = _get_ls_origin() or "https://smith.langchain.com"
230
+ studio_url = f"{studio_origin}/studio/?baseUrl={local_url}"
231
+
232
+ def _open_browser():
233
+ nonlocal studio_origin, studio_url
234
+ import time
235
+ import urllib.request
236
+ import webbrowser
237
+ from concurrent.futures import ThreadPoolExecutor
238
+
239
+ thread_logger = logging.getLogger("browser_opener")
240
+ if not thread_logger.handlers:
241
+ handler = logging.StreamHandler()
242
+ handler.setFormatter(logging.Formatter("%(message)s"))
243
+ thread_logger.addHandler(handler)
244
+
245
+ with ThreadPoolExecutor(max_workers=1) as executor:
246
+ org_id_future = executor.submit(_get_org_id)
247
+
248
+ while True:
249
+ try:
250
+ with urllib.request.urlopen(f"{local_url}/ok") as response:
251
+ if response.status == 200:
252
+ try:
253
+ org_id = org_id_future.result(timeout=3.0)
254
+ if org_id:
255
+ studio_url = f"{studio_origin}/studio/?baseUrl={local_url}&organizationId={org_id}"
256
+ except TimeoutError as e:
257
+ thread_logger.debug(
258
+ f"Failed to get organization ID: {str(e)}"
259
+ )
260
+ pass
261
+ thread_logger.info(
262
+ f"Server started in {time.time() - start_time:.2f}s"
233
263
  )
234
- pass
235
- thread_logger.info(
236
- f"Server started in {time.time() - start_time:.2f}s"
237
- )
238
- thread_logger.info("🎨 Opening Studio in your browser...")
239
- thread_logger.info("URL: " + studio_url)
240
- webbrowser.open(studio_url)
241
- return
242
- except urllib.error.URLError:
243
- pass
244
- time.sleep(0.1)
245
-
246
- welcome = f"""
264
+ thread_logger.info(
265
+ "🎨 Opening Studio in your browser..."
266
+ )
267
+ thread_logger.info("URL: " + studio_url)
268
+ webbrowser.open(studio_url)
269
+ return
270
+ except urllib.error.URLError:
271
+ pass
272
+ time.sleep(0.1)
273
+
274
+ welcome = f"""
247
275
 
248
276
  Welcome to
249
277
 
@@ -259,21 +287,8 @@ This in-memory server is designed for development and testing.
259
287
  For production use, please use LangGraph Cloud.
260
288
 
261
289
  """
262
- logger.info(welcome)
263
- with patch_environment(
264
- MIGRATIONS_PATH="__inmem",
265
- DATABASE_URI=":memory:",
266
- REDIS_URI="fake",
267
- N_JOBS_PER_WORKER=str(n_jobs_per_worker if n_jobs_per_worker else 1),
268
- LANGGRAPH_STORE=json.dumps(store) if store else None,
269
- LANGSERVE_GRAPHS=json.dumps(graphs) if graphs else None,
270
- LANGSMITH_LANGGRAPH_API_VARIANT="local_dev",
271
- LANGGRAPH_AUTH=json.dumps(auth) if auth else None,
272
- LANGGRAPH_HTTP=json.dumps(http) if http else None,
273
- # See https://developer.chrome.com/blog/private-network-access-update-2024-03
274
- ALLOW_PRIVATE_NETWORK="true",
275
- **(env_vars or {}),
276
- ):
290
+ logger.info(welcome)
291
+
277
292
  if open_browser:
278
293
  threading.Thread(target=_open_browser, daemon=True).start()
279
294
  supported_kwargs = {
@@ -166,9 +166,18 @@ if (
166
166
  LANGSMITH_API_KEY
167
167
  and not getenv("LANGCHAIN_TRACING_V2")
168
168
  and not getenv("LANGCHAIN_TRACING")
169
+ and not getenv("LANGSMITH_TRACING_V2")
170
+ and not getenv("LANGSMITH_TRACING")
169
171
  ):
170
172
  environ["LANGCHAIN_TRACING_V2"] = "true"
171
173
 
174
+ TRACING = (
175
+ env("LANGCHAIN_TRACING_V2", cast=bool, default=None)
176
+ or env("LANGCHAIN_TRACING", cast=bool, default=None)
177
+ or env("LANGSMITH_TRACING_V2", cast=bool, default=None)
178
+ or env("LANGSMITH_TRACING", cast=bool, default=None)
179
+ )
180
+
172
181
  # if variant is "licensed", update to "local" if using LANGSMITH_API_KEY instead
173
182
 
174
183
  if getenv("LANGSMITH_LANGGRAPH_API_VARIANT") == "licensed" and LANGSMITH_API_KEY:
@@ -17,7 +17,7 @@ async function main() {
17
17
  z.record(z.string()).parse(JSON.parse(process.env.LANGSERVE_GRAPHS))
18
18
  );
19
19
 
20
- const GRAPH_SCHEMAS: Record<string, Record<string, GraphSchema>> = {};
20
+ const GRAPH_SCHEMAS: Record<string, Record<string, GraphSchema> | false> = {};
21
21
 
22
22
  try {
23
23
  await Promise.all(
@@ -29,9 +29,12 @@ async function main() {
29
29
 
30
30
  try {
31
31
  console.info(`[${graphId}]: Extracting schema`);
32
- GRAPH_SCHEMAS[graphId] = await runGraphSchemaWorker(spec);
32
+ GRAPH_SCHEMAS[graphId] = await runGraphSchemaWorker(spec, {
33
+ timeoutMs: 120_000,
34
+ });
33
35
  } catch (error) {
34
36
  console.error(`[${graphId}]: Error extracting schema: ${error}`);
37
+ GRAPH_SCHEMAS[graphId] = false;
35
38
  }
36
39
  })
37
40
  );
@@ -43,6 +43,8 @@ import {
43
43
  GraphSpec,
44
44
  filterValidGraphSpecs,
45
45
  } from "./src/graph.mts";
46
+ import { asyncExitHook, gracefulExit } from "exit-hook";
47
+ import { awaitAllCallbacks } from "@langchain/core/callbacks/promises";
46
48
 
47
49
  const logger = createLogger({
48
50
  level: "debug",
@@ -76,7 +78,7 @@ const logger = createLogger({
76
78
  ],
77
79
  });
78
80
 
79
- let GRAPH_SCHEMA: Record<string, Record<string, GraphSchema>> = {};
81
+ let GRAPH_SCHEMA: Record<string, Record<string, GraphSchema> | false> = {};
80
82
  const GRAPH_RESOLVED: Record<string, CompiledGraph<string>> = {};
81
83
  const GRAPH_SPEC: Record<string, GraphSpec> = {};
82
84
 
@@ -92,6 +94,11 @@ async function getOrExtractSchema(graphId: string) {
92
94
  }
93
95
 
94
96
  if (!GRAPH_SCHEMA[graphId]) {
97
+ // This is only set during build phase
98
+ if (GRAPH_SCHEMA[graphId] === false) {
99
+ throw new Error(`Failed to locate schema for "${graphId}"`);
100
+ }
101
+
95
102
  try {
96
103
  const timer = logger.startTimer();
97
104
  GRAPH_SCHEMA[graphId] = await runGraphSchemaWorker(GRAPH_SPEC[graphId]);
@@ -604,12 +611,12 @@ const GetGraphPayload = z.object({
604
611
  async function getGraphRequest(rawPayload: z.infer<typeof GetGraphPayload>) {
605
612
  const { graph_id: graphId, ...payload } = rawPayload;
606
613
  const graph = getGraph(graphId);
607
- return graph
608
- .getGraph({
609
- ...getRunnableConfig(payload.config),
610
- xray: payload.xray ?? undefined,
611
- })
612
- .toJSON();
614
+
615
+ const drawable = await graph.getGraphAsync({
616
+ ...getRunnableConfig(payload.config),
617
+ xray: payload.xray ?? undefined,
618
+ });
619
+ return drawable.toJSON();
613
620
  }
614
621
 
615
622
  const GetSubgraphsPayload = z.object({
@@ -630,7 +637,7 @@ async function getSubgraphsRequest(
630
637
 
631
638
  if (!rootGraphId) throw new Error("Failed to find root graph");
632
639
 
633
- for (const [name] of graph.getSubgraphs(
640
+ for await (const [name] of graph.getSubgraphsAsync(
634
641
  payload.namespace ?? undefined,
635
642
  payload.recurse ?? undefined
636
643
  )) {
@@ -831,7 +838,8 @@ async function main() {
831
838
 
832
839
  process.on("uncaughtExceptionMonitor", (error) => {
833
840
  logger.error(error);
834
- process.exit(1);
841
+ gracefulExit();
835
842
  });
836
843
 
844
+ asyncExitHook(() => awaitAllCallbacks(), { wait: 3_000 });
837
845
  main();
@@ -39,6 +39,8 @@ import {
39
39
  GraphSpec,
40
40
  filterValidGraphSpecs,
41
41
  } from "./src/graph.mts";
42
+ import { asyncExitHook, gracefulExit } from "exit-hook";
43
+ import { awaitAllCallbacks } from "@langchain/core/callbacks/promises";
42
44
 
43
45
  const logger = createLogger({
44
46
  level: "debug",
@@ -72,7 +74,7 @@ const logger = createLogger({
72
74
  ],
73
75
  });
74
76
 
75
- let GRAPH_SCHEMA: Record<string, Record<string, GraphSchema>> = {};
77
+ let GRAPH_SCHEMA: Record<string, Record<string, GraphSchema> | false> = {};
76
78
  const GRAPH_RESOLVED: Record<string, CompiledGraph<string>> = {};
77
79
  const GRAPH_SPEC: Record<string, GraphSpec> = {};
78
80
 
@@ -87,6 +89,11 @@ async function getOrExtractSchema(graphId: string) {
87
89
  }
88
90
 
89
91
  if (!GRAPH_SCHEMA[graphId]) {
92
+ // This is only set during build phase
93
+ if (GRAPH_SCHEMA[graphId] === false) {
94
+ throw new Error(`Failed to locate schema for "${graphId}"`);
95
+ }
96
+
90
97
  try {
91
98
  const timer = logger.startTimer();
92
99
  GRAPH_SCHEMA[graphId] = await runGraphSchemaWorker(GRAPH_SPEC[graphId]);
@@ -208,6 +215,7 @@ async function* getRouterPackets(): AsyncGenerator<RouterPacket> {
208
215
  }
209
216
  }
210
217
 
218
+ const dealerSendQueue = new PQueue({ concurrency: 1 });
211
219
  async function sendRecv<T = any>(
212
220
  method: `${"checkpointer" | "store"}_${string}`,
213
221
  data: unknown
@@ -216,7 +224,9 @@ async function sendRecv<T = any>(
216
224
  createFuture(id);
217
225
 
218
226
  try {
219
- await remoteDealer.send(packPlain({ method, id, data }));
227
+ await dealerSendQueue.add(() =>
228
+ remoteDealer.send(packPlain({ method, id, data }))
229
+ );
220
230
  queue.add(scheduleRead, { timeout: 10_000, throwOnTimeout: true });
221
231
 
222
232
  return (await remoteTasks[id].promise) as T;
@@ -225,6 +235,7 @@ async function sendRecv<T = any>(
225
235
  }
226
236
  }
227
237
 
238
+ const routerSendQueue = new PQueue({ concurrency: 1 });
228
239
  const createSendWithTTL = (packet: RouterPacket) => {
229
240
  const { header, input } = packet;
230
241
  const { method, id } = input;
@@ -232,7 +243,9 @@ const createSendWithTTL = (packet: RouterPacket) => {
232
243
  let timer: NodeJS.Timeout | undefined = undefined;
233
244
  const sendData = async (result?: { success: boolean; data: unknown }) => {
234
245
  clearTimeout(timer);
235
- await clientRouter.send([header, pack({ method, id, ...result })]);
246
+ await routerSendQueue.add(() =>
247
+ clientRouter.send([header, pack({ method, id, ...result })])
248
+ );
236
249
  timer = setTimeout(() => sendData(), CLIENT_HEARTBEAT_INTERVAL_MS);
237
250
  };
238
251
 
@@ -855,7 +868,8 @@ async function main() {
855
868
 
856
869
  process.on("uncaughtExceptionMonitor", (error) => {
857
870
  logger.error(error);
858
- process.exit(1);
871
+ gracefulExit();
859
872
  });
860
873
 
874
+ asyncExitHook(() => awaitAllCallbacks(), { wait: 3_000 });
861
875
  main();
@@ -14,6 +14,7 @@
14
14
  "@types/json-schema": "^7.0.15",
15
15
  "@typescript/vfs": "^1.6.0",
16
16
  "dedent": "^1.5.3",
17
+ "exit-hook": "^4.0.0",
17
18
  "hono": "^4.5.4",
18
19
  "p-queue": "^8.0.1",
19
20
  "p-retry": "^6.2.0",
@@ -31,5 +32,6 @@
31
32
  "postgres": "^3.4.4",
32
33
  "prettier": "^3.3.3",
33
34
  "vitest": "^3.0.4"
34
- }
35
- }
35
+ },
36
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
37
+ }
@@ -2,9 +2,11 @@ import asyncio
2
2
  import base64
3
3
  import os
4
4
  import shutil
5
+ import ssl
5
6
  from collections.abc import AsyncIterator
6
7
  from typing import Any, Literal
7
8
 
9
+ import certifi
8
10
  import httpx
9
11
  import orjson
10
12
  import structlog
@@ -38,6 +40,7 @@ logger = structlog.stdlib.get_logger(__name__)
38
40
 
39
41
  GRAPH_SOCKET = "./graph.sock"
40
42
  REMOTE_SOCKET = "./checkpointer.sock"
43
+ SSL = ssl.create_default_context(cafile=certifi.where())
41
44
 
42
45
 
43
46
  async def _client_stream(method: str, data: dict[str, Any]):
@@ -50,7 +53,7 @@ async def _client_stream(method: str, data: dict[str, Any]):
50
53
  base_url="http://graph",
51
54
  timeout=httpx.Timeout(None),
52
55
  limits=httpx.Limits(),
53
- transport=httpx.AsyncHTTPTransport(uds=GRAPH_SOCKET),
56
+ transport=httpx.AsyncHTTPTransport(uds=GRAPH_SOCKET, verify=SSL),
54
57
  )
55
58
 
56
59
  async with aconnect_sse(
@@ -77,7 +80,7 @@ async def _client_invoke(method: str, data: dict[str, Any]):
77
80
  base_url="http://graph",
78
81
  timeout=httpx.Timeout(None),
79
82
  limits=httpx.Limits(),
80
- transport=httpx.AsyncHTTPTransport(uds=GRAPH_SOCKET),
83
+ transport=httpx.AsyncHTTPTransport(uds=GRAPH_SOCKET, verify=SSL),
81
84
  )
82
85
 
83
86
  res = await _async_client.post(
@@ -612,12 +615,12 @@ async def wait_until_js_ready():
612
615
  async with (
613
616
  httpx.AsyncClient(
614
617
  base_url="http://graph",
615
- transport=httpx.AsyncHTTPTransport(uds=GRAPH_SOCKET),
618
+ transport=httpx.AsyncHTTPTransport(uds=GRAPH_SOCKET, verify=SSL),
616
619
  limits=httpx.Limits(),
617
620
  ) as graph_client,
618
621
  httpx.AsyncClient(
619
622
  base_url="http://checkpointer",
620
- transport=httpx.AsyncHTTPTransport(uds=REMOTE_SOCKET),
623
+ transport=httpx.AsyncHTTPTransport(uds=REMOTE_SOCKET, verify=SSL),
621
624
  limits=httpx.Limits(),
622
625
  ) as checkpointer_client,
623
626
  ):
@@ -641,12 +644,12 @@ async def js_healthcheck():
641
644
  async with (
642
645
  httpx.AsyncClient(
643
646
  base_url="http://graph",
644
- transport=httpx.AsyncHTTPTransport(uds=GRAPH_SOCKET),
647
+ transport=httpx.AsyncHTTPTransport(uds=GRAPH_SOCKET, verify=SSL),
645
648
  limits=httpx.Limits(),
646
649
  ) as graph_client,
647
650
  httpx.AsyncClient(
648
651
  base_url="http://checkpointer",
649
- transport=httpx.AsyncHTTPTransport(uds=REMOTE_SOCKET),
652
+ transport=httpx.AsyncHTTPTransport(uds=REMOTE_SOCKET, verify=SSL),
650
653
  limits=httpx.Limits(),
651
654
  ) as checkpointer_client,
652
655
  ):
@@ -82,9 +82,10 @@ export async function resolveGraph(
82
82
  return { sourceFile, exportSymbol, resolved };
83
83
  }
84
84
 
85
- export async function runGraphSchemaWorker(spec: GraphSpec) {
86
- const SCHEMA_RESOLVE_TIMEOUT_MS = 30_000;
87
-
85
+ export async function runGraphSchemaWorker(
86
+ spec: GraphSpec,
87
+ options?: { timeoutMs?: number }
88
+ ) {
88
89
  return await new Promise<Record<string, GraphSchema>>((resolve, reject) => {
89
90
  const worker = new Worker(
90
91
  new URL("./parser/parser.worker.mjs", import.meta.url).pathname
@@ -94,7 +95,7 @@ export async function runGraphSchemaWorker(spec: GraphSpec) {
94
95
  const timeoutId = setTimeout(() => {
95
96
  worker.terminate();
96
97
  reject(new Error("Schema extract worker timed out"));
97
- }, SCHEMA_RESOLVE_TIMEOUT_MS);
98
+ }, options?.timeoutMs ?? 30_000);
98
99
 
99
100
  worker.on("message", (result) => {
100
101
  worker.terminate();
@@ -1032,6 +1032,11 @@ events@^3.3.0:
1032
1032
  resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
1033
1033
  integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
1034
1034
 
1035
+ exit-hook@^4.0.0:
1036
+ version "4.0.0"
1037
+ resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-4.0.0.tgz#c1e16ebd03d3166f837b1502dac755bb5c460d58"
1038
+ integrity sha512-Fqs7ChZm72y40wKjOFXBKg7nJZvQJmewP5/7LtePDdnah/+FH9Hp5sgMujSCMPXlxOAW2//1jrW9pnsY7o20vQ==
1039
+
1035
1040
  expect-type@^1.1.0:
1036
1041
  version "1.1.0"
1037
1042
  resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.1.0.tgz#a146e414250d13dfc49eafcfd1344a4060fa4c75"
@@ -28,6 +28,7 @@ from langgraph_api.middleware.private_network import PrivateNetworkMiddleware
28
28
  from langgraph_api.utils import SchemaGenerator
29
29
  from langgraph_license.middleware import LicenseValidationMiddleware
30
30
  from langgraph_storage.retry import OVERLOADED_EXCEPTIONS
31
+ from langgraph_sdk.client import configure_loopback_transports
31
32
 
32
33
  logging.captureWarnings(True)
33
34
  logger = structlog.stdlib.get_logger(__name__)
@@ -127,7 +128,9 @@ if user_router:
127
128
  app.exception_handlers[k] = v
128
129
  else:
129
130
  logger.debug(f"Overriding exception handler for {k}")
130
-
131
+ # If the user creates a loopback client with `get_client() (no url)
132
+ # this will update the http transport to connect to the right app
133
+ configure_loopback_transports(app)
131
134
 
132
135
  else:
133
136
  # It's a regular starlette app
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "langgraph-api"
3
- version = "0.0.25"
3
+ version = "0.0.27"
4
4
  description = ""
5
5
  authors = [
6
6
  "Nuno Campos <nuno@langchain.dev>",
@@ -23,19 +23,19 @@ python = ">=3.11.0,<4.0"
23
23
  sse-starlette = ">=2.1.0,<2.2.0"
24
24
  starlette = ">=0.38.6"
25
25
  watchfiles = ">=0.13"
26
- langgraph = ">=0.2.56,<0.3.0"
26
+ langgraph = ">=0.2.56,<0.4.0"
27
27
  langgraph-checkpoint = ">=2.0.15,<3.0"
28
- orjson = ">=3.10.1"
28
+ orjson = ">=3.9.7"
29
29
  uvicorn = ">=0.26.0"
30
30
  langsmith = ">=0.1.63,<0.4.0"
31
- httpx = ">=0.27.0"
31
+ httpx = ">=0.25.0"
32
32
  langchain-core = ">=0.2.38,<0.4.0"
33
- tenacity = ">=8.3.0,<10"
34
- jsonschema-rs = "^0.25.0"
35
- structlog = "^24.4.0"
33
+ tenacity = ">=8.0.0"
34
+ jsonschema-rs = "^0.20.0"
35
+ structlog = "^23.1.0"
36
36
  pyjwt = "^2.9.0"
37
37
  cryptography = "^43.0.3"
38
- langgraph-sdk = "^0.1.51"
38
+ langgraph-sdk = "^0.1.53"
39
39
 
40
40
  [tool.poetry.group.dev.dependencies]
41
41
  ruff = "^0.6.2"
File without changes