mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-18 05:35:43 +02:00
Compare commits
219
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af78f4b216 | ||
|
|
8b080a091e | ||
|
|
1157ec77b4 | ||
|
|
a10a66cbd1 | ||
|
|
ae525fb74f | ||
|
|
a6dde39be7 | ||
|
|
c1661dd07f | ||
|
|
2d848ffddd | ||
|
|
4c8d965710 | ||
|
|
4ac1c628ee | ||
|
|
41f8e61589 | ||
|
|
57a877279d | ||
|
|
d6dea53323 | ||
|
|
504e91ad5a | ||
|
|
10abf2deb1 | ||
|
|
5796ca9a0a | ||
|
|
fca3e4513c | ||
|
|
c5744f583b | ||
|
|
d298b489b4 | ||
|
|
c4144bb48f | ||
|
|
2c3e380a35 | ||
|
|
cf39fa5a91 | ||
|
|
7e666b58cd | ||
|
|
3f400b38d1 | ||
|
|
6527df688c | ||
|
|
aec841bd2a | ||
|
|
2d3121a17c | ||
|
|
06f9142419 | ||
|
|
a926450601 | ||
|
|
abb96c0e2f | ||
|
|
d9e3d83894 | ||
|
|
cedecd8ed6 | ||
|
|
6bf9a7a4bc | ||
|
|
bce1dcfcd2 | ||
|
|
9b46cba1fb | ||
|
|
a6dab889d1 | ||
|
|
7c6dbb3972 | ||
|
|
e08b8352a8 | ||
|
|
f7fe7c6698 | ||
|
|
c2bc6ab8e9 | ||
|
|
420550501f | ||
|
|
a0599139b8 | ||
|
|
c2f359f708 | ||
|
|
7f78a011fd | ||
|
|
7d166bfb9f | ||
|
|
6cc8899818 | ||
|
|
1ba96f49bf | ||
|
|
b0958115c1 | ||
|
|
04fb14d3ae | ||
|
|
efb0e8c176 | ||
|
|
0584eaa5c4 | ||
|
|
0c73af5624 | ||
|
|
9d1bb9d86c | ||
|
|
7c69cb54a6 | ||
|
|
c2279cbe6f | ||
|
|
3a024cff6d | ||
|
|
4a0b2fa0ef | ||
|
|
36179ab1d2 | ||
|
|
20ddb2b8b4 | ||
|
|
b0a25f2794 | ||
|
|
ea0aebaa2e | ||
|
|
26c68aa528 | ||
|
|
4101aebeea | ||
|
|
90ac06deb6 | ||
|
|
32d66d48eb | ||
|
|
d933d455ec | ||
|
|
c421afba65 | ||
|
|
6139dacef9 | ||
|
|
affaa90d2a | ||
|
|
9f969f5fe1 | ||
|
|
fb531b2473 | ||
|
|
fe4029b3b8 | ||
|
|
7cd9a8e5dd | ||
|
|
5ba02d5b46 | ||
|
|
11834512db | ||
|
|
eeb731c07e | ||
|
|
f0fced262a | ||
|
|
8dc4465d05 | ||
|
|
d0a3eaf601 | ||
|
|
6f45f13952 | ||
|
|
328129e5bd | ||
|
|
2d05a17dfb | ||
|
|
5a36229e38 | ||
|
|
eeadeb282e | ||
|
|
3a22aa0af3 | ||
|
|
9467a0e2bb | ||
|
|
8b55dff7a5 | ||
|
|
a19b74154a | ||
|
|
0607dc4611 | ||
|
|
a3ee814539 | ||
|
|
b65140a892 | ||
|
|
77a63608d1 | ||
|
|
c6179ca9d5 | ||
|
|
f087567853 | ||
|
|
bdef6b3f5d | ||
|
|
677d941bb6 | ||
|
|
a43acc33bd | ||
|
|
326fd55e4f | ||
|
|
6037f0210f | ||
|
|
d9328027f9 | ||
|
|
7170e04aa6 | ||
|
|
20581e61c0 | ||
|
|
4af07942ed | ||
|
|
682f39e0d3 | ||
|
|
7bbe8d8628 | ||
|
|
4dfd1c368c | ||
|
|
b75daf093e | ||
|
|
faacbc1570 | ||
|
|
f5f536ba78 | ||
|
|
7284326160 | ||
|
|
62f7548532 | ||
|
|
e6a9e1d1c1 | ||
|
|
94fa329100 | ||
|
|
264caa0684 | ||
|
|
cd33de2ad1 | ||
|
|
692f177a10 | ||
|
|
14d4be6c4a | ||
|
|
2e133d6189 | ||
|
|
89de950307 | ||
|
|
788b62c0fb | ||
|
|
1539a55d2c | ||
|
|
f3055178f3 | ||
|
|
c0067cd304 | ||
|
|
b90d7c4e58 | ||
|
|
2c14b1d658 | ||
|
|
50ce6badee | ||
|
|
6fc5b3aeda | ||
|
|
b543752878 | ||
|
|
8f6ad0b25a | ||
|
|
ada5d2ecb1 | ||
|
|
eaeafe54ab | ||
|
|
f761116de7 | ||
|
|
36cf353d19 | ||
|
|
d503c0bf33 | ||
|
|
25ba4c3bda | ||
|
|
dfc1c59ebf | ||
|
|
6f4c5fefee | ||
|
|
7cf230defa | ||
|
|
5db65e0281 | ||
|
|
b08c2e092f | ||
|
|
120ae38c12 | ||
|
|
22942d4eec | ||
|
|
1756ce1dd2 | ||
|
|
0b4638269b | ||
|
|
1ebdb1ba31 | ||
|
|
f3423c052e | ||
|
|
b63572ee16 | ||
|
|
ddf4e62bde | ||
|
|
d73902ae76 | ||
|
|
501ba8be34 | ||
|
|
998e194e82 | ||
|
|
ef65d3cf88 | ||
|
|
a692e24a58 | ||
|
|
a566f1f892 | ||
|
|
c0b29a6df5 | ||
|
|
a86eb4c5d0 | ||
|
|
3488eb2a2c | ||
|
|
875f20ba9f | ||
|
|
723d4641b0 | ||
|
|
ae62b8faf2 | ||
|
|
9918488169 | ||
|
|
c37c9cbab3 | ||
|
|
0cd8745aad | ||
|
|
33d13c6f52 | ||
|
|
054e2759ca | ||
|
|
23b71048c1 | ||
|
|
a15f542a1f | ||
|
|
d43eaf1f42 | ||
|
|
16b363fbb0 | ||
|
|
68a75135b0 | ||
|
|
5c0c0fb186 | ||
|
|
4571b708d9 | ||
|
|
e365b2b8bd | ||
|
|
b5504506a7 | ||
|
|
c6ae8d25b9 | ||
|
|
0bd7dd2c52 | ||
|
|
82978a8dd8 | ||
|
|
925150a35d | ||
|
|
2920a9dd19 | ||
|
|
db8ed4e9e4 | ||
|
|
b16fcc8468 | ||
|
|
a2fe4df89b | ||
|
|
69dd20e523 | ||
|
|
5152a96fce | ||
|
|
220314b53a | ||
|
|
38bbd92e01 | ||
|
|
e3cb2dd23b | ||
|
|
2f23d1a30d | ||
|
|
88e195bb78 | ||
|
|
41a4f993b1 | ||
|
|
b8f3f48da9 | ||
|
|
94f7f0632d | ||
|
|
b3e0582255 | ||
|
|
24c7a8db3f | ||
|
|
f8eb4244e0 | ||
|
|
62c3bbc9fe | ||
|
|
76bbb761b4 | ||
|
|
80cd91344f | ||
|
|
9e4c41cbed | ||
|
|
1fda568df9 | ||
|
|
08295ecadb | ||
|
|
4ecbabafe8 | ||
|
|
246efe71f4 | ||
|
|
116121eb3a | ||
|
|
18887e9f86 | ||
|
|
bec28226d0 | ||
|
|
967e368e14 | ||
|
|
9d4dd066e5 | ||
|
|
81027b2b80 | ||
|
|
296bf5f75e | ||
|
|
f43c806736 | ||
|
|
36f444dcb6 | ||
|
|
781a115f92 | ||
|
|
95d056e735 | ||
|
|
64adb2bab3 | ||
|
|
e87f0fb0cd | ||
|
|
c70f283f83 | ||
|
|
1d6b0c36e9 | ||
|
|
9f48bb0b61 |
@@ -1,21 +1,21 @@
|
||||
name: "\U0001F41B Bug Report"
|
||||
description: Report a bug in LangGraph. To report a security issue, please instead use the security option below. For questions, please use the LangChain Forum at forum.langchain.com.
|
||||
labels: [pending,bug]
|
||||
labels: [pending, bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to file a bug report.
|
||||
|
||||
|
||||
Use this to report BUGS in LangGraph. For usage questions, feature requests and general design questions, please use the [LangChain Forum](https://forum.langchain.com/).
|
||||
|
||||
|
||||
Relevant links to check before filing a bug report to see if your issue has already been reported, fixed or
|
||||
if there's another way to solve your problem:
|
||||
|
||||
|
||||
* [LangChain Forum](https://forum.langchain.com/),
|
||||
* [LangGraph Github Issues](https://github.com/langchain-ai/langgraph/issues),
|
||||
* [LangGraph how-to guides](https://langchain-ai.github.io/langgraph/how-tos/).
|
||||
* [LangChain documentation with the integrated search](https://python.langchain.com/docs/get_started/introduction),
|
||||
* [LangChain documentation with the integrated search](https://docs.langchain.com/),
|
||||
* [GitHub search](https://github.com/langchain-ai/langgraph),
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
blank_issues_enabled: false
|
||||
version: 2.1
|
||||
contact_links:
|
||||
- name: Documentation
|
||||
url: https://github.com/langchain-ai/docs/issues/new?template=langgraph.yml
|
||||
about: Report an issue related to the LangGraph documentation
|
||||
- name: LangChain Forum
|
||||
url: https://forum.langchain.com/
|
||||
about: General community discussions, support, and feature requests
|
||||
about: General community discussions and support
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
name: Documentation
|
||||
description: Report an issue related to the LangGraph documentation.
|
||||
title: "DOC: <Please write a comprehensive title after the 'DOC: ' prefix>"
|
||||
labels: [documentation]
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Issue with current documentation:"
|
||||
description: >
|
||||
Please make sure to leave a reference to the document/code you're
|
||||
referring to.
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Idea or request for content:"
|
||||
description: >
|
||||
Please describe as clearly as possible what topics you think are missing
|
||||
from the current documentation.
|
||||
@@ -1,10 +1,15 @@
|
||||
import ast
|
||||
import os
|
||||
from itertools import filterfalse
|
||||
from typing import List, Tuple
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
ROOT_PATH = os.path.abspath(os.path.join(__file__, "..", "..", ".."))
|
||||
CLIENT_PATH = os.path.join(ROOT_PATH, "libs", "sdk-py", "langgraph_sdk", "client.py")
|
||||
ASYNC_TO_SYNC_METHOD_MAP: Dict[str, str] = {
|
||||
"aclose": "close",
|
||||
"__aenter__": "__enter__",
|
||||
"__aexit__": "__exit__",
|
||||
}
|
||||
|
||||
|
||||
def get_class_methods(node: ast.ClassDef) -> List[str]:
|
||||
@@ -22,7 +27,7 @@ def find_classes(tree: ast.AST) -> List[Tuple[str, List[str]]]:
|
||||
|
||||
def compare_sync_async_methods(sync_methods: List[str], async_methods: List[str]) -> List[str]:
|
||||
sync_set = set(sync_methods)
|
||||
async_set = set(async_methods)
|
||||
async_set = {ASYNC_TO_SYNC_METHOD_MAP.get(async_method, async_method) for async_method in async_methods}
|
||||
missing_in_sync = list(async_set - sync_set)
|
||||
missing_in_async = list(sync_set - async_set)
|
||||
return missing_in_sync + missing_in_async
|
||||
|
||||
@@ -1,108 +1,164 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
import pathlib
|
||||
import sys
|
||||
import langgraph_cli
|
||||
import langgraph_cli.docker
|
||||
import langgraph_cli.config
|
||||
import time
|
||||
from urllib import error, request
|
||||
|
||||
import langgraph_cli
|
||||
import langgraph_cli.config
|
||||
import langgraph_cli.docker
|
||||
from langgraph_cli.cli import prepare_args_and_stdin
|
||||
from langgraph_cli.constants import DEFAULT_PORT
|
||||
from langgraph_cli.exec import Runner, subp_exec
|
||||
from langgraph_cli.progress import Progress
|
||||
from langgraph_cli.constants import DEFAULT_PORT
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
def test(
|
||||
config: pathlib.Path,
|
||||
port: int,
|
||||
tag: str,
|
||||
verbose: bool,
|
||||
):
|
||||
def test(config: pathlib.Path, port: int, tag: str, verbose: bool):
|
||||
"""Spin up API with Postgres/Redis via docker compose and wait until ready."""
|
||||
logger.info("Starting test...")
|
||||
with Runner() as runner, Progress(message="Pulling...") as set:
|
||||
# check docker available
|
||||
# Detect docker/compose capabilities
|
||||
capabilities = langgraph_cli.docker.check_capabilities(runner)
|
||||
# open config
|
||||
|
||||
# Validate config and prepare compose stdin/args using built image
|
||||
config_json = langgraph_cli.config.validate_config_file(config)
|
||||
args, stdin = prepare_args_and_stdin(
|
||||
capabilities=capabilities,
|
||||
config_path=config,
|
||||
config=config_json,
|
||||
docker_compose=None,
|
||||
port=port,
|
||||
watch=False,
|
||||
debugger_port=None,
|
||||
debugger_base_url=f"http://127.0.0.1:{port}",
|
||||
postgres_uri=None,
|
||||
api_version=None,
|
||||
image=tag,
|
||||
base_image=None,
|
||||
)
|
||||
|
||||
set("Running...")
|
||||
args = [
|
||||
"run",
|
||||
"--rm",
|
||||
"-p",
|
||||
f"{port}:8000",
|
||||
]
|
||||
if isinstance(config_json["env"], str):
|
||||
args.extend(
|
||||
[
|
||||
"--env-file",
|
||||
str(config.parent / config_json["env"]),
|
||||
]
|
||||
)
|
||||
else:
|
||||
for k, v in config_json["env"].items():
|
||||
args.extend(
|
||||
[
|
||||
"-e",
|
||||
f"{k}={v}",
|
||||
]
|
||||
)
|
||||
if capabilities.healthcheck_start_interval:
|
||||
args.extend(
|
||||
[
|
||||
"--health-interval",
|
||||
"5s",
|
||||
"--health-retries",
|
||||
"1",
|
||||
"--health-start-period",
|
||||
"10s",
|
||||
"--health-start-interval",
|
||||
"1s",
|
||||
]
|
||||
)
|
||||
else:
|
||||
args.extend(
|
||||
[
|
||||
"--health-interval",
|
||||
"5s",
|
||||
"--health-retries",
|
||||
"2",
|
||||
]
|
||||
)
|
||||
# Compose up with wait (implies detach), similar to `langgraph up --wait`
|
||||
args_up = [*args, "up", "--remove-orphans", "--wait"]
|
||||
|
||||
_task = None
|
||||
|
||||
def on_stdout(line: str):
|
||||
nonlocal _task
|
||||
if "GET /ok" in line or "Uvicorn running on" in line:
|
||||
set("")
|
||||
sys.stdout.write(
|
||||
f"""Ready!
|
||||
- API: http://localhost:{port}
|
||||
"""
|
||||
)
|
||||
sys.stdout.flush()
|
||||
_task.cancel()
|
||||
return True
|
||||
return False
|
||||
|
||||
async def subp_exec_task(*args, **kwargs):
|
||||
nonlocal _task
|
||||
_task = asyncio.create_task(subp_exec(*args, **kwargs))
|
||||
await _task
|
||||
compose_cmd = ["docker", "compose"]
|
||||
if capabilities.compose_type == "standalone":
|
||||
compose_cmd = ["docker-compose"]
|
||||
|
||||
set("Starting...")
|
||||
try:
|
||||
runner.run(
|
||||
subp_exec_task(
|
||||
"docker",
|
||||
*args,
|
||||
tag,
|
||||
subp_exec(
|
||||
*compose_cmd,
|
||||
*args_up,
|
||||
input=stdin,
|
||||
verbose=verbose,
|
||||
on_stdout=on_stdout,
|
||||
)
|
||||
)
|
||||
except asyncio.CancelledError:
|
||||
except Exception as e: # noqa: BLE001
|
||||
# On failure, show diagnostics then ensure clean teardown
|
||||
sys.stderr.write(f"docker compose up failed: {e}\n")
|
||||
try:
|
||||
sys.stderr.write("\n== docker compose ps ==\n")
|
||||
runner.run(
|
||||
subp_exec(*compose_cmd, *args, "ps", input=stdin, verbose=False)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
sys.stderr.write("\n== docker compose logs (api) ==\n")
|
||||
runner.run(
|
||||
subp_exec(
|
||||
*compose_cmd,
|
||||
*args,
|
||||
"logs",
|
||||
"langgraph-api",
|
||||
input=stdin,
|
||||
verbose=False,
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
runner.run(
|
||||
subp_exec(
|
||||
*compose_cmd,
|
||||
*args,
|
||||
"down",
|
||||
"-v",
|
||||
"--remove-orphans",
|
||||
input=stdin,
|
||||
verbose=False,
|
||||
)
|
||||
)
|
||||
finally:
|
||||
raise
|
||||
|
||||
set("")
|
||||
base_url = f"http://localhost:{port}"
|
||||
ok_url = f"{base_url}/ok"
|
||||
logger.info(f"Waiting for {ok_url} to respond with 200...")
|
||||
deadline = time.time() + 30
|
||||
last_err: Exception | None = None
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
with request.urlopen(ok_url, timeout=2) as resp:
|
||||
if resp.status == 200:
|
||||
sys.stdout.write(
|
||||
f"""Ready!\n- API: {base_url}\n- /ok: 200 OK\n"""
|
||||
)
|
||||
sys.stdout.flush()
|
||||
break
|
||||
else:
|
||||
last_err = RuntimeError(f"Unexpected status: {resp.status}")
|
||||
logger.error(f"Unexpected status: {resp.status}")
|
||||
except error.URLError as e:
|
||||
logger.error(f"URLError: {e}")
|
||||
last_err = e
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.error(f"Exception: {e}")
|
||||
last_err = e
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
logger.error("Timeout waiting for /ok to return 200")
|
||||
# Bring stack down before raising
|
||||
args_down = [*args, "down", "-v", "--remove-orphans"]
|
||||
try:
|
||||
runner.run(
|
||||
subp_exec(
|
||||
*compose_cmd,
|
||||
*args_down,
|
||||
input=stdin,
|
||||
verbose=verbose,
|
||||
)
|
||||
)
|
||||
finally:
|
||||
raise SystemExit(
|
||||
f"/ok did not return 202 within timeout. Last error: {last_err}"
|
||||
)
|
||||
|
||||
# Clean up: bring compose stack down to free ports for next test
|
||||
logger.info("Test succeeded. Bringing down compose stack...")
|
||||
try:
|
||||
args_down = [*args, "down", "-v", "--remove-orphans"]
|
||||
runner.run(
|
||||
subp_exec(
|
||||
*compose_cmd,
|
||||
*args_down,
|
||||
input=stdin,
|
||||
verbose=verbose,
|
||||
)
|
||||
)
|
||||
logger.info("Compose stack down. Finishing...")
|
||||
except Exception:
|
||||
logger.exception("Failed to bring down compose stack")
|
||||
pass
|
||||
|
||||
logger.info("Test finished")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
@@ -110,6 +166,12 @@ if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-t", "--tag", type=str)
|
||||
parser.add_argument("-c", "--config", type=str, default="./langgraph.json")
|
||||
parser.add_argument("-p", "--port", default=DEFAULT_PORT)
|
||||
parser.add_argument("-p", "--port", type=int, default=DEFAULT_PORT)
|
||||
args = parser.parse_args()
|
||||
test(pathlib.Path(args.config), args.port, args.tag, verbose=True)
|
||||
try:
|
||||
test(pathlib.Path(args.config), args.port, args.tag, verbose=True)
|
||||
except BaseException:
|
||||
logger.exception("Test failed")
|
||||
raise
|
||||
|
||||
logger.info("Test execution finished")
|
||||
|
||||
@@ -13,13 +13,26 @@ jobs:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.14"
|
||||
example:
|
||||
- name: A
|
||||
workdir: libs/cli/examples
|
||||
tag: langgraph-test-a
|
||||
- name: B
|
||||
workdir: libs/cli/examples/graphs
|
||||
tag: langgraph-test-b
|
||||
- name: C
|
||||
workdir: libs/cli/examples/graphs_reqs_a
|
||||
tag: langgraph-test-c
|
||||
- name: D
|
||||
workdir: libs/cli/examples/graphs_reqs_b
|
||||
tag: langgraph-test-d
|
||||
name: "CLI integration test"
|
||||
defaults:
|
||||
run:
|
||||
working-directory: libs/cli
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: Ana06/get-changed-files@v2.3.0
|
||||
@@ -27,48 +40,79 @@ jobs:
|
||||
filter: "libs/cli/**"
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
if: steps.changed-files.outputs.all
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
enable-cache: true
|
||||
cache-suffix: "cli-integration-test"
|
||||
ignore-nothing-to-cache: true
|
||||
- name: Setup env
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: libs/cli/examples
|
||||
run: cat .env.example > .env
|
||||
- name: Install cli globally
|
||||
if: steps.changed-files.outputs.all
|
||||
run: pip install -e .
|
||||
- name: Build and test service A
|
||||
- name: Build and test service ${{ matrix.example.name }}
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: libs/cli/examples
|
||||
working-directory: ${{ matrix.example.workdir }}
|
||||
env:
|
||||
LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }}
|
||||
run: |
|
||||
# The build-arg isn't used; just testing that we accept other args
|
||||
langgraph build -t langgraph-test-a --base-image "langchain/langgraph-trial"
|
||||
cp .env.example .envg
|
||||
timeout 60 python ../../../.github/scripts/run_langgraph_cli_test.py -c langgraph.json -t langgraph-test-a
|
||||
- name: Build and test service B
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: libs/cli/examples/graphs
|
||||
run: |
|
||||
langgraph build -t langgraph-test-b --base-image "langchain/langgraph-trial"
|
||||
timeout 60 python ../../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-b
|
||||
- name: Build and test service C
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: libs/cli/examples/graphs_reqs_a
|
||||
run: |
|
||||
langgraph build -t langgraph-test-c --base-image "langchain/langgraph-trial"
|
||||
timeout 60 python ../../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-c
|
||||
- name: Build and test service D
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: libs/cli/examples/graphs_reqs_b
|
||||
run: |
|
||||
langgraph build -t langgraph-test-d --base-image "langchain/langgraph-trial"
|
||||
timeout 60 python ../../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-d
|
||||
# Build the image for this example
|
||||
langgraph build -t ${{ matrix.example.tag }}
|
||||
# Prepare environment file from local or parent example directory
|
||||
if [ -f .env.example ]; then cp .env.example .env; elif [ -f ../.env.example ]; then cp ../.env.example .env && cp ../.env.example ../.env; fi
|
||||
if [ -n "${{ secrets.LANGSMITH_API_KEY }}" ]; then echo "LANGSMITH_API_KEY=${{ secrets.LANGSMITH_API_KEY }}" >> .env; if [ -f ../.env ]; then echo "LANGSMITH_API_KEY=${{ secrets.LANGSMITH_API_KEY }}" >> ../.env; fi; fi
|
||||
# Run the integration test using the built tag
|
||||
# Compute repo root to reference the shared script robustly
|
||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
timeout 60 python "$REPO_ROOT/.github/scripts/run_langgraph_cli_test.py" -t ${{ matrix.example.tag }}
|
||||
|
||||
- name: Build JS service
|
||||
if: steps.changed-files.outputs.all
|
||||
if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' }}
|
||||
working-directory: libs/cli/js-examples
|
||||
run: |
|
||||
langgraph build -t langgraph-test-e
|
||||
|
||||
- name: Build JS monorepo service
|
||||
if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' }}
|
||||
working-directory: libs/cli/js-monorepo-example
|
||||
run: |
|
||||
langgraph build -t langgraph-test-f -c apps/agent/langgraph.json --build-command "yarn run turbo build" --install-command "yarn install"
|
||||
|
||||
- name: Build Python monorepo service
|
||||
if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' }}
|
||||
working-directory: libs/cli/python-monorepo-example
|
||||
run: |
|
||||
langgraph build -t langgraph-test-g -c apps/agent/langgraph.json
|
||||
cp apps/agent/.env.example apps/agent/.env
|
||||
if [ -n "${{ secrets.LANGSMITH_API_KEY }}" ]; then echo "LANGSMITH_API_KEY=${{ secrets.LANGSMITH_API_KEY }}" >> apps/agent/.env; fi
|
||||
timeout 60 python ../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-g -c apps/agent/langgraph.json
|
||||
|
||||
- name: Build and test prerelease reqs service
|
||||
if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' }}
|
||||
working-directory: libs/cli/examples/graph_prerelease_reqs
|
||||
run: |
|
||||
langgraph build -t langgraph-test-h
|
||||
cp ../.env.example .env
|
||||
if [ -n "${{ secrets.LANGSMITH_API_KEY }}" ]; then echo "LANGSMITH_API_KEY=${{ secrets.LANGSMITH_API_KEY }}" >> .env; fi
|
||||
timeout 60 python ../../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-h
|
||||
echo "Finished starting up langgraph-test-h"
|
||||
LANGGRAPH_VERSION=$(docker run --rm --entrypoint "" langgraph-test-h python -c "import sys; from importlib.metadata import version; v = version('langgraph'); print(v);")
|
||||
if [ "$LANGGRAPH_VERSION" != "1.0.2" ]; then
|
||||
echo "LANGGRAPH_VERSION != 1.0.2; $LANGGRAPH_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
LANGCHAIN_OPENAI_VERSION=$(docker run --rm --entrypoint "" langgraph-test-h python -c "import sys; from importlib.metadata import version; v = version('langchain-openai'); print(v);")
|
||||
if [ "$LANGCHAIN_OPENAI_VERSION" != "1.0.1" ]; then
|
||||
echo "LANGCHAIN_OPENAI_VERSION != 1.0.1; $LANGCHAIN_OPENAI_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
LANGCHAIN_ANTHROPIC_VERSION=$(docker run --rm --entrypoint "" langgraph-test-h python -c "import sys; from importlib.metadata import version; v = version('langchain-anthropic'); print(v);")
|
||||
if [ "$LANGCHAIN_ANTHROPIC_VERSION" != "1.0.0a5" ]; then
|
||||
echo "LANGCHAIN_ANTHROPIC_VERSION != 1.0.0a5; $LANGCHAIN_ANTHROPIC_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build and test prerelease reqs fail service
|
||||
if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' }}
|
||||
working-directory: libs/cli/examples/graph_prerelease_reqs_fail
|
||||
run: |
|
||||
langgraph build -t langgraph-test-i || [ $? -eq 1 ]
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- "3.12"
|
||||
name: "lint #${{ matrix.python-version }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: Ana06/get-changed-files@v2.3.0
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
filter: "${{ inputs.working-directory }}/**"
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
if: steps.changed-files.outputs.all
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
enable-cache: true
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: uv sync --frozen --group dev
|
||||
run: uv sync --frozen --group lint
|
||||
|
||||
- name: Get .mypy_cache to speed up mypy
|
||||
if: steps.changed-files.outputs.all
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
- name: Install test dependencies
|
||||
if: steps.changed-files.outputs.all
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: uv sync --group dev
|
||||
run: uv sync --group lint
|
||||
|
||||
- name: Get .mypy_cache_test to speed up mypy
|
||||
if: steps.changed-files.outputs.all
|
||||
|
||||
@@ -17,17 +17,17 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
- "3.14"
|
||||
|
||||
name: "test #${{ matrix.python-version }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
enable-cache: true
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: uv sync --frozen --group dev
|
||||
run: uv sync --frozen --group test --no-dev
|
||||
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
|
||||
@@ -12,20 +12,20 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
- "3.14"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: libs/langgraph
|
||||
name: "test #${{ matrix.python-version }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
enable-cache: true
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: uv sync --frozen --group dev
|
||||
run: uv sync --frozen --group test --no-dev
|
||||
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
|
||||
@@ -16,7 +16,6 @@ permissions:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
@@ -24,10 +23,10 @@ jobs:
|
||||
version: ${{ steps.check-version.outputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Python $${ env.PYTHON_VERSION }}
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
enable-cache: true
|
||||
@@ -49,7 +48,7 @@ jobs:
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: test-dist
|
||||
path: ${{ inputs.working-directory }}/dist/
|
||||
@@ -75,9 +74,9 @@ jobs:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: test-dist
|
||||
path: ${{ inputs.working-directory }}/dist/
|
||||
|
||||
@@ -17,16 +17,16 @@ jobs:
|
||||
run:
|
||||
working-directory: libs/langgraph
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- run: SHA=$(git rev-parse HEAD) && echo "SHA=$SHA" >> $GITHUB_ENV
|
||||
- name: Set up Python 3.11
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: "3.11"
|
||||
enable-cache: true
|
||||
cache-suffix: "bench"
|
||||
- name: Install dependencies
|
||||
run: uv sync --group dev
|
||||
run: uv sync --group test
|
||||
- name: Run benchmarks
|
||||
run: OUTPUT=out/benchmark-baseline.json make -s benchmark
|
||||
- name: Save outputs
|
||||
|
||||
@@ -15,20 +15,20 @@ jobs:
|
||||
run:
|
||||
working-directory: libs/langgraph
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- id: files
|
||||
name: Get changed files
|
||||
uses: Ana06/get-changed-files@v2.3.0
|
||||
with:
|
||||
format: json
|
||||
- name: Set up Python 3.11
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: "3.11"
|
||||
enable-cache: true
|
||||
cache-suffix: "bench"
|
||||
- name: Install dependencies
|
||||
run: uv sync --group dev
|
||||
run: uv sync --group test
|
||||
- name: Download baseline
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
- name: Annotation
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const file = JSON.parse(`${{ steps.files.outputs.added_modified_renamed }}`)[0]
|
||||
|
||||
@@ -3,7 +3,8 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, v1]
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
@@ -26,7 +27,7 @@ jobs:
|
||||
python: ${{ steps.filter.outputs.python }}
|
||||
deps: ${{ steps.filter.outputs.deps }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
@@ -77,6 +78,7 @@ jobs:
|
||||
"libs/checkpoint-sqlite",
|
||||
"libs/checkpoint-postgres",
|
||||
"libs/prebuilt",
|
||||
"libs/sdk-py",
|
||||
]
|
||||
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
|
||||
uses: ./.github/workflows/_test.yml
|
||||
@@ -98,9 +100,9 @@ jobs:
|
||||
name: "Check SDK methods matching"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: Run check_sdk_methods script
|
||||
@@ -116,9 +118,9 @@ jobs:
|
||||
python-version:
|
||||
- "3.11"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: "3.11"
|
||||
enable-cache: true
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
name: Deploy Docs
|
||||
name: Deploy Docs Redirects
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@@ -23,30 +20,18 @@ defaults:
|
||||
working-directory: docs
|
||||
|
||||
jobs:
|
||||
get-changed-files:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
changed-files: ${{ steps.changed-files.outputs.added_modified }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: Ana06/get-changed-files@v2.3.0
|
||||
with:
|
||||
filter: "docs/docs/**"
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10 # Job will be cancelled if it runs for more than 10 minutes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.MKDOCS_GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: "3.12"
|
||||
enable-cache: true
|
||||
@@ -62,85 +47,22 @@ jobs:
|
||||
uv run pip install "git+https://${GITHUB_TOKEN}@github.com/langchain-ai/mkdocs-material-insiders.git"
|
||||
fi
|
||||
|
||||
- name: Run unit tests
|
||||
# Run unit tests on the docs build pipeline
|
||||
run: make tests
|
||||
- name: Lint Docs
|
||||
# This step lints the docs using the existing linting set up.
|
||||
# It should be very fast and should not require any external services.
|
||||
run: make lint-docs
|
||||
- name: Build llms-text
|
||||
run: make llms-text
|
||||
- name: Build site
|
||||
run: |
|
||||
# If this is main branch, then we want to download stats. we do this
|
||||
# with the env variable DOWNLOAD_STATS=true
|
||||
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
|
||||
DOWNLOAD_STATS=true make build-docs
|
||||
else
|
||||
make build-docs
|
||||
fi
|
||||
|
||||
- name: Build site (redirects only)
|
||||
run: make build-docs
|
||||
env:
|
||||
MKDOCS_GIT_COMMITTERS_APIKEY: ${{ secrets.MKDOCS_GIT_COMMITTERS_APIKEY }}
|
||||
OPENAI_API_KEY: sf-proj-1234567890 # fake placeholder, shouldn't actually be used
|
||||
ANTHROPIC_API_KEY: sk-ant-api03-1234567890 # fake placeholder, shouldn't actually be used
|
||||
- name: Check links in notebooks
|
||||
env:
|
||||
LANGCHAIN_API_KEY: test
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "schedule" ]; then
|
||||
echo "Running link check on all HTML files matching notebooks in docs directory..."
|
||||
uv run pytest -v \
|
||||
--check-links-ignore "https://(api|web|docs)\.smith\.langchain\.com/.*" \
|
||||
--check-links-ignore "https://academy\.langchain\.com/.*" \
|
||||
--check-links-ignore "https://x.com/.*" \
|
||||
--check-links-ignore "https://twitter.com/.*" \
|
||||
--check-links-ignore "https://github\.com/.*" \
|
||||
--check-links-ignore "http://localhost:8123/.*" \
|
||||
--check-links-ignore "http://localhost:2024.*" \
|
||||
--check-links-ignore "http://127.0.0.1:.*" \
|
||||
--check-links-ignore "/.*\.(ipynb|html)$" \
|
||||
--check-links-ignore "https://python\.langchain\.com/.*" \
|
||||
--check-links-ignore "https://openai\.com/.*" \
|
||||
--check-links-ignore "https://www\.uber\.com/.*" \
|
||||
--check-links-ignore "https://pepy\.tech/.*" \
|
||||
--check-links-ignore "docs/docs/static/wordmark_*" \
|
||||
--check-links $(find site -name "index.html" | grep -v 'storm/index.html')
|
||||
|
||||
else
|
||||
echo "Fetching changes from origin/main..."
|
||||
git fetch origin main
|
||||
echo "Checking for changed notebook files..."
|
||||
CHANGED_FILES=$(git diff --name-only --diff-filter=d origin/main | grep 'docs/docs/.*\.ipynb$' | grep -v 'storm.ipynb' | sed -E 's|^docs/docs/|site/|; s/\.ipynb$/\/index.html/' || true)
|
||||
echo "Changed files: ${CHANGED_FILES}"
|
||||
if [ -n "${CHANGED_FILES}" ]; then
|
||||
echo "Running link check on HTML files matching changed notebook files..."
|
||||
uv run pytest -v \
|
||||
--check-links-ignore "https://(api|web|docs)\.smith\.langchain\.com/.*" \
|
||||
--check-links-ignore "https://academy\.langchain\.com/.*" \
|
||||
--check-links-ignore "http://localhost:8123/.*" \
|
||||
--check-links-ignore "http://localhost:2024.*" \
|
||||
--check-links-ignore "http://127.0.0.1:.*" \
|
||||
--check-links-ignore "https://x.com/.*" \
|
||||
--check-links-ignore "https://twitter.com/.*" \
|
||||
--check-links-ignore "https://github\.com/.*" \
|
||||
--check-links-ignore "/.*\.(ipynb|html)$" \
|
||||
--check-links-ignore "docs/docs/static/wordmark_*" \
|
||||
--check-links ${CHANGED_FILES} \
|
||||
|| ([ $? = 5 ] && exit 0 || exit $?)
|
||||
else
|
||||
echo "No notebook files changed."
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Configure GitHub Pages
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Upload Pages Artifact
|
||||
# if: github.ref == 'refs/heads/main'
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: ./docs/site/
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate PR Title
|
||||
uses: amannn/action-semantic-pull-request@v5
|
||||
uses: amannn/action-semantic-pull-request@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -40,6 +40,7 @@ jobs:
|
||||
sdk-py
|
||||
docs
|
||||
ci
|
||||
deps
|
||||
requireScope: false
|
||||
ignoreLabels: |
|
||||
ignore-lint-pr-title
|
||||
|
||||
@@ -16,7 +16,6 @@ env:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
@@ -26,10 +25,10 @@ jobs:
|
||||
tag: ${{ steps.check-version.outputs.tag }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Python
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
enable-cache: true
|
||||
@@ -51,7 +50,7 @@ jobs:
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: dist
|
||||
path: ${{ inputs.working-directory }}/dist/
|
||||
@@ -62,7 +61,13 @@ jobs:
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
run: |
|
||||
PKG_NAME=$(grep -m 1 "^name = " pyproject.toml | cut -d '"' -f 2)
|
||||
VERSION=$(grep -m 1 "^version = " pyproject.toml | cut -d '"' -f 2)
|
||||
if grep -q 'dynamic.*=.*\[.*"version".*\]' pyproject.toml; then
|
||||
# handle dynamic versioning
|
||||
DIR_NAME=$(echo "$PKG_NAME" | tr '-' '_')
|
||||
VERSION=$(grep -m 1 '^__version__' "${DIR_NAME}/__init__.py" | cut -d '"' -f 2)
|
||||
else
|
||||
VERSION=$(grep -m 1 "^version = " pyproject.toml | cut -d '"' -f 2)
|
||||
fi
|
||||
SHORT_PKG_NAME="$(echo "$PKG_NAME" | sed -e 's/langgraph//g' -e 's/-//g')"
|
||||
if [ -z $SHORT_PKG_NAME ]; then
|
||||
TAG="$VERSION"
|
||||
@@ -81,7 +86,7 @@ jobs:
|
||||
outputs:
|
||||
release-body: ${{ steps.generate-release-body.outputs.release-body }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
repository: langchain-ai/langgraph
|
||||
path: langgraph
|
||||
@@ -152,7 +157,7 @@ jobs:
|
||||
- test-pypi-publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
# We explicitly *don't* set up caching here. This ensures our tests are
|
||||
# maximally sensitive to catching breakage.
|
||||
@@ -168,7 +173,7 @@ jobs:
|
||||
# used in the real world.
|
||||
|
||||
- name: Set up Python
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
enable-cache: true
|
||||
@@ -216,7 +221,7 @@ jobs:
|
||||
uv run python -c "import $IMPORT_NAME; print(dir($IMPORT_NAME))"
|
||||
|
||||
- name: Import test dependencies
|
||||
run: uv sync --group dev
|
||||
run: uv sync --group test
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
|
||||
# Overwrite the local version of the package with the test PyPI version.
|
||||
@@ -255,16 +260,16 @@ jobs:
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Python
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
enable-cache: true
|
||||
cache-suffix: "release"
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: dist
|
||||
path: ${{ inputs.working-directory }}/dist/
|
||||
@@ -296,16 +301,16 @@ jobs:
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Python
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
enable-cache: true
|
||||
cache-suffix: "release"
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: dist
|
||||
path: ${{ inputs.working-directory }}/dist/
|
||||
|
||||
@@ -28,9 +28,9 @@ jobs:
|
||||
- "latest"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up Python + Poetry
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: "3.11"
|
||||
enable-cache: true
|
||||
|
||||
@@ -16,13 +16,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
# use minimum supported Python version
|
||||
python-version: "3.9"
|
||||
python-version: "3.10"
|
||||
enable-cache: true
|
||||
cache-suffix: "uv-lock-upgrade"
|
||||
|
||||
@@ -33,8 +33,8 @@ jobs:
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: "chore[deps]: upgrade dependencies with `uv lock --upgrade`"
|
||||
title: "chore[deps]: upgrade dependencies with `uv lock --upgrade`"
|
||||
commit-message: "chore(deps): upgrade dependencies with `uv lock --upgrade`"
|
||||
title: "chore(deps): upgrade dependencies with `uv lock --upgrade`"
|
||||
body: |
|
||||
This PR updates the dependencies in all Python packages using `uv lock --upgrade`.
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ Below is a high-level overview:
|
||||
- **langgraph** – core framework for building stateful, multi-actor agents.
|
||||
- **prebuilt** – high-level APIs for creating and running agents and tools.
|
||||
- **sdk-js** – JS/TS SDK for interacting with the LangGraph REST API.
|
||||
- **sdk-py** – Python SDK for the LangGraph Platform API.
|
||||
- **sdk-py** – Python SDK for the LangGraph Server API.
|
||||
|
||||
### Dependency map
|
||||
|
||||
|
||||
+3
-3
@@ -277,9 +277,9 @@ def my_function(arg1: int, arg2: str) -> float:
|
||||
Examples:
|
||||
This is a section for examples of how to use the function.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
my_function(1, "hello")
|
||||
```python
|
||||
my_function(1, "hello")
|
||||
\```
|
||||
|
||||
Args:
|
||||
arg1: This is a description of arg1. We do not need to specify the type since
|
||||
|
||||
@@ -63,15 +63,15 @@ LangGraph provides low-level supporting infrastructure for *any* long-running, s
|
||||
While LangGraph can be used standalone, it also integrates seamlessly with any LangChain product, giving developers a full suite of tools for building agents. To improve your LLM application development, pair LangGraph with:
|
||||
|
||||
- [LangSmith](http://www.langchain.com/langsmith) — Helpful for agent evals and observability. Debug poor-performing LLM app runs, evaluate agent trajectories, gain visibility in production, and improve performance over time.
|
||||
- [LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) — Deploy and scale agents effortlessly with a purpose-built deployment platform for long running, stateful workflows. Discover, reuse, configure, and share agents across teams — and iterate quickly with visual prototyping in [LangGraph Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/).
|
||||
- [LangChain](https://python.langchain.com/docs/introduction/) – Provides integrations and composable components to streamline LLM application development.
|
||||
- [LangSmith Deployment](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) — Deploy and scale agents effortlessly with a purpose-built deployment platform for long running, stateful workflows. Discover, reuse, configure, and share agents across teams — and iterate quickly with visual prototyping in [LangGraph Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/).
|
||||
- [LangChain](https://docs.langchain.com/oss/python/langchain/overview) – Provides integrations and composable components to streamline LLM application development.
|
||||
|
||||
> [!NOTE]
|
||||
> Looking for the JS version of LangGraph? See the [JS repo](https://github.com/langchain-ai/langgraphjs) and the [JS docs](https://langchain-ai.github.io/langgraphjs/).
|
||||
|
||||
## Additional resources
|
||||
|
||||
- [Guides](https://langchain-ai.github.io/langgraph/how-tos/): Quick, actionable code snippets for topics such as streaming, adding memory & persistence, and design patterns (e.g. branching, subgraphs, etc.).
|
||||
- [Guides](https://langchain-ai.github.io/langgraph/guides/): Quick, actionable code snippets for topics such as streaming, adding memory & persistence, and design patterns (e.g. branching, subgraphs, etc.).
|
||||
- [Reference](https://langchain-ai.github.io/langgraph/reference/graphs/): Detailed reference on core classes, methods, how to use the graph and checkpointing APIs, and higher-level prebuilt components.
|
||||
- [Examples](https://langchain-ai.github.io/langgraph/examples/): Guided examples on getting started with LangGraph.
|
||||
- [LangChain Forum](https://forum.langchain.com/): Connect with the community and share all of your technical questions, ideas, and feedback.
|
||||
@@ -81,4 +81,4 @@ While LangGraph can be used standalone, it also integrates seamlessly with any L
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
LangGraph is inspired by [Pregel](https://research.google/pubs/pub37252/) and [Apache Beam](https://beam.apache.org/). The public interface draws inspiration from [NetworkX](https://networkx.org/documentation/latest/). LangGraph is built by LangChain Inc, the creators of LangChain, but can be used without LangChain.
|
||||
LangGraph is inspired by [Pregel](https://research.google/pubs/pub37252/) and [Apache Beam](https://beam.apache.org/). The public interface draws inspiration from [NetworkX](https://networkx.org/documentation/latest/). LangGraph is built by LangChain Inc, the creators of LangChain, but can be used without LangChain.
|
||||
|
||||
@@ -18,6 +18,9 @@ build-prebuilt:
|
||||
build-docs: build-prebuilt
|
||||
TARGET_LANGUAGE=python uv run python -m mkdocs build --clean -f mkdocs.yml --strict
|
||||
|
||||
build-docs-js: build-prebuilt
|
||||
TARGET_LANGUAGE=js uv run python -m mkdocs build --clean -f mkdocs.yml --strict
|
||||
|
||||
llms-text:
|
||||
uv run python -m _scripts.generate_llms_text docs/llms-full.txt
|
||||
|
||||
|
||||
+113
-10
@@ -1,24 +1,126 @@
|
||||
# Setup
|
||||
# LangGraph Documentation
|
||||
|
||||
To setup requirements for building docs you can run:
|
||||
For more information on contributing to our documentation, see the [Contributing Guide](../CONTRIBUTING.md).
|
||||
|
||||
```bash
|
||||
uv sync --group test
|
||||
## Structure
|
||||
|
||||
The primary documentation is located in the `docs/` directory. This directory contains both the source files for the main documentation as well as the API reference doc build process.
|
||||
|
||||
### Main Documentation
|
||||
|
||||
Main documentation files are located in `docs/docs/` and are written in Markdown format. The site uses [**MkDocs**](https://www.mkdocs.org/) with the [Material theme](https://squidfunk.github.io/mkdocs-material/) and includes:
|
||||
|
||||
- **Concepts**: Core LangGraph concepts and explanations
|
||||
- **Tutorials**: Step-by-step learning guides
|
||||
- **How-tos**: Task-focused guides for specific use cases
|
||||
- **Examples**: Real-world applications and use cases
|
||||
- **Jupyter Notebooks**: Interactive tutorials that are automatically converted to markdown
|
||||
|
||||
### API Reference
|
||||
|
||||
API reference documentation is defined in `docs/docs/reference/`. Each `.md` file outlines the "template" that each page is built from. Reference content is automatically generated from docstrings in the codebase using the **mkdocstrings** plugin. Once generated, the content is plugged into the corresponding markdown file where it is referenced by using manual directives to specify which classes and/or functions are documented:
|
||||
|
||||
```markdown
|
||||
::: langgraph.graph.state.StateGraph
|
||||
options:
|
||||
show_if_no_docstring: true
|
||||
show_root_heading: true
|
||||
show_root_full_path: false
|
||||
members:
|
||||
- add_node
|
||||
- add_edge
|
||||
- add_conditional_edges
|
||||
- add_sequence
|
||||
- compile
|
||||
```
|
||||
|
||||
## Serving documentation locally
|
||||
## Build Process
|
||||
|
||||
To run the documentation server locally you can run:
|
||||
Docs are built following these steps:
|
||||
|
||||
1. **Content Processing:**
|
||||
- `_scripts/notebook_hooks.py` - Main processing pipeline that:
|
||||
- Converts how-tos/tutorial Jupyter notebooks to markdown using `notebook_convert.py`
|
||||
- Adds automatic API reference links to code blocks using `generate_api_reference_links.py`
|
||||
- Handles conditional rendering for Python/JS versions
|
||||
- Processes highlight comments and custom syntax
|
||||
|
||||
2. **API Reference Generation:**
|
||||
- **mkdocstrings** plugin extracts docstrings from Python source code
|
||||
- Manual `::: module.Class` directives in reference pages (`/docs/docs/*`) specify what to document
|
||||
- Cross-references are automatically generated between docs and API
|
||||
|
||||
3. **Site Generation:**
|
||||
- **MkDocs** processes all markdown files and generates static HTML
|
||||
- Custom hooks handle redirects and inject additional functionality
|
||||
|
||||
4. **Deployment:**
|
||||
- Site is deployed with Vercel
|
||||
- `make build-docs` generates production build (also usable for local testing)
|
||||
- Automatic redirects handle URL changes between versions
|
||||
|
||||
### Local Development
|
||||
|
||||
For local development, use the Makefile targets:
|
||||
|
||||
```bash
|
||||
# Serve docs locally with hot reloading
|
||||
make serve-docs
|
||||
|
||||
# Clean build for production testing
|
||||
make build-docs
|
||||
|
||||
# Serve with clean build
|
||||
make serve-clean-docs
|
||||
```
|
||||
|
||||
This will start the documentation server on [http://127.0.0.1:8000/langgraph/](http://127.0.0.1:8000/langgraph/).
|
||||
The `serve-docs` command:
|
||||
|
||||
- Watches source files for changes
|
||||
- Includes dirty builds for faster iteration
|
||||
- Serves on [http://127.0.0.1:8000/langgraph/](http://127.0.0.1:8000/langgraph/)
|
||||
|
||||
## Standards
|
||||
|
||||
**Docstring Format:**
|
||||
The API reference uses **Google-style docstrings** with Markdown markup. The `mkdocstrings` plugin processes these to generate documentation.
|
||||
|
||||
**Required format:**
|
||||
|
||||
```python
|
||||
def example_function(param1: str, param2: int = 5) -> bool:
|
||||
"""Brief description of the function.
|
||||
|
||||
Longer description can go here. Use Markdown syntax for
|
||||
rich formatting like **bold** and *italic*.
|
||||
|
||||
Args:
|
||||
param1: Description of the first parameter.
|
||||
param2: Description of the second parameter with default value.
|
||||
|
||||
Returns:
|
||||
Description of the return value.
|
||||
|
||||
Raises:
|
||||
ValueError: When param1 is empty.
|
||||
TypeError: When param2 is not an integer.
|
||||
|
||||
!!! warning
|
||||
This function is experimental and may change.
|
||||
|
||||
!!! version-added "Added in version 0.2.0"
|
||||
"""
|
||||
```
|
||||
|
||||
**Special Markers:**
|
||||
|
||||
- **MkDocs admonitions**: `!!! warning`, `!!! note`, `!!! version-added`
|
||||
- **Code blocks**: Standard markdown ``` syntax
|
||||
- **Cross-references**: Automatic linking via `generate_api_reference_links.py`
|
||||
|
||||
## Execute notebooks
|
||||
|
||||
If you would like to automatically execute all of the notebooks, to mimic the "Run notebooks" GHA, you can run:
|
||||
If you would like to automatically execute all of the notebooks, to mimic the "Run notebooks" GitHub action, you can run:
|
||||
|
||||
```bash
|
||||
python _scripts/prepare_notebooks_for_ci.py
|
||||
@@ -33,8 +135,9 @@ python _scripts/prepare_notebooks_for_ci.py --comment-install-cells
|
||||
```
|
||||
|
||||
`prepare_notebooks_for_ci.py` script will add VCR cassette context manager for each cell in the notebook, so that:
|
||||
* when the notebook is run for the first time, cells with network requests will be recorded to a VCR cassette file
|
||||
* when the notebook is run subsequently, the cells with network requests will be replayed from the cassettes
|
||||
|
||||
- when the notebook is run for the first time, cells with network requests will be recorded to a VCR cassette file
|
||||
- when the notebook is run subsequently, the cells with network requests will be replayed from the cassettes
|
||||
|
||||
## Adding new notebooks
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"""Generate API reference links for imports in Python code blocks within markdown files."""
|
||||
|
||||
import ast
|
||||
import importlib
|
||||
import logging
|
||||
@@ -70,8 +72,18 @@ MANUAL_API_REFERENCES_LANGGRAPH = [
|
||||
([], "langgraph.checkpoint.postgres.aio", "AsyncPostgresSaver", "checkpoints"),
|
||||
([], "langgraph.checkpoint.postgres", "PostgresSaver", "checkpoints"),
|
||||
# other prebuilts
|
||||
(["langgraph_supervisor"], "langgraph_supervisor.supervisor", "create_supervisor", "supervisor"),
|
||||
(["langgraph_supervisor"], "langgraph_supervisor.handoff", "create_handoff_tool", "supervisor"),
|
||||
(
|
||||
["langgraph_supervisor"],
|
||||
"langgraph_supervisor.supervisor",
|
||||
"create_supervisor",
|
||||
"supervisor",
|
||||
),
|
||||
(
|
||||
["langgraph_supervisor"],
|
||||
"langgraph_supervisor.handoff",
|
||||
"create_handoff_tool",
|
||||
"supervisor",
|
||||
),
|
||||
([], "langgraph_supervisor.handoff", "create_forward_message_tool", "supervisor"),
|
||||
(["langgraph_swarm"], "langgraph_swarm.swarm", "create_swarm", "swarm"),
|
||||
(["langgraph_swarm"], "langgraph_swarm.swarm", "add_active_agent_router", "swarm"),
|
||||
|
||||
@@ -29,7 +29,11 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _transform_link(
|
||||
link_name: str, scope: str, file_path: str, line_number: int, custom_title: Optional[str] = None
|
||||
link_name: str,
|
||||
scope: str,
|
||||
file_path: str,
|
||||
line_number: int,
|
||||
custom_title: Optional[str] = None,
|
||||
) -> Optional[str]:
|
||||
"""Transform a cross-reference link based on the current scope.
|
||||
|
||||
@@ -38,7 +42,7 @@ def _transform_link(
|
||||
scope: The current scope context ("global", "python", "js", etc.).
|
||||
file_path: The file path for error reporting.
|
||||
line_number: The line number for error reporting.
|
||||
custom_title: Optional custom title for the link. If None, uses link_name.
|
||||
custom_title: Optional custom title for the link. If `None`, uses link_name.
|
||||
|
||||
Returns:
|
||||
A formatted markdown link if the link is found in the scope mapping,
|
||||
@@ -117,7 +121,9 @@ CROSS_REFERENCE_PATTERN = re.compile(
|
||||
)
|
||||
|
||||
|
||||
def _replace_autolinks(markdown: str, file_path: str, *, default_scope: str = "python") -> str:
|
||||
def _replace_autolinks(
|
||||
markdown: str, file_path: str, *, default_scope: str = "python"
|
||||
) -> str:
|
||||
"""Preprocess markdown lines to handle @[links] with conditional fence scopes.
|
||||
|
||||
This function processes markdown content to transform @[link_name] references
|
||||
@@ -169,7 +175,7 @@ def _replace_autolinks(markdown: str, file_path: str, *, default_scope: str = "p
|
||||
# This is @[ref] format
|
||||
link_name = match.group("link_name")
|
||||
custom_title = None
|
||||
|
||||
|
||||
transformed = _transform_link(
|
||||
link_name, current_scope, file_path, line_number, custom_title
|
||||
)
|
||||
|
||||
Binary file not shown.
@@ -2108,9 +2108,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"hono@npm:^4.5.4":
|
||||
version: 4.8.9
|
||||
resolution: "hono@npm:4.8.9"
|
||||
checksum: 10c0/385539d1787fdc747bc869ef0e5ccc9f39cbe40289b94f23eecfc82c6ca440f059704647cd6381a5066d2cf7baa43ab25184c78d44af4c5c98a5c5b07670059e
|
||||
version: 4.9.7
|
||||
resolution: "hono@npm:4.9.7"
|
||||
checksum: 10c0/089184660a9211ea216ab95bafa45260e371651cb019db49828064b7982b0ae61cc3c4715324bfeb9037aa2460c39ffa2c91d84ad0c8d500fa77cbcc7fc07a8f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
+50
-66
@@ -4,52 +4,48 @@ This module provides link mappings for different language/framework scopes
|
||||
to resolve @[link_name] references to actual URLs.
|
||||
"""
|
||||
|
||||
# Python-specific link mappings
|
||||
# Python-specific link mappings
|
||||
PYTHON_LINK_MAP = {
|
||||
"StateGraph": "reference/graphs/#langgraph.graph.StateGraph",
|
||||
"add_conditional_edges": "reference/graphs/#langgraph.graph.StateGraph.add_conditional_edges",
|
||||
"add_edge": "reference/graphs/#langgraph.graph.StateGraph.add_edge",
|
||||
"add_node": "reference/graphs/#langgraph.graph.StateGraph.add_node",
|
||||
"add_messages": "reference/messages/#langgraph.graph.message.add_messages",
|
||||
"ToolNode": "reference/prebuilt/#langgraph.prebuilt.tool_node.ToolNode",
|
||||
"add_conditional_edges": "reference/graphs/#langgraph.graph.state.StateGraph.add_conditional_edges",
|
||||
"add_edge": "reference/graphs/#langgraph.graph.state.StateGraph.add_edge",
|
||||
"add_node": "reference/graphs/#langgraph.graph.state.StateGraph.add_node",
|
||||
"add_messages": "reference/graphs/#langgraph.graph.message.add_messages",
|
||||
"ToolNode": "reference/agents/#langgraph.prebuilt.tool_node.ToolNode",
|
||||
"CompiledStateGraph.astream": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.astream",
|
||||
"Pregel.astream": "reference/graphs/#langgraph.pregel.Pregel.astream",
|
||||
"Pregel.astream": "reference/pregel/#langgraph.pregel.Pregel.astream",
|
||||
"AsyncPostgresSaver": "reference/checkpoints/#langgraph.checkpoint.postgres.aio.AsyncPostgresSaver",
|
||||
"AsyncSqliteSaver": "reference/checkpoints/#langgraph.checkpoint.sqlite.aio.AsyncSqliteSaver",
|
||||
"BaseCheckpointSaver": "reference/checkpoints/#langgraph.checkpoint.base.BaseCheckpointSaver",
|
||||
"BaseStore": "reference/stores/#langgraph.store.base.BaseStore",
|
||||
"BaseStore.put": "reference/stores/#langgraph.store.base.BaseStore.put",
|
||||
"BinaryOperatorAggregate": "reference/channels/#langgraph.channels.BinaryOperatorAggregate",
|
||||
"BaseStore": "reference/store/#langgraph.store.base.BaseStore",
|
||||
"BaseStore.put": "reference/store/#langgraph.store.base.BaseStore.put",
|
||||
"BinaryOperatorAggregate": "reference/pregel/#langgraph.pregel.Pregel--advanced-channels-context-and-binaryoperatoraggregate",
|
||||
"CipherProtocol": "reference/checkpoints/#langgraph.checkpoint.serde.base.CipherProtocol",
|
||||
"client.runs.stream": "reference/client/#langgraph_sdk.client.RunsClient.stream",
|
||||
"client.runs.wait": "reference/client/#langgraph_sdk.client.RunsClient.wait",
|
||||
"client.threads.get_history": "reference/client/#langgraph_sdk.client.ThreadsClient.get_history",
|
||||
"client.threads.update_state": "reference/client/#langgraph_sdk.client.ThreadsClient.update_state",
|
||||
"client.runs.stream": "cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.RunsClient.stream",
|
||||
"client.runs.wait": "cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.RunsClient.wait",
|
||||
"client.threads.get_history": "cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.ThreadsClient.get_history",
|
||||
"client.threads.update_state": "cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.ThreadsClient.update_state",
|
||||
"Command": "reference/types/#langgraph.types.Command",
|
||||
"CompiledStateGraph": "reference/graphs/#langgraph.graph.state.CompiledStateGraph",
|
||||
"create_react_agent": "reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent",
|
||||
"create_supervisor": "reference/supervisor/#langgraph_supervisor.supervisor.create_supervisor",
|
||||
"EncryptedSerializer": "reference/checkpoints/#langgraph.checkpoint.serde.encrypted.EncryptedSerializer",
|
||||
"entrypoint.final": "reference/functions/#langgraph.func.entrypoint.final",
|
||||
"entrypoint": "reference/functions/#langgraph.func.entrypoint",
|
||||
"entrypoint.final": "reference/func/#langgraph.func.entrypoint.final",
|
||||
"entrypoint": "reference/func/#langgraph.func.entrypoint",
|
||||
"from_pycryptodome_aes": "reference/checkpoints/#langgraph.checkpoint.serde.encrypted.EncryptedSerializer.from_pycryptodome_aes",
|
||||
# "getContextVariable": "<insert-ref>",
|
||||
"get_state_history": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.get_state_history",
|
||||
"get_stream_writer": "reference/config/#langgraph.config.get_stream_writer",
|
||||
"HumanInterrupt": "reference/prebuilt/#langgraph.prebuilt.interrupt.HumanInterrupt",
|
||||
"InjectedState": "reference/prebuilt/#langgraph.prebuilt.InjectedState",
|
||||
"InjectedState": "reference/agents/#langgraph.prebuilt.tool_node.InjectedState",
|
||||
"InMemorySaver": "reference/checkpoints/#langgraph.checkpoint.memory.InMemorySaver",
|
||||
"interrupt": "reference/graphs/#langgraph.graph.interrupt",
|
||||
"interrupt": "reference/types/#langgraph.types.Interrupt",
|
||||
"CompiledStateGraph.invoke": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.invoke",
|
||||
"JsonPlusSerializer": "reference/checkpoints/#langgraph.checkpoint.serde.jsonplus.JsonPlusSerializer",
|
||||
"langgraph.json": "reference/configuration/#configuration-file",
|
||||
"langgraph.json": "cloud/reference/cli/#configuration-file",
|
||||
"LastValue": "reference/channels/#langgraph.channels.LastValue",
|
||||
# "MemorySaver": "<insert-ref>",
|
||||
# "messagesStateReducer": "<insert-ref>",
|
||||
"PostgresSaver": "reference/checkpoints/#langgraph.checkpoint.postgres.PostgresSaver",
|
||||
"Pregel": "reference/graphs/#langgraph.pregel.Pregel",
|
||||
"Pregel.stream": "reference/graphs/#langgraph.pregel.Pregel.stream",
|
||||
"Pregel": "reference/pregel/",
|
||||
"Pregel.stream": "reference/pregel/#langgraph.pregel.Pregel.stream",
|
||||
"pre_model_hook": "reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent",
|
||||
"protocol": "reference/checkpoints/#langgraph.checkpoint.serde.base.SerializerProtocol",
|
||||
"Send": "reference/types/#langgraph.types.Send",
|
||||
@@ -57,68 +53,56 @@ PYTHON_LINK_MAP = {
|
||||
"SqliteSaver": "reference/checkpoints/#langgraph.checkpoint.sqlite.SqliteSaver",
|
||||
"START": "reference/constants/#langgraph.constants.START",
|
||||
"CompiledStateGraph.stream": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.stream",
|
||||
"task": "reference/functions/#langgraph.func.task",
|
||||
"task": "reference/func/#langgraph.func.task",
|
||||
"Topic": "reference/channels/#langgraph.channels.Topic",
|
||||
"update_state": "reference/graphs/#langgraph.graph.state.CompiledStateGraph.update_state",
|
||||
}
|
||||
|
||||
|
||||
# JavaScript-specific link mappings
|
||||
JS_LINK_MAP = {
|
||||
"Auth": "reference/classes/sdk_auth.Auth.html",
|
||||
"StateGraph": "reference/classes/langgraph.StateGraph.html",
|
||||
"add_conditional_edges": "reference/functions/langgraph_StateGraph.addConditionalEdges.html",
|
||||
"add_edge": "reference/functions/langgraph_StateGraph.addEdge.html",
|
||||
"add_node": "reference/functions/langgraph_StateGraph.addNode.html",
|
||||
"add_messages": "reference/functions/langgraph_message.addMessages.html",
|
||||
"add_conditional_edges": "/reference/classes/langgraph.StateGraph.html#addConditionalEdges",
|
||||
"add_edge": "reference/classes/langgraph.StateGraph.html#addEdge",
|
||||
"add_node": "reference/classes/langgraph.StateGraph.html#addNode",
|
||||
"add_messages": "reference/modules/langgraph.html#addMessages",
|
||||
"ToolNode": "reference/classes/langgraph_prebuilt.ToolNode.html",
|
||||
"CompiledStateGraph.astream()": "reference/functions/langgraph_CompiledStateGraph.astream.html",
|
||||
"Pregel.astream": "reference/functions/langgraph_Pregel.astream.html",
|
||||
"AsyncPostgresSaver": "reference/classes/langgraph_checkpoint_postgres_aio.AsyncPostgresSaver.html",
|
||||
"AsyncSqliteSaver": "reference/classes/langgraph_checkpoint_sqlite_aio.AsyncSqliteSaver.html",
|
||||
"BaseCheckpointSaver": "reference/classes/langgraph_checkpoint_base.BaseCheckpointSaver.html",
|
||||
"BaseStore": "reference/classes/langgraph_store_base.BaseStore.html",
|
||||
"BaseStore.put": "reference/functions/langgraph_store_base.BaseStore.put.html",
|
||||
"BinaryOperatorAggregate": "reference/classes/langgraph_channels.BinaryOperatorAggregate.html",
|
||||
"CipherProtocol": "reference/classes/langgraph_checkpoint_serde_base.CipherProtocol.html",
|
||||
"client.runs.stream": "reference/functions/langgraph_sdk_client.RunsClient.stream.html",
|
||||
"client.runs.wait": "reference/functions/langgraph_sdk_client.RunsClient.wait.html",
|
||||
"client.threads.get_history": "reference/functions/langgraph_sdk_client.ThreadsClient.getHistory.html",
|
||||
"client.threads.update_state": "reference/functions/langgraph_sdk_client.ThreadsClient.updateState.html",
|
||||
"BaseCheckpointSaver": "reference/classes/checkpoint.BaseCheckpointSaver.html",
|
||||
"BaseStore": "reference/classes/checkpoint.BaseStore.html",
|
||||
"BaseStore.put": "reference/classes/checkpoint.BaseStore.html#put",
|
||||
"BinaryOperatorAggregate": "reference/classes/langgraph.BinaryOperatorAggregate.html",
|
||||
"client.runs.stream": "reference/classes/sdk_client.RunsClient.html#stream",
|
||||
"client.runs.wait": "reference/classes/sdk_client.RunsClient.html#wait",
|
||||
"client.threads.get_history": "reference/classes/sdk_client.ThreadsClient.html#getHistory",
|
||||
"client.threads.update_state": "reference/classes/sdk_client.ThreadsClient.html#updateState",
|
||||
"Command": "reference/classes/langgraph.Command.html",
|
||||
"CompiledStateGraph": "reference/classes/langgraph.CompiledStateGraph.html",
|
||||
"create_react_agent": "reference/functions/langgraph_prebuilt.createReactAgent.html",
|
||||
"create_supervisor": "reference/functions/langgraph_supervisor.createSupervisor.html",
|
||||
"EncryptedSerializer": "reference/classes/langgraph_checkpoint_serde_encrypted.EncryptedSerializer.html",
|
||||
"entrypoint.final": "reference/functions/langgraph_func.entrypoint.final.html",
|
||||
"entrypoint": "reference/functions/langgraph_func.entrypoint.html",
|
||||
"from_pycryptodome_aes": "reference/functions/langgraph_checkpoint_serde_encrypted.EncryptedSerializer.fromPycryptodomeAes.html",
|
||||
"entrypoint.final": "reference/functions/langgraph.entrypoint.html#final",
|
||||
"entrypoint": "reference/functions/langgraph.entrypoint.html",
|
||||
"getContextVariable": "https://v03.api.js.langchain.com/functions/_langchain_core.context.getContextVariable.html",
|
||||
"get_state_history": "reference/functions/langgraph_CompiledStateGraph.getStateHistory.html",
|
||||
"get_stream_writer": "reference/functions/langgraph_config.getStreamWriter.html",
|
||||
"HumanInterrupt": "reference/classes/langgraph_prebuilt.HumanInterrupt.html",
|
||||
"InjectedState": "reference/classes/langgraph_prebuilt.InjectedState.html",
|
||||
"InMemorySaver": "reference/classes/langgraph_checkpoint_memory.InMemorySaver.html",
|
||||
"get_state_history": "reference/classes/langgraph.CompiledStateGraph.html#getStateHistory",
|
||||
"HumanInterrupt": "reference/interfaces/langgraph_prebuilt.HumanInterrupt.html",
|
||||
"interrupt": "reference/functions/langgraph.interrupt-2.html",
|
||||
"CompiledStateGraph.invoke": "reference/functions/langgraph_CompiledStateGraph.invoke.html",
|
||||
"JsonPlusSerializer": "reference/classes/langgraph_checkpoint_serde_jsonplus.JsonPlusSerializer.html",
|
||||
"langgraph.json": "reference/configuration.html",
|
||||
"LastValue": "reference/classes/langgraph_channels.LastValue.html",
|
||||
"CompiledStateGraph.invoke": "reference/classes/langgraph.CompiledStateGraph.html#invoke",
|
||||
"langgraph.json": "cloud/reference/cli/#configuration-file",
|
||||
"MemorySaver": "reference/classes/checkpoint.MemorySaver.html",
|
||||
"messagesStateReducer": "reference/functions/langgraph.messagesStateReducer.html",
|
||||
"PostgresSaver": "reference/classes/langgraph_checkpoint_postgres.PostgresSaver.html",
|
||||
"PostgresSaver": "reference/classes/checkpoint_postgres.PostgresSaver.html",
|
||||
"Pregel": "reference/classes/langgraph.Pregel.html",
|
||||
"Pregel.stream": "reference/functions/langgraph_Pregel.stream.html",
|
||||
"Pregel.stream": "reference/classes/langgraph.Pregel.html#stream",
|
||||
"pre_model_hook": "reference/functions/langgraph_prebuilt.createReactAgent.html",
|
||||
"protocol": "reference/classes/langgraph_checkpoint_serde_base.SerializerProtocol.html",
|
||||
"protocol": "reference/interfaces/checkpoint.SerializerProtocol.html",
|
||||
"Send": "reference/classes/langgraph.Send.html",
|
||||
"SerializerProtocol": "reference/classes/langgraph_checkpoint_serde_base.SerializerProtocol.html",
|
||||
"SqliteSaver": "reference/classes/langgraph_checkpoint_sqlite.SqliteSaver.html",
|
||||
"START": "reference/constants.html#START",
|
||||
"CompiledStateGraph.stream": "reference/functions/langgraph_CompiledStateGraph.stream.html",
|
||||
"task": "reference/functions/langgraph_func.task.html",
|
||||
"Topic": "reference/classes/langgraph_channels.Topic.html",
|
||||
"update_state": "reference/functions/langgraph_CompiledStateGraph.updateState.html",
|
||||
"SerializerProtocol": "reference/interfaces/checkpoint.SerializerProtocol.html",
|
||||
"SqliteSaver": "reference/classes/checkpoint_sqlite.SqliteSaver.html",
|
||||
"START": "reference/variables/langgraph.START.html",
|
||||
"CompiledStateGraph.stream": "reference/classes/langgraph.CompiledStateGraph.html#stream",
|
||||
"task": "reference/functions/langgraph.task.html",
|
||||
## TODO (hntrl): export Topic from langgraphjs
|
||||
# "Topic": "reference/classes/langgraph_channels.Topic.html",
|
||||
"update_state": "reference/classes/langgraph.CompiledStateGraph.html#updateState",
|
||||
}
|
||||
|
||||
# TODO: Allow updating these to localhost for local development
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"""Convert Jupyter notebooks to markdown with custom processing."""
|
||||
|
||||
import ast
|
||||
import os
|
||||
import re
|
||||
|
||||
+491
-99
@@ -27,106 +27,413 @@ DISABLED = os.getenv("DISABLE_NOTEBOOK_CONVERT") in ("1", "true", "True")
|
||||
|
||||
REDIRECT_MAP = {
|
||||
# lib redirects
|
||||
"how-tos/stream-values.ipynb": "how-tos/streaming.md#stream-graph-state",
|
||||
"how-tos/stream-updates.ipynb": "how-tos/streaming.md#stream-graph-state",
|
||||
"how-tos/streaming-content.ipynb": "how-tos/streaming.md",
|
||||
"how-tos/stream-multiple.ipynb": "how-tos/streaming.md#stream-multiple-nodes",
|
||||
"how-tos/streaming-tokens-without-langchain.ipynb": "how-tos/streaming.md#use-with-any-llm",
|
||||
"how-tos/streaming-from-final-node.ipynb": "how-tos/streaming-specific-nodes.ipynb",
|
||||
"how-tos/streaming-events-from-within-tools-without-langchain.ipynb": "how-tos/streaming-events-from-within-tools.ipynb#example-without-langchain",
|
||||
"how-tos/stream-values.ipynb": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/stream-updates.ipynb": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/streaming-content.ipynb": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/stream-multiple.ipynb": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/streaming-tokens-without-langchain.ipynb": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/streaming-from-final-node.ipynb": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/streaming-events-from-within-tools-without-langchain.ipynb": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
# graph-api
|
||||
"how-tos/state-reducers.ipynb": "how-tos/graph-api.md#define-and-update-state",
|
||||
"how-tos/sequence.ipynb": "how-tos/graph-api.md#create-a-sequence-of-steps",
|
||||
"how-tos/branching.ipynb": "how-tos/graph-api.md#create-branches",
|
||||
"how-tos/recursion-limit.ipynb": "how-tos/graph-api.md#create-and-control-loops",
|
||||
"how-tos/visualization.ipynb": "how-tos/graph-api.md#visualize-your-graph",
|
||||
"how-tos/input_output_schema.ipynb": "how-tos/graph-api.md#define-input-and-output-schemas",
|
||||
"how-tos/pass_private_state.ipynb": "how-tos/graph-api.md#pass-private-state-between-nodes",
|
||||
"how-tos/state-model.ipynb": "how-tos/graph-api.md#use-pydantic-models-for-graph-state",
|
||||
"how-tos/map-reduce.ipynb": "how-tos/graph-api.md#map-reduce-and-the-send-api",
|
||||
"how-tos/command.ipynb": "how-tos/graph-api.md#combine-control-flow-and-state-updates-with-command",
|
||||
"how-tos/configuration.ipynb": "how-tos/graph-api.md#add-runtime-configuration",
|
||||
"how-tos/node-retries.ipynb": "how-tos/graph-api.md#add-retry-policies",
|
||||
"how-tos/return-when-recursion-limit-hits.ipynb": "how-tos/graph-api.md#impose-a-recursion-limit",
|
||||
"how-tos/async.ipynb": "how-tos/graph-api.md#async",
|
||||
"how-tos/state-reducers.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#define-and-update-state",
|
||||
"how-tos/sequence.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#create-a-sequence-of-steps",
|
||||
"how-tos/branching.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#create-branches",
|
||||
"how-tos/recursion-limit.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#create-and-control-loops",
|
||||
"how-tos/visualization.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#visualize-your-graph",
|
||||
"how-tos/input_output_schema.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#define-input-and-output-schemas",
|
||||
"how-tos/pass_private_state.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#pass-private-state-between-nodes",
|
||||
"how-tos/state-model.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#use-pydantic-models-for-graph-state",
|
||||
"how-tos/map-reduce.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#map-reduce-and-the-send-api",
|
||||
"how-tos/command.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#combine-control-flow-and-state-updates-with-command",
|
||||
"how-tos/configuration.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#add-runtime-configuration",
|
||||
"how-tos/node-retries.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#add-retry-policies",
|
||||
"how-tos/return-when-recursion-limit-hits.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#impose-a-recursion-limit",
|
||||
"how-tos/async.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api#async",
|
||||
# memory how-tos
|
||||
"how-tos/memory/manage-conversation-history.ipynb": "how-tos/memory/add-memory.md",
|
||||
"how-tos/memory/delete-messages.ipynb": "how-tos/memory/add-memory.md#delete-messages",
|
||||
"how-tos/memory/add-summary-conversation-history.ipynb": "how-tos/memory/add-memory.md#summarize-messages",
|
||||
"how-tos/memory.ipynb": "how-tos/memory/add-memory.md",
|
||||
"agents/memory.ipynb": "how-tos/memory/add-memory.md",
|
||||
"how-tos/memory/manage-conversation-history.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"how-tos/memory/delete-messages.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory#delete-messages",
|
||||
"how-tos/memory/add-summary-conversation-history.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory#summarize-messages",
|
||||
"how-tos/memory.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"agents/memory.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
# subgraph how-tos
|
||||
"how-tos/subgraph-transform-state.ipynb": "how-tos/subgraph.md#different-state-schemas",
|
||||
"how-tos/subgraphs-manage-state.ipynb": "how-tos/subgraph.md#add-persistence",
|
||||
"how-tos/subgraph-transform-state.ipynb": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs#different-state-schemas",
|
||||
"how-tos/subgraphs-manage-state.ipynb": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs#add-persistence",
|
||||
# persistence how-tos
|
||||
"how-tos/persistence_postgres.ipynb": "how-tos/memory/add-memory.md#use-in-production",
|
||||
"how-tos/persistence_mongodb.ipynb": "how-tos/memory/add-memory.md#use-in-production",
|
||||
"how-tos/persistence_redis.ipynb": "how-tos/memory/add-memory.md#use-in-production",
|
||||
"how-tos/subgraph-persistence.ipynb": "how-tos/memory/add-memory.md#use-with-subgraphs",
|
||||
"how-tos/cross-thread-persistence.ipynb": "how-tos/memory/add-memory.md#add-long-term-memory",
|
||||
"cloud/how-tos/copy_threads": "cloud/how-tos/use_threads",
|
||||
"cloud/how-tos/check-thread-status": "cloud/how-tos/use_threads",
|
||||
"cloud/concepts/threads.md": "concepts/persistence.md#threads",
|
||||
"how-tos/persistence.ipynb": "how-tos/memory/add-memory.md",
|
||||
"how-tos/persistence_postgres.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory#use-in-production",
|
||||
"how-tos/persistence_mongodb.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory#use-in-production",
|
||||
"how-tos/persistence_redis.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory#use-in-production",
|
||||
"how-tos/subgraph-persistence.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory#use-with-subgraphs",
|
||||
"how-tos/cross-thread-persistence.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory#add-long-term-memory",
|
||||
"cloud/how-tos/copy_threads": "https://docs.langchain.com/langsmith/use-threads",
|
||||
"cloud/how-tos/check-thread-status": "https://docs.langchain.com/langsmith/use-threads",
|
||||
"cloud/concepts/threads.md": "https://docs.langchain.com/oss/python/langgraph/persistence#threads",
|
||||
"how-tos/persistence.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
# tool calling how-tos
|
||||
"how-tos/tool-calling-errors.ipynb": "how-tos/tool-calling.ipynb#handle-errors",
|
||||
"how-tos/pass-config-to-tools.ipynb": "how-tos/tool-calling.ipynb#access-config",
|
||||
"how-tos/pass-run-time-values-to-tools.ipynb": "how-tos/tool-calling.ipynb#read-state",
|
||||
"how-tos/update-state-from-tools.ipynb": "how-tos/tool-calling.ipynb#update-state",
|
||||
"agents/tools.md": "how-tos/tool-calling.md",
|
||||
"how-tos/tool-calling-errors.ipynb": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"how-tos/pass-config-to-tools.ipynb": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"how-tos/pass-run-time-values-to-tools.ipynb": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"how-tos/update-state-from-tools.ipynb": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"agents/tools.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
# multi-agent how-tos
|
||||
"how-tos/agent-handoffs.ipynb": "how-tos/multi_agent.md#handoffs",
|
||||
"how-tos/multi-agent-network.ipynb": "how-tos/multi_agent.md#use-in-a-multi-agent-system",
|
||||
"how-tos/multi-agent-multi-turn-convo.ipynb": "how-tos/multi_agent.md#multi-turn-conversation",
|
||||
"how-tos/agent-handoffs.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"how-tos/multi-agent-network.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"how-tos/multi-agent-multi-turn-convo.ipynb": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
# cloud redirects
|
||||
"cloud/index.md": "index.md",
|
||||
"cloud/how-tos/index.md": "concepts/langgraph_platform",
|
||||
"cloud/concepts/api.md": "concepts/langgraph_server.md",
|
||||
"cloud/concepts/cloud.md": "concepts/langgraph_cloud.md",
|
||||
"cloud/faq/studio.md": "concepts/langgraph_studio.md#studio-faqs",
|
||||
"cloud/how-tos/human_in_the_loop_edit_state.md": "cloud/how-tos/add-human-in-the-loop.md",
|
||||
"cloud/how-tos/human_in_the_loop_user_input.md": "cloud/how-tos/add-human-in-the-loop.md",
|
||||
"concepts/platform_architecture.md": "concepts/langgraph_cloud#architecture",
|
||||
"cloud/index.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"cloud/how-tos/index.md": "https://docs.langchain.com/langsmith/home",
|
||||
"cloud/concepts/api.md": "https://docs.langchain.com/langsmith/langgraph-server",
|
||||
"cloud/concepts/cloud.md": "https://docs.langchain.com/langsmith/cloud",
|
||||
"cloud/faq/studio.md": "https://docs.langchain.com/langsmith/studio",
|
||||
"cloud/how-tos/human_in_the_loop_edit_state.md": "https://docs.langchain.com/langsmith/add-human-in-the-loop",
|
||||
"cloud/how-tos/human_in_the_loop_user_input.md": "https://docs.langchain.com/langsmith/add-human-in-the-loop",
|
||||
"concepts/platform_architecture.md": "https://docs.langchain.com/langsmith/cloud#architecture",
|
||||
# cloud streaming redirects
|
||||
"cloud/how-tos/stream_values.md": "cloud/how-tos/streaming.md#stream-graph-state",
|
||||
"cloud/how-tos/stream_updates.md": "cloud/how-tos/streaming.md#stream-graph-state",
|
||||
"cloud/how-tos/stream_messages.md": "cloud/how-tos/streaming.md#messages",
|
||||
"cloud/how-tos/stream_events.md": "cloud/how-tos/streaming.md#stream-events",
|
||||
"cloud/how-tos/stream_debug.md": "cloud/how-tos/streaming.md#debug",
|
||||
"cloud/how-tos/stream_multiple.md": "cloud/how-tos/streaming.md#stream-multiple-modes",
|
||||
"cloud/concepts/streaming.md": "concepts/streaming.md",
|
||||
"agents/streaming.md": "how-tos/streaming.md",
|
||||
"cloud/how-tos/stream_values.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/how-tos/stream_updates.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/how-tos/stream_messages.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/how-tos/stream_events.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/how-tos/stream_debug.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/how-tos/stream_multiple.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/concepts/streaming.md": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"agents/streaming.md": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
# prebuilt redirects
|
||||
"how-tos/create-react-agent.ipynb": "agents/agents.md#basic-configuration",
|
||||
"how-tos/create-react-agent-memory.ipynb": "agents/memory.md",
|
||||
"how-tos/create-react-agent-system-prompt.ipynb": "agents/context.md#prompts",
|
||||
"how-tos/create-react-agent-structured-output.ipynb": "agents/agents.md#structured-output",
|
||||
"how-tos/create-react-agent.ipynb": "https://docs.langchain.com/oss/python/langchain/agents#basic-configuration",
|
||||
"how-tos/create-react-agent-memory.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"how-tos/create-react-agent-system-prompt.ipynb": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"how-tos/create-react-agent-structured-output.ipynb": "https://docs.langchain.com/oss/python/langchain/agents#structured-output",
|
||||
# misc
|
||||
"prebuilt.md": "agents/prebuilt.md",
|
||||
"reference/prebuilt.md": "reference/agents.md",
|
||||
"concepts/high_level.md": "index.md",
|
||||
"concepts/index.md": "index.md",
|
||||
"concepts/v0-human-in-the-loop.md": "concepts/human-in-the-loop.md",
|
||||
"how-tos/index.md": "index.md",
|
||||
"tutorials/introduction.ipynb": "concepts/why-langgraph.md",
|
||||
"agents/deployment.md": "tutorials/langgraph-platform/local-server.md",
|
||||
"prebuilt.md": "https://docs.langchain.com/oss/python/langchain/agents",
|
||||
"reference/prebuilt.md": "https://reference.langchain.com/python/langgraph/agents/",
|
||||
"concepts/high_level.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"concepts/index.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"concepts/v0-human-in-the-loop.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"how-tos/index.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"tutorials/introduction.ipynb": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"agents/deployment.md": "https://docs.langchain.com/oss/python/langgraph/local-server",
|
||||
# deployment redirects
|
||||
"how-tos/deploy-self-hosted.md": "cloud/deployment/self_hosted_data_plane.md",
|
||||
"concepts/self_hosted.md": "concepts/langgraph_self_hosted_data_plane.md",
|
||||
"tutorials/deployment.md": "concepts/deployment_options.md",
|
||||
"how-tos/deploy-self-hosted.md": "https://docs.langchain.com/langsmith/hosting",
|
||||
"concepts/self_hosted.md": "https://docs.langchain.com/langsmith/hosting",
|
||||
"tutorials/deployment.md": "https://docs.langchain.com/langsmith/deployments",
|
||||
# assistant redirects
|
||||
"cloud/how-tos/assistant_versioning.md": "cloud/how-tos/configuration_cloud.md",
|
||||
"cloud/concepts/runs.md": "concepts/assistants.md#execution",
|
||||
"cloud/how-tos/assistant_versioning.md": "https://docs.langchain.com/langsmith/configuration-cloud",
|
||||
"cloud/concepts/runs.md": "https://docs.langchain.com/langsmith/assistants#execution",
|
||||
# hitl redirects
|
||||
"how-tos/wait-user-input-functional.ipynb": "how-tos/use-functional-api.md",
|
||||
"how-tos/review-tool-calls-functional.ipynb": "how-tos/use-functional-api.md",
|
||||
"how-tos/create-react-agent-hitl.ipynb": "how-tos/human_in_the_loop/add-human-in-the-loop.md",
|
||||
"agents/human-in-the-loop.md": "how-tos/human_in_the_loop/add-human-in-the-loop.md",
|
||||
"how-tos/human_in_the_loop/dynamic_breakpoints.ipynb": "how-tos/human_in_the_loop/breakpoints.md",
|
||||
"concepts/breakpoints.md": "concepts/human_in_the_loop.md",
|
||||
"how-tos/human_in_the_loop/breakpoints.md": "how-tos/human_in_the_loop/add-human-in-the-loop.md",
|
||||
"cloud/how-tos/human_in_the_loop_breakpoint.md": "cloud/how-tos/add-human-in-the-loop.md",
|
||||
"how-tos/human_in_the_loop/edit-graph-state.ipynb": "how-tos/human_in_the_loop/time-travel.md",
|
||||
"how-tos/wait-user-input-functional.ipynb": "https://docs.langchain.com/oss/python/langgraph/functional-api",
|
||||
"how-tos/review-tool-calls-functional.ipynb": "https://docs.langchain.com/oss/python/langgraph/functional-api",
|
||||
"how-tos/create-react-agent-hitl.ipynb": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"agents/human-in-the-loop.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"how-tos/human_in_the_loop/dynamic_breakpoints.ipynb": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"concepts/breakpoints.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"how-tos/human_in_the_loop/breakpoints.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"cloud/how-tos/human_in_the_loop_breakpoint.md": "https://docs.langchain.com/langsmith/add-human-in-the-loop",
|
||||
"how-tos/human_in_the_loop/edit-graph-state.ipynb": "https://docs.langchain.com/oss/python/langgraph/use-time-travel",
|
||||
|
||||
# LGP mintlify migration redirects
|
||||
"tutorials/auth/getting_started.md": "https://docs.langchain.com/langsmith/auth",
|
||||
"tutorials/auth/resource_auth.md": "https://docs.langchain.com/langsmith/resource-auth",
|
||||
"tutorials/auth/add_auth_server.md": "https://docs.langchain.com/langsmith/add-auth-server",
|
||||
"how-tos/use-remote-graph.md": "https://docs.langchain.com/langsmith/use-remote-graph",
|
||||
"how-tos/autogen-integration.md": "https://docs.langchain.com/langsmith/autogen-integration",
|
||||
"cloud/how-tos/use_stream_react.md": "https://docs.langchain.com/langsmith/use-stream-react",
|
||||
"cloud/how-tos/generative_ui_react.md": "https://docs.langchain.com/langsmith/generative-ui-react",
|
||||
"concepts/langgraph_platform.md": "https://docs.langchain.com/langsmith/deployments",
|
||||
"concepts/langgraph_components.md": "https://docs.langchain.com/langsmith/components",
|
||||
"concepts/langgraph_server.md": "https://docs.langchain.com/langsmith/langgraph-server",
|
||||
"concepts/langgraph_data_plane.md": "https://docs.langchain.com/langsmith/data-plane",
|
||||
"concepts/langgraph_control_plane.md": "https://docs.langchain.com/langsmith/control-plane",
|
||||
"concepts/langgraph_cli.md": "https://docs.langchain.com/langsmith/cli",
|
||||
"concepts/langgraph_studio.md": "https://docs.langchain.com/langsmith/studio",
|
||||
"cloud/how-tos/studio/quick_start.md": "https://docs.langchain.com/langsmith/quick-start-studio",
|
||||
"cloud/how-tos/invoke_studio.md": "https://docs.langchain.com/langsmith/use-studio#run-application",
|
||||
"cloud/how-tos/studio/manage_assistants.md": "https://docs.langchain.com/langsmith/use-studio#manage-assistants",
|
||||
"cloud/how-tos/threads_studio.md": "https://docs.langchain.com/langsmith/use-studio#manage-threads",
|
||||
"cloud/how-tos/iterate_graph_studio.md": "https://docs.langchain.com/langsmith/observability-studio#iterate-on-prompts",
|
||||
"cloud/how-tos/studio/run_evals.md": "https://docs.langchain.com/langsmith/observability-studio#run-experiments-over-a-dataset",
|
||||
"cloud/how-tos/clone_traces_studio.md": "https://docs.langchain.com/langsmith/observability-studio#debug-langsmith-traces",
|
||||
"cloud/how-tos/datasets_studio.md": "https://docs.langchain.com/langsmith/observability-studio#add-node-to-dataset",
|
||||
"concepts/sdk.md": "https://docs.langchain.com/langsmith/sdk",
|
||||
"concepts/plans.md": "https://langchain.com/pricing",
|
||||
"concepts/application_structure.md": "https://docs.langchain.com/langsmith/application-structure",
|
||||
"concepts/scalability_and_resilience.md": "https://docs.langchain.com/langsmith/scalability-and-resilience",
|
||||
"concepts/auth.md": "https://docs.langchain.com/langsmith/authentication-methods",
|
||||
"how-tos/auth/custom_auth.md": "https://docs.langchain.com/langsmith/custom-auth",
|
||||
"how-tos/auth/openapi_security.md": "https://docs.langchain.com/langsmith/openapi-security",
|
||||
"concepts/assistants.md": "https://docs.langchain.com/langsmith/assistants",
|
||||
"cloud/how-tos/configuration_cloud.md": "https://docs.langchain.com/langsmith/cloud",
|
||||
"cloud/how-tos/use_threads.md": "https://docs.langchain.com/langsmith/use-threads",
|
||||
"cloud/how-tos/background_run.md": "https://docs.langchain.com/langsmith/background-run",
|
||||
"cloud/how-tos/same-thread.md": "https://docs.langchain.com/langsmith/same-thread",
|
||||
"cloud/how-tos/stateless_runs.md": "https://docs.langchain.com/langsmith/stateless-runs",
|
||||
"cloud/how-tos/configurable_headers.md": "https://docs.langchain.com/langsmith/configurable-headers",
|
||||
"concepts/double_texting.md": "https://docs.langchain.com/langsmith/double-texting",
|
||||
"cloud/how-tos/interrupt_concurrent.md": "https://docs.langchain.com/langsmith/interrupt-concurrent",
|
||||
"cloud/how-tos/rollback_concurrent.md": "https://docs.langchain.com/langsmith/rollback-concurrent",
|
||||
"cloud/how-tos/reject_concurrent.md": "https://docs.langchain.com/langsmith/reject-concurrent",
|
||||
"cloud/how-tos/enqueue_concurrent.md": "https://docs.langchain.com/langsmith/enqueue-concurrent",
|
||||
"cloud/concepts/webhooks.md": "https://docs.langchain.com/langsmith/use-webhooks",
|
||||
"cloud/how-tos/webhooks.md": "https://docs.langchain.com/langsmith/use-webhooks",
|
||||
"cloud/concepts/cron_jobs.md": "https://docs.langchain.com/langsmith/cron-jobs",
|
||||
"cloud/how-tos/cron_jobs.md": "https://docs.langchain.com/langsmith/cron-jobs",
|
||||
"how-tos/http/custom_lifespan.md": "https://docs.langchain.com/langsmith/custom-lifespan",
|
||||
"how-tos/http/custom_middleware.md": "https://docs.langchain.com/langsmith/custom-middleware",
|
||||
"how-tos/http/custom_routes.md": "https://docs.langchain.com/langsmith/custom-routes",
|
||||
"cloud/concepts/data_storage_and_privacy.md": "https://docs.langchain.com/langsmith/data-storage-and-privacy",
|
||||
"cloud/deployment/semantic_search.md": "https://docs.langchain.com/langsmith/semantic-search",
|
||||
"how-tos/ttl/configure_ttl.md": "https://docs.langchain.com/langsmith/configure-ttl",
|
||||
"concepts/deployment_options.md": "https://docs.langchain.com/langsmith/platform-setup",
|
||||
"cloud/quick_start.md": "https://docs.langchain.com/langsmith/deployment-quickstart",
|
||||
"cloud/deployment/setup.md": "https://docs.langchain.com/langsmith/setup-app-requirements-txt",
|
||||
"cloud/deployment/setup_pyproject.md": "https://docs.langchain.com/langsmith/setup-pyproject",
|
||||
"cloud/deployment/setup_javascript.md": "https://docs.langchain.com/langsmith/setup-javascript",
|
||||
"cloud/deployment/custom_docker.md": "https://docs.langchain.com/langsmith/custom-docker",
|
||||
"cloud/deployment/graph_rebuild.md": "https://docs.langchain.com/langsmith/graph-rebuild",
|
||||
"concepts/langgraph_cloud.md": "https://docs.langchain.com/langsmith/cloud",
|
||||
"concepts/langgraph_self_hosted_data_plane.md": "https://docs.langchain.com/langsmith/hybrid",
|
||||
"concepts/langgraph_self_hosted_control_plane.md": "https://docs.langchain.com/langsmith/self-hosted",
|
||||
"concepts/langgraph_standalone_container.md": "https://docs.langchain.com/langsmith/self-hosted#standalone-server",
|
||||
"cloud/deployment/cloud.md": "https://docs.langchain.com/langsmith/cloud",
|
||||
"cloud/deployment/self_hosted_data_plane.md": "https://docs.langchain.com/langsmith/deploy-hybrid",
|
||||
"cloud/deployment/self_hosted_control_plane.md": "https://docs.langchain.com/langsmith/deploy-self-hosted-full-platform",
|
||||
"cloud/deployment/standalone_container.md": "https://docs.langchain.com/langsmith/deploy-standalone-server",
|
||||
"concepts/server-mcp.md": "https://docs.langchain.com/langsmith/server-mcp",
|
||||
"cloud/how-tos/human_in_the_loop_time_travel.md": "https://docs.langchain.com/langsmith/human-in-the-loop-time-travel",
|
||||
"cloud/how-tos/add-human-in-the-loop.md": "https://docs.langchain.com/langsmith/add-human-in-the-loop",
|
||||
"cloud/deployment/egress.md": "https://docs.langchain.com/langsmith/env-var",
|
||||
"cloud/how-tos/streaming.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/reference/api/api_ref.md": "https://docs.langchain.com/langsmith/server-api-ref",
|
||||
"cloud/reference/langgraph_server_changelog.md": "https://docs.langchain.com/langsmith/langgraph-server-changelog",
|
||||
"cloud/reference/api/api_ref_control_plane.md": "https://docs.langchain.com/langsmith/api-ref-control-plane",
|
||||
"cloud/reference/cli.md": "https://docs.langchain.com/langsmith/cli",
|
||||
"cloud/reference/env_var.md": "https://docs.langchain.com/langsmith/env-var",
|
||||
"troubleshooting/studio.md": "https://docs.langchain.com/langsmith/troubleshooting-studio",
|
||||
|
||||
# LangGraph mintlify migration redirects
|
||||
"index.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"agents/agents.md": "https://docs.langchain.com/oss/python/langchain/agents",
|
||||
"concepts/why-langgraph.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"tutorials/get-started/1-build-basic-chatbot.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"tutorials/get-started/2-add-tools.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"tutorials/get-started/3-add-memory.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"tutorials/get-started/4-human-in-the-loop.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"tutorials/get-started/5-customize-state.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"tutorials/get-started/6-time-travel.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"tutorials/langsmith/local-server.md": "https://docs.langchain.com/oss/python/langgraph/local-server",
|
||||
"tutorials/workflows.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"concepts/agentic_concepts.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"guides/index.md": "https://docs.langchain.com/oss/python/langchain/overview",
|
||||
"agents/overview.md": "https://docs.langchain.com/oss/python/langchain/agents",
|
||||
"agents/run_agents.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"concepts/low_level.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"how-tos/graph-api.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"concepts/functional_api.md": "https://docs.langchain.com/oss/python/langgraph/functional-api",
|
||||
"how-tos/use-functional-api.md": "https://docs.langchain.com/oss/python/langgraph/functional-api",
|
||||
"concepts/pregel.md": "https://docs.langchain.com/oss/python/langgraph/pregel",
|
||||
"concepts/streaming.md": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/streaming.md": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"concepts/persistence.md": "https://docs.langchain.com/oss/python/langgraph/persistence",
|
||||
"concepts/durable_execution.md": "https://docs.langchain.com/oss/python/langgraph/durable-execution",
|
||||
"concepts/memory.md": "https://docs.langchain.com/oss/python/langgraph/memory",
|
||||
"how-tos/memory/add-memory.md": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"agents/context.md": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"agents/models.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"concepts/tools.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"how-tos/tool-calling.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"concepts/human_in_the_loop.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"how-tos/human_in_the_loop/add-human-in-the-loop.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"concepts/time-travel.md": "https://docs.langchain.com/oss/python/langgraph/persistence",
|
||||
"how-tos/human_in_the_loop/time-travel.md": "https://docs.langchain.com/oss/python/langgraph/use-time-travel",
|
||||
"concepts/subgraphs.md": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs",
|
||||
"how-tos/subgraph.md": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs",
|
||||
"concepts/multi_agent.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"agents/multi-agent.md": "https://docs.langchain.com/oss/python/langchain/multi-agent",
|
||||
"how-tos/multi_agent.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"concepts/mcp.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"agents/mcp.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"concepts/tracing.md": "https://docs.langchain.com/oss/python/langgraph/observability",
|
||||
"how-tos/enable-tracing.md": "https://docs.langchain.com/oss/python/langgraph/observability",
|
||||
"agents/evals.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"examples/index.md": "https://docs.langchain.com/oss/python/langgraph/case-studies",
|
||||
"concepts/template_applications.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"tutorials/rag/langgraph_agentic_rag.md": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
|
||||
"tutorials/multi_agent/agent_supervisor.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"tutorials/sql/sql-agent.md": "https://docs.langchain.com/oss/python/langgraph/sql-agent",
|
||||
"agents/ui.md": "https://docs.langchain.com/oss/python/langgraph/ui",
|
||||
"how-tos/run-id-langsmith.md": "https://docs.langchain.com/oss/python/langgraph/observability",
|
||||
"troubleshooting/errors/index.md": "https://docs.langchain.com/oss/python/langgraph/common-errors",
|
||||
"troubleshooting/errors/INVALID_CHAT_HISTORY.md": "https://docs.langchain.com/oss/python/langgraph/INVALID_CHAT_HISTORY",
|
||||
"troubleshooting/errors/INVALID_LICENSE.md": "https://docs.langchain.com/oss/python/langgraph/common-errors",
|
||||
"adopters.md": "https://docs.langchain.com/oss/python/langgraph/case-studies",
|
||||
"concepts/faq.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"agents/prebuilt.md": "https://docs.langchain.com/oss/python/langchain/agents",
|
||||
"reference/index.md": "https://reference.langchain.com/python/langgraph/",
|
||||
"reference/graphs.md": "https://reference.langchain.com/python/langgraph/graphs/",
|
||||
"reference/func.md": "https://reference.langchain.com/python/langgraph/func/",
|
||||
"reference/pregel.md": "https://reference.langchain.com/python/langgraph/pregel/",
|
||||
"reference/checkpoints.md": "https://reference.langchain.com/python/langgraph/checkpoints/",
|
||||
"reference/store.md": "https://reference.langchain.com/python/langgraph/store/",
|
||||
"reference/cache.md": "https://reference.langchain.com/python/langgraph/cache/",
|
||||
"reference/types.md": "https://reference.langchain.com/python/langgraph/types/",
|
||||
"reference/runtime.md": "https://reference.langchain.com/python/langgraph/runtime/",
|
||||
"reference/config.md": "https://reference.langchain.com/python/langgraph/config/",
|
||||
"reference/errors.md": "https://reference.langchain.com/python/langgraph/errors/",
|
||||
"reference/constants.md": "https://reference.langchain.com/python/langgraph/constants/",
|
||||
"reference/channels.md": "https://reference.langchain.com/python/langgraph/channels/",
|
||||
"reference/agents.md": "https://reference.langchain.com/python/langgraph/agents/",
|
||||
"reference/supervisor.md": "https://reference.langchain.com/python/langgraph/supervisor/",
|
||||
"reference/swarm.md": "https://reference.langchain.com/python/langgraph/swarm/",
|
||||
"reference/mcp.md": "https://reference.langchain.com/python/langgraph/mcp/",
|
||||
"cloud/reference/sdk/python_sdk_ref.md": "https://reference.langchain.com/python/platform/python_sdk/",
|
||||
"reference/remote_graph.md": "https://reference.langchain.com/python/platform/remote_graph/",
|
||||
|
||||
# additional exclude-search entries from mkdocs.yml
|
||||
"additional-resources/index.md": "https://docs.langchain.com/oss/python/langchain/overview",
|
||||
"cloud/concepts/cron_jobs.md": "https://docs.langchain.com/langsmith/cron-jobs",
|
||||
"cloud/concepts/data_storage_and_privacy.md": "https://docs.langchain.com/langsmith/data-storage-and-privacy",
|
||||
"cloud/concepts/webhooks.md": "https://docs.langchain.com/langsmith/use-webhooks",
|
||||
"cloud/deployment/cloud.md": "https://docs.langchain.com/langsmith/cloud",
|
||||
"cloud/deployment/custom_docker.md": "https://docs.langchain.com/langsmith/custom-docker",
|
||||
"cloud/deployment/egress.md": "https://docs.langchain.com/langsmith/env-var",
|
||||
"cloud/deployment/graph_rebuild.md": "https://docs.langchain.com/langsmith/graph-rebuild",
|
||||
"cloud/deployment/self_hosted_control_plane.md": "https://docs.langchain.com/langsmith/hosting",
|
||||
"cloud/deployment/self_hosted_data_plane.md": "https://docs.langchain.com/langsmith/hosting",
|
||||
"cloud/deployment/semantic_search.md": "https://docs.langchain.com/langsmith/semantic-search",
|
||||
"cloud/deployment/setup_javascript.md": "https://docs.langchain.com/langsmith/setup-javascript",
|
||||
"cloud/deployment/setup_pyproject.md": "https://docs.langchain.com/langsmith/setup-pyproject",
|
||||
"cloud/deployment/setup.md": "https://docs.langchain.com/langsmith/setup-app-requirements-txt",
|
||||
"cloud/deployment/standalone_container.md": "https://docs.langchain.com/langsmith/docker",
|
||||
"cloud/how-tos/add-human-in-the-loop.md": "https://docs.langchain.com/langsmith/add-human-in-the-loop",
|
||||
"cloud/how-tos/background_run.md": "https://docs.langchain.com/langsmith/background-run",
|
||||
"cloud/how-tos/clone_traces_studio.md": "https://docs.langchain.com/langsmith/observability",
|
||||
"cloud/how-tos/configurable_headers.md": "https://docs.langchain.com/langsmith/configurable-headers",
|
||||
"cloud/how-tos/configuration_cloud.md": "https://docs.langchain.com/langsmith/configuration-cloud",
|
||||
"cloud/how-tos/cron_jobs.md": "https://docs.langchain.com/langsmith/cron-jobs",
|
||||
"cloud/how-tos/datasets_studio.md": "https://docs.langchain.com/langsmith/use-studio",
|
||||
"cloud/how-tos/enqueue_concurrent.md": "https://docs.langchain.com/langsmith/enqueue-concurrent",
|
||||
"cloud/how-tos/generative_ui_react.md": "https://docs.langchain.com/langsmith/generative-ui-react",
|
||||
"cloud/how-tos/human_in_the_loop_time_travel.md": "https://docs.langchain.com/langsmith/human-in-the-loop-time-travel",
|
||||
"cloud/how-tos/interrupt_concurrent.md": "https://docs.langchain.com/langsmith/interrupt-concurrent",
|
||||
"cloud/how-tos/invoke_studio.md": "https://docs.langchain.com/langsmith/use-studio",
|
||||
"cloud/how-tos/iterate_graph_studio.md": "https://docs.langchain.com/langsmith/use-studio",
|
||||
"cloud/how-tos/reject_concurrent.md": "https://docs.langchain.com/langsmith/reject-concurrent",
|
||||
"cloud/how-tos/rollback_concurrent.md": "https://docs.langchain.com/langsmith/rollback-concurrent",
|
||||
"cloud/how-tos/same-thread.md": "https://docs.langchain.com/langsmith/same-thread",
|
||||
"cloud/how-tos/stateless_runs.md": "https://docs.langchain.com/langsmith/stateless-runs",
|
||||
"cloud/how-tos/streaming.md": "https://docs.langchain.com/langsmith/streaming",
|
||||
"cloud/how-tos/studio/manage_assistants.md": "https://docs.langchain.com/langsmith/use-studio",
|
||||
"cloud/how-tos/studio/quick_start.md": "https://docs.langchain.com/langsmith/quick-start-studio",
|
||||
"cloud/how-tos/studio/run_evals.md": "https://docs.langchain.com/langsmith/observability",
|
||||
"cloud/how-tos/threads_studio.md": "https://docs.langchain.com/langsmith/use-threads",
|
||||
"cloud/how-tos/use_stream_react.md": "https://docs.langchain.com/langsmith/use-stream-react",
|
||||
"cloud/how-tos/use_threads.md": "https://docs.langchain.com/langsmith/use-threads",
|
||||
"cloud/how-tos/webhooks.md": "https://docs.langchain.com/langsmith/use-webhooks",
|
||||
"cloud/quick_start.md": "https://docs.langchain.com/langsmith/deployment-quickstart",
|
||||
"cloud/reference/api/api_ref_control_plane.md": "https://docs.langchain.com/langsmith/api-ref-control-plane",
|
||||
"cloud/reference/api/api_ref.md": "https://docs.langchain.com/langsmith/server-api-ref",
|
||||
"cloud/reference/cli.md": "https://docs.langchain.com/langsmith/cli",
|
||||
"cloud/reference/env_var.md": "https://docs.langchain.com/langsmith/env-var",
|
||||
"cloud/reference/langgraph_server_changelog.md": "https://docs.langchain.com/langsmith/langgraph-server-changelog",
|
||||
"cloud/reference/sdk/js_ts_sdk_ref.md": "https://reference.langchain.com/javascript/modules/langsmith.html",
|
||||
"concepts/application_structure.md": "https://docs.langchain.com/langsmith/application-structure",
|
||||
"concepts/assistants.md": "https://docs.langchain.com/langsmith/assistants",
|
||||
"concepts/auth.md": "https://docs.langchain.com/langsmith/auth",
|
||||
"concepts/deployment_options.md": "https://docs.langchain.com/langsmith/deployments",
|
||||
"concepts/double_texting.md": "https://docs.langchain.com/langsmith/double-texting",
|
||||
"concepts/faq.md": "https://docs.langchain.com/langsmith/faq",
|
||||
"concepts/langgraph_cli.md": "https://docs.langchain.com/langsmith/cli",
|
||||
"concepts/langgraph_cloud.md": "https://docs.langchain.com/langsmith/cloud",
|
||||
"concepts/langgraph_components.md": "https://docs.langchain.com/langsmith/components",
|
||||
"concepts/langgraph_control_plane.md": "https://docs.langchain.com/langsmith/control-plane",
|
||||
"concepts/langgraph_data_plane.md": "https://docs.langchain.com/langsmith/data-plane",
|
||||
"concepts/langgraph_platform.md": "https://docs.langchain.com/langsmith/home",
|
||||
"concepts/langgraph_self_hosted_control_plane.md": "https://docs.langchain.com/langsmith/hosting",
|
||||
"concepts/langgraph_self_hosted_data_plane.md": "https://docs.langchain.com/langsmith/hosting",
|
||||
"concepts/langgraph_server.md": "https://docs.langchain.com/langsmith/langgraph-server",
|
||||
"concepts/langgraph_standalone_container.md": "https://docs.langchain.com/langsmith/docker",
|
||||
"concepts/langgraph_studio.md": "https://docs.langchain.com/langsmith/studio",
|
||||
"concepts/plans.md": "https://docs.langchain.com/langsmith/home",
|
||||
"concepts/scalability_and_resilience.md": "https://docs.langchain.com/langsmith/scalability-and-resilience",
|
||||
"concepts/sdk.md": "https://docs.langchain.com/langsmith/sdk",
|
||||
"concepts/server-mcp.md": "https://docs.langchain.com/langsmith/server-mcp",
|
||||
"concepts/template_applications.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"concepts/why-langgraph.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"examples/index.md": "https://docs.langchain.com/oss/python/langgraph/case-studies",
|
||||
"guides/index.md": "https://docs.langchain.com/oss/python/langchain/overview",
|
||||
"how-tos/auth/custom_auth.md": "https://docs.langchain.com/langsmith/custom-auth",
|
||||
"how-tos/auth/openapi_security.md": "https://docs.langchain.com/langsmith/openapi-security",
|
||||
"how-tos/autogen-integration.md": "https://docs.langchain.com/langsmith/autogen-integration",
|
||||
"how-tos/http/custom_lifespan.md": "https://docs.langchain.com/langsmith/custom-lifespan",
|
||||
"how-tos/http/custom_middleware.md": "https://docs.langchain.com/langsmith/custom-middleware",
|
||||
"how-tos/http/custom_routes.md": "https://docs.langchain.com/langsmith/custom-routes",
|
||||
"how-tos/ttl/configure_ttl.md": "https://docs.langchain.com/langsmith/configure-ttl",
|
||||
"how-tos/use-remote-graph.md": "https://docs.langchain.com/langsmith/use-remote-graph",
|
||||
"index.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"snippets/chat_model_tabs.md": "https://docs.langchain.com/oss/python/langchain/overview",
|
||||
"troubleshooting/errors/GRAPH_RECURSION_LIMIT.md": "https://docs.langchain.com/oss/python/langgraph/GRAPH_RECURSION_LIMIT",
|
||||
"troubleshooting/errors/index.md": "https://docs.langchain.com/oss/python/langgraph/common-errors",
|
||||
"troubleshooting/errors/INVALID_CHAT_HISTORY.md": "https://docs.langchain.com/oss/python/langgraph/INVALID_CHAT_HISTORY",
|
||||
"troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md": "https://docs.langchain.com/oss/python/langgraph/INVALID_CONCURRENT_GRAPH_UPDATE",
|
||||
"troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md": "https://docs.langchain.com/oss/python/langgraph/INVALID_GRAPH_NODE_RETURN_VALUE",
|
||||
"troubleshooting/errors/INVALID_LICENSE.md": "https://docs.langchain.com/oss/python/langgraph/common-errors",
|
||||
"troubleshooting/errors/MULTIPLE_SUBGRAPHS.md": "https://docs.langchain.com/oss/python/langgraph/MULTIPLE_SUBGRAPHS",
|
||||
"troubleshooting/studio.md": "https://docs.langchain.com/langsmith/troubleshooting-studio",
|
||||
"tutorials/auth/add_auth_server.md": "https://docs.langchain.com/langsmith/add-auth-server",
|
||||
"tutorials/auth/getting_started.md": "https://docs.langchain.com/langsmith/auth",
|
||||
"tutorials/auth/resource_auth.md": "https://docs.langchain.com/langsmith/resource-auth",
|
||||
"agents/agents.md": "https://docs.langchain.com/oss/python/langchain/agents",
|
||||
"concepts/why-langgraph.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"tutorials/langsmith/local-server.md": "https://docs.langchain.com/oss/python/langgraph/local-server",
|
||||
"tutorials/workflows.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"concepts/agentic_concepts.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"guides/index.md": "https://docs.langchain.com/oss/python/langchain/overview",
|
||||
"agents/overview.md": "https://docs.langchain.com/oss/python/langchain/agents",
|
||||
"concepts/agentic_concepts.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"agents/run_agents.md": "https://docs.langchain.com/oss/python/langgraph/quickstart",
|
||||
"concepts/low_level.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"how-tos/graph-api.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"concepts/functional_api.md": "https://docs.langchain.com/oss/python/langgraph/functional-api",
|
||||
"how-tos/use-functional-api.md": "https://docs.langchain.com/oss/python/langgraph/functional-api",
|
||||
"concepts/pregel.md": "https://docs.langchain.com/oss/python/langgraph/pregel",
|
||||
"concepts/streaming.md": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"how-tos/streaming.md": "https://docs.langchain.com/oss/python/langgraph/streaming",
|
||||
"concepts/persistence.md": "https://docs.langchain.com/oss/python/langgraph/persistence",
|
||||
"concepts/durable_execution.md": "https://docs.langchain.com/oss/python/langgraph/durable-execution",
|
||||
"concepts/memory.md": "https://docs.langchain.com/oss/python/langgraph/memory",
|
||||
"how-tos/memory/add-memory.md": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"agents/context.md": "https://docs.langchain.com/oss/python/langgraph/add-memory",
|
||||
"agents/models.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"concepts/tools.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"how-tos/tool-calling.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"concepts/human_in_the_loop.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"how-tos/human_in_the_loop/add-human-in-the-loop.md": "https://docs.langchain.com/oss/python/langgraph/interrupts",
|
||||
"concepts/time-travel.md": "https://docs.langchain.com/oss/python/langgraph/persistence",
|
||||
"how-tos/human_in_the_loop/time-travel.md": "https://docs.langchain.com/oss/python/langgraph/use-time-travel",
|
||||
"concepts/subgraphs.md": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs",
|
||||
"how-tos/subgraph.md": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs",
|
||||
"concepts/multi_agent.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"agents/multi-agent.md": "https://docs.langchain.com/oss/python/langchain/multi-agent",
|
||||
"how-tos/multi_agent.md": "https://docs.langchain.com/oss/python/langgraph/graph-api",
|
||||
"concepts/mcp.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"agents/mcp.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"concepts/tracing.md": "https://docs.langchain.com/oss/python/langgraph/observability",
|
||||
"how-tos/enable-tracing.md": "https://docs.langchain.com/oss/python/langgraph/observability",
|
||||
"agents/evals.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"examples/index.md": "https://docs.langchain.com/oss/python/langgraph/case-studies",
|
||||
"concepts/template_applications.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"tutorials/rag/langgraph_agentic_rag.md": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
|
||||
"tutorials/multi_agent/agent_supervisor.md": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
|
||||
"tutorials/sql/sql-agent.md": "https://docs.langchain.com/oss/python/langgraph/sql-agent",
|
||||
"agents/ui.md": "https://docs.langchain.com/oss/python/langgraph/ui",
|
||||
"how-tos/run-id-langsmith.md": "https://docs.langchain.com/oss/python/langgraph/observability",
|
||||
"troubleshooting/errors/index.md": "https://docs.langchain.com/oss/python/langgraph/common-errors",
|
||||
"troubleshooting/errors/GRAPH_RECURSION_LIMIT.md": "https://docs.langchain.com/oss/python/langgraph/GRAPH_RECURSION_LIMIT",
|
||||
"troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md": "https://docs.langchain.com/oss/python/langgraph/INVALID_CONCURRENT_GRAPH_UPDATE",
|
||||
"troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md": "https://docs.langchain.com/oss/python/langgraph/INVALID_GRAPH_NODE_RETURN_VALUE",
|
||||
"troubleshooting/errors/MULTIPLE_SUBGRAPHS.md": "https://docs.langchain.com/oss/python/langgraph/MULTIPLE_SUBGRAPHS",
|
||||
"troubleshooting/errors/INVALID_CHAT_HISTORY.md": "https://docs.langchain.com/oss/python/langgraph/INVALID_CHAT_HISTORY",
|
||||
"troubleshooting/errors/INVALID_LICENSE.md": "https://docs.langchain.com/oss/python/langgraph/common-errors",
|
||||
"adopters.md": "https://docs.langchain.com/oss/python/langgraph/case-studies",
|
||||
"concepts/faq.md": "https://docs.langchain.com/oss/python/langgraph/overview",
|
||||
"agents/prebuilt.md": "https://docs.langchain.com/oss/python/langchain/agents",
|
||||
}
|
||||
|
||||
|
||||
@@ -481,17 +788,102 @@ def on_post_page(html: str, page: Page, config: MkDocsConfig) -> str:
|
||||
# Create HTML files for redirects after site dir has been built
|
||||
def on_post_build(config):
|
||||
use_directory_urls = config.get("use_directory_urls")
|
||||
site_dir = config["site_dir"]
|
||||
|
||||
# Track which paths have explicit redirects
|
||||
redirected_paths = set()
|
||||
|
||||
# Process explicit redirects from REDIRECT_MAP
|
||||
for page_old, page_new in REDIRECT_MAP.items():
|
||||
# Convert .ipynb to .md for path calculation
|
||||
page_old = page_old.replace(".ipynb", ".md")
|
||||
page_new = page_new.replace(".ipynb", ".md")
|
||||
page_new_before_hash, hash, suffix = page_new.partition("#")
|
||||
old_html_path = File(page_old, "", "", use_directory_urls).dest_path.replace(
|
||||
os.sep, "/"
|
||||
)
|
||||
new_html_path = File(page_new_before_hash, "", "", True).url
|
||||
new_html_path = (
|
||||
posixpath.relpath(new_html_path, start=posixpath.dirname(old_html_path))
|
||||
+ hash
|
||||
+ suffix
|
||||
)
|
||||
_write_html(config["site_dir"], old_html_path, new_html_path)
|
||||
|
||||
# Calculate the HTML path for the old page (whether it exists or not)
|
||||
if use_directory_urls:
|
||||
# With directory URLs: /path/to/page/ becomes /path/to/page/index.html
|
||||
if page_old.endswith(".md"):
|
||||
old_html_path = page_old[:-3] + "/index.html"
|
||||
else:
|
||||
old_html_path = page_old + "/index.html"
|
||||
else:
|
||||
# Without directory URLs: /path/to/page.md becomes /path/to/page.html
|
||||
if page_old.endswith(".md"):
|
||||
old_html_path = page_old[:-3] + ".html"
|
||||
else:
|
||||
old_html_path = page_old + ".html"
|
||||
|
||||
# Track this path as redirected
|
||||
redirected_paths.add(old_html_path)
|
||||
|
||||
if isinstance(page_new, str) and page_new.startswith("http"):
|
||||
# Handle external redirects
|
||||
_write_html(site_dir, old_html_path, page_new)
|
||||
else:
|
||||
# Handle internal redirects
|
||||
page_new = page_new.replace(".ipynb", ".md")
|
||||
page_new_before_hash, hash, suffix = page_new.partition("#")
|
||||
|
||||
# Try to get the new path using File class, but fallback to manual calculation
|
||||
try:
|
||||
new_html_path = File(page_new_before_hash, "", "", True).url
|
||||
new_html_path = (
|
||||
posixpath.relpath(new_html_path, start=posixpath.dirname(old_html_path))
|
||||
+ hash
|
||||
+ suffix
|
||||
)
|
||||
except:
|
||||
# Fallback: calculate relative path manually
|
||||
if use_directory_urls:
|
||||
if page_new_before_hash.endswith(".md"):
|
||||
new_html_path = page_new_before_hash[:-3] + "/"
|
||||
else:
|
||||
new_html_path = page_new_before_hash + "/"
|
||||
else:
|
||||
if page_new_before_hash.endswith(".md"):
|
||||
new_html_path = page_new_before_hash[:-3] + ".html"
|
||||
else:
|
||||
new_html_path = page_new_before_hash + ".html"
|
||||
new_html_path += hash + suffix
|
||||
|
||||
_write_html(site_dir, old_html_path, new_html_path)
|
||||
|
||||
# Create root index.html redirect
|
||||
root_redirect_html = """<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Redirecting to LangGraph Documentation</title>
|
||||
<link rel="canonical" href="https://docs.langchain.com/oss/python/langgraph/overview">
|
||||
<meta name="robots" content="noindex">
|
||||
<script>var anchor=window.location.hash.substr(1);location.href="https://docs.langchain.com/oss/python/langgraph/overview"+(anchor?"#"+anchor:"")</script>
|
||||
<meta http-equiv="refresh" content="0; url=https://docs.langchain.com/oss/python/langgraph/overview">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Documentation has moved</h1>
|
||||
<p>The LangGraph documentation has moved to <a href="https://docs.langchain.com/oss/python/langgraph/overview">docs.langchain.com</a>.</p>
|
||||
<p>Redirecting you now...</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
root_index_path = os.path.join(site_dir, "index.html")
|
||||
with open(root_index_path, "w", encoding="utf-8") as f:
|
||||
f.write(root_redirect_html)
|
||||
|
||||
# Create server-side catch-all redirect file for Netlify/Cloudflare Pages
|
||||
# This handles any pages not explicitly mapped in REDIRECT_MAP
|
||||
# Note: This won't work on GitHub Pages, but kept for potential future use
|
||||
redirects_content = """# Netlify/Cloudflare Pages redirect rules
|
||||
# Specific redirects are handled by individual HTML redirect pages
|
||||
# This is the catch-all for any unmapped pages
|
||||
|
||||
# Exclude reference docs from catch-all
|
||||
/reference/* 200
|
||||
|
||||
# Catch-all: redirect any page not explicitly mapped
|
||||
/* https://docs.langchain.com/oss/python/langgraph/overview 301
|
||||
"""
|
||||
|
||||
redirects_path = os.path.join(site_dir, "_redirects")
|
||||
with open(redirects_path, "w", encoding="utf-8") as f:
|
||||
f.write(redirects_content)
|
||||
|
||||
@@ -20,16 +20,19 @@ class Package(TypedDict):
|
||||
description: str
|
||||
"""A brief description of what the package does."""
|
||||
|
||||
|
||||
class ResolvedPackage(Package):
|
||||
weekly_downloads: int | None
|
||||
"""The weekly download count of the package."""
|
||||
language: str
|
||||
"""The language of the package. (either 'python' or 'js')"""
|
||||
|
||||
|
||||
HERE = pathlib.Path(__file__).parent
|
||||
PACKAGES_FILE = HERE / "packages.yml"
|
||||
PACKAGES = yaml.safe_load(PACKAGES_FILE.read_text())["packages"]
|
||||
|
||||
|
||||
def _get_pypi_downloads(package: Package) -> int:
|
||||
"""Retrieve the weekly download count for a package from PyPIStats."""
|
||||
|
||||
@@ -72,7 +75,8 @@ def _get_pypi_downloads(package: Package) -> int:
|
||||
return sum(entry["downloads"] for entry in sorted_data[:7])
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
|
||||
def _get_npm_downloads(package: Package) -> int:
|
||||
"""Retrieve the weekly download count for a package on the npm registry."""
|
||||
|
||||
@@ -82,14 +86,18 @@ def _get_npm_downloads(package: Package) -> int:
|
||||
npm_response = requests.get(npm_url)
|
||||
npm_response.raise_for_status()
|
||||
except requests.exceptions.HTTPError:
|
||||
raise AssertionError(f"Package {package['name']} does not exist on npm registry")
|
||||
raise AssertionError(
|
||||
f"Package {package['name']} does not exist on npm registry"
|
||||
)
|
||||
|
||||
npm_data = npm_response.json()
|
||||
|
||||
# Retrieve the first publish date using the 'created' timestamp from the 'time' field.
|
||||
created_str = npm_data.get("time", {}).get("created")
|
||||
if created_str is None:
|
||||
raise AssertionError(f"Package {package['name']} has no creation time in registry data")
|
||||
raise AssertionError(
|
||||
f"Package {package['name']} has no creation time in registry data"
|
||||
)
|
||||
# Remove the trailing 'Z' if present and parse the ISO format timestamp
|
||||
first_publish_date = datetime.fromisoformat(created_str.rstrip("Z"))
|
||||
|
||||
@@ -103,7 +111,10 @@ def _get_npm_downloads(package: Package) -> int:
|
||||
else:
|
||||
return None
|
||||
|
||||
def _get_weekly_downloads(packages: dict[str, list[Package]], fake: bool) -> list[ResolvedPackage]:
|
||||
|
||||
def _get_weekly_downloads(
|
||||
packages: dict[str, list[Package]], fake: bool
|
||||
) -> list[ResolvedPackage]:
|
||||
"""Retrieve the weekly download count for a dictionary of python or js packages."""
|
||||
resolved_packages: list[ResolvedPackage] = []
|
||||
|
||||
@@ -131,7 +142,7 @@ def _get_weekly_downloads(packages: dict[str, list[Package]], fake: bool) -> lis
|
||||
num_downloads = _get_npm_downloads(package)
|
||||
else:
|
||||
num_downloads = None
|
||||
|
||||
|
||||
resolved_packages.append(
|
||||
{
|
||||
"name": package["name"],
|
||||
@@ -145,12 +156,13 @@ def _get_weekly_downloads(packages: dict[str, list[Package]], fake: bool) -> lis
|
||||
|
||||
return resolved_packages
|
||||
|
||||
|
||||
def main(output_file: str, fake: bool) -> None:
|
||||
"""Main function to generate package download information.
|
||||
|
||||
Args:
|
||||
output_file: Path to the output YAML file.
|
||||
fake: If True, use fake download counts for testing purposes.
|
||||
fake: If `True`, use fake download counts for testing purposes.
|
||||
"""
|
||||
resolved_packages: list[ResolvedPackage] = _get_weekly_downloads(PACKAGES, fake)
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ pip install -U langgraph "langchain[anthropic]"
|
||||
|
||||
!!! info
|
||||
|
||||
LangChain is installed so the agent can call the [model](https://python.langchain.com/docs/integrations/chat/).
|
||||
`langchain[anthropic]` is installed so the agent can call the [model](https://python.langchain.com/docs/integrations/chat/).
|
||||
|
||||
:::
|
||||
|
||||
@@ -41,7 +41,7 @@ npm install @langchain/langgraph @langchain/core @langchain/anthropic
|
||||
|
||||
!!! info
|
||||
|
||||
LangChain is installed so the agent can call the [model](https://js.langchain.com/docs/integrations/chat/).
|
||||
`@langchain/core` `@langchain/anthropic` are installed so the agent can call the [model](https://js.langchain.com/docs/integrations/chat/).
|
||||
|
||||
:::
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
**Context engineering** is the practice of building dynamic systems that provide the right information and tools, in the right format, so that an AI application can accomplish a task. Context can be characterized along two key dimensions:
|
||||
|
||||
1. By **mutability**:
|
||||
- **Static context**: Immutable data that doesn't change during execution (e.g., user metadata, database connections, tools)
|
||||
- **Dynamic context**: Mutable data that evolves as the application runs (e.g., conversation history, intermediate results, tool call observations)
|
||||
- **Static context**: Immutable data that doesn't change during execution (e.g., user metadata, database connections, tools)
|
||||
- **Dynamic context**: Mutable data that evolves as the application runs (e.g., conversation history, intermediate results, tool call observations)
|
||||
2. By **lifetime**:
|
||||
- **Runtime context**: Data scoped to a single run or invocation
|
||||
- **Cross-conversation context**: Data that persists across multiple conversations or sessions
|
||||
- **Runtime context**: Data scoped to a single run or invocation
|
||||
- **Cross-conversation context**: Data that persists across multiple conversations or sessions
|
||||
|
||||
!!! tip "Runtime context vs LLM context"
|
||||
|
||||
@@ -33,7 +33,7 @@ LangGraph provides three ways to manage context, which combines the mutability a
|
||||
|
||||
**Static runtime context** represents immutable data like user metadata, tools, and database connections that are passed to an application at the start of a run via the `context` argument to `invoke`/`stream`. This data does not change during execution.
|
||||
|
||||
!!! version-added "New in LangGraph v0.6: `context` replaces `config['configurable']`"
|
||||
!!! version-added "Added in version 0.6.0: `context` replaces `config['configurable']`"
|
||||
|
||||
Runtime context is now passed to the `context` argument of `invoke`/`stream`,
|
||||
which replaces the previous pattern of passing application configuration to `config['configurable']`.
|
||||
@@ -90,7 +90,7 @@ graph.invoke( # (1)!
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
# highlight-next-line
|
||||
def node(state: State, config: Runtime[ContextSchema]):
|
||||
def node(state: State, runtime: Runtime[ContextSchema]):
|
||||
user_name = runtime.context.user_name
|
||||
...
|
||||
```
|
||||
|
||||
@@ -145,6 +145,76 @@ const agent = createReactAgent({
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
### Dynamic model selection
|
||||
|
||||
Pass a callable function to `create_react_agent` to dynamically select the model at runtime. This is useful for scenarios where you want to choose a model based on user input, configuration settings, or other runtime conditions.
|
||||
|
||||
The selector function must return a chat model. If you're using tools, you must bind the tools to the model within the selector function.
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
from langchain.chat_models import init_chat_model
|
||||
from langchain_core.language_models import BaseChatModel
|
||||
from langchain_core.tools import tool
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.prebuilt.chat_agent_executor import AgentState
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
@tool
|
||||
def weather() -> str:
|
||||
"""Returns the current weather conditions."""
|
||||
return "It's nice and sunny."
|
||||
|
||||
|
||||
# Define the runtime context
|
||||
@dataclass
|
||||
class CustomContext:
|
||||
provider: Literal["anthropic", "openai"]
|
||||
|
||||
# Initialize models
|
||||
openai_model = init_chat_model("openai:gpt-4o")
|
||||
anthropic_model = init_chat_model("anthropic:claude-sonnet-4-20250514")
|
||||
|
||||
|
||||
# Selector function for model choice
|
||||
def select_model(state: AgentState, runtime: Runtime[CustomContext]) -> BaseChatModel:
|
||||
if runtime.context.provider == "anthropic":
|
||||
model = anthropic_model
|
||||
elif runtime.context.provider == "openai":
|
||||
model = openai_model
|
||||
else:
|
||||
raise ValueError(f"Unsupported provider: {runtime.context.provider}")
|
||||
|
||||
# With dynamic model selection, you must bind tools explicitly
|
||||
return model.bind_tools([weather])
|
||||
|
||||
|
||||
# Create agent with dynamic model selection
|
||||
agent = create_react_agent(select_model, tools=[weather])
|
||||
|
||||
# Invoke with context to select model
|
||||
output = agent.invoke(
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Which model is handling this?",
|
||||
}
|
||||
]
|
||||
},
|
||||
context=CustomContext(provider="openai"),
|
||||
)
|
||||
|
||||
print(output["messages"][-1].text())
|
||||
```
|
||||
|
||||
!!! version-added "Added in version 0.6.0"
|
||||
|
||||
:::
|
||||
|
||||
## Advanced model configuration
|
||||
|
||||
### Disable streaming
|
||||
@@ -281,11 +351,13 @@ If your desired LLM isn't officially supported by LangChain, consider these opti
|
||||
:::python
|
||||
|
||||
1. **Implement a custom LangChain chat model**: Create a model conforming to the [LangChain chat model interface](https://python.langchain.com/docs/how_to/custom_chat_model/). This enables full compatibility with LangGraph's agents and workflows but requires understanding of the LangChain framework.
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. **Implement a custom LangChain chat model**: Create a model conforming to the [LangChain chat model interface](https://js.langchain.com/docs/how_to/custom_chat/). This enables full compatibility with LangGraph's agents and workflows but requires understanding of the LangChain framework.
|
||||
|
||||
:::
|
||||
|
||||
2. **Direct invocation with custom streaming**: Use your model directly by [adding custom streaming logic](../how-tos/streaming.md#use-with-any-llm) with `StreamWriter`.
|
||||
@@ -301,6 +373,7 @@ If your desired LLM isn't officially supported by LangChain, consider these opti
|
||||
- [Force model to call a specific tool](https://python.langchain.com/docs/how_to/tool_choice/)
|
||||
- [All chat model how-to guides](https://python.langchain.com/docs/how_to/#chat-models)
|
||||
- [Chat model integrations](https://python.langchain.com/docs/integrations/chat/)
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
@@ -311,4 +384,5 @@ If your desired LLM isn't officially supported by LangChain, consider these opti
|
||||
- [Force model to call a specific tool](https://js.langchain.com/docs/how_to/tool_choice/)
|
||||
- [All chat model how-to guides](https://js.langchain.com/docs/how_to/#chat-models)
|
||||
- [Chat model integrations](https://js.langchain.com/docs/integrations/chat/)
|
||||
|
||||
:::
|
||||
|
||||
@@ -367,13 +367,13 @@ To implement handoffs with `createReactAgent`, you need to:
|
||||
|
||||
3. Define a parent graph that contains individual agents as nodes:
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState } from "@langchain/langgraph";
|
||||
const multiAgentGraph = new StateGraph(MessagesZodState)
|
||||
.addNode("flight_assistant", flightAssistant)
|
||||
.addNode("hotel_assistant", hotelAssistant)
|
||||
// ...
|
||||
```
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState } from "@langchain/langgraph";
|
||||
const multiAgentGraph = new StateGraph(MessagesZodState)
|
||||
.addNode("flight_assistant", flightAssistant)
|
||||
.addNode("hotel_assistant", hotelAssistant)
|
||||
// ...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
@@ -619,7 +619,8 @@ for await (const chunk of multiAgentGraph.stream({
|
||||
3. Name of the agent or node to hand off to.
|
||||
4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.
|
||||
5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.
|
||||
:::
|
||||
|
||||
:::
|
||||
|
||||
!!! Note
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ function generateCodeSnippet({ tools, pre, post, response }) {
|
||||
if (post) lines.push(" post_model_hook=post_model_hook,");
|
||||
if (response) lines.push(" response_format=ResponseFormat,");
|
||||
|
||||
lines.push(")", "", "agent.get_graph().draw_mermaid_png()");
|
||||
lines.push(")", "", "# Visualize the graph", "# For Jupyter or GUI environments:", "agent.get_graph().draw_mermaid_png()", "", "# To save PNG to file:", "png_data = agent.get_graph().draw_mermaid_png()", "with open(\"graph.png\", \"wb\") as f:", " f.write(png_data)", "", "# For terminal/ASCII output:", "agent.get_graph().draw_ascii()");
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
@@ -99,8 +99,8 @@ Starting from the `LangGraph Platform` view...
|
||||
1. In the top-right corner, select the gear icon (`Deployment Settings`).
|
||||
1. Update the `Git Branch` to the desired branch.
|
||||
1. Check/uncheck checkbox to `Automatically update deployment on push to branch`.
|
||||
1. Branch creation/deletion and tag creation/deletion events will not trigger an update. Only pushes to an existing branch will trigger an update.
|
||||
1. Pushes in quick succession to a branch will not trigger subsequent updates. In the future, this functionality may be changed/improved.
|
||||
1. Branch creation/deletion and tag creation/deletion events will not trigger an update. Only pushes to an existing branch will trigger an update.
|
||||
1. Pushes in quick succession to a branch will queue subsequent updates. Once a build completes, the most recent commit will begin building and the other queued builds will be skipped.
|
||||
|
||||
## Add or Remove GitHub Repositories
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ Before deploying, review the [conceptual guide for the Standalone Container](../
|
||||
|
||||
`<database_name_1>` and `database_name_2` are different databases within the same instance, but `<hostname_1>` is shared. **The same database cannot be used for separate deployments**.
|
||||
|
||||
1. `LANGSMITH_API_KEY`: (if using [Lite](../../concepts/langgraph_server.md#server-versions)) LangSmith API key. This will be used to authenticate ONCE at server start up.
|
||||
1. `LANGGRAPH_CLOUD_LICENSE_KEY`: (if using [Enterprise](../../concepts/langgraph_data_plane.md#licensing)) LangGraph Platform license key. This will be used to authenticate ONCE at server start up.
|
||||
1. `LANGSMITH_ENDPOINT`: To send traces to a [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting) instance, set `LANGSMITH_ENDPOINT` to the hostname of the self-hosted LangSmith instance.
|
||||
1. Egress to `https://beacon.langchain.com` from your network. This is required for license verification and usage reporting if not running in air-gapped mode. See the [Egress documentation](../../cloud/deployment/egress.md) for more details.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -483,19 +483,19 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
|
||||
|
||||
RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt langchain_community langchain_anthropic langchain_openai wikipedia scikit-learn
|
||||
|
||||
ADD ./graphs /deps/__outer_graphs/src
|
||||
ADD ./graphs /deps/outer-graphs/src
|
||||
RUN set -ex && \
|
||||
for line in '[project]' \
|
||||
'name = "graphs"' \
|
||||
'version = "0.1"' \
|
||||
'[tool.setuptools.package-data]' \
|
||||
'"*" = ["**/*"]'; do \
|
||||
echo "$line" >> /deps/__outer_graphs/pyproject.toml; \
|
||||
echo "$line" >> /deps/outer-graphs/pyproject.toml; \
|
||||
done
|
||||
|
||||
RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
|
||||
|
||||
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_graphs/src/agent.py:graph", "storm": "/deps/__outer_graphs/src/storm.py:graph"}'
|
||||
ENV LANGSERVE_GRAPHS='{"agent": "/deps/outer-graphs/src/agent.py:graph", "storm": "/deps/outer-graphs/src/storm.py:graph"}'
|
||||
```
|
||||
|
||||
???+ note "Updating your langgraph.json file"
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
# LangGraph Server Changelog
|
||||
|
||||
> **Note:** This changelog is no longer actively maintained. For the most up-to-date LangGraph Server changelog, please visit our new documentation site: [LangGraph Server Changelog](https://docs.langchain.com/langgraph-platform/langgraph-server-changelog#langgraph-server-changelog)
|
||||
|
||||
[LangGraph Server](../../concepts/langgraph_server.md) is an API platform for creating and managing agent-based applications. It provides built-in persistence, a task queue, and supports deploying, configuring, and running assistants (agentic workflows) at scale. This changelog documents all notable updates, features, and fixes to LangGraph Server releases.
|
||||
|
||||
---
|
||||
|
||||
## v0.2.111 (2025-07-29)
|
||||
- Started the heartbeat immediately upon connection to prevent JS graph streaming errors during long startups.
|
||||
|
||||
## v0.2.110 (2025-07-29)
|
||||
- Added interrupts as default values for all operations except streams to maintain consistent behavior.
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@ LangGraph Platform provides different security defaults:
|
||||
- Can be customized with your auth handler
|
||||
|
||||
!!! note "Custom auth"
|
||||
Custom auth **is supported** for all plans in LangGraph Platform.
|
||||
|
||||
Custom auth **is supported** for all plans in LangGraph Platform.
|
||||
|
||||
### Self-Hosted
|
||||
|
||||
@@ -43,11 +44,6 @@ Custom auth **is supported** for all plans in LangGraph Platform.
|
||||
- Complete flexibility to implement your security model
|
||||
- You control all aspects of authentication and authorization
|
||||
|
||||
!!! note "Custom auth"
|
||||
|
||||
Custom auth is supported for **Enterprise** self-hosted deployments.
|
||||
Standalone Container (Lite) deployments do not support custom auth natively.
|
||||
|
||||
## System Architecture
|
||||
|
||||
A typical authentication setup involves three main components:
|
||||
|
||||
@@ -7,10 +7,7 @@ search:
|
||||
|
||||
## Free deployment
|
||||
|
||||
There are two free options for deploying LangGraph applications via the LangGraph Server:
|
||||
|
||||
1. [Local](../tutorials/langgraph-platform/local-server.md): Deploy for local testing and development.
|
||||
1. [Standalone Container (Lite)](../concepts/langgraph_standalone_container.md): A limited version of Standalone Container for deployments unlikely to see more than 1 million node executions per year and that do not need crons and other enterprise features. Standalone Container (Lite) deployment option is free with a LangSmith API key.
|
||||
[Local](../tutorials/langgraph-platform/local-server.md): Deploy for local testing and development.
|
||||
|
||||
## Production deployment
|
||||
|
||||
@@ -33,8 +30,7 @@ A quick comparison:
|
||||
| **CI/CD** | Managed internally by platform | Managed externally by you | Managed externally by you | Managed externally by you |
|
||||
| **Data/compute residency** | LangChain's cloud | Your cloud | Your cloud | Your cloud |
|
||||
| **LangSmith compatibility** | Trace to LangSmith SaaS | Trace to LangSmith SaaS | Trace to Self-Hosted LangSmith | Optional tracing |
|
||||
| **[Server version compatibility](../concepts/langgraph_server.md#server-versions)** | Enterprise | Enterprise | Enterprise | Lite, Enterprise |
|
||||
| **[Pricing](https://www.langchain.com/pricing-langgraph-platform)** | Plus | Enterprise | Enterprise | Developer |
|
||||
| **[Pricing](https://www.langchain.com/pricing-langgraph-platform)** | Plus | Enterprise | Enterprise | Enterprise |
|
||||
|
||||
## Cloud SaaS
|
||||
|
||||
|
||||
@@ -21,12 +21,16 @@ To leverage durable execution in LangGraph, you need to:
|
||||
1. Enable [persistence](./persistence.md) in your workflow by specifying a [checkpointer](./persistence.md#checkpointer-libraries) that will save workflow progress.
|
||||
2. Specify a [thread identifier](./persistence.md#threads) when executing a workflow. This will track the execution history for a particular instance of the workflow.
|
||||
|
||||
:::python
|
||||
:::python
|
||||
|
||||
3. Wrap any non-deterministic operations (e.g., random number generation) or operations with side effects (e.g., file writes, API calls) inside @[tasks][task] to ensure that when a workflow is resumed, these operations are not repeated for the particular run, and instead their results are retrieved from the persistence layer. For more information, see [Determinism and Consistent Replay](#determinism-and-consistent-replay).
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
:::js
|
||||
|
||||
3. Wrap any non-deterministic operations (e.g., random number generation) or operations with side effects (e.g., file writes, API calls) inside @[tasks][task] to ensure that when a workflow is resumed, these operations are not repeated for the particular run, and instead their results are retrieved from the persistence layer. For more information, see [Determinism and Consistent Replay](#determinism-and-consistent-replay).
|
||||
|
||||
:::
|
||||
|
||||
## Determinism and Consistent Replay
|
||||
@@ -51,6 +55,53 @@ For some examples of pitfalls to avoid, see the [Common Pitfalls](./functional_a
|
||||
how to structure your code using **tasks** to avoid these issues. The same principles apply to the @[StateGraph (Graph API)][StateGraph].
|
||||
:::
|
||||
|
||||
## Durability modes
|
||||
|
||||
LangGraph supports three durability modes that allow you to balance performance and data consistency based on your application's requirements. The durability modes, from least to most durable, are as follows:
|
||||
|
||||
- [`"exit"`](#exit)
|
||||
- [`"async"`](#async)
|
||||
- [`"sync"`](#sync)
|
||||
|
||||
A higher durability mode add more overhead to the workflow execution.
|
||||
|
||||
!!! version-added "Added in version 0.6.0"
|
||||
|
||||
Use the `durability` parameter instead of `checkpoint_during` (deprecated in v0.6.0) for persistence policy management:
|
||||
|
||||
* `durability="async"` replaces `checkpoint_during=True`
|
||||
* `durability="exit"` replaces `checkpoint_during=False`
|
||||
|
||||
for persistence policy management, with the following mapping:
|
||||
|
||||
* `checkpoint_during=True` -> `durability="async"`
|
||||
* `checkpoint_during=False` -> `durability="exit"`
|
||||
|
||||
### `"exit"`
|
||||
|
||||
Changes are persisted only when graph execution completes (either successfully or with an error). This provides the best performance for long-running graphs but means intermediate state is not saved, so you cannot recover from mid-execution failures or interrupt the graph execution.
|
||||
|
||||
### `"async"`
|
||||
|
||||
Changes are persisted asynchronously while the next step executes. This provides good performance and durability, but there's a small risk that checkpoints might not be written if the process crashes during execution.
|
||||
|
||||
### `"sync"`
|
||||
|
||||
Changes are persisted synchronously before the next step starts. This ensures that every checkpoint is written before continuing execution, providing high durability at the cost of some performance overhead.
|
||||
|
||||
You can specify the durability mode when calling any graph execution method:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
graph.stream(
|
||||
{"input": "test"},
|
||||
durability="sync"
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Using tasks in nodes
|
||||
|
||||
If a [node](./low_level.md#nodes) contains multiple operations, you may find it easier to convert each operation into a **task** rather than refactor the operations into individual nodes.
|
||||
@@ -265,12 +316,14 @@ Once you have enabled durable execution in your workflow, you can resume executi
|
||||
|
||||
- **Pausing and Resuming Workflows:** Use the @[interrupt][interrupt] function to pause a workflow at specific points and the @[Command] primitive to resume it with updated state. See [**Human-in-the-Loop**](./human_in_the_loop.md) for more details.
|
||||
- **Recovering from Failures:** Automatically resume workflows from the last successful checkpoint after an exception (e.g., LLM provider outage). This involves executing the workflow with the same thread identifier by providing it with a `None` as the input value (see this [example](../how-tos/use-functional-api.md#resuming-after-an-error) with the functional API).
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- **Pausing and Resuming Workflows:** Use the @[interrupt][interrupt] function to pause a workflow at specific points and the @[Command] primitive to resume it with updated state. See [**Human-in-the-Loop**](./human_in_the_loop.md) for more details.
|
||||
- **Recovering from Failures:** Automatically resume workflows from the last successful checkpoint after an exception (e.g., LLM provider outage). This involves executing the workflow with the same thread identifier by providing it with a `null` as the input value (see this [example](../how-tos/use-functional-api.md#resuming-after-an-error) with the functional API).
|
||||
|
||||
:::
|
||||
|
||||
## Starting Points for Resuming Workflows
|
||||
@@ -281,6 +334,7 @@ Once you have enabled durable execution in your workflow, you can resume executi
|
||||
- If you're making a subgraph call inside a node, the starting point will be the **parent** node that called the subgraph that was halted.
|
||||
Inside the subgraph, the starting point will be the specific [**node**](./low_level.md#nodes) where execution stopped.
|
||||
- If you're using the Functional API, the starting point is the beginning of the [**entrypoint**](./functional_api.md#entrypoint) where execution stopped.
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
@@ -289,4 +343,5 @@ Once you have enabled durable execution in your workflow, you can resume executi
|
||||
- If you're making a subgraph call inside a node, the starting point will be the **parent** node that called the subgraph that was halted.
|
||||
Inside the subgraph, the starting point will be the specific [**node**](./low_level.md#nodes) where execution stopped.
|
||||
- If you're using the Functional API, the starting point is the beginning of the [**entrypoint**](./functional_api.md#entrypoint) where execution stopped.
|
||||
|
||||
:::
|
||||
|
||||
@@ -13,21 +13,6 @@ Use LangGraph Server to create and manage [assistants](assistants.md), [threads]
|
||||
|
||||
For detailed information on the API endpoints and data models, see [LangGraph Platform API reference docs](../cloud/reference/api/api_ref.html).
|
||||
|
||||
## Server versions
|
||||
|
||||
There are two versions of LangGraph Server:
|
||||
|
||||
- `Lite` is a limited version of the LangGraph Server that you can run locally or in a self-hosted manner (up to 1 million [nodes executed](../concepts/faq.md#what-does-nodes-executed-mean-for-langgraph-platform-usage) per year).
|
||||
- `Enterprise` is the full version of the LangGraph Server. To use the `Enterprise` version, you must acquire a license key that you will need to specify when running the Docker image. To acquire a license key, please email sales@langchain.dev.
|
||||
|
||||
Feature Differences:
|
||||
|
||||
| | Lite | Enterprise |
|
||||
|-------|------------|------------|
|
||||
| [Cron Jobs](../cloud/concepts/cron_jobs.md) |❌|✅|
|
||||
| [Custom Authentication](../concepts/auth.md) |❌|✅|
|
||||
| [Deployment options](../concepts/deployment_options.md) | Standalone container | Cloud SaaS, Self-Hosted Data Plane, Self-Hosted Control Plane, Standalone container
|
||||
|
||||
## Application structure
|
||||
|
||||
To deploy a LangGraph Server application, you need to specify the graph(s) you want to deploy, as well as any relevant configuration settings, such as dependencies and environment variables.
|
||||
|
||||
@@ -34,12 +34,3 @@ The Standalone Container deployment option supports deploying data plane infrast
|
||||
### Docker
|
||||
|
||||
The Standalone Container deployment option supports deploying data plane infrastructure to any Docker-supported compute platform.
|
||||
|
||||
## Lite vs. Enterprise
|
||||
|
||||
The Standalone Container deployment option supports both of the [server versions](../concepts/langgraph_server.md#langgraph-server):
|
||||
|
||||
- The `Lite` version is free, but has limited features.
|
||||
- The `Enterprise` version has custom pricing and is fully featured.
|
||||
|
||||
For more details on feature difference, see [LangGraph Server](../concepts/langgraph_server.md#server-versions).
|
||||
|
||||
@@ -88,8 +88,6 @@ Typically, all graph nodes communicate with a single schema. This means that the
|
||||
|
||||
It is possible to have nodes write to private state channels inside the graph for internal node communication. We can simply define a private schema, `PrivateState`.
|
||||
|
||||
See [this guide](../how-tos/graph-api.ipynb#pass-private-state-between-nodes) for more detail.
|
||||
|
||||
It is also possible to define explicit input and output schemas for a graph. In these cases, we define an "internal" schema that contains _all_ keys relevant to graph operations. But, we also define `input` and `output` schemas that are sub-sets of the "internal" schema to constrain the input and output of the graph. See [this guide](../how-tos/graph-api.md#define-input-and-output-schemas) for more detail.
|
||||
|
||||
Let's look at an example:
|
||||
@@ -473,7 +471,7 @@ const builder = new StateGraph(State);
|
||||
|
||||
:::
|
||||
|
||||
Behind the scenes, functions are converted to [RunnableLambda](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.RunnableLambda.html#langchain_core.runnables.base.RunnableLambda)s, which add batch and async support to your function, along with native tracing and debugging.
|
||||
Behind the scenes, functions are converted to [RunnableLambda](https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.base.RunnableLambda.html)s, which add batch and async support to your function, along with native tracing and debugging.
|
||||
|
||||
If you add a node to a graph without specifying a name, it will be given a default name equivalent to the function name.
|
||||
|
||||
@@ -701,7 +699,8 @@ graph.addConditionalEdges("nodeA", routingFunction, {
|
||||
:::
|
||||
|
||||
!!! tip
|
||||
Use [`Command`](#command) instead of conditional edges if you want to combine state updates and routing in a single function.
|
||||
|
||||
Use [`Command`](#command) instead of conditional edges if you want to combine state updates and routing in a single function.
|
||||
|
||||
### Entry Point
|
||||
|
||||
@@ -820,7 +819,6 @@ def my_node(state: State) -> Command[Literal["my_other_node"]]:
|
||||
return Command(update={"foo": "baz"}, goto="my_other_node")
|
||||
```
|
||||
|
||||
Check out this [how-to guide](../how-tos/graph-api.ipynb#combine-control-flow-and-state-updates-with-command) for an end-to-end example of how to use `Command`.
|
||||
:::
|
||||
|
||||
:::js
|
||||
@@ -860,7 +858,6 @@ builder.addNode("myNode", myNode, {
|
||||
});
|
||||
```
|
||||
|
||||
Check out this [how-to guide](../how-tos/graph-api.ipynb#combine-control-flow-and-state-updates-with-command) for an end-to-end example of how to use `Command`.
|
||||
:::
|
||||
|
||||
!!! important
|
||||
@@ -1043,7 +1040,7 @@ def node_a(state: State, runtime: Runtime[ContextSchema]):
|
||||
...
|
||||
```
|
||||
|
||||
See [this guide](../how-tos/graph-api.ipynb#add-runtime-configuration) for a full breakdown on configuration.
|
||||
See [this guide](../how-tos/graph-api.md#add-runtime-configuration) for a full breakdown on configuration.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
@@ -6,52 +6,14 @@
|
||||
|
||||
Install the `langchain-mcp-adapters` library to use MCP tools in LangGraph:
|
||||
|
||||
:::python
|
||||
```bash
|
||||
pip install langchain-mcp-adapters
|
||||
```
|
||||
:::
|
||||
|
||||
## Authenticate to an MCP server
|
||||
|
||||
You can set up [custom authentication middleware](../how-tos/auth/custom_auth.md) to authenticate a user with an MCP server to get access to user-scoped tools within your LangGraph Platform deployment.
|
||||
|
||||
!!! note
|
||||
|
||||
Custom authentication is a LangGraph Platform feature.
|
||||
|
||||
An example architecture for this flow:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
%% Actors
|
||||
participant ClientApp as Client
|
||||
participant AuthProv as Auth Provider
|
||||
participant LangGraph as LangGraph Backend
|
||||
participant SecretStore as Secret Store
|
||||
participant MCPServer as MCP Server
|
||||
|
||||
%% Platform login / AuthN
|
||||
ClientApp ->> AuthProv: 1. Login (username / password)
|
||||
AuthProv -->> ClientApp: 2. Return token
|
||||
ClientApp ->> LangGraph: 3. Request with token
|
||||
|
||||
Note over LangGraph: 4. Validate token (@auth.authenticate)
|
||||
LangGraph -->> AuthProv: 5. Fetch user info
|
||||
AuthProv -->> LangGraph: 6. Confirm validity
|
||||
|
||||
%% Fetch user tokens from secret store
|
||||
LangGraph ->> SecretStore: 6a. Fetch user tokens
|
||||
SecretStore -->> LangGraph: 6b. Return tokens
|
||||
|
||||
Note over LangGraph: 7. Apply access control (@auth.on.*)
|
||||
|
||||
%% MCP round-trip
|
||||
Note over LangGraph: 8. Build MCP client with user token
|
||||
LangGraph ->> MCPServer: 9. Call MCP tool (with header)
|
||||
Note over MCPServer: 10. MCP validates header and runs tool
|
||||
MCPServer -->> LangGraph: 11. Tool response
|
||||
|
||||
%% Return to caller
|
||||
LangGraph -->> ClientApp: 12. Return resources / tool output
|
||||
:::js
|
||||
```bash
|
||||
npm install @langchain/mcp-adapters
|
||||
```
|
||||
|
||||
For more information, see [MCP endpoint in LangGraph Server](../concepts/server-mcp.md).
|
||||
:::
|
||||
@@ -134,7 +134,7 @@ def update_instructions(state: State, store: BaseStore):
|
||||
namespace = ("instructions",)
|
||||
current_instructions = store.search(namespace)[0]
|
||||
# Memory logic
|
||||
prompt = prompt_template.format(instructions=instructions.value["instructions"], conversation=state["messages"])
|
||||
prompt = prompt_template.format(instructions=current_instructions.value["instructions"], conversation=state["messages"])
|
||||
output = llm.invoke(prompt)
|
||||
new_instructions = output['new_instructions']
|
||||
store.put(("agent_instructions",), "agent_a", {"instructions": new_instructions})
|
||||
@@ -278,4 +278,4 @@ const items = await store.search(
|
||||
```
|
||||
:::
|
||||
|
||||
For more information about the memory store, see the [Persistence](persistence.md#memory-store) guide.
|
||||
For more information about the memory store, see the [Persistence](persistence.md#memory-store) guide.
|
||||
|
||||
@@ -897,5 +897,5 @@ There are two high-level approaches to achieve that:
|
||||
|
||||
An agent might need to have a different state schema from the rest of the agents. For example, a search agent might only need to keep track of queries and retrieved documents. There are two ways to achieve this in LangGraph:
|
||||
|
||||
- Define [subgraph](./subgraphs.md) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, it's important to [add input / output transformations](../how-tos/subgraph.ipynb#different-state-schemas) so that the parent graph knows how to communicate with the subgraphs.
|
||||
- Define agent node functions with a [private input state schema](../how-tos/graph-api.ipynb#pass-private-state-between-nodes) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
|
||||
- Define [subgraph](./subgraphs.md) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, it's important to [add input / output transformations](../how-tos/subgraph.md#different-state-schemas) so that the parent graph knows how to communicate with the subgraphs.
|
||||
- Define agent node functions with a [private input state schema](../how-tos/graph-api.md#pass-private-state-between-nodes) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
|
||||
|
||||
@@ -315,7 +315,8 @@ In our example, the output of `get_state_history` will look like this:
|
||||
tasks=(),
|
||||
),
|
||||
StateSnapshot(
|
||||
values={'foo': 'a', 'bar': ['a']}, next=('node_b',),
|
||||
values={'foo': 'a', 'bar': ['a']},
|
||||
next=('node_b',),
|
||||
config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef663ba-28f9-6ec4-8001-31981c2c39f8'}},
|
||||
metadata={'source': 'loop', 'writes': {'node_a': {'foo': 'a', 'bar': ['a']}}, 'step': 1},
|
||||
created_at='2024-08-29T19:19:38.819946+00:00',
|
||||
|
||||
@@ -10,17 +10,17 @@ search:
|
||||
LangGraph Platform is a solution for deploying agentic applications in production.
|
||||
There are three different plans for using it.
|
||||
|
||||
- **Developer**: All [LangSmith](https://smith.langchain.com/) users have access to this plan. You can sign up for this plan simply by creating a LangSmith account. This gives you access to the [Standalone Container (Lite)](./deployment_options.md) deployment option.
|
||||
- **Developer**: All [LangSmith](https://smith.langchain.com/) users have access to this plan. You can sign up for this plan simply by creating a LangSmith account. This gives you access to the [local deployment](./deployment_options.md#free-deployment) option.
|
||||
- **Plus**: All [LangSmith](https://smith.langchain.com/) users with a [Plus account](https://docs.smith.langchain.com/administration/pricing) have access to this plan. You can sign up for this plan simply by upgrading your LangSmith account to the Plus plan type. This gives you access to the [Cloud](./deployment_options.md#cloud-saas) deployment option.
|
||||
- **Enterprise**: This is separate from LangSmith plans. You can sign up for this plan by contacting sales@langchain.dev. This gives you access to all [deployment options](./deployment_options.md).
|
||||
- **Enterprise**: This is separate from LangSmith plans. You can sign up for this plan by [contacting our sales team](https://www.langchain.com/contact-sales). This gives you access to all [deployment options](./deployment_options.md).
|
||||
|
||||
|
||||
## Plan Details
|
||||
|
||||
| | Developer | Plus | Enterprise |
|
||||
|------------------------------------------------------------------|---------------------------------------------|-------------------------------------------------------|-----------------------------------------------------|
|
||||
| Deployment Options | Standalone Container (Lite) | Cloud SaaS | <ul><li>Cloud SaaS</li><li>Self-Hosted Data Plane</li><li>Self-Hosted Control Plane</li><li>Standalone Container (Enterprise)</li></ul> |
|
||||
| Usage | Free, limited to 1M [nodes executed](../concepts/faq.md#what-does-nodes-executed-mean-for-langgraph-platform-usage) per year | See [Pricing](https://www.langchain.com/langgraph-platform-pricing) | Custom |
|
||||
| Deployment Options | Local | Cloud SaaS | <ul><li>Cloud SaaS</li><li>Self-Hosted Data Plane</li><li>Self-Hosted Control Plane</li><li>Standalone Container</li></ul> |
|
||||
| Usage | Free | See [Pricing](https://www.langchain.com/langgraph-platform-pricing) | Custom |
|
||||
| APIs for retrieving and updating state and conversational history | ✅ | ✅ | ✅ |
|
||||
| APIs for retrieving and updating long-term memory | ✅ | ✅ | ✅ |
|
||||
| Horizontally scalable task queues and servers | ✅ | ✅ | ✅ |
|
||||
|
||||
@@ -9,15 +9,4 @@ The pages in this section provide end-to-end examples for the following topics:
|
||||
- [Agent Supervisor](../tutorials/multi_agent/agent_supervisor.md): Build a supervisor agent that can manage a team of agents.
|
||||
- [SQL agent](../tutorials/sql/sql-agent.md): Build a SQL agent that can execute SQL queries and return the results.
|
||||
- [Prebuilt chat UI](../agents/ui.md): Use a prebuilt chat UI to interact with any LangGraph agent.
|
||||
- [Graph runs in LangSmith](../how-tos/run-id-langsmith.md): Use LangSmith to track and analyze graph runs.
|
||||
|
||||
## LangGraph Platform
|
||||
|
||||
- [Set up custom authentication](../tutorials/auth/getting_started.md): Set up custom authentication for your LangGraph application.
|
||||
- [Make conversations private](../tutorials/auth/resource_auth.md): Make conversations private by using resource-based authentication.
|
||||
- [Connect an authentication provider](../tutorials/auth/add_auth_server.md): Connect an authentication provider to your LangGraph application.
|
||||
- [Rebuild graph at runtime](../cloud/deployment/graph_rebuild.md): Rebuild a graph at runtime.
|
||||
- [Use RemoteGraph](../how-tos/use-remote-graph.md): Use RemoteGraph to deploy your LangGraph application to a remote server.
|
||||
- [Deploy CrewAI, AutoGen, and other frameworks](../how-tos/autogen-integration.md): Deploy CrewAI, AutoGen, and other frameworks with LangGraph.
|
||||
- [Integrate LangGraph into a React app](../cloud/how-tos/use_stream_react.md)
|
||||
- [Implement Generative User Interfaces with LangGraph](../cloud/how-tos/generative_ui_react.md)
|
||||
- [Graph runs in LangSmith](../how-tos/run-id-langsmith.md): Use LangSmith to track and analyze graph runs.
|
||||
@@ -31,15 +31,3 @@ These capabilities are available in both LangGraph OSS and the LangGraph Platfor
|
||||
- [MCP](../concepts/mcp.md): Use MCP servers in a LangGraph graph.
|
||||
- [Evaluation](../agents/evals.md): Use LangSmith to evaluate your graph's performance.
|
||||
|
||||
## Platform-only capabilities
|
||||
|
||||
These capabilities are only available in [LangGraph Platform](../concepts/langgraph_platform.md).
|
||||
|
||||
- [Authentication and access control](../concepts/auth.md): Authenticate and authorize users to access a LangGraph graph.
|
||||
- [Assistants](../concepts/assistants.md): Build assistants that can be used to interact with a LangGraph graph.
|
||||
- [Double-texting](../concepts/double_texting.md): Handle double-texting (consecutive messages before a first response is returned) in a LangGraph graph.
|
||||
- [Webhooks](../cloud/concepts/webhooks.md): Send webhooks to a LangGraph graph.
|
||||
- [Cron jobs](../cloud/concepts/cron_jobs.md): Schedule jobs to run at a specific time.
|
||||
- [Server customization](../how-tos/http/custom_lifespan.md): Customize the server that runs a LangGraph graph.
|
||||
- [Data management](../cloud/concepts/data_storage_and_privacy.md): Manage data in a LangGraph graph.
|
||||
- [Deployment](../concepts/deployment_options.md): Deploy a LangGraph graph to a server.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 8.7 KiB |
@@ -11,13 +11,13 @@
|
||||
|
||||
???+ note "Support by deployment type"
|
||||
|
||||
Custom auth is supported for all deployments in the **managed LangGraph Platform**, as well as **Enterprise** self-hosted plans. It is not supported for **Lite** self-hosted plans.
|
||||
Custom auth is supported for all deployments in the **managed LangGraph Platform**, as well as **Enterprise** self-hosted plans.
|
||||
|
||||
This guide shows how to add custom authentication to your LangGraph Platform application. This guide applies to both LangGraph Platform and self-hosted deployments. It does not apply to isolated usage of the LangGraph open source library in your own custom server.
|
||||
|
||||
!!! note
|
||||
|
||||
Custom auth is supported for all **managed LangGraph Platform** deployments, as well as **Enterprise** self-hosted plans. It is not supported for **Lite** self-hosted plans.
|
||||
Custom auth is supported for all **managed LangGraph Platform** deployments, as well as **Enterprise** self-hosted plans.
|
||||
|
||||
## Add custom authentication to your deployment
|
||||
|
||||
@@ -145,7 +145,7 @@ def my_node(state, config):
|
||||
By default, if you add custom authorization on your resources, this will also apply to interactions made from the Studio. If you want, you can handle logged-in Studio users differently by checking [is_studio_user()](../../reference/functions/sdk_auth.isStudioUser.html).
|
||||
|
||||
!!! note
|
||||
`is_studio_user` was added in version 0.1.73 of the langgraph-sdk. If you're on an older version, you can still check whether `isinstance(ctx.user, StudioUser)`.
|
||||
`is_studio_user` was added in version 0.1.73 of the langgraph-sdk. If you're on an older version, you can still check whether `isinstance(ctx.user, StudioUser)`.
|
||||
|
||||
```python
|
||||
from langgraph_sdk.auth import is_studio_user, Auth
|
||||
|
||||
+1464
-41
File diff suppressed because it is too large
Load Diff
@@ -366,8 +366,8 @@ result = graph.invoke(
|
||||
|
||||
# Resume with mapping of interrupt IDs to values
|
||||
resume_map = {
|
||||
i.interrupt_id: f"human input for prompt {i.value}"
|
||||
for i in parent.get_state(thread_config).interrupts
|
||||
i.id: f"edited text for {i.value['text_to_revise']}"
|
||||
for i in graph.get_state(config).interrupts
|
||||
}
|
||||
print(graph.invoke(Command(resume=resume_map), config=config))
|
||||
# > {'text_1': 'edited text for original text 1', 'text_2': 'edited text for original text 2'}
|
||||
|
||||
@@ -1436,6 +1436,7 @@ await agent.invoke(
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.config import get_store
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.store.memory import InMemoryStore
|
||||
|
||||
@@ -2797,4 +2798,6 @@ await checkpointer.deleteThread(threadId);
|
||||
## Prebuilt memory tools
|
||||
|
||||
**LangMem** is a LangChain-maintained library that offers tools for managing long-term memories in your agent. See the [LangMem documentation](https://langchain-ai.github.io/langmem/) for usage examples.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ To set up communication between the agents in a multi-agent system you can use [
|
||||
|
||||
To implement handoffs, you can return `Command` objects from your agent nodes or tools:
|
||||
|
||||
:::python
|
||||
```python
|
||||
from typing import Annotated
|
||||
from langchain_core.tools import tool, InjectedToolCallId
|
||||
@@ -57,7 +58,7 @@ def create_handoff_tool(*, agent_name: str, description: str | None = None):
|
||||
return handoff_tool
|
||||
```
|
||||
|
||||
1. Access the [state](../concepts/low_level.md#state) of the agent that is calling the handoff tool using the @[InjectedState][InjectedState] annotation.
|
||||
1. Access the [state](../concepts/low_level.md#state) of the agent that is calling the handoff tool using the @[InjectedState] annotation.
|
||||
2. The `Command` primitive allows specifying a state update and a node transition as a single operation, making it useful for implementing handoffs.
|
||||
3. Name of the agent or node to hand off to.
|
||||
4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.
|
||||
@@ -73,25 +74,109 @@ def create_handoff_tool(*, agent_name: str, description: str | None = None):
|
||||
commands = [tools_by_name[tool_call["name"]].invoke(tool_call) for tool_call in tool_calls]
|
||||
return commands
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { Command, MessagesZodState } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
function createHandoffTool({
|
||||
agentName,
|
||||
description,
|
||||
}: {
|
||||
agentName: string;
|
||||
description?: string;
|
||||
}) {
|
||||
const name = `transfer_to_${agentName}`;
|
||||
const toolDescription = description || `Transfer to ${agentName}`;
|
||||
|
||||
return tool(
|
||||
async (_, config) => {
|
||||
// (1)!
|
||||
const state = config.state;
|
||||
const toolCallId = config.toolCall.id;
|
||||
|
||||
const toolMessage = {
|
||||
role: "tool" as const,
|
||||
content: `Successfully transferred to ${agentName}`,
|
||||
name: name,
|
||||
tool_call_id: toolCallId,
|
||||
};
|
||||
|
||||
return new Command({
|
||||
// (3)!
|
||||
goto: agentName,
|
||||
// (4)!
|
||||
update: { messages: [...state.messages, toolMessage] },
|
||||
// (5)!
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
},
|
||||
{
|
||||
name,
|
||||
description: toolDescription,
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
1. Access the [state](../concepts/low_level.md#state) of the agent that is calling the handoff tool through the `config` parameter.
|
||||
2. The `Command` primitive allows specifying a state update and a node transition as a single operation, making it useful for implementing handoffs.
|
||||
3. Name of the agent or node to hand off to.
|
||||
4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.
|
||||
5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.
|
||||
|
||||
!!! tip
|
||||
|
||||
If you want to use tools that return `Command`, you can either use prebuilt @[`create_react_agent`][create_react_agent] / @[`ToolNode`][ToolNode] components, or implement your own tool-executing node that collects `Command` objects returned by the tools and returns a list of them, e.g.:
|
||||
|
||||
```typescript
|
||||
const callTools = async (state) => {
|
||||
// ...
|
||||
const commands = await Promise.all(
|
||||
toolCalls.map(toolCall => toolsByName[toolCall.name].invoke(toolCall))
|
||||
);
|
||||
return commands;
|
||||
};
|
||||
```
|
||||
:::
|
||||
|
||||
!!! Important
|
||||
|
||||
This handoff implementation assumes that:
|
||||
|
||||
- each agent receives overall message history (across all agents) in the multi-agent system as its input. If you want more control over agent inputs, see [this section](#control-agent-inputs)
|
||||
- each agent outputs its internal messages history to the overall message history of the multi-agent system. If you want more control over **how agent outputs are added**, wrap the agent in a separate node function:
|
||||
- each agent receives overall message history (across all agents) in the multi-agent system as its input. If you want more control over agent inputs, see [this section](#control-agent-inputs)
|
||||
- each agent outputs its internal messages history to the overall message history of the multi-agent system. If you want more control over **how agent outputs are added**, wrap the agent in a separate node function:
|
||||
|
||||
```python
|
||||
def call_hotel_assistant(state):
|
||||
# return agent's final response,
|
||||
# excluding inner monologue
|
||||
response = hotel_assistant.invoke(state)
|
||||
# highlight-next-line
|
||||
return {"messages": response["messages"][-1]}
|
||||
```
|
||||
:::python
|
||||
```python
|
||||
def call_hotel_assistant(state):
|
||||
# return agent's final response,
|
||||
# excluding inner monologue
|
||||
response = hotel_assistant.invoke(state)
|
||||
# highlight-next-line
|
||||
return {"messages": response["messages"][-1]}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
const callHotelAssistant = async (state) => {
|
||||
// return agent's final response,
|
||||
// excluding inner monologue
|
||||
const response = await hotelAssistant.invoke(state);
|
||||
// highlight-next-line
|
||||
return { messages: [response.messages.at(-1)] };
|
||||
};
|
||||
```
|
||||
:::
|
||||
|
||||
### Control agent inputs
|
||||
|
||||
:::python
|
||||
You can use the @[`Send()`][Send] primitive to directly send data to the worker agents during the handoff. For example, you can request that the calling agent populate a task description for the next agent:
|
||||
|
||||
```python
|
||||
@@ -129,6 +214,63 @@ def create_task_description_handoff_tool(
|
||||
|
||||
return handoff_tool
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can use the @[`Send()`][Send] primitive to directly send data to the worker agents during the handoff. For example, you can request that the calling agent populate a task description for the next agent:
|
||||
|
||||
```typescript
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { Command, Send, MessagesZodState } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
function createTaskDescriptionHandoffTool({
|
||||
agentName,
|
||||
description,
|
||||
}: {
|
||||
agentName: string;
|
||||
description?: string;
|
||||
}) {
|
||||
const name = `transfer_to_${agentName}`;
|
||||
const toolDescription = description || `Ask ${agentName} for help.`;
|
||||
|
||||
return tool(
|
||||
async (
|
||||
{ taskDescription },
|
||||
config
|
||||
) => {
|
||||
const state = config.state;
|
||||
|
||||
const taskDescriptionMessage = {
|
||||
role: "user" as const,
|
||||
content: taskDescription,
|
||||
};
|
||||
const agentInput = {
|
||||
...state,
|
||||
messages: [taskDescriptionMessage],
|
||||
};
|
||||
|
||||
return new Command({
|
||||
// highlight-next-line
|
||||
goto: [new Send(agentName, agentInput)],
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
},
|
||||
{
|
||||
name,
|
||||
description: toolDescription,
|
||||
schema: z.object({
|
||||
taskDescription: z
|
||||
.string()
|
||||
.describe(
|
||||
"Description of what the next agent should do, including all of the relevant context."
|
||||
),
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
See the multi-agent [supervisor](../tutorials/multi_agent/agent_supervisor.md#4-create-delegation-tasks) example for a full example of using @[`Send()`][Send] in handoffs.
|
||||
|
||||
@@ -136,6 +278,7 @@ See the multi-agent [supervisor](../tutorials/multi_agent/agent_supervisor.md#4-
|
||||
|
||||
You can use handoffs in any agents built with LangGraph. We recommend using the prebuilt [agent](../agents/overview.md) or [`ToolNode`](./tool-calling.md#toolnode), as they natively support handoffs tools returning `Command`. Below is an example of how you can implement a multi-agent system for booking travel using handoffs:
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.graph import StateGraph, START, MessagesState
|
||||
@@ -176,9 +319,65 @@ multi_agent_graph = (
|
||||
.compile()
|
||||
)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { StateGraph, START, MessagesZodState } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
function createHandoffTool({
|
||||
agentName,
|
||||
description,
|
||||
}: {
|
||||
agentName: string;
|
||||
description?: string;
|
||||
}) {
|
||||
// same implementation as above
|
||||
// ...
|
||||
return new Command(/* ... */);
|
||||
}
|
||||
|
||||
// Handoffs
|
||||
const transferToHotelAssistant = createHandoffTool({
|
||||
agentName: "hotel_assistant",
|
||||
});
|
||||
const transferToFlightAssistant = createHandoffTool({
|
||||
agentName: "flight_assistant",
|
||||
});
|
||||
|
||||
// Define agents
|
||||
const flightAssistant = createReactAgent({
|
||||
llm: model,
|
||||
// highlight-next-line
|
||||
tools: [/* ... */, transferToHotelAssistant],
|
||||
// highlight-next-line
|
||||
name: "flight_assistant",
|
||||
});
|
||||
|
||||
const hotelAssistant = createReactAgent({
|
||||
llm: model,
|
||||
// highlight-next-line
|
||||
tools: [/* ... */, transferToFlightAssistant],
|
||||
// highlight-next-line
|
||||
name: "hotel_assistant",
|
||||
});
|
||||
|
||||
// Define multi-agent graph
|
||||
const multiAgentGraph = new StateGraph(MessagesZodState)
|
||||
// highlight-next-line
|
||||
.addNode("flight_assistant", flightAssistant)
|
||||
// highlight-next-line
|
||||
.addNode("hotel_assistant", hotelAssistant)
|
||||
.addEdge(START, "flight_assistant")
|
||||
.compile();
|
||||
```
|
||||
:::
|
||||
|
||||
??? example "Full example: Multi-agent system for booking travel"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from typing import Annotated
|
||||
from langchain_core.messages import convert_to_messages
|
||||
@@ -323,6 +522,183 @@ multi_agent_graph = (
|
||||
3. Name of the agent or node to hand off to.
|
||||
4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.
|
||||
5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { StateGraph, START, MessagesZodState, Command } from "@langchain/langgraph";
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { isBaseMessage } from "@langchain/core/messages";
|
||||
import { z } from "zod";
|
||||
|
||||
// We'll use a helper to render the streamed agent outputs nicely
|
||||
const prettyPrintMessages = (update: Record<string, any>) => {
|
||||
// Handle tuple case with namespace
|
||||
if (Array.isArray(update)) {
|
||||
const [ns, updateData] = update;
|
||||
// Skip parent graph updates in the printouts
|
||||
if (ns.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const graphId = ns[ns.length - 1].split(":")[0];
|
||||
console.log(`Update from subgraph ${graphId}:\n`);
|
||||
update = updateData;
|
||||
}
|
||||
|
||||
for (const [nodeName, updateValue] of Object.entries(update)) {
|
||||
console.log(`Update from node ${nodeName}:\n`);
|
||||
|
||||
const messages = updateValue.messages || [];
|
||||
for (const message of messages) {
|
||||
if (isBaseMessage(message)) {
|
||||
const textContent =
|
||||
typeof message.content === "string"
|
||||
? message.content
|
||||
: JSON.stringify(message.content);
|
||||
console.log(`${message.getType()}: ${textContent}`);
|
||||
}
|
||||
}
|
||||
console.log("\n");
|
||||
}
|
||||
};
|
||||
|
||||
function createHandoffTool({
|
||||
agentName,
|
||||
description,
|
||||
}: {
|
||||
agentName: string;
|
||||
description?: string;
|
||||
}) {
|
||||
const name = `transfer_to_${agentName}`;
|
||||
const toolDescription = description || `Transfer to ${agentName}`;
|
||||
|
||||
return tool(
|
||||
async (_, config) => {
|
||||
// highlight-next-line
|
||||
const state = config.state; // (1)!
|
||||
const toolCallId = config.toolCall.id;
|
||||
|
||||
const toolMessage = {
|
||||
role: "tool" as const,
|
||||
content: `Successfully transferred to ${agentName}`,
|
||||
name: name,
|
||||
tool_call_id: toolCallId,
|
||||
};
|
||||
|
||||
return new Command({
|
||||
// highlight-next-line
|
||||
goto: agentName, // (3)!
|
||||
// highlight-next-line
|
||||
update: { messages: [...state.messages, toolMessage] }, // (4)!
|
||||
// highlight-next-line
|
||||
graph: Command.PARENT, // (5)!
|
||||
});
|
||||
},
|
||||
{
|
||||
name,
|
||||
description: toolDescription,
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Handoffs
|
||||
const transferToHotelAssistant = createHandoffTool({
|
||||
agentName: "hotel_assistant",
|
||||
description: "Transfer user to the hotel-booking assistant.",
|
||||
});
|
||||
|
||||
const transferToFlightAssistant = createHandoffTool({
|
||||
agentName: "flight_assistant",
|
||||
description: "Transfer user to the flight-booking assistant.",
|
||||
});
|
||||
|
||||
// Simple agent tools
|
||||
const bookHotel = tool(
|
||||
async ({ hotelName }) => {
|
||||
return `Successfully booked a stay at ${hotelName}.`;
|
||||
},
|
||||
{
|
||||
name: "book_hotel",
|
||||
description: "Book a hotel",
|
||||
schema: z.object({
|
||||
hotelName: z.string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const bookFlight = tool(
|
||||
async ({ fromAirport, toAirport }) => {
|
||||
return `Successfully booked a flight from ${fromAirport} to ${toAirport}.`;
|
||||
},
|
||||
{
|
||||
name: "book_flight",
|
||||
description: "Book a flight",
|
||||
schema: z.object({
|
||||
fromAirport: z.string(),
|
||||
toAirport: z.string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const model = new ChatAnthropic({
|
||||
model: "claude-3-5-sonnet-latest",
|
||||
});
|
||||
|
||||
// Define agents
|
||||
const flightAssistant = createReactAgent({
|
||||
llm: model,
|
||||
// highlight-next-line
|
||||
tools: [bookFlight, transferToHotelAssistant],
|
||||
prompt: "You are a flight booking assistant",
|
||||
// highlight-next-line
|
||||
name: "flight_assistant",
|
||||
});
|
||||
|
||||
const hotelAssistant = createReactAgent({
|
||||
llm: model,
|
||||
// highlight-next-line
|
||||
tools: [bookHotel, transferToFlightAssistant],
|
||||
prompt: "You are a hotel booking assistant",
|
||||
// highlight-next-line
|
||||
name: "hotel_assistant",
|
||||
});
|
||||
|
||||
// Define multi-agent graph
|
||||
const multiAgentGraph = new StateGraph(MessagesZodState)
|
||||
.addNode("flight_assistant", flightAssistant)
|
||||
.addNode("hotel_assistant", hotelAssistant)
|
||||
.addEdge(START, "flight_assistant")
|
||||
.compile();
|
||||
|
||||
// Run the multi-agent graph
|
||||
const stream = await multiAgentGraph.stream(
|
||||
{
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "book a flight from BOS to JFK and a stay at McKittrick Hotel",
|
||||
},
|
||||
],
|
||||
},
|
||||
// highlight-next-line
|
||||
{ subgraphs: true }
|
||||
);
|
||||
|
||||
for await (const chunk of stream) {
|
||||
prettyPrintMessages(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
1. Access agent's state
|
||||
2. The `Command` primitive allows specifying a state update and a node transition as a single operation, making it useful for implementing handoffs.
|
||||
3. Name of the agent or node to hand off to.
|
||||
4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.
|
||||
5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.
|
||||
:::
|
||||
|
||||
## Multi-turn conversation
|
||||
|
||||
@@ -333,6 +709,7 @@ The agents can then be implemented as nodes in a graph that executes agent steps
|
||||
1. **Wait for user input** to continue the conversation, or
|
||||
2. **Route to another agent** (or back to itself, such as in a loop) via a [handoff](#handoffs)
|
||||
|
||||
:::python
|
||||
```python
|
||||
def human(state) -> Command[Literal["agent", "another_agent"]]:
|
||||
"""A node for collecting user input."""
|
||||
@@ -360,6 +737,44 @@ def agent(state) -> Command[Literal["agent", "another_agent", "human"]]:
|
||||
else:
|
||||
return Command(goto="human") # Go to human node
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { interrupt, Command } from "@langchain/langgraph";
|
||||
|
||||
function human(state: MessagesState): Command {
|
||||
const userInput: string = interrupt("Ready for user input.");
|
||||
|
||||
// Determine the active agent
|
||||
const activeAgent = /* ... */;
|
||||
|
||||
return new Command({
|
||||
update: {
|
||||
messages: [{
|
||||
role: "human",
|
||||
content: userInput,
|
||||
}]
|
||||
},
|
||||
goto: activeAgent,
|
||||
});
|
||||
}
|
||||
|
||||
function agent(state: MessagesState): Command {
|
||||
// The condition for routing/halting can be anything, e.g. LLM tool call / structured output, etc.
|
||||
const goto = getNextAgent(/* ... */); // 'agent' / 'anotherAgent'
|
||||
|
||||
if (goto) {
|
||||
return new Command({
|
||||
goto,
|
||||
update: { myStateKey: "myStateValue" }
|
||||
});
|
||||
}
|
||||
|
||||
return new Command({ goto: "human" });
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
??? example "Full example: multi-agent system for travel recommendations"
|
||||
|
||||
@@ -370,6 +785,7 @@ def agent(state) -> Command[Literal["agent", "another_agent", "human"]]:
|
||||
* travel_advisor: can help with travel destination recommendations. Can ask hotel_advisor for help.
|
||||
* hotel_advisor: can help with hotel recommendations. Can ask travel_advisor for help.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langgraph.graph import MessagesState, StateGraph, START
|
||||
@@ -571,10 +987,267 @@ def agent(state) -> Command[Literal["agent", "another_agent", "human"]]:
|
||||
|
||||
Would you like more specific information about any of these activities or would you like to know about other options in the area?
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { StateGraph, START, MessagesZodState, Command, interrupt, MemorySaver } from "@langchain/langgraph";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatAnthropic({ model: "claude-3-5-sonnet-latest" });
|
||||
|
||||
const MultiAgentState = MessagesZodState.extend({
|
||||
lastActiveAgent: z.string().optional(),
|
||||
});
|
||||
|
||||
// Define travel advisor tools
|
||||
const getTravelRecommendations = tool(
|
||||
async () => {
|
||||
// Placeholder implementation
|
||||
return "Based on current trends, I recommend visiting Japan, Portugal, or New Zealand.";
|
||||
},
|
||||
{
|
||||
name: "get_travel_recommendations",
|
||||
description: "Get current travel destination recommendations",
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
|
||||
const makeHandoffTool = (agentName: string) => {
|
||||
return tool(
|
||||
async (_, config) => {
|
||||
const state = config.state;
|
||||
const toolCallId = config.toolCall.id;
|
||||
|
||||
const toolMessage = {
|
||||
role: "tool" as const,
|
||||
content: `Successfully transferred to ${agentName}`,
|
||||
name: `transfer_to_${agentName}`,
|
||||
tool_call_id: toolCallId,
|
||||
};
|
||||
|
||||
return new Command({
|
||||
goto: agentName,
|
||||
update: { messages: [...state.messages, toolMessage] },
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
},
|
||||
{
|
||||
name: `transfer_to_${agentName}`,
|
||||
description: `Transfer to ${agentName}`,
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const travelAdvisorTools = [
|
||||
getTravelRecommendations,
|
||||
makeHandoffTool("hotel_advisor"),
|
||||
];
|
||||
|
||||
const travelAdvisor = createReactAgent({
|
||||
llm: model,
|
||||
tools: travelAdvisorTools,
|
||||
prompt: [
|
||||
"You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). ",
|
||||
"If you need hotel recommendations, ask 'hotel_advisor' for help. ",
|
||||
"You MUST include human-readable response before transferring to another agent."
|
||||
].join("")
|
||||
});
|
||||
|
||||
const callTravelAdvisor = async (
|
||||
state: z.infer<typeof MultiAgentState>
|
||||
): Promise<Command> => {
|
||||
const response = await travelAdvisor.invoke(state);
|
||||
const update = { ...response, lastActiveAgent: "travel_advisor" };
|
||||
return new Command({ update, goto: "human" });
|
||||
};
|
||||
|
||||
// Define hotel advisor tools
|
||||
const getHotelRecommendations = tool(
|
||||
async () => {
|
||||
// Placeholder implementation
|
||||
return "I recommend the Ritz-Carlton for luxury stays or boutique hotels for unique experiences.";
|
||||
},
|
||||
{
|
||||
name: "get_hotel_recommendations",
|
||||
description: "Get hotel recommendations for destinations",
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
|
||||
const hotelAdvisorTools = [
|
||||
getHotelRecommendations,
|
||||
makeHandoffTool("travel_advisor"),
|
||||
];
|
||||
|
||||
const hotelAdvisor = createReactAgent({
|
||||
llm: model,
|
||||
tools: hotelAdvisorTools,
|
||||
prompt: [
|
||||
"You are a hotel expert that can provide hotel recommendations for a given destination. ",
|
||||
"If you need help picking travel destinations, ask 'travel_advisor' for help.",
|
||||
"You MUST include human-readable response before transferring to another agent."
|
||||
].join("")
|
||||
});
|
||||
|
||||
const callHotelAdvisor = async (
|
||||
state: z.infer<typeof MultiAgentState>
|
||||
): Promise<Command> => {
|
||||
const response = await hotelAdvisor.invoke(state);
|
||||
const update = { ...response, lastActiveAgent: "hotel_advisor" };
|
||||
return new Command({ update, goto: "human" });
|
||||
};
|
||||
|
||||
const humanNode = async (
|
||||
state: z.infer<typeof MultiAgentState>
|
||||
): Promise<Command> => {
|
||||
const userInput: string = interrupt("Ready for user input.");
|
||||
const activeAgent = state.lastActiveAgent || "travel_advisor";
|
||||
|
||||
return new Command({
|
||||
update: {
|
||||
messages: [
|
||||
{
|
||||
role: "human",
|
||||
content: userInput,
|
||||
}
|
||||
]
|
||||
},
|
||||
goto: activeAgent,
|
||||
});
|
||||
};
|
||||
|
||||
const builder = new StateGraph(MultiAgentState)
|
||||
.addNode("travel_advisor", callTravelAdvisor)
|
||||
.addNode("hotel_advisor", callHotelAdvisor)
|
||||
.addNode("human", humanNode)
|
||||
.addEdge(START, "travel_advisor");
|
||||
|
||||
const checkpointer = new MemorySaver();
|
||||
const graph = builder.compile({ checkpointer });
|
||||
```
|
||||
|
||||
Let's test a multi turn conversation with this application.
|
||||
|
||||
```typescript
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
const threadConfig = { configurable: { thread_id: uuidv4() } };
|
||||
|
||||
const inputs = [
|
||||
// 1st round of conversation
|
||||
{
|
||||
messages: [
|
||||
{ role: "user", content: "i wanna go somewhere warm in the caribbean" }
|
||||
]
|
||||
},
|
||||
// Since we're using `interrupt`, we'll need to resume using the Command primitive.
|
||||
// 2nd round of conversation
|
||||
new Command({
|
||||
resume: "could you recommend a nice hotel in one of the areas and tell me which area it is."
|
||||
}),
|
||||
// 3rd round of conversation
|
||||
new Command({
|
||||
resume: "i like the first one. could you recommend something to do near the hotel?"
|
||||
}),
|
||||
];
|
||||
|
||||
for (const [idx, userInput] of inputs.entries()) {
|
||||
console.log();
|
||||
console.log(`--- Conversation Turn ${idx + 1} ---`);
|
||||
console.log();
|
||||
console.log(`User: ${JSON.stringify(userInput)}`);
|
||||
console.log();
|
||||
|
||||
for await (const update of await graph.stream(
|
||||
userInput,
|
||||
{ ...threadConfig, streamMode: "updates" }
|
||||
)) {
|
||||
for (const [nodeId, value] of Object.entries(update)) {
|
||||
if (value?.messages?.length) {
|
||||
const lastMessage = value.messages.at(-1);
|
||||
if (lastMessage?.getType?.() === "ai") {
|
||||
console.log(`${nodeId}: ${lastMessage.content}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
--- Conversation Turn 1 ---
|
||||
|
||||
User: {"messages":[{"role":"user","content":"i wanna go somewhere warm in the caribbean"}]}
|
||||
|
||||
travel_advisor: Based on the recommendations, Aruba would be an excellent choice for your Caribbean getaway! Aruba is known as "One Happy Island" and offers:
|
||||
- Year-round warm weather with consistent temperatures around 82°F (28°C)
|
||||
- Beautiful white sand beaches like Eagle Beach and Palm Beach
|
||||
- Clear turquoise waters perfect for swimming and snorkeling
|
||||
- Minimal rainfall and location outside the hurricane belt
|
||||
- A blend of Caribbean and Dutch culture
|
||||
- Great dining options and nightlife
|
||||
- Various water sports and activities
|
||||
|
||||
Would you like me to get some specific hotel recommendations in Aruba for your stay? I can transfer you to our hotel advisor who can help with accommodations.
|
||||
|
||||
--- Conversation Turn 2 ---
|
||||
|
||||
User: Command { resume: 'could you recommend a nice hotel in one of the areas and tell me which area it is.' }
|
||||
|
||||
hotel_advisor: Based on the recommendations, I can suggest two excellent options:
|
||||
|
||||
1. The Ritz-Carlton, Aruba - Located in Palm Beach
|
||||
- This luxury resort is situated in the vibrant Palm Beach area
|
||||
- Known for its exceptional service and amenities
|
||||
- Perfect if you want to be close to dining, shopping, and entertainment
|
||||
- Features multiple restaurants, a casino, and a world-class spa
|
||||
- Located on a pristine stretch of Palm Beach
|
||||
|
||||
2. Bucuti & Tara Beach Resort - Located in Eagle Beach
|
||||
- An adults-only boutique resort on Eagle Beach
|
||||
- Known for being more intimate and peaceful
|
||||
- Award-winning for its sustainability practices
|
||||
- Perfect for a romantic getaway or peaceful vacation
|
||||
- Located on one of the most beautiful beaches in the Caribbean
|
||||
|
||||
Would you like more specific information about either of these properties or their locations?
|
||||
|
||||
--- Conversation Turn 3 ---
|
||||
|
||||
User: Command { resume: 'i like the first one. could you recommend something to do near the hotel?' }
|
||||
|
||||
travel_advisor: Near the Ritz-Carlton in Palm Beach, here are some highly recommended activities:
|
||||
|
||||
1. Visit the Palm Beach Plaza Mall - Just a short walk from the hotel, featuring shopping, dining, and entertainment
|
||||
2. Try your luck at the Stellaris Casino - It's right in the Ritz-Carlton
|
||||
3. Take a sunset sailing cruise - Many depart from the nearby pier
|
||||
4. Visit the California Lighthouse - A scenic landmark just north of Palm Beach
|
||||
5. Enjoy water sports at Palm Beach:
|
||||
- Jet skiing
|
||||
- Parasailing
|
||||
- Snorkeling
|
||||
- Stand-up paddleboarding
|
||||
|
||||
Would you like more specific information about any of these activities or would you like to know about other options in the area?
|
||||
```
|
||||
:::
|
||||
|
||||
## Prebuilt implementations
|
||||
|
||||
LangGraph comes with prebuilt implementations of two of the most popular multi-agent architectures:
|
||||
|
||||
:::python
|
||||
- [supervisor](../agents/multi-agent.md#supervisor) — individual agents are coordinated by a central supervisor agent. The supervisor controls all communication flow and task delegation, making decisions about which agent to invoke based on the current context and task requirements. You can use [`langgraph-supervisor`](https://github.com/langchain-ai/langgraph-supervisor-py) library to create a supervisor multi-agent systems.
|
||||
- [swarm](../agents/multi-agent.md#supervisor) — agents dynamically hand off control to one another based on their specializations. The system remembers which agent was last active, ensuring that on subsequent interactions, the conversation resumes with that agent. You can use [`langgraph-swarm`](https://github.com/langchain-ai/langgraph-swarm-py) library to create a swarm multi-agent systems.
|
||||
- [swarm](../agents/multi-agent.md#supervisor) — agents dynamically hand off control to one another based on their specializations. The system remembers which agent was last active, ensuring that on subsequent interactions, the conversation resumes with that agent. You can use [`langgraph-swarm`](https://github.com/langchain-ai/langgraph-swarm-py) library to create a swarm multi-agent systems.
|
||||
:::
|
||||
|
||||
:::js
|
||||
- [supervisor](../agents/multi-agent.md#supervisor) — individual agents are coordinated by a central supervisor agent. The supervisor controls all communication flow and task delegation, making decisions about which agent to invoke based on the current context and task requirements. You can use [`langgraph-supervisor`](https://github.com/langchain-ai/langgraph-supervisor-js) library to create a supervisor multi-agent systems.
|
||||
- [swarm](../agents/multi-agent.md#supervisor) — agents dynamically hand off control to one another based on their specializations. The system remembers which agent was last active, ensuring that on subsequent interactions, the conversation resumes with that agent. You can use [`langgraph-swarm`](https://github.com/langchain-ai/langgraph-swarm-js) library to create a swarm multi-agent systems.
|
||||
:::
|
||||
@@ -9,11 +9,20 @@ When adding subgraphs, you need to define how the parent graph and the subgraph
|
||||
|
||||
## Setup
|
||||
|
||||
:::python
|
||||
```bash
|
||||
pip install -U langgraph
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```bash
|
||||
npm install @langchain/langgraph
|
||||
```
|
||||
:::
|
||||
|
||||
!!! tip "Set up LangSmith for LangGraph development"
|
||||
|
||||
Sign up for [LangSmith](https://smith.langchain.com) to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com).
|
||||
|
||||
## Shared state schemas
|
||||
@@ -22,6 +31,7 @@ A common case is for the parent graph and subgraph to communicate over a shared
|
||||
|
||||
If your subgraph shares state keys with the parent graph, you can follow these steps to add it to your graph:
|
||||
|
||||
:::python
|
||||
1. Define the subgraph workflow (`subgraph_builder` in the example below) and compile it
|
||||
2. Pass compiled subgraph to the `.add_node` method when defining the parent graph workflow
|
||||
|
||||
@@ -49,9 +59,41 @@ builder.add_node("node_1", subgraph)
|
||||
builder.add_edge(START, "node_1")
|
||||
graph = builder.compile()
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
1. Define the subgraph workflow (`subgraphBuilder` in the example below) and compile it
|
||||
2. Pass compiled subgraph to the `.addNode` method when defining the parent graph workflow
|
||||
|
||||
```typescript
|
||||
import { StateGraph, START } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({
|
||||
foo: z.string(),
|
||||
});
|
||||
|
||||
// Subgraph
|
||||
const subgraphBuilder = new StateGraph(State)
|
||||
.addNode("subgraphNode1", (state) => {
|
||||
return { foo: "hi! " + state.foo };
|
||||
})
|
||||
.addEdge(START, "subgraphNode1");
|
||||
|
||||
const subgraph = subgraphBuilder.compile();
|
||||
|
||||
// Parent graph
|
||||
const builder = new StateGraph(State)
|
||||
.addNode("node1", subgraph)
|
||||
.addEdge(START, "node1");
|
||||
|
||||
const graph = builder.compile();
|
||||
```
|
||||
:::
|
||||
|
||||
??? example "Full example: shared state schemas"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
from langgraph.graph.state import StateGraph, START
|
||||
@@ -101,6 +143,61 @@ graph = builder.compile()
|
||||
{'node_1': {'foo': 'hi! foo'}}
|
||||
{'node_2': {'foo': 'hi! foobar'}}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { StateGraph, START } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
// Define subgraph
|
||||
const SubgraphState = z.object({
|
||||
foo: z.string(), // (1)!
|
||||
bar: z.string(), // (2)!
|
||||
});
|
||||
|
||||
const subgraphBuilder = new StateGraph(SubgraphState)
|
||||
.addNode("subgraphNode1", (state) => {
|
||||
return { bar: "bar" };
|
||||
})
|
||||
.addNode("subgraphNode2", (state) => {
|
||||
// note that this node is using a state key ('bar') that is only available in the subgraph
|
||||
// and is sending update on the shared state key ('foo')
|
||||
return { foo: state.foo + state.bar };
|
||||
})
|
||||
.addEdge(START, "subgraphNode1")
|
||||
.addEdge("subgraphNode1", "subgraphNode2");
|
||||
|
||||
const subgraph = subgraphBuilder.compile();
|
||||
|
||||
// Define parent graph
|
||||
const ParentState = z.object({
|
||||
foo: z.string(),
|
||||
});
|
||||
|
||||
const builder = new StateGraph(ParentState)
|
||||
.addNode("node1", (state) => {
|
||||
return { foo: "hi! " + state.foo };
|
||||
})
|
||||
.addNode("node2", subgraph)
|
||||
.addEdge(START, "node1")
|
||||
.addEdge("node1", "node2");
|
||||
|
||||
const graph = builder.compile();
|
||||
|
||||
for await (const chunk of await graph.stream({ foo: "foo" })) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
3. This key is shared with the parent graph state
|
||||
4. This key is private to the `SubgraphState` and is not visible to the parent graph
|
||||
|
||||
```
|
||||
{ node1: { foo: 'hi! foo' } }
|
||||
{ node2: { foo: 'hi! foobar' } }
|
||||
```
|
||||
:::
|
||||
|
||||
## Different state schemas
|
||||
|
||||
@@ -108,6 +205,7 @@ For more complex systems you might want to define subgraphs that have a **comple
|
||||
|
||||
If that's the case for your application, you need to define a node **function that invokes the subgraph**. This function needs to transform the input (parent) state to the subgraph state before invoking the subgraph, and transform the results back to the parent state before returning the state update from the node.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
from langgraph.graph.state import StateGraph, START
|
||||
@@ -142,9 +240,48 @@ graph = builder.compile()
|
||||
|
||||
1. Transform the state to the subgraph state
|
||||
2. Transform response back to the parent state
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { StateGraph, START } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const SubgraphState = z.object({
|
||||
bar: z.string(),
|
||||
});
|
||||
|
||||
// Subgraph
|
||||
const subgraphBuilder = new StateGraph(SubgraphState)
|
||||
.addNode("subgraphNode1", (state) => {
|
||||
return { bar: "hi! " + state.bar };
|
||||
})
|
||||
.addEdge(START, "subgraphNode1");
|
||||
|
||||
const subgraph = subgraphBuilder.compile();
|
||||
|
||||
// Parent graph
|
||||
const State = z.object({
|
||||
foo: z.string(),
|
||||
});
|
||||
|
||||
const builder = new StateGraph(State)
|
||||
.addNode("node1", async (state) => {
|
||||
const subgraphOutput = await subgraph.invoke({ bar: state.foo }); // (1)!
|
||||
return { foo: subgraphOutput.bar }; // (2)!
|
||||
})
|
||||
.addEdge(START, "node1");
|
||||
|
||||
const graph = builder.compile();
|
||||
```
|
||||
|
||||
1. Transform the state to the subgraph state
|
||||
2. Transform response back to the parent state
|
||||
:::
|
||||
|
||||
??? example "Full example: different state schemas"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
from langgraph.graph.state import StateGraph, START
|
||||
@@ -200,11 +337,74 @@ graph = builder.compile()
|
||||
(('node_2:9c36dd0f-151a-cb42-cbad-fa2f851f9ab7',), {'grandchild_2': {'bar': 'hi! foobaz'}})
|
||||
((), {'node_2': {'foo': 'hi! foobaz'}})
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { StateGraph, START } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
// Define subgraph
|
||||
const SubgraphState = z.object({
|
||||
// note that none of these keys are shared with the parent graph state
|
||||
bar: z.string(),
|
||||
baz: z.string(),
|
||||
});
|
||||
|
||||
const subgraphBuilder = new StateGraph(SubgraphState)
|
||||
.addNode("subgraphNode1", (state) => {
|
||||
return { baz: "baz" };
|
||||
})
|
||||
.addNode("subgraphNode2", (state) => {
|
||||
return { bar: state.bar + state.baz };
|
||||
})
|
||||
.addEdge(START, "subgraphNode1")
|
||||
.addEdge("subgraphNode1", "subgraphNode2");
|
||||
|
||||
const subgraph = subgraphBuilder.compile();
|
||||
|
||||
// Define parent graph
|
||||
const ParentState = z.object({
|
||||
foo: z.string(),
|
||||
});
|
||||
|
||||
const builder = new StateGraph(ParentState)
|
||||
.addNode("node1", (state) => {
|
||||
return { foo: "hi! " + state.foo };
|
||||
})
|
||||
.addNode("node2", async (state) => {
|
||||
const response = await subgraph.invoke({ bar: state.foo }); // (1)!
|
||||
return { foo: response.bar }; // (2)!
|
||||
})
|
||||
.addEdge(START, "node1")
|
||||
.addEdge("node1", "node2");
|
||||
|
||||
const graph = builder.compile();
|
||||
|
||||
for await (const chunk of await graph.stream(
|
||||
{ foo: "foo" },
|
||||
{ subgraphs: true }
|
||||
)) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
3. Transform the state to the subgraph state
|
||||
4. Transform response back to the parent state
|
||||
|
||||
```
|
||||
[[], { node1: { foo: 'hi! foo' } }]
|
||||
[['node2:9c36dd0f-151a-cb42-cbad-fa2f851f9ab7'], { subgraphNode1: { baz: 'baz' } }]
|
||||
[['node2:9c36dd0f-151a-cb42-cbad-fa2f851f9ab7'], { subgraphNode2: { bar: 'hi! foobaz' } }]
|
||||
[[], { node2: { foo: 'hi! foobaz' } }]
|
||||
```
|
||||
:::
|
||||
|
||||
??? example "Full example: different state schemas (two levels of subgraphs)"
|
||||
|
||||
This is an example with two levels of subgraphs: parent -> child -> grandchild.
|
||||
|
||||
:::python
|
||||
```python
|
||||
# Grandchild graph
|
||||
from typing_extensions import TypedDict
|
||||
@@ -288,14 +488,102 @@ graph = builder.compile()
|
||||
((), {'child': {'my_key': 'hi Bob, how are you today?'}})
|
||||
((), {'parent_2': {'my_key': 'hi Bob, how are you today? bye!'}})
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { StateGraph, START, END } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
// Grandchild graph
|
||||
const GrandChildState = z.object({
|
||||
myGrandchildKey: z.string(),
|
||||
});
|
||||
|
||||
const grandchild = new StateGraph(GrandChildState)
|
||||
.addNode("grandchild1", (state) => {
|
||||
// NOTE: child or parent keys will not be accessible here
|
||||
return { myGrandchildKey: state.myGrandchildKey + ", how are you" };
|
||||
})
|
||||
.addEdge(START, "grandchild1")
|
||||
.addEdge("grandchild1", END);
|
||||
|
||||
const grandchildGraph = grandchild.compile();
|
||||
|
||||
// Child graph
|
||||
const ChildState = z.object({
|
||||
myChildKey: z.string(),
|
||||
});
|
||||
|
||||
const child = new StateGraph(ChildState)
|
||||
.addNode("child1", async (state) => {
|
||||
// NOTE: parent or grandchild keys won't be accessible here
|
||||
const grandchildGraphInput = { myGrandchildKey: state.myChildKey }; // (1)!
|
||||
const grandchildGraphOutput = await grandchildGraph.invoke(grandchildGraphInput);
|
||||
return { myChildKey: grandchildGraphOutput.myGrandchildKey + " today?" }; // (2)!
|
||||
}) // (3)!
|
||||
.addEdge(START, "child1")
|
||||
.addEdge("child1", END);
|
||||
|
||||
const childGraph = child.compile();
|
||||
|
||||
// Parent graph
|
||||
const ParentState = z.object({
|
||||
myKey: z.string(),
|
||||
});
|
||||
|
||||
const parent = new StateGraph(ParentState)
|
||||
.addNode("parent1", (state) => {
|
||||
// NOTE: child or grandchild keys won't be accessible here
|
||||
return { myKey: "hi " + state.myKey };
|
||||
})
|
||||
.addNode("child", async (state) => {
|
||||
const childGraphInput = { myChildKey: state.myKey }; // (4)!
|
||||
const childGraphOutput = await childGraph.invoke(childGraphInput);
|
||||
return { myKey: childGraphOutput.myChildKey }; // (5)!
|
||||
}) // (6)!
|
||||
.addNode("parent2", (state) => {
|
||||
return { myKey: state.myKey + " bye!" };
|
||||
})
|
||||
.addEdge(START, "parent1")
|
||||
.addEdge("parent1", "child")
|
||||
.addEdge("child", "parent2")
|
||||
.addEdge("parent2", END);
|
||||
|
||||
const parentGraph = parent.compile();
|
||||
|
||||
for await (const chunk of await parentGraph.stream(
|
||||
{ myKey: "Bob" },
|
||||
{ subgraphs: true }
|
||||
)) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
7. We're transforming the state from the child state channels (`myChildKey`) to the grandchild state channels (`myGrandchildKey`)
|
||||
8. We're transforming the state from the grandchild state channels (`myGrandchildKey`) back to the child state channels (`myChildKey`)
|
||||
9. We're passing a function here instead of just compiled graph (`grandchildGraph`)
|
||||
10. We're transforming the state from the parent state channels (`myKey`) to the child state channels (`myChildKey`)
|
||||
11. We're transforming the state from the child state channels (`myChildKey`) back to the parent state channels (`myKey`)
|
||||
12. We're passing a function here instead of just a compiled graph (`childGraph`)
|
||||
|
||||
```
|
||||
[[], { parent1: { myKey: 'hi Bob' } }]
|
||||
[['child:2e26e9ce-602f-862c-aa66-1ea5a4655e3b', 'child1:781bb3b1-3971-84ce-810b-acf819a03f9c'], { grandchild1: { myGrandchildKey: 'hi Bob, how are you' } }]
|
||||
[['child:2e26e9ce-602f-862c-aa66-1ea5a4655e3b'], { child1: { myChildKey: 'hi Bob, how are you today?' } }]
|
||||
[[], { child: { myKey: 'hi Bob, how are you today?' } }]
|
||||
[[], { parent2: { myKey: 'hi Bob, how are you today? bye!' } }]
|
||||
```
|
||||
:::
|
||||
|
||||
## Add persistence
|
||||
|
||||
You only need to **provide the checkpointer when compiling the parent graph**. LangGraph will automatically propagate the checkpointer to the child subgraphs.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.graph import START, StateGraph
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
class State(TypedDict):
|
||||
@@ -317,20 +605,66 @@ builder = StateGraph(State)
|
||||
builder.add_node("node_1", subgraph)
|
||||
builder.add_edge(START, "node_1")
|
||||
|
||||
checkpointer = InMemorySaver()
|
||||
checkpointer = MemorySaver()
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
```
|
||||
:::
|
||||
|
||||
If you want the subgraph to **have its own memory**, you can compile it `with checkpointer=True`. This is useful in [multi-agent](../concepts/multi_agent.md) systems, if you want agents to keep track of their internal message histories:
|
||||
:::js
|
||||
```typescript
|
||||
import { StateGraph, START, MemorySaver } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({
|
||||
foo: z.string(),
|
||||
});
|
||||
|
||||
// Subgraph
|
||||
const subgraphBuilder = new StateGraph(State)
|
||||
.addNode("subgraphNode1", (state) => {
|
||||
return { foo: state.foo + "bar" };
|
||||
})
|
||||
.addEdge(START, "subgraphNode1");
|
||||
|
||||
const subgraph = subgraphBuilder.compile();
|
||||
|
||||
// Parent graph
|
||||
const builder = new StateGraph(State)
|
||||
.addNode("node1", subgraph)
|
||||
.addEdge(START, "node1");
|
||||
|
||||
const checkpointer = new MemorySaver();
|
||||
const graph = builder.compile({ checkpointer });
|
||||
```
|
||||
:::
|
||||
|
||||
If you want the subgraph to **have its own memory**, you can compile it with the appropriate checkpointer option. This is useful in [multi-agent](../concepts/multi_agent.md) systems, if you want agents to keep track of their internal message histories:
|
||||
|
||||
:::python
|
||||
```python
|
||||
subgraph_builder = StateGraph(...)
|
||||
subgraph = subgraph_builder.compile(checkpointer=True)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
const subgraphBuilder = new StateGraph(...)
|
||||
const subgraph = subgraphBuilder.compile({ checkpointer: true });
|
||||
```
|
||||
:::
|
||||
|
||||
## View subgraph state
|
||||
|
||||
When you enable [persistence](../concepts/persistence.md), you can [inspect the graph state](../concepts/persistence.md#checkpoints) (checkpoint) via `graph.get_state(config)`. To view the subgraph state, you can use `graph.get_state(config, subgraphs=True)`.
|
||||
When you enable [persistence](../concepts/persistence.md), you can [inspect the graph state](../concepts/persistence.md#checkpoints) (checkpoint) via the appropriate method. To view the subgraph state, you can use the subgraphs option.
|
||||
|
||||
:::python
|
||||
You can inspect the graph state via `graph.get_state(config)`. To view the subgraph state, you can use `graph.get_state(config, subgraphs=True)`.
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can inspect the graph state via `graph.getState(config)`. To view the subgraph state, you can use `graph.getState(config, { subgraphs: true })`.
|
||||
:::
|
||||
|
||||
!!! important "Available **only** when interrupted"
|
||||
|
||||
@@ -338,9 +672,10 @@ When you enable [persistence](../concepts/persistence.md), you can [inspect the
|
||||
|
||||
??? example "View interrupted subgraph state"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.graph import START, StateGraph
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.types import interrupt, Command
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
@@ -365,7 +700,7 @@ When you enable [persistence](../concepts/persistence.md), you can [inspect the
|
||||
builder.add_node("node_1", subgraph)
|
||||
builder.add_edge(START, "node_1")
|
||||
|
||||
checkpointer = InMemorySaver()
|
||||
checkpointer = MemorySaver()
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
|
||||
config = {"configurable": {"thread_id": "1"}}
|
||||
@@ -379,11 +714,53 @@ When you enable [persistence](../concepts/persistence.md), you can [inspect the
|
||||
```
|
||||
|
||||
1. This will be available only when the subgraph is interrupted. Once you resume the graph, you won't be able to access the subgraph state.
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { StateGraph, START, MemorySaver, interrupt, Command } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({
|
||||
foo: z.string(),
|
||||
});
|
||||
|
||||
// Subgraph
|
||||
const subgraphBuilder = new StateGraph(State)
|
||||
.addNode("subgraphNode1", (state) => {
|
||||
const value = interrupt("Provide value:");
|
||||
return { foo: state.foo + value };
|
||||
})
|
||||
.addEdge(START, "subgraphNode1");
|
||||
|
||||
const subgraph = subgraphBuilder.compile();
|
||||
|
||||
// Parent graph
|
||||
const builder = new StateGraph(State)
|
||||
.addNode("node1", subgraph)
|
||||
.addEdge(START, "node1");
|
||||
|
||||
const checkpointer = new MemorySaver();
|
||||
const graph = builder.compile({ checkpointer });
|
||||
|
||||
const config = { configurable: { thread_id: "1" } };
|
||||
|
||||
await graph.invoke({ foo: "" }, config);
|
||||
const parentState = await graph.getState(config);
|
||||
const subgraphState = (await graph.getState(config, { subgraphs: true })).tasks[0].state; // (1)!
|
||||
|
||||
// resume the subgraph
|
||||
await graph.invoke(new Command({ resume: "bar" }), config);
|
||||
```
|
||||
|
||||
2. This will be available only when the subgraph is interrupted. Once you resume the graph, you won't be able to access the subgraph state.
|
||||
:::
|
||||
|
||||
## Stream subgraph outputs
|
||||
|
||||
To include outputs from subgraphs in the streamed outputs, you can set `subgraphs=True` in the `.stream()` method of the parent graph. This will stream outputs from both the parent graph and any subgraphs.
|
||||
To include outputs from subgraphs in the streamed outputs, you can set the subgraphs option in the stream method of the parent graph. This will stream outputs from both the parent graph and any subgraphs.
|
||||
|
||||
:::python
|
||||
```python
|
||||
for chunk in graph.stream(
|
||||
{"foo": "foo"},
|
||||
@@ -394,9 +771,27 @@ for chunk in graph.stream(
|
||||
```
|
||||
|
||||
1. Set `subgraphs=True` to stream outputs from subgraphs.
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
for await (const chunk of await graph.stream(
|
||||
{ foo: "foo" },
|
||||
{
|
||||
subgraphs: true, // (1)!
|
||||
streamMode: "updates",
|
||||
}
|
||||
)) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
1. Set `subgraphs: true` to stream outputs from subgraphs.
|
||||
:::
|
||||
|
||||
??? example "Stream from subgraphs"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
from langgraph.graph.state import StateGraph, START
|
||||
@@ -450,4 +845,66 @@ for chunk in graph.stream(
|
||||
(('node_2:e58e5673-a661-ebb0-70d4-e298a7fc28b7',), {'subgraph_node_1': {'bar': 'bar'}})
|
||||
(('node_2:e58e5673-a661-ebb0-70d4-e298a7fc28b7',), {'subgraph_node_2': {'foo': 'hi! foobar'}})
|
||||
((), {'node_2': {'foo': 'hi! foobar'}})
|
||||
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { StateGraph, START } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
// Define subgraph
|
||||
const SubgraphState = z.object({
|
||||
foo: z.string(),
|
||||
bar: z.string(),
|
||||
});
|
||||
|
||||
const subgraphBuilder = new StateGraph(SubgraphState)
|
||||
.addNode("subgraphNode1", (state) => {
|
||||
return { bar: "bar" };
|
||||
})
|
||||
.addNode("subgraphNode2", (state) => {
|
||||
// note that this node is using a state key ('bar') that is only available in the subgraph
|
||||
// and is sending update on the shared state key ('foo')
|
||||
return { foo: state.foo + state.bar };
|
||||
})
|
||||
.addEdge(START, "subgraphNode1")
|
||||
.addEdge("subgraphNode1", "subgraphNode2");
|
||||
|
||||
const subgraph = subgraphBuilder.compile();
|
||||
|
||||
// Define parent graph
|
||||
const ParentState = z.object({
|
||||
foo: z.string(),
|
||||
});
|
||||
|
||||
const builder = new StateGraph(ParentState)
|
||||
.addNode("node1", (state) => {
|
||||
return { foo: "hi! " + state.foo };
|
||||
})
|
||||
.addNode("node2", subgraph)
|
||||
.addEdge(START, "node1")
|
||||
.addEdge("node1", "node2");
|
||||
|
||||
const graph = builder.compile();
|
||||
|
||||
for await (const chunk of await graph.stream(
|
||||
{ foo: "foo" },
|
||||
{
|
||||
streamMode: "updates",
|
||||
subgraphs: true, // (1)!
|
||||
}
|
||||
)) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
2. Set `subgraphs: true` to stream outputs from subgraphs.
|
||||
|
||||
```
|
||||
[[], { node1: { foo: 'hi! foo' } }]
|
||||
[['node2:e58e5673-a661-ebb0-70d4-e298a7fc28b7'], { subgraphNode1: { bar: 'bar' } }]
|
||||
[['node2:e58e5673-a661-ebb0-70d4-e298a7fc28b7'], { subgraphNode2: { foo: 'hi! foobar' } }]
|
||||
[[], { node2: { foo: 'hi! foobar' } }]
|
||||
```
|
||||
:::
|
||||
@@ -172,6 +172,82 @@ await agent.invoke({
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
### Dynamically select tools
|
||||
|
||||
Configure tool availability at runtime based on context:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
from langchain.chat_models import init_chat_model
|
||||
from langchain_core.tools import tool
|
||||
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.prebuilt.chat_agent_executor import AgentState
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
|
||||
@dataclass
|
||||
class CustomContext:
|
||||
tools: list[Literal["weather", "compass"]]
|
||||
|
||||
|
||||
@tool
|
||||
def weather() -> str:
|
||||
"""Returns the current weather conditions."""
|
||||
return "It's nice and sunny."
|
||||
|
||||
|
||||
@tool
|
||||
def compass() -> str:
|
||||
"""Returns the direction the user is facing."""
|
||||
return "North"
|
||||
|
||||
model = init_chat_model("anthropic:claude-sonnet-4-20250514")
|
||||
|
||||
# highlight-next-line
|
||||
def configure_model(state: AgentState, runtime: Runtime[CustomContext]):
|
||||
"""Configure the model with tools based on runtime context."""
|
||||
selected_tools = [
|
||||
tool
|
||||
for tool in [weather, compass]
|
||||
if tool.name in runtime.context.tools
|
||||
]
|
||||
return model.bind_tools(selected_tools)
|
||||
|
||||
|
||||
agent = create_react_agent(
|
||||
# Dynamically configure the model with tools based on runtime context
|
||||
# highlight-next-line
|
||||
configure_model,
|
||||
# Initialize with all tools available
|
||||
# highlight-next-line
|
||||
tools=[weather, compass]
|
||||
)
|
||||
|
||||
output = agent.invoke(
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Who are you and what tools do you have access to?",
|
||||
}
|
||||
]
|
||||
},
|
||||
# highlight-next-line
|
||||
context=CustomContext(tools=["weather"]), # Only enable the weather tool
|
||||
)
|
||||
|
||||
print(output["messages"][-1].text())
|
||||
```
|
||||
|
||||
!!! version-added "Added in version 0.6.0"
|
||||
|
||||
:::
|
||||
|
||||
## Use in a workflow
|
||||
|
||||
If you are writing a custom workflow, you will need to:
|
||||
@@ -1495,6 +1571,7 @@ const saveUserInfo = tool(
|
||||
|
||||
from langchain_core.tools import tool
|
||||
from langgraph.config import get_store
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.store.memory import InMemoryStore
|
||||
|
||||
|
||||
+3
-3
@@ -10,7 +10,7 @@
|
||||
- [Implementing Human-in-the-Loop Controls in LangGraph](https://langchain-ai.github.io/langgraph/tutorials/get-started/4-human-in-the-loop/): This page provides a comprehensive guide on adding human-in-the-loop controls to LangGraph workflows, enabling agents to pause execution for human input. It details the use of the `interrupt` function to facilitate user feedback and outlines the steps to integrate a `human_assistance` tool into a chatbot. Additionally, the tutorial covers graph compilation, visualization, and resuming execution with human input.
|
||||
- [Customizing State in LangGraph for Enhanced Chatbot Functionality](https://langchain-ai.github.io/langgraph/tutorials/get-started/5-customize-state/): This tutorial guides you through the process of adding custom fields to the state in LangGraph, enabling complex behaviors in your chatbot without relying solely on message lists. You will learn how to implement human-in-the-loop controls to verify information before it is stored in the state. By the end of this tutorial, you will have a deeper understanding of state management and how to enhance your chatbot's capabilities.
|
||||
- [Implementing Time Travel in LangGraph Chatbots](https://langchain-ai.github.io/langgraph/tutorials/get-started/6-time-travel/): This page provides a comprehensive guide on utilizing the time travel functionality in LangGraph to enhance chatbot interactions. It covers how to rewind, add steps, and replay the state history of a chatbot, allowing users to explore different outcomes and fix mistakes. Additionally, it includes code snippets and practical examples to help developers implement these features effectively.
|
||||
- [LangGraph Deployment Options](https://langchain-ai.github.io/langgraph/tutorials/deployment/): This page outlines the various options available for deploying LangGraph applications, including local testing and different cloud-based solutions. It details free deployment methods such as Local and Standalone Container (Lite), as well as production options like Cloud SaaS and self-hosted solutions. Each deployment method is linked to further documentation for in-depth guidance.
|
||||
- [LangGraph Deployment Options](https://langchain-ai.github.io/langgraph/tutorials/deployment/): This page outlines the various options available for deploying LangGraph applications, including local testing and different cloud-based solutions. It details free deployment methods such as Local, as well as production options like Cloud SaaS and self-hosted solutions. Each deployment method is linked to further documentation for in-depth guidance.
|
||||
- [Agent Development with LangGraph](https://langchain-ai.github.io/langgraph/agents/overview/): This page provides an overview of agent development using LangGraph, highlighting its prebuilt components and capabilities for building agent-based applications. It explains the structure of an agent, key features such as memory integration and human-in-the-loop control, and outlines the package ecosystem available for developers. With LangGraph, users can focus on application logic while leveraging robust infrastructure for state management and feedback.
|
||||
- [Guide to Running Agents in LangGraph](https://langchain-ai.github.io/langgraph/agents/run_agents/): This page provides a comprehensive overview of how to execute agents in LangGraph, detailing both synchronous and asynchronous methods. It covers input and output formats, streaming capabilities, and how to manage execution limits to prevent infinite loops. Additionally, it includes code examples and links to further resources for deeper understanding.
|
||||
- [Streaming Data in LangGraph](https://langchain-ai.github.io/langgraph/agents/streaming/): This page provides an overview of streaming data types in LangGraph, including agent progress, LLM tokens, and custom updates. It includes code examples for both synchronous and asynchronous streaming methods. Additionally, it covers how to stream multiple modes and disable streaming when necessary.
|
||||
@@ -73,7 +73,7 @@
|
||||
- [Integrating Semantic Search in LangGraph](https://langchain-ai.github.io/langgraph/cloud/deployment/semantic_search/): This guide provides step-by-step instructions on how to implement semantic search in your LangGraph deployment. It covers prerequisites, configuration of the store, and usage examples for searching memories and documents by semantic similarity. Additionally, it includes information on using custom embeddings and querying via the LangGraph SDK.
|
||||
- [Configuring Time-to-Live (TTL) in LangGraph Applications](https://langchain-ai.github.io/langgraph/how-tos/ttl/configure_ttl/): This guide provides detailed instructions on how to configure Time-to-Live (TTL) settings for checkpoints and store items in LangGraph applications. It covers the necessary configurations in the `langgraph.json` file, including strategies for managing data lifecycle and memory. Additionally, it explains how to combine TTL configurations and override them at runtime.
|
||||
- [LangGraph Authentication & Access Control Overview](https://langchain-ai.github.io/langgraph/concepts/auth/): This page provides a comprehensive guide to the authentication and authorization mechanisms within the LangGraph Platform. It explains the core concepts of authentication versus authorization, outlines default security models, and details the system architecture involved in user identity management. Additionally, it covers implementation examples for authentication and authorization handlers, along with common access patterns and supported resources.
|
||||
- [Custom Authentication Setup for LangGraph Platform](https://langchain-ai.github.io/langgraph/how-tos/auth/custom_auth/): This guide provides step-by-step instructions on how to implement custom authentication in your LangGraph Platform application. It covers the necessary prerequisites, implementation details, configuration updates, and client connection methods. The guide is applicable to both managed and Enterprise self-hosted deployments, but not to Lite self-hosted plans.
|
||||
- [Custom Authentication Setup for LangGraph Platform](https://langchain-ai.github.io/langgraph/how-tos/auth/custom_auth/): This guide provides step-by-step instructions on how to implement custom authentication in your LangGraph Platform application. It covers the necessary prerequisites, implementation details, configuration updates, and client connection methods. The guide is applicable to both managed and Enterprise self-hosted deployments.
|
||||
- [Documenting API Authentication in OpenAPI for LangGraph](https://langchain-ai.github.io/langgraph/how-tos/auth/openapi_security/): This guide provides instructions on how to customize the security schema for your LangGraph Platform API documentation using OpenAPI. It covers default security schemes for both LangGraph Platform and self-hosted deployments, as well as how to implement custom authentication. Additionally, it includes examples for OAuth2 and API key authentication, along with testing procedures.
|
||||
- [Managing Assistants in LangGraph](https://langchain-ai.github.io/langgraph/concepts/assistants/): This page provides an overview of how to create and manage assistants within the LangGraph Platform, which allows for separate configuration of agents without altering the core graph logic. It covers the prerequisites, configuration options, and versioning of assistants, highlighting their role in optimizing agent performance for different tasks. Additionally, it includes links to relevant API references and how-to guides for further assistance.
|
||||
- [Managing Assistants in LangGraph](https://langchain-ai.github.io/langgraph/cloud/how-tos/configuration_cloud/): This documentation page provides a comprehensive guide on how to create, configure, and manage assistants using the LangGraph SDK and Platform UI. It includes code examples in Python and JavaScript, as well as instructions for creating new versions and using previous versions of assistants. Additionally, it covers the process of utilizing assistants in various environments.
|
||||
@@ -112,7 +112,7 @@
|
||||
- [Deploying a Self-Hosted Data Plane](https://langchain-ai.github.io/langgraph/cloud/deployment/self_hosted_data_plane/): This page provides a comprehensive guide on deploying a Self-Hosted Data Plane using Kubernetes and Amazon ECS. It outlines the prerequisites, setup steps, and configuration details necessary for a successful deployment. Additionally, it highlights the current beta status of this deployment option.
|
||||
- [Self-Hosted Control Plane Deployment Guide](https://langchain-ai.github.io/langgraph/concepts/langgraph_self_hosted_control_plane/): This page provides an overview of the Self-Hosted Control Plane deployment option, currently in beta. It outlines the requirements, architecture, and compute platforms supported for deploying the control and data planes in your cloud environment. Additionally, it includes important links and resources for managing your self-hosted infrastructure.
|
||||
- [Deploying a Self-Hosted Control Plane](https://langchain-ai.github.io/langgraph/cloud/deployment/self_hosted_control_plane/): This page provides a comprehensive guide on deploying a Self-Hosted Control Plane using Kubernetes. It outlines the prerequisites, setup steps, and configuration details necessary for a successful deployment. Additionally, it highlights the beta status of this deployment option and includes links to relevant resources for further assistance.
|
||||
- [Deploying LangGraph Server with Standalone Container](https://langchain-ai.github.io/langgraph/concepts/langgraph_standalone_container/): This page provides a comprehensive guide on deploying a LangGraph Server using the Standalone Container option. It outlines the architecture, supported compute platforms, and differences between Lite and Enterprise server versions. Users will find essential information on managing the data plane infrastructure without a control plane.
|
||||
- [Deploying LangGraph Server with Standalone Container](https://langchain-ai.github.io/langgraph/concepts/langgraph_standalone_container/): This page provides a comprehensive guide on deploying a LangGraph Server using the Standalone Container option. It outlines the architecture, supported compute platforms, and Enterprise server version features. Users will find essential information on managing the data plane infrastructure without a control plane.
|
||||
- [Deploying a Standalone Container with LangGraph](https://langchain-ai.github.io/langgraph/cloud/deployment/standalone_container/): This documentation provides a comprehensive guide on deploying a standalone container for the LangGraph application. It covers prerequisites, environment variable configurations, and deployment methods using Docker and Docker Compose. Additionally, it includes instructions for deploying on Kubernetes using Helm.
|
||||
- [Scalability and Resilience of LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/scalability_and_resilience/): This page provides an overview of the scalability and resilience features of the LangGraph Platform. It details how the platform handles server and queue scalability, as well as the mechanisms in place for ensuring resilience during both graceful and hard shutdowns. Additionally, it covers the resilience strategies employed for Postgres and Redis to maintain service availability.
|
||||
- [LangGraph Platform Plans Overview](https://langchain-ai.github.io/langgraph/concepts/plans/): This page provides an overview of the different plans available for the LangGraph Platform, including Developer, Plus, and Enterprise options. Each plan offers varying deployment options, usage limits, and features tailored to different user needs. For detailed pricing and related resources, links to additional documentation are also included.
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
::: langgraph.cache.base
|
||||
::: langgraph.cache.memory
|
||||
::: langgraph.cache.sqlite
|
||||
::: langgraph.cache.sqlite
|
||||
|
||||
@@ -49,9 +49,8 @@ Higher-level abstractions for common workflows, agents, and other patterns.
|
||||
|
||||
Tools for deploying and connecting to the LangGraph Platform.
|
||||
|
||||
- [CLI](../cloud/reference/cli.md): Command-line interface for building and deploying LangGraph Platform applications.
|
||||
- [Server API](../cloud/reference/api/api_ref.md): REST API for the LangGraph Server.
|
||||
- [SDK (Python)](../cloud/reference/sdk/python_sdk_ref.md): Python SDK for interacting with instances of the LangGraph Server.
|
||||
- [SDK (JS/TS)](../cloud/reference/sdk/js_ts_sdk_ref.md): JavaScript/TypeScript SDK for interacting with instances of the LangGraph Server.
|
||||
- [RemoteGraph](remote_graph.md): `Pregel` abstraction for connecting to LangGraph Server instances.
|
||||
- [Environment variables](../cloud/reference/env_var.md): Supported configuration variables when deploying with the LangGraph Platform.
|
||||
|
||||
See the [LangGraph Platform reference](https://docs.langchain.com/langgraph-platform/reference-overview) for more reference documentation.
|
||||
@@ -21,15 +21,9 @@ See the [local server](../../tutorials/langgraph-platform/local-server.md) docs
|
||||
|
||||
If you would like a fast managed environment, consider the [Cloud SaaS](../../concepts/langgraph_cloud.md) deployment option. This requires no additional license key.
|
||||
|
||||
#### For Standalone Container (Lite)
|
||||
#### For Standalone Container
|
||||
|
||||
If your deployment is unlikely to see more than 1 million node executions per year and don't need Crons and other enterprise features, consider the [Standalone Container](../../concepts/deployment_options.md) deployment option.
|
||||
|
||||
You can deploy with Standalone Container by setting a valid `LANGSMITH_API_KEY` in your environment (e.g., in the `.env` file referenced by `langgraph.json`) and building a Docker image. The API key must be associated with an account on a **Plus** plan or greater.
|
||||
|
||||
#### For Standalone Container (Enterprise)
|
||||
|
||||
For full self-hosting, set the `LANGGRAPH_CLOUD_LICENSE_KEY` environment variable. If you are interested in an enterprise license key, please contact the LangChain support team.
|
||||
For self-hosting, set the `LANGGRAPH_CLOUD_LICENSE_KEY` environment variable. If you are interested in an enterprise license key, please contact the LangChain support team.
|
||||
|
||||
For more information on deployment options and their features, see the [Deployment Options](../../concepts/deployment_options.md) documentation.
|
||||
|
||||
@@ -38,12 +32,7 @@ For more information on deployment options and their features, see the [Deployme
|
||||
|
||||
If you have confirmed that you would like to self-host LangGraph Platform, please verify your credentials.
|
||||
|
||||
#### For Standalone Container (Lite)
|
||||
|
||||
1. Confirm that you have provided a working `LANGSMITH_API_KEY` environment variable in your deployment environment or `.env` file
|
||||
2. Confirm the provided API key is associated with an account on a **Plus** or **Enterprise** plan (or equivalent)
|
||||
|
||||
#### For Standalone Container (Enterprise)
|
||||
#### For Standalone Container
|
||||
|
||||
1. Confirm that you have provided a working `LANGGRAPH_CLOUD_LICENSE_KEY` environment variable in your deployment environment or `.env` file
|
||||
2. Confirm the key is still valid and has not surpassed its expiration date
|
||||
@@ -11,11 +11,4 @@ Errors referenced below will have an `lc_error_code` property corresponding to o
|
||||
- [INVALID_CONCURRENT_GRAPH_UPDATE](./INVALID_CONCURRENT_GRAPH_UPDATE.md)
|
||||
- [INVALID_GRAPH_NODE_RETURN_VALUE](./INVALID_GRAPH_NODE_RETURN_VALUE.md)
|
||||
- [MULTIPLE_SUBGRAPHS](./MULTIPLE_SUBGRAPHS.md)
|
||||
- [INVALID_CHAT_HISTORY](./INVALID_CHAT_HISTORY.md)
|
||||
|
||||
## LangGraph Platform
|
||||
|
||||
These guides provide troubleshooting information for errors that are specific to the LangGraph Platform.
|
||||
|
||||
- [INVALID_LICENSE](./INVALID_LICENSE.md)
|
||||
- [Studio Errors](../studio.md)
|
||||
- [INVALID_CHAT_HISTORY](./INVALID_CHAT_HISTORY.md)
|
||||
@@ -68,7 +68,7 @@ The server will start and open the studio in your browser:
|
||||
> - 📚 API Docs: http://127.0.0.1:2024/docs
|
||||
>
|
||||
> This in-memory server is designed for development and testing.
|
||||
> For production use, please use LangGraph Platform.
|
||||
> For production use, please use LangSmith Deployment.
|
||||
```
|
||||
|
||||
If you were to self-host this on the public internet, anyone could access it!
|
||||
|
||||
@@ -17,7 +17,7 @@ Create a `MemorySaver` checkpointer:
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
memory = InMemorySaver()
|
||||
```
|
||||
@@ -447,3 +447,4 @@ const graph = new StateGraph(State)
|
||||
## Next steps
|
||||
|
||||
In the next tutorial, you will [add human-in-the-loop to the chatbot](./4-human-in-the-loop.md) to handle situations where it may need guidance or verification before proceeding.
|
||||
|
||||
|
||||
@@ -294,9 +294,9 @@ Now that you have a LangGraph app running locally, take your journey further by
|
||||
:::python
|
||||
|
||||
- [Python SDK Reference](../../cloud/reference/sdk/python_sdk_ref.md): Explore the Python SDK API Reference.
|
||||
:::
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- [JS/TS SDK Reference](../../cloud/reference/sdk/js_ts_sdk_ref.md): Explore the JS/TS SDK API Reference.
|
||||
:::
|
||||
:::
|
||||
|
||||
@@ -85,124 +85,80 @@ Let's [run the agent](../../agents/run_agents.md) to verify that it behaves as e
|
||||
|
||||
!!! note "We'll use `pretty_print_messages` helper to render the streamed agent outputs nicely"
|
||||
|
||||
```python
|
||||
from langchain_core.messages import convert_to_messages
|
||||
|
||||
|
||||
def pretty_print_message(message, indent=False):
|
||||
pretty_message = message.pretty_repr(html=True)
|
||||
if not indent:
|
||||
print(pretty_message)
|
||||
return
|
||||
|
||||
indented = "\n".join("\t" + c for c in pretty_message.split("\n"))
|
||||
print(indented)
|
||||
|
||||
|
||||
def pretty_print_messages(update, last_message=False):
|
||||
is_subgraph = False
|
||||
if isinstance(update, tuple):
|
||||
ns, update = update
|
||||
# skip parent graph updates in the printouts
|
||||
if len(ns) == 0:
|
||||
return
|
||||
|
||||
graph_id = ns[-1].split(":")[0]
|
||||
print(f"Update from subgraph {graph_id}:")
|
||||
print("\n")
|
||||
is_subgraph = True
|
||||
|
||||
for node_name, node_update in update.items():
|
||||
update_label = f"Update from node {node_name}:"
|
||||
if is_subgraph:
|
||||
update_label = "\t" + update_label
|
||||
|
||||
print(update_label)
|
||||
print("\n")
|
||||
|
||||
messages = convert_to_messages(node_update["messages"])
|
||||
if last_message:
|
||||
messages = messages[-1:]
|
||||
|
||||
for m in messages:
|
||||
pretty_print_message(m, indent=is_subgraph)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
```python
|
||||
from langchain_core.messages import convert_to_messages
|
||||
```python
|
||||
from langchain_core.messages import convert_to_messages
|
||||
|
||||
|
||||
def pretty_print_message(message, indent=False):
|
||||
pretty_message = message.pretty_repr(html=True)
|
||||
if not indent:
|
||||
print(pretty_message)
|
||||
return
|
||||
def pretty_print_message(message, indent=False):
|
||||
pretty_message = message.pretty_repr(html=True)
|
||||
if not indent:
|
||||
print(pretty_message)
|
||||
return
|
||||
|
||||
indented = "\n".join("\t" + c for c in pretty_message.split("\n"))
|
||||
print(indented)
|
||||
indented = "\n".join("\t" + c for c in pretty_message.split("\n"))
|
||||
print(indented)
|
||||
|
||||
|
||||
def pretty_print_messages(update, last_message=False):
|
||||
is_subgraph = False
|
||||
if isinstance(update, tuple):
|
||||
ns, update = update
|
||||
# skip parent graph updates in the printouts
|
||||
if len(ns) == 0:
|
||||
return
|
||||
def pretty_print_messages(update, last_message=False):
|
||||
is_subgraph = False
|
||||
if isinstance(update, tuple):
|
||||
ns, update = update
|
||||
# skip parent graph updates in the printouts
|
||||
if len(ns) == 0:
|
||||
return
|
||||
|
||||
graph_id = ns[-1].split(":")[0]
|
||||
print(f"Update from subgraph {graph_id}:")
|
||||
print("\n")
|
||||
is_subgraph = True
|
||||
graph_id = ns[-1].split(":")[0]
|
||||
print(f"Update from subgraph {graph_id}:")
|
||||
print("\n")
|
||||
is_subgraph = True
|
||||
|
||||
for node_name, node_update in update.items():
|
||||
update_label = f"Update from node {node_name}:"
|
||||
if is_subgraph:
|
||||
update_label = "\t" + update_label
|
||||
for node_name, node_update in update.items():
|
||||
update_label = f"Update from node {node_name}:"
|
||||
if is_subgraph:
|
||||
update_label = "\t" + update_label
|
||||
|
||||
print(update_label)
|
||||
print("\n")
|
||||
print(update_label)
|
||||
print("\n")
|
||||
|
||||
messages = convert_to_messages(node_update["messages"])
|
||||
if last_message:
|
||||
messages = messages[-1:]
|
||||
messages = convert_to_messages(node_update["messages"])
|
||||
if last_message:
|
||||
messages = messages[-1:]
|
||||
|
||||
for m in messages:
|
||||
pretty_print_message(m, indent=is_subgraph)
|
||||
print("\n")
|
||||
```
|
||||
for m in messages:
|
||||
pretty_print_message(m, indent=is_subgraph)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
```python
|
||||
for chunk in research_agent.stream(
|
||||
{"messages": [{"role": "user", "content": "who is the mayor of NYC?"}]}
|
||||
):
|
||||
pretty_print_messages(chunk)
|
||||
```
|
||||
```python
|
||||
for chunk in research_agent.stream(
|
||||
{"messages": [{"role": "user", "content": "who is the mayor of NYC?"}]}
|
||||
):
|
||||
pretty_print_messages(chunk)
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```
|
||||
Update from node agent:
|
||||
**Output:**
|
||||
```
|
||||
Update from node agent:
|
||||
|
||||
|
||||
================================== Ai Message ==================================
|
||||
Name: research_agent
|
||||
Tool Calls:
|
||||
tavily_search (call_U748rQhQXT36sjhbkYLSXQtJ)
|
||||
Call ID: call_U748rQhQXT36sjhbkYLSXQtJ
|
||||
Args:
|
||||
query: current mayor of New York City
|
||||
search_depth: basic
|
||||
================================== Ai Message ==================================
|
||||
Name: research_agent
|
||||
Tool Calls:
|
||||
tavily_search (call_U748rQhQXT36sjhbkYLSXQtJ)
|
||||
Call ID: call_U748rQhQXT36sjhbkYLSXQtJ
|
||||
Args:
|
||||
query: current mayor of New York City
|
||||
search_depth: basic
|
||||
|
||||
|
||||
Update from node tools:
|
||||
Update from node tools:
|
||||
|
||||
|
||||
================================= Tool Message ==================================
|
||||
Name: tavily_search
|
||||
================================= Tool Message ==================================
|
||||
Name: tavily_search
|
||||
|
||||
{"query": "current mayor of New York City", "follow_up_questions": null, "answer": null, "images": [], "results": [{"title": "List of mayors of New York City - Wikipedia", "url": "https://en.wikipedia.org/wiki/List_of_mayors_of_New_York_City", "content": "The mayor of New York City is the chief executive of the Government of New York City, as stipulated by New York City's charter.The current officeholder, the 110th in the sequence of regular mayors, is Eric Adams, a member of the Democratic Party.. During the Dutch colonial period from 1624 to 1664, New Amsterdam was governed by the Director of Netherland.", "score": 0.9039154, "raw_content": null}, {"title": "Office of the Mayor | Mayor's Bio | City of New York - NYC.gov", "url": "https://www.nyc.gov/office-of-the-mayor/bio.page", "content": "Mayor Eric Adams has served the people of New York City as an NYPD officer, State Senator, Brooklyn Borough President, and now as the 110th Mayor of the City of New York. He gave voice to a diverse coalition of working families in all five boroughs and is leading the fight to bring back New York City's economy, reduce inequality, improve", "score": 0.8405867, "raw_content": null}, {"title": "Eric Adams - Wikipedia", "url": "https://en.wikipedia.org/wiki/Eric_Adams", "content": "Eric Leroy Adams (born September 1, 1960) is an American politician and former police officer who has served as the 110th mayor of New York City since 2022. Adams was an officer in the New York City Transit Police and then the New York City Police Department (```
|
||||
```
|
||||
{"query": "current mayor of New York City", "follow_up_questions": null, "answer": null, "images": [], "results": [{"title": "List of mayors of New York City - Wikipedia", "url": "https://en.wikipedia.org/wiki/List_of_mayors_of_New_York_City", "content": "The mayor of New York City is the chief executive of the Government of New York City, as stipulated by New York City's charter.The current officeholder, the 110th in the sequence of regular mayors, is Eric Adams, a member of the Democratic Party.. During the Dutch colonial period from 1624 to 1664, New Amsterdam was governed by the Director of Netherland.", "score": 0.9039154, "raw_content": null}, {"title": "Office of the Mayor | Mayor's Bio | City of New York - NYC.gov", "url": "https://www.nyc.gov/office-of-the-mayor/bio.page", "content": "Mayor Eric Adams has served the people of New York City as an NYPD officer, State Senator, Brooklyn Borough President, and now as the 110th Mayor of the City of New York. He gave voice to a diverse coalition of working families in all five boroughs and is leading the fight to bring back New York City's economy, reduce inequality, improve", "score": 0.8405867, "raw_content": null}, {"title": "Eric Adams - Wikipedia", "url": "https://en.wikipedia.org/wiki/Eric_Adams", "content": "Eric Leroy Adams (born September 1, 1960) is an American politician and former police officer who has served as the 110th mayor of New York City since 2022. Adams was an officer in the New York City Transit Police and then the New York City Police Department (```
|
||||
```
|
||||
|
||||
### Math agent
|
||||
|
||||
@@ -809,4 +765,4 @@ Update from subgraph research_agent:
|
||||
Name: tavily_search
|
||||
|
||||
{"query": "2024 United States GDP value from a reputable source", "follow_up_questions": null, "answer": null, "images": [], "results": [{"url": "https://www.focus-economics.com/countries/united-states/", "title": "United States Economy Overview - Focus Economics", "content": "The United States' Macroeconomic Analysis:\n------------------------------------------\n\n**Nominal GDP of USD 29,185 billion in 2024.**\n\n**Nominal GDP of USD 29,179 billion in 2024.**\n\n**GDP per capita of USD 86,635 compared to the global average of USD 10,589.**\n\n**GDP per capita of USD 86,652 compared to the global average of USD 10,589.**\n\n**Average real GDP growth of 2.5% over the last decade.**\n\n**Average real GDP growth of ```
|
||||
```
|
||||
```
|
||||
|
||||
@@ -1948,7 +1948,7 @@ const llmWithTools = llm.bindTools(tools);
|
||||
|
||||
|
||||
# Conditional edge function to route to the tool node or end based upon whether the LLM made a tool call
|
||||
def should_continue(state: MessagesState) -> Literal["environment", END]:
|
||||
def should_continue(state: MessagesState) -> Literal["Action", END]:
|
||||
"""Decide if we should continue the loop or stop based upon whether the LLM made a tool call"""
|
||||
|
||||
messages = state["messages"]
|
||||
|
||||
+160
-197
@@ -52,6 +52,164 @@ theme:
|
||||
plugins:
|
||||
- search:
|
||||
separator: '[\s\u200b\-,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;'
|
||||
- exclude-search:
|
||||
exclude:
|
||||
- additional-resources/index.md
|
||||
- agents/prebuilt.md
|
||||
- cloud/concepts/cron_jobs.md
|
||||
- cloud/concepts/data_storage_and_privacy.md
|
||||
- cloud/concepts/webhooks.md
|
||||
- cloud/deployment/cloud.md
|
||||
- cloud/deployment/custom_docker.md
|
||||
- cloud/deployment/egress.md
|
||||
- cloud/deployment/graph_rebuild.md
|
||||
- cloud/deployment/self_hosted_control_plane.md
|
||||
- cloud/deployment/self_hosted_data_plane.md
|
||||
- cloud/deployment/semantic_search.md
|
||||
- cloud/deployment/setup_javascript.md
|
||||
- cloud/deployment/setup_pyproject.md
|
||||
- cloud/deployment/setup.md
|
||||
- cloud/deployment/standalone_container.md
|
||||
- cloud/how-tos/add-human-in-the-loop.md
|
||||
- cloud/how-tos/background_run.md
|
||||
- cloud/how-tos/clone_traces_studio.md
|
||||
- cloud/how-tos/configurable_headers.md
|
||||
- cloud/how-tos/configuration_cloud.md
|
||||
- cloud/how-tos/cron_jobs.md
|
||||
- cloud/how-tos/datasets_studio.md
|
||||
- cloud/how-tos/enqueue_concurrent.md
|
||||
- cloud/how-tos/generative_ui_react.md
|
||||
- cloud/how-tos/human_in_the_loop_time_travel.md
|
||||
- cloud/how-tos/interrupt_concurrent.md
|
||||
- cloud/how-tos/invoke_studio.md
|
||||
- cloud/how-tos/iterate_graph_studio.md
|
||||
- cloud/how-tos/reject_concurrent.md
|
||||
- cloud/how-tos/rollback_concurrent.md
|
||||
- cloud/how-tos/same-thread.md
|
||||
- cloud/how-tos/stateless_runs.md
|
||||
- cloud/how-tos/streaming.md
|
||||
- cloud/how-tos/studio/manage_assistants.md
|
||||
- cloud/how-tos/studio/quick_start.md
|
||||
- cloud/how-tos/studio/run_evals.md
|
||||
- cloud/how-tos/threads_studio.md
|
||||
- cloud/how-tos/use_stream_react.md
|
||||
- cloud/how-tos/use_threads.md
|
||||
- cloud/how-tos/webhooks.md
|
||||
- cloud/quick_start.md
|
||||
- cloud/reference/api/api_ref_control_plane.md
|
||||
- cloud/reference/api/api_ref.md
|
||||
- cloud/reference/cli.md
|
||||
- cloud/reference/env_var.md
|
||||
- cloud/reference/langgraph_server_changelog.md
|
||||
- cloud/reference/sdk/js_ts_sdk_ref.md
|
||||
- concepts/application_structure.md
|
||||
- concepts/assistants.md
|
||||
- concepts/auth.md
|
||||
- concepts/deployment_options.md
|
||||
- concepts/double_texting.md
|
||||
- concepts/faq.md
|
||||
- concepts/langgraph_cli.md
|
||||
- concepts/langgraph_cloud.md
|
||||
- concepts/langgraph_components.md
|
||||
- concepts/langgraph_control_plane.md
|
||||
- concepts/langgraph_data_plane.md
|
||||
- concepts/langgraph_platform.md
|
||||
- concepts/langgraph_self_hosted_control_plane.md
|
||||
- concepts/langgraph_self_hosted_data_plane.md
|
||||
- concepts/langgraph_server.md
|
||||
- concepts/langgraph_standalone_container.md
|
||||
- concepts/langgraph_studio.md
|
||||
- concepts/plans.md
|
||||
- concepts/scalability_and_resilience.md
|
||||
- concepts/sdk.md
|
||||
- concepts/server-mcp.md
|
||||
- concepts/template_applications.md
|
||||
- concepts/why-langgraph.md
|
||||
- examples/index.md
|
||||
- guides/index.md
|
||||
- how-tos/auth/custom_auth.md
|
||||
- how-tos/auth/openapi_security.md
|
||||
- how-tos/autogen-integration.md
|
||||
- how-tos/http/custom_lifespan.md
|
||||
- how-tos/http/custom_middleware.md
|
||||
- how-tos/http/custom_routes.md
|
||||
- how-tos/ttl/configure_ttl.md
|
||||
- how-tos/use-remote-graph.md
|
||||
- index.md
|
||||
- reference/index.md
|
||||
- snippets/chat_model_tabs.md
|
||||
- troubleshooting/errors/GRAPH_RECURSION_LIMIT.md
|
||||
- troubleshooting/errors/index.md
|
||||
- troubleshooting/errors/INVALID_CHAT_HISTORY.md
|
||||
- troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md
|
||||
- troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md
|
||||
- troubleshooting/errors/INVALID_LICENSE.md
|
||||
- troubleshooting/errors/MULTIPLE_SUBGRAPHS.md
|
||||
- troubleshooting/studio.md
|
||||
- tutorials/auth/add_auth_server.md
|
||||
- tutorials/auth/getting_started.md
|
||||
- tutorials/auth/resource_auth.md
|
||||
- agents/agents.md
|
||||
- concepts/why-langgraph.md
|
||||
- tutorials/get-started/1-build-basic-chatbot.md
|
||||
- tutorials/get-started/2-add-tools.md
|
||||
- tutorials/get-started/3-add-memory.md
|
||||
- tutorials/get-started/4-human-in-the-loop.md
|
||||
- tutorials/get-started/5-customize-state.md
|
||||
- tutorials/get-started/6-time-travel.md
|
||||
- tutorials/langgraph-platform/local-server.md
|
||||
- tutorials/workflows.md
|
||||
- concepts/agentic_concepts.md
|
||||
- guides/index.md
|
||||
- agents/overview.md
|
||||
- agents/run_agents.md
|
||||
- concepts/low_level.md
|
||||
- how-tos/graph-api.md
|
||||
- concepts/functional_api.md
|
||||
- how-tos/use-functional-api.md
|
||||
- concepts/pregel.md
|
||||
- concepts/streaming.md
|
||||
- how-tos/streaming.md
|
||||
- concepts/persistence.md
|
||||
- concepts/durable_execution.md
|
||||
- concepts/memory.md
|
||||
- how-tos/memory/add-memory.md
|
||||
- agents/context.md
|
||||
- agents/models.md
|
||||
- concepts/tools.md
|
||||
- how-tos/tool-calling.md
|
||||
- concepts/human_in_the_loop.md
|
||||
- how-tos/human_in_the_loop/add-human-in-the-loop.md
|
||||
- concepts/time-travel.md
|
||||
- how-tos/human_in_the_loop/time-travel.md
|
||||
- concepts/subgraphs.md
|
||||
- how-tos/subgraph.md
|
||||
- concepts/multi_agent.md
|
||||
- agents/multi-agent.md
|
||||
- how-tos/multi_agent.md
|
||||
- concepts/mcp.md
|
||||
- agents/mcp.md
|
||||
- concepts/tracing.md
|
||||
- how-tos/enable-tracing.md
|
||||
- agents/evals.md
|
||||
- examples/index.md
|
||||
- concepts/template_applications.md # TODO: make tutorial
|
||||
- tutorials/rag/langgraph_agentic_rag.md
|
||||
- tutorials/multi_agent/agent_supervisor.md
|
||||
- tutorials/sql/sql-agent.md
|
||||
- agents/ui.md
|
||||
- how-tos/run-id-langsmith.md
|
||||
- troubleshooting/errors/index.md
|
||||
- troubleshooting/errors/GRAPH_RECURSION_LIMIT.md
|
||||
- troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md
|
||||
- troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md
|
||||
- troubleshooting/errors/MULTIPLE_SUBGRAPHS.md
|
||||
- troubleshooting/errors/INVALID_CHAT_HISTORY.md
|
||||
- troubleshooting/errors/INVALID_LICENSE.md
|
||||
- adopters.md
|
||||
- concepts/faq.md
|
||||
- agents/prebuilt.md # NOTE: prebuilt.md is auto-generated by `make build-prebuilt`
|
||||
|
||||
- tags
|
||||
- include-markdown
|
||||
- mkdocstrings:
|
||||
@@ -89,156 +247,6 @@ plugins:
|
||||
- "!^_"
|
||||
|
||||
nav:
|
||||
- Get started:
|
||||
- index.md
|
||||
- Quickstarts:
|
||||
- Start with a prebuilt agent: agents/agents.md
|
||||
- Build a custom workflow:
|
||||
- concepts/why-langgraph.md
|
||||
- 1. Build a basic chatbot: tutorials/get-started/1-build-basic-chatbot.md
|
||||
- 2. Add tools: tutorials/get-started/2-add-tools.md
|
||||
- 3. Add memory: tutorials/get-started/3-add-memory.md
|
||||
- 4. Add human-in-the-loop: tutorials/get-started/4-human-in-the-loop.md
|
||||
- 5. Customize state: tutorials/get-started/5-customize-state.md
|
||||
- 6. Time travel: tutorials/get-started/6-time-travel.md
|
||||
- Run a local server: tutorials/langgraph-platform/local-server.md
|
||||
- General concepts:
|
||||
- Workflows & agents: tutorials/workflows.md
|
||||
- Agent architectures: concepts/agentic_concepts.md
|
||||
|
||||
- Guides:
|
||||
- guides/index.md
|
||||
- Agent development:
|
||||
- Overview: agents/overview.md
|
||||
- Run an agent: agents/run_agents.md
|
||||
- LangGraph APIs:
|
||||
- Graph API:
|
||||
- Overview: concepts/low_level.md
|
||||
- Use the Graph API: how-tos/graph-api.md
|
||||
- Functional API:
|
||||
- Overview: concepts/functional_api.md
|
||||
- Use the Functional API: how-tos/use-functional-api.md
|
||||
- Runtime: concepts/pregel.md
|
||||
- Core capabilities:
|
||||
- Streaming:
|
||||
- Overview: concepts/streaming.md
|
||||
- Stream outputs: how-tos/streaming.md
|
||||
- Use Server API: cloud/how-tos/streaming.md
|
||||
- Persistence:
|
||||
- Overview: concepts/persistence.md
|
||||
- Durable execution:
|
||||
- Overview: concepts/durable_execution.md
|
||||
- Memory:
|
||||
- Overview: concepts/memory.md
|
||||
- Add memory: how-tos/memory/add-memory.md
|
||||
- Context:
|
||||
- Add context: agents/context.md
|
||||
- Models:
|
||||
- Configure model: agents/models.md
|
||||
- Tools:
|
||||
- Overview: concepts/tools.md
|
||||
- Call tools: how-tos/tool-calling.md
|
||||
- Human-in-the-loop:
|
||||
- Overview: concepts/human_in_the_loop.md
|
||||
- Add human intervention: how-tos/human_in_the_loop/add-human-in-the-loop.md
|
||||
- Use Server API: cloud/how-tos/add-human-in-the-loop.md
|
||||
- Time travel:
|
||||
- Overview: concepts/time-travel.md
|
||||
- Use time travel: how-tos/human_in_the_loop/time-travel.md
|
||||
- Use Server API: cloud/how-tos/human_in_the_loop_time_travel.md
|
||||
- Subgraphs:
|
||||
- Overview: concepts/subgraphs.md
|
||||
- Use subgraphs: how-tos/subgraph.md
|
||||
- Multi-agent:
|
||||
- Overview: concepts/multi_agent.md
|
||||
- Prebuilt implementation: agents/multi-agent.md
|
||||
- Custom implementation: how-tos/multi_agent.md
|
||||
- MCP:
|
||||
- Overview: concepts/mcp.md
|
||||
- Use MCP: agents/mcp.md
|
||||
- Server API: concepts/server-mcp.md
|
||||
- Tracing:
|
||||
- Overview: concepts/tracing.md
|
||||
- Enable tracing: how-tos/enable-tracing.md
|
||||
- Evaluate performance: agents/evals.md
|
||||
- Platform-only capabilities:
|
||||
- LangGraph Platform:
|
||||
- Overview: concepts/langgraph_platform.md
|
||||
- Components:
|
||||
- Overview: concepts/langgraph_components.md
|
||||
- LangGraph Server:
|
||||
- Overview: concepts/langgraph_server.md
|
||||
- Data plane: concepts/langgraph_data_plane.md
|
||||
- Control plane: concepts/langgraph_control_plane.md
|
||||
- LangGraph CLI: concepts/langgraph_cli.md
|
||||
- LangGraph Studio:
|
||||
- Overview: concepts/langgraph_studio.md
|
||||
- Quickstart: cloud/how-tos/studio/quick_start.md
|
||||
- cloud/how-tos/invoke_studio.md
|
||||
- cloud/how-tos/studio/manage_assistants.md
|
||||
- cloud/how-tos/threads_studio.md
|
||||
- cloud/how-tos/iterate_graph_studio.md
|
||||
- cloud/how-tos/studio/run_evals.md
|
||||
- cloud/how-tos/clone_traces_studio.md
|
||||
- cloud/how-tos/datasets_studio.md
|
||||
- LangGraph SDK: concepts/sdk.md
|
||||
- Plans & pricing: concepts/plans.md
|
||||
- Application structure: concepts/application_structure.md
|
||||
- Scalability & resilience: concepts/scalability_and_resilience.md
|
||||
- Authentication & access control:
|
||||
- Overview: concepts/auth.md
|
||||
- how-tos/auth/custom_auth.md
|
||||
- how-tos/auth/openapi_security.md
|
||||
- Assistants:
|
||||
- Overview: concepts/assistants.md
|
||||
- cloud/how-tos/configuration_cloud.md
|
||||
- Threads: cloud/how-tos/use_threads.md
|
||||
- Runs:
|
||||
- cloud/how-tos/background_run.md
|
||||
- cloud/how-tos/same-thread.md
|
||||
- cloud/how-tos/cron_jobs.md
|
||||
- cloud/how-tos/stateless_runs.md
|
||||
- cloud/how-tos/configurable_headers.md
|
||||
- Double-texting:
|
||||
- Overview: concepts/double_texting.md
|
||||
- cloud/how-tos/interrupt_concurrent.md
|
||||
- cloud/how-tos/rollback_concurrent.md
|
||||
- cloud/how-tos/reject_concurrent.md
|
||||
- cloud/how-tos/enqueue_concurrent.md
|
||||
- Webhooks:
|
||||
- Overview: cloud/concepts/webhooks.md
|
||||
- Use webhooks: cloud/how-tos/webhooks.md
|
||||
- Cron jobs:
|
||||
- Overview: cloud/concepts/cron_jobs.md
|
||||
- cloud/how-tos/cron_jobs.md
|
||||
- Server customization:
|
||||
- how-tos/http/custom_lifespan.md
|
||||
- how-tos/http/custom_middleware.md
|
||||
- how-tos/http/custom_routes.md
|
||||
- Data management:
|
||||
- cloud/concepts/data_storage_and_privacy.md
|
||||
- Add semantic search: cloud/deployment/semantic_search.md
|
||||
- Add TTLs: how-tos/ttl/configure_ttl.md
|
||||
- Deployment:
|
||||
- Overview: concepts/deployment_options.md
|
||||
- Quickstart: cloud/quick_start.md
|
||||
- Set up your application:
|
||||
- Use requirements.txt: cloud/deployment/setup.md
|
||||
- Use pyproject.toml: cloud/deployment/setup_pyproject.md
|
||||
- Use JavaScript: cloud/deployment/setup_javascript.md
|
||||
- Use custom Docker: cloud/deployment/custom_docker.md
|
||||
- Rebuild graph at runtime: cloud/deployment/graph_rebuild.md
|
||||
- Deployment options:
|
||||
- Cloud SaaS: concepts/langgraph_cloud.md
|
||||
- Self-Hosted Data Plane: concepts/langgraph_self_hosted_data_plane.md
|
||||
- Self-Hosted Control Plane: concepts/langgraph_self_hosted_control_plane.md
|
||||
- Standalone Container: concepts/langgraph_standalone_container.md
|
||||
- Deploy to production:
|
||||
- Cloud SaaS: cloud/deployment/cloud.md
|
||||
- Self-Hosted Data Plane: cloud/deployment/self_hosted_data_plane.md
|
||||
- Self-Hosted Control Plane: cloud/deployment/self_hosted_control_plane.md
|
||||
- Standalone Container: cloud/deployment/standalone_container.md
|
||||
|
||||
- Reference:
|
||||
- reference/index.md
|
||||
- LangGraph:
|
||||
@@ -260,53 +268,9 @@ nav:
|
||||
- Swarm: reference/swarm.md
|
||||
- MCP Adapters: reference/mcp.md
|
||||
- LangGraph Platform:
|
||||
- Server API: cloud/reference/api/api_ref.md
|
||||
- Server changelog: cloud/reference/langgraph_server_changelog.md
|
||||
- Control Plane API: cloud/reference/api/api_ref_control_plane.md
|
||||
- CLI: cloud/reference/cli.md
|
||||
- SDK (Python): cloud/reference/sdk/python_sdk_ref.md
|
||||
- SDK (JS/TS): https://langchain-ai.github.io/langgraphjs/reference/modules/sdk.html
|
||||
- RemoteGraph: reference/remote_graph.md
|
||||
- Environment variables: cloud/reference/env_var.md
|
||||
|
||||
- Examples:
|
||||
- examples/index.md
|
||||
- Template applications: concepts/template_applications.md # TODO: make tutorial
|
||||
- Agentic RAG: tutorials/rag/langgraph_agentic_rag.md
|
||||
- Agent Supervisor: tutorials/multi_agent/agent_supervisor.md
|
||||
- SQL agent: tutorials/sql/sql-agent.md
|
||||
- Prebuilt chat UI: agents/ui.md
|
||||
- Graph runs in LangSmith: how-tos/run-id-langsmith.md
|
||||
- LangGraph Platform:
|
||||
- Authentication:
|
||||
- tutorials/auth/getting_started.md
|
||||
- tutorials/auth/resource_auth.md
|
||||
- tutorials/auth/add_auth_server.md
|
||||
- Use RemoteGraph: how-tos/use-remote-graph.md
|
||||
- Deploy CrewAI, AutoGen, and other frameworks: how-tos/autogen-integration.md
|
||||
- Front-end and generative UI:
|
||||
- Integrate LangGraph into a React app: cloud/how-tos/use_stream_react.md
|
||||
- Implement generative UI with LangGraph: cloud/how-tos/generative_ui_react.md
|
||||
|
||||
- Additional resources:
|
||||
- additional-resources/index.md
|
||||
- agents/prebuilt.md # NOTE: prebuilt.md is auto-generated by `make build-prebuilt`
|
||||
- LangGraph Academy course: https://academy.langchain.com/courses/intro-to-langgraph
|
||||
- Case studies: adopters.md
|
||||
- concepts/faq.md
|
||||
- llms.txt: llms-txt-overview.md
|
||||
- LangChain Forum: https://forum.langchain.com/
|
||||
- Troubleshooting:
|
||||
- Errors:
|
||||
- troubleshooting/errors/index.md
|
||||
- troubleshooting/errors/GRAPH_RECURSION_LIMIT.md
|
||||
- troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md
|
||||
- troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md
|
||||
- troubleshooting/errors/MULTIPLE_SUBGRAPHS.md
|
||||
- troubleshooting/errors/INVALID_CHAT_HISTORY.md
|
||||
- troubleshooting/errors/INVALID_LICENSE.md
|
||||
- LangGraph Studio: troubleshooting/studio.md
|
||||
|
||||
- RemoteGraph: reference/remote_graph.md
|
||||
|
||||
markdown_extensions:
|
||||
- abbr
|
||||
@@ -383,5 +347,4 @@ extra_css:
|
||||
- stylesheets/version_admonitions.css
|
||||
- stylesheets/logos.css
|
||||
- stylesheets/sticky_navigation.css
|
||||
- stylesheets/agent_graph_widget.css
|
||||
|
||||
- stylesheets/agent_graph_widget.css
|
||||
@@ -291,7 +291,7 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
}
|
||||
|
||||
.md-banner {
|
||||
background-color: #CFC9FA;
|
||||
background-color: #FFAE42;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
@@ -360,5 +360,5 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
{% endblock %}
|
||||
|
||||
{% block announce %}
|
||||
Our <a href="https://academy.langchain.com/courses/ambient-agents/?utm_medium=internal&utm_source=docs&utm_campaign=q2-2025_ambient-agents_co" target="_blank">Building Ambient Agents with LangGraph</a> course is now available on LangChain Academy!
|
||||
These docs will be deprecated and removed with the release of LangGraph v1.0 in October 2025. <a href="https://docs.langchain.com/oss/python/langgraph/overview" target="_blank">Visit the v1.0 alpha docs</a>
|
||||
{% endblock %}
|
||||
|
||||
+5
-4
@@ -7,14 +7,14 @@ name = "langgraph-docs"
|
||||
version = "0.0.1"
|
||||
description = "LangGraph docs"
|
||||
authors = []
|
||||
requires-python = "~=3.11"
|
||||
requires-python = ">=3.11.0,<4.0.0"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
dependencies = [
|
||||
"aiohappyeyeballs==2.4.3",
|
||||
"hub>=3.0.1,<4",
|
||||
"xxhash>=3.5.0,<4",
|
||||
"black>=25.1.0,<26",
|
||||
"hub>=3.0.1,<4.0.0",
|
||||
"xxhash>=3.5.0,<4.0.0",
|
||||
"black>=25.1.0,<26.0.0",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
@@ -39,6 +39,7 @@ docs = [
|
||||
"markdown-callouts",
|
||||
"markdown-include",
|
||||
"mkdocs-exclude",
|
||||
"mkdocs-exclude-search",
|
||||
"psycopg[binary]",
|
||||
"psycopg-pool",
|
||||
"pygments-ansi-color",
|
||||
|
||||
Generated
+19
-4
@@ -1,5 +1,5 @@
|
||||
version = 1
|
||||
revision = 2
|
||||
revision = 3
|
||||
requires-python = ">=3.11, <4"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.13' and platform_python_implementation != 'PyPy'",
|
||||
@@ -2337,7 +2337,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "0.6.1"
|
||||
version = "0.6.7"
|
||||
source = { editable = "../libs/langgraph" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
@@ -2380,6 +2380,7 @@ dev = [
|
||||
{ name = "pytest-repeat" },
|
||||
{ name = "pytest-watcher" },
|
||||
{ name = "pytest-xdist", extras = ["psutil"] },
|
||||
{ name = "redis" },
|
||||
{ name = "ruff" },
|
||||
{ name = "syrupy" },
|
||||
{ name = "types-requests" },
|
||||
@@ -2413,6 +2414,7 @@ dev = [
|
||||
{ name = "pytest-asyncio" },
|
||||
{ name = "pytest-mock" },
|
||||
{ name = "pytest-watcher" },
|
||||
{ name = "redis" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
@@ -2524,6 +2526,7 @@ docs = [
|
||||
{ name = "markdown-include" },
|
||||
{ name = "mkdocs" },
|
||||
{ name = "mkdocs-exclude" },
|
||||
{ name = "mkdocs-exclude-search" },
|
||||
{ name = "mkdocs-git-committers-plugin-2" },
|
||||
{ name = "mkdocs-include-markdown-plugin" },
|
||||
{ name = "mkdocs-material", extra = ["imaging"] },
|
||||
@@ -2595,6 +2598,7 @@ docs = [
|
||||
{ name = "markdown-include" },
|
||||
{ name = "mkdocs" },
|
||||
{ name = "mkdocs-exclude" },
|
||||
{ name = "mkdocs-exclude-search" },
|
||||
{ name = "mkdocs-git-committers-plugin-2" },
|
||||
{ name = "mkdocs-include-markdown-plugin", specifier = ">=7.1.6" },
|
||||
{ name = "mkdocs-material", extras = ["imaging"] },
|
||||
@@ -2641,7 +2645,7 @@ test = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-prebuilt"
|
||||
version = "0.6.1"
|
||||
version = "0.6.4"
|
||||
source = { editable = "../libs/prebuilt" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
@@ -2672,7 +2676,6 @@ dev = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-sdk"
|
||||
version = "0.2.0"
|
||||
source = { editable = "../libs/sdk-py" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
@@ -3030,6 +3033,18 @@ dependencies = [
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/b5/3a8e289282c9e8d7003f8a2f53d673d4fdaa81d493dc6966092d9985b6fc/mkdocs-exclude-1.0.2.tar.gz", hash = "sha256:ba6fab3c80ddbe3fd31d3e579861fd3124513708271180a5f81846da8c7e2a51", size = 6751, upload-time = "2019-02-20T23:34:12.81Z" }
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-exclude-search"
|
||||
version = "0.6.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mkdocs" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1d/52/8243589d294cf6091c1145896915fe50feea0e91d64d843942d0175770c2/mkdocs-exclude-search-0.6.6.tar.gz", hash = "sha256:3cdff1b9afdc1b227019cd1e124f401453235b92153d60c0e5e651a76be4f044", size = 9501, upload-time = "2023-12-03T22:58:21.259Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/ef/9af45ffb1bdba684a0694922abae0bb771e9777aba005933f838b7f1bcea/mkdocs_exclude_search-0.6.6-py3-none-any.whl", hash = "sha256:2b4b941d1689808db533fe4a6afba75ce76c9bab8b21d4e31efc05fd8c4e0a4f", size = 7821, upload-time = "2023-12-03T22:58:19.355Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-get-deps"
|
||||
version = "0.2.0"
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b3cec425",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/human_in_the_loop/dynamic_breakpoints.ipynb"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4876215f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/human_in_the_loop/edit-graph-state.ipynb"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.8"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b162f1bd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/human_in_the_loop/review-tool-calls.ipynb"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "84c5f6f1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/human_in_the_loop/time-travel.ipynb"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5eb637a4",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/multi_agent/agent_supervisor.ipynb"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"id": "18526f23",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/persistence_postgres.ipynb"
|
||||
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/memory/add-memory.md"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -707,7 +707,9 @@
|
||||
" \"\"\"\n",
|
||||
" Find all tool calls in the messages returned\n",
|
||||
" \"\"\"\n",
|
||||
" tool_calls = [tc['name'] for m in messages['messages'] for tc in getattr(m, 'tool_calls', [])]\n",
|
||||
" tool_calls = [\n",
|
||||
" tc[\"name\"] for m in messages[\"messages\"] for tc in getattr(m, \"tool_calls\", [])\n",
|
||||
" ]\n",
|
||||
" return tool_calls\n",
|
||||
"\n",
|
||||
"\n",
|
||||
|
||||
@@ -7,11 +7,6 @@ from contextlib import contextmanager
|
||||
from typing import Any
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from psycopg import Capabilities, Connection, Cursor, Pipeline
|
||||
from psycopg.rows import DictRow, dict_row
|
||||
from psycopg.types.json import Jsonb
|
||||
from psycopg_pool import ConnectionPool
|
||||
|
||||
from langgraph.checkpoint.base import (
|
||||
WRITES_IDX_MAP,
|
||||
ChannelVersions,
|
||||
@@ -19,12 +14,17 @@ from langgraph.checkpoint.base import (
|
||||
CheckpointMetadata,
|
||||
CheckpointTuple,
|
||||
get_checkpoint_id,
|
||||
get_checkpoint_metadata,
|
||||
get_serializable_checkpoint_metadata,
|
||||
)
|
||||
from langgraph.checkpoint.serde.base import SerializerProtocol
|
||||
from psycopg import Capabilities, Connection, Cursor, Pipeline
|
||||
from psycopg.rows import DictRow, dict_row
|
||||
from psycopg.types.json import Jsonb
|
||||
from psycopg_pool import ConnectionPool
|
||||
|
||||
from langgraph.checkpoint.postgres import _internal
|
||||
from langgraph.checkpoint.postgres.base import BasePostgresSaver
|
||||
from langgraph.checkpoint.postgres.shallow import ShallowPostgresSaver
|
||||
from langgraph.checkpoint.serde.base import SerializerProtocol
|
||||
|
||||
Conn = _internal.Conn # For backward compatibility
|
||||
|
||||
@@ -94,6 +94,7 @@ class PostgresSaver(BasePostgresSaver):
|
||||
for v, migration in zip(
|
||||
range(version + 1, len(self.MIGRATIONS)),
|
||||
self.MIGRATIONS[version + 1 :],
|
||||
strict=False,
|
||||
):
|
||||
cur.execute(migration)
|
||||
cur.execute(f"INSERT INTO checkpoint_migrations (v) VALUES ({v})")
|
||||
@@ -115,12 +116,12 @@ class PostgresSaver(BasePostgresSaver):
|
||||
|
||||
Args:
|
||||
config: The config to use for listing the checkpoints.
|
||||
filter: Additional filtering criteria for metadata. Defaults to None.
|
||||
before: If provided, only checkpoints before the specified checkpoint ID are returned. Defaults to None.
|
||||
limit: The maximum number of checkpoints to return. Defaults to None.
|
||||
filter: Additional filtering criteria for metadata.
|
||||
before: If provided, only checkpoints before the specified checkpoint ID are returned.
|
||||
limit: The maximum number of checkpoints to return.
|
||||
|
||||
Yields:
|
||||
Iterator[CheckpointTuple]: An iterator of checkpoint tuples.
|
||||
An iterator of checkpoint tuples.
|
||||
|
||||
Examples:
|
||||
>>> from langgraph.checkpoint.postgres import PostgresSaver
|
||||
@@ -182,7 +183,7 @@ class PostgresSaver(BasePostgresSaver):
|
||||
"""Get a checkpoint tuple from the database.
|
||||
|
||||
This method retrieves a checkpoint tuple from the Postgres database based on the
|
||||
provided config. If the config contains a "checkpoint_id" key, the checkpoint with
|
||||
provided config. If the config contains a `checkpoint_id` key, the checkpoint with
|
||||
the matching thread ID and timestamp is retrieved. Otherwise, the latest checkpoint
|
||||
for the given thread ID is retrieved.
|
||||
|
||||
@@ -190,7 +191,7 @@ class PostgresSaver(BasePostgresSaver):
|
||||
config: The config to use for retrieving the checkpoint.
|
||||
|
||||
Returns:
|
||||
Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
||||
The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -325,7 +326,7 @@ class PostgresSaver(BasePostgresSaver):
|
||||
checkpoint["id"],
|
||||
checkpoint_id,
|
||||
Jsonb(copy),
|
||||
Jsonb(get_checkpoint_metadata(config, metadata)),
|
||||
Jsonb(get_serializable_checkpoint_metadata(config, metadata)),
|
||||
),
|
||||
)
|
||||
return next_config
|
||||
@@ -450,7 +451,7 @@ class PostgresSaver(BasePostgresSaver):
|
||||
{
|
||||
**value["checkpoint"],
|
||||
"channel_values": {
|
||||
**value["checkpoint"].get("channel_values"),
|
||||
**(value["checkpoint"].get("channel_values") or {}),
|
||||
**self._load_blobs(value["channel_values"]),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
from collections.abc import AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Union
|
||||
|
||||
from psycopg import AsyncConnection
|
||||
from psycopg.rows import DictRow
|
||||
from psycopg_pool import AsyncConnectionPool
|
||||
|
||||
Conn = Union[AsyncConnection[DictRow], AsyncConnectionPool[AsyncConnection[DictRow]]]
|
||||
Conn = AsyncConnection[DictRow] | AsyncConnectionPool[AsyncConnection[DictRow]]
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from typing import Union
|
||||
|
||||
from psycopg import Connection
|
||||
from psycopg.rows import DictRow
|
||||
from psycopg_pool import ConnectionPool
|
||||
|
||||
Conn = Union[Connection[DictRow], ConnectionPool[Connection[DictRow]]]
|
||||
Conn = Connection[DictRow] | ConnectionPool[Connection[DictRow]]
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
||||
@@ -7,11 +7,6 @@ from contextlib import asynccontextmanager
|
||||
from typing import Any
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline, Capabilities
|
||||
from psycopg.rows import DictRow, dict_row
|
||||
from psycopg.types.json import Jsonb
|
||||
from psycopg_pool import AsyncConnectionPool
|
||||
|
||||
from langgraph.checkpoint.base import (
|
||||
WRITES_IDX_MAP,
|
||||
ChannelVersions,
|
||||
@@ -19,12 +14,17 @@ from langgraph.checkpoint.base import (
|
||||
CheckpointMetadata,
|
||||
CheckpointTuple,
|
||||
get_checkpoint_id,
|
||||
get_checkpoint_metadata,
|
||||
get_serializable_checkpoint_metadata,
|
||||
)
|
||||
from langgraph.checkpoint.serde.base import SerializerProtocol
|
||||
from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline, Capabilities
|
||||
from psycopg.rows import DictRow, dict_row
|
||||
from psycopg.types.json import Jsonb
|
||||
from psycopg_pool import AsyncConnectionPool
|
||||
|
||||
from langgraph.checkpoint.postgres import _ainternal
|
||||
from langgraph.checkpoint.postgres.base import BasePostgresSaver
|
||||
from langgraph.checkpoint.postgres.shallow import AsyncShallowPostgresSaver
|
||||
from langgraph.checkpoint.serde.base import SerializerProtocol
|
||||
|
||||
Conn = _ainternal.Conn # For backward compatibility
|
||||
|
||||
@@ -99,6 +99,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
for v, migration in zip(
|
||||
range(version + 1, len(self.MIGRATIONS)),
|
||||
self.MIGRATIONS[version + 1 :],
|
||||
strict=False,
|
||||
):
|
||||
await cur.execute(migration)
|
||||
await cur.execute(f"INSERT INTO checkpoint_migrations (v) VALUES ({v})")
|
||||
@@ -121,11 +122,11 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
Args:
|
||||
config: Base configuration for filtering checkpoints.
|
||||
filter: Additional filtering criteria for metadata.
|
||||
before: If provided, only checkpoints before the specified checkpoint ID are returned. Defaults to None.
|
||||
before: If provided, only checkpoints before the specified checkpoint ID are returned.
|
||||
limit: Maximum number of checkpoints to return.
|
||||
|
||||
Yields:
|
||||
AsyncIterator[CheckpointTuple]: An asynchronous iterator of matching checkpoint tuples.
|
||||
An asynchronous iterator of matching checkpoint tuples.
|
||||
"""
|
||||
where, args = self._search_where(config, filter, before)
|
||||
query = self.SELECT_SQL + where + " ORDER BY checkpoint_id DESC"
|
||||
@@ -169,7 +170,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
"""Get a checkpoint tuple from the database asynchronously.
|
||||
|
||||
This method retrieves a checkpoint tuple from the Postgres database based on the
|
||||
provided config. If the config contains a "checkpoint_id" key, the checkpoint with
|
||||
provided config. If the config contains a `checkpoint_id` key, the checkpoint with
|
||||
the matching thread ID and "checkpoint_id" is retrieved. Otherwise, the latest checkpoint
|
||||
for the given thread ID is retrieved.
|
||||
|
||||
@@ -177,7 +178,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
config: The config to use for retrieving the checkpoint.
|
||||
|
||||
Returns:
|
||||
Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
||||
The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
||||
"""
|
||||
thread_id = config["configurable"]["thread_id"]
|
||||
checkpoint_id = get_checkpoint_id(config)
|
||||
@@ -283,7 +284,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
checkpoint["id"],
|
||||
checkpoint_id,
|
||||
Jsonb(copy),
|
||||
Jsonb(get_checkpoint_metadata(config, metadata)),
|
||||
Jsonb(get_serializable_checkpoint_metadata(config, metadata)),
|
||||
),
|
||||
)
|
||||
return next_config
|
||||
@@ -409,7 +410,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
{
|
||||
**value["checkpoint"],
|
||||
"channel_values": {
|
||||
**value["checkpoint"].get("channel_values"),
|
||||
**(value["checkpoint"].get("channel_values") or {}),
|
||||
**self._load_blobs(value["channel_values"]),
|
||||
},
|
||||
},
|
||||
@@ -444,11 +445,11 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
Args:
|
||||
config: Base configuration for filtering checkpoints.
|
||||
filter: Additional filtering criteria for metadata.
|
||||
before: If provided, only checkpoints before the specified checkpoint ID are returned. Defaults to None.
|
||||
before: If provided, only checkpoints before the specified checkpoint ID are returned.
|
||||
limit: Maximum number of checkpoints to return.
|
||||
|
||||
Yields:
|
||||
Iterator[CheckpointTuple]: An iterator of matching checkpoint tuples.
|
||||
An iterator of matching checkpoint tuples.
|
||||
"""
|
||||
try:
|
||||
# check if we are in the main thread, only bg threads can block
|
||||
@@ -476,7 +477,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
"""Get a checkpoint tuple from the database.
|
||||
|
||||
This method retrieves a checkpoint tuple from the Postgres database based on the
|
||||
provided config. If the config contains a "checkpoint_id" key, the checkpoint with
|
||||
provided config. If the config contains a `checkpoint_id` key, the checkpoint with
|
||||
the matching thread ID and "checkpoint_id" is retrieved. Otherwise, the latest checkpoint
|
||||
for the given thread ID is retrieved.
|
||||
|
||||
@@ -484,7 +485,7 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
config: The config to use for retrieving the checkpoint.
|
||||
|
||||
Returns:
|
||||
Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
||||
The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
||||
"""
|
||||
try:
|
||||
# check if we are in the main thread, only bg threads can block
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
import warnings
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, Optional, cast
|
||||
from importlib.metadata import version as get_version
|
||||
from typing import Any, cast
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from psycopg.types.json import Jsonb
|
||||
|
||||
from langgraph.checkpoint.base import (
|
||||
WRITES_IDX_MAP,
|
||||
BaseCheckpointSaver,
|
||||
@@ -14,8 +14,21 @@ from langgraph.checkpoint.base import (
|
||||
get_checkpoint_id,
|
||||
)
|
||||
from langgraph.checkpoint.serde.types import TASKS
|
||||
from psycopg.types.json import Jsonb
|
||||
|
||||
MetadataInput = Optional[dict[str, Any]]
|
||||
MetadataInput = dict[str, Any] | None
|
||||
|
||||
try:
|
||||
major, minor = get_version("langgraph").split(".")[:2]
|
||||
if int(major) == 0 and int(minor) < 5:
|
||||
warnings.warn(
|
||||
"You're using incompatible versions of langgraph and checkpoint-postgres. Please upgrade langgraph to avoid unexpected behavior.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
except Exception:
|
||||
# skip version check if running from source
|
||||
pass
|
||||
|
||||
"""
|
||||
To add a new migration, add a new string to the MIGRATIONS list.
|
||||
|
||||
@@ -3,9 +3,19 @@ import threading
|
||||
import warnings
|
||||
from collections.abc import AsyncIterator, Iterator, Sequence
|
||||
from contextlib import asynccontextmanager, contextmanager
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.checkpoint.base import (
|
||||
WRITES_IDX_MAP,
|
||||
ChannelVersions,
|
||||
Checkpoint,
|
||||
CheckpointMetadata,
|
||||
CheckpointTuple,
|
||||
get_serializable_checkpoint_metadata,
|
||||
)
|
||||
from langgraph.checkpoint.serde.base import SerializerProtocol
|
||||
from langgraph.checkpoint.serde.types import TASKS
|
||||
from psycopg import (
|
||||
AsyncConnection,
|
||||
AsyncCursor,
|
||||
@@ -19,18 +29,8 @@ from psycopg.rows import DictRow, dict_row
|
||||
from psycopg.types.json import Jsonb
|
||||
from psycopg_pool import AsyncConnectionPool, ConnectionPool
|
||||
|
||||
from langgraph.checkpoint.base import (
|
||||
WRITES_IDX_MAP,
|
||||
ChannelVersions,
|
||||
Checkpoint,
|
||||
CheckpointMetadata,
|
||||
CheckpointTuple,
|
||||
get_checkpoint_metadata,
|
||||
)
|
||||
from langgraph.checkpoint.postgres import _ainternal, _internal
|
||||
from langgraph.checkpoint.postgres.base import BasePostgresSaver
|
||||
from langgraph.checkpoint.serde.base import SerializerProtocol
|
||||
from langgraph.checkpoint.serde.types import TASKS
|
||||
|
||||
"""
|
||||
To add a new migration, add a new string to the MIGRATIONS list.
|
||||
@@ -151,7 +151,7 @@ def _dump_blobs(
|
||||
checkpoint_ns: str,
|
||||
values: dict[str, Any],
|
||||
versions: ChannelVersions,
|
||||
) -> list[tuple[str, str, str, str, Optional[bytes]]]:
|
||||
) -> list[tuple[str, str, str, str, bytes | None]]:
|
||||
if not versions:
|
||||
return []
|
||||
|
||||
@@ -186,8 +186,8 @@ class ShallowPostgresSaver(BasePostgresSaver):
|
||||
def __init__(
|
||||
self,
|
||||
conn: _internal.Conn,
|
||||
pipe: Optional[Pipeline] = None,
|
||||
serde: Optional[SerializerProtocol] = None,
|
||||
pipe: Pipeline | None = None,
|
||||
serde: SerializerProtocol | None = None,
|
||||
) -> None:
|
||||
warnings.warn(
|
||||
"ShallowPostgresSaver is deprecated as of version 2.0.20 and will be removed in 3.0.0. "
|
||||
@@ -249,6 +249,7 @@ class ShallowPostgresSaver(BasePostgresSaver):
|
||||
for v, migration in zip(
|
||||
range(version + 1, len(self.MIGRATIONS)),
|
||||
self.MIGRATIONS[version + 1 :],
|
||||
strict=False,
|
||||
):
|
||||
cur.execute(migration)
|
||||
cur.execute(f"INSERT INTO checkpoint_migrations (v) VALUES ({v})")
|
||||
@@ -257,11 +258,11 @@ class ShallowPostgresSaver(BasePostgresSaver):
|
||||
|
||||
def list(
|
||||
self,
|
||||
config: Optional[RunnableConfig],
|
||||
config: RunnableConfig | None,
|
||||
*,
|
||||
filter: Optional[dict[str, Any]] = None,
|
||||
before: Optional[RunnableConfig] = None,
|
||||
limit: Optional[int] = None,
|
||||
filter: dict[str, Any] | None = None,
|
||||
before: RunnableConfig | None = None,
|
||||
limit: int | None = None,
|
||||
) -> Iterator[CheckpointTuple]:
|
||||
"""List checkpoints from the database.
|
||||
|
||||
@@ -299,7 +300,7 @@ class ShallowPostgresSaver(BasePostgresSaver):
|
||||
pending_writes=self._load_writes(value["pending_writes"]),
|
||||
)
|
||||
|
||||
def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
|
||||
def get_tuple(self, config: RunnableConfig) -> CheckpointTuple | None:
|
||||
"""Get a checkpoint tuple from the database.
|
||||
|
||||
This method retrieves a checkpoint tuple from the Postgres database based on the
|
||||
@@ -309,7 +310,7 @@ class ShallowPostgresSaver(BasePostgresSaver):
|
||||
config: The config to use for retrieving the checkpoint.
|
||||
|
||||
Returns:
|
||||
Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
||||
The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -441,7 +442,7 @@ class ShallowPostgresSaver(BasePostgresSaver):
|
||||
thread_id,
|
||||
checkpoint_ns,
|
||||
Jsonb(copy),
|
||||
Jsonb(get_checkpoint_metadata(config, metadata)),
|
||||
Jsonb(get_serializable_checkpoint_metadata(config, metadata)),
|
||||
),
|
||||
)
|
||||
return next_config
|
||||
@@ -542,8 +543,8 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
|
||||
def __init__(
|
||||
self,
|
||||
conn: _ainternal.Conn,
|
||||
pipe: Optional[AsyncPipeline] = None,
|
||||
serde: Optional[SerializerProtocol] = None,
|
||||
pipe: AsyncPipeline | None = None,
|
||||
serde: SerializerProtocol | None = None,
|
||||
) -> None:
|
||||
warnings.warn(
|
||||
"AsyncShallowPostgresSaver is deprecated as of version 2.0.20 and will be removed in 3.0.0. "
|
||||
@@ -570,7 +571,7 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
|
||||
conn_string: str,
|
||||
*,
|
||||
pipeline: bool = False,
|
||||
serde: Optional[SerializerProtocol] = None,
|
||||
serde: SerializerProtocol | None = None,
|
||||
) -> AsyncIterator["AsyncShallowPostgresSaver"]:
|
||||
"""Create a new AsyncShallowPostgresSaver instance from a connection string.
|
||||
|
||||
@@ -610,6 +611,7 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
|
||||
for v, migration in zip(
|
||||
range(version + 1, len(self.MIGRATIONS)),
|
||||
self.MIGRATIONS[version + 1 :],
|
||||
strict=False,
|
||||
):
|
||||
await cur.execute(migration)
|
||||
await cur.execute(f"INSERT INTO checkpoint_migrations (v) VALUES ({v})")
|
||||
@@ -618,11 +620,11 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
|
||||
|
||||
async def alist(
|
||||
self,
|
||||
config: Optional[RunnableConfig],
|
||||
config: RunnableConfig | None,
|
||||
*,
|
||||
filter: Optional[dict[str, Any]] = None,
|
||||
before: Optional[RunnableConfig] = None,
|
||||
limit: Optional[int] = None,
|
||||
filter: dict[str, Any] | None = None,
|
||||
before: RunnableConfig | None = None,
|
||||
limit: int | None = None,
|
||||
) -> AsyncIterator[CheckpointTuple]:
|
||||
"""List checkpoints from the database asynchronously.
|
||||
|
||||
@@ -662,7 +664,7 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
|
||||
),
|
||||
)
|
||||
|
||||
async def aget_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
|
||||
async def aget_tuple(self, config: RunnableConfig) -> CheckpointTuple | None:
|
||||
"""Get a checkpoint tuple from the database asynchronously.
|
||||
|
||||
This method retrieves a checkpoint tuple from the Postgres database based on the
|
||||
@@ -672,7 +674,7 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
|
||||
config: The config to use for retrieving the checkpoint.
|
||||
|
||||
Returns:
|
||||
Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
||||
The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
||||
"""
|
||||
thread_id = config["configurable"]["thread_id"]
|
||||
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
|
||||
@@ -774,7 +776,7 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
|
||||
thread_id,
|
||||
checkpoint_ns,
|
||||
Jsonb(copy),
|
||||
Jsonb(get_checkpoint_metadata(config, metadata)),
|
||||
Jsonb(get_serializable_checkpoint_metadata(config, metadata)),
|
||||
),
|
||||
)
|
||||
return next_config
|
||||
@@ -861,11 +863,11 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
|
||||
|
||||
def list(
|
||||
self,
|
||||
config: Optional[RunnableConfig],
|
||||
config: RunnableConfig | None,
|
||||
*,
|
||||
filter: Optional[dict[str, Any]] = None,
|
||||
before: Optional[RunnableConfig] = None,
|
||||
limit: Optional[int] = None,
|
||||
filter: dict[str, Any] | None = None,
|
||||
before: RunnableConfig | None = None,
|
||||
limit: int | None = None,
|
||||
) -> Iterator[CheckpointTuple]:
|
||||
"""List checkpoints from the database.
|
||||
|
||||
@@ -883,7 +885,7 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
|
||||
except StopAsyncIteration:
|
||||
break
|
||||
|
||||
def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
|
||||
def get_tuple(self, config: RunnableConfig) -> CheckpointTuple | None:
|
||||
"""Get a checkpoint tuple from the database.
|
||||
|
||||
This method retrieves a checkpoint tuple from the Postgres database based on the
|
||||
@@ -893,7 +895,7 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
|
||||
config: The config to use for retrieving the checkpoint.
|
||||
|
||||
Returns:
|
||||
Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
||||
The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
||||
"""
|
||||
try:
|
||||
# check if we are in the main thread, only bg threads can block
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from langgraph.store.postgres.aio import AsyncPostgresStore
|
||||
from langgraph.store.postgres.base import PostgresStore
|
||||
from langgraph.store.postgres.base import PoolConfig, PostgresStore
|
||||
|
||||
__all__ = ["AsyncPostgresStore", "PostgresStore"]
|
||||
__all__ = ["AsyncPostgresStore", "PoolConfig", "PostgresStore"]
|
||||
|
||||
@@ -2,17 +2,12 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from collections.abc import AsyncIterator, Iterable, Sequence
|
||||
from collections.abc import AsyncIterator, Callable, Iterable, Sequence
|
||||
from contextlib import asynccontextmanager
|
||||
from types import TracebackType
|
||||
from typing import Any, Callable, cast
|
||||
from typing import Any, cast
|
||||
|
||||
import orjson
|
||||
from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline, Capabilities
|
||||
from psycopg.rows import DictRow, dict_row
|
||||
from psycopg_pool import AsyncConnectionPool
|
||||
|
||||
from langgraph.checkpoint.postgres import _ainternal
|
||||
from langgraph.store.base import (
|
||||
GetOp,
|
||||
ListNamespacesOp,
|
||||
@@ -22,6 +17,11 @@ from langgraph.store.base import (
|
||||
SearchOp,
|
||||
)
|
||||
from langgraph.store.base.batch import AsyncBatchedBaseStore
|
||||
from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline, Capabilities
|
||||
from psycopg.rows import DictRow, dict_row
|
||||
from psycopg_pool import AsyncConnectionPool
|
||||
|
||||
from langgraph.checkpoint.postgres import _ainternal
|
||||
from langgraph.store.postgres.base import (
|
||||
PLACEHOLDER,
|
||||
BasePostgresStore,
|
||||
@@ -339,7 +339,7 @@ class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[_ainternal.Con
|
||||
|
||||
Args:
|
||||
timeout: Maximum time to wait for the task to stop, in seconds.
|
||||
If None, wait indefinitely.
|
||||
If `None`, wait indefinitely.
|
||||
|
||||
Returns:
|
||||
bool: True if the task was successfully stopped or wasn't running,
|
||||
@@ -465,7 +465,9 @@ class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[_ainternal.Con
|
||||
query,
|
||||
[
|
||||
p
|
||||
for (ns, k, pathname, _), vector in zip(txt_params, vectors)
|
||||
for (ns, k, pathname, _), vector in zip(
|
||||
txt_params, vectors, strict=False
|
||||
)
|
||||
for p in (ns, k, pathname, vector)
|
||||
],
|
||||
)
|
||||
@@ -486,13 +488,13 @@ class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[_ainternal.Con
|
||||
vectors = await self.embeddings.aembed_documents(
|
||||
[query for _, query in embedding_requests]
|
||||
)
|
||||
for (idx, _), vector in zip(embedding_requests, vectors):
|
||||
for (idx, _), vector in zip(embedding_requests, vectors, strict=False):
|
||||
_paramslist = queries[idx][1]
|
||||
for i in range(len(_paramslist)):
|
||||
if _paramslist[i] is PLACEHOLDER:
|
||||
_paramslist[i] = vector
|
||||
|
||||
for (idx, _), (query, params) in zip(search_ops, queries):
|
||||
for (idx, _), (query, params) in zip(search_ops, queries, strict=False):
|
||||
await cur.execute(query, params)
|
||||
rows = cast(list[Row], await cur.fetchall())
|
||||
items = [
|
||||
@@ -510,7 +512,7 @@ class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[_ainternal.Con
|
||||
cur: AsyncCursor[DictRow],
|
||||
) -> None:
|
||||
queries = self._get_batch_list_namespaces_queries(list_ops)
|
||||
for (query, params), (idx, _) in zip(queries, list_ops):
|
||||
for (query, params), (idx, _) in zip(queries, list_ops, strict=False):
|
||||
await cur.execute(query, params)
|
||||
rows = cast(list[dict], await cur.fetchall())
|
||||
namespaces = [_decode_ns_bytes(row["truncated_prefix"]) for row in rows]
|
||||
|
||||
@@ -6,30 +6,20 @@ import json
|
||||
import logging
|
||||
import threading
|
||||
from collections import defaultdict
|
||||
from collections.abc import Iterable, Iterator, Sequence
|
||||
from collections.abc import Callable, Iterable, Iterator, Sequence
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Generic,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
import orjson
|
||||
from psycopg import Capabilities, Connection, Cursor, Pipeline
|
||||
from psycopg.rows import DictRow, dict_row
|
||||
from psycopg.types.json import Jsonb
|
||||
from psycopg_pool import ConnectionPool
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.checkpoint.postgres import _ainternal as _ainternal
|
||||
from langgraph.checkpoint.postgres import _internal as _pg_internal
|
||||
from langgraph.store.base import (
|
||||
BaseStore,
|
||||
GetOp,
|
||||
@@ -46,6 +36,14 @@ from langgraph.store.base import (
|
||||
get_text_at_path,
|
||||
tokenize_path,
|
||||
)
|
||||
from psycopg import Capabilities, Connection, Cursor, Pipeline
|
||||
from psycopg.rows import DictRow, dict_row
|
||||
from psycopg.types.json import Jsonb
|
||||
from psycopg_pool import ConnectionPool
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.checkpoint.postgres import _ainternal as _ainternal
|
||||
from langgraph.checkpoint.postgres import _internal as _pg_internal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langchain_core.embeddings import Embeddings
|
||||
@@ -141,7 +139,7 @@ CREATE INDEX CONCURRENTLY IF NOT EXISTS store_vectors_embedding_idx ON store_vec
|
||||
]
|
||||
|
||||
|
||||
C = TypeVar("C", bound=Union[_pg_internal.Conn, _ainternal.Conn])
|
||||
C = TypeVar("C", bound=_pg_internal.Conn | _ainternal.Conn)
|
||||
|
||||
|
||||
class PoolConfig(TypedDict, total=False):
|
||||
@@ -255,7 +253,7 @@ class BasePostgresStore(Generic[C]):
|
||||
|
||||
results = []
|
||||
for namespace, items in namespace_groups.items():
|
||||
_, keys = zip(*items)
|
||||
_, keys = zip(*items, strict=False)
|
||||
this_refresh_ttls = refresh_ttls[namespace]
|
||||
|
||||
query = """
|
||||
@@ -868,7 +866,7 @@ class PostgresStore(BaseStore, BasePostgresStore[_pg_internal.Conn]):
|
||||
|
||||
Args:
|
||||
timeout: Maximum time to wait for the thread to stop, in seconds.
|
||||
If None, wait indefinitely.
|
||||
If `None`, wait indefinitely.
|
||||
|
||||
Returns:
|
||||
bool: True if the thread was successfully stopped or wasn't running,
|
||||
@@ -1014,7 +1012,9 @@ class PostgresStore(BaseStore, BasePostgresStore[_pg_internal.Conn]):
|
||||
query,
|
||||
[
|
||||
p
|
||||
for (ns, k, pathname, _), vector in zip(txt_params, vectors)
|
||||
for (ns, k, pathname, _), vector in zip(
|
||||
txt_params, vectors, strict=False
|
||||
)
|
||||
for p in (ns, k, pathname, vector)
|
||||
],
|
||||
)
|
||||
@@ -1035,13 +1035,15 @@ class PostgresStore(BaseStore, BasePostgresStore[_pg_internal.Conn]):
|
||||
embeddings = self.embeddings.embed_documents(
|
||||
[query for _, query in embedding_requests]
|
||||
)
|
||||
for (idx, _), embedding in zip(embedding_requests, embeddings):
|
||||
for (idx, _), embedding in zip(
|
||||
embedding_requests, embeddings, strict=False
|
||||
):
|
||||
_paramslist = queries[idx][1]
|
||||
for i in range(len(_paramslist)):
|
||||
if _paramslist[i] is PLACEHOLDER:
|
||||
_paramslist[i] = embedding
|
||||
|
||||
for (idx, _), (query, params) in zip(search_ops, queries):
|
||||
for (idx, _), (query, params) in zip(search_ops, queries, strict=False):
|
||||
cur.execute(query, params)
|
||||
rows = cast(list[Row], cur.fetchall())
|
||||
results[idx] = [
|
||||
@@ -1058,7 +1060,7 @@ class PostgresStore(BaseStore, BasePostgresStore[_pg_internal.Conn]):
|
||||
cur: Cursor[DictRow],
|
||||
) -> None:
|
||||
for (query, params), (idx, _) in zip(
|
||||
self._get_batch_list_namespaces_queries(list_ops), list_ops
|
||||
self._get_batch_list_namespaces_queries(list_ops), list_ops, strict=False
|
||||
):
|
||||
cur.execute(query, params)
|
||||
results[idx] = [_decode_ns_bytes(row["truncated_prefix"]) for row in cur]
|
||||
|
||||
@@ -4,15 +4,15 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "langgraph-checkpoint-postgres"
|
||||
version = "2.0.23"
|
||||
version = "3.0.0"
|
||||
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
|
||||
authors = []
|
||||
requires-python = ">=3.9"
|
||||
requires-python = ">=3.10"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
license-files = ['LICENSE']
|
||||
dependencies = [
|
||||
"langgraph-checkpoint>=2.0.21,<3.0.0",
|
||||
"langgraph-checkpoint>=2.1.2,<4.0.0",
|
||||
"orjson>=3.10.1",
|
||||
"psycopg>=3.2.0",
|
||||
"psycopg-pool>=3.2.0",
|
||||
@@ -22,18 +22,24 @@ dependencies = [
|
||||
Repository = "https://www.github.com/langchain-ai/langgraph"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"ruff",
|
||||
"codespell",
|
||||
test = [
|
||||
"pytest",
|
||||
"anyio",
|
||||
"pytest-asyncio",
|
||||
"pytest-mock",
|
||||
"mypy",
|
||||
"psycopg[binary]",
|
||||
"langgraph-checkpoint",
|
||||
"pytest-watcher",
|
||||
]
|
||||
lint = [
|
||||
"ruff",
|
||||
"codespell",
|
||||
"mypy",
|
||||
]
|
||||
dev = [
|
||||
{include-group = "test"},
|
||||
{include-group = "lint"},
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
default-groups = ['dev']
|
||||
@@ -55,8 +61,10 @@ lint.select = [
|
||||
"UP", # pyupgrade
|
||||
"B", # flake8-bugbear
|
||||
"I", # isort
|
||||
"UP", # pyupgrade
|
||||
]
|
||||
lint.ignore = ["E501", "B008"]
|
||||
target-version = "py310"
|
||||
|
||||
[tool.mypy]
|
||||
# https://mypy.readthedocs.io/en/stable/config_file.html
|
||||
|
||||
@@ -6,10 +6,6 @@ from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from psycopg import AsyncConnection
|
||||
from psycopg.rows import dict_row
|
||||
from psycopg_pool import AsyncConnectionPool
|
||||
|
||||
from langgraph.checkpoint.base import (
|
||||
EXCLUDED_METADATA_KEYS,
|
||||
Checkpoint,
|
||||
@@ -17,11 +13,15 @@ from langgraph.checkpoint.base import (
|
||||
create_checkpoint,
|
||||
empty_checkpoint,
|
||||
)
|
||||
from langgraph.checkpoint.serde.types import TASKS
|
||||
from psycopg import AsyncConnection
|
||||
from psycopg.rows import dict_row
|
||||
from psycopg_pool import AsyncConnectionPool
|
||||
|
||||
from langgraph.checkpoint.postgres.aio import (
|
||||
AsyncPostgresSaver,
|
||||
AsyncShallowPostgresSaver,
|
||||
)
|
||||
from langgraph.checkpoint.serde.types import TASKS
|
||||
from tests.conftest import DEFAULT_POSTGRES_URI
|
||||
|
||||
|
||||
@@ -187,13 +187,11 @@ def test_data():
|
||||
metadata_1: CheckpointMetadata = {
|
||||
"source": "input",
|
||||
"step": 2,
|
||||
"writes": {},
|
||||
"score": 1,
|
||||
}
|
||||
metadata_2: CheckpointMetadata = {
|
||||
"source": "loop",
|
||||
"step": 1,
|
||||
"writes": {"foo": "bar"},
|
||||
"score": None,
|
||||
}
|
||||
metadata_3: CheckpointMetadata = {}
|
||||
@@ -220,7 +218,6 @@ async def test_combined_metadata(saver_name: str, test_data) -> None:
|
||||
metadata: CheckpointMetadata = {
|
||||
"source": "loop",
|
||||
"step": 1,
|
||||
"writes": {"foo": "bar"},
|
||||
"score": None,
|
||||
}
|
||||
await saver.aput(config, chkpnt, metadata, {})
|
||||
@@ -246,7 +243,6 @@ async def test_asearch(saver_name: str, test_data) -> None:
|
||||
query_1 = {"source": "input"} # search by 1 key
|
||||
query_2 = {
|
||||
"step": 1,
|
||||
"writes": {"foo": "bar"},
|
||||
} # search by multiple keys
|
||||
query_3: dict[str, Any] = {} # search by no keys, return all checkpoints
|
||||
query_4 = {"source": "update", "step": 1} # no match
|
||||
@@ -344,3 +340,34 @@ async def test_pending_sends_migration(saver_name: str) -> None:
|
||||
TASKS: ["send-1", "send-2", "send-3"]
|
||||
}
|
||||
assert TASKS in search_results[0].checkpoint["channel_versions"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("saver_name", ["base", "pool", "pipe"])
|
||||
async def test_get_checkpoint_no_channel_values(
|
||||
monkeypatch, saver_name: str, test_data
|
||||
) -> None:
|
||||
"""Backwards compatibility test that verifies a checkpoint with no channel_values key can be retrieved without throwing an error."""
|
||||
async with _saver(saver_name) as saver:
|
||||
config = {
|
||||
"configurable": {
|
||||
"thread_id": "thread-2",
|
||||
"checkpoint_ns": "",
|
||||
"__super_private_key": "super_private_value",
|
||||
},
|
||||
"metadata": {"run_id": "my_run_id"},
|
||||
}
|
||||
chkpnt: Checkpoint = create_checkpoint(empty_checkpoint(), {}, 1)
|
||||
await saver.aput(config, chkpnt, {}, {})
|
||||
|
||||
load_checkpoint_tuple = saver._load_checkpoint_tuple
|
||||
|
||||
def patched_load_checkpoint_tuple(value):
|
||||
value["checkpoint"].pop("channel_values", None)
|
||||
return load_checkpoint_tuple(value)
|
||||
|
||||
monkeypatch.setattr(
|
||||
saver, "_load_checkpoint_tuple", patched_load_checkpoint_tuple
|
||||
)
|
||||
|
||||
checkpoint = await saver.aget_tuple(config)
|
||||
assert checkpoint.checkpoint["channel_values"] == {}
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import itertools
|
||||
import sys
|
||||
import uuid
|
||||
from collections.abc import AsyncIterator
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
@@ -12,8 +11,6 @@ from typing import Any
|
||||
|
||||
import pytest
|
||||
from langchain_core.embeddings import Embeddings
|
||||
from psycopg import AsyncConnection
|
||||
|
||||
from langgraph.store.base import (
|
||||
GetOp,
|
||||
Item,
|
||||
@@ -21,6 +18,8 @@ from langgraph.store.base import (
|
||||
PutOp,
|
||||
SearchOp,
|
||||
)
|
||||
from psycopg import AsyncConnection
|
||||
|
||||
from langgraph.store.postgres import AsyncPostgresStore
|
||||
from tests.conftest import (
|
||||
DEFAULT_URI,
|
||||
@@ -34,9 +33,6 @@ TTL_MINUTES = TTL_SECONDS / 60
|
||||
|
||||
@pytest.fixture(scope="function", params=["default", "pipe", "pool"])
|
||||
async def store(request) -> AsyncIterator[AsyncPostgresStore]:
|
||||
if sys.version_info < (3, 10):
|
||||
pytest.skip("Async Postgres tests require Python 3.10+")
|
||||
|
||||
database = f"test_{uuid.uuid4().hex[:16]}"
|
||||
uri_parts = DEFAULT_URI.split("/")
|
||||
uri_base = "/".join(uri_parts[:-1])
|
||||
@@ -358,8 +354,6 @@ async def _create_vector_store(
|
||||
text_fields: list[str] | None = None,
|
||||
) -> AsyncIterator[AsyncPostgresStore]:
|
||||
"""Create a store with vector search enabled."""
|
||||
if sys.version_info < (3, 10):
|
||||
pytest.skip("Async Postgres tests require Python 3.10+")
|
||||
|
||||
database = f"test_{uuid.uuid4().hex[:16]}"
|
||||
uri_parts = DEFAULT_URI.split("/")
|
||||
|
||||
@@ -9,8 +9,6 @@ from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from langchain_core.embeddings import Embeddings
|
||||
from psycopg import Connection
|
||||
|
||||
from langgraph.store.base import (
|
||||
GetOp,
|
||||
Item,
|
||||
@@ -19,6 +17,8 @@ from langgraph.store.base import (
|
||||
PutOp,
|
||||
SearchOp,
|
||||
)
|
||||
from psycopg import Connection
|
||||
|
||||
from langgraph.store.postgres import PostgresStore
|
||||
from tests.conftest import (
|
||||
DEFAULT_URI,
|
||||
@@ -754,7 +754,7 @@ def _cosine_similarity(X: list[float], Y: list[list[float]]) -> list[float]:
|
||||
|
||||
similarities = []
|
||||
for y in Y:
|
||||
dot_product = sum(a * b for a, b in zip(X, y))
|
||||
dot_product = sum(a * b for a, b in zip(X, y, strict=False))
|
||||
norm1 = sum(a * a for a in X) ** 0.5
|
||||
norm2 = sum(a * a for a in y) ** 0.5
|
||||
similarity = dot_product / (norm1 * norm2) if norm1 > 0 and norm2 > 0 else 0.0
|
||||
@@ -771,7 +771,7 @@ def _inner_product(X: list[float], Y: list[list[float]]) -> list[float]:
|
||||
|
||||
similarities = []
|
||||
for y in Y:
|
||||
similarity = sum(a * b for a, b in zip(X, y))
|
||||
similarity = sum(a * b for a, b in zip(X, y, strict=False))
|
||||
similarities.append(similarity)
|
||||
|
||||
return similarities
|
||||
@@ -785,7 +785,7 @@ def _neg_l2_distance(X: list[float], Y: list[list[float]]) -> list[float]:
|
||||
|
||||
similarities = []
|
||||
for y in Y:
|
||||
similarity = sum((a - b) ** 2 for a, b in zip(X, y)) ** 0.5
|
||||
similarity = sum((a - b) ** 2 for a, b in zip(X, y, strict=False)) ** 0.5
|
||||
similarities.append(-similarity)
|
||||
|
||||
return similarities
|
||||
@@ -861,3 +861,41 @@ def test_store_ttl(store):
|
||||
# Now has been (TTL_SECONDS-2)*2 > TTL_SECONDS + TTL_SECONDS/2
|
||||
res = store.search(ns, query="bar", refresh_ttl=False)
|
||||
assert len(res) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"vector_type,distance_type",
|
||||
[
|
||||
("vector", "cosine"),
|
||||
("vector", "inner_product"),
|
||||
("halfvec", "cosine"),
|
||||
("halfvec", "inner_product"),
|
||||
],
|
||||
)
|
||||
def test_non_ascii(
|
||||
request: Any,
|
||||
fake_embeddings: CharacterEmbeddings,
|
||||
vector_type: str,
|
||||
distance_type: str,
|
||||
) -> None:
|
||||
"""Test support for non-ascii characters"""
|
||||
with _create_vector_store(vector_type, distance_type, fake_embeddings) as store:
|
||||
store.put(("user_123", "memories"), "1", {"text": "这是中文"}) # Chinese
|
||||
store.put(
|
||||
("user_123", "memories"), "2", {"text": "これは日本語です"}
|
||||
) # Japanese
|
||||
store.put(("user_123", "memories"), "3", {"text": "이건 한국어야"}) # Korean
|
||||
store.put(("user_123", "memories"), "4", {"text": "Это русский"}) # Russian
|
||||
store.put(("user_123", "memories"), "5", {"text": "यह रूसी है"}) # Hindi
|
||||
|
||||
result1 = store.search(("user_123", "memories"), query="这是中文")
|
||||
result2 = store.search(("user_123", "memories"), query="これは日本語です")
|
||||
result3 = store.search(("user_123", "memories"), query="이건 한국어야")
|
||||
result4 = store.search(("user_123", "memories"), query="Это русский")
|
||||
result5 = store.search(("user_123", "memories"), query="यह रूसी है")
|
||||
|
||||
assert result1[0].key == "1"
|
||||
assert result2[0].key == "2"
|
||||
assert result3[0].key == "3"
|
||||
assert result4[0].key == "4"
|
||||
assert result5[0].key == "5"
|
||||
|
||||
@@ -7,10 +7,6 @@ from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from psycopg import Connection
|
||||
from psycopg.rows import dict_row
|
||||
from psycopg_pool import ConnectionPool
|
||||
|
||||
from langgraph.checkpoint.base import (
|
||||
EXCLUDED_METADATA_KEYS,
|
||||
Checkpoint,
|
||||
@@ -18,8 +14,12 @@ from langgraph.checkpoint.base import (
|
||||
create_checkpoint,
|
||||
empty_checkpoint,
|
||||
)
|
||||
from langgraph.checkpoint.postgres import PostgresSaver, ShallowPostgresSaver
|
||||
from langgraph.checkpoint.serde.types import TASKS
|
||||
from psycopg import Connection
|
||||
from psycopg.rows import dict_row
|
||||
from psycopg_pool import ConnectionPool
|
||||
|
||||
from langgraph.checkpoint.postgres import PostgresSaver, ShallowPostgresSaver
|
||||
from tests.conftest import DEFAULT_POSTGRES_URI
|
||||
|
||||
|
||||
@@ -169,13 +169,11 @@ def test_data():
|
||||
metadata_1: CheckpointMetadata = {
|
||||
"source": "input",
|
||||
"step": 2,
|
||||
"writes": {},
|
||||
"score": 1,
|
||||
}
|
||||
metadata_2: CheckpointMetadata = {
|
||||
"source": "loop",
|
||||
"step": 1,
|
||||
"writes": {"foo": "bar"},
|
||||
"score": None,
|
||||
}
|
||||
metadata_3: CheckpointMetadata = {}
|
||||
@@ -202,7 +200,6 @@ def test_combined_metadata(saver_name: str, test_data) -> None:
|
||||
metadata: CheckpointMetadata = {
|
||||
"source": "loop",
|
||||
"step": 1,
|
||||
"writes": {"foo": "bar"},
|
||||
"score": None,
|
||||
}
|
||||
saver.put(config, chkpnt, metadata, {})
|
||||
@@ -228,7 +225,6 @@ def test_search(saver_name: str, test_data) -> None:
|
||||
query_1 = {"source": "input"} # search by 1 key
|
||||
query_2 = {
|
||||
"step": 1,
|
||||
"writes": {"foo": "bar"},
|
||||
} # search by multiple keys
|
||||
query_3: dict[str, Any] = {} # search by no keys, return all checkpoints
|
||||
query_4 = {"source": "update", "step": 1} # no match
|
||||
@@ -332,3 +328,33 @@ def test_pending_sends_migration(saver_name: str) -> None:
|
||||
TASKS: ["send-1", "send-2", "send-3"]
|
||||
}
|
||||
assert TASKS in search_results[0].checkpoint["channel_versions"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("saver_name", ["base", "pool", "pipe"])
|
||||
def test_get_checkpoint_no_channel_values(
|
||||
monkeypatch, saver_name: str, test_data
|
||||
) -> None:
|
||||
"""Backwards compatibility test that verifies a checkpoint with no channel_values key can be retrieved without throwing an error."""
|
||||
with _saver(saver_name) as saver:
|
||||
config = {
|
||||
"configurable": {
|
||||
"thread_id": "thread-2",
|
||||
"checkpoint_ns": "",
|
||||
"__super_private_key": "super_private_value",
|
||||
},
|
||||
}
|
||||
chkpnt: Checkpoint = create_checkpoint(empty_checkpoint(), {}, 1)
|
||||
saver.put(config, chkpnt, {}, {})
|
||||
|
||||
load_checkpoint_tuple = saver._load_checkpoint_tuple
|
||||
|
||||
def patched_load_checkpoint_tuple(value):
|
||||
value["checkpoint"].pop("channel_values", None)
|
||||
return load_checkpoint_tuple(value)
|
||||
|
||||
monkeypatch.setattr(
|
||||
saver, "_load_checkpoint_tuple", patched_load_checkpoint_tuple
|
||||
)
|
||||
|
||||
checkpoint = saver.get_tuple(config)
|
||||
assert checkpoint.checkpoint["channel_values"] == {}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user