langgraph-api 0.2.27__tar.gz → 0.2.29__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 (114) hide show
  1. langgraph_api-0.2.29/.gitignore +5 -0
  2. langgraph_api-0.2.29/Makefile +114 -0
  3. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/PKG-INFO +24 -30
  4. langgraph_api-0.2.29/benchmark/weather.js +75 -0
  5. langgraph_api-0.2.29/constraints.txt +19 -0
  6. langgraph_api-0.2.29/forbidden.txt +2 -0
  7. langgraph_api-0.2.29/healthcheck.py +18 -0
  8. langgraph_api-0.2.29/langgraph_api/__init__.py +1 -0
  9. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/api/assistants.py +4 -4
  10. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/api/store.py +10 -6
  11. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/config.py +1 -0
  12. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/graph.py +28 -5
  13. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/remote.py +16 -11
  14. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/metadata.py +28 -16
  15. langgraph_api-0.2.29/langgraph_api/store.py +127 -0
  16. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/stream.py +17 -7
  17. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/worker.py +1 -1
  18. langgraph_api-0.2.29/pyproject.toml +125 -0
  19. langgraph_api-0.2.29/scripts/create_license.py +60 -0
  20. langgraph_api-0.2.29/uv.lock +1340 -0
  21. langgraph_api-0.2.27/langgraph_api/__init__.py +0 -1
  22. langgraph_api-0.2.27/langgraph_api/js/tests/api.test.mts +0 -2194
  23. langgraph_api-0.2.27/langgraph_api/js/tests/auth.test.mts +0 -648
  24. langgraph_api-0.2.27/langgraph_api/js/tests/compose-postgres.auth.yml +0 -59
  25. langgraph_api-0.2.27/langgraph_api/js/tests/compose-postgres.yml +0 -59
  26. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/.gitignore +0 -1
  27. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/agent.css +0 -1
  28. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/agent.mts +0 -187
  29. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/agent.ui.tsx +0 -10
  30. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/agent_simple.mts +0 -105
  31. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/auth.mts +0 -106
  32. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/command.mts +0 -48
  33. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/delay.mts +0 -30
  34. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/dynamic.mts +0 -24
  35. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/error.mts +0 -17
  36. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/http.mts +0 -76
  37. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/langgraph.json +0 -11
  38. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/nested.mts +0 -44
  39. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/package.json +0 -13
  40. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/weather.mts +0 -57
  41. langgraph_api-0.2.27/langgraph_api/js/tests/graphs/yarn.lock +0 -242
  42. langgraph_api-0.2.27/langgraph_api/js/tests/utils.mts +0 -17
  43. langgraph_api-0.2.27/pyproject.toml +0 -98
  44. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/LICENSE +0 -0
  45. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/README.md +0 -0
  46. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/api/__init__.py +0 -0
  47. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/api/mcp.py +0 -0
  48. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/api/meta.py +0 -0
  49. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/api/openapi.py +0 -0
  50. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/api/runs.py +0 -0
  51. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/api/threads.py +0 -0
  52. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/api/ui.py +0 -0
  53. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/asgi_transport.py +0 -0
  54. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/asyncio.py +0 -0
  55. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/auth/__init__.py +0 -0
  56. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/auth/custom.py +0 -0
  57. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/auth/langsmith/__init__.py +0 -0
  58. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/auth/langsmith/backend.py +0 -0
  59. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/auth/langsmith/client.py +0 -0
  60. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/auth/middleware.py +0 -0
  61. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/auth/noop.py +0 -0
  62. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/auth/studio_user.py +0 -0
  63. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/cli.py +0 -0
  64. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/command.py +0 -0
  65. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/cron_scheduler.py +0 -0
  66. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/errors.py +0 -0
  67. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/http.py +0 -0
  68. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/.gitignore +0 -0
  69. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/.prettierrc +0 -0
  70. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/__init__.py +0 -0
  71. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/base.py +0 -0
  72. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/build.mts +0 -0
  73. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/client.http.mts +0 -0
  74. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/client.mts +0 -0
  75. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/errors.py +0 -0
  76. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/global.d.ts +0 -0
  77. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/package.json +0 -0
  78. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/schema.py +0 -0
  79. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/src/graph.mts +0 -0
  80. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/src/load.hooks.mjs +0 -0
  81. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/src/preload.mjs +0 -0
  82. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/src/utils/files.mts +0 -0
  83. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/src/utils/importMap.mts +0 -0
  84. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
  85. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/src/utils/serde.mts +0 -0
  86. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/sse.py +0 -0
  87. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/tsconfig.json +0 -0
  88. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/ui.py +0 -0
  89. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/js/yarn.lock +0 -0
  90. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/logging.py +0 -0
  91. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/middleware/__init__.py +0 -0
  92. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/middleware/http_logger.py +0 -0
  93. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/middleware/private_network.py +0 -0
  94. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/middleware/request_id.py +0 -0
  95. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/models/__init__.py +0 -0
  96. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/models/run.py +0 -0
  97. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/patch.py +0 -0
  98. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/queue_entrypoint.py +0 -0
  99. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/route.py +0 -0
  100. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/schema.py +0 -0
  101. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/serde.py +0 -0
  102. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/server.py +0 -0
  103. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/sse.py +0 -0
  104. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/state.py +0 -0
  105. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/thread_ttl.py +0 -0
  106. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/tunneling/cloudflare.py +0 -0
  107. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/utils.py +0 -0
  108. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/validation.py +0 -0
  109. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_api/webhook.py +0 -0
  110. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_license/__init__.py +0 -0
  111. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_license/validation.py +0 -0
  112. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/langgraph_runtime/__init__.py +0 -0
  113. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/logging.json +0 -0
  114. {langgraph_api-0.2.27 → langgraph_api-0.2.29}/openapi.json +0 -0
