dheraingoud's picture
feat: synchronize proxy with upstream commits up to 5305bd7 and integrate NIM key rotator
0a54372
Raw
History Blame Contribute Delete
4.82 kB
"""Configured provider model validation."""
import asyncio
from collections import defaultdict
from collections.abc import Callable
import httpx
from loguru import logger
from free_claude_code.application.errors import ApplicationUnavailableError
from free_claude_code.application.model_metadata import ProviderModelInfo
from free_claude_code.config.model_refs import (
ConfiguredChatModelRef,
configured_chat_model_refs,
)
from free_claude_code.config.settings import Settings
from free_claude_code.core.failures import ExecutionFailure
from free_claude_code.providers.base import BaseProvider
from free_claude_code.providers.model_listing import ModelListResponseError
from .model_cache import ProviderModelCache
ProviderResolver = Callable[[str], BaseProvider]
def provider_query_failure_reason(exc: BaseException, settings: Settings) -> str:
"""Return a concise model-list query failure reason for user-facing logs."""
if isinstance(exc, ModelListResponseError):
return f"malformed model-list response: {exc.message}"
if isinstance(exc, httpx.HTTPStatusError):
return f"query failure: HTTP {exc.response.status_code}"
if isinstance(exc, ApplicationUnavailableError):
return f"query failure: {exc.message}"
if isinstance(exc, ExecutionFailure) and settings.log_api_error_tracebacks:
return f"query failure: {exc.message}"
return f"query failure: {type(exc).__name__}"
class ConfiguredModelValidator:
"""Validate configured provider/model refs against upstream model lists."""
def __init__(
self,
settings: Settings,
provider_resolver: ProviderResolver,
model_cache: ProviderModelCache,
) -> None:
self._settings = settings
self._provider_resolver = provider_resolver
self._model_cache = model_cache
async def validate_configured_models(self) -> None:
"""Fail unless every configured chat model exists upstream."""
refs = configured_chat_model_refs(self._settings)
refs_by_provider: dict[str, list[ConfiguredChatModelRef]] = defaultdict(list)
for ref in refs:
refs_by_provider[ref.provider_id].append(ref)
failures: list[str] = []
tasks: dict[str, asyncio.Task[frozenset[ProviderModelInfo]]] = {}
for provider_id, provider_refs in refs_by_provider.items():
try:
provider = self._provider_resolver(provider_id)
except Exception as exc:
failures.extend(
self._format_provider_query_failures(provider_refs, exc)
)
continue
tasks[provider_id] = asyncio.create_task(provider.list_model_infos())
if tasks:
results = await asyncio.gather(*tasks.values(), return_exceptions=True)
for (provider_id, _task), result in zip(
tasks.items(), results, strict=True
):
provider_refs = refs_by_provider[provider_id]
if isinstance(result, BaseException):
if isinstance(result, asyncio.CancelledError):
raise result
failures.extend(
self._format_provider_query_failures(provider_refs, result)
)
continue
model_ids = frozenset(info.model_id for info in result)
self._model_cache.cache_model_infos(provider_id, result)
failures.extend(
self._format_missing_model_failure(ref)
for ref in provider_refs
if ref.model_id not in model_ids
)
if failures:
message = "Configured model validation failed:\n" + "\n".join(
f"- {failure}" for failure in failures
)
raise ApplicationUnavailableError(message)
logger.info(
"Configured provider models validated: models={} providers={}",
len(refs),
len(refs_by_provider),
)
def _format_provider_query_failures(
self,
refs: list[ConfiguredChatModelRef],
exc: BaseException,
) -> list[str]:
reason = provider_query_failure_reason(exc, self._settings)
return [self._format_model_validation_failure(ref, reason) for ref in refs]
def _format_missing_model_failure(self, ref: ConfiguredChatModelRef) -> str:
return self._format_model_validation_failure(ref, "missing model")
@staticmethod
def _format_model_validation_failure(
ref: ConfiguredChatModelRef, problem: str
) -> str:
return (
f"sources={','.join(ref.sources)} provider={ref.provider_id} "
f"model={ref.model_id} problem={problem}"
)