from typing import Any from fastapi import HTTPException, status from app.core.config import Settings from app.modules.ai_agent.adapters import httpx from app.modules.ai_agent.adapters.base import AIAdapter from app.modules.ai_agent.adapters.common import ( _chat_completion_payload, _chat_messages, _response_payload, _service_root, ) from app.modules.ai_agent.constants import ( AUTHORIZATION_BEARER_TEMPLATE, UNEXPECTED_HERMES_RESPONSE, AIErrorKey, AIHttpHeader, AIHttpPath, AIHttpPayloadKey, AIProviderName, AIResponseKey, ) class HermesAdapter(AIAdapter): """Adapter for the Hermes OpenAI-compatible agent endpoint.""" provider_name = AIProviderName.HERMES def __init__(self, settings: Settings): self.settings = settings def ask(self, prompt: str, context: dict[str, Any] | None = None) -> dict[str, Any]: url = f"{self.settings.hermes_base_url.rstrip('/')}{AIHttpPath.CHAT_COMPLETIONS}" headers = {} if self.settings.hermes_api_key: headers[AIHttpHeader.AUTHORIZATION] = AUTHORIZATION_BEARER_TEMPLATE.format( token=self.settings.hermes_api_key ) if self.settings.hermes_session_id: headers[AIHttpHeader.HERMES_SESSION_ID] = self.settings.hermes_session_id payload = { AIHttpPayloadKey.MODEL: self.settings.hermes_model, AIHttpPayloadKey.MESSAGES: _chat_messages(prompt, context), AIHttpPayloadKey.STREAM: False, } with httpx.Client(timeout=300, trust_env=False) as client: response = client.post(url, json=payload, headers=headers) if response.status_code >= status.HTTP_400_BAD_REQUEST: raise HTTPException( status_code=status.HTTP_502_BAD_GATEWAY, detail={AIErrorKey.HERMES: response.text}, ) data = _chat_completion_payload(response, AIErrorKey.HERMES) try: answer = data[AIHttpPayloadKey.CHOICES][0][AIHttpPayloadKey.MESSAGE][ AIHttpPayloadKey.CONTENT ] except (KeyError, IndexError, TypeError) as exc: raise HTTPException( status_code=status.HTTP_502_BAD_GATEWAY, detail={ AIErrorKey.HERMES: UNEXPECTED_HERMES_RESPONSE, AIResponseKey.RAW: data, }, ) from exc return {AIResponseKey.ANSWER: answer, AIResponseKey.RAW: data} def health(self) -> dict[str, Any]: """Check Hermes Agent health without triggering a chat completion.""" url = f"{_service_root(self.settings.hermes_base_url, AIHttpPath.V1)}{AIHttpPath.HEALTH}" headers = {} if self.settings.hermes_api_key: headers[AIHttpHeader.AUTHORIZATION] = AUTHORIZATION_BEARER_TEMPLATE.format( token=self.settings.hermes_api_key ) with httpx.Client(timeout=5, trust_env=False) as client: response = client.get(url, headers=headers) return { AIResponseKey.OK: response.status_code < status.HTTP_400_BAD_REQUEST, AIResponseKey.BASE_URL: self.settings.hermes_base_url.rstrip("/"), AIResponseKey.HEALTH: _response_payload(response), }