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, ) from app.modules.ai_agent.constants import ( AUTHORIZATION_BEARER_TEMPLATE, DIRECT_LLM_API_KEY_MISSING, UNEXPECTED_HERMES_RESPONSE, AIErrorKey, AIHttpHeader, AIHttpPath, AIHttpPayloadKey, AIProviderName, AIResponseKey, ) class DirectLLMAdapter(AIAdapter): """Adapter for OpenAI-compatible chat completions APIs.""" provider_name = AIProviderName.DIRECT_LLM def __init__(self, settings: Settings): self.settings = settings def ask(self, prompt: str, context: dict[str, Any] | None = None) -> dict[str, Any]: if not self.settings.direct_llm_api_key: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=DIRECT_LLM_API_KEY_MISSING, ) url = f"{self.settings.direct_llm_base_url.rstrip('/')}{AIHttpPath.CHAT_COMPLETIONS}" headers = { AIHttpHeader.AUTHORIZATION: AUTHORIZATION_BEARER_TEMPLATE.format( token=self.settings.direct_llm_api_key ) } payload = { AIHttpPayloadKey.MODEL: self.settings.direct_llm_model, AIHttpPayloadKey.MESSAGES: _chat_messages(prompt, context), } with httpx.Client(timeout=60, 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.DIRECT_LLM: response.text}, ) data = _chat_completion_payload(response, AIErrorKey.DIRECT_LLM) 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.DIRECT_LLM: UNEXPECTED_HERMES_RESPONSE, AIResponseKey.RAW: data, }, ) from exc return {AIResponseKey.ANSWER: answer, AIResponseKey.RAW: data}