OSVauco/emma/emma_server.py
Chris Christiansen 9fcb9c354a
Some checks are pending
Check Python Version Consistency / Check Python Version (push) Waiting to run
feat(core): Fresh initialization - Deploy v3.6.1 Singularity Architecture
2026-09-03 04:03:09 +00:00

139 lines
4.3 KiB
Python

"""
emma_server.py — HTTP-wrapper rundt Emma-agenten.
Start: cd /home/chris_christiansen/OSVauco && python -m emma.emma_server
Port: 8765
Endepunkter:
POST /ask {"prompt": "...", "history": [...]} → {"response": "..."}
GET /health → {"status": "ok", "model": "...", "memory_patterns": N}
GET /stats → Flynn-statistikk
Modell-prioritet (iht. llm-stack.md):
1. qwen2.5-coder:7b (kode/repo-arbeid, tools)
2. qwen2.5:7b (generell, tools)
3. gemma3:4b (fallback, ingen tools)
Regler (iht. emma_identity.py + HANDOFF.md):
- Emma rapporterer kun til Chris Christiansen
- Guardrails kan ikke deaktiveres
- Aldri deploy uten eksplisitt GO fra Chris
- Gitea er primær Git
"""
import sys
import json
import logging
from pathlib import Path
from http.server import BaseHTTPRequestHandler, HTTPServer
sys.path.insert(0, str(Path(__file__).parent.parent))
from emma.emma_backend_ollama import OllamaBackend
from emma.emma_resonance_persistent import PersistentMorphicMemory
from emma.emma_mcot import EmmaMCoTAgent
from emma.emma_guardrails import EmmaGuardrails
from emma.emma_flynn import FlynnTracker
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [emma-server] %(levelname)s %(message)s",
)
log = logging.getLogger("emma-server")
# Modell-prioritet iht. llm-stack.md
DEFAULT_MODEL = "qwen2.5-coder:7b"
DEFAULT_EMBED = "qwen2.5:3b" # brukes til morfisk resonans-embedding
PORT = 8765
def _build_agent(model: str = DEFAULT_MODEL) -> tuple:
llm = OllamaBackend(model=model, embed_model=DEFAULT_EMBED)
memory = PersistentMorphicMemory()
guardrails = EmmaGuardrails()
agent = EmmaMCoTAgent(llm=llm, memory=memory)
return agent, guardrails, memory
# Bygg agent ved oppstart
log.info(f"Laster Emma med modell={DEFAULT_MODEL} ...")
_agent, _guardrails, _memory = _build_agent()
log.info(f"Emma klar | {len(_memory)} morfiske m\u00f8nstre lastet")
class EmmaHandler(BaseHTTPRequestHandler):
def log_message(self, format, *args): # noqa: A002
log.info(f"{self.address_string()} {format % args}")
def _send_json(self, code: int, data: dict):
body = json.dumps(data, ensure_ascii=False).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def _read_body(self) -> dict:
length = int(self.headers.get("Content-Length", 0))
raw = self.rfile.read(length) if length else b"{}"
try:
return json.loads(raw)
except json.JSONDecodeError:
return {}
def do_GET(self):
if self.path == "/health":
self._send_json(200, {
"status": "ok",
"model": DEFAULT_MODEL,
"memory_patterns": len(_memory),
"agent": "EmmaMCoTAgent",
})
elif self.path == "/stats":
stats = FlynnTracker().summary() or {}
self._send_json(200, stats)
else:
self._send_json(404, {"error": "not found"})
def do_POST(self):
if self.path != "/ask":
self._send_json(404, {"error": "not found"})
return
body = self._read_body()
prompt = (body.get("prompt") or "").strip()
history = body.get("history") or []
if not prompt:
self._send_json(400, {"error": "prompt er p\u00e5krevd"})
return
# Guardrail-sjekk
ok, reason = _guardrails.can_execute(prompt)
if not ok:
log.warning(f"Guardrail blokkerte: {reason}")
self._send_json(403, {"error": reason, "blocked": True})
return
log.info(f"Sp\u00f8rsm\u00e5l: {prompt[:80]}")
try:
response = _agent.run(prompt, history)
self._send_json(200, {"response": response, "model": DEFAULT_MODEL})
except Exception as e:
log.error(f"Agent-feil: {e}")
self._send_json(500, {"error": str(e)})
def main():
server = HTTPServer(("0.0.0.0", PORT), EmmaHandler)
log.info(f"Emma HTTP-server lytter p\u00e5 port {PORT}")
try:
server.serve_forever()
except KeyboardInterrupt:
log.info("Emma server stoppet")
server.server_close()
if __name__ == "__main__":
main()