"""Provider adapter for verified delivery; guards stay independent of the model vendor.""" from __future__ import annotations import json import os import urllib.parse import urllib.request class NoRedirect(urllib.request.HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): return None def complete_with_configured_model(messages: list[dict], *, max_tokens: int | None = None) -> dict: if max_tokens is not None and (type(max_tokens) is not int or not 1 <= max_tokens <= 16384): raise ValueError("Invalid model output budget") provider = os.getenv("ANSWER_PROVIDER", "openai_compatible").strip() model = os.getenv("ANSWER_MODEL", "").strip() if provider == "anthropic": key = (os.getenv("ANSWER_API_KEY") or os.getenv("ANTHROPIC_API_KEY", "")).strip() endpoint = os.getenv("ANSWER_LLM_URL", "https://api.anthropic.com/v1/messages").strip() system = "\n\n".join(row["content"] for row in messages if row["role"] == "system") body = { "model": model, "messages": [row for row in messages if row["role"] != "system"], "max_tokens": max_tokens or int(os.getenv("ANSWER_MAX_TOKENS", "4096")), "temperature": 0, } if system: body["system"] = system headers = {"x-api-key": key, "anthropic-version": "2023-06-01"} elif provider == "openai_compatible": # Compatibility for historical experiments; new installations use ANSWER_API_KEY. legacy = os.getenv("OPENWEBUI_API_KEY", "").strip() key = os.getenv("ANSWER_API_KEY", "").strip() or legacy endpoint = os.getenv( "ANSWER_LLM_URL", "https://ui.tukekemt.xyz/api/chat/completions" if legacy else "" ).strip() model = model or ("model120-fast" if legacy else "") body = {"model": model, "messages": messages, "temperature": 0, "stream": False} if max_tokens is not None: body["max_tokens"] = max_tokens headers = {"Authorization": "Bearer " + key} else: raise RuntimeError("Unsupported answer provider") parsed = urllib.parse.urlsplit(endpoint) if ( not key or not model or any(c in key for c in "\r\n") or parsed.scheme != "https" or not parsed.hostname or parsed.username is not None or parsed.password is not None or parsed.fragment or parsed.query or any(c.isspace() for c in endpoint) ): raise RuntimeError("Answer model is not configured") request = urllib.request.Request( endpoint, data=json.dumps(body, ensure_ascii=False).encode(), headers={"Content-Type": "application/json", **headers}, ) with urllib.request.build_opener(NoRedirect()).open(request, timeout=120) as response: data = response.read(2 * 1024 * 1024 + 1) if len(data) > 2 * 1024 * 1024: raise RuntimeError("Answer model output is too large") value = json.loads(data) if provider == "anthropic": if value.get("stop_reason") != "end_turn": raise RuntimeError("Incomplete answer model output") text = "".join( block["text"] for block in value.get("content", []) if block.get("type") == "text" ) if not text: raise RuntimeError("Missing answer model output") return {"choices": [{"message": {"content": text}}]} return value