@@ -0,0 +1,5 @@
1
+ .envrc
2
+ .ipynb_checkpoints
3
+ .langgraph-data
4
+ *.sock
5
+ langgraph_api.duckdb*
@@ -0,0 +1,114 @@
1
+ .PHONY: build release lint format test test_watch start start-inmem start-inmem-license-oss start check-version
2
+
3
+ # lint commands
4
+
5
+ lint:
6
+ uv run ruff check .
7
+ uv run ruff format . --diff
8
+
9
+ format:
10
+ uv run ruff check --fix .
11
+ uv run ruff format .
12
+
13
+ # test commands
14
+
15
+ TEST ?= "tests/integration_tests/"
16
+ AUTH_TEST ?= "tests/integration_tests/test_custom_auth.py"
17
+ LANGGRAPH_HTTP ?= {"disable_mcp": false}
18
+ LANGGRAPH_AES_KEY ?= '1234567890123456'
19
+
20
+ ifeq ($(LANGGRAPH_HTTP),fastapi)
21
+ HTTP_CONFIG := {"app": "./tests/graphs/my_router.py:app", "disable_mcp": false, "mount_prefix": "/my-cool/api"}
22
+ else
23
+ HTTP_CONFIG := $(LANGGRAPH_HTTP)
24
+ endif
25
+
26
+ LANGGRAPH_STORE ?= ""
27
+ ifeq ($(LANGGRAPH_STORE),custom)
28
+ STORE_CONFIG := {"path": "./tests/graphs/custom_store.py:generate_store"}
29
+ else
30
+ STORE_CONFIG := {"index": {"dims": 500, "embed": "./tests/graphs/test_utils/embeddings.py:embeddings"}}
31
+ endif
32
+
33
+ REVISION ?= $(shell git rev-parse --short HEAD)
34
+
35
+ test-license-oss:
36
+ LANGGRAPH_RUNTIME_EDITION=inmem LANGGRAPH_HTTP='$(HTTP_CONFIG)' LANGGRAPH_STORE='$(STORE_CONFIG)' REDIS_URI=_FAKE DATABASE_URI=:memory: MIGRATIONS_PATH=__inmem__ uv run pytest -v $(TEST)
37
+
38
+ test-watch-oss:
39
+ LANGGRAPH_RUNTIME_EDITION=inmem LANGGRAPH_HTTP='$(HTTP_CONFIG)' REDIS_URI=_FAKE DATABASE_URI=:memory: MIGRATIONS_PATH=__inmem__ uv run ptw . -- -x -vv --ff --capture=no $(TEST)
40
+
41
+ test: test-license-oss
42
+ test-watch: test-watch-oss
43
+
44
+ test-auth:
45
+ LANGGRAPH_RUNTIME_EDITION=inmem LANGGRAPH_AUTH='{"path": "./tests/graphs/fastapi_jwt_auth.py:get_current_active_user"}' REDIS_URI=_FAKE DATABASE_URI=:memory: MIGRATIONS_PATH=__inmem__ uv run pytest -v $(AUTH_TEST)
46
+
47
+ test-auth-watch:
48
+ LANGGRAPH_RUNTIME_EDITION=inmem LANGGRAPH_AUTH='{"path": "./tests/graphs/fastapi_jwt_auth.py:get_current_active_user"}' REDIS_URI=_FAKE DATABASE_URI=:memory: MIGRATIONS_PATH=__inmem__ uv run ptw . -- -x -vv --ff --capture=no $(AUTH_TEST)
49
+
50
+ # dev commands
51
+
52
+ start:
53
+ LANGGRAPH_HTTP='$(HTTP_CONFIG)' \
54
+ LANGGRAPH_RUNTIME_EDITION=inmem \
55
+ LANGGRAPH_AES_KEY='$(LANGGRAPH_AES_KEY)' \
56
+ N_JOBS_PER_WORKER=2 \
57
+ LANGGRAPH_CONFIG='{"agent": {"configurable": {"model_name": "openai"}}}' \
58
+ LANGSERVE_GRAPHS='{"agent": "./tests/graphs/agent.py:graph", "single_node": "./tests/graphs/single_node.py:graph", "other": "./tests/graphs/other.py:make_graph", "weather": "./tests/graphs/weather.py:mk_weather_graph", "searchy": "./tests/graphs/searchy.py:graph", "agent_simple": "./tests/graphs/agent_simple.py:graph"}' \
59
+ LANGGRAPH_STORE='$(STORE_CONFIG)' \
60
+ LANGGRAPH_CONFIG='{"agent": {"configurable": {"model_name": "openai"}}}' \
61
+ LANGSMITH_LANGGRAPH_API_VARIANT=test \
62
+ REDIS_URI=fake \
63
+ DATABASE_URI=:memory: \
64
+ MIGRATIONS_PATH=__inmem \
65
+ uv run uvicorn \
66
+ "langgraph_api.server:app" \
67
+ --reload \
68
+ --port 9123 \
69
+ --reload-dir langgraph_api \
70
+ --no-access-log
71
+
72
+
73
+ start-license-oss: start
74
+
75
+
76
+ start-auth-jwt:
77
+ LANGGRAPH_RUNTIME_EDITION=inmem LANGGRAPH_HTTP='$(HTTP_CONFIG)' \
78
+ LANGGRAPH_AES_KEY='$(LANGGRAPH_AES_KEY)' \
79
+ N_JOBS_PER_WORKER=2 \
80
+ LANGSERVE_GRAPHS='{"agent": "./tests/graphs/agent.py:graph", "other": "./tests/graphs/other.py:make_graph", "weather": "./tests/graphs/weather.py:mk_weather_graph", "searchy": "./tests/graphs/searchy.py:graph", "agent_simple": "./tests/graphs/agent_simple.py:graph"}' \
81
+ LANGGRAPH_STORE='$(STORE_CONFIG)' \
82
+ LANGGRAPH_AUTH='{"path": "tests/graphs/jwt_auth.py:auth"}' \
83
+ LANGSMITH_LANGGRAPH_API_VARIANT=test \
84
+ REDIS_URI=fake \
85
+ DATABASE_URI=:memory: \
86
+ MIGRATIONS_PATH=__inmem \
87
+ uv run uvicorn \
88
+ "langgraph_api.server:app" \
89
+ --reload \
90
+ --port 9123 \
91
+ --reload-dir langgraph_api \
92
+ --no-access-log
93
+
94
+ start-auth-fastapi-jwt:
95
+ LANGGRAPH_RUNTIME_EDITION=inmem LANGGRAPH_HTTP='$(HTTP_CONFIG)' \
96
+ N_JOBS_PER_WORKER=2 \
97
+ LANGSERVE_GRAPHS='{"agent": "./tests/graphs/agent.py:graph", "other": "./tests/graphs/other.py:make_graph", "weather": "./tests/graphs/weather.py:mk_weather_graph", "searchy": "./tests/graphs/searchy.py:graph", "agent_simple": "./tests/graphs/agent_simple.py:graph"}' \
98
+ LANGGRAPH_STORE='$(STORE_CONFIG)' \
99
+ LANGGRAPH_AUTH='{"path": "./tests/graphs/fastapi_jwt_auth.py:auth"}' \
100
+ LANGSMITH_LANGGRAPH_API_VARIANT=test \
101
+ REDIS_URI=fake \
102
+ DATABASE_URI=:memory: \
103
+ MIGRATIONS_PATH=__inmem \
104
+ uv run uvicorn \
105
+ "langgraph_api.server:app" \
106
+ --reload \
107
+ --port 9123 \
108
+ --reload-dir langgraph_api \
109
+ --no-access-log
110
+
111
+ VERSION_KIND ?= patch
112
+
113
+ bump-version:
114
+ uv run --with hatch hatch version $(VERSION_KIND)
@@ -1,35 +1,29 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: langgraph-api
3
- Version: 0.2.27
4
- Summary:
3
+ Version: 0.2.29
4
+ Author-email: Nuno Campos <nuno@langchain.dev>, Will Fu-Hinthorn <will@langchain.dev>
5
5
  License: Elastic-2.0
