mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-26 01:12:21 +00:00
feat(proxy_cli): add --validate_config dry-run flag (#41705)
* feat(proxy_cli): add --validate_config dry-run flag Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * style(tests): format --validate_config CliRunner calls Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(proxy_cli): run --validate_config before the ollama auto-start Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(proxy_cli): restore file and add ollama validate_config regression test Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: yassin <yassin@berri.ai>
This commit is contained in:
parent
e3f087315d
commit
de8aeff6c6
2 changed files with 132 additions and 0 deletions
|
|
@ -210,6 +210,25 @@ class ProxyInitializationHelpers:
|
|||
response: Final = httpx.get(url=f"http://{host}:{port}/health")
|
||||
print(json.dumps(response.json(), indent=4))
|
||||
|
||||
@staticmethod
|
||||
def _run_config_validation(config: str | None) -> None:
|
||||
if config is None:
|
||||
raise click.UsageError("--validate_config requires --config <path>")
|
||||
import asyncio
|
||||
|
||||
from litellm.proxy.proxy_server import ProxyConfig
|
||||
|
||||
async def _load() -> int:
|
||||
_, model_list, _ = await ProxyConfig().load_config(router=None, config_file_path=config)
|
||||
return len(model_list)
|
||||
|
||||
try:
|
||||
model_count: Final = asyncio.run(_load())
|
||||
except Exception as error:
|
||||
click.echo(f"LiteLLM: config validation failed: {error}", err=True)
|
||||
raise click.exceptions.Exit(1) from error
|
||||
click.echo(f"LiteLLM: config OK ({model_count} models)")
|
||||
|
||||
@staticmethod
|
||||
def _run_test_chat_completion(
|
||||
host: str,
|
||||
|
|
@ -887,6 +906,12 @@ class ProxyInitializationHelpers:
|
|||
default=False,
|
||||
help="Skip starting the server after setup (useful for migrations only)",
|
||||
)
|
||||
@click.option(
|
||||
"--validate_config",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Load and validate the config file (including mcp_servers) without starting the server, then exit. Exit code 1 on any config error.",
|
||||
)
|
||||
@click.option(
|
||||
"--keepalive_timeout",
|
||||
default=None,
|
||||
|
|
@ -1027,6 +1052,7 @@ def run_server(
|
|||
log_config,
|
||||
use_prisma_db_push: bool,
|
||||
skip_server_startup,
|
||||
validate_config: bool,
|
||||
keepalive_timeout,
|
||||
timeout_worker_healthcheck,
|
||||
max_requests_before_restart,
|
||||
|
|
@ -1069,6 +1095,9 @@ def run_server(
|
|||
if version is True:
|
||||
ProxyInitializationHelpers._echo_litellm_version()
|
||||
return
|
||||
if validate_config is True:
|
||||
ProxyInitializationHelpers._run_config_validation(config)
|
||||
return
|
||||
if model and "ollama" in model and api_base is None:
|
||||
ProxyInitializationHelpers._run_ollama_serve()
|
||||
if health is True:
|
||||
|
|
|
|||
|
|
@ -3224,3 +3224,106 @@ class TestLibpqSslParamTranslation:
|
|||
assert query["sslmode"] == ["require"]
|
||||
assert query["sslcert"] == ["/certs/rds-bundle.pem"]
|
||||
assert query["sslaccept"] == ["strict"]
|
||||
|
||||
|
||||
@pytest.mark.xdist_group("proxy_cli")
|
||||
class TestValidateConfigFlag:
|
||||
def test_validate_config_valid_config_exits_zero(self, tmp_path, monkeypatch):
|
||||
from click.testing import CliRunner
|
||||
|
||||
from litellm.proxy.proxy_cli import run_server
|
||||
|
||||
monkeypatch.delenv("DATABASE_URL", raising=False)
|
||||
monkeypatch.delenv("DIRECT_URL", raising=False)
|
||||
config_path = tmp_path / "config.yaml"
|
||||
config_path.write_text(
|
||||
yaml.safe_dump(
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-4o",
|
||||
"litellm_params": {
|
||||
"model": "openai/gpt-4o",
|
||||
"api_key": "sk-fake",
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
result = CliRunner().invoke(run_server, ["--config", str(config_path), "--validate_config"])
|
||||
|
||||
assert result.exit_code == 0, f"exit_code={result.exit_code}, output={result.output}"
|
||||
assert "config OK" in result.output
|
||||
|
||||
def test_validate_config_invalid_mcp_server_exits_one(self, tmp_path, monkeypatch):
|
||||
from click.testing import CliRunner
|
||||
|
||||
from litellm.proxy.proxy_cli import run_server
|
||||
|
||||
monkeypatch.delenv("DATABASE_URL", raising=False)
|
||||
monkeypatch.delenv("DIRECT_URL", raising=False)
|
||||
config_path = tmp_path / "config.yaml"
|
||||
config_path.write_text(
|
||||
yaml.safe_dump(
|
||||
{
|
||||
"mcp_servers": {
|
||||
"zapier": {
|
||||
"url": "https://example.com/mcp",
|
||||
"transport": "http",
|
||||
"per_server_oauth_discovery": "yes",
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
result = CliRunner().invoke(run_server, ["--config", str(config_path), "--validate_config"])
|
||||
|
||||
assert result.exit_code == 1, f"exit_code={result.exit_code}, output={result.output}"
|
||||
assert "per_server_oauth_discovery must be a boolean" in result.output
|
||||
|
||||
def test_validate_config_without_config_is_usage_error(self, monkeypatch):
|
||||
from click.testing import CliRunner
|
||||
|
||||
from litellm.proxy.proxy_cli import run_server
|
||||
|
||||
result = CliRunner().invoke(run_server, ["--validate_config"])
|
||||
|
||||
assert result.exit_code != 0
|
||||
assert "--validate_config requires --config" in result.output
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_validate_config_with_ollama_model_does_not_start_ollama(self, mock_popen, tmp_path, monkeypatch):
|
||||
from click.testing import CliRunner
|
||||
|
||||
from litellm.proxy.proxy_cli import run_server
|
||||
|
||||
monkeypatch.delenv("DATABASE_URL", raising=False)
|
||||
monkeypatch.delenv("DIRECT_URL", raising=False)
|
||||
config_path = tmp_path / "config.yaml"
|
||||
config_path.write_text(
|
||||
yaml.safe_dump(
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-4o",
|
||||
"litellm_params": {
|
||||
"model": "openai/gpt-4o",
|
||||
"api_key": "sk-fake",
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
result = CliRunner().invoke(
|
||||
run_server,
|
||||
["--config", str(config_path), "--model", "ollama/llama3", "--validate_config"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, f"exit_code={result.exit_code}, output={result.output}"
|
||||
assert "config OK" in result.output
|
||||
mock_popen.assert_not_called()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue