Merge branch 'sr/version-added-for-context' of https://github.com/langchain-ai/langgraph into sr/version-added-for-context

This commit is contained in:
Sydney Runkle
2025-07-24 17:31:25 -04:00
17 changed files with 771 additions and 24 deletions
@@ -4,6 +4,28 @@
---
## v0.2.102 (2025-07-24)
- Captured interrupt events in the wait method to preserve legacy behavior and stream updates by default.
- Added support for SDK structlog in the JavaScript environment, enhancing logging capabilities.
## v0.2.101 (2025-07-24)
- Used the correct metadata endpoint for self-hosted environments, resolving an access issue.
## v0.2.99 (2025-07-22)
- Improved license validation by adding an in-memory cache and handling Redis connection errors more effectively.
- Automatically remove agents from memory that are removed from `langgraph.json` to prevent persistence issues.
- Ensured the UI namespace for generated UI is a valid JavaScript property name to prevent errors.
- Raised a 422 error for improved request validation feedback.
## v0.2.98 (2025-07-19)
- Added langgraph node context for improved log filtering and trace visibility.
## v0.2.97 (2025-07-19)
- Fixed scheduling issue with ckpt ingestion worker that occurred on isolated background loops.
- Ensured queue worker starts only after all migrations have completed.
- Added more detailed error messages for thread state issues and improved response handling when state updates fail.
- Exposed interrupt ID while retrieving thread state for enhanced API response details.
## v0.2.96 (2025-07-17)
- Added a fallback mechanism for configurable header patterns to handle exclude/include settings more effectively.
@@ -119,6 +119,11 @@ These metrics are displayed as charts in the Control Plane UI.
### LangSmith Integration
A [LangSmith](https://docs.smith.langchain.com/) tracing project is automatically created for each deployment. The tracing project has the same name as the deployment. When creating a deployment, the `LANGCHAIN_TRACING` and `LANGSMITH_API_KEY`/`LANGCHAIN_API_KEY` environment variables do not need to be specified; they are set automatically by the control plane.
A [LangSmith](https://docs.smith.langchain.com/) tracing project and LangSmith API key are automatically created for each deployment. The deployment uses the API key to automatically send traces to LangSmith.
When a deployment is deleted, the traces and the tracing project are not deleted.
- The tracing project has the same name as the deployment.
- The API key has the description `LangGraph Platform: <deployment_name>`.
- The API key is never revealed and cannot be deleted manually.
- When creating a deployment, the `LANGCHAIN_TRACING` and `LANGSMITH_API_KEY`/`LANGCHAIN_API_KEY` environment variables do not need to be specified; they are set automatically by the control plane.
When a deployment is deleted, the traces and the tracing project are not deleted. However, the API will be deleted when the deployment is deleted.
+32 -4
View File
@@ -153,6 +153,12 @@ OPT_POSTGRES_URI = click.option(
help="Postgres URI to use for the database. Defaults to launching a local database",
)
OPT_API_VERSION = click.option(
"--api-version",
type=str,
help="API server version to use for the base image. If unspecified, the latest version will be used.",
)
@click.group()
@click.version_option(version=__version__, prog_name="LangGraph CLI")
@@ -170,6 +176,7 @@ def cli():
@OPT_DEBUGGER_BASE_URL
@OPT_WATCH
@OPT_POSTGRES_URI
@OPT_API_VERSION
@click.option(
"--image",
type=str,
@@ -203,6 +210,7 @@ def up(
debugger_port: Optional[int],
debugger_base_url: Optional[str],
postgres_uri: Optional[str],
api_version: Optional[str],
image: Optional[str],
base_image: Optional[str],
):
@@ -225,6 +233,7 @@ For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KE
debugger_port=debugger_port,
debugger_base_url=debugger_base_url,
postgres_uri=postgres_uri,
api_version=api_version,
image=image,
base_image=base_image,
)
@@ -290,6 +299,7 @@ def _build(
config: pathlib.Path,
config_json: dict,
base_image: Optional[str],
api_version: Optional[str],
pull: bool,
tag: str,
passthrough: Sequence[str] = (),
@@ -300,7 +310,7 @@ def _build(
subp_exec(
"docker",
"pull",
langgraph_cli.config.docker_tag(config_json, base_image),
langgraph_cli.config.docker_tag(config_json, base_image, api_version),
verbose=True,
)
)
@@ -314,7 +324,7 @@ def _build(
]
# apply config
stdin, additional_contexts = langgraph_cli.config.config_to_docker(
config, config_json, base_image
config, config_json, base_image, api_version
)
# add additional_contexts
if additional_contexts:
@@ -355,6 +365,7 @@ def _build(
"\n\n \b\nExamples:\n --base-image langchain/langgraph-server:0.2.18 # Pin to a specific patch version"
"\n --base-image langchain/langgraph-server:0.2 # Pin to a minor version (Python)",
)
@OPT_API_VERSION
@click.argument("docker_build_args", nargs=-1, type=click.UNPROCESSED)
@cli.command(
help="📦 Build LangGraph API server Docker image.",
@@ -367,6 +378,7 @@ def build(
config: pathlib.Path,
docker_build_args: Sequence[str],
base_image: Optional[str],
api_version: Optional[str],
pull: bool,
tag: str,
):
@@ -376,7 +388,15 @@ def build(
config_json = langgraph_cli.config.validate_config_file(config)
warn_non_wolfi_distro(config_json)
_build(
runner, set, config, config_json, base_image, pull, tag, docker_build_args
runner,
set,
config,
config_json,
base_image,
api_version,
pull,
tag,
docker_build_args,
)
@@ -456,12 +476,14 @@ tests
"\n\n \b\nExamples:\n --base-image langchain/langgraph-server:0.2.18 # Pin to a specific patch version"
"\n --base-image langchain/langgraph-server:0.2 # Pin to a minor version (Python)",
)
@OPT_API_VERSION
@log_command
def dockerfile(
save_path: str,
config: pathlib.Path,
add_docker_compose: bool,
base_image: Optional[str] = None,
api_version: Optional[str] = None,
) -> None:
save_path = pathlib.Path(save_path).absolute()
secho(f"🔍 Validating configuration at path: {config}", fg="yellow")
@@ -474,6 +496,7 @@ def dockerfile(
config,
config_json,
base_image=base_image,
api_version=api_version,
)
with open(str(save_path), "w", encoding="utf-8") as f:
f.write(dockerfile)
@@ -739,6 +762,7 @@ def prepare_args_and_stdin(
debugger_port: Optional[int] = None,
debugger_base_url: Optional[str] = None,
postgres_uri: Optional[str] = None,
api_version: Optional[str] = None,
# Like "my-tag" (if you already built it locally)
image: Optional[str] = None,
# Like "langchain/langgraphjs-api" or "langchain/langgraph-api
@@ -754,6 +778,7 @@ def prepare_args_and_stdin(
postgres_uri=postgres_uri,
image=image, # Pass image to compose YAML generator
base_image=base_image,
api_version=api_version,
)
args = [
"--project-directory",
@@ -769,6 +794,7 @@ def prepare_args_and_stdin(
config,
watch=watch,
base_image=langgraph_cli.config.default_base_image(config),
api_version=api_version,
image=image,
)
return args, stdin
@@ -787,6 +813,7 @@ def prepare(
debugger_port: Optional[int] = None,
debugger_base_url: Optional[str] = None,
postgres_uri: Optional[str] = None,
api_version: Optional[str] = None,
image: Optional[str] = None,
base_image: Optional[str] = None,
) -> tuple[list[str], str]:
@@ -799,7 +826,7 @@ def prepare(
subp_exec(
"docker",
"pull",
langgraph_cli.config.docker_tag(config_json, base_image),
langgraph_cli.config.docker_tag(config_json, base_image, api_version),
verbose=verbose,
)
)
@@ -814,6 +841,7 @@ def prepare(
debugger_port=debugger_port,
debugger_base_url=debugger_base_url or f"http://127.0.0.1:{port}",
postgres_uri=postgres_uri,
api_version=api_version,
image=image,
base_image=base_image,
)
+25 -7
View File
@@ -1213,6 +1213,7 @@ def python_config_to_docker(
config_path: pathlib.Path,
config: Config,
base_image: str,
api_version: Optional[str] = None,
) -> tuple[str, dict[str, str]]:
"""Generate a Dockerfile from the configuration."""
pip_installer = config.get("pip_installer", "auto")
@@ -1360,7 +1361,7 @@ ADD {relpath} /deps/{name}
"# -- End of JS dependencies install --",
]
)
image_str = docker_tag(config, base_image)
image_str = docker_tag(config, base_image, api_version)
docker_file_contents = [
f"FROM {image_str}",
"",
@@ -1402,10 +1403,11 @@ def node_config_to_docker(
config_path: pathlib.Path,
config: Config,
base_image: str,
api_version: Optional[str] = None,
) -> tuple[str, dict[str, str]]:
faux_path = f"/deps/{config_path.parent.name}"
install_cmd = _get_node_pm_install_cmd(config_path, config)
image_str = docker_tag(config, base_image)
image_str = docker_tag(config, base_image, api_version)
env_vars: list[str] = []
@@ -1461,6 +1463,7 @@ def default_base_image(config: Config) -> str:
def docker_tag(
config: Config,
base_image: Optional[str] = None,
api_version: Optional[str] = None,
) -> str:
base_image = base_image or default_base_image(config)
@@ -1473,28 +1476,43 @@ def docker_tag(
if "/langgraph-server" in base_image:
return f"{base_image}-py{config['python_version']}"
# Build the standard tag format
language, version = None, None
if config.get("node_version") and not config.get("python_version"):
return f"{base_image}:{config['node_version']}{distro_tag}"
return f"{base_image}:{config['python_version']}{distro_tag}"
language, version = "node", config["node_version"]
else:
language, version = "py", config["python_version"]
version_distro_tag = f"{version}{distro_tag}"
# Prepend API version if provided
if api_version:
full_tag = f"{api_version}-{language}{version_distro_tag}"
else:
full_tag = version_distro_tag
return f"{base_image}:{full_tag}"
def config_to_docker(
config_path: pathlib.Path,
config: Config,
base_image: Optional[str] = None,
api_version: Optional[str] = None,
) -> tuple[str, dict[str, str]]:
base_image = base_image or default_base_image(config)
if config.get("node_version") and not config.get("python_version"):
return node_config_to_docker(config_path, config, base_image)
return node_config_to_docker(config_path, config, base_image, api_version)
return python_config_to_docker(config_path, config, base_image)
return python_config_to_docker(config_path, config, base_image, api_version)
def config_to_compose(
config_path: pathlib.Path,
config: Config,
base_image: Optional[str] = None,
api_version: Optional[str] = None,
image: Optional[str] = None,
watch: bool = False,
) -> str:
@@ -1531,7 +1549,7 @@ def config_to_compose(
else:
dockerfile, additional_contexts = config_to_docker(
config_path, config, base_image
config_path, config, base_image, api_version
)
additional_contexts_str = "\n".join(
+4
View File
@@ -147,6 +147,8 @@ def compose_as_dict(
image: Optional[str] = None,
# Base image to use for the LangGraph API server
base_image: Optional[str] = None,
# API version of the base image
api_version: Optional[str] = None,
) -> dict:
"""Create a docker compose file as a dictionary in YML style."""
if postgres_uri is None:
@@ -252,6 +254,7 @@ def compose(
postgres_uri: Optional[str] = None,
image: Optional[str] = None,
base_image: Optional[str] = None,
api_version: Optional[str] = None,
) -> str:
"""Create a docker compose file as a string."""
compose_content = compose_as_dict(
@@ -262,6 +265,7 @@ def compose(
postgres_uri=postgres_uri,
image=image,
base_image=base_image,
api_version=api_version,
)
compose_str = dict_to_yaml(compose_content)
return compose_str
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph-cli"
version = "0.3.5"
version = "0.3.6"
description = "CLI for interacting with LangGraph API"
authors = []
requires-python = ">=3.9"
+245
View File
@@ -574,3 +574,248 @@ def test_build_generate_proper_build_context():
assert len(build_contexts) == 2, (
f"Expected 2 build contexts, but found {len(build_contexts)}"
)
def test_dockerfile_command_with_api_version() -> None:
"""Test the 'dockerfile' command with --api-version flag."""
runner = CliRunner()
config_content = {
"python_version": "3.11",
"graphs": {"agent": "agent.py:graph"},
"dependencies": ["."],
}
with temporary_config_folder(config_content) as temp_dir:
save_path = temp_dir / "Dockerfile"
agent_path = temp_dir / "agent.py"
agent_path.touch()
result = runner.invoke(
cli,
[
"dockerfile",
str(save_path),
"--config",
str(temp_dir / "config.json"),
"--api-version",
"0.2.74",
],
)
# Assert command was successful
assert result.exit_code == 0, result.output
assert "✅ Created: Dockerfile" in result.output
# Check if Dockerfile was created and contains correct FROM line
assert save_path.exists()
with open(save_path) as f:
dockerfile = f.read()
assert "FROM langchain/langgraph-api:0.2.74-py3.11" in dockerfile
def test_dockerfile_command_with_api_version_and_base_image() -> None:
"""Test the 'dockerfile' command with both --api-version and --base-image flags."""
runner = CliRunner()
config_content = {
"python_version": "3.12",
"graphs": {"agent": "agent.py:graph"},
"dependencies": ["."],
"image_distro": "wolfi",
}
with temporary_config_folder(config_content) as temp_dir:
save_path = temp_dir / "Dockerfile"
agent_path = temp_dir / "agent.py"
agent_path.touch()
result = runner.invoke(
cli,
[
"dockerfile",
str(save_path),
"--config",
str(temp_dir / "config.json"),
"--api-version",
"1.0.0",
"--base-image",
"my-registry/custom-api",
],
)
# Assert command was successful
assert result.exit_code == 0, result.output
assert "✅ Created: Dockerfile" in result.output
# Check if Dockerfile was created and contains correct FROM line
assert save_path.exists()
with open(save_path) as f:
dockerfile = f.read()
assert "FROM my-registry/custom-api:1.0.0-py3.12-wolfi" in dockerfile
def test_dockerfile_command_with_api_version_nodejs() -> None:
"""Test the 'dockerfile' command with --api-version flag for Node.js config."""
runner = CliRunner()
config_content = {
"node_version": "20",
"graphs": {"agent": "agent.js:graph"},
}
with temporary_config_folder(config_content) as temp_dir:
save_path = temp_dir / "Dockerfile"
agent_path = temp_dir / "agent.js"
agent_path.touch()
result = runner.invoke(
cli,
[
"dockerfile",
str(save_path),
"--config",
str(temp_dir / "config.json"),
"--api-version",
"0.2.74",
],
)
# Assert command was successful
assert result.exit_code == 0, result.output
assert "✅ Created: Dockerfile" in result.output
# Check if Dockerfile was created and contains correct FROM line
assert save_path.exists()
with open(save_path) as f:
dockerfile = f.read()
assert "FROM langchain/langgraphjs-api:0.2.74-node20" in dockerfile
def test_build_command_with_api_version() -> None:
"""Test the 'build' command with --api-version flag."""
runner = CliRunner()
config_content = {
"python_version": "3.11",
"graphs": {"agent": "agent.py:graph"},
"dependencies": ["."],
"image_distro": "wolfi", # Use wolfi to avoid warning messages
}
with temporary_config_folder(config_content) as temp_dir:
agent_path = temp_dir / "agent.py"
agent_path.touch()
# Mock docker command since we don't want to actually build
with runner.isolated_filesystem():
result = runner.invoke(
cli,
[
"build",
"--tag",
"test-image",
"--config",
str(temp_dir / "config.json"),
"--api-version",
"0.2.74",
"--no-pull", # Avoid pulling non-existent images
],
catch_exceptions=True,
)
# Check that the build command is called with the correct tag
# The output should contain the docker build command with the api_version tag
assert "langchain/langgraph-api:0.2.74-py3.11-wolfi" in result.output
def test_build_command_with_api_version_and_base_image() -> None:
"""Test the 'build' command with both --api-version and --base-image flags."""
runner = CliRunner()
config_content = {
"python_version": "3.12",
"graphs": {"agent": "agent.py:graph"},
"dependencies": ["."],
"image_distro": "wolfi", # Use wolfi to avoid warning messages
}
with temporary_config_folder(config_content) as temp_dir:
agent_path = temp_dir / "agent.py"
agent_path.touch()
# Mock docker command since we don't want to actually build
with runner.isolated_filesystem():
result = runner.invoke(
cli,
[
"build",
"--tag",
"test-image",
"--config",
str(temp_dir / "config.json"),
"--api-version",
"1.0.0",
"--base-image",
"my-registry/custom-api",
"--no-pull", # Avoid pulling non-existent images
],
catch_exceptions=True,
)
# Check that the build command includes the api_version
assert "my-registry/custom-api:1.0.0-py3.12-wolfi" in result.output
def test_prepare_args_and_stdin_with_api_version() -> None:
"""Test prepare_args_and_stdin function with api_version parameter."""
config_path = pathlib.Path(__file__).parent / "langgraph.json"
config = validate_config(
Config(dependencies=["."], graphs={"agent": "agent.py:graph"})
)
port = 8000
api_version = "0.2.74"
actual_args, actual_stdin = prepare_args_and_stdin(
capabilities=DEFAULT_DOCKER_CAPABILITIES,
config_path=config_path,
config=config,
docker_compose=None,
port=port,
watch=False,
api_version=api_version,
)
expected_args = [
"--project-directory",
str(pathlib.Path(__file__).parent.absolute()),
"-f",
"-",
]
# Check that the args are correct
assert actual_args == expected_args
# Check that the stdin contains the correct FROM line with api_version
assert "FROM langchain/langgraph-api:0.2.74-py3.11" in actual_stdin
def test_prepare_args_and_stdin_with_api_version_and_image() -> None:
"""Test prepare_args_and_stdin function with both api_version and image parameters."""
config_path = pathlib.Path(__file__).parent / "langgraph.json"
config = validate_config(
Config(dependencies=["."], graphs={"agent": "agent.py:graph"})
)
port = 8000
api_version = "0.2.74"
image = "my-custom-image:latest"
actual_args, actual_stdin = prepare_args_and_stdin(
capabilities=DEFAULT_DOCKER_CAPABILITIES,
config_path=config_path,
config=config,
docker_compose=None,
port=port,
watch=False,
api_version=api_version,
image=image,
)
# When image is provided, api_version should be ignored for the image
# but the stdin should not contain a build section (since image is provided)
assert "pull_policy: build" not in actual_stdin
+192
View File
@@ -1337,3 +1337,195 @@ def test_docker_tag_different_node_versions_with_distro():
)
tag = docker_tag(config)
assert tag == expected_tag, f"Failed for Node.js {node_version}"
def test_docker_tag_with_api_version():
"""Test docker_tag function with api_version parameter."""
# Test 1: Python config with api_version and default distro
config = validate_config(
{
"python_version": "3.11",
"dependencies": ["."],
"graphs": {"agent": "./agent.py:graph"},
}
)
tag = docker_tag(config, api_version="0.2.74")
assert tag == "langchain/langgraph-api:0.2.74-py3.11"
# Test 2: Python config with api_version and wolfi distro
config = validate_config(
{
"python_version": "3.12",
"dependencies": ["."],
"graphs": {"agent": "./agent.py:graph"},
"image_distro": "wolfi",
}
)
tag = docker_tag(config, api_version="0.2.74")
assert tag == "langchain/langgraph-api:0.2.74-py3.12-wolfi"
# Test 3: Node.js config with api_version and default distro
config = validate_config(
{
"node_version": "20",
"graphs": {"agent": "./agent.js:graph"},
}
)
tag = docker_tag(config, api_version="0.2.74")
assert tag == "langchain/langgraphjs-api:0.2.74-node20"
# Test 4: Node.js config with api_version and wolfi distro
config = validate_config(
{
"node_version": "20",
"graphs": {"agent": "./agent.js:graph"},
"image_distro": "wolfi",
}
)
tag = docker_tag(config, api_version="0.2.74")
assert tag == "langchain/langgraphjs-api:0.2.74-node20-wolfi"
# Test 5: Custom base image with api_version
config = validate_config(
{
"python_version": "3.11",
"dependencies": ["."],
"graphs": {"agent": "./agent.py:graph"},
"base_image": "my-registry/custom-image",
}
)
tag = docker_tag(config, base_image="my-registry/custom-image", api_version="1.0.0")
assert tag == "my-registry/custom-image:1.0.0-py3.11"
# Test 6: api_version with different Python versions
for python_version in ["3.11", "3.12", "3.13"]:
config = validate_config(
{
"python_version": python_version,
"dependencies": ["."],
"graphs": {"agent": "./agent.py:graph"},
}
)
tag = docker_tag(config, api_version="0.2.74")
assert tag == f"langchain/langgraph-api:0.2.74-py{python_version}"
# Test 7: Without api_version should work as before
config = validate_config(
{
"python_version": "3.11",
"dependencies": ["."],
"graphs": {"agent": "./agent.py:graph"},
}
)
tag = docker_tag(config)
assert tag == "langchain/langgraph-api:3.11"
# Test 8: api_version with multiplatform config (should default to Python)
config = validate_config(
{
"python_version": "3.11",
"node_version": "20",
"dependencies": ["."],
"graphs": {"python": "./agent.py:graph", "js": "./agent.js:graph"},
}
)
tag = docker_tag(config, api_version="0.2.74")
assert tag == "langchain/langgraph-api:0.2.74-py3.11"
# Test 9: api_version with _INTERNAL_docker_tag should ignore api_version
config = validate_config(
{
"python_version": "3.11",
"dependencies": ["."],
"graphs": {"agent": "./agent.py:graph"},
"_INTERNAL_docker_tag": "internal-tag",
}
)
tag = docker_tag(config, api_version="0.2.74")
assert tag == "langchain/langgraph-api:internal-tag"
# Test 10: api_version with langgraph-server base image should follow special format
config = validate_config(
{
"python_version": "3.11",
"dependencies": ["."],
"graphs": {"agent": "./agent.py:graph"},
}
)
tag = docker_tag(
config, base_image="langchain/langgraph-server:0.2", api_version="0.2.74"
)
assert tag == "langchain/langgraph-server:0.2-py3.11"
def test_config_to_docker_with_api_version():
"""Test config_to_docker function with api_version parameter."""
# Test Python config with api_version
graphs = {"agent": "./agent.py:graph"}
actual_docker_stdin, additional_contexts = config_to_docker(
PATH_TO_CONFIG,
validate_config({"dependencies": ["."], "graphs": graphs}),
"langchain/langgraph-api",
api_version="0.2.74",
)
# Check that the FROM line uses the api_version
lines = actual_docker_stdin.split("\n")
from_line = lines[0]
assert from_line == "FROM langchain/langgraph-api:0.2.74-py3.11"
# Test Node.js config with api_version
graphs = {"agent": "./agent.js:graph"}
actual_docker_stdin, additional_contexts = config_to_docker(
PATH_TO_CONFIG,
validate_config({"node_version": "20", "graphs": graphs}),
"langchain/langgraphjs-api",
api_version="0.2.74",
)
# Check that the FROM line uses the api_version
lines = actual_docker_stdin.split("\n")
from_line = lines[0]
assert from_line == "FROM langchain/langgraphjs-api:0.2.74-node20"
def test_config_to_compose_with_api_version():
"""Test config_to_compose function with api_version parameter."""
# Test Python config with api_version
config = validate_config(
{
"dependencies": ["."],
"graphs": {"agent": "./agent.py:graph"},
}
)
actual_compose_str = config_to_compose(
PATH_TO_CONFIG,
config,
"langchain/langgraph-api",
api_version="0.2.74",
)
# Check that the compose file includes the correct FROM line with api_version
assert "FROM langchain/langgraph-api:0.2.74-py3.11" in actual_compose_str
# Test Node.js config with api_version
config = validate_config(
{
"node_version": "20",
"graphs": {"agent": "./agent.js:graph"},
}
)
actual_compose_str = config_to_compose(
PATH_TO_CONFIG,
config,
"langchain/langgraphjs-api",
api_version="0.2.74",
)
# Check that the compose file includes the correct FROM line with api_version
assert "FROM langchain/langgraphjs-api:0.2.74-node20" in actual_compose_str
+217
View File
@@ -146,3 +146,220 @@ services:
REDIS_URI: redis://langgraph-redis:6379
POSTGRES_URI: {DEFAULT_POSTGRES_URI}"""
assert clean_empty_lines(actual_compose_str) == expected_compose_str
def test_compose_with_api_version():
"""Test compose function with api_version parameter."""
port = 8123
api_version = "0.2.74"
actual_compose_str = compose(
DEFAULT_DOCKER_CAPABILITIES, port=port, api_version=api_version
)
# The compose function should generate a compose file that doesn't directly
# reference the api_version, since it's handled in the docker tag creation
# when building the image. The compose function mainly sets up services.
expected_compose_str = f"""volumes:
langgraph-data:
driver: local
services:
langgraph-redis:
image: redis:6
healthcheck:
test: redis-cli ping
interval: 5s
timeout: 1s
retries: 5
langgraph-postgres:
image: pgvector/pgvector:pg16
ports:
- "5433:5432"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
command:
- postgres
- -c
- shared_preload_libraries=vector
volumes:
- langgraph-data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U postgres
start_period: 10s
timeout: 1s
retries: 5
interval: 5s
langgraph-api:
ports:
- "{port}:8000"
depends_on:
langgraph-redis:
condition: service_healthy
langgraph-postgres:
condition: service_healthy
environment:
REDIS_URI: redis://langgraph-redis:6379
POSTGRES_URI: {DEFAULT_POSTGRES_URI}"""
assert clean_empty_lines(actual_compose_str) == expected_compose_str
def test_compose_with_api_version_and_base_image():
"""Test compose function with both api_version and base_image parameters."""
port = 8123
api_version = "1.0.0"
base_image = "my-registry/custom-api"
actual_compose_str = compose(
DEFAULT_DOCKER_CAPABILITIES,
port=port,
api_version=api_version,
base_image=base_image,
)
# Similar to the previous test - the compose function doesn't directly embed
# the api_version or base_image into the compose file since those are handled
# during the docker build process
expected_compose_str = f"""volumes:
langgraph-data:
driver: local
services:
langgraph-redis:
image: redis:6
healthcheck:
test: redis-cli ping
interval: 5s
timeout: 1s
retries: 5
langgraph-postgres:
image: pgvector/pgvector:pg16
ports:
- "5433:5432"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
command:
- postgres
- -c
- shared_preload_libraries=vector
volumes:
- langgraph-data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U postgres
start_period: 10s
timeout: 1s
retries: 5
interval: 5s
langgraph-api:
ports:
- "{port}:8000"
depends_on:
langgraph-redis:
condition: service_healthy
langgraph-postgres:
condition: service_healthy
environment:
REDIS_URI: redis://langgraph-redis:6379
POSTGRES_URI: {DEFAULT_POSTGRES_URI}"""
assert clean_empty_lines(actual_compose_str) == expected_compose_str
def test_compose_with_api_version_and_custom_postgres():
"""Test compose function with api_version and custom postgres URI."""
port = 8123
api_version = "0.2.74"
custom_postgres_uri = "postgresql://user:pass@external-db:5432/mydb"
actual_compose_str = compose(
DEFAULT_DOCKER_CAPABILITIES,
port=port,
api_version=api_version,
postgres_uri=custom_postgres_uri,
)
expected_compose_str = f"""services:
langgraph-redis:
image: redis:6
healthcheck:
test: redis-cli ping
interval: 5s
timeout: 1s
retries: 5
langgraph-api:
ports:
- "{port}:8000"
depends_on:
langgraph-redis:
condition: service_healthy
environment:
REDIS_URI: redis://langgraph-redis:6379
POSTGRES_URI: {custom_postgres_uri}"""
assert clean_empty_lines(actual_compose_str) == expected_compose_str
def test_compose_with_api_version_and_debugger():
"""Test compose function with api_version and debugger port."""
port = 8123
debugger_port = 8001
api_version = "0.2.74"
actual_compose_str = compose(
DEFAULT_DOCKER_CAPABILITIES,
port=port,
api_version=api_version,
debugger_port=debugger_port,
)
expected_compose_str = f"""volumes:
langgraph-data:
driver: local
services:
langgraph-redis:
image: redis:6
healthcheck:
test: redis-cli ping
interval: 5s
timeout: 1s
retries: 5
langgraph-postgres:
image: pgvector/pgvector:pg16
ports:
- "5433:5432"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
command:
- postgres
- -c
- shared_preload_libraries=vector
volumes:
- langgraph-data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U postgres
start_period: 10s
timeout: 1s
retries: 5
interval: 5s
langgraph-debugger:
image: langchain/langgraph-debugger
restart: on-failure
depends_on:
langgraph-postgres:
condition: service_healthy
ports:
- "{debugger_port}:3968"
langgraph-api:
ports:
- "{port}:8000"
depends_on:
langgraph-redis:
condition: service_healthy
langgraph-postgres:
condition: service_healthy
environment:
REDIS_URI: redis://langgraph-redis:6379
POSTGRES_URI: {DEFAULT_POSTGRES_URI}"""
assert clean_empty_lines(actual_compose_str) == expected_compose_str
+1 -1
View File
@@ -531,7 +531,7 @@ wheels = [
[[package]]
name = "langgraph-cli"
version = "0.3.5"
version = "0.3.6"
source = { editable = "." }
dependencies = [
{ name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+16
View File
@@ -8,6 +8,7 @@ from typing import (
cast,
)
import langsmith as ls
from langchain_core.runnables import RunnableConfig
from langchain_core.runnables.graph import (
Edge as DrawableEdge,
@@ -118,6 +119,7 @@ class RemoteGraph(PregelProtocol):
sync_client: SyncLangGraphClient | None = None,
config: RunnableConfig | None = None,
name: str | None = None,
distributed_tracing: bool = False,
):
"""Specify `url`, `api_key`, and/or `headers` to create default sync and async clients.
@@ -136,6 +138,7 @@ class RemoteGraph(PregelProtocol):
name: Human-readable name to attach to the RemoteGraph instance.
This is useful for adding `RemoteGraph` as a subgraph via `graph.add_node(remote_graph)`.
If not provided, defaults to the assistant ID.
distributed_tracing: Whether to enable sending LangSmith distributed tracing headers.
"""
self.assistant_id = assistant_id
if name is None:
@@ -143,6 +146,7 @@ class RemoteGraph(PregelProtocol):
else:
self.name = name
self.config = config
self.distributed_tracing = distributed_tracing
if client is None and url is not None:
client = get_client(url=url, api_key=api_key, headers=headers)
@@ -672,6 +676,7 @@ class RemoteGraph(PregelProtocol):
interrupt_after=interrupt_after,
stream_subgraphs=subgraphs or stream is not None,
if_not_exists="create",
headers=self._merge_tracing_headers(kwargs.pop("headers", None) or {}),
**kwargs,
):
# split mode and ns
@@ -774,6 +779,7 @@ class RemoteGraph(PregelProtocol):
interrupt_after=interrupt_after,
stream_subgraphs=subgraphs or stream is not None,
if_not_exists="create",
headers=self._merge_tracing_headers(kwargs.pop("headers", None) or {}),
**kwargs,
):
# split mode and ns
@@ -909,3 +915,13 @@ class RemoteGraph(PregelProtocol):
return chunk
except UnboundLocalError:
return None
def _merge_tracing_headers(self, headers: dict[str, str]) -> dict[str, str]:
if rt := ls.get_current_run_tree():
tracing_headers = rt.to_headers()
baggage = tracing_headers.pop("baggage")
if "baggage" in headers:
baggage = headers["baggage"] + "," + baggage
tracing_headers["baggage"] = baggage
headers.update(tracing_headers)
return headers
+1 -1
View File
@@ -15,7 +15,7 @@ dependencies = [
"langchain-core>=0.1",
"langgraph-checkpoint>=2.1.0,<3.0.0",
"langgraph-sdk>=0.2.0,<0.3.0",
"langgraph-prebuilt>=0.5.0,<0.6.0",
"langgraph-prebuilt>=0.6.0,<0.7.0",
"xxhash>=3.5.0",
"pydantic>=2.7.4",
]
+3 -3
View File
@@ -1394,7 +1394,7 @@ dev = [
[[package]]
name = "langgraph-cli"
version = "0.3.5"
version = "0.3.6"
source = { editable = "../cli" }
dependencies = [
{ name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
@@ -1433,7 +1433,7 @@ dev = [
[[package]]
name = "langgraph-prebuilt"
version = "0.5.2"
version = "0.6.0"
source = { editable = "../prebuilt" }
dependencies = [
{ name = "langchain-core" },
@@ -1481,7 +1481,7 @@ wheels = [
[[package]]
name = "langgraph-sdk"
version = "0.2.0a1"
version = "0.2.0"
source = { editable = "../sdk-py" }
dependencies = [
{ name = "httpx" },
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph-prebuilt"
version = "0.5.2"
version = "0.6.0"
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
authors = []
requires-python = ">=3.9"
+2 -2
View File
@@ -460,7 +460,7 @@ dev = [
[[package]]
name = "langgraph-prebuilt"
version = "0.5.2"
version = "0.6.0"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },
@@ -507,7 +507,7 @@ dev = [
[[package]]
name = "langgraph-sdk"
version = "0.2.0a1"
version = "0.2.0"
source = { editable = "../sdk-py" }
dependencies = [
{ name = "httpx" },
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph-sdk"
version = "0.2.0a1"
version = "0.2.0"
description = "SDK for interacting with LangGraph API"
authors = []
requires-python = ">=3.9"
+1 -1
View File
@@ -119,7 +119,7 @@ wheels = [
[[package]]
name = "langgraph-sdk"
version = "0.2.0a1"
version = "0.2.0"
source = { editable = "." }
dependencies = [
{ name = "httpx" },