6
- Author: Nuno Campos
7
- Author-email: nuno@langchain.dev
6
+ License-File: LICENSE
8
7
  Requires-Python: >=3.11
9
- Classifier: License :: Other/Proprietary License
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.11
12
- Classifier: Programming Language :: Python :: 3.12
13
- Classifier: Programming Language :: Python :: 3.13
14
- Requires-Dist: cloudpickle (>=3.0.0,<4.0.0)
15
- Requires-Dist: cryptography (>=42.0.0,<45.0)
16
- Requires-Dist: httpx (>=0.25.0)
17
- Requires-Dist: jsonschema-rs (>=0.20.0,<0.30)
18
- Requires-Dist: langchain-core (>=0.2.38) ; python_version < "4.0"
19
- Requires-Dist: langgraph (>=0.3.27) ; python_version < "4.0"
20
- Requires-Dist: langgraph-checkpoint (>=2.0.23) ; python_version < "4.0"
21
- Requires-Dist: langgraph-runtime-inmem (>=0.0.9,<0.1)
22
- Requires-Dist: langgraph-sdk (>=0.1.66,<0.2.0) ; python_version < "4.0"
23
- Requires-Dist: langsmith (>=0.1.63)
24
- Requires-Dist: orjson (>=3.9.7)
25
- Requires-Dist: pyjwt (>=2.9.0,<3.0.0)
26
- Requires-Dist: sse-starlette (>=2.1.0,<2.2.0)
27
- Requires-Dist: starlette (>=0.38.6)
28
- Requires-Dist: structlog (>=24.1.0,<26)
29
- Requires-Dist: tenacity (>=8.0.0)
30
- Requires-Dist: truststore (>=0.1)
31
- Requires-Dist: uvicorn (>=0.26.0)
32
- Requires-Dist: watchfiles (>=0.13)
8
+ Requires-Dist: cloudpickle>=3.0.0
9
+ Requires-Dist: cryptography<45.0,>=42.0.0
10
+ Requires-Dist: httpx>=0.25.0
11
+ Requires-Dist: jsonschema-rs<0.30,>=0.20.0
12
+ Requires-Dist: langchain-core>=0.2.38
13
+ Requires-Dist: langgraph-checkpoint>=2.0.23
14
+ Requires-Dist: langgraph-runtime-inmem<0.2,>=0.1.0
15
+ Requires-Dist: langgraph-sdk>=0.1.66
16
+ Requires-Dist: langgraph>=0.3.27
17
+ Requires-Dist: langsmith>=0.1.112
18
+ Requires-Dist: orjson>=3.9.7
19
+ Requires-Dist: pyjwt>=2.9.0
20
+ Requires-Dist: sse-starlette<2.2.0,>=2.1.0
21
+ Requires-Dist: starlette>=0.38.6
22
+ Requires-Dist: structlog<26,>=24.1.0
23
+ Requires-Dist: tenacity>=8.0.0
24
+ Requires-Dist: truststore>=0.1
25
+ Requires-Dist: uvicorn>=0.26.0
26
+ Requires-Dist: watchfiles>=0.13
33
27
  Description-Content-Type: text/markdown
34
28
 
35
29
  # LangGraph API
@@ -132,4 +126,4 @@ Options:
132
126
 
133
127
  ## License
134
128
 
135
- This project is licensed under the Elastic License 2.0 - see the [LICENSE](./LICENSE) file for details.
129
+ This project is licensed under the Elastic License 2.0 - see the [LICENSE](./LICENSE) file for details.
@@ -0,0 +1,75 @@
1
+ import http from "k6/http";
2
+ import { check } from "k6";
3
+
4
+ export const options = {
5
+ vus: 100,
6
+ duration: "30s",
7
+ };
8
+
9
+ const ORIGIN = "http://localhost:9123";
10
+
11
+ export function setup() {
12
+ const assistant = http.post(
13
+ `${ORIGIN}/assistants`,
14
+ JSON.stringify({ graph_id: "weather" }),
15
+ { headers: { "Content-Type": "application/json" } }
16
+ );
17
+
18
+ check(assistant, { "assistant status is 200": (r) => r.status === 200 });
19
+
20
+ return assistant.json();
21
+ }
22
+
23
+ export default function main(assistant) {
24
+ const thread = http.post(`${ORIGIN}/threads`, JSON.stringify({}), {
25
+ params: { headers: { "Content-Type": "application/json" } },
26
+ });
27
+
28
+ check(thread, { "thread status is 200": (r) => r.status === 200 });
29
+
30
+ const wait = http.post(
31
+ `${ORIGIN}/threads/${thread.json().thread_id}/runs/wait`,
32
+ JSON.stringify({
33
+ input: {
34
+ messages: [{ role: "human", content: "SF", id: "initial-message" }],
35
+ },
36
+ assistant_id: assistant.assistant_id,
37
+ }),
38
+ { headers: { "Content-Type": "application/json" } }
39
+ );
40
+
41
+ check(wait, {
42
+ "interrupted run status is 200": (r) => r.status === 200,
43
+ "interrupted run has values": (r) => {
44
+ const json = r.json();
45
+ if (json.route !== "weather") return false;
46
+ if (json.messages.length !== 1) return false;
47
+ if (json.messages[0].id !== "initial-message") return false;
48
+ if (json.messages[0].content !== "SF") return false;
49
+ return true;
50
+ },
51
+ });
52
+
53
+ const continueWait = http.post(
54
+ `${ORIGIN}/threads/${thread.json().thread_id}/runs/wait`,
55
+ JSON.stringify({
56
+ input: null,
57
+ assistant_id: assistant.assistant_id,
58
+ }),
59
+ { headers: { "Content-Type": "application/json" } }
60
+ );
61
+
62
+ check(continueWait, {
63
+ "run status is 200": (r) => r.status === 200,
64
+ "run has values": (r) => {
65
+ const json = r.json();
66
+ if (json.route !== "weather") return false;
67
+ if (json.messages.length !== 2) return false;
68
+ if (json.messages[0].id !== "initial-message") return false;
69
+ if (json.messages[0].content !== "SF") return false;
70
+ if (json.messages[1].content !== "It's sunny in San Francisco!")
71
+ return false;
72
+ return true;
73
+ },
74
+ });
75
+ }
@@ -0,0 +1,19 @@
1
+ # When editing this file, also update docs pages:
2
+ # https://github.com/langchain-ai/langgraph/blob/main/docs/docs/cloud/deployment/setup.md
3
+ # https://github.com/langchain-ai/langgraph/blob/main/docs/docs/cloud/deployment/setup_pyproject.md
4
+ langgraph>=0.3.27
5
+ langgraph-sdk>=0.1.66
6
+ langgraph-checkpoint>=2.0.23
7
+ langchain-core>=0.2.43
8
+ langsmith>=0.1.112
9
+ orjson>=3.9.7,<3.10.17
10
+ httpx>=0.25.0
11
+ tenacity>=8.0.0
12
+ uvicorn>=0.26.0
13
+ sse-starlette>=2.1.0,<2.2.0
14
+ uvloop>=0.18.0
15
+ httptools>=0.5.0
16
+ jsonschema-rs>=0.20.0
17
+ structlog>=24.1.0
18
+ cloudpickle>=3.0.0
19
+ truststore>=0.1
@@ -0,0 +1,2 @@
1
+ # Block user overrides of the base api.
2
+ langgraph-api>=0.1.0
@@ -0,0 +1,18 @@
1
+ import json
2
+ import os
3
+ import urllib.request
4
+
5
+ prefix = ""
6
+ # Override prefix if it's set in the http config
7
+ if (http := os.environ.get("LANGGRAPH_HTTP")) and (
8
+ mount_prefix := json.loads(http).get("mount_prefix")
9
+ ):
10
+ prefix = mount_prefix
11
+ # Override that
12
+ if os.environ.get("MOUNT_PREFIX"):
13
+ prefix = os.environ["MOUNT_PREFIX"]
14
+
15
+ with urllib.request.urlopen(
16
+ f"http://localhost:{os.environ['PORT']}{prefix}/ok"
17
+ ) as response:
18
+ assert response.status == 200
@@ -0,0 +1 @@
1
+ __version__ = "0.2.29"
@@ -9,6 +9,7 @@ from starlette.exceptions import HTTPException
9
9
  from starlette.responses import Response
