langgraph-api 0.2.26__tar.gz → 0.2.28__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 (115) hide show
  1. langgraph_api-0.2.28/.dockerignore +14 -0
  2. langgraph_api-0.2.28/.gitignore +5 -0
  3. langgraph_api-0.2.28/Makefile +114 -0
  4. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/PKG-INFO +24 -30
  5. langgraph_api-0.2.28/benchmark/weather.js +75 -0
  6. langgraph_api-0.2.28/constraints.txt +19 -0
  7. langgraph_api-0.2.28/forbidden.txt +2 -0
  8. langgraph_api-0.2.28/healthcheck.py +18 -0
  9. langgraph_api-0.2.28/langgraph_api/__init__.py +1 -0
  10. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/api/assistants.py +4 -4
  11. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/api/store.py +10 -6
  12. langgraph_api-0.2.28/langgraph_api/asgi_transport.py +171 -0
  13. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/asyncio.py +17 -0
  14. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/config.py +1 -0
  15. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/graph.py +28 -5
  16. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/remote.py +16 -11
  17. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/metadata.py +28 -16
  18. langgraph_api-0.2.28/langgraph_api/store.py +127 -0
  19. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/stream.py +17 -7
  20. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/worker.py +1 -1
  21. langgraph_api-0.2.28/pyproject.toml +102 -0
  22. langgraph_api-0.2.28/scripts/create_license.py +60 -0
  23. langgraph_api-0.2.28/uv.lock +1340 -0
  24. langgraph_api-0.2.26/langgraph_api/__init__.py +0 -1
  25. langgraph_api-0.2.26/langgraph_api/js/tests/api.test.mts +0 -2194
  26. langgraph_api-0.2.26/langgraph_api/js/tests/auth.test.mts +0 -648
  27. langgraph_api-0.2.26/langgraph_api/js/tests/compose-postgres.auth.yml +0 -59
  28. langgraph_api-0.2.26/langgraph_api/js/tests/compose-postgres.yml +0 -59
  29. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/.gitignore +0 -1
  30. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/agent.css +0 -1
  31. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/agent.mts +0 -187
  32. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/agent.ui.tsx +0 -10
  33. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/agent_simple.mts +0 -105
  34. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/auth.mts +0 -106
  35. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/command.mts +0 -48
  36. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/delay.mts +0 -30
  37. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/dynamic.mts +0 -24
  38. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/error.mts +0 -17
  39. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/http.mts +0 -76
  40. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/langgraph.json +0 -11
  41. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/nested.mts +0 -44
  42. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/package.json +0 -13
  43. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/weather.mts +0 -57
  44. langgraph_api-0.2.26/langgraph_api/js/tests/graphs/yarn.lock +0 -242
  45. langgraph_api-0.2.26/langgraph_api/js/tests/utils.mts +0 -17
  46. langgraph_api-0.2.26/pyproject.toml +0 -98
  47. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/LICENSE +0 -0
  48. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/README.md +0 -0
  49. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/api/__init__.py +0 -0
  50. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/api/mcp.py +0 -0
  51. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/api/meta.py +0 -0
  52. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/api/openapi.py +0 -0
  53. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/api/runs.py +0 -0
  54. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/api/threads.py +0 -0
  55. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/api/ui.py +0 -0
  56. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/auth/__init__.py +0 -0
  57. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/auth/custom.py +0 -0
  58. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/auth/langsmith/__init__.py +0 -0
  59. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/auth/langsmith/backend.py +0 -0
  60. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/auth/langsmith/client.py +0 -0
  61. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/auth/middleware.py +0 -0
  62. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/auth/noop.py +0 -0
  63. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/auth/studio_user.py +0 -0
  64. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/cli.py +0 -0
  65. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/command.py +0 -0
  66. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/cron_scheduler.py +0 -0
  67. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/errors.py +0 -0
  68. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/http.py +0 -0
  69. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/.gitignore +0 -0
  70. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/.prettierrc +0 -0
  71. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/__init__.py +0 -0
  72. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/base.py +0 -0
  73. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/build.mts +0 -0
  74. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/client.http.mts +0 -0
  75. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/client.mts +0 -0
  76. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/errors.py +0 -0
  77. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/global.d.ts +0 -0
  78. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/package.json +0 -0
  79. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/schema.py +0 -0
  80. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/src/graph.mts +0 -0
  81. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/src/load.hooks.mjs +0 -0
  82. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/src/preload.mjs +0 -0
  83. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/src/utils/files.mts +0 -0
  84. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/src/utils/importMap.mts +0 -0
  85. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
  86. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/src/utils/serde.mts +0 -0
  87. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/sse.py +0 -0
  88. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/tsconfig.json +0 -0
  89. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/ui.py +0 -0
  90. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/js/yarn.lock +0 -0
  91. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/logging.py +0 -0
  92. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/middleware/__init__.py +0 -0
  93. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/middleware/http_logger.py +0 -0
  94. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/middleware/private_network.py +0 -0
  95. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/middleware/request_id.py +0 -0
  96. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/models/__init__.py +0 -0
  97. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/models/run.py +0 -0
  98. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/patch.py +0 -0
  99. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/queue_entrypoint.py +0 -0
  100. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/route.py +0 -0
  101. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/schema.py +0 -0
  102. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/serde.py +0 -0
  103. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/server.py +0 -0
  104. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/sse.py +0 -0
  105. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/state.py +0 -0
  106. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/thread_ttl.py +0 -0
  107. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/tunneling/cloudflare.py +0 -0
  108. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/utils.py +0 -0
  109. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/validation.py +0 -0
  110. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_api/webhook.py +0 -0
  111. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_license/__init__.py +0 -0
  112. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_license/validation.py +0 -0
  113. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/langgraph_runtime/__init__.py +0 -0
  114. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/logging.json +0 -0
  115. {langgraph_api-0.2.26 → langgraph_api-0.2.28}/openapi.json +0 -0
@@ -0,0 +1,14 @@
1
+ .venv
2
+ .envrc
3
+ tests
4
+ compose.yml
5
+ .mypy_cache
6
+ .pytest_cache
7
+ .ruff_cache
8
+ poetry.lock
9
+ uv.lock
10
+ Dockerfile
11
+ Dockerfile.node
12
+ *.sock
13
+ node_modules
14
+ langgraph_api/js/node_modules
@@ -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.26
4
- Summary:
3
+ Version: 0.2.28
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.28"
@@ -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"],
@@ -0,0 +1,171 @@
1
+ """ASGI transport that lets you schedule to the main loop.
2
+
3
+ Adapted from: https://github.com/encode/httpx/blob/6c7af967734bafd011164f2a1653abc87905a62b/httpx/_transports/asgi.py#L1
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import typing
9
+
10
+ from httpx import ASGITransport as ASGITransportBase
11
+ from httpx import AsyncByteStream, Request, Response
12
+
13
+ if typing.TYPE_CHECKING: # pragma: no cover
14
+ import asyncio
15
+
16
+ import trio
17
+
18
+ Event = asyncio.Event | trio.Event
19
+
20
+ __all__ = ["ASGITransport"]
21
+
22
+
23
+ def is_running_trio() -> bool:
24
+ try:
25
+ # sniffio is a dependency of trio.
26
+
27
+ # See https://github.com/python-trio/trio/issues/2802
28
+ import sniffio
29
+
30
+ if sniffio.current_async_library() == "trio":
31
+ return True
32
+ except ImportError: # pragma: nocover
33
+ pass
34
+
35
+ return False
36
+
37
+
38
+ def create_event() -> Event:
39
+ if is_running_trio():
40
+ import trio
41
+
42
+ return trio.Event()
43
+
44
+ import asyncio
45
+
46
+ return asyncio.Event()
47
+
48
+
49
+ class ASGIResponseStream(AsyncByteStream):
50
+ def __init__(self, body: list[bytes]) -> None:
51
+ self._body = body
52
+
53
+ async def __aiter__(self) -> typing.AsyncIterator[bytes]:
54
+ yield b"".join(self._body)
55
+
56
+
57
+ class ASGITransport(ASGITransportBase):
58
+ """
59
+ A custom AsyncTransport that handles sending requests directly to an ASGI app.
60
+
61
+ ```python
62
+ transport = httpx.ASGITransport(
63
+ app=app,
64
+ root_path="/submount",
65
+ client=("1.2.3.4", 123)
66
+ )
67
+ client = httpx.AsyncClient(transport=transport)
68
+ ```
69
+
70
+ Arguments:
71
+
72
+ * `app` - The ASGI application.
73
+ * `raise_app_exceptions` - Boolean indicating if exceptions in the application
74
+ should be raised. Default to `True`. Can be set to `False` for use cases
75
+ such as testing the content of a client 500 response.
76
+ * `root_path` - The root path on which the ASGI application should be mounted.
77
+ * `client` - A two-tuple indicating the client IP and port of incoming requests.
78
+ ```
79
+ """
80
+
81
+ async def handle_async_request(
82
+ self,
83
+ request: Request,
84
+ ) -> Response:
85
+ from langgraph_api.asyncio import call_soon_in_main_loop
86
+
87
+ assert isinstance(request.stream, AsyncByteStream)
88
+
89
+ # ASGI scope.
90
+ scope = {
91
+ "type": "http",
92
+ "asgi": {"version": "3.0"},
93
+ "http_version": "1.1",
94
+ "method": request.method,
95
+ "headers": [(k.lower(), v) for (k, v) in request.headers.raw],
96
+ "scheme": request.url.scheme,
97
+ "path": request.url.path,
98
+ "raw_path": request.url.raw_path.split(b"?")[0],
99
+ "query_string": request.url.query,
100
+ "server": (request.url.host, request.url.port),
101
+ "client": self.client,
102
+ "root_path": self.root_path,
103
+ }
104
+
105
+ # Request.
106
+ request_body_chunks = request.stream.__aiter__()
107
+ request_complete = False
108
+
109
+ # Response.
110
+ status_code = None
111
+ response_headers = None
112
+ body_parts = []
113
+ response_started = False
114
+ response_complete = create_event()
115
+
116
+ # ASGI callables.
117
+
118
+ async def receive() -> dict[str, typing.Any]:
119
+ nonlocal request_complete
120
+
121
+ if request_complete:
122
+ await response_complete.wait()
123
+ return {"type": "http.disconnect"}
124
+
125
+ try:
126
+ body = await request_body_chunks.__anext__()
127
+ except StopAsyncIteration:
128
+ request_complete = True
129
+ return {"type": "http.request", "body": b"", "more_body": False}
130
+ return {"type": "http.request", "body": body, "more_body": True}
131
+
132
+ async def send(message: typing.MutableMapping[str, typing.Any]) -> None:
133
+ nonlocal status_code, response_headers, response_started
134
+
135
+ if message["type"] == "http.response.start":
136
+ assert not response_started
137
+
138
+ status_code = message["status"]
139
+ response_headers = message.get("headers", [])
140
+ response_started = True
141
+
142
+ elif message["type"] == "http.response.body":
143
+ assert not response_complete.is_set()
144
+ body = message.get("body", b"")
145
+ more_body = message.get("more_body", False)
146
+
147
+ if body and request.method != "HEAD":
148
+ body_parts.append(body)
149
+
150
+ if not more_body:
151
+ response_complete.set()
152
+
153
+ try:
154
+ await call_soon_in_main_loop(self.app(scope, receive, send))
155
+ except Exception: # noqa: PIE-786
156
+ if self.raise_app_exceptions:
157
+ raise
158
+
159
+ response_complete.set()
160
+ if status_code is None:
161
+ status_code = 500
162
+ if response_headers is None:
163
+ response_headers = {}
164
+
165
+ assert response_complete.is_set()
166
+ assert status_code is not None
167
+ assert response_headers is not None
168
+
169
+ stream = ASGIResponseStream(body_parts)
170
+
171
+ return Response(status_code, headers=response_headers, stream=stream)
@@ -6,6 +6,7 @@ from functools import partial
6
6
  from typing import Any, Generic, TypeVar
7
7
 
8
8
  import structlog
9
+ from langgraph.utils.future import chain_future
9
10
 
10
11
  T = TypeVar("T")
11
12
 
@@ -19,6 +20,12 @@ def set_event_loop(loop: asyncio.AbstractEventLoop) -> None:
19
20
  _MAIN_LOOP = loop
20
21
 
21
22
 
23
+ def get_event_loop() -> asyncio.AbstractEventLoop:
24
+ if _MAIN_LOOP is None:
25
+ raise RuntimeError("No event loop set")
26
+ return _MAIN_LOOP
27
+
28
+
22
29
  async def sleep_if_not_done(delay: float, done: asyncio.Event) -> None:
23
30
  try:
24
31
  await asyncio.wait_for(done.wait(), delay)
@@ -118,6 +125,16 @@ def run_coroutine_threadsafe(
118
125
  return future
119
126
 
120
127
 
128
+ def call_soon_in_main_loop(coro: Coroutine[Any, Any, T]) -> asyncio.Future[T]:
129
+ """Run a coroutine in the main event loop."""
130
+ if _MAIN_LOOP is None:
131
+ raise RuntimeError("No event loop set")
132
+ main_loop_fut = asyncio.ensure_future(coro, loop=_MAIN_LOOP)
133
+ this_loop_fut = asyncio.get_running_loop().create_future()
134
+ _MAIN_LOOP.call_soon_threadsafe(chain_future, main_loop_fut, this_loop_fut)
135
+ return this_loop_fut
136
+
137
+
121
138
  class SimpleTaskGroup(AbstractAsyncContextManager["SimpleTaskGroup"]):
122
139
  """An async task group that can be configured to wait and/or cancel tasks on exit.
123
140
 
@@ -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