1 line
8.8 KiB
JSON
1 line
8.8 KiB
JSON
{"repo": "ankit-anand123/hypotestx", "files": {"hypotestx/core/llm/backends/openai_compat.py": {"babb7886416abda9529d74864dc791840d438bfa": {"t": 1785674496.6574786, "n": 8386, "h": "9d7d54d78c6f6525", "c": "\"\"\"\nOpenAI-compatible backend.\n\nWorks with any API that follows the OpenAI chat-completion format:\n - OpenAI (https://platform.openai.com)\n - Groq (https://console.groq.com) — free tier, very fast\n - Together AI (https://www.together.ai) — many open-source models\n - Perplexity (https://www.perplexity.ai)\n - Mistral AI (https://mistral.ai)\n - Azure OpenAI (set provider=\"azure\" or base_url to your Azure endpoint)\n - Local llama.cpp (--server mode exposes OpenAI-compatible API)\n - vLLM, LiteLLM, etc.\n\nUsage:\n # OpenAI\n result = hx.analyze(df, \"...\", backend=\"openai\", api_key=\"sk-...\")\n\n # Groq (free tier)\n result = hx.analyze(df, \"...\", backend=\"groq\",\n api_key=\"gsk_...\",\n model=\"llama-3.3-70b-versatile\")\n\n # Azure OpenAI\n result = hx.analyze(df, \"...\", backend=\"azure\",\n api_key=\"<azure-api-key>\",\n base_url=\"https://<resource>.openai.azure.com\",\n model=\"<deployment-name>\",\n api_version=\"2024-02-01\")\n\n # Generic OpenAI-compatible\n backend = OpenAICompatBackend(\n api_key = \"...\",\n base_url = \"https://api.together.xyz/v1\",\n model = \"meta-llama/Llama-3-70b-chat-hf\",\n )\n result = hx.analyze(df, \"...\", backend=backend)\n\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nimport urllib.error\nimport urllib.request\nfrom typing import Dict, List, Optional\n\nfrom ..base import LLMBackend\n\n# Known provider shorthand configs\n_PROVIDER_CONFIGS: Dict[str, Dict] = {\n \"openai\": {\n \"base_url\": \"https://api.openai.com/v1\",\n \"default_model\": \"gpt-4o-mini\",\n },\n \"groq\": {\n \"base_url\": \"https://api.groq.com/openai/v1\",\n \"default_model\": \"llama-3.3-70b-versatile\",\n },\n \"together\": {\n \"base_url\": \"https://api.together.xyz/v1\",\n \"default_model\": \"meta-llama/Llama-3-70b-chat-hf\",\n },\n \"perplexity\": {\n \"base_url\": \"https://api.perplexity.ai\",\n \"default_model\": \"llama-3.1-sonar-small-128k-online\",\n },\n \"mistral\": {\n \"base_url\": \"https://api.mistral.ai/v1\",\n \"default_model\": \"mistral-small-latest\",\n },\n # Azure OpenAI — base_url and model (deployment name) must be supplied by\n # the user; this entry only carries the default API version.\n \"azure\": {\n \"base_url\": \"\", # must be provided: https://<resource>.openai.azure.com\n \"default_model\": \"\", # must be provided: deployment name\n \"api_version\": \"2024-02-01\",\n },\n}\n\n\ndef _is_azure_url(url: str) -> bool:\n \"\"\"Return True when *url* looks like an Azure OpenAI endpoint.\"\"\"\n return \".openai.azure.com\" in url.lower() or \"azure\" in url.lower()\n\n\nclass OpenAICompatBackend(LLMBackend):\n \"\"\"\n Backend for any OpenAI-compatible chat-completion API.\n\n Args:\n api_key: API key / bearer token.\n For Azure this is the ``api-key`` header value.\n base_url: Base URL ending in ``/v1`` (e.g. ``https://api.groq.com/openai/v1``).\n For Azure: ``https://<resource>.openai.azure.com`` (no trailing path).\n model: Model name. For Azure this is the *deployment name*.\n provider: Shorthand: ``\"openai\"``, ``\"groq\"``, ``\"together\"``,\n ``\"perplexity\"``, ``\"mistral\"``, ``\"azure\"``.\n Sets base_url + model automatically if not specified.\n timeout: HTTP timeout in seconds (default: 60).\n temperature: Sampling temperature (default: 0 for deterministic routing).\n max_tokens: Maximum tokens in the response (default: 512).\n extra_headers: Additional HTTP headers dict.\n api_version: Azure API version string (default: ``\"2024-02-01\"``).\n Only used when provider is ``\"azure\"`` or base_url is an\n Azure endpoint.\n \"\"\"\n\n name = \"openai_compat\"\n\n def __init__(\n self,\n api_key: str,\n base_url: str = \"\",\n model: str = \"\",\n provider: str = \"openai\",\n timeout: int = 60,\n temperature: float = 0.0,\n max_tokens: int = 512,\n extra_headers: Optional[Dict[str, str]] = None,\n api_version: str = \"\",\n ):\n cfg = _PROVIDER_CONFIGS.get(provider.lower(), _PROVIDER_CONFIGS[\"openai\"])\n self.api_key = api_key\n self.base_url = (base_url or cfg.get(\"base_url\", \"\")).rstrip(\"/\")\n self.model = model or cfg.get(\"default_model\", \"\")\n self.provider = provider\n self.timeout = timeout\n self.temperature = temperature\n self.max_tokens = max_tokens\n self.extra_headers = extra_headers or {}\n self.name = provider.lower()\n\n # Azure-specific: determine whether this is an Azure endpoint\n self._is_azure = provider.lower() == \"azure\" or _is_azure_url(self.base_url)\n # API version (only meaningful for Azure)\n self.api_version = api_version or cfg.get(\"api_version\", \"\") or \"2024-02-01\"\n\n if self._is_azure:\n if not self.base_url:\n raise ValueError(\n \"Azure OpenAI requires base_url \"\n \"(e.g. 'https://<resource>.openai.azure.com'). \"\n \"Pass base_url='https://...' to get_backend() or OpenAICompatBackend().\"\n )\n if not self.model:\n raise ValueError(\n \"Azure OpenAI requires model to be set to the deployment name. \"\n \"Pass model='<deployment-name>' to get_backend() or OpenAICompatBackend().\"\n )\n\n # ------------------------------------------------------------------ #\n # LLMBackend interface #\n # ------------------------------------------------------------------ #\n\n def _build_url(self) -> str:\n \"\"\"Return the fully-qualified chat/completions URL for this provider.\"\"\"\n if self._is_azure:\n # Azure URL format:\n # https://<resource>.openai.azure.com/openai/deployments/<deployment>/chat/completions?api-version=<ver>\n return (\n f\"{self.base_url}/openai/deployments/{self.model}\"\n f\"/chat/completions?api-version={self.api_version}\"\n )\n return f\"{self.base_url}/chat/completions\"\n\n def chat(self, messages: List[Dict[str, str]]) -> str:\n \"\"\"Call the OpenAI-compatible /chat/completions endpoint.\"\"\"\n url = self._build_url()\n\n # Azure uses a fixed deployment name in the URL, so it must not\n # appear again in the JSON body.\n body: Dict = {\n \"messages\": messages,\n \"temperature\": self.temperature,\n \"max_tokens\": self.max_tokens,\n }\n if not self._is_azure:\n body[\"model\"] = self.model\n\n payload = json.dumps(body).encode(\"utf-8\")\n\n if self._is_azure:\n # Azure authenticates with the api-key header (not Bearer token)\n headers = {\n \"Content-Type\": \"application/json\",\n \"api-key\": self.api_key,\n }\n else:\n headers = {\n \"Content-Type\": \"application/json\",\n \"Authorization\": f\"Bearer {self.api_key}\",\n }\n headers.update(self.extra_headers)\n\n req = urllib.request.Request(url, data=payload, headers=headers)\n\n try:\n with urllib.request.urlopen(req, timeout=self.timeout) as resp:\n data = json.loads(resp.read().decode(\"utf-8\"))\n except urllib.error.HTTPError as exc:\n body_txt = exc.read().decode(\"utf-8\", errors=\"replace\")\n raise RuntimeError(f\"[{self.name}] HTTP {exc.code}: {body_txt}\") from exc\n except urllib.error.URLError as exc:\n raise RuntimeError(f\"[{self.name}] Connection error: {exc.reason}\") from exc\n\n return data[\"choices\"][0][\"message\"][\"content\"]\n\n def __repr__(self) -> str:\n if self._is_azure:\n return (\n f\"<OpenAICompatBackend provider='azure' \"\n f\"deployment='{self.model}' api_version='{self.api_version}'>\"\n )\n return f\"<OpenAICompatBackend provider='{self.provider}' \" f\"model='{self.model}'>\"\n"}}}} |