"""OpenAI-compatible providers (one implementation, many vendors). OpenAI, xAI (api.x.ai/v1), Ollama (…:11434/v1), OpenRouter, Together, vLLM, etc. all speak the OpenAI Chat Completions / Embeddings API — they differ only by base URL, key, and model name. So a single class, parameterized by those, plugs in every one of them via the official `openai` SDK. """ from openai import AsyncOpenAI from app.integrations.models.base import EmbeddingProvider, LLMProvider class OpenAICompatibleLLMProvider(LLMProvider): def __init__(self, *, api_key: str | None, base_url: str, model: str, max_tokens: int = 4096) -> None: # Local backends (Ollama) ignore the key but the SDK requires a non-empty one. self._client = AsyncOpenAI(api_key=api_key or "not-needed", base_url=base_url) self._model = model self._max_tokens = max_tokens async def complete(self, *, prompt: str, system: str | None = None) -> str: messages: list[dict] = [] if system: messages.append({"role": "system", "content": system}) messages.append({"role": "user", "content": prompt}) resp = await self._client.chat.completions.create( model=self._model, max_tokens=self._max_tokens, messages=messages ) return resp.choices[0].message.content or "" class OpenAICompatibleEmbeddingProvider(EmbeddingProvider): def __init__(self, *, api_key: str | None, base_url: str, model: str, dimensions: int) -> None: self._client = AsyncOpenAI(api_key=api_key or "not-needed", base_url=base_url) self._model = model self.dimensions = dimensions async def embed(self, texts: list[str]) -> list[list[float]]: resp = await self._client.embeddings.create(model=self._model, input=texts) return [d.embedding for d in resp.data]