feat(cloud): manage LLM providers in database
Tests / Test passed: 664

This commit is contained in:
2026-07-14 00:31:47 +08:00
parent b613a315ff
commit a166ffd8a4
35 changed files with 2432 additions and 204 deletions
@@ -1,80 +1,51 @@
from __future__ import annotations
from cloud.planner_config import (
DEFAULT_MODEL_BY_PROVIDER,
DEFAULT_PROVIDER,
DEFAULT_TIMEOUT_SECONDS,
CloudPlannerConfig,
build_cloud_planner_client,
load_cloud_planner_config,
)
from datetime import UTC, datetime
from cloud.llm_providers import LlmProviderProfile, ResolvedLlmProviderProfile
from cloud.planner_config import build_cloud_planner_client
from runtime.tool_calling_client import (
AnthropicToolCallingClient,
OpenAIToolCallingClient,
)
_NO_RELEVANT_VARS = {"UNRELATED": "1"}
def test_load_cloud_planner_config_defaults_when_unset() -> None:
config = load_cloud_planner_config(_NO_RELEVANT_VARS)
assert config == CloudPlannerConfig(
provider=DEFAULT_PROVIDER,
model="",
timeout=DEFAULT_TIMEOUT_SECONDS,
def _resolved_profile(
provider_type: str, *, base_url: str | None = None
) -> ResolvedLlmProviderProfile:
now = datetime.now(UTC)
profile = LlmProviderProfile(
id="provider-1",
name="Managed provider",
name_normalized="managed provider",
provider_type=provider_type, # type: ignore[arg-type]
model="test-model",
base_url=base_url,
timeout_seconds=15,
api_key_ciphertext="ciphertext",
key_last_rotated_at=now,
enabled=True,
revision=1,
created_at=now,
updated_at=now,
)
assert config.resolved_model() == DEFAULT_MODEL_BY_PROVIDER[DEFAULT_PROVIDER]
return ResolvedLlmProviderProfile(profile=profile, api_key="managed-api-key")
def test_load_cloud_planner_config_selects_provider_and_resolves_default_model() -> (
None
):
config = load_cloud_planner_config({"AI_PLANNER_PROVIDER": "openai"})
assert config.provider == "openai"
assert config.resolved_model() == "gpt-5.6"
def test_load_cloud_planner_config_falls_back_to_default_provider_when_unsupported() -> (
None
):
config = load_cloud_planner_config({"AI_PLANNER_PROVIDER": "not-a-real-provider"})
assert config.provider == DEFAULT_PROVIDER
def test_load_cloud_planner_config_model_override_wins_regardless_of_provider() -> None:
config = load_cloud_planner_config(
{"AI_PLANNER_PROVIDER": "openai", "AI_PLANNER_MODEL": "custom-model"}
)
assert config.resolved_model() == "custom-model"
def test_load_cloud_planner_config_parses_valid_timeout() -> None:
config = load_cloud_planner_config({"AI_PLANNER_TIMEOUT_SECONDS": "12.5"})
assert config.timeout == 12.5
def test_load_cloud_planner_config_falls_back_to_default_timeout_when_invalid() -> None:
for value in ["not-a-number", "0", "-5"]:
config = load_cloud_planner_config(
{"AI_PLANNER_TIMEOUT_SECONDS": value, **_NO_RELEVANT_VARS}
)
assert config.timeout == DEFAULT_TIMEOUT_SECONDS
def test_build_cloud_planner_client_selects_anthropic_by_default() -> None:
client = build_cloud_planner_client(CloudPlannerConfig())
def test_build_cloud_planner_client_uses_managed_anthropic_key() -> None:
client = build_cloud_planner_client(_resolved_profile("anthropic"))
assert isinstance(client, AnthropicToolCallingClient)
assert client.model == DEFAULT_MODEL_BY_PROVIDER["anthropic"]
assert client.model == "test-model"
assert client._api_key == "managed-api-key"
def test_build_cloud_planner_client_selects_openai() -> None:
client = build_cloud_planner_client(CloudPlannerConfig(provider="openai"))
def test_build_cloud_planner_client_uses_openai_compatible_base_url() -> None:
client = build_cloud_planner_client(
_resolved_profile("openai-compatible", base_url="https://compat.example/v1")
)
assert isinstance(client, OpenAIToolCallingClient)
assert client.model == DEFAULT_MODEL_BY_PROVIDER["openai"]
assert client.model == "test-model"
assert client._api_key == "managed-api-key"
assert client._base_url == "https://compat.example/v1"