Files
ai-ops-agent/relay/relay.py
2025-09-18 14:37:09 -04:00

77 lines
2.8 KiB
Python

import os, json, httpx
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
AGENT_URL = os.getenv("AGENT_URL", "http://ai-agent:8080")
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
def _read_api_key():
# Prefer file from Docker secret if present
path = os.getenv("OPENAI_API_KEY_FILE", "/run/secrets/openai_api_key")
if os.path.exists(path):
return open(path, "r").read().strip()
return os.getenv("OPENAI_API_KEY", "")
SYSTEM_PROMPT = (
"You are an ops command planner. Convert the user's intent into a STRICT JSON object "
"with fields: action (scale|restart_service), params (dict). No prose. Examples: "
'{"action":"scale","params":{"service":"weblabs_php","replicas":3}} '
'or {"action":"restart_service","params":{"service":"weblabs_php"}}. '
"Only produce valid JSON. If unclear, choose the safest no-op."
)
class ChatIn(BaseModel):
prompt: str
app = FastAPI(title="AI Relay (LLM -> Agent)")
@app.get("/health")
def health():
return {"ok": True}
@app.post("/chat")
async def chat(inp: ChatIn):
api_key = _read_api_key()
if not api_key:
raise HTTPException(500, "Missing OPENAI_API_KEY (env or secret).")
# Call OpenAI Responses API
url = "https://api.openai.com/v1/responses"
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
body = {
"model": OPENAI_MODEL,
"input": f"{SYSTEM_PROMPT}\nUSER: {inp.prompt}",
"max_output_tokens": 300,
"temperature": 0.1
}
async with httpx.AsyncClient(timeout=30) as client:
r = await client.post(url, headers=headers, json=body)
if r.status_code >= 400:
raise HTTPException(502, f"OpenAI error: {r.text}")
data = r.json()
# Responses API returns output in 'output_text' (or tool messages). Try common fields.
content = data.get("output_text") or data.get("content") or ""
if isinstance(content, list):
# Some responses return a list of content parts; take text from first text part
for part in content:
if part.get("type") in ("output_text", "text") and part.get("text"):
content = part["text"]
break
if not isinstance(content, str):
content = str(content)
# Parse JSON from the model output
try:
cmd = json.loads(content)
except Exception as e:
raise HTTPException(500, f"Failed to parse model JSON: {e}; content={content[:200]}")
# Forward to the agent
async with httpx.AsyncClient(timeout=15) as client:
r = await client.post(f"{AGENT_URL}/command", json=cmd)
if r.status_code >= 400:
raise HTTPException(r.status_code, f"Agent error: {r.text}")
return r.json()