langgraph-api 0.0.5__tar.gz → 0.0.7__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.0.5 → langgraph_api-0.0.7}/PKG-INFO +2 -2
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/api/runs.py +16 -2
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/api/store.py +17 -23
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/cli.py +12 -8
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/graph.py +6 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/client.mts +9 -2
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/package.json +3 -3
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/remote.py +44 -42
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/graph.mts +14 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/hooks.mjs +9 -8
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/api.test.mts +86 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/graphs/agent.mts +9 -4
- langgraph_api-0.0.7/langgraph_api/js/tests/graphs/package.json +7 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/graphs/yarn.lock +39 -24
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/yarn.lock +17 -17
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/schema.py +1 -1
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/stream.py +10 -6
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/database.py +2 -3
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/ops.py +10 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/store.py +20 -2
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/openapi.json +34 -6
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/pyproject.toml +3 -3
- langgraph_api-0.0.5/langgraph_api/js/tests/graphs/package.json +0 -7
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/LICENSE +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/README.md +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/__init__.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/api/__init__.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/api/assistants.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/api/meta.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/api/openapi.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/api/threads.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/asyncio.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/auth/__init__.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/auth/langsmith/__init__.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/auth/langsmith/backend.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/auth/langsmith/client.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/auth/middleware.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/auth/noop.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/config.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/cron_scheduler.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/errors.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/http.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/http_logger.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/.gitignore +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/build.mts +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/global.d.ts +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/server_sent_events.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/parser/parser.mts +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/parser/parser.worker.mjs +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/schema/types.mts +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/schema/types.template.mts +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/utils/importMap.mts +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/src/utils/serde.mts +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/compose-postgres.yml +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/graphs/.gitignore +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/graphs/error.mts +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/graphs/langgraph.json +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/graphs/nested.mts +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/graphs/weather.mts +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/parser.test.mts +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/js/tests/utils.mts +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/lifespan.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/logging.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/metadata.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/models/__init__.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/models/run.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/patch.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/queue.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/route.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/serde.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/server.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/sse.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/state.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/utils.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_api/validation.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_license/__init__.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_license/middleware.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_license/validation.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/__init__.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/checkpoint.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/queue.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/retry.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/langgraph_storage/ttl_dict.py +0 -0
- {langgraph_api-0.0.5 → langgraph_api-0.0.7}/logging.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: langgraph-api
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary:
|
|
5
5
|
License: Elastic-2.0
|
|
6
6
|
Author: Nuno Campos
|
|
@@ -14,7 +14,7 @@ Requires-Dist: cryptography (>=43.0.3,<44.0.0)
|
|
|
14
14
|
Requires-Dist: httpx (>=0.27.0)
|
|
15
15
|
Requires-Dist: jsonschema-rs (>=0.25.0,<0.26.0)
|
|
16
16
|
Requires-Dist: langchain-core (>=0.2.38,<0.4.0)
|
|
17
|
-
Requires-Dist: langgraph (>=0.2.
|
|
17
|
+
Requires-Dist: langgraph (>=0.2.56,<0.3.0)
|
|
18
18
|
Requires-Dist: langgraph-checkpoint (>=2.0.7,<3.0)
|
|
19
19
|
Requires-Dist: langsmith (>=0.1.63,<0.2.0)
|
|
20
20
|
Requires-Dist: orjson (>=3.10.1)
|
|
@@ -283,10 +283,18 @@ async def list_runs_http(
|
|
|
283
283
|
validate_uuid(thread_id, "Invalid thread ID: must be a UUID")
|
|
284
284
|
limit = int(request.query_params.get("limit", 10))
|
|
285
285
|
offset = int(request.query_params.get("offset", 0))
|
|
286
|
+
status = request.query_params.get("status")
|
|
286
287
|
async with connect() as conn, conn.pipeline():
|
|
287
288
|
thread, runs = await asyncio.gather(
|
|
288
289
|
Threads.get(conn, thread_id),
|
|
289
|
-
Runs.search(
|
|
290
|
+
Runs.search(
|
|
291
|
+
conn,
|
|
292
|
+
thread_id,
|
|
293
|
+
limit=limit,
|
|
294
|
+
offset=offset,
|
|
295
|
+
status=status,
|
|
296
|
+
metadata=None,
|
|
297
|
+
),
|
|
290
298
|
)
|
|
291
299
|
await fetchone(thread)
|
|
292
300
|
return ApiResponse([run async for run in runs])
|
|
@@ -323,9 +331,15 @@ async def join_run_stream_endpoint(request: ApiRequest):
|
|
|
323
331
|
"""Wait for a run to finish."""
|
|
324
332
|
thread_id = request.path_params["thread_id"]
|
|
325
333
|
run_id = request.path_params["run_id"]
|
|
334
|
+
cancel_on_disconnect_str = request.query_params.get("cancel_on_disconnect", "false")
|
|
335
|
+
cancel_on_disconnect = cancel_on_disconnect_str.lower() in {"true", "yes", "1"}
|
|
326
336
|
validate_uuid(thread_id, "Invalid thread ID: must be a UUID")
|
|
327
337
|
validate_uuid(run_id, "Invalid run ID: must be a UUID")
|
|
328
|
-
return EventSourceResponse(
|
|
338
|
+
return EventSourceResponse(
|
|
339
|
+
Runs.Stream.join(
|
|
340
|
+
run_id, thread_id=thread_id, cancel_on_disconnect=cancel_on_disconnect
|
|
341
|
+
)
|
|
342
|
+
)
|
|
329
343
|
|
|
330
344
|
|
|
331
345
|
@retry_db
|
|
@@ -8,7 +8,6 @@ from langgraph_api.validation import (
|
|
|
8
8
|
StorePutRequest,
|
|
9
9
|
StoreSearchRequest,
|
|
10
10
|
)
|
|
11
|
-
from langgraph_storage.database import connect
|
|
12
11
|
from langgraph_storage.retry import retry_db
|
|
13
12
|
from langgraph_storage.store import Store
|
|
14
13
|
|
|
@@ -31,8 +30,7 @@ async def put_item(request: ApiRequest):
|
|
|
31
30
|
return err
|
|
32
31
|
key = payload["key"]
|
|
33
32
|
value = payload["value"]
|
|
34
|
-
|
|
35
|
-
await Store(conn).aput(namespace, key, value)
|
|
33
|
+
await Store().aput(namespace, key, value)
|
|
36
34
|
return Response(status_code=204)
|
|
37
35
|
|
|
38
36
|
|
|
@@ -45,8 +43,7 @@ async def get_item(request: ApiRequest):
|
|
|
45
43
|
key = request.query_params.get("key")
|
|
46
44
|
if not key:
|
|
47
45
|
return ApiResponse({"error": "Key is required"}, status_code=400)
|
|
48
|
-
|
|
49
|
-
result = await Store(conn).aget(namespace, key)
|
|
46
|
+
result = await Store().aget(namespace, key)
|
|
50
47
|
return ApiResponse(result.dict() if result is not None else None)
|
|
51
48
|
|
|
52
49
|
|
|
@@ -58,8 +55,7 @@ async def delete_item(request: ApiRequest):
|
|
|
58
55
|
if err := _validate_namespace(namespace):
|
|
59
56
|
return err
|
|
60
57
|
key = payload["key"]
|
|
61
|
-
|
|
62
|
-
await Store(conn).adelete(namespace, key)
|
|
58
|
+
await Store().adelete(namespace, key)
|
|
63
59
|
return Response(status_code=204)
|
|
64
60
|
|
|
65
61
|
|
|
@@ -74,14 +70,13 @@ async def search_items(request: ApiRequest):
|
|
|
74
70
|
limit = payload.get("limit") or 10
|
|
75
71
|
offset = payload.get("offset") or 0
|
|
76
72
|
query = payload.get("query")
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
)
|
|
73
|
+
items = await Store().asearch(
|
|
74
|
+
namespace_prefix,
|
|
75
|
+
filter=filter,
|
|
76
|
+
limit=limit,
|
|
77
|
+
offset=offset,
|
|
78
|
+
query=query,
|
|
79
|
+
)
|
|
85
80
|
return ApiResponse({"items": [item.dict() for item in items]})
|
|
86
81
|
|
|
87
82
|
|
|
@@ -98,14 +93,13 @@ async def list_namespaces(request: ApiRequest):
|
|
|
98
93
|
max_depth = payload.get("max_depth")
|
|
99
94
|
limit = payload.get("limit", 100)
|
|
100
95
|
offset = payload.get("offset", 0)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
)
|
|
96
|
+
result = await Store().alist_namespaces(
|
|
97
|
+
prefix=prefix,
|
|
98
|
+
suffix=suffix,
|
|
99
|
+
max_depth=max_depth,
|
|
100
|
+
limit=limit,
|
|
101
|
+
offset=offset,
|
|
102
|
+
)
|
|
109
103
|
return ApiResponse({"namespaces": result})
|
|
110
104
|
|
|
111
105
|
|
|
@@ -94,10 +94,6 @@ class StoreConfig(TypedDict, total=False):
|
|
|
94
94
|
index: IndexConfig
|
|
95
95
|
|
|
96
96
|
|
|
97
|
-
class Config(TypedDict, total=False):
|
|
98
|
-
store: StoreConfig | None
|
|
99
|
-
|
|
100
|
-
|
|
101
97
|
def run_server(
|
|
102
98
|
host: str = "127.0.0.1",
|
|
103
99
|
port: int = 2024,
|
|
@@ -107,10 +103,11 @@ def run_server(
|
|
|
107
103
|
env_file: str | None = None,
|
|
108
104
|
open_browser: bool = False,
|
|
109
105
|
debug_port: int | None = None,
|
|
106
|
+
wait_for_client: bool = False,
|
|
110
107
|
env: str | pathlib.Path | Mapping[str, str] | None = None,
|
|
111
108
|
reload_includes: Sequence[str] | None = None,
|
|
112
109
|
reload_excludes: Sequence[str] | None = None,
|
|
113
|
-
|
|
110
|
+
store: StoreConfig | None = None,
|
|
114
111
|
**kwargs: Any,
|
|
115
112
|
):
|
|
116
113
|
"""Run the LangGraph API server."""
|
|
@@ -148,8 +145,9 @@ def run_server(
|
|
|
148
145
|
logger.info(" - Host: 0.0.0.0")
|
|
149
146
|
logger.info(f" - Port: {debug_port}")
|
|
150
147
|
logger.info("3. Start the debugger to connect to the server.")
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
if wait_for_client:
|
|
149
|
+
debugpy.wait_for_client()
|
|
150
|
+
logger.info("Debugger attached. Starting server...")
|
|
153
151
|
|
|
154
152
|
local_url = f"http://{host}:{port}"
|
|
155
153
|
studio_url = f"https://smith.langchain.com/studio/?baseUrl={local_url}"
|
|
@@ -213,7 +211,7 @@ For production use, please use LangGraph Cloud.
|
|
|
213
211
|
DATABASE_URI=":memory:",
|
|
214
212
|
REDIS_URI="fake",
|
|
215
213
|
N_JOBS_PER_WORKER=str(n_jobs_per_worker if n_jobs_per_worker else 1),
|
|
216
|
-
|
|
214
|
+
LANGGRAPH_STORE=json.dumps(store) if store else None,
|
|
217
215
|
LANGSERVE_GRAPHS=json.dumps(graphs) if graphs else None,
|
|
218
216
|
LANGSMITH_LANGGRAPH_API_VARIANT="local_dev",
|
|
219
217
|
**(env_vars or {}),
|
|
@@ -274,6 +272,11 @@ def main():
|
|
|
274
272
|
parser.add_argument(
|
|
275
273
|
"--debug-port", type=int, help="Port for debugger to listen on (default: none)"
|
|
276
274
|
)
|
|
275
|
+
parser.add_argument(
|
|
276
|
+
"--wait-for-client",
|
|
277
|
+
action="store_true",
|
|
278
|
+
help="Whether to break and wait for a debugger to attach",
|
|
279
|
+
)
|
|
277
280
|
|
|
278
281
|
args = parser.parse_args()
|
|
279
282
|
|
|
@@ -289,6 +292,7 @@ def main():
|
|
|
289
292
|
n_jobs_per_worker=args.n_jobs_per_worker,
|
|
290
293
|
open_browser=not args.no_browser,
|
|
291
294
|
debug_port=args.debug_port,
|
|
295
|
+
wait_for_client=args.wait_for_client,
|
|
292
296
|
env=config_data.get("env", None),
|
|
293
297
|
)
|
|
294
298
|
|
|
@@ -196,6 +196,12 @@ async def collect_graphs_from_env(register: bool = False) -> None:
|
|
|
196
196
|
py_specs = list(filterfalse(is_js_spec, specs))
|
|
197
197
|
|
|
198
198
|
if js_specs:
|
|
199
|
+
if os.environ.get("LANGSMITH_LANGGRAPH_API_VARIANT") == "local_dev":
|
|
200
|
+
raise NotImplementedError(
|
|
201
|
+
"LangGraph.JS graphs are not yet supported in local development mode. "
|
|
202
|
+
"To run your JS graphs, either use the LangGraph Studio application "
|
|
203
|
+
"or run `langgraph up` to start the server in a Docker container."
|
|
204
|
+
)
|
|
199
205
|
import sys
|
|
200
206
|
|
|
201
207
|
from langgraph_api.js.remote import (
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
BaseStore,
|
|
10
10
|
Item,
|
|
11
11
|
Operation,
|
|
12
|
+
Command,
|
|
12
13
|
OperationResults,
|
|
13
14
|
type Checkpoint,
|
|
14
15
|
type CheckpointMetadata,
|
|
@@ -459,7 +460,7 @@ async function main() {
|
|
|
459
460
|
|
|
460
461
|
const specs = z
|
|
461
462
|
.record(z.string())
|
|
462
|
-
.parse(JSON.parse(process.env.LANGSERVE_GRAPHS));
|
|
463
|
+
.parse(JSON.parse(process.env.LANGSERVE_GRAPHS ?? "{}"));
|
|
463
464
|
|
|
464
465
|
if (!process.argv.includes("--skip-schema-cache")) {
|
|
465
466
|
try {
|
|
@@ -495,6 +496,7 @@ async function main() {
|
|
|
495
496
|
"json",
|
|
496
497
|
z.object({
|
|
497
498
|
input: z.unknown(),
|
|
499
|
+
command: z.object({ resume: z.unknown() }).nullish(),
|
|
498
500
|
stream_mode: z
|
|
499
501
|
.union([ExtraStreamModeSchema, z.array(ExtraStreamModeSchema)])
|
|
500
502
|
.optional(),
|
|
@@ -512,6 +514,11 @@ async function main() {
|
|
|
512
514
|
const graph = getGraph(c.req.param("graphId"));
|
|
513
515
|
const payload = c.req.valid("json");
|
|
514
516
|
|
|
517
|
+
const input = payload.command
|
|
518
|
+
? // @ts-expect-error Update LG.js to mark `resume as optional
|
|
519
|
+
new Command(payload.command)
|
|
520
|
+
: payload.input;
|
|
521
|
+
|
|
515
522
|
const userStreamMode =
|
|
516
523
|
payload.stream_mode == null
|
|
517
524
|
? []
|
|
@@ -551,7 +558,7 @@ async function main() {
|
|
|
551
558
|
const streamMode = [...graphStreamMode];
|
|
552
559
|
|
|
553
560
|
try {
|
|
554
|
-
for await (const data of graph.streamEvents(
|
|
561
|
+
for await (const data of graph.streamEvents(input, {
|
|
555
562
|
...config,
|
|
556
563
|
version: "v2",
|
|
557
564
|
streamMode,
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@hono/node-server": "^1.12.0",
|
|
11
11
|
"@hono/zod-validator": "^0.2.2",
|
|
12
|
-
"@langchain/core": "^0.3.
|
|
13
|
-
"@langchain/langgraph": "^0.2.
|
|
12
|
+
"@langchain/core": "^0.3.22",
|
|
13
|
+
"@langchain/langgraph": "^0.2.26",
|
|
14
14
|
"@types/json-schema": "^7.0.15",
|
|
15
15
|
"@typescript/vfs": "^1.6.0",
|
|
16
16
|
"dedent": "^1.5.3",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"zod": "^3.23.8"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@langchain/langgraph-sdk": "^0.0.
|
|
27
|
+
"@langchain/langgraph-sdk": "^0.0.31",
|
|
28
28
|
"@types/node": "^22.2.0",
|
|
29
29
|
"postgres": "^3.4.4",
|
|
30
30
|
"prettier": "^3.3.3",
|
|
@@ -20,7 +20,7 @@ from langchain_core.runnables.schema import (
|
|
|
20
20
|
from langgraph.checkpoint.serde.base import SerializerProtocol
|
|
21
21
|
from langgraph.pregel.types import PregelTask, StateSnapshot
|
|
22
22
|
from langgraph.store.base import GetOp, Item, ListNamespacesOp, PutOp, SearchOp
|
|
23
|
-
from langgraph.types import Interrupt
|
|
23
|
+
from langgraph.types import Command, Interrupt
|
|
24
24
|
from pydantic import BaseModel
|
|
25
25
|
from starlette.applications import Starlette
|
|
26
26
|
from starlette.requests import Request
|
|
@@ -91,12 +91,18 @@ class RemotePregel(Runnable):
|
|
|
91
91
|
if version != "v2":
|
|
92
92
|
raise ValueError("Only v2 of astream_events is supported")
|
|
93
93
|
|
|
94
|
+
data = {
|
|
95
|
+
"command" if isinstance(input, Command) else "input": input,
|
|
96
|
+
"config": config,
|
|
97
|
+
**kwargs,
|
|
98
|
+
}
|
|
99
|
+
|
|
94
100
|
async with aconnect_sse(
|
|
95
101
|
self._async_client,
|
|
96
102
|
"POST",
|
|
97
103
|
f"/{self.graph_id}/streamEvents",
|
|
98
104
|
headers={"Content-Type": "application/json"},
|
|
99
|
-
data=orjson.dumps(
|
|
105
|
+
data=orjson.dumps(data),
|
|
100
106
|
) as event_source:
|
|
101
107
|
async for sse in event_source.aiter_sse():
|
|
102
108
|
event = orjson.loads(sse["data"])
|
|
@@ -173,15 +179,19 @@ class RemotePregel(Runnable):
|
|
|
173
179
|
tuple(task["path"]) if task.get("path") else tuple(),
|
|
174
180
|
# TODO: figure out how to properly deserialise errors
|
|
175
181
|
task.get("error"),
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
182
|
+
(
|
|
183
|
+
tuple(
|
|
184
|
+
Interrupt(
|
|
185
|
+
value=interrupt["value"],
|
|
186
|
+
when=interrupt["when"],
|
|
187
|
+
resumable=interrupt.get("resumable", True),
|
|
188
|
+
ns=interrupt.get("ns"),
|
|
189
|
+
)
|
|
190
|
+
for interrupt in task.get("interrupts")
|
|
180
191
|
)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
else [],
|
|
192
|
+
if task.get("interrupts")
|
|
193
|
+
else []
|
|
194
|
+
),
|
|
185
195
|
state,
|
|
186
196
|
)
|
|
187
197
|
)
|
|
@@ -413,17 +423,13 @@ async def run_remote_checkpointer():
|
|
|
413
423
|
await server.serve()
|
|
414
424
|
|
|
415
425
|
|
|
416
|
-
def _get_passthrough_store(
|
|
426
|
+
def _get_passthrough_store():
|
|
417
427
|
from langgraph_storage.store import Store
|
|
418
428
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
return store
|
|
429
|
+
return Store()
|
|
422
430
|
|
|
423
431
|
|
|
424
432
|
async def run_remote_store():
|
|
425
|
-
from langgraph_storage.database import connect
|
|
426
|
-
|
|
427
433
|
async def abatch(request: Request):
|
|
428
434
|
payload = orjson.loads(await request.body())
|
|
429
435
|
operations = payload.get("operations", [])
|
|
@@ -470,9 +476,8 @@ async def run_remote_store():
|
|
|
470
476
|
{"error": f"Unknown operation type: {op}"}, status_code=400
|
|
471
477
|
)
|
|
472
478
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
results = await store.abatch(processed_operations)
|
|
479
|
+
store = _get_passthrough_store()
|
|
480
|
+
results = await store.abatch(processed_operations)
|
|
476
481
|
|
|
477
482
|
# Handle potentially undefined or non-dict results
|
|
478
483
|
processed_results = []
|
|
@@ -512,9 +517,8 @@ async def run_remote_store():
|
|
|
512
517
|
|
|
513
518
|
namespaces = namespaces_str.split(".")
|
|
514
519
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
result = await store.aget(namespaces, key)
|
|
520
|
+
store = _get_passthrough_store()
|
|
521
|
+
result = await store.aget(namespaces, key)
|
|
518
522
|
|
|
519
523
|
return ApiResponse(result)
|
|
520
524
|
|
|
@@ -524,10 +528,10 @@ async def run_remote_store():
|
|
|
524
528
|
namespace = tuple(payload["namespace"].split("."))
|
|
525
529
|
key = payload["key"]
|
|
526
530
|
value = payload["value"]
|
|
531
|
+
index = payload.get("index")
|
|
527
532
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
await store.aput(namespace, key, value)
|
|
533
|
+
store = _get_passthrough_store()
|
|
534
|
+
await store.aput(namespace, key, value, index=index)
|
|
531
535
|
|
|
532
536
|
return ApiResponse({"success": True})
|
|
533
537
|
|
|
@@ -538,12 +542,12 @@ async def run_remote_store():
|
|
|
538
542
|
filter = payload.get("filter")
|
|
539
543
|
limit = payload.get("limit", 10)
|
|
540
544
|
offset = payload.get("offset", 0)
|
|
545
|
+
query = payload.get("query")
|
|
541
546
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
)
|
|
547
|
+
store = _get_passthrough_store()
|
|
548
|
+
result = await store.asearch(
|
|
549
|
+
namespace_prefix, filter=filter, limit=limit, offset=offset, query=query
|
|
550
|
+
)
|
|
547
551
|
|
|
548
552
|
return ApiResponse([item.dict() for item in result])
|
|
549
553
|
|
|
@@ -553,9 +557,8 @@ async def run_remote_store():
|
|
|
553
557
|
namespace = tuple(payload["namespace"])
|
|
554
558
|
key = payload["key"]
|
|
555
559
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
await store.adelete(namespace, key)
|
|
560
|
+
store = _get_passthrough_store()
|
|
561
|
+
await store.adelete(namespace, key)
|
|
559
562
|
|
|
560
563
|
return ApiResponse({"success": True})
|
|
561
564
|
|
|
@@ -568,15 +571,14 @@ async def run_remote_store():
|
|
|
568
571
|
limit = payload.get("limit", 100)
|
|
569
572
|
offset = payload.get("offset", 0)
|
|
570
573
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
)
|
|
574
|
+
store = _get_passthrough_store()
|
|
575
|
+
result = await store.alist_namespaces(
|
|
576
|
+
prefix=prefix,
|
|
577
|
+
suffix=suffix,
|
|
578
|
+
max_depth=max_depth,
|
|
579
|
+
limit=limit,
|
|
580
|
+
offset=offset,
|
|
581
|
+
)
|
|
580
582
|
|
|
581
583
|
return ApiResponse([list(ns) for ns in result])
|
|
582
584
|
|
|
@@ -20,6 +20,20 @@ export interface GraphSpec {
|
|
|
20
20
|
exportSymbol: string;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
export async function resolveGraph(
|
|
24
|
+
spec: string,
|
|
25
|
+
options?: { onlyFilePresence?: false }
|
|
26
|
+
): Promise<{
|
|
27
|
+
sourceFile: string;
|
|
28
|
+
exportSymbol: string;
|
|
29
|
+
resolved: CompiledGraph<string>;
|
|
30
|
+
}>;
|
|
31
|
+
|
|
32
|
+
export async function resolveGraph(
|
|
33
|
+
spec: string,
|
|
34
|
+
options: { onlyFilePresence: true }
|
|
35
|
+
): Promise<{ sourceFile: string; exportSymbol: string; resolved: undefined }>;
|
|
36
|
+
|
|
23
37
|
export async function resolveGraph(
|
|
24
38
|
spec: string,
|
|
25
39
|
options?: { onlyFilePresence?: boolean }
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
// This hook is to ensure that @langchain/langgraph package
|
|
2
2
|
// found in /api folder has precendence compared to user-provided package
|
|
3
3
|
// found in /deps. Does not attempt to semver check for too old packages.
|
|
4
|
-
|
|
4
|
+
const OVERRIDE_RESOLVE = [
|
|
5
|
+
"@langchain/langgraph",
|
|
6
|
+
"@langchain/langgraph-checkpoint",
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
export const resolve = async (specifier, context, nextResolve) => {
|
|
5
10
|
const parentURL = new URL("./graph.mts", import.meta.url).toString();
|
|
6
11
|
|
|
7
|
-
if (
|
|
8
|
-
|
|
9
|
-
return nextResolve(specifier, { ...context, parentURL });
|
|
10
|
-
} catch (error) {
|
|
11
|
-
return nextResolve(specifier, context);
|
|
12
|
-
}
|
|
12
|
+
if (OVERRIDE_RESOLVE.includes(specifier)) {
|
|
13
|
+
return nextResolve(specifier, { ...context, parentURL });
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
return nextResolve(specifier, context);
|
|
16
|
-
}
|
|
17
|
+
};
|
|
@@ -447,6 +447,35 @@ describe("runs", () => {
|
|
|
447
447
|
await sql`DELETE FROM store`;
|
|
448
448
|
});
|
|
449
449
|
|
|
450
|
+
it.skip.concurrent("list runs", async () => {
|
|
451
|
+
const assistant = await client.assistants.create({ graphId: "agent" });
|
|
452
|
+
const thread = await client.threads.create();
|
|
453
|
+
await client.runs.wait(thread.thread_id, assistant.assistant_id, {
|
|
454
|
+
input: { messages: [{ type: "human", content: "foo" }] },
|
|
455
|
+
config: globalConfig,
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const pendingRun = await client.runs.create(
|
|
459
|
+
thread.thread_id,
|
|
460
|
+
assistant.assistant_id,
|
|
461
|
+
{
|
|
462
|
+
input: { messages: [{ type: "human", content: "bar" }] },
|
|
463
|
+
config: globalConfig,
|
|
464
|
+
}
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
let runs = await client.runs.list(thread.thread_id);
|
|
468
|
+
expect(runs.length).toBe(2);
|
|
469
|
+
|
|
470
|
+
runs = await client.runs.list(thread.thread_id, { status: "pending" });
|
|
471
|
+
expect(runs.length).toBe(1);
|
|
472
|
+
|
|
473
|
+
await client.runs.cancel(thread.thread_id, pendingRun.run_id);
|
|
474
|
+
|
|
475
|
+
runs = await client.runs.list(thread.thread_id, { status: "interrupted" });
|
|
476
|
+
expect(runs.length).toBe(1);
|
|
477
|
+
});
|
|
478
|
+
|
|
450
479
|
it.concurrent("stream values", async () => {
|
|
451
480
|
const assistant = await client.assistants.create({ graphId: "agent" });
|
|
452
481
|
const thread = await client.threads.create();
|
|
@@ -1500,6 +1529,63 @@ describe("subgraphs", () => {
|
|
|
1500
1529
|
expect(innerHistory[0].values.messages.length).toBe(2);
|
|
1501
1530
|
expect(innerHistory[innerHistory.length - 1].next).toEqual(["__start__"]);
|
|
1502
1531
|
});
|
|
1532
|
+
|
|
1533
|
+
it.concurrent("interrupt inside node", async () => {
|
|
1534
|
+
const assistant = await client.assistants.create({ graphId: "agent" });
|
|
1535
|
+
|
|
1536
|
+
let thread = await client.threads.create();
|
|
1537
|
+
await client.runs.wait(thread.thread_id, assistant.assistant_id, {
|
|
1538
|
+
input: {
|
|
1539
|
+
messages: [{ role: "human", content: "SF", id: "initial-message" }],
|
|
1540
|
+
interrupt: true,
|
|
1541
|
+
},
|
|
1542
|
+
config: globalConfig,
|
|
1543
|
+
});
|
|
1544
|
+
|
|
1545
|
+
const state = await client.threads.getState(thread.thread_id);
|
|
1546
|
+
expect(state.next).toEqual(["agent"]);
|
|
1547
|
+
expect(state.tasks).toMatchObject([
|
|
1548
|
+
{
|
|
1549
|
+
id: expect.any(String),
|
|
1550
|
+
name: "agent",
|
|
1551
|
+
path: ["__pregel_pull", "agent"],
|
|
1552
|
+
error: null,
|
|
1553
|
+
interrupts: [
|
|
1554
|
+
{
|
|
1555
|
+
value: "i want to interrupt",
|
|
1556
|
+
when: "during",
|
|
1557
|
+
resumable: true,
|
|
1558
|
+
ns: [expect.stringMatching(/^agent:/)],
|
|
1559
|
+
},
|
|
1560
|
+
],
|
|
1561
|
+
checkpoint: null,
|
|
1562
|
+
state: null,
|
|
1563
|
+
result: null,
|
|
1564
|
+
},
|
|
1565
|
+
]);
|
|
1566
|
+
|
|
1567
|
+
thread = await client.threads.get(thread.thread_id);
|
|
1568
|
+
expect(thread.status).toBe("interrupted");
|
|
1569
|
+
expect(thread.interrupts).toMatchObject({
|
|
1570
|
+
[state.tasks[0].id]: [
|
|
1571
|
+
{
|
|
1572
|
+
value: "i want to interrupt",
|
|
1573
|
+
when: "during",
|
|
1574
|
+
resumable: true,
|
|
1575
|
+
ns: [expect.stringMatching(/^agent:/)],
|
|
1576
|
+
},
|
|
1577
|
+
],
|
|
1578
|
+
});
|
|
1579
|
+
|
|
1580
|
+
const stream = await gatherIterator(
|
|
1581
|
+
client.runs.stream(thread.thread_id, assistant.assistant_id, {
|
|
1582
|
+
command: { resume: "i want to resume" },
|
|
1583
|
+
})
|
|
1584
|
+
);
|
|
1585
|
+
|
|
1586
|
+
expect(stream.at(-1)?.event).toBe("values");
|
|
1587
|
+
expect(stream.at(-1)?.data.messages.length).toBe(4);
|
|
1588
|
+
});
|
|
1503
1589
|
});
|
|
1504
1590
|
|
|
1505
1591
|
describe("errors", () => {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
messagesStateReducer,
|
|
8
8
|
SharedValue,
|
|
9
9
|
LangGraphRunnableConfig,
|
|
10
|
+
interrupt,
|
|
10
11
|
} from "@langchain/langgraph";
|
|
11
12
|
import { FakeListChatModel } from "@langchain/core/utils/testing";
|
|
12
13
|
|
|
@@ -16,6 +17,7 @@ const GraphAnnotationOutput = Annotation.Root({
|
|
|
16
17
|
default: () => [],
|
|
17
18
|
}),
|
|
18
19
|
sharedStateValue: Annotation<string | null>(),
|
|
20
|
+
interrupt: Annotation<boolean>(),
|
|
19
21
|
});
|
|
20
22
|
|
|
21
23
|
const GraphAnnotationInput = Annotation.Root({
|
|
@@ -36,8 +38,10 @@ const getModel = (threadId: string) => {
|
|
|
36
38
|
|
|
37
39
|
const agentNode = async (
|
|
38
40
|
state: typeof GraphAnnotationInput.State,
|
|
39
|
-
config: LangGraphRunnableConfig
|
|
41
|
+
config: LangGraphRunnableConfig
|
|
40
42
|
) => {
|
|
43
|
+
if (state.interrupt) interrupt("i want to interrupt");
|
|
44
|
+
|
|
41
45
|
const model = getModel(config.configurable?.thread_id ?? "$");
|
|
42
46
|
const response = await model.invoke(state.messages);
|
|
43
47
|
const sharedStateValue = state.sharedState?.data?.user_id ?? null;
|
|
@@ -52,6 +56,7 @@ const agentNode = async (
|
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
return {
|
|
59
|
+
interrupt: false,
|
|
55
60
|
messages: [response],
|
|
56
61
|
sharedState: { data: { user_id: config?.configurable?.user_id } },
|
|
57
62
|
sharedStateValue,
|
|
@@ -60,7 +65,7 @@ const agentNode = async (
|
|
|
60
65
|
|
|
61
66
|
const toolNode = async (
|
|
62
67
|
state: typeof GraphAnnotationInput.State,
|
|
63
|
-
config: LangGraphRunnableConfig
|
|
68
|
+
config: LangGraphRunnableConfig
|
|
64
69
|
) => {
|
|
65
70
|
const store = config.store;
|
|
66
71
|
let sharedStateFromStoreConfig: Record<string, any> | null = null;
|
|
@@ -84,7 +89,7 @@ const toolNode = async (
|
|
|
84
89
|
|
|
85
90
|
const checkSharedStateNode = async (
|
|
86
91
|
_: typeof GraphAnnotationInput.State,
|
|
87
|
-
config: LangGraphRunnableConfig
|
|
92
|
+
config: LangGraphRunnableConfig
|
|
88
93
|
): Promise<Partial<typeof GraphAnnotationInput.State>> => {
|
|
89
94
|
const store = config.store;
|
|
90
95
|
const namespace = ["inputtedState", "data"];
|
|
@@ -114,7 +119,7 @@ const workflow = new StateGraph(
|
|
|
114
119
|
input: GraphAnnotationInput,
|
|
115
120
|
output: GraphAnnotationOutput,
|
|
116
121
|
},
|
|
117
|
-
Annotation.Root({ model_name: Annotation<string> })
|
|
122
|
+
Annotation.Root({ model_name: Annotation<string> })
|
|
118
123
|
)
|
|
119
124
|
.addNode("agent", agentNode)
|
|
120
125
|
.addNode("tool", toolNode)
|