10
10
  from starlette.routing import BaseRoute
11
11
 
12
+ from langgraph_api import store as api_store
12
13
  from langgraph_api.graph import get_assistant_id, get_graph
13
14
  from langgraph_api.js.base import BaseRemotePregel
14
15
  from langgraph_api.route import ApiRequest, ApiResponse, ApiRoute
@@ -25,7 +26,6 @@ from langgraph_runtime.checkpoint import Checkpointer
25
26
  from langgraph_runtime.database import connect
26
27
  from langgraph_runtime.ops import Assistants
27
28
  from langgraph_runtime.retry import retry_db
28
- from langgraph_runtime.store import Store
29
29
 
30
30
  logger = structlog.stdlib.get_logger(__name__)
31
31
 
@@ -194,7 +194,7 @@ async def get_assistant_graph(
194
194
  assistant["graph_id"],
195
195
  config,
196
196
  checkpointer=Checkpointer(conn),
197
- store=Store(),
197
+ store=(await api_store.get_store()),
198
198
  ) as graph:
199
199
  xray: bool | int = False
200
200
  xray_query = request.query_params.get("xray")
@@ -240,7 +240,7 @@ async def get_assistant_subgraphs(
240
240
  assistant["graph_id"],
241
241
  config,
242
242
  checkpointer=Checkpointer(conn),
243
- store=Store(),
243
+ store=(await api_store.get_store()),
244
244
  ) as graph:
