cli: rename langgraph.json to langsmith.json

Rename the default config filename from `langgraph.json` to
`langsmith.json` as part of the "LangGraph Platform" to "LangSmith
Deployment" rebranding.

Backward compatibility is preserved: the CLI falls back to
`langgraph.json` with a deprecation warning when `langsmith.json`
is not found. A new `_resolve_config` callback handles the
resolution logic for all `--config` click options.

Changes span libs/cli (core logic, examples, tests), libs/sdk-py
(docstrings), libs/langgraph (Makefile), and .github CI scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
William Fu-Hinthorn
2026-03-29 07:41:32 -07:00
co-authored by Claude Opus 4.6
parent ae76f33c6d
commit a99d733bb5
29 changed files with 125 additions and 62 deletions
+1 -1
View File
@@ -165,7 +165,7 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-t", "--tag", type=str)
parser.add_argument("-c", "--config", type=str, default="./langgraph.json")
parser.add_argument("-c", "--config", type=str, default="./langsmith.json")
parser.add_argument("-p", "--port", type=int, default=DEFAULT_PORT)
args = parser.parse_args()
try:
+3 -3
View File
@@ -83,13 +83,13 @@ jobs:
if: ${{ (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') &&matrix.example.name == 'A' }}
working-directory: libs/cli/js-monorepo-example
run: |
langgraph build -t langgraph-test-f -c apps/agent/langgraph.json --build-command "yarn run turbo build" --install-command "yarn install"
langgraph build -t langgraph-test-f -c apps/agent/langsmith.json --build-command "yarn run turbo build" --install-command "yarn install"
- name: Build Python monorepo service
if: ${{ (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') &&matrix.example.name == 'A' }}
working-directory: libs/cli/python-monorepo-example
run: |
langgraph build -t langgraph-test-g -c apps/agent/langgraph.json
langgraph build -t langgraph-test-g -c apps/agent/langsmith.json
- name: Test Python monorepo service
if: ${{ (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') &&matrix.example.name == 'A' && env.HAS_LANGSMITH_API_KEY == 'true' }}
working-directory: libs/cli/python-monorepo-example
@@ -98,7 +98,7 @@ jobs:
run: |
cp apps/agent/.env.example apps/agent/.env
echo "LANGSMITH_API_KEY=${{ secrets.LANGSMITH_API_KEY }}" >> apps/agent/.env
timeout 60 python ../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-g -c apps/agent/langgraph.json
timeout 60 python ../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-g -c apps/agent/langsmith.json
- name: Build prerelease reqs service
if: ${{ (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') &&matrix.example.name == 'A' }}
+2 -2
View File
@@ -31,7 +31,7 @@ langgraph dev [OPTIONS]
--no-reload Disable auto-reload
--debug-port INTEGER Enable remote debugging
--no-browser Skip opening browser window
-c, --config FILE Config file path (default: langgraph.json)
-c, --config FILE Config file path (default: langsmith.json)
```
### `langgraph up` 🚀
@@ -64,7 +64,7 @@ langgraph dockerfile SAVE_PATH [OPTIONS]
## Configuration
The CLI uses a `langgraph.json` configuration file with these key settings:
The CLI uses a `langsmith.json` configuration file with these key settings:
```json
{
+2 -2
View File
@@ -2,7 +2,7 @@
"""
Script to generate a JSON schema for the langgraph-cli Config class.
This script creates a schema.json file that can be referenced in langgraph.json files
This script creates a schema.json file that can be referenced in langsmith.json files
to provide IDE autocompletion and validation.
"""
@@ -243,7 +243,7 @@ def main():
print(
f"You can now add '$schema: https://raw.githubusercontent.com/langchain-ai/langgraph/refs/heads/main/libs/cli/schemas/schema.json'"
f" or '$schema: https://raw.githubusercontent.com/langchain-ai/langgraph/refs/heads/main/libs/cli/schemas/schema.{schema_version}.json'"
" to your langgraph.json files"
" to your langsmith.json files"
)
+81 -19
View File
@@ -24,7 +24,7 @@ import langgraph_cli.config
import langgraph_cli.docker
from langgraph_cli.analytics import log_command
from langgraph_cli.config import Config
from langgraph_cli.constants import DEFAULT_CONFIG, DEFAULT_PORT
from langgraph_cli.constants import DEFAULT_CONFIG, DEFAULT_PORT, LEGACY_CONFIG
from langgraph_cli.docker import DockerCapabilities
from langgraph_cli.exec import Runner, subp_exec
from langgraph_cli.helpers import format_log_entry, level_fg, resolve_deployment_id
@@ -97,7 +97,7 @@ _DEPLOYMENT_NAME_ENV = "LANGSMITH_DEPLOYMENT_NAME"
def _parse_env_from_config(
config_json: dict, config_path: pathlib.Path
) -> dict[str, str]:
"""Resolve env vars from langgraph.json 'env' field or a .env fallback."""
"""Resolve env vars from langsmith.json 'env' field or a .env fallback."""
env_field = config_json.get("env")
# validate_config_file will default env to {}
if isinstance(env_field, dict) and env_field:
@@ -106,7 +106,7 @@ def _parse_env_from_config(
env_path = (config_path.parent / env_field).resolve()
if not env_path.exists():
click.secho(
f"Warning: env file '{env_field}' specified in langgraph.json not found.",
f"Warning: env file '{env_field}' specified in langsmith.json not found.",
fg="yellow",
)
return {}
@@ -157,6 +157,47 @@ def _docker_config_for_token(registry_host: str, token: str):
yield tmpdir
def _resolve_config(
ctx: click.Context, param: click.Parameter, value: str | None
) -> pathlib.Path:
"""Resolve config path, falling back from langsmith.json to langgraph.json."""
if value is not None and value not in (DEFAULT_CONFIG, LEGACY_CONFIG):
# User explicitly passed a config path
path = pathlib.Path(value).resolve()
if not path.exists():
raise click.BadParameter(
f"Path '{value}' does not exist.", ctx=ctx, param=param
)
return path
# Check for default config, then legacy fallback
default_path = pathlib.Path(DEFAULT_CONFIG).resolve()
if default_path.exists():
return default_path
legacy_path = pathlib.Path(LEGACY_CONFIG).resolve()
if legacy_path.exists():
click.secho(
f"Warning: '{LEGACY_CONFIG}' is deprecated. "
f"Rename it to '{DEFAULT_CONFIG}'.",
fg="yellow",
)
return legacy_path
if value is not None:
# User explicitly passed one of the default/legacy names but neither exists
raise click.BadParameter(
f"Path '{value}' does not exist.", ctx=ctx, param=param
)
raise click.BadParameter(
f"No config file found. Expected '{DEFAULT_CONFIG}' "
f"(or '{LEGACY_CONFIG}') in the current directory.",
ctx=ctx,
param=param,
)
OPT_DOCKER_COMPOSE = click.option(
"--docker-compose",
"-d",
@@ -189,7 +230,7 @@ OPT_CONFIG = click.option(
\b
Example:
langgraph up -c langgraph.json
langgraph up -c langsmith.json
\b
Example:
@@ -220,14 +261,16 @@ OPT_CONFIG = click.option(
}
}
Defaults to looking for langgraph.json in the current directory.""",
Defaults to looking for langsmith.json in the current directory. Also accepts the legacy langgraph.json.""",
default=DEFAULT_CONFIG,
callback=_resolve_config,
is_eager=True,
type=click.Path(
exists=True,
exists=False,
file_okay=True,
dir_okay=False,
resolve_path=True,
path_type=pathlib.Path,
resolve_path=False,
path_type=str,
),
)
OPT_PORT = click.option(
@@ -645,7 +688,7 @@ def _build(
)
@click.option(
"--build-command",
help="Custom build command to run from the langgraph.json directory. If not provided, uses default build process.",
help="Custom build command to run from the langsmith.json directory. If not provided, uses default build process.",
)
@click.argument("docker_build_args", nargs=-1, type=click.UNPROCESSED)
@cli.command(
@@ -754,12 +797,28 @@ def _deploy_base_options(
"-c",
default=DEFAULT_CONFIG,
hidden=True,
type=click.Path(
exists=validate_config_path,
file_okay=True,
dir_okay=False,
resolve_path=True,
path_type=pathlib.Path,
**(
{
"callback": _resolve_config,
"is_eager": True,
"type": click.Path(
exists=False,
file_okay=True,
dir_okay=False,
resolve_path=False,
path_type=str,
),
}
if validate_config_path
else {
"type": click.Path(
exists=False,
file_okay=True,
dir_okay=False,
resolve_path=True,
path_type=pathlib.Path,
),
}
),
),
click.option("--pull/--no-pull", default=True, hidden=True),
@@ -788,7 +847,7 @@ def _deploy_base_options(
"[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 "
"Run from the root of your LangGraph project (where langsmith.json "
"is located). This command also accepts build flags (--base-image, "
"--config, --pull, etc.). See 'langgraph build --help' for details."
),
@@ -1727,9 +1786,12 @@ def dockerfile(
)
@click.option(
"--config",
type=click.Path(exists=True),
default="langgraph.json",
help="Path to configuration file declaring dependencies, graphs and environment variables",
type=click.Path(exists=False),
default=DEFAULT_CONFIG,
callback=_resolve_config,
is_eager=True,
help="Path to configuration file declaring dependencies, graphs and environment variables. "
"Defaults to looking for langsmith.json in the current directory. Also accepts the legacy langgraph.json.",
)
@click.option(
"--n-jobs-per-worker",
+3 -3
View File
@@ -550,7 +550,7 @@ def _update_graph_paths(
the host system is Windows).
Args:
config_path: The path to the config file (e.g. `langgraph.json`).
config_path: The path to the config file (e.g. `langsmith.json`).
config: The validated configuration dictionary.
local_deps: An object containing references to local dependencies:
- real Python packages (with a `pyproject.toml` or `setup.py`)
@@ -1280,7 +1280,7 @@ def docker_tag(
def _calculate_relative_workdir(config_path: pathlib.Path, build_context: str) -> str:
"""Calculate the relative path from build context to langgraph.json directory."""
"""Calculate the relative path from build context to langsmith.json directory."""
config_dir = config_path.parent.resolve()
build_context_path = pathlib.Path(build_context).resolve()
@@ -1290,7 +1290,7 @@ def _calculate_relative_workdir(config_path: pathlib.Path, build_context: str) -
except ValueError as _:
raise ValueError(
f"Configuration file {config_path} is not under the build context {build_context}. "
f"Please run the command from a directory that contains your langgraph.json file, "
f"Please run the command from a directory that contains your langsmith.json file, "
) from None
+2 -1
View File
@@ -1,4 +1,5 @@
DEFAULT_CONFIG = "langgraph.json"
DEFAULT_CONFIG = "langsmith.json"
LEGACY_CONFIG = "langgraph.json"
DEFAULT_PORT = 8123
# analytics
+1 -1
View File
@@ -21,7 +21,7 @@ def warn_non_wolfi_distro(config_json: dict) -> None:
fg="yellow",
)
click.secho(
' To switch, add \'"image_distro": "wolfi"\' to your langgraph.json config file.',
' To switch, add \'"image_distro": "wolfi"\' to your langsmith.json config file.',
fg="yellow",
)
click.secho("") # Empty line for better readability
+7 -7
View File
@@ -50,7 +50,7 @@ def temporary_config_folder(config_content: dict, levels: int = 0):
def test_prepare_args_and_stdin() -> None:
# this basically serves as an end-to-end test for using config and docker helpers
config_path = pathlib.Path(__file__).parent / "langgraph.json"
config_path = pathlib.Path(__file__).parent / "langsmith.json"
config = validate_config(
Config(dependencies=[".", "../../.."], graphs={"agent": "agent.py:graph"})
)
@@ -159,7 +159,7 @@ services:
develop:
watch:
- path: langgraph.json
- path: langsmith.json
action: rebuild
- path: .
action: rebuild
@@ -172,7 +172,7 @@ services:
def test_prepare_args_and_stdin_with_image() -> None:
# this basically serves as an end-to-end test for using config and docker helpers
config_path = pathlib.Path(__file__).parent / "langgraph.json"
config_path = pathlib.Path(__file__).parent / "langsmith.json"
config = validate_config(
Config(dependencies=[".", "../../.."], graphs={"agent": "agent.py:graph"})
)
@@ -263,7 +263,7 @@ services:
develop:
watch:
- path: langgraph.json
- path: langsmith.json
action: rebuild
- path: .
action: rebuild
@@ -1136,7 +1136,7 @@ def test_build_command_with_api_version_and_base_image() -> None:
def test_prepare_args_and_stdin_with_api_version() -> None:
"""Test prepare_args_and_stdin function with api_version parameter."""
config_path = pathlib.Path(__file__).parent / "langgraph.json"
config_path = pathlib.Path(__file__).parent / "langsmith.json"
config = validate_config(
Config(dependencies=["."], graphs={"agent": "agent.py:graph"})
)
@@ -1169,7 +1169,7 @@ def test_prepare_args_and_stdin_with_api_version() -> None:
def test_prepare_args_and_stdin_with_api_version_and_image() -> None:
"""Test prepare_args_and_stdin function with both api_version and image parameters."""
config_path = pathlib.Path(__file__).parent / "langgraph.json"
config_path = pathlib.Path(__file__).parent / "langsmith.json"
config = validate_config(
Config(dependencies=["."], graphs={"agent": "agent.py:graph"})
)
@@ -1298,7 +1298,7 @@ def test_dockerfile_command_distributed_with_explicit_base_image() -> None:
def test_prepare_args_and_stdin_distributed_mode() -> None:
"""Test prepare_args_and_stdin with distributed mode includes all services."""
config_path = pathlib.Path(__file__).parent / "langgraph.json"
config_path = pathlib.Path(__file__).parent / "langsmith.json"
config = validate_config(
Config(dependencies=["."], graphs={"agent": "agent.py:graph"})
)
+1 -1
View File
@@ -309,7 +309,7 @@ def test_validate_config_file():
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir_path = pathlib.Path(tmpdir)
config_path = tmpdir_path / "langgraph.json"
config_path = tmpdir_path / "langsmith.json"
node_config = {"node_version": "20", "graphs": {"agent": "./agent.js:graph"}}
with open(config_path, "w") as f:
@@ -85,7 +85,7 @@ class TestNormalizeImageTag:
class TestParseEnvFromConfig:
def test_env_dict(self, tmp_path):
config_path = tmp_path / "langgraph.json"
config_path = tmp_path / "langsmith.json"
config_path.touch()
result = _parse_env_from_config({"env": {"FOO": "bar", "NUM": 42}}, config_path)
assert result == {"FOO": "bar", "NUM": "42"}
@@ -93,7 +93,7 @@ class TestParseEnvFromConfig:
def test_env_string_dotenv_file(self, tmp_path):
env_file = tmp_path / "my.env"
env_file.write_text("KEY1=val1\nKEY2=val2\n")
config_path = tmp_path / "langgraph.json"
config_path = tmp_path / "langsmith.json"
config_path.touch()
result = _parse_env_from_config({"env": "my.env"}, config_path)
assert result == {"KEY1": "val1", "KEY2": "val2"}
@@ -102,7 +102,7 @@ class TestParseEnvFromConfig:
env_file = tmp_path / ".env"
env_file.write_text("DEFAULT_KEY=default_val\n")
monkeypatch.chdir(tmp_path)
config_path = tmp_path / "langgraph.json"
config_path = tmp_path / "langsmith.json"
config_path.touch()
result = _parse_env_from_config({}, config_path)
assert result == {"DEFAULT_KEY": "default_val"}
@@ -112,14 +112,14 @@ class TestParseEnvFromConfig:
env_file = tmp_path / ".env"
env_file.write_text("MY_KEY=my_val\n")
monkeypatch.chdir(tmp_path)
config_path = tmp_path / "langgraph.json"
config_path = tmp_path / "langsmith.json"
config_path.touch()
result = _parse_env_from_config({"env": {}}, config_path)
assert result == {"MY_KEY": "my_val"}
def test_env_missing_no_dotenv_returns_empty(self, tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
config_path = tmp_path / "langgraph.json"
config_path = tmp_path / "langsmith.json"
config_path.touch()
result = _parse_env_from_config({}, config_path)
assert result == {}
@@ -128,7 +128,7 @@ class TestParseEnvFromConfig:
# Lines like "KEY=" produce empty string, lines like "KEY" produce None
env_file = tmp_path / "test.env"
env_file.write_text("GOOD=value\nEMPTY=\n")
config_path = tmp_path / "langgraph.json"
config_path = tmp_path / "langsmith.json"
config_path.touch()
result = _parse_env_from_config({"env": "test.env"}, config_path)
assert "GOOD" in result
+4 -4
View File
@@ -48,7 +48,7 @@ def test_warn_non_wolfi_distro_with_debian(capsys):
in captured.out
)
assert (
'To switch, add \'"image_distro": "wolfi"\' to your langgraph.json config file.'
'To switch, add \'"image_distro": "wolfi"\' to your langsmith.json config file.'
in captured.out
)
@@ -69,7 +69,7 @@ def test_warn_non_wolfi_distro_with_default_debian(capsys):
in captured.out
)
assert (
'To switch, add \'"image_distro": "wolfi"\' to your langgraph.json config file.'
'To switch, add \'"image_distro": "wolfi"\' to your langsmith.json config file.'
in captured.out
)
@@ -100,7 +100,7 @@ def test_warn_non_wolfi_distro_with_other_distro(capsys):
in captured.out
)
assert (
'To switch, add \'"image_distro": "wolfi"\' to your langgraph.json config file.'
'To switch, add \'"image_distro": "wolfi"\' to your langsmith.json config file.'
in captured.out
)
@@ -128,7 +128,7 @@ def test_warn_non_wolfi_distro_output_formatting():
),
(
(
' To switch, add \'"image_distro": "wolfi"\' to your langgraph.json config file.',
' To switch, add \'"image_distro": "wolfi"\' to your langsmith.json config file.',
),
{"fg": "yellow"},
),
+1 -1
View File
@@ -44,7 +44,7 @@ stop-services:
docker compose -f tests/compose-postgres.yml -f tests/compose-redis.yml down -v
start-dev-server:
LOG_LEVEL=warning uv run langgraph dev --config tests/example_app/langgraph.json --no-browser & echo "$$!" > .devserver.pid
LOG_LEVEL=warning uv run langgraph dev --config tests/example_app/langsmith.json --no-browser & echo "$$!" > .devserver.pid
@echo "Dev server started."
stop-dev-server:
@@ -323,7 +323,7 @@ class AssistantsClient:
Useful when graph is configurable and you want to create different assistants based on different configurations.
Args:
graph_id: The ID of the graph the assistant should use. The graph ID is normally set in your langgraph.json configuration.
graph_id: The ID of the graph the assistant should use. The graph ID is normally set in your langsmith.json configuration.
config: Configuration to use for the graph.
metadata: Metadata to add to assistant.
context: Static context to add to the assistant.
@@ -395,7 +395,7 @@ class AssistantsClient:
Args:
assistant_id: Assistant to update.
graph_id: The ID of the graph the assistant should use.
The graph ID is normally set in your langgraph.json configuration. If `None`, assistant will keep pointing to same graph.
The graph ID is normally set in your langsmith.json configuration. If `None`, assistant will keep pointing to same graph.
config: Configuration to use for the graph.
context: Static context to add to the assistant.
!!! version-added "Added in version 0.6.0"
@@ -538,7 +538,7 @@ class AssistantsClient:
Args:
metadata: Metadata to filter by. Exact match filter for each KV pair.
graph_id: The ID of the graph to filter by.
The graph ID is normally set in your langgraph.json configuration.
The graph ID is normally set in your langsmith.json configuration.
name: The name of the assistant to filter by.
The filtering logic will match assistants where 'name' is a substring (case insensitive) of the assistant name.
limit: The maximum number of results to return.
@@ -327,7 +327,7 @@ class SyncAssistantsClient:
Useful when graph is configurable and you want to create different assistants based on different configurations.
Args:
graph_id: The ID of the graph the assistant should use. The graph ID is normally set in your langgraph.json configuration.
graph_id: The ID of the graph the assistant should use. The graph ID is normally set in your langsmith.json configuration.
config: Configuration to use for the graph.
context: Static context to add to the assistant.
!!! version-added "Added in version 0.6.0"
@@ -399,7 +399,7 @@ class SyncAssistantsClient:
Args:
assistant_id: Assistant to update.
graph_id: The ID of the graph the assistant should use.
The graph ID is normally set in your langgraph.json configuration. If `None`, assistant will keep pointing to same graph.
The graph ID is normally set in your langsmith.json configuration. If `None`, assistant will keep pointing to same graph.
config: Configuration to use for the graph.
context: Static context to add to the assistant.
!!! version-added "Added in version 0.6.0"
@@ -540,7 +540,7 @@ class SyncAssistantsClient:
Args:
metadata: Metadata to filter by. Exact match filter for each KV pair.
graph_id: The ID of the graph to filter by.
The graph ID is normally set in your langgraph.json configuration.
The graph ID is normally set in your langsmith.json configuration.
name: The name of the assistant to filter by.
The filtering logic will match assistants where 'name' is a substring (case insensitive) of the assistant name.
limit: The maximum number of results to return.
+2 -2
View File
@@ -19,11 +19,11 @@ class Auth:
actions.
To use, create a separate python file and add the path to the file to your
LangGraph API configuration file (`langgraph.json`). Within that file, create
LangGraph API configuration file (`langsmith.json`). Within that file, create
an instance of the Auth class and register authentication and authorization
handlers as needed.
Example `langgraph.json` file:
Example `langsmith.json` file:
```json
{
+1 -1
View File
@@ -218,7 +218,7 @@ class BaseUser(typing.Protocol):
class StudioUser:
"""A user object that's populated from authenticated requests from the LangGraph studio.
Note: Studio auth can be disabled in your `langgraph.json` config.
Note: Studio auth can be disabled in your `langsmith.json` config.
```json
{
@@ -210,11 +210,11 @@ class Encryption:
metadata, context, kwargs, values, etc.).
To use, create a separate Python file and add the path to the file to your
LangGraph API configuration file (`langgraph.json`). Within that file, create
LangGraph API configuration file (`langsmith.json`). Within that file, create
an instance of the Encryption class and register encryption and decryption
handlers as needed.
Example `langgraph.json` file:
Example `langsmith.json` file:
```json
{