@@ -1,86 +1,24 @@
|
||||
"""Cloud Control Plane's own AI Planner provider configuration.
|
||||
|
||||
Analogous to ``runtime/planner_config.py``, but loaded from the Cloud API's
|
||||
own process environment rather than a Host Agent's. There is no ``enabled``
|
||||
flag here: the planner-decision endpoint always exists once the Cloud API is
|
||||
running, and simply fails a given request if the configured provider call
|
||||
fails (see ``cloud.internal_api.api``). Provider API keys
|
||||
(``ANTHROPIC_API_KEY``/``OPENAI_API_KEY``) are not modeled as fields here --
|
||||
like the Host Agent's direct-transport path, they are read implicitly by the
|
||||
``anthropic``/``openai`` SDK clients from the process environment.
|
||||
"""
|
||||
"""Construct Cloud planner clients from resolved database Provider profiles."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cloud.llm_providers import ResolvedLlmProviderProfile
|
||||
from runtime.tool_calling_client import (
|
||||
AnthropicToolCallingClient,
|
||||
OpenAIToolCallingClient,
|
||||
ToolCallingClient,
|
||||
)
|
||||
|
||||
DEFAULT_PROVIDER = "anthropic"
|
||||
DEFAULT_MODEL_BY_PROVIDER = {
|
||||
"anthropic": "claude-sonnet-5",
|
||||
"openai": "gpt-5.6",
|
||||
}
|
||||
DEFAULT_TIMEOUT_SECONDS = 30.0
|
||||
|
||||
PROVIDER_ENV = "AI_PLANNER_PROVIDER"
|
||||
MODEL_ENV = "AI_PLANNER_MODEL"
|
||||
TIMEOUT_ENV = "AI_PLANNER_TIMEOUT_SECONDS"
|
||||
|
||||
SUPPORTED_PROVIDERS = frozenset(DEFAULT_MODEL_BY_PROVIDER)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CloudPlannerConfig:
|
||||
provider: str = DEFAULT_PROVIDER
|
||||
model: str = ""
|
||||
timeout: float = DEFAULT_TIMEOUT_SECONDS
|
||||
|
||||
def resolved_model(self) -> str:
|
||||
return self.model or DEFAULT_MODEL_BY_PROVIDER[self.provider]
|
||||
|
||||
|
||||
def load_cloud_planner_config(
|
||||
env: Mapping[str, str] | None = None,
|
||||
) -> CloudPlannerConfig:
|
||||
values = env or os.environ
|
||||
return CloudPlannerConfig(
|
||||
provider=_parse_provider(values.get(PROVIDER_ENV)),
|
||||
model=values.get(MODEL_ENV) or "",
|
||||
timeout=_parse_timeout(values.get(TIMEOUT_ENV)),
|
||||
)
|
||||
|
||||
|
||||
def build_cloud_planner_client(config: CloudPlannerConfig) -> ToolCallingClient:
|
||||
"""Construct the same provider client the Host Agent's direct transport uses.
|
||||
|
||||
Reuses ``runtime.tool_calling_client``'s Anthropic/OpenAI wire-format
|
||||
translation (see design decision D1) instead of a second implementation.
|
||||
"""
|
||||
model = config.resolved_model()
|
||||
if config.provider == "openai":
|
||||
return OpenAIToolCallingClient(model=model)
|
||||
return AnthropicToolCallingClient(model=model)
|
||||
|
||||
|
||||
def _parse_provider(value: str | None) -> str:
|
||||
if value is None:
|
||||
return DEFAULT_PROVIDER
|
||||
provider = value.strip().lower()
|
||||
return provider if provider in SUPPORTED_PROVIDERS else DEFAULT_PROVIDER
|
||||
|
||||
|
||||
def _parse_timeout(value: str | None) -> float:
|
||||
if value is None:
|
||||
return DEFAULT_TIMEOUT_SECONDS
|
||||
try:
|
||||
timeout = float(value)
|
||||
except ValueError:
|
||||
return DEFAULT_TIMEOUT_SECONDS
|
||||
return timeout if timeout > 0 else DEFAULT_TIMEOUT_SECONDS
|
||||
def build_cloud_planner_client(
|
||||
resolved: ResolvedLlmProviderProfile,
|
||||
) -> ToolCallingClient:
|
||||
"""Build a provider client without reading Cloud API environment variables."""
|
||||
profile = resolved.profile
|
||||
if profile.provider_type == "openai-compatible":
|
||||
return OpenAIToolCallingClient(
|
||||
model=profile.model,
|
||||
api_key=resolved.api_key,
|
||||
base_url=profile.base_url,
|
||||
)
|
||||
return AnthropicToolCallingClient(model=profile.model, api_key=resolved.api_key)
|
||||
|
||||
Reference in New Issue
Block a user