diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index c2d2f70e0..fe68aea78 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,15 +1,8 @@
-blank_issues_enabled: true
version: 2.1
contact_links:
- - name: 🤔 Question or Problem
- about: Ask a question or ask about a problem in GitHub Discussions.
- url: https://github.com/langchain-ai/langgraph/discussions/categories/q-a
- name: Feature Request
url: https://github.com/langchain-ai/langgraph/discussions/categories/ideas
about: Suggest a feature or an idea
- - name: Show and tell
- about: Show what you built with LangChain
- url: https://github.com/langchain-ai/langgraph/discussions/categories/show-and-tell
- name: LangChain Forum
url: https://forum.langchain.com/
about: General community discussions and support
diff --git a/docs/docs/additional-resources/index.md b/docs/docs/additional-resources/index.md
index 6e4e7a93b..33e52e805 100644
--- a/docs/docs/additional-resources/index.md
+++ b/docs/docs/additional-resources/index.md
@@ -8,4 +8,4 @@ This section contains additional resources for LangGraph.
- [FAQ](../concepts/faq.md): A collection of frequently asked questions about LangGraph.
- [llms.txt](../llms-txt-overview.md): A list of documentation files in the `llms.txt` format that allow LLMs and agents to access our documentation.
- [LangChain Forum](https://forum.langchain.com/): A place to ask questions and get help from other LangGraph users.
-- [Troubleshooting](../troubleshooting/errors/index.md.md): A collection of troubleshooting guides for common issues.
\ No newline at end of file
+- [Troubleshooting](../troubleshooting/errors/index.md): A collection of troubleshooting guides for common issues.
\ No newline at end of file
diff --git a/docs/docs/cloud/reference/cli.md b/docs/docs/cloud/reference/cli.md
index b43be5485..2181f59e2 100644
--- a/docs/docs/cloud/reference/cli.md
+++ b/docs/docs/cloud/reference/cli.md
@@ -51,6 +51,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
| `node_version` | Specify `node_version: 20` to use LangGraph.js. |
| `pip_config_file` | Path to `pip` config file. |
| `pip_installer` | _(Added in v0.3)_ Optional. Python package installer selector. It can be set to `"auto"`, `"pip"`, or `"uv"`. From version 0.3 onward the default strategy is to run `uv pip`, which typically delivers faster builds while remaining a drop-in replacement. In the uncommon situation where `uv` cannot handle your dependency graph or the structure of your `pyproject.toml`, specify `"pip"` here to revert to the earlier behaviour. |
+ | `keep_pkg_tools` | _(Added in v0.3.4)_ Optional. Control whether to retain Python packaging tools (`pip`, `setuptools`, `wheel`) in the final image. Accepted values:
true : Keep all three tools (skip uninstall).false / omitted : Uninstall all three tools (default behaviour).list[str] : Names of tools to retain. Each value must be one of "pip", "setuptools", "wheel".
. By default, all three tools are uninstalled. |
| `dockerfile_lines` | Array of additional lines to add to Dockerfile following the import from parent image. |
| `checkpointer` | Configuration for the checkpointer. Contains a `ttl` field which is an object with the following keys: - `strategy`: How to handle expired checkpoints (e.g., `"delete"`).
- `sweep_interval_minutes`: How often to check for expired checkpoints (integer).
- `default_ttl`: Default time-to-live for checkpoints in **minutes** (integer). Defines how long checkpoints are kept before the specified strategy is applied.
|
| `http` | HTTP server configuration with the following fields: - `app`: Path to custom Starlette/FastAPI app (e.g., `"./src/agent/webapp.py:app"`). See [custom routes guide](../../how-tos/http/custom_routes.md).
- `cors`: CORS configuration with fields for `allow_origins`, `allow_methods`, `allow_headers`, etc.
- `configurable_headers`: Define which request headers to exclude or include as a run's configurable values.
- `disable_assistants`: Disable `/assistants` routes
- `disable_mcp`: Disable `/mcp` routes
- `disable_meta`: Disable `/ok`, `/info`, `/metrics`, and `/docs` routes
- `disable_runs`: Disable `/runs` routes
- `disable_store`: Disable `/store` routes
- `disable_threads`: Disable `/threads` routes
- `disable_ui`: Disable `/ui` routes
- `disable_webhooks`: Disable webhooks calls on run completion in all routes
- `mount_prefix`: Prefix for mounted routes (e.g., "/my-deployment/api")
|
diff --git a/docs/docs/cloud/reference/env_var.md b/docs/docs/cloud/reference/env_var.md
index 563cec2a1..333508d96 100644
--- a/docs/docs/cloud/reference/env_var.md
+++ b/docs/docs/cloud/reference/env_var.md
@@ -10,6 +10,10 @@ This environment variable should be set to `True` if the implementation of a gra
Defaults to `False`.
+## `BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS`
+
+Specifies, in seconds, how long the server will wait for background jobs to finish after the queue receives a shutdown signal. After this period, the server will force termination. Defaults to `180` seconds. Set this to ensure jobs have enough time to complete cleanly during shutdown. Added in `langgraph-api==0.2.16`.
+
## `BG_JOB_TIMEOUT_SECS`
The timeout of a background run can be increased. However, the infrastructure for a Cloud SaaS deployment enforces a 1 hour timeout limit for API requests. This means the connection between client and server will timeout after 1 hour. This is not configurable.
@@ -18,10 +22,6 @@ A background run can execute for longer than 1 hour, but a client must reconnect
Defaults to `3600`.
-## `BG_JOB_SHUTDOWN_GRACE_PERIOD_SECS`
-
-Specifies, in seconds, how long the server will wait for background jobs to finish after the queue receives a shutdown signal. After this period, the server will force termination. Defaults to `3600` seconds. Set this to ensure jobs have enough time to complete cleanly during shutdown. Added in `langgraph-api==0.2.16`.
-
## `DD_API_KEY`
Specify `DD_API_KEY` (your [Datadog API Key](https://docs.datadoghq.com/account_management/api-app-keys/)) to automatically enable Datadog tracing for the deployment. Specify other [`DD_*` environment variables](https://ddtrace.readthedocs.io/en/stable/configuration.html) to configure the tracing instrumentation.
@@ -40,6 +40,14 @@ Type of authentication for the LangGraph Server deployment. Valid values: `langs
For deployments to LangGraph Platform, this environment variable is set automatically. For local development or deployments where authentication is handled externally (e.g. self-hosted), set this environment variable to `noop`.
+## `LANGGRAPH_POSTGRES_POOL_MAX_SIZE`
+
+Beginning with langgraph-api version `0.2.12`, the maximum size of the Postgres connection pool (per replica) can be controlled using the `LANGGRAPH_POSTGRES_POOL_MAX_SIZE` environment variable. By setting this variable, you can determine the upper bound on the number of simultaneous connections the server will establish with the Postgres database.
+
+For example, if a deployment is scaled up to 10 replicas and `LANGGRAPH_POSTGRES_POOL_MAX_SIZE` is configured to `150`, then up to `1500` connections to Postgres can be established. This is particularly useful for deployments where database resources are limited (or more available) or where you need to tune connection behavior for performance or scaling reasons.
+
+Defaults to `150` connections.
+
## `LANGSMITH_RUNS_ENDPOINTS`
For deployments with [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting) only.
@@ -54,6 +62,10 @@ Set `LANGSMITH_TRACING` to `false` to disable tracing to LangSmith.
Defaults to `true`.
+## `LOG_COLOR`
+
+This is mainly relevant in the context of using the dev server via the `langgraph dev` command. Set `LOG_COLOR` to `true` to enable ANSI-colored console output when using the default console renderer. Disabling color output by setting this variable to `false` produces monochrome logs. Defaults to `true`.
+
## `LOG_LEVEL`
Configure [log level](https://docs.python.org/3/library/logging.html#logging-levels). Defaults to `INFO`.
@@ -62,9 +74,14 @@ Configure [log level](https://docs.python.org/3/library/logging.html#logging-lev
Set `LOG_JSON` to `true` to render all log messages as JSON objects using the configured `JSONRenderer`. This produces structured logs that can be easily parsed or ingested by log management systems. Defaults to `false`.
-## `LOG_COLOR`
+## `MOUNT_PREFIX`
-This is mainly relevant in the context of using the dev server via the `langgraph dev` command. Set `LOG_COLOR` to `true` to enable ANSI-colored console output when using the default console renderer. Disabling color output by setting this variable to `false` produces monochrome logs. Defaults to `true`.
+!!! info "Only Allowed in Self-Hosted Deployments"
+ The `MOUNT_PREFIX` environment variable is only allowed in Self-Hosted Deployment models, LangGraph Platform SaaS will not allow this environment variable.
+
+Set `MOUNT_PREFIX` to serve the LangGraph Server under a specific path prefix. This is useful for deployments where the server is behind a reverse proxy or load balancer that requires a specific path prefix.
+
+For example, if the server is to be served under `https://example.com/langgraph`, set `MOUNT_PREFIX` to `/langgraph`.
## `N_JOBS_PER_WORKER`
@@ -94,16 +111,14 @@ Database Connectivity:
- The custom Postgres instance must be accessible by the LangGraph Server. The user is responsible for ensuring connectivity.
-## `LANGGRAPH_POSTGRES_POOL_MAX_SIZE`
+## `REDIS_CLUSTER`
-Beginning with langgraph-api version `0.2.12`, the maximum size of the Postgres connection pool can be controlled using the `LANGGRAPH_POSTGRES_POOL_MAX_SIZE` environment variable. By setting this variable, you can determine the upper bound on the number of simultaneous connections the server will establish with the Postgres database. This is particularly useful for deployments where database resources are limited (or more available) or where you need to tune connection behavior for performance or scaling reasons. If not specified, the pool size defaults to 150 connections.
+!!! info "Only Allowed in Self-Hosted Deployments"
+ Redis Cluster mode is only available in Self-Hosted Deployment models, LangGraph Platform SaaS will provision a redis instance for you by default.
-## `REDIS_URI_CUSTOM`
+Set `REDIS_CLUSTER` to `True` to enable Redis Cluster mode. When enabled, the system will connect to Redis using cluster mode. This is useful when connecting to a Redis Cluster deployment.
-!!! info "Only for Self-Hosted Data Plane and Self-Hosted Control Plane"
- Custom Redis instances are only available for [Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md) deployments.
-
-Specify `REDIS_URI_CUSTOM` to use a custom Redis instance. The value of `REDIS_URI_CUSTOM` must be a valid [Redis connection URI](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis.from_url).
+Defaults to `False`.
## `REDIS_KEY_PREFIX`
@@ -114,20 +129,19 @@ Specify a prefix for Redis keys. This allows multiple LangGraph Server instances
Defaults to `''`.
-## `REDIS_CLUSTER`
+## `REDIS_URI_CUSTOM`
-!!! info "Only Allowed in Self-Hosted Deployments"
- Redis Cluster mode is only available in Self-Hosted Deployment models, LangGraph Platform SaaS will provision a redis instance for you by default.
+!!! info "Only for Self-Hosted Data Plane and Self-Hosted Control Plane"
+ Custom Redis instances are only available for [Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md) deployments.
-Set `REDIS_CLUSTER` to `True` to enable Redis Cluster mode. When enabled, the system will connect to Redis using cluster mode. This is useful when connecting to a Redis Cluster deployment.
+Specify `REDIS_URI_CUSTOM` to use a custom Redis instance. The value of `REDIS_URI_CUSTOM` must be a valid [Redis connection URI](https://redis-py.readthedocs.io/en/stable/connections.html#redis.Redis.from_url).
-Defaults to `False`.
+## `RESUMABLE_STREAM_TTL_SECONDS`
-## `MOUNT_PREFIX`
+Time-to-live in seconds for resumable stream data in Redis.
-!!! info "Only Allowed in Self-Hosted Deployments"
- The `MOUNT_PREFIX` environment variable is only allowed in Self-Hosted Deployment models, LangGraph Platform SaaS will not allow this environment variable.
+When a run is created and the output is streamed, the stream can be configured to be resumable (e.g. `stream_resumable=True`). If a stream is resumable, output from the stream is temporarily stored in Redis. The TTL for this data can be configured by setting `RESUMABLE_STREAM_TTL_SECONDS`.
-Set `MOUNT_PREFIX` to serve the LangGraph Server under a specific path prefix. This is useful for deployments where the server is behind a reverse proxy or load balancer that requires a specific path prefix.
+See the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.RunsClient.stream) and [JS/TS](https://langchain-ai.github.io/langgraphjs/reference/classes/sdk_client.RunsClient.html#stream) SDKs for more details on how to implement resumable streams.
-For example, if the server is to be served under `https://example.com/langgraph`, set `MOUNT_PREFIX` to `/langgraph`.
+Defaults to `120` seconds.
diff --git a/docs/docs/stylesheets/navigation_title_ovverides.css b/docs/docs/stylesheets/navigation_title_ovverides.css
deleted file mode 100644
index 4e7c937d7..000000000
--- a/docs/docs/stylesheets/navigation_title_ovverides.css
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * This file is used to override the navigation title for the LangGraph documentation.
- * It is used to change the title of the first and second items in the navigation menu.
- * The first item is the Guides page, and the second item is the Reference page.
- */
-
-.md-nav--primary > .md-nav__list > .md-nav__item:nth-child(1) > .md-nav__link .md-ellipsis {
- visibility: hidden !important;
- position: relative;
-}
-
-.md-nav--primary > .md-nav__list > .md-nav__item:nth-child(1) > .md-nav__link .md-ellipsis::after {
- content: "Home";
- visibility: visible;
- position: absolute;
- left: 0;
-}
-
-.md-nav--primary > .md-nav__list > .md-nav__item:nth-child(2) > .md-nav__link .md-ellipsis {
- visibility: hidden !important;
- position: relative;
-}
-
-.md-nav--primary > .md-nav__list > .md-nav__item:nth-child(2) > .md-nav__link .md-ellipsis::after {
- content: "Home";
- visibility: visible;
- position: absolute;
- left: 0;
-}
\ No newline at end of file
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index a66966905..f11c007ec 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -381,7 +381,6 @@ validation:
copyright: >
Copyright © 2025 LangChain, Inc | Consent Preferences
extra_css:
- - stylesheets/navigation_title_ovverides.css
- stylesheets/version_admonitions.css
- stylesheets/logos.css
- stylesheets/sticky_navigation.css
diff --git a/libs/cli/examples/graphs_reqs_a/graphs_submod/agent.py b/libs/cli/examples/graphs_reqs_a/graphs_submod/agent.py
index 5ab615871..1073110cb 100644
--- a/libs/cli/examples/graphs_reqs_a/graphs_submod/agent.py
+++ b/libs/cli/examples/graphs_reqs_a/graphs_submod/agent.py
@@ -17,7 +17,7 @@ model_oai = ChatOpenAI(temperature=0)
model_anth = model_anth.bind_tools(tools)
model_oai = model_oai.bind_tools(tools)
-prompt = open("prompt.txt").read()
+prompt = open(Path(__file__).parent.parent / "prompt.txt").read()
subprompt = open(Path(__file__).parent / "subprompt.txt").read()
diff --git a/libs/cli/examples/graphs_reqs_b/graphs_submod/agent.py b/libs/cli/examples/graphs_reqs_b/graphs_submod/agent.py
index 5ab615871..1073110cb 100644
--- a/libs/cli/examples/graphs_reqs_b/graphs_submod/agent.py
+++ b/libs/cli/examples/graphs_reqs_b/graphs_submod/agent.py
@@ -17,7 +17,7 @@ model_oai = ChatOpenAI(temperature=0)
model_anth = model_anth.bind_tools(tools)
model_oai = model_oai.bind_tools(tools)
-prompt = open("prompt.txt").read()
+prompt = open(Path(__file__).parent.parent / "prompt.txt").read()
subprompt = open(Path(__file__).parent / "subprompt.txt").read()
diff --git a/libs/cli/examples/langgraph.json b/libs/cli/examples/langgraph.json
index 51eb14be9..0d8563e55 100644
--- a/libs/cli/examples/langgraph.json
+++ b/libs/cli/examples/langgraph.json
@@ -8,6 +8,7 @@
"scikit-learn",
"./graphs"
],
+ "keep_pkg_tools": false,
"graphs": {
"agent": "./graphs/agent.py:graph",
"storm": "./graphs/storm.py:graph"
diff --git a/libs/cli/langgraph_cli/config.py b/libs/cli/langgraph_cli/config.py
index cd0356350..9030c3ba2 100644
--- a/libs/cli/langgraph_cli/config.py
+++ b/libs/cli/langgraph_cli/config.py
@@ -338,7 +338,10 @@ class HttpConfig(TypedDict, total=False):
Default is False.
"""
disable_meta: bool
- """Optional. If True, all meta endpoints (/ok, /info, /metrics, /docs) are disabled.
+ """Optional. Remove meta endpoints.
+
+ Set to True to disable the following endpoints: /openapi.json, /info, /metrics, /docs.
+ This will also make the /ok endpoint skip any DB or other checks, always returning {"ok": True}.
Default is False.
"""
@@ -471,21 +474,61 @@ class Config(TypedDict, total=False):
"""Optional. Named definitions of UI components emitted by the agent, each pointing to a JS/TS file.
"""
+ keep_pkg_tools: Optional[Union[bool, list[str]]]
+ """Optional. Control whether to retain Python packaging tools in the final image.
+
+ Allowed tools are: "pip", "setuptools", "wheel".
+ You can also set to true to include all packaging tools.
+ """
-PIP_CLEANUP_LINES = """# -- Ensure user deps didn't inadvertently overwrite langgraph-api
+
+_BUILD_TOOLS = ("pip", "setuptools", "wheel")
+
+
+def _get_pip_cleanup_lines(
+ install_cmd: str,
+ to_uninstall: Optional[tuple[str]],
+ pip_installer: Literal["uv", "pip"],
+) -> str:
+ commands = [
+ f"""# -- Ensure user deps didn't inadvertently overwrite langgraph-api
RUN mkdir -p /api/langgraph_api /api/langgraph_runtime /api/langgraph_license && \
- touch /api/langgraph_api/__init__.py /api/langgraph_runtime/__init__.py /api/langgraph_license/__init__.py
+touch /api/langgraph_api/__init__.py /api/langgraph_runtime/__init__.py /api/langgraph_license/__init__.py
RUN PYTHONDONTWRITEBYTECODE=1 {install_cmd} --no-cache-dir --no-deps -e /api
# -- End of ensuring user deps didn't inadvertently overwrite langgraph-api --
-# -- Removing pip from the final image ~<:===~~~ --
-RUN pip uninstall -y pip setuptools wheel && \
- rm -rf /usr/local/lib/python*/site-packages/pip* /usr/local/lib/python*/site-packages/setuptools* /usr/local/lib/python*/site-packages/wheel* && \
- find /usr/local/bin -name "pip*" -delete || true
-# pip removal for wolfi
-RUN rm -rf /usr/lib/python*/site-packages/pip* /usr/lib/python*/site-packages/setuptools* /usr/lib/python*/site-packages/wheel* && \
- find /usr/bin -name "pip*" -delete || true
-{uv_removal}
-# -- End of pip removal --"""
+# -- Removing build deps from the final image ~<:===~~~ --"""
+ ]
+ if to_uninstall:
+ for pack in to_uninstall:
+ if pack not in _BUILD_TOOLS:
+ raise ValueError(
+ f"Invalid build tool: {pack}; must be one of {', '.join(_BUILD_TOOLS)}"
+ )
+ packs_str = " ".join(sorted(to_uninstall))
+ commands.append(f"RUN pip uninstall -y {packs_str}")
+ # Ensure the directories are removed entirely
+ packages_rm = " ".join(
+ f"/usr/local/lib/python*/site-packages/{pack}*" for pack in to_uninstall
+ )
+ if "pip" in to_uninstall:
+ packages_rm += ' && find /usr/local/bin -name "pip*" -delete || true'
+ commands.append(f"RUN rm -rf {packages_rm}")
+ wolfi_packages_rm = " ".join(
+ f"/usr/lib/python*/site-packages/{pack}*" for pack in to_uninstall
+ )
+ if "pip" in to_uninstall:
+ wolfi_packages_rm += ' && find /usr/bin -name "pip*" -delete || true'
+ commands.append(f"RUN rm -rf {wolfi_packages_rm}")
+ if pip_installer == "uv":
+ commands.append(
+ f"RUN uv pip uninstall --system {packs_str} && rm /usr/bin/uv /usr/bin/uvx"
+ )
+ else:
+ if pip_installer == "uv":
+ commands.append(
+ "RUN rm /usr/bin/uv /usr/bin/uvx\n# -- End of build deps removal --"
+ )
+ return "\n".join(commands)
def _parse_version(version_str: str) -> tuple[int, int]:
@@ -563,6 +606,7 @@ def validate_config(config: Config) -> Config:
"checkpointer": config.get("checkpointer"),
"ui": config.get("ui"),
"ui_config": config.get("ui_config"),
+ "keep_pkg_tools": config.get("keep_pkg_tools"),
}
if config.get("node_version"):
@@ -635,6 +679,22 @@ def validate_config(config: Config) -> Config:
f"Invalid http.app format: '{http_conf['app']}'. "
"Must be in format './path/to/file.py:attribute_name'"
)
+ if keep_pkg_tools := config.get("keep_pkg_tools"):
+ if isinstance(keep_pkg_tools, list):
+ for tool in keep_pkg_tools:
+ if tool not in _BUILD_TOOLS:
+ raise ValueError(
+ f"Invalid keep_pkg_tools: '{tool}'. "
+ "Must be one of 'pip', 'setuptools', 'wheel'."
+ )
+ elif keep_pkg_tools is True:
+ pass
+ else:
+ raise ValueError(
+ f"Invalid keep_pkg_tools: '{keep_pkg_tools}'. "
+ "Must be bool or list[str] (with values"
+ " 'pip', 'setuptools', and/or 'wheel')."
+ )
return config
@@ -1128,6 +1188,27 @@ def _image_supports_uv(base_image: str) -> bool:
return version >= min_uv
+def get_build_tools_to_uninstall(config: Config) -> tuple[str]:
+ keep_pkg_tools = config.get("keep_pkg_tools")
+ if not keep_pkg_tools:
+ return _BUILD_TOOLS
+ if keep_pkg_tools is True:
+ return ()
+ expected = _BUILD_TOOLS
+ if isinstance(keep_pkg_tools, list):
+ for tool in keep_pkg_tools:
+ if tool not in expected:
+ raise ValueError(
+ f"Invalid build tool to uninstall: {tool}. Expected one of {expected}"
+ )
+ return tuple(sorted(set(_BUILD_TOOLS) - set(keep_pkg_tools)))
+ else:
+ raise ValueError(
+ f"Invalid value for keep_pkg_tools: {keep_pkg_tools}."
+ " Expected True or a list containing any of {expected}."
+ )
+
+
def python_config_to_docker(
config_path: pathlib.Path,
config: Config,
@@ -1135,20 +1216,18 @@ def python_config_to_docker(
) -> tuple[str, dict[str, str]]:
"""Generate a Dockerfile from the configuration."""
pip_installer = config.get("pip_installer", "auto")
-
+ build_tools_to_uninstall = get_build_tools_to_uninstall(config)
+ if pip_installer == "auto":
+ if _image_supports_uv(base_image):
+ pip_installer = "uv"
+ else:
+ pip_installer = "pip"
if pip_installer == "uv":
install_cmd = "uv pip install --system"
- uv_removal = "RUN uv pip uninstall --system pip setuptools wheel && rm /usr/bin/uv /usr/bin/uvx"
elif pip_installer == "pip":
install_cmd = "pip install"
- uv_removal = ""
else:
- if _image_supports_uv(base_image):
- install_cmd = "uv pip install --system"
- uv_removal = "RUN uv pip uninstall --system pip setuptools wheel && rm /usr/bin/uv /usr/bin/uvx"
- else:
- install_cmd = "pip install"
- uv_removal = ""
+ raise ValueError(f"Invalid pip_installer: {pip_installer}")
# configure pip
pip_install = f"PYTHONDONTWRITEBYTECODE=1 {install_cmd} --no-cache-dir -c /api/constraints.txt"
@@ -1297,7 +1376,11 @@ ADD {relpath} /deps/{name}
js_inst_str,
"",
# Add pip cleanup after all installations are complete
- PIP_CLEANUP_LINES.format(install_cmd=install_cmd, uv_removal=uv_removal),
+ _get_pip_cleanup_lines(
+ install_cmd=install_cmd,
+ to_uninstall=build_tools_to_uninstall,
+ pip_installer=pip_installer,
+ ),
"",
f"WORKDIR {local_deps.working_dir}" if local_deps.working_dir else "",
]
diff --git a/libs/cli/pyproject.toml b/libs/cli/pyproject.toml
index fb2345bae..05045d3bc 100644
--- a/libs/cli/pyproject.toml
+++ b/libs/cli/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph-cli"
-version = "0.3.3"
+version = "0.3.4"
description = "CLI for interacting with LangGraph API"
authors = []
requires-python = ">=3.9"
diff --git a/libs/cli/schemas/schema.json b/libs/cli/schemas/schema.json
index c069f7e9e..e76e69826 100644
--- a/libs/cli/schemas/schema.json
+++ b/libs/cli/schemas/schema.json
@@ -134,6 +134,23 @@
],
"description": "Optional. Linux distribution for the base image.\n\nMust be either 'debian' or 'wolfi'. If omitted, defaults to 'debian'.\n"
},
+ "keep_pkg_tools": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Optional. Control whether to retain Python packaging tools in the final image.\n\nYou can also set to true to include all packaging tools.\n"
+ },
"pip_installer": {
"anyOf": [
{
@@ -298,6 +315,23 @@
],
"description": "Optional. Linux distribution for the base image.\n\nMust be either 'debian' or 'wolfi'. If omitted, defaults to 'debian'.\n"
},
+ "keep_pkg_tools": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Optional. Control whether to retain Python packaging tools in the final image.\n\nYou can also set to true to include all packaging tools.\n"
+ },
"pip_installer": {
"anyOf": [
{
@@ -505,7 +539,7 @@
},
"disable_meta": {
"type": "boolean",
- "description": "Optional. If True, all meta endpoints (/ok, /info, /metrics, /docs) are disabled.\n\nDefault is False.\n"
+ "description": "Optional. Remove meta endpoints.\n\n\nDefault is False.\n"
},
"disable_runs": {
"type": "boolean",
diff --git a/libs/cli/schemas/schema.v0.json b/libs/cli/schemas/schema.v0.json
index c069f7e9e..e76e69826 100644
--- a/libs/cli/schemas/schema.v0.json
+++ b/libs/cli/schemas/schema.v0.json
@@ -134,6 +134,23 @@
],
"description": "Optional. Linux distribution for the base image.\n\nMust be either 'debian' or 'wolfi'. If omitted, defaults to 'debian'.\n"
},
+ "keep_pkg_tools": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Optional. Control whether to retain Python packaging tools in the final image.\n\nYou can also set to true to include all packaging tools.\n"
+ },
"pip_installer": {
"anyOf": [
{
@@ -298,6 +315,23 @@
],
"description": "Optional. Linux distribution for the base image.\n\nMust be either 'debian' or 'wolfi'. If omitted, defaults to 'debian'.\n"
},
+ "keep_pkg_tools": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Optional. Control whether to retain Python packaging tools in the final image.\n\nYou can also set to true to include all packaging tools.\n"
+ },
"pip_installer": {
"anyOf": [
{
@@ -505,7 +539,7 @@
},
"disable_meta": {
"type": "boolean",
- "description": "Optional. If True, all meta endpoints (/ok, /info, /metrics, /docs) are disabled.\n\nDefault is False.\n"
+ "description": "Optional. Remove meta endpoints.\n\n\nDefault is False.\n"
},
"disable_runs": {
"type": "boolean",
diff --git a/libs/cli/schemas/version.schema.json b/libs/cli/schemas/version.schema.json
new file mode 100644
index 000000000..041b130c1
--- /dev/null
+++ b/libs/cli/schemas/version.schema.json
@@ -0,0 +1,46 @@
+{
+ "$id": "https://github.com/langchain-ai/langgraph/libs/cli/schemas/version.schema.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "LangGraph Platform configuration (when building via the langgraph-cli).",
+ "type": "object",
+ "oneOf": [
+ {
+ "allOf": [
+ {
+ "oneOf": [
+ {
+ "properties": {
+ "version": {
+ "type": "string",
+ "maxLength": 0
+ }
+ },
+ "required": ["version"]
+ },
+ {
+ "not": {
+ "required": ["version"]
+ }
+ }
+ ]
+ },
+ {
+ "$ref": "https://raw.githubusercontent.com/langchain-ai/langgraph/main/libs/cli/schemas/schema.json"
+ }
+ ]
+ },
+ {
+ "allOf": [
+ {
+ "properties": {
+ "version": { "const": "v0" }
+ },
+ "required": ["version"]
+ },
+ {
+ "$ref": "https://raw.githubusercontent.com/langchain-ai/langgraph/main/libs/cli/schemas/schema.v0.json"
+ }
+ ]
+ }
+ ]
+}
diff --git a/libs/cli/tests/unit_tests/cli/test_cli.py b/libs/cli/tests/unit_tests/cli/test_cli.py
index 268f44e00..e6d0a17b4 100644
--- a/libs/cli/tests/unit_tests/cli/test_cli.py
+++ b/libs/cli/tests/unit_tests/cli/test_cli.py
@@ -10,13 +10,14 @@ from pathlib import Path
from click.testing import CliRunner
from langgraph_cli.cli import cli, prepare_args_and_stdin
-from langgraph_cli.config import PIP_CLEANUP_LINES, Config, validate_config
+from langgraph_cli.config import Config, _get_pip_cleanup_lines, validate_config
from langgraph_cli.docker import DEFAULT_POSTGRES_URI, DockerCapabilities, Version
from langgraph_cli.util import clean_empty_lines
-FORMATTED_CLEANUP_LINES = PIP_CLEANUP_LINES.format(
+FORMATTED_CLEANUP_LINES = _get_pip_cleanup_lines(
install_cmd="uv pip install --system",
- uv_removal="RUN uv pip uninstall --system pip setuptools wheel && rm /usr/bin/uv /usr/bin/uvx",
+ to_uninstall=("pip", "setuptools", "wheel"),
+ pip_installer="uv",
)
DEFAULT_DOCKER_CAPABILITIES = DockerCapabilities(
version_docker=Version(26, 1, 1),
diff --git a/libs/cli/tests/unit_tests/test_config.py b/libs/cli/tests/unit_tests/test_config.py
index 3971b60e2..691c1e888 100644
--- a/libs/cli/tests/unit_tests/test_config.py
+++ b/libs/cli/tests/unit_tests/test_config.py
@@ -9,7 +9,8 @@ import click
import pytest
from langgraph_cli.config import (
- PIP_CLEANUP_LINES,
+ _BUILD_TOOLS,
+ _get_pip_cleanup_lines,
config_to_compose,
config_to_docker,
docker_tag,
@@ -18,9 +19,10 @@ from langgraph_cli.config import (
)
from langgraph_cli.util import clean_empty_lines
-FORMATTED_CLEANUP_LINES = PIP_CLEANUP_LINES.format(
+FORMATTED_CLEANUP_LINES = _get_pip_cleanup_lines(
install_cmd="uv pip install --system",
- uv_removal="RUN uv pip uninstall --system pip setuptools wheel && rm /usr/bin/uv /usr/bin/uvx",
+ to_uninstall=("pip", "setuptools", "wheel"),
+ pip_installer="uv",
)
PATH_TO_CONFIG = pathlib.Path(__file__).parent / "test_config.json"
@@ -51,6 +53,7 @@ def test_validate_config():
"http": None,
"ui": None,
"ui_config": None,
+ "keep_pkg_tools": None,
**expected_config,
}
assert actual_config == expected_config
@@ -77,6 +80,7 @@ def test_validate_config():
"http": None,
"ui": None,
"ui_config": None,
+ "keep_pkg_tools": None,
}
actual_config = validate_config(expected_config)
assert actual_config == expected_config
@@ -925,6 +929,53 @@ def test_config_to_docker_pip_installer():
assert "uv pip install --system" in docker_default
+def test_config_retain_build_tools():
+ graphs = {"agent": "./graphs/agent.py:graph"}
+ base_config = {
+ "python_version": "3.11",
+ "dependencies": ["."],
+ "graphs": graphs,
+ }
+ config_true = validate_config(
+ {**copy.deepcopy(base_config), "keep_pkg_tools": True}
+ )
+ docker_true, _ = config_to_docker(
+ PATH_TO_CONFIG, config_true, "langchain/langgraph-api:0.2.47"
+ )
+ assert not any(
+ "/usr/local/lib/python*/site-packages/" + pckg + "*" in docker_true
+ for pckg in _BUILD_TOOLS
+ )
+ assert "RUN pip uninstall -y pip setuptools wheel" not in docker_true
+ config_false = validate_config(
+ {**copy.deepcopy(base_config), "keep_pkg_tools": False}
+ )
+ docker_false, _ = config_to_docker(
+ PATH_TO_CONFIG, config_false, "langchain/langgraph-api:0.2.47"
+ )
+ assert all(
+ "/usr/local/lib/python*/site-packages/" + pckg + "*" in docker_false
+ for pckg in _BUILD_TOOLS
+ )
+ assert "RUN pip uninstall -y pip setuptools wheel" in docker_false
+ config_list = validate_config(
+ {**copy.deepcopy(base_config), "keep_pkg_tools": ["pip", "setuptools"]}
+ )
+ docker_list, _ = config_to_docker(
+ PATH_TO_CONFIG, config_list, "langchain/langgraph-api:0.2.47"
+ )
+ assert all(
+ "/usr/local/lib/python*/site-packages/" + pckg + "*" in docker_list
+ for pckg in ("wheel",)
+ )
+ assert not any(
+ "/usr/local/lib/python*/site-packages/" + pckg + "*" in docker_list
+ for pckg in ("pip", "setuptools")
+ )
+ assert "RUN pip uninstall -y wheel" in docker_list
+ assert "RUN pip uninstall -y pip setuptools" not in docker_list
+
+
# config_to_compose
def test_config_to_compose_simple_config():
graphs = {"agent": "./agent.py:graph"}
diff --git a/libs/cli/uv.lock b/libs/cli/uv.lock
index 256e0c717..fc8d95552 100644
--- a/libs/cli/uv.lock
+++ b/libs/cli/uv.lock
@@ -522,7 +522,7 @@ wheels = [
[[package]]
name = "langgraph-cli"
-version = "0.3.3"
+version = "0.3.4"
source = { editable = "." }
dependencies = [
{ name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py
index dcebe5b34..80ac19176 100644
--- a/libs/langgraph/langgraph/types.py
+++ b/libs/langgraph/langgraph/types.py
@@ -382,7 +382,7 @@ def interrupt(value: Any) -> Any:
from langgraph.checkpoint.memory import MemorySaver
from langgraph.constants import START
from langgraph.graph import StateGraph
- from langgraph.types import interrupt
+ from langgraph.types import interrupt, Command
class State(TypedDict):