Compare commits

..
Author SHA1 Message Date
Richard Scarrott 62c4e502f7 release(langgraph): 1.2.21 2026-11-12 15:34:22 +00:00
19 changed files with 197 additions and 303 deletions

No files matched your search

+11 -20
View File
@@ -40,7 +40,7 @@ langgraph new [PATH] --template TEMPLATE_NAME
### `langgraph dev` 🏃‍♀️
Run Agent Server in development mode with hot reloading.
Run LangGraph API server in development mode with hot reloading.
```bash
langgraph dev [OPTIONS]
@@ -54,7 +54,7 @@ langgraph dev [OPTIONS]
### `langgraph up` 🚀
Launch Agent Server in Docker.
Launch LangGraph API server in Docker.
```bash
langgraph up [OPTIONS]
@@ -86,23 +86,14 @@ langgraph dockerfile SAVE_PATH [OPTIONS]
-c, --config FILE Config file path
```
### `langgraph deploy`
Build and deploy an Agent Server image to LangSmith. Use `--agent-server-id` to
update an existing Agent Server and `--agent-server-type` to select its type.
The legacy `--deployment-id` and `--deployment-type` spellings remain supported
as hidden compatibility aliases, with no removal scheduled. Older Python CLI
versions and the JavaScript CLI may still require the legacy spellings; check
command help for your installed version.
### `langgraph deploy metrics`
Fetch Agent Server monitoring time series as JSON through the LangSmith control plane.
Use `langgraph deploy list` to find the Agent Server ID. Authentication uses the same
API key and workspace configuration as the other `deploy` commands.
Fetch deployment monitoring time series as JSON through the LangSmith control plane.
Use `langgraph deploy list` to find the deployment ID. Authentication uses the same
API key and workspace configuration as the other deployment commands.
```bash
langgraph deploy metrics AGENT_SERVER_ID \
langgraph deploy metrics DEPLOYMENT_ID \
--start-time 2026-10-28T00:00:00Z \
--end-time 2026-10-28T01:00:00Z \
--metrics cpu_usage,memory_usage,worker_count
@@ -117,8 +108,8 @@ langgraph deploy metrics AGENT_SERVER_ID \
- Queue: `run_queue_wait_time`
These are exact metric names, not free-text search terms. Availability depends on
the Agent Server and platform; a supported metric may have no data. Use
`lg_run_count` for Agent Server runs, not the UI schema's placeholder `run_count`.
the deployment and platform; a supported metric may have no data. Use
`lg_run_count` for deployment runs, not the UI schema's placeholder `run_count`.
Omit `--metrics` to fetch all available metrics. Time bounds default to the last
15 minutes, and the backend picks the resolution from the time
@@ -145,8 +136,8 @@ series has fewer than two timestamps. UI metadata and repeated series IDs are
omitted. The host-backend response itself is unchanged.
```bash
langgraph deploy metrics AGENT_SERVER_ID --summary
langgraph deploy metrics AGENT_SERVER_ID --raw
langgraph deploy metrics DEPLOYMENT_ID --summary
langgraph deploy metrics DEPLOYMENT_ID --raw
```
`--summary` returns the same shape without `points`. `--raw` returns the original
@@ -154,7 +145,7 @@ UI chart schema without reshaping or rounding. These flags are mutually exclusiv
Neither mode returns traces.
Size comparison using the realistic synthetic unit-test fixture (compact JSON
serialization for all modes; not a live Agent Server benchmark):
serialization for all modes; not a live deployment benchmark):
| Before: raw UI schema | After: default | After: summary |
| --- | --- | --- |
-4
View File
@@ -107,10 +107,6 @@ def add_descriptions_to_schema(schema, cls):
# Recursively process nested definitions
if "$defs" in schema:
for def_name, def_schema in schema["$defs"].items():
# msgspec copies raw __doc__, which keeps indentation before Python
# 3.13, so clean it here too for classes not listed below.
if def_schema.get("description"):
def_schema["description"] = inspect.cleandoc(def_schema["description"])
# Find the class that corresponds to this definition
for potential_cls in [
Config,
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.4.43"
__version__ = "0.4.42"
+17 -17
View File
@@ -1,4 +1,4 @@
"""CLI entrypoint for Agent Server."""
"""CLI entrypoint for LangGraph API server."""
import os
import pathlib
@@ -46,7 +46,7 @@ OPT_CONFIG = click.option(
\b
Config file must be a JSON file that has the following keys:
- "dependencies": array of dependencies for the Agent Server. Dependencies can be one of the following:
- "dependencies": array of dependencies for langgraph API server. Dependencies can be one of the following:
- ".", which would look for local python packages, as well as pyproject.toml, setup.py or requirements.txt in the app directory
- "./local_package"
- "<package_name>
@@ -150,7 +150,7 @@ OPT_API_VERSION = click.option(
"--api-version",
type=str,
help=(
"Agent Server version to use for the base image. If unspecified, the "
"API server version to use for the base image. If unspecified, the "
"latest stable version will be used. Compatible ranges like "
"~=0.11.0.dev5 stay on 0.11.0.dev5 while only newer dev builds exist, "
"then resolve to the newest matching rc or stable release, for example "
@@ -330,7 +330,7 @@ def _studio_link(
@click.option(
"--base-image",
default=None,
help="Base image to use for the Agent Server. Pin to specific versions using version tags. Defaults to langchain/langgraph-api or langchain/langgraphjs-api."
help="Base image to use for the LangGraph API server. Pin to specific versions using version tags. Defaults to langchain/langgraph-api or langchain/langgraphjs-api."
"\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)",
)
@@ -339,7 +339,7 @@ def _studio_link(
is_flag=True,
help="Wait for services to start before returning. Implies --detach",
)
@cli.command(help="🚀 Launch Agent Server.")
@cli.command(help="🚀 Launch LangGraph API server.")
@log_command
def up(
config: pathlib.Path,
@@ -365,9 +365,9 @@ def up(
api_url=api_url,
debugger_base_url=debugger_base_url,
)
click.secho("Starting Agent Server...", fg="green")
click.secho("Starting LangGraph API server...", fg="green")
click.secho(
"""For local dev, requires env var LANGSMITH_API_KEY with access to LangSmith Agent Server.
"""For local dev, requires env var LANGSMITH_API_KEY with access to LangSmith Deployment.
For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KEY.""",
)
with Runner() as runner, Progress(message="Pulling...") as set:
@@ -457,7 +457,7 @@ For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KE
)
@click.option(
"--base-image",
help="Base image to use for the Agent Server. Pin to specific versions using version tags. Defaults to langchain/langgraph-api or langchain/langgraphjs-api."
help="Base image to use for the LangGraph API server. Pin to specific versions using version tags. Defaults to langchain/langgraph-api or langchain/langgraphjs-api."
"\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)",
)
@@ -473,7 +473,7 @@ For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KE
)
@click.argument("docker_build_args", nargs=-1, type=click.UNPROCESSED)
@cli.command(
help="📦 Build Agent Server Docker image.",
help="📦 Build LangGraph API server Docker image.",
context_settings=dict(
ignore_unknown_options=True,
),
@@ -590,12 +590,12 @@ tests
@OPT_CONFIG
@click.argument("save_path", type=click.Path(resolve_path=True))
@cli.command(
help="🐳 Generate a Dockerfile for the Agent Server, with Docker Compose options."
help="🐳 Generate a Dockerfile for the LangGraph API server, with Docker Compose options."
)
@click.option(
"--add-docker-compose",
help=(
"Add additional files for running the Agent Server with "
"Add additional files for running the LangGraph API server with "
"docker-compose. These files include a docker-compose.yml, .env file, "
"and a .dockerignore file."
),
@@ -603,7 +603,7 @@ tests
)
@click.option(
"--base-image",
help="Base image to use for the Agent Server. Pin to specific versions using version tags. Defaults to langchain/langgraph-api or langchain/langgraphjs-api."
help="Base image to use for the LangGraph API server. Pin to specific versions using version tags. Defaults to langchain/langgraph-api or langchain/langgraphjs-api."
"\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)",
)
@@ -695,7 +695,7 @@ def dockerfile(
"\n",
"# LANGSMITH_API_KEY=your-api-key",
"\n",
"# Or if you have an Agent Server license key, "
"# Or if you have a LangSmith Deployment license key, "
"then uncomment the following line: ",
"\n",
"# LANGGRAPH_CLOUD_LICENSE_KEY=your-license-key",
@@ -792,7 +792,7 @@ def dockerfile(
"--server-log-level",
type=str,
default="WARNING",
help="Set the log level for the Agent Server.",
help="Set the log level for the API server.",
)
@click.option(
"--ssl-certfile",
@@ -820,7 +820,7 @@ def dockerfile(
)
@cli.command(
"dev",
help="🏃‍♀️‍➡️ Run Agent Server in development mode with hot reloading and debugging support",
help="🏃‍♀️‍➡️ Run LangGraph API server in development mode with hot reloading and debugging support",
)
@log_command
def dev(
@@ -839,7 +839,7 @@ def dev(
ssl_certfile: pathlib.Path | None,
ssl_keyfile: pathlib.Path | None,
):
"""CLI entrypoint for running the Agent Server."""
"""CLI entrypoint for running the LangGraph API server."""
if (ssl_certfile is None) != (ssl_keyfile is None):
raise click.UsageError(
"Both --ssl-certfile and --ssl-keyfile must be provided to enable HTTPS."
@@ -1055,7 +1055,7 @@ def prepare(
image: str | None = None,
base_image: str | None = None,
) -> tuple[list[str], str]:
"""Prepare the arguments and stdin for running the Agent Server."""
"""Prepare the arguments and stdin for running the LangGraph API server."""
config_json = langgraph_cli.config.validate_config_file(config_path)
warn_non_wolfi_distro(config_json)
# pull latest images
+84 -104
View File
@@ -138,7 +138,7 @@ class BuildResult:
progress_message: str = ""
timeout_seconds: int = 300
poll_interval_seconds: int = 1
no_result_message: str = "Agent Server updated"
no_result_message: str = "Deployment updated"
on_poll: Callable[[str, str, Callable[[str], None]], None] | None = None
on_interrupt: Callable[[str], None] | None = None
show_build_logs_on_failure: bool = False
@@ -465,7 +465,7 @@ def deployment_selector(deployment_id: str | None, name: str | None) -> ById | B
return ById(deployment_id)
if name:
return ByName(name)
raise click.UsageError("Either --agent-server-id or --name is required.")
raise click.UsageError("Either --deployment-id or --name is required.")
def validate_deploy_commands(
@@ -513,9 +513,9 @@ def find_deployment_by_name(
def normalize_name(value: str | None) -> str:
"""Sanitize an Agent Server/directory name into a valid Agent Server name.
"""Sanitize a deployment/directory name into a valid deployment name.
Agent Server names only allow lowercase
LangSmith Deployment names only allow lowercase
alphanumeric characters and hyphens ([a-z0-9-]).
Invalid characters are replaced with hyphens.
"""
@@ -571,8 +571,8 @@ def _validate_prebuilt_image(
if image_platform != _DEPLOYMENT_PLATFORM:
detected = image_platform or "unknown"
raise click.ClickException(
f"Docker image '{image}' targets {detected}, but LangSmith Agent Server "
f"hosting requires {_DEPLOYMENT_PLATFORM}. Rebuild or pull the image for "
f"Docker image '{image}' targets {detected}, but LangSmith Deployment "
f"requires {_DEPLOYMENT_PLATFORM}. Rebuild or pull the image for "
f"{_DEPLOYMENT_PLATFORM} before deploying with --image."
)
_get_emitter().info(f"Image is available for {_DEPLOYMENT_PLATFORM}")
@@ -643,7 +643,7 @@ class DeploymentRow:
identifier = _text(deployment, "id")
if identifier is None:
raise HostBackendError(
"The control plane returned an Agent Server without an id."
"The control plane returned a deployment without an id."
)
return cls(identifier, _text(deployment, "name"), _deployment_url(deployment))
@@ -655,7 +655,7 @@ class DeploymentRow:
DEPLOYMENTS_TABLE = Table(
("Agent Server ID", "Agent Server Name", "Agent Server URL"), DeploymentRow.cells
("Deployment ID", "Deployment Name", "Deployment URL"), DeploymentRow.cells
)
LISTENERS_TABLE = Table(("Listener ID", "Compute ID", "Namespaces"), Listener.cells)
@@ -806,7 +806,7 @@ def _log_deploy_step(step: int, message: str, **extra: object) -> None:
def _fetch_deployment(
client: HostBackendClient, step: int, selector: ById
) -> tuple[ExistingDeployment, int]:
_log_deploy_step(step, f"Using Agent Server {selector.deployment_id}")
_log_deploy_step(step, f"Using deployment {selector.deployment_id}")
resource = _call_host_backend_with_optional_tenant(
client, lambda c: c.get_deployment(selector.deployment_id)
)
@@ -833,7 +833,7 @@ def _find_deployment(
)
if len(existing) > 1:
raise click.ClickException(
"This control plane does not filter Agent Servers by agent, so the "
"This control plane does not filter deployments by agent, so the "
f"CLI cannot tell which one belongs to '{selector.agent_id}' in "
f"{selector.environment}. Deploy by --name instead."
)
@@ -846,7 +846,7 @@ def _find_deployment(
None,
)
else:
_log_deploy_step(step, f"Looking up Agent Server '{selector.name}'")
_log_deploy_step(step, f"Looking up deployment '{selector.name}'")
found = _call_host_backend_with_optional_tenant(
client, lambda c: find_deployment_by_name(c, selector.name)
)
@@ -854,7 +854,7 @@ def _find_deployment(
if found is None:
em.warn(not_found_message)
else:
em.info(f"Found existing Agent Server (ID: {found.id})")
em.info(f"Found existing deployment (ID: {found.id})")
return found, step + 1
@@ -877,9 +877,9 @@ def _create_deployment(
) -> tuple[CreatedDeployment, int]:
_log_deploy_step(
step,
f"Creating Agent Server for agent '{agent['agent_id']}' in {agent['environment']}"
f"Creating deployment for agent '{agent['agent_id']}' in {agent['environment']}"
if agent is not None
else f"Creating Agent Server '{name}'",
else f"Creating deployment '{name}'",
)
try:
created = client.create_deployment(
@@ -893,7 +893,7 @@ def _create_deployment(
except HostBackendError as err:
if agent is not None and err.status_code == 409:
raise HostBackendError(
"This agent already has an Agent Server in this environment.",
"This agent already has a deployment in this environment.",
status_code=409,
) from None
raise
@@ -903,8 +903,8 @@ def _create_deployment(
"POST /v2/deployments succeeded but response missing a valid 'id'"
)
if agent is not None:
_get_emitter().info(f"Agent Server name: {created.get('name')}")
_get_emitter().info(f"Agent Server ID: {created_id}", deployment_id=created_id)
_get_emitter().info(f"Deployment name: {created.get('name')}")
_get_emitter().info(f"Deployment ID: {created_id}", deployment_id=created_id)
return CreatedDeployment(created_id, created), step + 1
@@ -1324,10 +1324,10 @@ def _run_local_build(
in err.message
):
raise click.ClickException(
f"Agent Server '{deployment_id}' was not created by 'langgraph deploy' "
f"Deployment '{deployment_id}' was not created by 'langgraph deploy' "
"and cannot be updated with this command.\n"
"Please create a new Agent Server by running 'langgraph deploy' "
"without --agent-server-id, or use a different --name."
"Please create a new deployment by running 'langgraph deploy' "
"without --deployment-id, or use a different --name."
) from None
raise
deployment_token = push_data.get("token")
@@ -1391,7 +1391,7 @@ def _run_local_build(
verbose=verbose,
)
_log_deploy_step(step, f"Updating Agent Server {deployment_id}")
_log_deploy_step(step, f"Updating deployment {deployment_id}")
updated = client.update_deployment(
deployment_id,
resolved_image,
@@ -1400,7 +1400,7 @@ def _run_local_build(
tracked_packages=tracked_packages,
)
return _image_revision_result(updated, "Agent Server updated")
return _image_revision_result(updated, "Deployment updated")
def _run_remote_build(
@@ -1488,7 +1488,7 @@ def _run_remote_build(
def _handle_interrupt(revision_id: str) -> None:
em.warn(
f"\nInterrupted. Agent Server ID: {deployment_id}, Revision ID: {revision_id}"
f"\nInterrupted. Deployment ID: {deployment_id}, Revision ID: {revision_id}"
)
em.warn("The build will continue remotely.")
@@ -1558,7 +1558,7 @@ def _resolve_or_create(
"Scale to zero deployments are not supported in AWS environments"
):
raise HostBackendError(
f"{err.message}\nUse --agent-server-type dev instead.",
f"{err.message}\nUse --deployment-type dev instead.",
status_code=err.status_code,
detail=err.detail,
) from None
@@ -1613,10 +1613,10 @@ def _available_listeners(client: HostBackendClient) -> tuple[Listener, ...]:
def _ensure_customer_registry_source(existing: ExistingDeployment) -> None:
if existing.source != _CUSTOMER_REGISTRY_SOURCE:
raise click.UsageError(
f"Agent Server {existing.id} was not created from an external image "
f"Deployment {existing.id} was not created from an external image "
"and cannot be updated with --push-to or --image-uri. Run without "
"either flag to keep its current build mode, or use a different "
"--name to create a new Agent Server."
"--name to create a new deployment."
)
@@ -1630,7 +1630,7 @@ class ManagedRegistrySource:
deployment_id, step = _resolve_or_create(
ctx,
source="internal_docker",
not_found_message="No Agent Server found. Will create after build.",
not_found_message="No deployment found. Will create after build.",
)
build_result = _run_local_build(
client=ctx.client,
@@ -1654,7 +1654,7 @@ class RemoteBuildSource:
deployment_id, step = _resolve_or_create(
ctx,
source="internal_source",
not_found_message="No Agent Server found. Will create.",
not_found_message="No deployment found. Will create.",
)
build_result = _run_remote_build(
client=ctx.client,
@@ -1723,24 +1723,24 @@ class CreateOnlySettings:
if push_to is None and image_uri is None and self.requested:
raise click.UsageError(
"--listener-id, --k8s-namespace, and --image-pull-secret only "
"apply when creating an Agent Server with --push-to or --image-uri."
"apply when creating a deployment with --push-to or --image-uri."
)
def ensure_not_existing(self, deployment_id: str) -> None:
if self.placement.requested:
raise click.UsageError(
"Listener and namespace are fixed when an Agent Server is "
f"created. Agent Server {deployment_id} already exists, so "
"Listener and namespace are fixed when a deployment is "
f"created. Deployment {deployment_id} already exists, so "
"drop --listener-id and --k8s-namespace, or create a new "
"Agent Server with a different --name."
"deployment with a different --name."
)
if self.image_pull_secrets:
raise click.UsageError(
"langgraph deploy does not update image pull secrets on an "
f"existing Agent Server. Agent Server {deployment_id} already "
f"existing deployment. Deployment {deployment_id} already "
"exists, so drop --image-pull-secret and update resource_spec "
"through the API or the LangSmith UI instead, or create a "
"new Agent Server with a different --name."
"new deployment with a different --name."
)
@@ -1757,7 +1757,7 @@ class CustomerRegistrySource:
ctx.client,
1,
ctx.selector,
not_found_message="No Agent Server found. Will create after push.",
not_found_message="No deployment found. Will create after push.",
)
if found is not None:
return self._update(ctx, found, step)
@@ -1771,7 +1771,7 @@ class CustomerRegistrySource:
_ensure_customer_registry_source(existing)
self.create_only.ensure_not_existing(existing.id)
image_uri, step = self._publish(ctx, step)
_log_deploy_step(step, f"Updating Agent Server {existing.id}")
_log_deploy_step(step, f"Updating deployment {existing.id}")
updated = ctx.client.update_deployment(
existing.id,
image_uri,
@@ -1780,7 +1780,7 @@ class CustomerRegistrySource:
tracked_packages=ctx.tracked_packages,
)
return DeployOutcome(
existing.id, _image_revision_result(updated, "Agent Server updated")
existing.id, _image_revision_result(updated, "Deployment updated")
)
def _resolve_placement(self, ctx: DeployContext) -> Placement:
@@ -1829,7 +1829,7 @@ class CustomerRegistrySource:
) from None
raise
return DeployOutcome(
created.id, _image_revision_result(created.resource, "Agent Server created")
created.id, _image_revision_result(created.resource, "Deployment created")
)
def _publish(self, ctx: DeployContext, step: int) -> tuple[str, int]:
@@ -2052,7 +2052,7 @@ def _call_host_backend_with_optional_tenant(
if err.status_code == 403 and "not enabled" in err.message.lower():
smith_base = client.endpoints.dashboard_url
raise HostBackendError(
"LangSmith Agent Server hosting is not enabled for this organization. "
"LangSmith Deployment is not enabled for this organization. "
f"Enable it at {smith_base}/host/deployments"
" (ensure this matches the organization for your API key).",
status_code=403,
@@ -2064,15 +2064,6 @@ def _call_host_backend_with_optional_tenant(
# Click options shared by deploy commands
# ---------------------------------------------------------------------------
def _normalize_deploy_token(token: str) -> str:
"""Accept legacy option names without advertising them in help or errors."""
return {
"deployment-id": "agent-server-id",
"deployment-type": "agent-server-type",
}.get(token, token)
OPT_HOST_API_KEY = click.option(
"--api-key",
envvar="LANGGRAPH_HOST_API_KEY",
@@ -2086,9 +2077,9 @@ OPT_HOST_DEPLOYMENT_NAME = click.option(
"--name",
envvar=_DEPLOYMENT_NAME_ENV,
help=(
"Agent Server name. Can also be set via LANGSMITH_DEPLOYMENT_NAME "
"Deployment name. Can also be set via LANGSMITH_DEPLOYMENT_NAME "
"environment variable or .env file. Defaults to current directory name "
"if --agent-server-id is not provided."
"if --deployment-id is not provided."
),
)
@@ -2216,21 +2207,19 @@ def _deploy_base_options(
OPT_AGENT_ID,
OPT_AGENT_ENVIRONMENT,
click.option(
"--agent-server-id",
"deployment_id",
"--deployment-id",
help=(
"ID of an existing Agent Server to update. If omitted, "
"--name is used to find or create the Agent Server."
"ID of an existing deployment to update. If omitted, "
"--name is used to find or create the deployment."
),
),
click.option(
"--agent-server-type",
"deployment_type",
"--deployment-type",
type=click.Choice(["serverless", "dedicated", "dev", "prod"]),
default="serverless",
show_default=True,
help=(
"Agent Server type (used when creating a new Agent Server): "
"Deployment type (used when creating a new deployment): "
"serverless (scale to zero) or dedicated (prod); "
"legacy dev and prod are also accepted. "
"Ignored with --push-to."
@@ -2249,7 +2238,7 @@ def _deploy_base_options(
"--tag",
"-t",
default=None,
help="Tag to use for the pushed Agent Server image. [default: latest]",
help="Tag to use for the pushed deployment image. [default: latest]",
),
click.option(
"--image",
@@ -2282,16 +2271,16 @@ def _deploy_base_options(
click.option(
"--listener-id",
help=(
"Listener that will run the Agent Server, for workspaces that "
"Listener that will run the deployment, for workspaces that "
"deploy through a listener in your own cluster. Only used when "
"creating an Agent Server with --push-to or --image-uri."
"creating a deployment with --push-to or --image-uri."
),
),
click.option(
"--k8s-namespace",
help=(
"Kubernetes namespace the listener deploys into. Only used when "
"creating an Agent Server with --push-to or --image-uri."
"creating a deployment with --push-to or --image-uri."
),
),
click.option(
@@ -2300,9 +2289,9 @@ def _deploy_base_options(
multiple=True,
help=(
"Name of a Kubernetes secret (type kubernetes.io/"
"dockerconfigjson) in the Agent Server's namespace to use for "
"dockerconfigjson) in the deployment's namespace to use for "
"pulling the image from a private registry. Repeat to give "
"more than one. Only used when creating an Agent Server with "
"more than one. Only used when creating a deployment with "
"--push-to or --image-uri."
),
),
@@ -2369,18 +2358,14 @@ def _deploy_base_options(
@click.group(
cls=DeployGroup,
help=(
"[Beta] Build and deploy an Agent Server image to LangSmith.\n\n"
"[Beta] Build and deploy a LangGraph image to LangSmith Deployment.\n\n"
"This command is in beta and under active development. "
"Expect frequent updates and improvements.\n\n"
"Run from the root of your LangGraph project (where langgraph.json "
"is located). This command also accepts build flags (--base-image, "
"--config, --pull, etc.). See 'langgraph build --help' for details."
),
context_settings=dict(
ignore_unknown_options=True,
allow_extra_args=True,
token_normalize_func=_normalize_deploy_token,
),
context_settings=dict(ignore_unknown_options=True, allow_extra_args=True),
invoke_without_command=True, # allow `deploy` click group to execute without command
)
@_deploy_base_options(include_docker_args=False, validate_config_path=False)
@@ -2404,11 +2389,7 @@ def deploy(ctx: click.Context, **_: object):
@_deploy_base_options()
@click.command(
context_settings=dict(
ignore_unknown_options=True, token_normalize_func=_normalize_deploy_token
)
)
@click.command(context_settings=dict(ignore_unknown_options=True))
def _deploy_cmd(
config: pathlib.Path,
pull: bool,
@@ -2458,13 +2439,13 @@ def _deploy_cmd(
)
if name is not None or deployment_id is not None:
raise click.UsageError(
"--agent-id and --agent-environment cannot be combined with --name or --agent-server-id."
"--agent-id and --agent-environment cannot be combined with --name or --deployment-id."
)
agent = {"agent_id": agent_id, "environment": environment}
if not config.exists():
message = (
"We couldn't find a langgraph.json file. Run `langgraph deploy` from "
"the root of your LangGraph project. To get started, visit "
"the root of a LangSmith Deployment project. To get started, visit "
"https://docs.langchain.com/langsmith/deployment-quickstart."
)
if json_output:
@@ -2483,14 +2464,14 @@ def _deploy_cmd(
if no_input:
name = default_name
else:
name = click.prompt("Agent Server name", default=default_name)
name = click.prompt("Deployment name", default=default_name)
if name and not deployment_id:
name = normalize_name(name)
if not no_input:
env_path = _resolve_env_path(config_json, config)
if env_path is not None:
set_key(str(env_path), _DEPLOYMENT_NAME_ENV, name)
em.info(f"Saved Agent Server name to {env_path}")
em.info(f"Saved deployment name to {env_path}")
secrets = _secrets_from_env(_env_without_deployment_name(env_vars))
@@ -2587,7 +2568,7 @@ def _deploy_cmd(
client,
outcome.deployment_id,
last_status,
dashboard_label="Agent Server dashboard",
dashboard_label="Deployment dashboard",
status_url=dep_status_url,
)
@@ -2604,16 +2585,16 @@ def _deploy_cmd(
@click.option(
"--name-contains",
default="",
help="Only show Agent Servers whose names contain this value.",
help="Only show deployments whose names contain this value.",
)
@click.option(
"--json",
"json_output",
is_flag=True,
default=False,
help="Print the Agent Servers as a JSON array of {id, name} instead of a table.",
help="Print the deployments as a JSON array of {id, name} instead of a table.",
)
@deploy.command("list", help="[Beta] List Agent Servers.")
@deploy.command("list", help="[Beta] List LangSmith Deployments.")
def deploy_list(
api_key: str | None,
host_url: str | None,
@@ -2645,7 +2626,7 @@ def deploy_list(
if json_output:
click.echo(json_mod.dumps([deployment.as_json() for deployment in deployments]))
elif not deployments:
click.echo("No Agent Servers found.")
click.echo("No deployments found.")
else:
click.echo(DEPLOYMENTS_TABLE.render(deployments))
@@ -2657,7 +2638,7 @@ def deploy_list(
@OPT_HOST_API_KEY
@OPT_HOST_URL
@click.argument("deployment_id", type=click.UUID, metavar="AGENT_SERVER_ID")
@click.argument("deployment_id", type=click.UUID)
@click.option(
"--start-time",
help="ISO8601 start time. Defaults to 15 minutes before --end-time.",
@@ -2670,7 +2651,7 @@ def deploy_list(
"memory_usage, cpu_usage, disk_usage, restart_count, replica_count, "
"worker_count, lg_run_count, responses_per_second, "
"error_responses_per_second, p95_latency, run_queue_wait_time. "
"Availability depends on the Agent Server and platform."
"Availability depends on the deployment and platform."
),
)
@click.option(
@@ -2680,7 +2661,7 @@ def deploy_list(
"--raw", is_flag=True, help="Print the original host-backend chart schema."
)
@deploy.command(
"metrics", help="[Beta] Fetch Agent Server monitoring time series as compact JSON."
"metrics", help="[Beta] Fetch deployment monitoring time series as compact JSON."
)
def deploy_metrics(
api_key: str | None,
@@ -2781,7 +2762,7 @@ def deploy_listeners_list(
@deploy.group(
"revisions", cls=NestedHelpGroup, help="[Beta] Manage Agent Server revisions."
"revisions", cls=NestedHelpGroup, help="[Beta] Manage deployment revisions."
)
def deploy_revisions() -> None:
pass
@@ -2796,12 +2777,12 @@ def deploy_revisions() -> None:
show_default=True,
help="Maximum number of revisions to return.",
)
@click.argument("deployment_id", metavar="AGENT_SERVER_ID")
@click.argument("deployment_id")
@deploy_revisions.command(
"list",
help=(
"[Beta] List revisions for an Agent Server.\n\n"
"Use the `deploy list` command to list Agent Server IDs."
"[Beta] List revisions for a LangSmith Deployment.\n\n"
"Use the `deploy list` command to list deployment IDs."
),
)
def deploy_revisions_list(
@@ -2813,7 +2794,7 @@ def deploy_revisions_list(
lambda c: c.list_revisions(deployment_id, limit=limit),
)
if not revisions:
click.echo(f"No revisions found for Agent Server {deployment_id}.")
click.echo(f"No revisions found for deployment {deployment_id}.")
return
click.echo(REVISIONS_TABLE.render(_with_replaced_status(revisions)))
@@ -2831,12 +2812,12 @@ def deploy_revisions_list(
default=False,
help="Delete without prompting for confirmation.",
)
@click.argument("deployment_id", metavar="AGENT_SERVER_ID")
@click.argument("deployment_id")
@deploy.command(
"delete",
help=(
"[Beta] Delete an Agent Server.\n\n"
"Use the `deploy list` command to list Agent Server IDs."
"[Beta] Delete a LangSmith Deployment.\n\n"
"Use the `deploy list` command to list deployment IDs."
),
)
def deploy_delete(
@@ -2845,7 +2826,7 @@ def deploy_delete(
if not force:
response = click.prompt(
click.style(
f"Are you sure you want to delete Agent Server ID {deployment_id}? (Y/n)",
f"Are you sure you want to delete deployment ID {deployment_id}? (Y/n)",
fg="yellow",
),
default="Y",
@@ -2858,7 +2839,7 @@ def deploy_delete(
client,
lambda c: c.delete_deployment(deployment_id),
)
click.secho(f"Deleted Agent Server {deployment_id}.", fg="green")
click.secho(f"Deleted deployment {deployment_id}.", fg="green")
# ---------------------------------------------------------------------------
@@ -2869,9 +2850,8 @@ def deploy_delete(
@OPT_HOST_API_KEY
@OPT_HOST_DEPLOYMENT_NAME
@click.option(
"--agent-server-id",
"deployment_id",
help="Agent Server ID. If omitted, --name is used to find the Agent Server.",
"--deployment-id",
help="Deployment ID. If omitted, --name is used to find the deployment.",
)
@click.option(
"--type",
@@ -2880,8 +2860,8 @@ def deploy_delete(
default="deploy",
show_default=True,
help=(
"Log stream to fetch: 'deploy' shows Agent Server runtime logs; "
"'build' shows build logs (for Agent Servers built remotely)."
"Log stream to fetch: 'deploy' shows agent server runtime logs; "
"'build' shows build logs (for deployments built remotely)."
),
)
@click.option(
@@ -2926,7 +2906,7 @@ def deploy_delete(
@deploy.command(
"logs",
help=(
"[Beta] Fetch Agent Server logs. Use 'deploy' for Agent Server runtime "
"[Beta] Fetch LangSmith Deployment logs. Use 'deploy' for agent runtime "
"logs, or 'build' for remote build logs."
),
)
@@ -2958,14 +2938,14 @@ def deploy_logs(
client, lambda c: find_deployment_by_name(c, name_to_find)
)
if found is None:
raise click.ClickException(f"Agent Server '{name_to_find}' not found.")
raise click.ClickException(f"Deployment '{name_to_find}' not found.")
dep_id = found.id
if log_type == "build" and not revision_id:
revisions = client.list_revisions(dep_id, limit=1)
if not revisions:
raise click.ClickException(
"No revisions found for this Agent Server. Cannot fetch build logs."
"No revisions found for this deployment. Cannot fetch build logs."
)
revision_id = str(revisions[0]["id"])
click.secho(f"Using latest revision: {revision_id}", fg="cyan")
+1 -1
View File
@@ -172,7 +172,7 @@ def compose_as_dict(
postgres_uri: str | None = None,
# If you are running against an already-built image, you can pass it here
image: str | None = None,
# Base image to use for the Agent Server
# Base image to use for the LangGraph API server
base_image: str | None = None,
# API version of the base image
api_version: str | None = None,
+9 -9
View File
@@ -317,7 +317,7 @@ class AuthConfig(TypedDict, total=False):
"""Optional. Whether to disable LangSmith API-key authentication for requests originating the Studio.
Defaults to False, meaning that if a particular header is set, the server will verify the `x-api-key` header
value is a valid API key for the Agent Server's workspace. If `True`, all requests will go through your custom
value is a valid API key for the deployment's workspace. If `True`, all requests will go through your custom
authentication logic, regardless of origin of the request.
"""
openapi: SecurityConfig
@@ -461,7 +461,7 @@ class ConfigurableHeaderConfig(TypedDict, total=False):
class HttpConfig(TypedDict, total=False):
"""Configuration for the built-in HTTP server that powers your Agent Server's routes and endpoints."""
"""Configuration for the built-in HTTP server that powers your deployment's routes and endpoints."""
app: str
"""Optional. Import path to a custom Starlette/FastAPI application to mount.
@@ -490,12 +490,12 @@ class HttpConfig(TypedDict, total=False):
Default is False.
"""
disable_mcp: bool
"""Optional. If `True`, /mcp routes are removed, disabling default support to expose the Agent Server as an MCP server.
"""Optional. If `True`, /mcp routes are removed, disabling default support to expose the deployment as an MCP server.
Default is False.
"""
disable_a2a: bool
"""Optional. If `True`, /a2a routes are removed, disabling default support to expose the Agent Server as an agent-to-agent (A2A) server.
"""Optional. If `True`, /a2a routes are removed, disabling default support to expose the deployment as an agent-to-agent (A2A) server.
Default is False.
"""
@@ -638,16 +638,16 @@ class Config(TypedDict, total=False):
python_version: str
"""Optional. Python version in 'major.minor' format (e.g. '3.11').
Must be at least 3.11 or greater for this Agent Server to function properly.
Must be at least 3.11 or greater for this deployment to function properly.
"""
node_version: str | None
"""Optional. Node.js version as a major version (e.g. '20'), if your Agent Server needs Node.
"""Optional. Node.js version as a major version (e.g. '20'), if your deployment needs Node.
Must be >= 20 if provided.
"""
api_version: str | None
"""Optional. Which semantic version of the Agent Server to use.
"""Optional. Which semantic version of the LangGraph API server to use.
Defaults to latest. Check the
[changelog](https://docs.langchain.com/langgraph-platform/langgraph-server-changelog)
@@ -658,7 +658,7 @@ class Config(TypedDict, total=False):
"""
base_image: str | None
"""Optional. Base image to use for the Agent Server.
"""Optional. Base image to use for the LangGraph API server.
Defaults to langchain/langgraph-api or langchain/langgraphjs-api."""
@@ -740,7 +740,7 @@ class Config(TypedDict, total=False):
"""
env: dict[str, str] | str
"""Optional. Environment variables to set for your Agent Server.
"""Optional. Environment variables to set for your deployment.
- If given as a dict, keys are variable names and values are their values.
- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.
+16 -16
View File
@@ -12,7 +12,7 @@
"properties": {
"python_version": {
"type": "string",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this Agent Server to function properly.\n",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this deployment to function properly.\n",
"enum": [
"3.11",
"3.12",
@@ -50,7 +50,7 @@
"type": "null"
}
],
"description": "Optional. Which semantic version of the Agent Server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
"description": "Optional. Which semantic version of the LangGraph API server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
},
"auth": {
"anyOf": [
@@ -72,7 +72,7 @@
"type": "null"
}
],
"description": "Optional. Base image to use for the Agent Server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
"description": "Optional. Base image to use for the LangGraph API server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
},
"checkpointer": {
"anyOf": [
@@ -122,7 +122,7 @@
"type": "string"
}
],
"description": "Optional. Environment variables to set for your Agent Server.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
"description": "Optional. Environment variables to set for your deployment.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
},
"graphs": {
"type": "object",
@@ -265,7 +265,7 @@
"properties": {
"python_version": {
"type": "string",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this Agent Server to function properly.\n",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this deployment to function properly.\n",
"enum": [
"3.11",
"3.12",
@@ -303,7 +303,7 @@
"type": "null"
}
],
"description": "Optional. Which semantic version of the Agent Server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
"description": "Optional. Which semantic version of the LangGraph API server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
},
"auth": {
"anyOf": [
@@ -325,7 +325,7 @@
"type": "null"
}
],
"description": "Optional. Base image to use for the Agent Server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
"description": "Optional. Base image to use for the LangGraph API server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
},
"checkpointer": {
"anyOf": [
@@ -375,7 +375,7 @@
"type": "string"
}
],
"description": "Optional. Environment variables to set for your Agent Server.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
"description": "Optional. Environment variables to set for your deployment.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
},
"graphs": {
"type": "object",
@@ -520,7 +520,7 @@
"type": "null"
}
],
"description": "Optional. Node.js version as a major version (e.g. '20'), if your Agent Server needs Node.\nMust be >= 20 if provided.\n"
"description": "Optional. Node.js version as a major version (e.g. '20'), if your deployment needs Node.\nMust be >= 20 if provided.\n"
},
"_INTERNAL_docker_tag": {
"anyOf": [
@@ -542,7 +542,7 @@
"type": "null"
}
],
"description": "Optional. Which semantic version of the Agent Server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
"description": "Optional. Which semantic version of the LangGraph API server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
},
"auth": {
"anyOf": [
@@ -564,7 +564,7 @@
"type": "null"
}
],
"description": "Optional. Base image to use for the Agent Server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
"description": "Optional. Base image to use for the LangGraph API server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
},
"checkpointer": {
"anyOf": [
@@ -614,7 +614,7 @@
"type": "string"
}
],
"description": "Optional. Environment variables to set for your Agent Server.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
"description": "Optional. Environment variables to set for your deployment.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
},
"graphs": {
"type": "object",
@@ -765,7 +765,7 @@
},
"disable_studio_auth": {
"type": "boolean",
"description": "Optional. Whether to disable LangSmith API-key authentication for requests originating the Studio.\n\nDefaults to False, meaning that if a particular header is set, the server will verify the `x-api-key` header\nvalue is a valid API key for the Agent Server's workspace. If `True`, all requests will go through your custom\nauthentication logic, regardless of origin of the request.\n"
"description": "Optional. Whether to disable LangSmith API-key authentication for requests originating the Studio.\n\nDefaults to False, meaning that if a particular header is set, the server will verify the `x-api-key` header\nvalue is a valid API key for the deployment's workspace. If `True`, all requests will go through your custom\nauthentication logic, regardless of origin of the request.\n"
},
"openapi": {
"$ref": "#/$defs/SecurityConfig",
@@ -1018,7 +1018,7 @@
},
"HttpConfig": {
"title": "HttpConfig",
"description": "Configuration for the built-in HTTP server that powers your Agent Server's routes and endpoints.",
"description": "Configuration for the built-in HTTP server that powers your deployment's routes and endpoints.",
"type": "object",
"properties": {
"app": {
@@ -1049,7 +1049,7 @@
},
"disable_a2a": {
"type": "boolean",
"description": "Optional. If `True`, /a2a routes are removed, disabling default support to expose the Agent Server as an agent-to-agent (A2A) server.\n\nDefault is False.\n"
"description": "Optional. If `True`, /a2a routes are removed, disabling default support to expose the deployment as an agent-to-agent (A2A) server.\n\nDefault is False.\n"
},
"disable_assistants": {
"type": "boolean",
@@ -1057,7 +1057,7 @@
},
"disable_mcp": {
"type": "boolean",
"description": "Optional. If `True`, /mcp routes are removed, disabling default support to expose the Agent Server as an MCP server.\n\nDefault is False.\n"
"description": "Optional. If `True`, /mcp routes are removed, disabling default support to expose the deployment as an MCP server.\n\nDefault is False.\n"
},
"disable_meta": {
"type": "boolean",
+16 -16
View File
@@ -12,7 +12,7 @@
"properties": {
"python_version": {
"type": "string",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this Agent Server to function properly.\n",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this deployment to function properly.\n",
"enum": [
"3.11",
"3.12",
@@ -50,7 +50,7 @@
"type": "null"
}
],
"description": "Optional. Which semantic version of the Agent Server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
"description": "Optional. Which semantic version of the LangGraph API server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
},
"auth": {
"anyOf": [
@@ -72,7 +72,7 @@
"type": "null"
}
],
"description": "Optional. Base image to use for the Agent Server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
"description": "Optional. Base image to use for the LangGraph API server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
},
"checkpointer": {
"anyOf": [
@@ -122,7 +122,7 @@
"type": "string"
}
],
"description": "Optional. Environment variables to set for your Agent Server.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
"description": "Optional. Environment variables to set for your deployment.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
},
"graphs": {
"type": "object",
@@ -265,7 +265,7 @@
"properties": {
"python_version": {
"type": "string",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this Agent Server to function properly.\n",
"description": "Optional. Python version in 'major.minor' format (e.g. '3.11').\nMust be at least 3.11 or greater for this deployment to function properly.\n",
"enum": [
"3.11",
"3.12",
@@ -303,7 +303,7 @@
"type": "null"
}
],
"description": "Optional. Which semantic version of the Agent Server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
"description": "Optional. Which semantic version of the LangGraph API server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
},
"auth": {
"anyOf": [
@@ -325,7 +325,7 @@
"type": "null"
}
],
"description": "Optional. Base image to use for the Agent Server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
"description": "Optional. Base image to use for the LangGraph API server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
},
"checkpointer": {
"anyOf": [
@@ -375,7 +375,7 @@
"type": "string"
}
],
"description": "Optional. Environment variables to set for your Agent Server.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
"description": "Optional. Environment variables to set for your deployment.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
},
"graphs": {
"type": "object",
@@ -520,7 +520,7 @@
"type": "null"
}
],
"description": "Optional. Node.js version as a major version (e.g. '20'), if your Agent Server needs Node.\nMust be >= 20 if provided.\n"
"description": "Optional. Node.js version as a major version (e.g. '20'), if your deployment needs Node.\nMust be >= 20 if provided.\n"
},
"_INTERNAL_docker_tag": {
"anyOf": [
@@ -542,7 +542,7 @@
"type": "null"
}
],
"description": "Optional. Which semantic version of the Agent Server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
"description": "Optional. Which semantic version of the LangGraph API server to use.\n\nDefaults to latest. Check the\nfor more information.\n"
},
"auth": {
"anyOf": [
@@ -564,7 +564,7 @@
"type": "null"
}
],
"description": "Optional. Base image to use for the Agent Server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
"description": "Optional. Base image to use for the LangGraph API server.\n\nDefaults to langchain/langgraph-api or langchain/langgraphjs-api.\n"
},
"checkpointer": {
"anyOf": [
@@ -614,7 +614,7 @@
"type": "string"
}
],
"description": "Optional. Environment variables to set for your Agent Server.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
"description": "Optional. Environment variables to set for your deployment.\n\n- If given as a dict, keys are variable names and values are their values.\n- If given as a string, it must be a path to a file containing lines in KEY=VALUE format.\n\nenv=\".env\n"
},
"graphs": {
"type": "object",
@@ -765,7 +765,7 @@
},
"disable_studio_auth": {
"type": "boolean",
"description": "Optional. Whether to disable LangSmith API-key authentication for requests originating the Studio.\n\nDefaults to False, meaning that if a particular header is set, the server will verify the `x-api-key` header\nvalue is a valid API key for the Agent Server's workspace. If `True`, all requests will go through your custom\nauthentication logic, regardless of origin of the request.\n"
"description": "Optional. Whether to disable LangSmith API-key authentication for requests originating the Studio.\n\nDefaults to False, meaning that if a particular header is set, the server will verify the `x-api-key` header\nvalue is a valid API key for the deployment's workspace. If `True`, all requests will go through your custom\nauthentication logic, regardless of origin of the request.\n"
},
"openapi": {
"$ref": "#/$defs/SecurityConfig",
@@ -1018,7 +1018,7 @@
},
"HttpConfig": {
"title": "HttpConfig",
"description": "Configuration for the built-in HTTP server that powers your Agent Server's routes and endpoints.",
"description": "Configuration for the built-in HTTP server that powers your deployment's routes and endpoints.",
"type": "object",
"properties": {
"app": {
@@ -1049,7 +1049,7 @@
},
"disable_a2a": {
"type": "boolean",
"description": "Optional. If `True`, /a2a routes are removed, disabling default support to expose the Agent Server as an agent-to-agent (A2A) server.\n\nDefault is False.\n"
"description": "Optional. If `True`, /a2a routes are removed, disabling default support to expose the deployment as an agent-to-agent (A2A) server.\n\nDefault is False.\n"
},
"disable_assistants": {
"type": "boolean",
@@ -1057,7 +1057,7 @@
},
"disable_mcp": {
"type": "boolean",
"description": "Optional. If `True`, /mcp routes are removed, disabling default support to expose the Agent Server as an MCP server.\n\nDefault is False.\n"
"description": "Optional. If `True`, /mcp routes are removed, disabling default support to expose the deployment as an MCP server.\n\nDefault is False.\n"
},
"disable_meta": {
"type": "boolean",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$id": "https://github.com/langchain-ai/langgraph/libs/cli/schemas/version.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Agent Server configuration (when building via the langgraph-cli).",
"title": "LangGraph Platform configuration (when building via the langgraph-cli).",
"type": "object",
"oneOf": [
{
+18 -20
View File
@@ -352,7 +352,7 @@ def test_top_level_help_shows_deploy_subcommands() -> None:
assert "deploy delete" in result.output
assert "deploy revisions" in result.output
assert "deploy revisions list" in result.output
assert "[Beta] List Agent Servers." in result.output
assert "[Beta] List LangSmith Deployments." in result.output
def test_top_level_help_truncates_command_descriptions_to_single_line() -> None:
@@ -369,7 +369,7 @@ def test_top_level_help_truncates_command_descriptions_to_single_line() -> None:
assert not lines[lines.index(deploy_line) + 1].startswith(" ")
assert "..." in deploy_line
assert "[Beta] List Agent Servers." in deploy_list_line
assert "[Beta] List LangSmith Deployments." in deploy_list_line
def test_deploy_missing_config_shows_actionable_error(tmp_path, monkeypatch) -> None:
@@ -496,9 +496,9 @@ def test_deploy_list_command(monkeypatch) -> None:
"tenant_id": "",
"name_contains": "alp",
}
assert "Agent Server ID" in result.output
assert "Agent Server Name" in result.output
assert "Agent Server URL" in result.output
assert "Deployment ID" in result.output
assert "Deployment Name" in result.output
assert "Deployment URL" in result.output
assert "dep-123" in result.output
assert "https://beta.example.com" in result.output
@@ -528,7 +528,7 @@ def test_deploy_list_command_no_results(monkeypatch) -> None:
)
assert result.exit_code == 0, result.output
assert result.output.strip() == "No Agent Servers found."
assert result.output.strip() == "No deployments found."
def test_deploy_list_prints_an_aligned_table(monkeypatch) -> None:
@@ -562,10 +562,10 @@ def test_deploy_list_prints_an_aligned_table(monkeypatch) -> None:
assert result.exit_code == 0, result.output
assert result.output.splitlines() == [
"Agent Server ID Agent Server Name Agent Server URL ",
"--------------- ----------------- -------------------------",
"dep-123 alpha https://alpha.example.com",
"dep-4567 beta-agent - ",
"Deployment ID Deployment Name Deployment URL ",
"------------- --------------- -------------------------",
"dep-123 alpha https://alpha.example.com",
"dep-4567 beta-agent - ",
]
@@ -649,7 +649,7 @@ def test_deploy_list_rejects_a_deployment_without_an_id(monkeypatch) -> None:
result = _deploy_list_json(monkeypatch, [{"name": "alpha"}])
assert result.exit_code != 0
assert "Agent Server without an id" in result.output
assert "deployment without an id" in result.output
def test_deploy_list_json_keeps_the_agent_note_off_stdout(monkeypatch) -> None:
@@ -1061,7 +1061,7 @@ def test_deploy_revisions_list_command_no_results(monkeypatch) -> None:
)
assert result.exit_code == 0, result.output
assert result.output.strip() == "No revisions found for Agent Server dep-123."
assert result.output.strip() == "No revisions found for deployment dep-123."
def test_deploy_revisions_list_marks_older_deployed_revisions_as_replaced(
@@ -1145,7 +1145,7 @@ def test_deploy_revisions_list_command_with_explicit_limit(monkeypatch) -> None:
assert result.exit_code == 0, result.output
assert captured == {"deployment_id": "dep-123", "limit": "25"}
assert result.output.strip() == "No revisions found for Agent Server dep-123."
assert result.output.strip() == "No revisions found for deployment dep-123."
def test_deploy_revisions_list_missing_deployment_id_shows_usage() -> None:
@@ -1154,7 +1154,7 @@ def test_deploy_revisions_list_missing_deployment_id_shows_usage() -> None:
result = runner.invoke(cli, ["deploy", "revisions", "list"])
assert result.exit_code == 2, result.output
assert "Missing argument 'AGENT_SERVER_ID'" in result.output
assert "Missing argument 'DEPLOYMENT_ID'" in result.output
def test_deploy_delete_command(monkeypatch) -> None:
@@ -1195,10 +1195,10 @@ def test_deploy_delete_command(monkeypatch) -> None:
"deployment_id": "dep-123",
}
assert (
"Are you sure you want to delete Agent Server ID dep-123? (Y/n):"
"Are you sure you want to delete deployment ID dep-123? (Y/n):"
in click.unstyle(result.output)
)
assert result.output.strip().endswith("Deleted Agent Server dep-123.")
assert result.output.strip().endswith("Deleted deployment dep-123.")
def test_deploy_delete_command_cancelled(monkeypatch) -> None:
@@ -1266,16 +1266,14 @@ def test_deploy_delete_command_force(monkeypatch) -> None:
)
assert result.exit_code == 0, result.output
assert (
"Are you sure you want to delete Agent Server ID dep-123?" not in result.output
)
assert "Are you sure you want to delete deployment ID dep-123?" not in result.output
assert captured == {
"host_url": "https://api.example.com",
"api_key": "test-key",
"tenant_id": "",
"deployment_id": "dep-123",
}
assert result.output.strip() == "Deleted Agent Server dep-123."
assert result.output.strip() == "Deleted deployment dep-123."
def test_dockerfile_command_basic() -> None:
@@ -359,68 +359,6 @@ def deploy_project(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> DeployPro
return DeployProject(control_plane, docker, timeline, uploads)
@pytest.mark.parametrize(
("command", "expected"),
[
([], ["--agent-server-id", "--agent-server-type"]),
(["logs"], ["--agent-server-id"]),
(["delete"], ["AGENT_SERVER_ID"]),
(["revisions", "list"], ["AGENT_SERVER_ID"]),
(["metrics"], ["AGENT_SERVER_ID"]),
],
)
def test_deploy_help_uses_agent_server_names(command: list[str], expected: list[str]):
result = CliRunner().invoke(cli, ["deploy", *command, "--help"])
assert result.exit_code == 0, result.output
assert all(name in result.output for name in expected)
assert "Agent Server" in result.output
assert "--deployment-" not in result.output
assert "DEPLOYMENT_ID" not in result.output
@pytest.mark.parametrize("command", [[], ["logs"]])
@pytest.mark.parametrize("option", ["--agent-server-id", "--deployment-id"])
def test_missing_agent_server_id_uses_public_option_name(
command: list[str], option: str
):
result = CliRunner().invoke(cli, ["deploy", *command, option])
assert result.exit_code == 2, result.output
assert "Option '--agent-server-id' requires an argument" in result.output
assert "--deployment-id" not in result.output
@pytest.mark.parametrize("option", ["--agent-server-type", "--deployment-type"])
@pytest.mark.parametrize("values", [[], ["invalid"]])
def test_invalid_agent_server_type_uses_public_option_name(
option: str, values: list[str]
):
result = CliRunner().invoke(cli, ["deploy", option, *values])
assert result.exit_code == 2, result.output
assert "'--agent-server-type'" in result.output
assert "--deployment-type" not in result.output
@pytest.mark.parametrize("option", ["--agent-server-id", "--deployment-id"])
def test_deploy_logs_accepts_agent_server_id_aliases(
deploy_project, mocker, option: str
):
fetch_logs = mocker.patch.object(
HostBackendClient, "get_deploy_logs", return_value={"logs": []}
)
result = CliRunner().invoke(
cli, ["deploy", "logs", "--api-key", "test-key", option, "dep-existing"]
)
assert result.exit_code == 0, result.output
fetch_logs.assert_called_once_with(
"dep-existing", {"limit": 100, "order": "desc"}, None
)
@pytest.mark.parametrize("build_mode", ["--no-remote", "--remote"])
@pytest.mark.parametrize(
("detail", "expects_dev_hint"),
@@ -438,7 +376,7 @@ def test_create_failure_preserves_backend_error_and_only_hints_for_aws_rejection
assert result.exit_code == 1, result.output
assert detail in result.output
assert ("Use --agent-server-type dev instead." in result.output) is expects_dev_hint
assert ("Use --deployment-type dev instead." in result.output) is expects_dev_hint
assert deploy_project.control_plane.bodies[CREATE_DEPLOYMENT]["source_config"] == {
"deployment_type": "dev_zero"
}
@@ -477,7 +415,7 @@ def test_first_local_deploy_creates_then_builds_pushes_and_updates_in_order(
"docker inspect-digest",
_patch(CREATED_ID),
]
assert "Agent Server updated" in result.output
assert "Deployment updated" in result.output
@pytest.mark.parametrize(
@@ -490,15 +428,10 @@ def test_first_local_deploy_creates_then_builds_pushes_and_updates_in_order(
],
)
@pytest.mark.parametrize("build_mode", ["--no-remote", "--remote"])
@pytest.mark.parametrize("option", ["--agent-server-type", "--deployment-type"])
def test_deployment_type_aliases(
deploy_project: DeployProject,
deployment_type: str,
wire_type: str,
build_mode: str,
option: str,
deploy_project: DeployProject, deployment_type: str, wire_type: str, build_mode: str
) -> None:
result = deploy_project.run(build_mode, option, deployment_type)
result = deploy_project.run(build_mode, "--deployment-type", deployment_type)
assert result.exit_code == 0, result.output
assert deploy_project.control_plane.bodies[CREATE_DEPLOYMENT]["source_config"] == {
@@ -837,7 +770,7 @@ def test_push_to_builds_pushes_then_creates_an_external_deployment(
"source_revision_config": {"image_uri": EXTERNAL_DIGEST},
"secrets": [],
}
assert "Agent Server created" in result.output
assert "Deployment created" in result.output
def test_push_to_builds_directly_with_the_push_reference(
@@ -940,17 +873,16 @@ def test_push_to_rejects_a_non_external_deployment_before_any_docker_work(
assert deploy_project.docker.verbs() == []
@pytest.mark.parametrize("option", ["--agent-server-id", "--deployment-id"])
@pytest.mark.parametrize("equals", [False, True])
def test_push_to_with_deployment_id_fetches_the_deployment_once(
deploy_project: DeployProject, option: str, equals: bool
deploy_project: DeployProject,
) -> None:
deploy_project.control_plane.existing_deployments = [
{"id": "dep-ext", "name": "another-name", "source": "external_docker"}
]
id_args = [f"{option}=dep-ext"] if equals else [option, "dep-ext"]
result = deploy_project.run(*id_args, "--push-to", PUSH_REPOSITORY)
result = deploy_project.run(
"--deployment-id", "dep-ext", "--push-to", PUSH_REPOSITORY
)
assert result.exit_code == 0, result.output
assert deploy_project.timeline == [
@@ -1227,7 +1159,7 @@ def test_image_pull_secret_without_push_to_or_image_uri_is_refused(
result = deploy_project.run("--image-pull-secret", "regcred")
assert result.exit_code != 0
assert "only apply when creating an Agent Server with --push-to" in result.output
assert "only apply when creating a deployment with --push-to" in result.output
assert deploy_project.timeline == []
@@ -1309,7 +1241,7 @@ def test_listener_flags_are_refused_for_a_deployment_id_without_any_call(
)
assert result.exit_code != 0
assert "fixed when an Agent Server is created" in result.output
assert "fixed when a deployment is created" in result.output
assert deploy_project.timeline == []
@@ -1330,7 +1262,7 @@ def test_listener_flags_are_refused_on_an_existing_deployment(
)
assert result.exit_code != 0
assert "fixed when an Agent Server is created" in result.output
assert "fixed when a deployment is created" in result.output
assert deploy_project.docker.verbs() == []
+1 -1
View File
@@ -1204,7 +1204,7 @@ ENV LANGSERVE_GRAPHS='{{"agent": "/deps/outer-graphs/src/agent.py:graph"}}'
assert additional_contexts == {}
# node.js build used for LangSmith Agent Server
# node.js build used for LangSmith Deployment
def test_config_to_docker_nodejs():
graphs = {"agent": "./graphs/agent.js:graph"}
actual_docker_stdin, additional_contexts = config_to_docker(
@@ -118,5 +118,5 @@ def test_agent_lookup_refuses_a_control_plane_that_ignores_the_filter(deployment
result = CliRunner().invoke(cli, AGENT_ARGS)
assert result.exit_code != 0
assert "does not filter Agent Servers by agent" in result.output
assert "does not filter deployments by agent" in result.output
assert len(requests) == 1
@@ -187,7 +187,7 @@ class TestValidatePrebuiltImage:
monkeypatch.setattr(deploy_mod, "subp_exec", fake_subp_exec)
with pytest.raises(click.ClickException, match="hosting requires linux/amd64"):
with pytest.raises(click.ClickException, match="requires linux/amd64"):
_validate_prebuilt_image(_FakeRunner(), "repo/app:arm", verbose=False)
@@ -327,9 +327,6 @@ class TestCallHostBackendWithOptionalTenant:
)
assert exc_info.value.status_code == 403
assert "smith.langchain.com" in exc_info.value.message
assert exc_info.value.message.startswith(
"LangSmith Agent Server hosting is not enabled for this organization."
)
def test_403_not_enabled_eu_url(self):
detail = (
@@ -874,17 +871,17 @@ class TestSelectSource:
),
pytest.param(
{"placement": RequestedPlacement(listener_id="listener-1")},
"only apply when creating an Agent Server with --push-to",
"only apply when creating a deployment with --push-to",
id="listener_without_push_to",
),
pytest.param(
{"placement": RequestedPlacement(k8s_namespace="agents")},
"only apply when creating an Agent Server with --push-to",
"only apply when creating a deployment with --push-to",
id="namespace_without_push_to",
),
pytest.param(
{"image_pull_secrets": ("regcred",)},
"only apply when creating an Agent Server with --push-to",
"only apply when creating a deployment with --push-to",
id="image_pull_secret_without_push_to",
),
pytest.param(
@@ -1409,7 +1406,7 @@ class TestCreateOnlySettings:
settings = CreateOnlySettings(RequestedPlacement(listener_id="listener-1"))
with pytest.raises(
click.UsageError,
match="Listener and namespace are fixed when an Agent Server is created",
match="Listener and namespace are fixed when a deployment is created",
):
settings.ensure_not_existing("dep-1")
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph"
version = "1.2.20"
version = "1.2.21"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
requires-python = ">=3.10"
+1 -1
View File
@@ -1440,7 +1440,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.2.20"
version = "1.2.21"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },
+1 -1
View File
@@ -381,7 +381,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.2.20"
version = "1.2.21"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },
+1 -1
View File
@@ -394,7 +394,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.2.20"
version = "1.2.21"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },