langgraph-api 0.2.27__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.
- langgraph_api-0.2.28/.dockerignore +14 -0
- langgraph_api-0.2.28/.gitignore +5 -0
- langgraph_api-0.2.28/Makefile +114 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/PKG-INFO +24 -30
- langgraph_api-0.2.28/benchmark/weather.js +75 -0
- langgraph_api-0.2.28/constraints.txt +19 -0
- langgraph_api-0.2.28/forbidden.txt +2 -0
- langgraph_api-0.2.28/healthcheck.py +18 -0
- langgraph_api-0.2.28/langgraph_api/__init__.py +1 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/api/assistants.py +4 -4
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/api/store.py +10 -6
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/config.py +1 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/graph.py +28 -5
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/remote.py +16 -11
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/metadata.py +28 -16
- langgraph_api-0.2.28/langgraph_api/store.py +127 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/stream.py +17 -7
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/worker.py +1 -1
- langgraph_api-0.2.28/pyproject.toml +102 -0
- langgraph_api-0.2.28/scripts/create_license.py +60 -0
- langgraph_api-0.2.28/uv.lock +1340 -0
- langgraph_api-0.2.27/langgraph_api/__init__.py +0 -1
- langgraph_api-0.2.27/langgraph_api/js/tests/api.test.mts +0 -2194
- langgraph_api-0.2.27/langgraph_api/js/tests/auth.test.mts +0 -648
- langgraph_api-0.2.27/langgraph_api/js/tests/compose-postgres.auth.yml +0 -59
- langgraph_api-0.2.27/langgraph_api/js/tests/compose-postgres.yml +0 -59
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/.gitignore +0 -1
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/agent.css +0 -1
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/agent.mts +0 -187
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/agent.ui.tsx +0 -10
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/agent_simple.mts +0 -105
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/auth.mts +0 -106
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/command.mts +0 -48
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/delay.mts +0 -30
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/dynamic.mts +0 -24
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/error.mts +0 -17
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/http.mts +0 -76
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/langgraph.json +0 -11
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/nested.mts +0 -44
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/package.json +0 -13
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/weather.mts +0 -57
- langgraph_api-0.2.27/langgraph_api/js/tests/graphs/yarn.lock +0 -242
- langgraph_api-0.2.27/langgraph_api/js/tests/utils.mts +0 -17
- langgraph_api-0.2.27/pyproject.toml +0 -98
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/LICENSE +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/README.md +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/api/__init__.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/api/mcp.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/api/meta.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/api/openapi.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/api/runs.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/api/threads.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/api/ui.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/asgi_transport.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/asyncio.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/auth/__init__.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/auth/custom.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/auth/langsmith/__init__.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/auth/langsmith/backend.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/auth/langsmith/client.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/auth/middleware.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/auth/noop.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/auth/studio_user.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/cli.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/command.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/cron_scheduler.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/errors.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/http.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/.gitignore +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/.prettierrc +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/__init__.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/base.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/build.mts +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/client.http.mts +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/client.mts +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/errors.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/global.d.ts +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/package.json +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/schema.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/src/graph.mts +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/src/load.hooks.mjs +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/src/preload.mjs +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/src/utils/files.mts +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/src/utils/importMap.mts +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/src/utils/serde.mts +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/sse.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/tsconfig.json +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/ui.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/js/yarn.lock +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/logging.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/middleware/__init__.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/middleware/http_logger.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/middleware/private_network.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/middleware/request_id.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/models/__init__.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/models/run.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/patch.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/queue_entrypoint.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/route.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/schema.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/serde.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/server.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/sse.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/state.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/thread_ttl.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/tunneling/cloudflare.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/utils.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/validation.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_api/webhook.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_license/__init__.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_license/validation.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/langgraph_runtime/__init__.py +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/logging.json +0 -0
- {langgraph_api-0.2.27 → langgraph_api-0.2.28}/openapi.json +0 -0
|
@@ -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.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: langgraph-api
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
|
|
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
|
-
|
|
7
|
-
Author-email: nuno@langchain.dev
|
|
6
|
+
License-File: LICENSE
|
|
8
7
|
Requires-Python: >=3.11
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
Requires-Dist:
|
|
16
|
-
Requires-Dist:
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
Requires-Dist:
|
|
19
|
-
Requires-Dist:
|
|
20
|
-
Requires-Dist:
|
|
21
|
-
Requires-Dist:
|
|
22
|
-
Requires-Dist:
|
|
23
|
-
Requires-Dist:
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
Requires-Dist:
|
|
26
|
-
Requires-Dist:
|
|
27
|
-
Requires-Dist:
|
|
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,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=
|
|
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=
|
|
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=
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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"],
|
|
@@ -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=
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|