version-done

This commit is contained in:
Quanzheng Long
2026-03-10 10:24:46 -07:00
parent db40389fbd
commit 6bd3e8b1e5
7 changed files with 278 additions and 98 deletions
+95
View File
@@ -0,0 +1,95 @@
"""Resolve the LangGraph API version from CLI flags and langgraph.json."""
import json
import pathlib
import re
import urllib.request
import click
VERSION_MARKER_REPO = "langchain/langgraph-published-version-marker"
_PATCH_VERSION_RE = re.compile(r"^\d+\.\d+\.\d+$")
_SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+")
def resolve_langgraph_api_version(
config_path: pathlib.Path,
api_version_cli_param: str | None,
) -> str:
"""Resolve the API version from the CLI flag and/or langgraph.json.
Returns the resolved patch-level version string. When neither source
provides a version, the latest published version is fetched from Docker Hub.
Raises `click.ClickException` when both sources specify a version, or
when a version cannot be resolved via Docker Hub.
"""
api_version_langgraph_json = _read_api_version_from_config(config_path)
if api_version_cli_param and api_version_langgraph_json:
raise click.ClickException(
"API version specified in both --api-version CLI flag "
f"({api_version_cli_param!r}) and langgraph.json "
f"({api_version_langgraph_json!r}). Please use only one."
)
preferred_api_version = api_version_cli_param or api_version_langgraph_json
if preferred_api_version and _PATCH_VERSION_RE.match(preferred_api_version):
return preferred_api_version
version_prefix = preferred_api_version or ""
if version_prefix:
click.secho(
f"Resolving API version matching {version_prefix!r} from Docker Hub...",
fg="cyan",
)
else:
click.secho(
"Resolving latest API version from Docker Hub...",
fg="cyan",
)
resolved = _fetch_matching_version(version_prefix)
click.secho(f"Resolved API version: {resolved}", fg="cyan")
return resolved
def _read_api_version_from_config(config_path: pathlib.Path) -> str | None:
"""Read the `api_version` field from langgraph.json (if present)."""
try:
with open(config_path) as f:
raw_config = json.load(f)
except (OSError, json.JSONDecodeError):
return None
return raw_config.get("api_version")
def _fetch_matching_version(version_prefix: str = "") -> str:
"""Query Docker Hub for the latest patch version matching *version_prefix*.
When *version_prefix* is empty, returns the latest published version.
"""
url = f"https://hub.docker.com/v2/repositories/{VERSION_MARKER_REPO}/tags/?page_size=10"
if version_prefix:
url += f"&name={version_prefix}"
try:
with urllib.request.urlopen(url, timeout=10) as resp:
data = json.loads(resp.read())
except Exception as exc:
raise click.ClickException(
f"Failed to fetch API version from {VERSION_MARKER_REPO}: {exc}\n"
"You can specify an exact version with --api-version (e.g. 0.7.67)."
) from exc
for tag in data.get("results", []):
name = tag.get("name", "")
if _SEMVER_RE.match(name):
return name
if version_prefix:
msg = f"Could not find a version matching {version_prefix!r} in {VERSION_MARKER_REPO}."
else:
msg = f"Could not find a published version in {VERSION_MARKER_REPO}."
raise click.ClickException(
f"{msg}\nYou can specify an exact version with --api-version (e.g. 0.7.67)."
)
+5 -11
View File
@@ -23,6 +23,7 @@ from dotenv import dotenv_values
import langgraph_cli.config
import langgraph_cli.docker
from langgraph_cli.analytics import log_command
from langgraph_cli.api_version import resolve_langgraph_api_version
from langgraph_cli.config import Config
from langgraph_cli.constants import DEFAULT_CONFIG, DEFAULT_PORT
from langgraph_cli.docker import DockerCapabilities
@@ -352,6 +353,7 @@ def up(
image: str | None,
base_image: str | None,
):
api_version = resolve_langgraph_api_version(config, api_version)
click.secho("Starting LangGraph API server...", fg="green")
click.secho(
"""For local dev, requires env var LANGSMITH_API_KEY with access to LangSmith Deployment.
@@ -557,6 +559,7 @@ def build(
install_command: str | None,
build_command: str | None,
):
api_version = resolve_langgraph_api_version(config, api_version)
if install_command and langgraph_cli.config.has_disallowed_build_command_content(
install_command
):
@@ -689,6 +692,7 @@ def deploy(
no_wait: bool,
docker_build_args: Sequence[str],
):
deploy_api_version = resolve_langgraph_api_version(config, api_version)
click.secho(
"Note: 'langgraph deploy' is in beta. Expect frequent updates and improvements.",
fg="yellow",
@@ -721,7 +725,6 @@ def deploy(
"node_version"
)
deploy_engine_runtime_mode = "distributed" if is_python else "combined_queue_server"
deploy_api_version = api_version or config_json.get("api_version")
# Use buildx to cross-compile for amd64 when running on a non-x86_64 host
# (e.g. Apple Silicon). On amd64 hosts, plain docker build is sufficient.
@@ -1180,6 +1183,7 @@ def dockerfile(
api_version: str | None = None,
engine_runtime_mode: str = "combined_queue_worker",
) -> None:
api_version = resolve_langgraph_api_version(config, api_version)
save_path = pathlib.Path(save_path).absolute()
secho(f"🔍 Validating configuration at path: {config}", fg="yellow")
config_json = langgraph_cli.config.validate_config_file(config)
@@ -1529,16 +1533,6 @@ def prepare(
config_json = langgraph_cli.config.validate_config_file(config_path)
warn_non_wolfi_distro(config_json)
if engine_runtime_mode == "distributed" and not api_version and not image:
click.secho(
"Resolving latest published version for distributed runtime...",
fg="cyan",
)
api_version = langgraph_cli.config.fetch_latest_api_version()
click.secho(
f"Using version {api_version} for all distributed images.", fg="cyan"
)
# pull latest images
if pull:
runner.run(
+5 -32
View File
@@ -4,7 +4,6 @@ import os
import pathlib
import re
import textwrap
import urllib.request
from collections import Counter
from typing import Literal, NamedTuple
@@ -1234,37 +1233,6 @@ def node_config_to_docker(
return os.linesep.join(docker_file_contents), {}
VERSION_MARKER_REPO = "langchain/langgraph-published-version-marker"
_VERSION_RE = re.compile(r"^\d+\.\d+\.\d+")
def fetch_latest_api_version() -> str:
"""Fetch the latest published API version from the Docker Hub version marker.
The marker repo is tagged with ``<semver>``, ``<sha>``, and ``latest``.
We pick the first tag that looks like a semver version.
"""
url = f"https://hub.docker.com/v2/repositories/{VERSION_MARKER_REPO}/tags/?page_size=10"
try:
with urllib.request.urlopen(url, timeout=10) as resp:
data = json.loads(resp.read())
except Exception as exc:
raise click.ClickException(
f"Failed to fetch latest API version from {VERSION_MARKER_REPO}: {exc}\n"
"You can specify the version explicitly with --api-version."
) from exc
for tag in data.get("results", []):
name = tag.get("name", "")
if _VERSION_RE.match(name):
return name
raise click.ClickException(
f"Could not find a semver tag in {VERSION_MARKER_REPO}.\n"
"You can specify the version explicitly with --api-version."
)
def default_base_image(
config: Config, engine_runtime_mode: str = "combined_queue_worker"
) -> str:
@@ -1308,6 +1276,11 @@ def docker_tag(
else:
full_tag = version_distro_tag
# Strip an existing tag from base_image so we don't produce two colons
# (e.g. "langchain/langgraph-server:0.2" → "langchain/langgraph-server").
if ":" in base_image:
base_image = base_image.rsplit(":", 1)[0]
return f"{base_image}:{full_tag}"