"""Universal API client supporting multiple OpenAI-compatible providers.""" import httpx import json PROVIDERS = {} def init_providers(config: dict): global PROVIDERS PROVIDERS = config.get("providers", {}) def _get_client(provider_name: str) -> httpx.Client: p = PROVIDERS.get(provider_name) if not p: raise ValueError(f"Unknown provider: {provider_name}") return httpx.Client( base_url=p["base_url"], headers={"Authorization": f"Bearer {p['api_key']}"}, timeout=120.0, ) def chat( provider: str, model: str, messages: list[dict], temperature: float = 0.7, tools: list[dict] | None = None, ) -> dict: client = _get_client(provider) payload = { "model": model, "messages": messages, "temperature": temperature, } if tools: payload["tools"] = tools payload["tool_choice"] = "auto" resp = client.post("/chat/completions", json=payload) resp.raise_for_status() data = resp.json() message = data["choices"][0]["message"] result = { "content": message.get("content") or "", "tool_calls": None, } if message.get("tool_calls"): result["tool_calls"] = [] for tc in message["tool_calls"]: result["tool_calls"].append({ "id": tc["id"], "name": tc["function"]["name"], "arguments": tc["function"]["arguments"], }) return result def chat_stream( provider: str, model: str, messages: list[dict], temperature: float = 0.7, ) -> str: client = _get_client(provider) payload = { "model": model, "messages": messages, "temperature": temperature, "stream": True, } full_text = "" with client.stream("POST", "/chat/completions", json=payload) as resp: resp.raise_for_status() for line in resp.iter_lines(): if not line or not line.startswith("data: "): continue data = line[6:] if data == "[DONE]": break try: chunk = json.loads(data) delta = chunk["choices"][0]["delta"] if "content" in delta and delta["content"]: print(delta["content"], end="", flush=True) full_text += delta["content"] except (json.JSONDecodeError, KeyError, IndexError): continue print() return full_text