Compare commits

..
Author SHA1 Message Date
William FHandGitHub acae5e23b0 chore(sdk-py): cron tz support (#7108) 2026-03-10 17:38:02 -07:00
Sydney RunkleandGitHub 14ce607111 chore: remove md notes (#7103) 2026-03-10 08:44:55 -04:00
Sydney RunkleandGitHub 3330ccdea4 release(langgraph): 1.1 (#7102)
exciting!

relnotes preview:

# LangGraph 1.1.0 Release Notes

## Type-Safe Streaming & Invoke

LangGraph 1.1 introduces `version="v2"` — a new opt-in streaming format
that brings full type safety to `stream()`, `astream()`, `invoke()`, and
`ainvoke()`.

### What's changing

**v1 (default, unchanged):** `stream()` yields bare tuples like
`(stream_mode, data)` or just `data`. `invoke()` returns a plain `dict`.
Interrupts are mixed into the output dict under `"__interrupt__"`.

**v2 (opt-in):** `stream()` yields strongly-typed `StreamPart` dicts
with `type`, `ns`, `data`, and (for values) `interrupts` fields.
`invoke()` returns a `GraphOutput` object with `.value` and
`.interrupts` attributes. When your state schema is a Pydantic model or
dataclass, outputs are automatically coerced to the correct type.

### `invoke()` / `ainvoke()` with `version="v2"`

```python
from langgraph.types import GraphOutput

result = graph.invoke({"input": "hello"}, version="v2")

# result is a GraphOutput, not a dict
assert isinstance(result, GraphOutput)
result.value       # your output — dict, Pydantic model, or dataclass
result.interrupts  # tuple[Interrupt, ...], empty if none occurred
```

With a non-`"values"` stream mode, `invoke(..., stream_mode="updates",
version="v2")` returns `list[StreamPart]` instead of `list[tuple]`.

### `stream()` / `astream()` with `version="v2"`

```python
for part in graph.stream({"input": "hello"}, version="v2"):
    if part["type"] == "values":
        part["data"]        # OutputT — full state
        part["interrupts"]  # tuple[Interrupt, ...]
    elif part["type"] == "updates":
        part["data"]        # dict[str, Any]
    elif part["type"] == "messages":
        part["data"]        # tuple[BaseMessage, dict]
    elif part["type"] == "custom":
        part["data"]        # Any
    elif part["type"] == "tasks":
        part["data"]        # TaskPayload | TaskResultPayload
    elif part["type"] == "debug":
        part["data"]        # DebugPayload
```

Each stream mode has its own `TypedDict` — `ValuesStreamPart`,
`UpdatesStreamPart`, `MessagesStreamPart`, `CustomStreamPart`,
`CheckpointStreamPart`, `TasksStreamPart`, `DebugStreamPart` — all
importable from `langgraph.types`. The union type `StreamPart` is a
discriminated union on `part["type"]`, enabling full type narrowing in
editors and type checkers.

### Pydantic & dataclass output coercion

When your graph's state schema is a Pydantic model or dataclass,
`version="v2"` automatically coerces outputs to the declared type:

```python
from pydantic import BaseModel

class MyState(BaseModel):
    answer: str
    count: int

graph = StateGraph(MyState)
# ... build graph ...
compiled = graph.compile()

result = compiled.invoke({"answer": "", "count": 0}, version="v2")
assert isinstance(result.value, MyState)  # not a dict!
```

### Backward compatibility

- **Default is still `version="v1"`** — existing code works without
changes.
- To make migration easier, `GraphOutput` supports old-style best-effort
access to graph values and interrupts. Dict-style access
(`result["key"]`, `"key" in result`, `result["__interrupt__"]`) still
works and delegates to `result.value` / `result.interrupts` under the
hood. However, this is **deprecated** and emits a
`LangGraphDeprecatedSinceV11` warning. It will be removed in v3.0 —
migrate to `result.value` and `result.interrupts` at your convenience.

```python
result = graph.invoke({"input": "hello"}, version="v2")

# Old style — still works, but deprecated
result["input"]          # delegates to result.value["input"]
result["__interrupt__"]  # delegates to result.interrupts
"input" in result        # delegates to "input" in result.value

# New style — preferred
result.value["input"]
result.interrupts
```

## Migration Guide

1. **No action required** — `version="v1"` remains the default. All
existing code continues to work.
2. **Adopt v2 incrementally** — Add `version="v2"` to individual
`invoke()`/`stream()` calls to get typed outputs.
3. **Use typed imports** — Import `GraphOutput`, `StreamPart`, and
individual part types from `langgraph.types` for type-safe code.
2026-03-10 12:41:30 +00:00
Quanzheng LongandGitHub 4ef61690c6 feat(cli): add distributed runtime support to langgraph cli (#7096) 2026-03-09 20:43:45 -07:00
16 changed files with 598 additions and 71 deletions
+18 -48
View File
@@ -1,65 +1,35 @@
# LangGraph
> LangGraph is a framework for building stateful, multi-actor applications with LLMs. Gain control with LangGraph to design agents that reliably handle complex tasks.
LangGraph documentation has moved to docs.langchain.com.
LangGraph documentation has moved to https://docs.langchain.com/oss/python/langgraph/overview.
## Overview
## Docs
- [LangGraph overview](https://docs.langchain.com/oss/python/langgraph/overview): Gain control with LangGraph to design agents that reliably handle complex tasks
- [Quickstart](https://docs.langchain.com/oss/python/langgraph/quickstart): Build your first LangGraph agent
- [Install LangGraph](https://docs.langchain.com/oss/python/langgraph/install): Install the LangGraph package
- [Thinking in LangGraph](https://docs.langchain.com/oss/python/langgraph/thinking-in-langgraph): Learn how to think about building agents with LangGraph
- [Workflows and agents](https://docs.langchain.com/oss/python/langgraph/workflows-agents): Understand the difference between workflows and agents in LangGraph
## APIs
- [Graph API overview](https://docs.langchain.com/oss/python/langgraph/graph-api): Learn about the Graph API for building stateful applications
- [Use the graph API](https://docs.langchain.com/oss/python/langgraph/use-graph-api): Build agents using the Graph API
- [Functional API overview](https://docs.langchain.com/oss/python/langgraph/functional-api): Learn about the Functional API for building agents
- [Use the functional API](https://docs.langchain.com/oss/python/langgraph/use-functional-api): Build agents using the Functional API
- [Choosing between Graph and Functional APIs](https://docs.langchain.com/oss/python/langgraph/choosing-apis): Decide which API to use for your use case
- [LangGraph Overview](https://docs.langchain.com/oss/python/langgraph/overview): Introduction to LangGraph, a library for building stateful, multi-actor applications with LLMs.
- [Why LangGraph?](https://docs.langchain.com/oss/python/langgraph/why-langgraph): Motivation for LangGraph and its key features.
## Core Concepts
- [Persistence](https://docs.langchain.com/oss/python/langgraph/persistence): Save and restore graph state
- [Memory](https://docs.langchain.com/oss/python/langgraph/add-memory): Add memory to your agents
- [Memory overview](https://docs.langchain.com/oss/python/langgraph/memory): Understand memory concepts in LangGraph
- [Streaming](https://docs.langchain.com/oss/python/langgraph/streaming): Stream outputs from your graphs
- [Interrupts](https://docs.langchain.com/oss/python/langgraph/interrupts): Pause and resume graph execution for human-in-the-loop workflows
- [Subgraphs](https://docs.langchain.com/oss/python/langgraph/use-subgraphs): Compose graphs with subgraphs
- [Durable execution](https://docs.langchain.com/oss/python/langgraph/durable-execution): Build resilient agents with durable execution
- [Use time-travel](https://docs.langchain.com/oss/python/langgraph/use-time-travel): Debug and replay agent execution
- [Graph API](https://docs.langchain.com/oss/python/langgraph/graph-api): Learn how to define state, create nodes, and connect them with edges.
- [Streaming](https://docs.langchain.com/oss/python/langgraph/streaming): Stream outputs from your graph for better UX.
- [Persistence](https://docs.langchain.com/oss/python/langgraph/persistence): Add memory and checkpointing to your graphs.
- [Add Memory](https://docs.langchain.com/oss/python/langgraph/add-memory): Implement short-term and long-term memory.
- [Workflows & Agents](https://docs.langchain.com/oss/python/langgraph/workflows-agents): Build agents and workflows with LangGraph.
## Architecture
## How-To Guides
- [Application structure](https://docs.langchain.com/oss/python/langgraph/application-structure): Structure your LangGraph applications
- [LangGraph runtime](https://docs.langchain.com/oss/python/langgraph/pregel): Understand the LangGraph runtime
## Deployment & Operations
- [LangSmith Deployment](https://docs.langchain.com/oss/python/langgraph/deploy): Deploy LangGraph applications to LangSmith
- [Run a local server](https://docs.langchain.com/oss/python/langgraph/local-server): Run a LangGraph server locally
- [LangSmith Observability](https://docs.langchain.com/oss/python/langgraph/observability): Monitor and trace LangGraph applications
- [LangSmith Studio](https://docs.langchain.com/oss/python/langgraph/studio): Debug and visualize LangGraph applications
- [Agent Chat UI](https://docs.langchain.com/oss/python/langgraph/ui): Build chat interfaces for your agents
- [Test](https://docs.langchain.com/oss/python/langgraph/test): Test your LangGraph applications
- [Use Subgraphs](https://docs.langchain.com/oss/python/langgraph/use-subgraphs): Compose graphs using subgraphs.
- [Observability](https://docs.langchain.com/oss/python/langgraph/observability): Add tracing and debugging to your graphs.
- [Common Errors](https://docs.langchain.com/oss/python/langgraph/common-errors): Troubleshoot common LangGraph errors.
## Tutorials
- [Build a custom RAG agent with LangGraph](https://docs.langchain.com/oss/python/langgraph/agentic-rag): Build a retrieval-augmented generation agent
- [Build a custom SQL agent](https://docs.langchain.com/oss/python/langgraph/sql-agent): Build an agent that queries databases
- [Agentic RAG](https://docs.langchain.com/oss/python/langgraph/agentic-rag): Build an agentic RAG system with LangGraph.
- [SQL Agent](https://docs.langchain.com/oss/python/langgraph/sql-agent): Create a SQL agent with LangGraph.
## Reference
- [Case studies](https://docs.langchain.com/oss/python/langgraph/case-studies): Real-world LangGraph implementations
- [Changelog](https://docs.langchain.com/oss/python/langgraph/changelog-py): Python package changelog
- [API Reference](https://reference.langchain.com/python/langgraph/): Complete API documentation for LangGraph.
## Errors
## LangGraph Platform
- [GRAPH_RECURSION_LIMIT](https://docs.langchain.com/oss/python/langgraph/errors/GRAPH_RECURSION_LIMIT): Graph exceeded maximum recursion depth
- [INVALID_CHAT_HISTORY](https://docs.langchain.com/oss/python/langgraph/errors/INVALID_CHAT_HISTORY): Invalid chat history format
- [INVALID_CONCURRENT_GRAPH_UPDATE](https://docs.langchain.com/oss/python/langgraph/errors/INVALID_CONCURRENT_GRAPH_UPDATE): Concurrent graph update conflict
- [INVALID_GRAPH_NODE_RETURN_VALUE](https://docs.langchain.com/oss/python/langgraph/errors/INVALID_GRAPH_NODE_RETURN_VALUE): Invalid return value from graph node
- [MISSING_CHECKPOINTER](https://docs.langchain.com/oss/python/langgraph/errors/MISSING_CHECKPOINTER): Checkpointer required but not configured
- [MULTIPLE_SUBGRAPHS](https://docs.langchain.com/oss/python/langgraph/errors/MULTIPLE_SUBGRAPHS): Multiple subgraphs error
For deploying LangGraph applications in production, see the [LangSmith documentation](https://docs.langchain.com/langsmith/agent-server).
+47 -3
View File
@@ -287,6 +287,13 @@ OPT_API_VERSION = click.option(
help="API server version to use for the base image. If unspecified, the latest version will be used.",
)
OPT_ENGINE_RUNTIME_MODE = click.option(
"--engine-runtime-mode",
type=click.Choice(["combined_queue_worker", "distributed"]),
default="combined_queue_worker",
help="Runtime mode. 'distributed' uses separate executor and orchestrator containers.",
)
@click.group()
@click.version_option(version=__version__, prog_name="LangGraph CLI")
@@ -305,6 +312,7 @@ def cli():
@OPT_WATCH
@OPT_POSTGRES_URI
@OPT_API_VERSION
@OPT_ENGINE_RUNTIME_MODE
@click.option(
"--image",
type=str,
@@ -339,6 +347,7 @@ def up(
debugger_base_url: str | None,
postgres_uri: str | None,
api_version: str | None,
engine_runtime_mode: str,
image: str | None,
base_image: str | None,
):
@@ -362,6 +371,7 @@ For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KE
debugger_base_url=debugger_base_url,
postgres_uri=postgres_uri,
api_version=api_version,
engine_runtime_mode=engine_runtime_mode,
image=image,
base_image=base_image,
)
@@ -518,6 +528,7 @@ def _build(
"\n --base-image langchain/langgraph-server:0.2 # Pin to a minor version (Python)",
)
@OPT_API_VERSION
@OPT_ENGINE_RUNTIME_MODE
@click.option(
"--install-command",
help="Custom install command to run from the build context root. If not provided, auto-detects based on package manager files.",
@@ -539,6 +550,7 @@ def build(
docker_build_args: Sequence[str],
base_image: str | None,
api_version: str | None,
engine_runtime_mode: str,
pull: bool,
tag: str,
install_command: str | None,
@@ -561,12 +573,17 @@ def build(
raise click.UsageError("Docker not installed") from None
config_json = langgraph_cli.config.validate_config_file(config)
warn_non_wolfi_distro(config_json)
effective_base_image = base_image
if engine_runtime_mode == "distributed" and not base_image:
effective_base_image = langgraph_cli.config.default_base_image(
config_json, engine_runtime_mode=engine_runtime_mode
)
_build(
runner,
set,
config,
config_json,
base_image,
effective_base_image,
api_version,
pull,
tag,
@@ -1136,6 +1153,7 @@ tests
"\n --base-image langchain/langgraph-server:0.2 # Pin to a minor version (Python)",
)
@OPT_API_VERSION
@OPT_ENGINE_RUNTIME_MODE
@log_command
def dockerfile(
save_path: str,
@@ -1143,6 +1161,7 @@ def dockerfile(
add_docker_compose: bool,
base_image: str | None = None,
api_version: str | None = None,
engine_runtime_mode: str = "combined_queue_worker",
) -> None:
save_path = pathlib.Path(save_path).absolute()
secho(f"🔍 Validating configuration at path: {config}", fg="yellow")
@@ -1150,11 +1169,17 @@ def dockerfile(
warn_non_wolfi_distro(config_json)
secho("✅ Configuration validated!", fg="green")
effective_base_image = base_image
if engine_runtime_mode == "distributed" and not base_image:
effective_base_image = langgraph_cli.config.default_base_image(
config_json, engine_runtime_mode=engine_runtime_mode
)
secho(f"📝 Generating Dockerfile at {save_path}", fg="yellow")
dockerfile, additional_contexts = langgraph_cli.config.config_to_docker(
config_path=config,
config=config_json,
base_image=base_image,
base_image=effective_base_image,
api_version=api_version,
)
with open(str(save_path), "w", encoding="utf-8") as f:
@@ -1425,6 +1450,7 @@ def prepare_args_and_stdin(
debugger_base_url: str | None = None,
postgres_uri: str | None = None,
api_version: str | None = None,
engine_runtime_mode: str = "combined_queue_worker",
# Like "my-tag" (if you already built it locally)
image: str | None = None,
# Like "langchain/langgraphjs-api" or "langchain/langgraph-api
@@ -1438,9 +1464,10 @@ def prepare_args_and_stdin(
debugger_port=debugger_port,
debugger_base_url=debugger_base_url,
postgres_uri=postgres_uri,
image=image, # Pass image to compose YAML generator
image=image,
base_image=base_image,
api_version=api_version,
engine_runtime_mode=engine_runtime_mode,
)
args = [
"--project-directory",
@@ -1458,6 +1485,7 @@ def prepare_args_and_stdin(
base_image=langgraph_cli.config.default_base_image(config),
api_version=api_version,
image=image,
engine_runtime_mode=engine_runtime_mode,
)
return args, stdin
@@ -1476,6 +1504,7 @@ def prepare(
debugger_base_url: str | None = None,
postgres_uri: str | None = None,
api_version: str | None = None,
engine_runtime_mode: str = "combined_queue_worker",
image: str | None = None,
base_image: str | None = None,
) -> tuple[list[str], str]:
@@ -1492,6 +1521,20 @@ def prepare(
verbose=verbose,
)
)
if engine_runtime_mode == "distributed":
executor_base = langgraph_cli.config.default_base_image(
config_json, engine_runtime_mode="distributed"
)
runner.run(
subp_exec(
"docker",
"pull",
langgraph_cli.config.docker_tag(
config_json, executor_base, api_version
),
verbose=verbose,
)
)
args, stdin = prepare_args_and_stdin(
capabilities=capabilities,
@@ -1504,6 +1547,7 @@ def prepare(
debugger_base_url=debugger_base_url or f"http://127.0.0.1:{port}",
postgres_uri=postgres_uri,
api_version=api_version,
engine_runtime_mode=engine_runtime_mode,
image=image,
base_image=base_image,
)
+70 -2
View File
@@ -1,3 +1,4 @@
import copy
import json
import os
import pathlib
@@ -1232,11 +1233,15 @@ def node_config_to_docker(
return os.linesep.join(docker_file_contents), {}
def default_base_image(config: Config) -> str:
def default_base_image(
config: Config, engine_runtime_mode: str = "combined_queue_worker"
) -> str:
if config.get("base_image"):
return config["base_image"]
if config.get("node_version") and not config.get("python_version"):
return "langchain/langgraphjs-api"
if engine_runtime_mode == "distributed":
return "langchain/langgraph-executor"
return "langchain/langgraph-api"
@@ -1329,6 +1334,7 @@ def config_to_compose(
api_version: str | None = None,
image: str | None = None,
watch: bool = False,
engine_runtime_mode: str = "combined_queue_worker",
) -> str:
base_image = base_image or default_base_image(config)
@@ -1362,6 +1368,11 @@ def config_to_compose(
"""
else:
# Save a pristine copy before config_to_docker mutates graph paths
config_snapshot = (
copy.deepcopy(config) if engine_runtime_mode == "distributed" else None
)
dockerfile, additional_contexts = config_to_docker(
config_path=config_path,
config=config,
@@ -1379,7 +1390,7 @@ def config_to_compose(
additional_contexts:
{additional_contexts_str}"""
return f"""
result = f"""
{textwrap.indent(env_vars_str, " ")}
{env_file_str}
pull_policy: build
@@ -1389,3 +1400,60 @@ def config_to_compose(
{textwrap.indent(dockerfile, " ")}
{watch_str}
"""
if engine_runtime_mode == "distributed":
executor_base_image = default_base_image(
config_snapshot, engine_runtime_mode="distributed"
)
executor_dockerfile, executor_additional_contexts = config_to_docker(
config_path=config_path,
config=config_snapshot,
base_image=executor_base_image,
api_version=api_version,
escape_variables=True,
)
executor_additional_contexts_str = "\n".join(
f" - {name}: {path}"
for name, path in executor_additional_contexts.items()
)
if executor_additional_contexts_str:
executor_additional_contexts_str = f"""
additional_contexts:
{executor_additional_contexts_str}"""
postgres_uri = "postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable"
result += f""" langgraph-orchestrator:
image: langchain/langgraph-orchestrator-licensed:latest
depends_on:
langgraph-api:
condition: service_healthy
langgraph-postgres:
condition: service_healthy
environment:
DATABASE_URI: {postgres_uri}
EXECUTOR_TARGET: langgraph-executor:8188
{env_file_str}
langgraph-executor:
depends_on:
langgraph-postgres:
condition: service_healthy
langgraph-api:
condition: service_healthy
entrypoint: ["sh", "/storage/executor_entrypoint.sh"]
environment:
DATABASE_URI: {postgres_uri}
REDIS_URI: redis://langgraph-redis:6379
EXECUTOR_GRPC_PORT: "8188"
ENGINE_GRPC_ADDRESS: "langgraph-orchestrator:50054"
LSD_GRPC_SERVER_ADDRESS: "localhost:50050"
LANGGRAPH_HTTP: ""
{env_file_str}
pull_policy: build
build:
context: .{executor_additional_contexts_str}
dockerfile_inline: |
{textwrap.indent(executor_dockerfile, " ")}
"""
return result
+11 -4
View File
@@ -149,6 +149,7 @@ def compose_as_dict(
base_image: str | None = None,
# API version of the base image
api_version: str | None = None,
engine_runtime_mode: str = "combined_queue_worker",
) -> dict:
"""Create a docker compose file as a dictionary in YML style."""
if postgres_uri is None:
@@ -207,15 +208,19 @@ def compose_as_dict(
)["langgraph-debugger"]
# Add langgraph-api service
api_environment = {
"REDIS_URI": "redis://langgraph-redis:6379",
"POSTGRES_URI": postgres_uri,
}
if engine_runtime_mode == "distributed":
api_environment["N_JOBS_PER_WORKER"] = '"0"'
services["langgraph-api"] = {
"ports": [f'"{port}:8000"'],
"depends_on": {
"langgraph-redis": {"condition": "service_healthy"},
},
"environment": {
"REDIS_URI": "redis://langgraph-redis:6379",
"POSTGRES_URI": postgres_uri,
},
"environment": api_environment,
}
if image:
services["langgraph-api"]["image"] = image
@@ -255,6 +260,7 @@ def compose(
image: str | None = None,
base_image: str | None = None,
api_version: str | None = None,
engine_runtime_mode: str = "combined_queue_worker",
) -> str:
"""Create a docker compose file as a string."""
compose_content = compose_as_dict(
@@ -266,6 +272,7 @@ def compose(
image=image,
base_image=base_image,
api_version=api_version,
engine_runtime_mode=engine_runtime_mode,
)
compose_str = dict_to_yaml(compose_content)
return compose_str
+136
View File
@@ -822,3 +822,139 @@ def test_prepare_args_and_stdin_with_api_version_and_image() -> None:
# 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
def test_dockerfile_command_distributed_mode() -> None:
"""Test the 'dockerfile' command with --engine-runtime-mode distributed."""
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"),
"--engine-runtime-mode",
"distributed",
],
)
assert result.exit_code == 0, result.output
assert "✅ Created: Dockerfile" in result.output
assert save_path.exists()
with open(save_path) as f:
dockerfile = f.read()
assert "FROM langchain/langgraph-executor:3.11" in dockerfile
def test_dockerfile_command_combined_mode() -> None:
"""Test the 'dockerfile' command with --engine-runtime-mode combined_queue_worker."""
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"),
"--engine-runtime-mode",
"combined_queue_worker",
],
)
assert result.exit_code == 0, result.output
assert save_path.exists()
with open(save_path) as f:
dockerfile = f.read()
assert "FROM langchain/langgraph-api:3.11" in dockerfile
def test_dockerfile_command_distributed_with_explicit_base_image() -> None:
"""Test distributed mode with explicit --base-image overrides executor default."""
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"),
"--engine-runtime-mode",
"distributed",
"--base-image",
"my-custom-executor:latest",
],
)
assert result.exit_code == 0, result.output
assert save_path.exists()
with open(save_path) as f:
dockerfile = f.read()
assert "FROM my-custom-executor:latest" in dockerfile
def test_prepare_args_and_stdin_distributed_mode() -> None:
"""Test prepare_args_and_stdin with distributed mode includes all services."""
config_path = pathlib.Path(__file__).parent / "langgraph.json"
config = validate_config(
Config(dependencies=["."], graphs={"agent": "agent.py:graph"})
)
port = 8000
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,
engine_runtime_mode="distributed",
)
# API service should use langgraph-api base image
assert "FROM langchain/langgraph-api:" in actual_stdin
# Distributed mode sets N_JOBS_PER_WORKER=0 on the API service
assert 'N_JOBS_PER_WORKER: "0"' in actual_stdin
# Orchestrator service present
assert "langgraph-orchestrator:" in actual_stdin
# Executor service present with correct base image
assert "langgraph-executor:" in actual_stdin
assert "FROM langchain/langgraph-executor:" in actual_stdin
assert "executor_entrypoint.sh" in actual_stdin
+189
View File
@@ -13,6 +13,7 @@ from langgraph_cli.config import (
_get_pip_cleanup_lines,
config_to_compose,
config_to_docker,
default_base_image,
docker_tag,
has_disallowed_build_command_content,
validate_config,
@@ -1695,6 +1696,194 @@ def test_config_to_compose_with_api_version():
assert "FROM langchain/langgraphjs-api:0.2.74-node20" in actual_compose_str
def test_default_base_image_combined_mode():
"""Test default_base_image returns langgraph-api for combined_queue_worker mode."""
config = validate_config(
{
"dependencies": ["."],
"graphs": {"agent": "./agent.py:graph"},
}
)
assert default_base_image(config) == "langchain/langgraph-api"
assert (
default_base_image(config, engine_runtime_mode="combined_queue_worker")
== "langchain/langgraph-api"
)
def test_default_base_image_distributed_mode():
"""Test default_base_image returns langgraph-executor for distributed mode."""
config = validate_config(
{
"dependencies": ["."],
"graphs": {"agent": "./agent.py:graph"},
}
)
assert (
default_base_image(config, engine_runtime_mode="distributed")
== "langchain/langgraph-executor"
)
def test_default_base_image_distributed_with_explicit_base():
"""Test default_base_image returns explicit base_image even in distributed mode."""
config = validate_config(
{
"dependencies": ["."],
"graphs": {"agent": "./agent.py:graph"},
"base_image": "my-custom-image:latest",
}
)
assert (
default_base_image(config, engine_runtime_mode="distributed")
== "my-custom-image:latest"
)
def test_default_base_image_nodejs():
"""Test default_base_image returns langgraphjs-api for Node.js config."""
config = validate_config(
{
"node_version": "20",
"graphs": {"agent": "./agent.js:graph"},
}
)
assert default_base_image(config) == "langchain/langgraphjs-api"
def test_config_to_docker_executor_base_image():
"""Test config_to_docker with executor base image for distributed mode."""
graphs = {"agent": "./agent.py:graph"}
config = validate_config({"dependencies": ["."], "graphs": graphs})
actual_docker_stdin, _ = config_to_docker(
PATH_TO_CONFIG,
config,
base_image="langchain/langgraph-executor",
)
assert "FROM langchain/langgraph-executor:3.11" in actual_docker_stdin
assert "LANGSERVE_GRAPHS=" in actual_docker_stdin
def test_config_to_compose_distributed_mode():
"""Test config_to_compose with engine_runtime_mode='distributed'."""
graphs = {"agent": "./agent.py:graph"}
actual_compose_stdin = config_to_compose(
PATH_TO_CONFIG,
validate_config({"dependencies": ["."], "graphs": graphs}),
"langchain/langgraph-api",
engine_runtime_mode="distributed",
)
# API service uses langchain/langgraph-api base image
assert "FROM langchain/langgraph-api:3.11" in actual_compose_stdin
# Orchestrator service is present
assert "langgraph-orchestrator:" in actual_compose_stdin
assert "EXECUTOR_TARGET: langgraph-executor:8188" in actual_compose_stdin
# Executor service is present with correct base image
assert "langgraph-executor:" in actual_compose_stdin
assert "FROM langchain/langgraph-executor:3.11" in actual_compose_stdin
assert 'entrypoint: ["sh", "/storage/executor_entrypoint.sh"]' in actual_compose_stdin
# Executor has required environment variables
assert "EXECUTOR_GRPC_PORT:" in actual_compose_stdin
assert "ENGINE_GRPC_ADDRESS:" in actual_compose_stdin
assert "LSD_GRPC_SERVER_ADDRESS:" in actual_compose_stdin
assert 'LANGGRAPH_HTTP: ""' in actual_compose_stdin
assert "REDIS_URI: redis://langgraph-redis:6379" in actual_compose_stdin
def test_config_to_compose_distributed_mode_with_env_file():
"""Test config_to_compose distributed mode propagates env_file to all services."""
graphs = {"agent": "./agent.py:graph"}
actual_compose_stdin = config_to_compose(
PATH_TO_CONFIG,
validate_config({"dependencies": ["."], "graphs": graphs, "env": ".env"}),
"langchain/langgraph-api",
engine_runtime_mode="distributed",
)
# env_file should appear multiple times: API, orchestrator, executor
env_file_count = actual_compose_stdin.count("env_file: .env")
assert env_file_count == 3, (
f"Expected env_file to appear 3 times (api, orchestrator, executor), "
f"got {env_file_count}"
)
def test_config_to_compose_distributed_mode_generates_two_dockerfiles():
"""Test that distributed mode generates separate Dockerfiles for API and executor."""
graphs = {"agent": "./agent.py:graph"}
actual_compose_stdin = config_to_compose(
PATH_TO_CONFIG,
validate_config({"dependencies": ["."], "graphs": graphs}),
"langchain/langgraph-api",
engine_runtime_mode="distributed",
)
# Should contain two different FROM lines
from_lines = [
line.strip()
for line in actual_compose_stdin.splitlines()
if line.strip().startswith("FROM ")
]
assert len(from_lines) == 2
assert "FROM langchain/langgraph-api:3.11" in from_lines[0]
assert "FROM langchain/langgraph-executor:3.11" in from_lines[1]
def test_config_to_compose_combined_mode_no_orchestrator():
"""Test that combined_queue_worker mode does NOT generate orchestrator/executor."""
graphs = {"agent": "./agent.py:graph"}
actual_compose_stdin = config_to_compose(
PATH_TO_CONFIG,
validate_config({"dependencies": ["."], "graphs": graphs}),
"langchain/langgraph-api",
engine_runtime_mode="combined_queue_worker",
)
assert "langgraph-orchestrator:" not in actual_compose_stdin
assert "langgraph-executor:" not in actual_compose_stdin
def test_config_to_compose_default_mode_no_orchestrator():
"""Test that default mode (no engine_runtime_mode) has no orchestrator/executor."""
graphs = {"agent": "./agent.py:graph"}
actual_compose_stdin = config_to_compose(
PATH_TO_CONFIG,
validate_config({"dependencies": ["."], "graphs": graphs}),
"langchain/langgraph-api",
)
assert "langgraph-orchestrator:" not in actual_compose_stdin
assert "langgraph-executor:" not in actual_compose_stdin
def test_config_to_compose_distributed_executor_gets_correct_paths():
"""Test that executor Dockerfile gets correct host paths despite API Dockerfile
mutation. This validates the deep copy fix in config_to_compose -- without it,
the executor's config_to_docker call would see already-mutated container paths
from the API's config_to_docker call, causing FileNotFoundError."""
graphs = {"agent": "./agent.py:graph"}
actual_compose_stdin = config_to_compose(
PATH_TO_CONFIG,
validate_config({"dependencies": ["."], "graphs": graphs}),
"langchain/langgraph-api",
engine_runtime_mode="distributed",
)
# Both API and executor Dockerfiles should contain valid LANGSERVE_GRAPHS
# referencing container paths (not host paths). If the deep copy was missing,
# the executor Dockerfile would fail to generate or have wrong paths.
from_lines = [
line.strip()
for line in actual_compose_stdin.splitlines()
if "LANGSERVE_GRAPHS=" in line.strip()
]
assert len(from_lines) == 2, (
f"Expected 2 LANGSERVE_GRAPHS lines (api + executor), got {len(from_lines)}"
)
class TestHasDisallowedBuildCommandContent:
"""Tests for has_disallowed_build_command_content."""
+55
View File
@@ -368,6 +368,61 @@ services:
assert clean_empty_lines(actual_compose_str) == expected_compose_str
def test_compose_distributed_mode_with_custom_db():
"""Test compose with engine_runtime_mode='distributed' adds N_JOBS_PER_WORKER=0."""
port = 8123
custom_postgres_uri = "custom_postgres_uri"
actual_compose_str = compose(
DEFAULT_DOCKER_CAPABILITIES,
port=port,
postgres_uri=custom_postgres_uri,
engine_runtime_mode="distributed",
)
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}
N_JOBS_PER_WORKER: "0\""""
assert clean_empty_lines(actual_compose_str) == expected_compose_str
def test_compose_distributed_mode_with_default_db():
"""Test compose distributed mode with default DB includes N_JOBS_PER_WORKER=0."""
port = 8123
actual_compose_str = compose(
DEFAULT_DOCKER_CAPABILITIES,
port=port,
engine_runtime_mode="distributed",
)
assert 'N_JOBS_PER_WORKER: "0"' in actual_compose_str
assert "langgraph-postgres:" in actual_compose_str
assert "langgraph-redis:" in actual_compose_str
def test_compose_combined_mode_has_no_n_jobs():
"""Test compose with default combined_queue_worker mode does NOT set N_JOBS_PER_WORKER."""
port = 8123
actual_compose_str = compose(
DEFAULT_DOCKER_CAPABILITIES,
port=port,
engine_runtime_mode="combined_queue_worker",
)
assert "N_JOBS_PER_WORKER" not in actual_compose_str
@pytest.mark.parametrize(
"input_str,expected",
[
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph"
version = "1.0.10"
version = "1.1.0"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
requires-python = ">=3.10"
+1 -1
View File
@@ -1367,7 +1367,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.0.10"
version = "1.1.0"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },
+1 -1
View File
@@ -268,7 +268,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.0.10"
version = "1.1.0"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },
+1 -1
View File
@@ -3,6 +3,6 @@ from langgraph_sdk.client import get_client, get_sync_client
from langgraph_sdk.encryption import Encryption
from langgraph_sdk.encryption.types import EncryptionContext
__version__ = "0.3.10"
__version__ = "0.3.11"
__all__ = ["Auth", "Encryption", "EncryptionContext", "get_client", "get_sync_client"]
+14 -4
View File
@@ -4,10 +4,11 @@ from __future__ import annotations
import warnings
from collections.abc import Mapping, Sequence
from datetime import datetime
from datetime import datetime, tzinfo
from typing import Any
from langgraph_sdk._async.http import HttpClient
from langgraph_sdk._shared.utilities import _resolve_timezone
from langgraph_sdk.schema import (
All,
Config,
@@ -70,6 +71,7 @@ class CronClient:
multitask_strategy: str | None = None,
end_time: datetime | None = None,
enabled: bool | None = None,
timezone: str | tzinfo | None = None,
stream_mode: StreamMode | Sequence[StreamMode] | None = None,
stream_subgraphs: bool | None = None,
stream_resumable: bool | None = None,
@@ -84,7 +86,7 @@ class CronClient:
assistant_id: The assistant ID or graph name to use for the cron job.
If using graph name, will default to first assistant created from that graph.
schedule: The cron schedule to execute this job on.
Schedules are interpreted in UTC.
Schedules are interpreted in UTC unless a timezone is specified.
input: The input to the graph.
metadata: Metadata to assign to the cron job runs.
config: The configuration for the assistant.
@@ -100,6 +102,7 @@ class CronClient:
Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'.
end_time: The time to stop running the cron job. If not provided, the cron job will run indefinitely.
enabled: Whether the cron job is enabled or not.
timezone: IANA timezone for the cron schedule. Accepts a string (e.g. 'America/New_York') or a ``datetime.tzinfo`` instance (e.g. ``ZoneInfo("America/New_York")``).
stream_mode: The stream mode(s) to use.
stream_subgraphs: Whether to stream output from subgraphs.
stream_resumable: Whether to persist the stream chunks in order to resume the stream later.
@@ -152,6 +155,7 @@ class CronClient:
"webhook": webhook,
"end_time": end_time.isoformat() if end_time else None,
"enabled": enabled,
"timezone": _resolve_timezone(timezone),
"stream_mode": stream_mode,
"stream_subgraphs": stream_subgraphs,
"stream_resumable": stream_resumable,
@@ -184,6 +188,7 @@ class CronClient:
multitask_strategy: str | None = None,
end_time: datetime | None = None,
enabled: bool | None = None,
timezone: str | tzinfo | None = None,
stream_mode: StreamMode | Sequence[StreamMode] | None = None,
stream_subgraphs: bool | None = None,
stream_resumable: bool | None = None,
@@ -197,7 +202,7 @@ class CronClient:
assistant_id: The assistant ID or graph name to use for the cron job.
If using graph name, will default to first assistant created from that graph.
schedule: The cron schedule to execute this job on.
Schedules are interpreted in UTC.
Schedules are interpreted in UTC unless a timezone is specified.
input: The input to the graph.
metadata: Metadata to assign to the cron job runs.
config: The configuration for the assistant.
@@ -215,6 +220,7 @@ class CronClient:
Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'.
end_time: The time to stop running the cron job. If not provided, the cron job will run indefinitely.
enabled: Whether the cron job is enabled or not.
timezone: IANA timezone for the cron schedule. Accepts a string (e.g. 'America/New_York') or a ``datetime.tzinfo`` instance (e.g. ``ZoneInfo("America/New_York")``).
stream_mode: The stream mode(s) to use.
stream_subgraphs: Whether to stream output from subgraphs.
stream_resumable: Whether to persist the stream chunks in order to resume the stream later.
@@ -268,6 +274,7 @@ class CronClient:
"on_run_completed": on_run_completed,
"end_time": end_time.isoformat() if end_time else None,
"enabled": enabled,
"timezone": _resolve_timezone(timezone),
"stream_mode": stream_mode,
"stream_subgraphs": stream_subgraphs,
"stream_resumable": stream_resumable,
@@ -324,6 +331,7 @@ class CronClient:
interrupt_after: All | list[str] | None = None,
on_run_completed: OnCompletionBehavior | None = None,
enabled: bool | None = None,
timezone: str | tzinfo | None = None,
stream_mode: StreamMode | Sequence[StreamMode] | None = None,
stream_subgraphs: bool | None = None,
stream_resumable: bool | None = None,
@@ -336,7 +344,7 @@ class CronClient:
Args:
cron_id: The cron ID to update.
schedule: The cron schedule to execute this job on.
Schedules are interpreted in UTC.
Schedules are interpreted in UTC unless a timezone is specified.
end_time: The end date to stop running the cron.
input: The input to the graph.
metadata: Metadata to assign to the cron job runs.
@@ -350,6 +358,7 @@ class CronClient:
after execution. 'keep' creates a new thread for each execution but does not
clean them up.
enabled: Enable or disable the cron job.
timezone: IANA timezone for the cron schedule. Accepts a string (e.g. 'America/New_York') or a ``datetime.tzinfo`` instance (e.g. ``ZoneInfo("America/New_York")``).
stream_mode: The stream mode(s) to use.
stream_subgraphs: Whether to stream output from subgraphs.
stream_resumable: Whether to persist the stream chunks in order to resume the stream later.
@@ -384,6 +393,7 @@ class CronClient:
"interrupt_after": interrupt_after,
"on_run_completed": on_run_completed,
"enabled": enabled,
"timezone": _resolve_timezone(timezone),
"stream_mode": stream_mode,
"stream_subgraphs": stream_subgraphs,
"stream_resumable": stream_resumable,
+34 -1
View File
@@ -6,13 +6,17 @@ import functools
import os
import re
from collections.abc import Mapping
from typing import Any, cast
from datetime import tzinfo
from typing import TYPE_CHECKING, Any, cast
import httpx
import langgraph_sdk
from langgraph_sdk.schema import RunCreateMetadata
if TYPE_CHECKING:
from zoneinfo import ZoneInfo
RESERVED_HEADERS = ("x-api-key",)
NOT_PROVIDED = cast(None, object())
@@ -125,6 +129,35 @@ def _sse_to_v2_dict(event: str, data: Any) -> dict[str, Any] | None:
return result
def _resolve_timezone(tz: str | tzinfo | ZoneInfo | None) -> str | None:
"""Convert a timezone argument to an IANA timezone string.
Accepts:
- A string (returned as-is, assumed to be an IANA timezone name)
- A ``datetime.tzinfo`` instance (e.g. ``zoneinfo.ZoneInfo("America/New_York")``,
``datetime.timezone.utc``). The ``key`` attribute is used if available,
otherwise ``tzname(None)`` is used.
- ``None`` (returned as ``None``)
"""
if tz is None or isinstance(tz, str):
return tz
if isinstance(tz, tzinfo):
# ZoneInfo objects have a .key attribute with the IANA name
if hasattr(tz, "key"):
return tz.key # type: ignore[union-attr]
# Fall back to tzname for fixed-offset timezones like datetime.timezone.utc
name = tz.tzname(None)
if name is not None:
return name
raise ValueError(
f"Cannot determine timezone name from {tz!r}. "
"Use a zoneinfo.ZoneInfo instance or pass a string like 'America/New_York'."
)
raise TypeError(
f"Expected str, datetime.tzinfo, or None for timezone, got {type(tz).__name__}"
)
def _provided_vals(d: Mapping[str, Any]) -> dict[str, Any]:
return {k: v for k, v in d.items() if v is not None}
+14 -4
View File
@@ -4,9 +4,10 @@ from __future__ import annotations
import warnings
from collections.abc import Mapping, Sequence
from datetime import datetime
from datetime import datetime, tzinfo
from typing import Any
from langgraph_sdk._shared.utilities import _resolve_timezone
from langgraph_sdk._sync.http import SyncHttpClient
from langgraph_sdk.schema import (
All,
@@ -64,6 +65,7 @@ class SyncCronClient:
multitask_strategy: str | None = None,
end_time: datetime | None = None,
enabled: bool | None = None,
timezone: str | tzinfo | None = None,
stream_mode: StreamMode | Sequence[StreamMode] | None = None,
stream_subgraphs: bool | None = None,
stream_resumable: bool | None = None,
@@ -78,7 +80,7 @@ class SyncCronClient:
assistant_id: The assistant ID or graph name to use for the cron job.
If using graph name, will default to first assistant created from that graph.
schedule: The cron schedule to execute this job on.
Schedules are interpreted in UTC.
Schedules are interpreted in UTC unless a timezone is specified.
input: The input to the graph.
metadata: Metadata to assign to the cron job runs.
config: The configuration for the assistant.
@@ -92,6 +94,7 @@ class SyncCronClient:
Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'.
end_time: The time to stop running the cron job. If not provided, the cron job will run indefinitely.
enabled: Whether the cron job is enabled. By default, it is considered enabled.
timezone: IANA timezone for the cron schedule. Accepts a string (e.g. 'America/New_York') or a ``datetime.tzinfo`` instance (e.g. ``ZoneInfo("America/New_York")``).
stream_mode: The stream mode(s) to use.
stream_subgraphs: Whether to stream output from subgraphs.
stream_resumable: Whether to persist the stream chunks in order to resume the stream later.
@@ -144,6 +147,7 @@ class SyncCronClient:
"multitask_strategy": multitask_strategy,
"end_time": end_time.isoformat() if end_time else None,
"enabled": enabled,
"timezone": _resolve_timezone(timezone),
"stream_mode": stream_mode,
"stream_subgraphs": stream_subgraphs,
"stream_resumable": stream_resumable,
@@ -174,6 +178,7 @@ class SyncCronClient:
multitask_strategy: str | None = None,
end_time: datetime | None = None,
enabled: bool | None = None,
timezone: str | tzinfo | None = None,
stream_mode: StreamMode | Sequence[StreamMode] | None = None,
stream_subgraphs: bool | None = None,
stream_resumable: bool | None = None,
@@ -187,7 +192,7 @@ class SyncCronClient:
assistant_id: The assistant ID or graph name to use for the cron job.
If using graph name, will default to first assistant created from that graph.
schedule: The cron schedule to execute this job on.
Schedules are interpreted in UTC.
Schedules are interpreted in UTC unless a timezone is specified.
input: The input to the graph.
metadata: Metadata to assign to the cron job runs.
config: The configuration for the assistant.
@@ -205,6 +210,7 @@ class SyncCronClient:
Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'.
end_time: The time to stop running the cron job. If not provided, the cron job will run indefinitely.
enabled: Whether the cron job is enabled. By default, it is considered enabled.
timezone: IANA timezone for the cron schedule. Accepts a string (e.g. 'America/New_York') or a ``datetime.tzinfo`` instance (e.g. ``ZoneInfo("America/New_York")``).
stream_mode: The stream mode(s) to use.
stream_subgraphs: Whether to stream output from subgraphs.
stream_resumable: Whether to persist the stream chunks in order to resume the stream later.
@@ -259,6 +265,7 @@ class SyncCronClient:
"multitask_strategy": multitask_strategy,
"end_time": end_time.isoformat() if end_time else None,
"enabled": enabled,
"timezone": _resolve_timezone(timezone),
"stream_mode": stream_mode,
"stream_subgraphs": stream_subgraphs,
"stream_resumable": stream_resumable,
@@ -313,6 +320,7 @@ class SyncCronClient:
interrupt_after: All | list[str] | None = None,
on_run_completed: OnCompletionBehavior | None = None,
enabled: bool | None = None,
timezone: str | tzinfo | None = None,
stream_mode: StreamMode | Sequence[StreamMode] | None = None,
stream_subgraphs: bool | None = None,
stream_resumable: bool | None = None,
@@ -325,7 +333,7 @@ class SyncCronClient:
Args:
cron_id: The cron ID to update.
schedule: The cron schedule to execute this job on.
Schedules are interpreted in UTC.
Schedules are interpreted in UTC unless a timezone is specified.
end_time: The end date to stop running the cron.
input: The input to the graph.
metadata: Metadata to assign to the cron job runs.
@@ -339,6 +347,7 @@ class SyncCronClient:
after execution. 'keep' creates a new thread for each execution but does not
clean them up.
enabled: Enable or disable the cron job.
timezone: IANA timezone for the cron schedule. Accepts a string (e.g. 'America/New_York') or a ``datetime.tzinfo`` instance (e.g. ``ZoneInfo("America/New_York")``).
stream_mode: The stream mode(s) to use.
stream_subgraphs: Whether to stream output from subgraphs.
stream_resumable: Whether to persist the stream chunks in order to resume the stream later.
@@ -373,6 +382,7 @@ class SyncCronClient:
"interrupt_after": interrupt_after,
"on_run_completed": on_run_completed,
"enabled": enabled,
"timezone": _resolve_timezone(timezone),
"stream_mode": stream_mode,
"stream_subgraphs": stream_subgraphs,
"stream_resumable": stream_resumable,
+5
View File
@@ -385,6 +385,8 @@ class Cron(TypedDict):
"""The end date to stop running the cron."""
schedule: str
"""The schedule to run, cron format."""
timezone: str | None
"""IANA timezone for the cron schedule (e.g. 'America/New_York'). Defaults to null, which is treated as UTC."""
created_at: datetime
"""The time the cron was created."""
updated_at: datetime
@@ -406,6 +408,8 @@ class CronUpdate(TypedDict, total=False):
schedule: str
"""The cron schedule to execute this job on."""
timezone: str
"""IANA timezone for the cron schedule (e.g. 'America/New_York')."""
end_time: datetime
"""The end date to stop running the cron."""
input: Input
@@ -482,6 +486,7 @@ CronSelectField = Literal[
"thread_id",
"end_time",
"schedule",
"timezone",
"created_at",
"updated_at",
"user_id",
+1 -1
View File
@@ -265,7 +265,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.0.10"
version = "1.1.0"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },