From 5c66e4ca00a0a7fe32232a90f4b546459c44c774 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 3 Jun 2026 13:58:55 +0200 Subject: feat: add local API support for OpenAI compatible APIs Signed-off-by: salmonik --- cogs/utils/llm.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 cogs/utils/llm.py (limited to 'cogs/utils/llm.py') diff --git a/cogs/utils/llm.py b/cogs/utils/llm.py new file mode 100644 index 0000000..2b05b12 --- /dev/null +++ b/cogs/utils/llm.py @@ -0,0 +1,62 @@ +# This file is part of Senator Sperma +# Licensed under the GPL-3.0 License +# Copyright (C) 2026 Salmonik @ salmonik@tuta.io +# https://codeberg.org/salmonik/senator-sperma + +import os +import httpx +import logging + +logger = logging.getLogger(__name__) + +BACKWARD_COMPAT_MSG = ( + "OPENROUTER_API_KEY/OPENROUTER_MODEL are deprecated. " + "Use LLM_API_KEY, LLM_MODEL instead." +) + + +def _get_config(): + api_url = os.getenv("LLM_API_URL", "https://openrouter.ai/api/v1/chat/completions") + api_key = os.getenv("LLM_API_KEY") or os.getenv("OPENROUTER_API_KEY") or "" + model = os.getenv("LLM_MODEL") or os.getenv("OPENROUTER_MODEL") or "" + + if os.getenv("OPENROUTER_API_KEY") and not os.getenv("LLM_API_KEY"): + logger.warning(BACKWARD_COMPAT_MSG) + if os.getenv("OPENROUTER_MODEL") and not os.getenv("LLM_MODEL"): + logger.warning(BACKWARD_COMPAT_MSG) + + return api_url, api_key, model + + +async def llm_completion( + messages: list[dict], + model: str | None = None, + api_url: str | None = None, + api_key: str | None = None, + extra: dict | None = None, + timeout: float = 180.0, +) -> str | None: + url = api_url or os.getenv("LLM_API_URL", "https://openrouter.ai/api/v1/chat/completions") + model = model or os.getenv("LLM_MODEL") or os.getenv("OPENROUTER_MODEL") or "" + api_key = api_key or os.getenv("LLM_API_KEY") or os.getenv("OPENROUTER_API_KEY") or "" + + headers = {"Content-Type": "application/json"} + if api_key: + headers["Authorization"] = f"Bearer {api_key}" + + payload: dict = {"model": model, "messages": messages} + if extra: + payload.update(extra) + + verify = os.getenv("LLM_API_VERIFY", "true").lower() in ("true", "1", "yes") + + try: + async with httpx.AsyncClient(timeout=timeout, verify=verify) as client: + resp = await client.post(url, headers=headers, json=payload) + resp.raise_for_status() + data = resp.json() + logger.info("LLM response: %s", data) + return data["choices"][0]["message"]["content"] + except httpx.HTTPError as e: + logger.error("LLM API request failed (url=%s, model=%s): %s", url, model, e, exc_info=True) + return None -- cgit v1.2.3