245
245
  namespace = request.path_params.get("namespace")
246
246
 
@@ -286,7 +286,7 @@ async def get_assistant_schemas(
286
286
  assistant["graph_id"],
287
287
  config,
288
288
  checkpointer=Checkpointer(conn),
289
- store=Store(),
289
+ store=(await api_store.get_store()),
290
290
  ) as graph:
291
291
  if isinstance(graph, BaseRemotePregel):
292
292
  schemas = await graph.fetch_state_schema()
@@ -6,6 +6,7 @@ from starlette.routing import BaseRoute
6
6
 
7
7
  from langgraph_api.auth.custom import handle_event as _handle_event
8
8
  from langgraph_api.route import ApiRequest, ApiResponse, ApiRoute
9
+ from langgraph_api.store import get_store
9
10
  from langgraph_api.utils import get_auth_ctx
10
11
  from langgraph_api.validation import (
11
12
  StoreDeleteRequest,
@@ -14,7 +15,6 @@ from langgraph_api.validation import (
14
15
  StoreSearchRequest,
15
16
  )
16
17
  from langgraph_runtime.retry import retry_db
17
- from langgraph_runtime.store import Store
18
18
 
19
19
 
20
20
  def _validate_namespace(namespace: tuple[str, ...]) -> Response | None:
@@ -57,7 +57,9 @@ async def put_item(request: ApiRequest):
57
57
  "value": payload["value"],
58
58
  }
59
59
  await handle_event("put", handler_payload)
60
- await Store().aput(namespace, handler_payload["key"], handler_payload["value"])
60
+ await (await get_store()).aput(
61
+ namespace, handler_payload["key"], handler_payload["value"]
62
+ )
61
63
  return Response(status_code=204)
62
64
 
63
65
 
@@ -75,7 +77,7 @@ async def get_item(request: ApiRequest):
75
77
  "key": key,
76
78
  }
77
79
  await handle_event("get", handler_payload)
78
- result = await Store().aget(namespace, key)
80
+ result = await (await get_store()).aget(namespace, key)
79
81
  return ApiResponse(result.dict() if result is not None else None)
80
82
 
81
83
 
@@ -91,7 +93,9 @@ async def delete_item(request: ApiRequest):
91
93
  "key": payload["key"],
92
94
  }
93
95
  await handle_event("delete", handler_payload)
94
- await Store().adelete(handler_payload["namespace"], handler_payload["key"])
96
+ await (await get_store()).adelete(
97
+ handler_payload["namespace"], handler_payload["key"]
98
+ )
95
99
  return Response(status_code=204)
