hermes-agent / app.py
kidpro2002's picture
Update app.py
94be797 verified
Raw
History Blame Contribute Delete
10.9 kB
import os
from datetime import datetime
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse
from pydantic import BaseModel
from typing import Optional, List
from openai import OpenAI
# ── App Setup ──────────────────────────────────────────────────────────────
app = FastAPI(title="HERMES AGENT", version="1.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# ── Config ─────────────────────────────────────────────────────────────────
NVIDIA_API_KEY = os.getenv("OPENAI_API_KEY", "")
SUPABASE_URL = os.getenv("SUPABASE_URL", "")
SUPABASE_KEY = os.getenv("SUPABASE_KEY", "")
HERMES_NAME = os.getenv("HERMES_NAME", "HERMES")
NVIDIA_MODEL = os.getenv("NVIDIA_MODEL", "meta/llama-3.1-70b-instruct")
HERMES_SYSTEM_PROMPT = os.getenv(
"HERMES_SYSTEM_PROMPT",
"Eres HERMES, un agente de inteligencia artificial avanzado, leal y preciso. "
"Tienes memoria persistente. Respondes en el mismo idioma que el usuario."
)
# ── NVIDIA NIM Client (OpenAI-compatible) ──────────────────────────────────
client: Optional[OpenAI] = None
if NVIDIA_API_KEY:
client = OpenAI(
base_url="https://integrate.api.nvidia.com/v1",
api_key=NVIDIA_API_KEY
)
print(f"[HERMES] NVIDIA NIM client initialized. Model: {NVIDIA_MODEL}")
else:
print("[HERMES] WARNING: OPENAI_API_KEY not set!")
# ── Supabase memory ────────────────────────────────────────────────────────
supabase = None
if SUPABASE_URL and SUPABASE_KEY:
try:
from supabase import create_client
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
print("[HERMES] Supabase connected.")
except Exception as e:
print(f"[HERMES] Supabase error: {e}")
# In-memory fallback when Supabase not configured
_mem_fallback: dict = {}
def get_memory(session_id: str, limit: int = 20) -> List[dict]:
if supabase:
try:
r = (supabase.table("hermes_memory")
.select("role,content")
.eq("session_id", session_id)
.order("created_at", desc=False)
.limit(limit)
.execute())
return r.data or []
except Exception as e:
print(f"[Memory] read error: {e}")
return _mem_fallback.get(session_id, [])[-limit:]
def save_memory(session_id: str, role: str, content: str, user_id: str = "anon"):
if supabase:
try:
supabase.table("hermes_memory").insert({
"session_id": session_id, "user_id": user_id,
"role": role, "content": content,
"created_at": datetime.utcnow().isoformat()
}).execute()
return
except Exception as e:
print(f"[Memory] save error: {e}")
# fallback
if session_id not in _mem_fallback:
_mem_fallback[session_id] = []
_mem_fallback[session_id].append({"role": role, "content": content})
# ── Models ─────────────────────────────────────────────────────────────────
class ChatRequest(BaseModel):
message: str
session_id: str = "default"
user_id: str = "anonymous"
class ChatResponse(BaseModel):
reply: str
session_id: str
timestamp: str
model: str
# ── Routes ─────────────────────────────────────────────────────────────────
@app.get("/", response_class=HTMLResponse)
async def root():
return """
<!DOCTYPE html>
<html lang='es'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<title>HERMES AGENT</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Segoe UI',sans-serif;background:#0d1117;color:#e6edf3;height:100vh;display:flex;flex-direction:column}
header{background:#161b22;padding:14px 20px;border-bottom:1px solid #30363d;display:flex;align-items:center;gap:10px}
header h1{font-size:1.2rem;color:#58a6ff}
.badge{font-size:.7rem;background:#1f6feb;color:#fff;padding:2px 8px;border-radius:10px}
.badge-green{background:#238636}
#chat-box{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px}
.msg{max-width:78%;padding:10px 14px;border-radius:12px;line-height:1.5;word-wrap:break-word;white-space:pre-wrap}
.user{align-self:flex-end;background:#1f6feb}
.agent{align-self:flex-start;background:#21262d;border:1px solid #30363d}
.agent-name{color:#58a6ff;font-size:.75rem;font-weight:700;display:block;margin-bottom:3px}
.error-msg{align-self:flex-start;background:#3d1c1c;border:1px solid #f85149;color:#f85149}
#input-area{padding:12px 16px;background:#161b22;border-top:1px solid #30363d;display:flex;gap:8px}
#input-area input{flex:1;padding:9px 14px;background:#0d1117;border:1px solid #30363d;border-radius:8px;color:#e6edf3;font-size:.95rem;outline:none}
#input-area input:focus{border-color:#58a6ff}
#input-area button{padding:9px 18px;background:#1f6feb;border:none;border-radius:8px;color:#fff;cursor:pointer;font-size:.95rem}
#input-area button:disabled{background:#30363d;cursor:not-allowed}
#status{font-size:.72rem;color:#8b949e;padding:3px 16px;background:#161b22}
</style>
</head>
<body>
<header>
<h1>&#9889; HERMES AGENT</h1>
<span class='badge'>v1.0</span>
<span class='badge badge-green'>NVIDIA NIM</span>
</header>
<div id='chat-box'></div>
<div id='status'>Listo. Escribe tu mensaje.</div>
<div id='input-area'>
<input type='text' id='msg-input' placeholder='Escribe tu mensaje...' autocomplete='off'/>
<button id='send-btn' onclick='sendMessage()'>Enviar</button>
</div>
<script>
const sessionId='ses_'+Math.random().toString(36).substr(2,9);
const chatBox=document.getElementById('chat-box');
const status=document.getElementById('status');
const btn=document.getElementById('send-btn');
function appendMsg(role,text,isError=false){
const d=document.createElement('div');
if(isError){d.className='msg error-msg';d.textContent='Error: '+text;}
else if(role==='user'){d.className='msg user';d.textContent=text;}
else{d.className='msg agent';d.innerHTML='<span class="agent-name">HERMES</span>'+text.replace(/</g,'&lt;');}
chatBox.appendChild(d);
chatBox.scrollTop=chatBox.scrollHeight;
}
async function sendMessage(){
const input=document.getElementById('msg-input');
const msg=input.value.trim();
if(!msg)return;
input.value='';
btn.disabled=true;
appendMsg('user',msg);
status.textContent='HERMES esta pensando...';
try{
const res=await fetch('/chat',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({message:msg,session_id:sessionId})
});
const data=await res.json();
if(!res.ok){appendMsg('agent',data.detail||'Error desconocido',true);}
else{appendMsg('agent',data.reply);status.textContent='Modelo: '+data.model+' | '+data.timestamp;}
}catch(e){appendMsg('agent','Error de conexion: '+e.message,true);status.textContent='Error.';}
finally{btn.disabled=false;input.focus();}
}
document.getElementById('msg-input').addEventListener('keydown',e=>{if(e.key==='Enter'&&!btn.disabled)sendMessage();});
</script>
</body>
</html>
"""
@app.get("/health")
async def health():
return {
"status": "ok",
"agent": HERMES_NAME,
"timestamp": datetime.utcnow().isoformat(),
"nvidia_key_set": bool(NVIDIA_API_KEY),
"model": NVIDIA_MODEL,
"supabase_connected": supabase is not None
}
@app.post("/chat", response_model=ChatResponse)
async def chat(req: ChatRequest):
if not client:
raise HTTPException(
status_code=503,
detail="NVIDIA API Key no configurada. Ve a Settings > Variables and secrets > agrega OPENAI_API_KEY con tu nvapi-... key."
)
history = get_memory(req.session_id)
messages = [{"role": "system", "content": HERMES_SYSTEM_PROMPT}]
for entry in history:
messages.append({"role": entry["role"], "content": entry["content"]})
messages.append({"role": "user", "content": req.message})
try:
response = client.chat.completions.create(
model=NVIDIA_MODEL,
messages=messages,
max_tokens=1024,
temperature=0.7,
stream=False
)
reply = response.choices[0].message.content
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
save_memory(req.session_id, "user", req.message, req.user_id)
save_memory(req.session_id, "assistant", reply, req.user_id)
return ChatResponse(
reply=reply,
session_id=req.session_id,
timestamp=datetime.utcnow().strftime("%H:%M:%S UTC"),
model=NVIDIA_MODEL
)
@app.get("/sessions/{session_id}/history")
async def get_history(session_id: str):
history = get_memory(session_id, limit=100)
return {"session_id": session_id, "messages": history, "count": len(history)}
@app.delete("/sessions/{session_id}")
async def clear_session(session_id: str):
if supabase:
try:
supabase.table("hermes_memory").delete().eq("session_id", session_id).execute()
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
elif session_id in _mem_fallback:
del _mem_fallback[session_id]
return {"status": "cleared", "session_id": session_id}