from app.contracts import ModelCapability, ProviderConfig, ProviderPreset, ProviderType from app.providers.base import ModelProvider from app.providers.credentials import CredentialResolver from app.providers.ollama import OllamaProvider from app.providers.openai_compatible import OpenAICompatibleProvider class UnsupportedProviderError(ValueError): pass class ProviderFactory: def __init__(self, credentials: CredentialResolver) -> None: self.credentials = credentials def build(self, config: ProviderConfig) -> ModelProvider: if config.provider_type in { ProviderType.openai_chat, ProviderType.openai_compatible, }: return OpenAICompatibleProvider( base_url=config.base_url or "https://api.openai.com/v1", credential_id=config.credential_id, credentials=self.credentials, ) if config.provider_type == ProviderType.ollama: return OllamaProvider(config.base_url or "http://127.0.0.1:11434") raise UnsupportedProviderError(config.provider_type.value) @staticmethod def presets() -> list[ProviderPreset]: return [ ProviderPreset( preset_id="openai", name="OpenAI", provider_type=ProviderType.openai_chat, base_url="https://api.openai.com/v1", ), ProviderPreset( preset_id="deepseek", name="DeepSeek", provider_type=ProviderType.openai_compatible, base_url="https://api.deepseek.com", ), ProviderPreset( preset_id="ollama", name="Ollama", provider_type=ProviderType.ollama, base_url="http://127.0.0.1:11434", requires_credential=False, ), ] @staticmethod def capabilities(provider_type: ProviderType) -> list[ModelCapability]: if provider_type in { ProviderType.openai_chat, ProviderType.openai_compatible, }: return [ ModelCapability.chat, ModelCapability.tool_calling, ModelCapability.streaming, ModelCapability.structured_output, ] if provider_type == ProviderType.ollama: return [ ModelCapability.chat, ModelCapability.tool_calling, ModelCapability.streaming, ] return []