96
100
 
97
101
 
@@ -114,7 +118,7 @@ async def search_items(request: ApiRequest):
114
118
  "query": query,
115
119
  }
116
120
  await handle_event("search", handler_payload)
117
- items = await Store().asearch(
121
+ items = await (await get_store()).asearch(
118
122
  handler_payload["namespace"],
119
123
  filter=handler_payload["filter"],
120
124
  limit=handler_payload["limit"],
@@ -145,7 +149,7 @@ async def list_namespaces(request: ApiRequest):
145
149
  "offset": offset,
146
150
  }
147
151
  await handle_event("list_namespaces", handler_payload)
148
- result = await Store().alist_namespaces(
152
+ result = await (await get_store()).alist_namespaces(
149
153
  prefix=handler_payload["namespace"],
150
154
  suffix=handler_payload["suffix"],
151
155
  max_depth=handler_payload["max_depth"],
@@ -109,6 +109,7 @@ class TTLConfig(TypedDict, total=False):
109
109
 
110
110
 
111
111
  class StoreConfig(TypedDict, total=False):
112
+ path: str
112
113
  index: IndexConfig
113
114
  ttl: TTLConfig
114
115
 
@@ -3,7 +3,6 @@ import functools
3
3
  import glob
4
4
  import importlib.util
5
5
  import inspect
6
- import json
7
6
  import os
8
7
  import sys
9
8
  import warnings
@@ -14,6 +13,7 @@ from random import choice
14
13
  from typing import TYPE_CHECKING, Any, NamedTuple
15
14
  from uuid import UUID, uuid5
16
15
 
16
+ import orjson
17
17
  import structlog
18
18
  from langchain_core.runnables.config import run_in_executor, var_child_runnable_config
19
19
  from langgraph.checkpoint.base import BaseCheckpointSaver
@@ -60,6 +60,12 @@ async def register_graph(
60
60
  if callable(graph):
61
61
  FACTORY_ACCEPTS_CONFIG[graph_id] = len(inspect.signature(graph).parameters) > 0
62
62
  async with connect() as conn:
63
+ graph_name = getattr(graph, "name", None) if isinstance(graph, Pregel) else None
64
+ assistant_name = (
65
+ graph_name
66
+ if graph_name is not None and graph_name != "LangGraph"
67
+ else graph_id
68
+ )
63
69
  await Assistants.put(
64
70
  conn,
65
71
  str(uuid5(NAMESPACE_GRAPH, graph_id)),
@@ -67,7 +73,7 @@ async def register_graph(
67
73
  metadata={"created_by": "system"},
68
74
  config=config or {},
69
75
  if_exists="do_nothing",
70
- name=graph_id,
76
+ name=assistant_name,
71
77
  description=description,
72
78
  )
73
79
 
@@ -200,10 +206,19 @@ def _load_graph_config_from_env() -> dict | None:
200
206
  config_str = os.getenv("LANGGRAPH_CONFIG")
201
207
  if not config_str:
202
208
  return None
209
+ try:
210
+ config_per_id = orjson.loads(config_str)
211
+ except orjson.JSONDecodeError as e:
212
+ raise ValueError(
213
+ "Provided environment variable LANGGRAPH_CONFIG must be a valid JSON object"
214
+ f"\nFound: {config_str}"
215
+ ) from e
203
216
 
204
- config_per_id = json.loads(config_str)
205
217
  if not isinstance(config_per_id, dict):
206
- raise ValueError("LANGGRAPH_CONFIG must be a JSON object")
218
+ raise ValueError(
219
+ "Provided environment variable LANGGRAPH_CONFIG must be a JSON object"
220
+ f"\nFound: {config_str}"
221
+ )
207
222
 
208
223
  return config_per_id
209
224
 
@@ -218,7 +233,15 @@ async def collect_graphs_from_env(register: bool = False) -> None:
218
233
  specs = []
219
234
  # graphs-config can be either a mapping from graph id to path where the graph
220
235
  # is defined or graph id to a dictionary containing information about the graph.
221
- graphs_config = json.loads(paths_str)
236
+ try:
237
+ graphs_config = orjson.loads(paths_str)
238
+ except orjson.JSONDecodeError as e:
239
+ raise ValueError(
240
+ "LANGSERVE_GRAPHS must be a valid JSON object."
241
+ f"\nFound: {paths_str}"
242
+ "\n The LANGSERVE_GRAPHS environment variable is typically set"
243
+ 'from the "graphs" field in your configuration (langgraph.json) file.'
244
+ ) from e
222
245
 
223
246
  for key, value in graphs_config.items():
224
247
  if isinstance(value, dict) and "path" in value:
@@ -39,6 +39,7 @@ from starlette.exceptions import HTTPException
39
39
  from starlette.requests import HTTPConnection, Request
40
40
  from starlette.routing import Route
41
41
 
42
+ from langgraph_api import store as api_store
42
43
  from langgraph_api.auth.custom import DotDict, ProxyUser
43
44
  from langgraph_api.config import LANGGRAPH_AUTH_TYPE
44
45
  from langgraph_api.js.base import BaseRemotePregel
@@ -70,6 +71,12 @@ _client = httpx.AsyncClient(
70
71
  )
71
72
 
72
73
 
74
+ def _snapshot_defaults():
75
+ if not hasattr(StateSnapshot, "interrupts"):
76
+ return {}
77
+ return {"interrupts": tuple()}
78
+
79
+
73
80
  def default_command(obj):
74
81
  if isinstance(obj, Send):
75
82
  return {"node": obj.node, "args": obj.arg}
@@ -251,7 +258,7 @@ class RemotePregel(BaseRemotePregel):
251
258
  item.get("parentConfig"),
252
259
  _convert_tasks(item.get("tasks", [])),
253
260
  # TODO: add handling of interrupts when multiple resumes land in JS
254
- tuple(),
261
+ **_snapshot_defaults(),
255
262
  )
256
263
 
257
264
  async def aget_state(
@@ -473,10 +480,8 @@ def _get_passthrough_checkpointer(conn: AsyncConnectionProto):
473
480
  return checkpointer
474
481
 
475
482
 
476
- def _get_passthrough_store():
477
- from langgraph_runtime.store import Store
478
-
479
- return Store()
483
+ async def _get_passthrough_store():
484
+ return await api_store.get_store()
480
485
 
481
486
 
482
487
  # Setup a HTTP server on top of CHECKPOINTER_SOCKET unix socket
@@ -574,7 +579,7 @@ async def run_remote_checkpointer():
574
579
  else:
575
580
  raise ValueError(f"Unknown operation type: {op}")
576
581
 
577
- store = _get_passthrough_store()
582
+ store = await _get_passthrough_store()
578
583
  results = await store.abatch(processed_operations)
579
584
 
580
585
  # Handle potentially undefined or non-dict results
@@ -613,7 +618,7 @@ async def run_remote_checkpointer():
613
618
 
614
619
  namespaces = namespaces_str.split(".")
615
620
 
616
- store = _get_passthrough_store()
621
+ store = await _get_passthrough_store()
617
622
  result = await store.aget(namespaces, key)
618
623
 
619
624
  return result
@@ -626,7 +631,7 @@ async def run_remote_checkpointer():
626
631
  value = payload["value"]
627
632
  index = payload.get("index")
628
633
 
629
- store = _get_passthrough_store()
634
+ store = await _get_passthrough_store()
630
635
  await store.aput(namespace, key, value, index=index)
631
636
 
632
637
  return {"success": True}
@@ -639,7 +644,7 @@ async def run_remote_checkpointer():
639
644
  offset = payload.get("offset", 0)
640
645
  query = payload.get("query")
641
646
 
642
- store = _get_passthrough_store()
647
+ store = await _get_passthrough_store()
643
648
  result = await store.asearch(
644
649
  namespace_prefix, filter=filter, limit=limit, offset=offset, query=query
645
650
  )
@@ -652,7 +657,7 @@ async def run_remote_checkpointer():
652
657
  namespace = tuple(payload["namespace"])
653
658
  key = payload["key"]
654
659
 
655
- store = _get_passthrough_store()
660
+ store = await _get_passthrough_store()
656
661
  await store.adelete(namespace, key)
657
662
 
658
663
  return {"success": True}
@@ -665,7 +670,7 @@ async def run_remote_checkpointer():
665
670
  limit = payload.get("limit", 100)
666
671
  offset = payload.get("offset", 0)
667
672
 
668
- store = _get_passthrough_store()
673
+ store = await _get_passthrough_store()
669
674
  result = await store.alist_namespaces(
670
675
  prefix=prefix,
671
676
  suffix=suffix,