"""OpenAI-compatible provider — handles both Ollama and LM Studio.""" import asyncio import openai from app.providers.base import AIProvider from app.schemas.chat import ChatMessage class OpenAICompatProvider(AIProvider): def __init__(self, config: dict, provider_name: str = "lmstudio") -> None: self._client = openai.AsyncOpenAI( base_url=config.get("base_url", "http://localhost:1234/v1"), api_key=config.get("api_key") or "not-required", ) self.model_name = config.get("model", "local-model") self.provider_name = provider_name async def chat( self, messages: list[ChatMessage], max_tokens: int, temperature: float, ) -> tuple[str, int | None, int | None]: raw_messages = [{"role": m.role, "content": m.content} for m in messages] try: response = await self._client.chat.completions.create( model=self.model_name, messages=raw_messages, max_tokens=max_tokens, temperature=temperature, ) except openai.APIConnectionError as exc: raise ProviderConnectionError(str(exc)) from exc except openai.APITimeoutError as exc: raise ProviderTimeoutError(str(exc)) from exc except openai.APIStatusError as exc: raise ProviderConnectionError(f"API error {exc.status_code}: {exc.message}") from exc content = response.choices[0].message.content or "" usage = response.usage input_tokens = usage.prompt_tokens if usage else None output_tokens = usage.completion_tokens if usage else None return content, input_tokens, output_tokens class ProviderConnectionError(Exception): pass class ProviderTimeoutError(Exception): pass