"""LLM bridge for pipeline handlers. Provides a simple async interface for handlers to call LLM APIs. Uses harnessed_agent's llm_chat under the hood when available, falls back to direct HTTP calls. """ import json import logging import os logger = logging.getLogger("pipeline.llm_bridge") async def llm_call(prompt: str, model: str = None, temperature: float = 0.7) -> str: """Call LLM and return text response. Tries multiple backends: 1. harnessed_agent.llm_chat (if loaded in ServerEnv) 2. Direct OpenAI-compatible API call """ # Try harnessed_agent first try: from ahserver.serverenv import ServerEnv env = ServerEnv() if hasattr(env, 'llm_chat'): result = await env.llm_chat(prompt, model=model, temperature=temperature) if isinstance(result, dict): return result.get("content", result.get("text", str(result))) return str(result) except Exception: pass # Fallback: direct HTTP call to OpenAI-compatible endpoint import aiohttp api_base = os.environ.get("LLM_API_BASE", "https://api.openai.com/v1") api_key = os.environ.get("LLM_API_KEY", "") model = model or os.environ.get("LLM_MODEL", "gpt-4o-mini") if not api_key: raise ValueError("No LLM API configured (set LLM_API_KEY env var)") headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", } payload = { "model": model, "messages": [{"role": "user", "content": prompt}], "temperature": temperature, } async with aiohttp.ClientSession() as session: async with session.post( f"{api_base}/chat/completions", headers=headers, json=payload, timeout=aiohttp.ClientTimeout(total=120) ) as resp: if resp.status != 200: text = await resp.text() raise ValueError(f"LLM API error {resp.status}: {text[:200]}") data = await resp.json() return data["choices"][0]["message"]["content"]