Files
foodlinkk-command-center/cockpit/herman.py
T

149 lines
5.7 KiB
Python
Raw Normal View History

from __future__ import annotations
import json
from typing import Any
import httpx
from app.config import settings
from app.db import execute, fetch_one
from app.services import ollama
AGENTS: dict[str, dict[str, str]] = {
"marketing": {"name": "Marketing", "persona": "Social, brand voice, campaigns for Foodlinkk."},
"bizdev": {"name": "BizDev", "persona": "Pipeline, retail partnerships, deal structuring."},
"finance": {"name": "Finance", "persona": "Margins, cashflow, pricing for food brands."},
"sourcing": {"name": "Sourcing", "persona": "Suppliers, MOQ, lead times, procurement."},
"product": {"name": "Product", "persona": "SKU development, launch timelines, shelf readiness."},
"halal": {"name": "Halal", "persona": "Halal compliance, certification, ingredient vetting."},
"design": {"name": "Design", "persona": "Packaging, visual identity, retail presentation."},
"knowledge": {"name": "Knowledge", "persona": "Internal docs, RAG, policy answers."},
}
IMAGE_KEYWORDS = (
"maak foto", "maak een foto", "genereer foto", "genereer afbeelding",
"maak afbeelding", "productfoto", "genereer image", "generate image",
"make image", "maak plaatje", "/genfoto",
)
def _wants_image(raw: str) -> bool:
t = (raw or "").strip().lower()
return any(k in t for k in IMAGE_KEYWORDS)
def _extract_image_prompt(raw: str) -> str:
t = raw.strip()
lower = t.lower()
for k in IMAGE_KEYWORDS:
if lower.startswith(k):
rest = t[len(k):].strip(" :,-")
if rest:
return rest
for k in IMAGE_KEYWORDS:
if k in lower:
idx = lower.index(k) + len(k)
rest = t[idx:].strip(" :,-")
if rest:
return rest
return t
async def _log_event(agent_name: str, event_type: str, title: str, body: str, metadata: dict | None = None) -> None:
payload = {
"agent_name": agent_name,
"agent_type": "herman_delegate",
"event_type": event_type,
"title": title[:255],
"body": body,
"metadata": metadata or {},
"status": "completed",
"channel": "herman",
}
try:
async with httpx.AsyncClient(timeout=15.0) as client:
await client.post(f"{settings.TOOLS_API_URL.rstrip('/')}/events", json=payload)
except Exception:
try:
execute(
"""INSERT INTO agent_events (agent_name, agent_type, event_type, title, body, status, channel, metadata)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s::jsonb)""",
(agent_name, "herman_delegate", event_type, title[:255], body, "completed", "herman", json.dumps(metadata or {})),
)
except Exception:
pass
def _pick_agent(raw: str) -> str:
text = (raw or "").strip().lower()
first = text.split()[0].replace(",", "").replace(".", "") if text else "knowledge"
if first in AGENTS:
return first
for k in AGENTS:
if k in text:
return k
return "knowledge"
async def chat(message: str) -> dict[str, Any]:
if _wants_image(message):
prompt = _extract_image_prompt(message)
try:
async with httpx.AsyncClient(timeout=620.0) as client:
r = await client.post(
f"{settings.TOOLS_API_URL.rstrip('/')}/images/generate",
json={"prompt": prompt, "width": 512, "height": 512, "steps": 15},
)
r.raise_for_status()
data = r.json()
filename = data.get("filename", "")
subfolder = data.get("subfolder", "")
img_type = data.get("type", "output")
proxy = f"/api/ai/generated-image?filename={filename}&subfolder={subfolder}&type={img_type}"
reply = f"Afbeelding gegenereerd voor: {prompt}"
await _log_event("design", "image_generated", "ComfyUI via Herman", prompt[:500], {"filename": filename})
return {
"agent": "design",
"agent_label": "Design",
"reply": reply,
"image_url": proxy,
"prompt": prompt,
}
except Exception as exc:
return {
"agent": "design",
"agent_label": "Design",
"reply": f"Kon geen afbeelding genereren: {exc}",
}
try:
async with httpx.AsyncClient(timeout=620.0) as client:
r = await client.post(
f"{settings.HERMAN_ORCHESTRATOR_URL.rstrip('/')}/chat",
json={"message": message, "agent": "default", "use_crm": True, "channel": "cockpit"},
)
r.raise_for_status()
data = r.json()
delegated = data.get("delegated_agents") or [data.get("agent", "herman")]
await _log_event(
"herman",
"openswarm_delegation",
f"Herman → {', '.join(delegated)}",
message[:2000],
{"delegated": delegated, "reason": data.get("routing_reason", "")},
)
return {
"agent": data.get("agent", "herman"),
"agent_label": data.get("agent_label", "Herman"),
"reply": data.get("reply", ""),
"delegated_agents": delegated,
"routing_reason": data.get("routing_reason", ""),
}
except Exception as exc:
return {
"agent": "herman",
"agent_label": "Herman",
"reply": f"Herman orchestrator niet bereikbaar: {exc}",
}
async def generate_briefing() -> str:
from app.services.briefing import generate_daily_briefing
content, stats = await generate_daily_briefing()
await _log_event("herman", "briefing", "CEO briefing generated", content[:1500], {"stats": stats})
return content