161 lines
5.4 KiB
Python
161 lines
5.4 KiB
Python
from fastapi import FastAPI, Body, Query, BackgroundTasks
|
|
from collections import deque
|
|
from datetime import datetime
|
|
from uuid import uuid4
|
|
import requests
|
|
import os
|
|
import sys
|
|
|
|
# ─────────────────────────────
|
|
# Config
|
|
# ─────────────────────────────
|
|
SUMMARY_MODEL = os.getenv("SUMMARY_MODEL_NAME", "mistral-7b-instruct-v0.2.Q4_K_M.gguf")
|
|
SUMMARY_URL = os.getenv("SUMMARY_API_URL", "http://localhost:8080/v1/completions")
|
|
SUMMARY_MAX_TOKENS = int(os.getenv("SUMMARY_MAX_TOKENS", "200"))
|
|
SUMMARY_TEMPERATURE = float(os.getenv("SUMMARY_TEMPERATURE", "0.3"))
|
|
|
|
NEOMEM_API = os.getenv("NEOMEM_API")
|
|
NEOMEM_KEY = os.getenv("NEOMEM_KEY")
|
|
|
|
# ─────────────────────────────
|
|
# App + session buffer
|
|
# ─────────────────────────────
|
|
app = FastAPI()
|
|
SESSIONS = {}
|
|
|
|
@app.on_event("startup")
|
|
def banner():
|
|
print("🧩 Intake v0.2 booting...")
|
|
print(f" Model: {SUMMARY_MODEL}")
|
|
print(f" API: {SUMMARY_URL}")
|
|
sys.stdout.flush()
|
|
|
|
# ─────────────────────────────
|
|
# Helper: summarize exchanges
|
|
# ─────────────────────────────
|
|
def llm(prompt: str):
|
|
try:
|
|
resp = requests.post(
|
|
SUMMARY_URL,
|
|
json={
|
|
"model": SUMMARY_MODEL,
|
|
"prompt": prompt,
|
|
"max_tokens": SUMMARY_MAX_TOKENS,
|
|
"temperature": SUMMARY_TEMPERATURE,
|
|
},
|
|
timeout=30,
|
|
)
|
|
resp.raise_for_status()
|
|
return resp.json().get("choices", [{}])[0].get("text", "").strip()
|
|
except Exception as e:
|
|
return f"[Error summarizing: {e}]"
|
|
|
|
def summarize_simple(exchanges):
|
|
"""Simple factual summary of recent exchanges."""
|
|
text = ""
|
|
for e in exchanges:
|
|
text += f"User: {e['user_msg']}\nAssistant: {e['assistant_msg']}\n\n"
|
|
|
|
prompt = f"""
|
|
Summarize the following conversation between Brian (user) and Lyra (assistant).
|
|
Focus only on factual content. Avoid names, examples, story tone, or invented details.
|
|
|
|
{text}
|
|
|
|
Summary:
|
|
"""
|
|
return llm(prompt)
|
|
|
|
# ─────────────────────────────
|
|
# NeoMem push
|
|
# ─────────────────────────────
|
|
def push_to_neomem(summary: str, session_id: str):
|
|
if not NEOMEM_API:
|
|
return
|
|
|
|
headers = {"Content-Type": "application/json"}
|
|
if NEOMEM_KEY:
|
|
headers["Authorization"] = f"Bearer {NEOMEM_KEY}"
|
|
|
|
payload = {
|
|
"messages": [{"role": "assistant", "content": summary}],
|
|
"user_id": "brian",
|
|
"metadata": {
|
|
"source": "intake",
|
|
"session_id": session_id
|
|
}
|
|
}
|
|
|
|
try:
|
|
requests.post(
|
|
f"{NEOMEM_API}/memories",
|
|
json=payload,
|
|
headers=headers,
|
|
timeout=20
|
|
).raise_for_status()
|
|
print(f"🧠 NeoMem updated for {session_id}")
|
|
except Exception as e:
|
|
print(f"NeoMem push failed: {e}")
|
|
|
|
# ─────────────────────────────
|
|
# Background summarizer
|
|
# ─────────────────────────────
|
|
def bg_summarize(session_id: str):
|
|
try:
|
|
hopper = SESSIONS.get(session_id)
|
|
if not hopper:
|
|
return
|
|
|
|
buf = list(hopper["buffer"])
|
|
summary = summarize_simple(buf)
|
|
push_to_neomem(summary, session_id)
|
|
|
|
print(f"🧩 Summary generated for {session_id}")
|
|
except Exception as e:
|
|
print(f"Summarizer error: {e}")
|
|
|
|
# ─────────────────────────────
|
|
# Routes
|
|
# ─────────────────────────────
|
|
|
|
@app.post("/add_exchange")
|
|
def add_exchange(exchange: dict = Body(...), background_tasks: BackgroundTasks = None):
|
|
|
|
session_id = exchange.get("session_id") or f"sess-{uuid4().hex[:8]}"
|
|
exchange["session_id"] = session_id
|
|
exchange["timestamp"] = datetime.now().isoformat()
|
|
|
|
if session_id not in SESSIONS:
|
|
SESSIONS[session_id] = {
|
|
"buffer": deque(maxlen=200),
|
|
"created_at": datetime.now()
|
|
}
|
|
print(f"🆕 Hopper created: {session_id}")
|
|
|
|
SESSIONS[session_id]["buffer"].append(exchange)
|
|
|
|
if background_tasks:
|
|
background_tasks.add_task(bg_summarize, session_id)
|
|
print(f"⏩ Summarization queued for {session_id}")
|
|
|
|
return {"ok": True, "session_id": session_id}
|
|
|
|
@app.post("/close_session/{session_id}")
|
|
def close_session(session_id: str):
|
|
if session_id in SESSIONS:
|
|
del SESSIONS[session_id]
|
|
return {"ok": True, "closed": session_id}
|
|
|
|
@app.get("/summaries")
|
|
def get_summary(session_id: str = Query(...)):
|
|
hopper = SESSIONS.get(session_id)
|
|
if not hopper:
|
|
return {"summary_text": "(none)", "session_id": session_id}
|
|
|
|
summary = summarize_simple(list(hopper["buffer"]))
|
|
return {"summary_text": summary, "session_id": session_id}
|
|
|
|
@app.get("/health")
|
|
def health():
|
|
return {"ok": True, "model": SUMMARY_MODEL, "url": SUMMARY_URL}
|