SysOps: manual — 2026-07-30 09:33 UTC

This commit is contained in:
sysops
2026-07-30 09:33:03 +00:00
parent f21d58483b
commit 5dd69a1138
25 changed files with 4148 additions and 734 deletions
+58
View File
@@ -119,6 +119,40 @@ def _agent_steps(delegated: list[str], routing_reason: str = "", extra: list[dic
return steps
async def _chat_via_llm_router(message: str, channel: str) -> dict[str, Any]:
from app.services import llm_router
system = (
"Je bent Herman, de AI-assistent van Foodlinkk B2B (halal vlees, export, CRM). "
"Antwoord in het Nederlands, bondig, actiegericht. Geef concrete vervolgstappen waar nuttig."
)
messages = [
{"role": "system", "content": system},
{"role": "user", "content": message},
]
reply, meta = await llm_router.chat_messages(messages, timeout=120.0)
provider = meta.get("provider_label") or "LLM"
model = meta.get("model") or ""
await _log_event(
"herman",
"llm_chat",
f"Herman via {provider}",
message[:2000],
{"provider": meta, "channel": channel},
channel=channel,
)
return {
"agent": "herman",
"agent_label": f"Herman · {provider}",
"reply": reply,
"delegated_agents": ["herman"],
"routing_reason": f"Cloud LLM · {provider}" + (f" · {model}" if model else ""),
"agent_steps": _agent_steps([], f"Via {provider}"),
"llm_provider": meta,
}
async def chat(
message: str,
channel: str = "cockpit",
@@ -344,6 +378,17 @@ async def chat(
"reply": f"Kon geen afbeelding genereren: {exc}",
}
backend = (settings.HERMAN_LLM_BACKEND or "auto").strip().lower()
if backend in ("router", "openrouter", "llm"):
try:
return await _chat_via_llm_router(message, channel)
except Exception as exc:
return {
"agent": "herman",
"agent_label": "Herman",
"reply": f"LLM provider niet beschikbaar: {exc}. Vul OpenRouter key in via Instellingen → AI / LLM.",
}
try:
async with httpx.AsyncClient(timeout=620.0) as client:
r = await client.post(
@@ -373,6 +418,19 @@ async def chat(
"crm_loaded": data.get("crm_loaded", False),
}
except Exception as exc:
if backend == "auto":
try:
return await _chat_via_llm_router(message, channel)
except Exception as llm_exc:
return {
"agent": "herman",
"agent_label": "Herman",
"reply": (
f"Orchestrator offline ({exc}). "
f"Cloud LLM ook niet beschikbaar ({llm_exc}). "
"Voeg OpenRouter API key toe: Instellingen → AI / LLM."
),
}
return {
"agent": "herman",
"agent_label": "Herman",
+69 -3
View File
@@ -48,13 +48,17 @@ LLM_PRESETS: dict[str, dict[str, Any]] = {
"label": "OpenRouter",
"api_base_url": "https://openrouter.ai/api/v1",
"models": [
"deepseek/deepseek-v4-flash",
"deepseek/deepseek-v4-flash:free",
"deepseek/deepseek-chat",
"deepseek/deepseek-r1",
"google/gemini-2.0-flash-exp:free",
"deepseek/deepseek-r1:free",
"meta-llama/llama-3.3-70b-instruct:free",
],
"default_model": "deepseek/deepseek-v4-flash",
"needs_key": True,
"signup_url": "https://openrouter.ai/",
"hint": "Veel gratis modellen via één API.",
"signup_url": "https://openrouter.ai/keys",
"hint": "Eén API key · DeepSeek V4 Flash via openrouter.ai. Vul key in Instellingen → AI.",
},
"mistral": {
"label": "Mistral AI",
@@ -74,6 +78,52 @@ LLM_PRESETS: dict[str, dict[str, Any]] = {
}
OPENROUTER_PLACEHOLDER_LABEL = "OpenRouter · DeepSeek V4 Flash"
def ensure_openrouter_placeholder() -> None:
"""Seed inactive OpenRouter row so user can fill API key later in Settings."""
row = fetch_one(
"SELECT id FROM llm_providers WHERE provider_type = 'openrouter' LIMIT 1"
)
if row:
return
execute(
"""INSERT INTO llm_providers (
label, provider_type, api_base_url, api_key, model,
is_active, is_default, extra_config, updated_at
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s::jsonb, NOW())""",
(
OPENROUTER_PLACEHOLDER_LABEL,
"openrouter",
"https://openrouter.ai/api/v1",
"",
"deepseek/deepseek-v4-flash",
False,
False,
json.dumps({"referer": "https://foodlinkk.local", "title": "Foodlinkk Command Center"}),
),
)
def _env_openrouter_provider() -> dict[str, Any] | None:
from app.config import settings as app_settings
key = (app_settings.OPENROUTER_API_KEY or "").strip()
if not key:
return None
return {
"id": -1,
"label": "OpenRouter (environment)",
"provider_type": "openrouter",
"api_base_url": (app_settings.OPENROUTER_BASE_URL or "https://openrouter.ai/api/v1").rstrip("/"),
"api_key": key,
"model": app_settings.OPENROUTER_MODEL or "deepseek/deepseek-v4-flash",
"extra_config": {"referer": "https://foodlinkk.local", "title": "Foodlinkk Command Center"},
}
def list_presets() -> list[dict[str, Any]]:
out = []
for key, meta in LLM_PRESETS.items():
@@ -127,6 +177,9 @@ def get_provider(provider_id: int | None = None) -> dict[str, Any] | None:
def resolve_provider(provider_id: int | None = None) -> dict[str, Any]:
row = get_provider(provider_id)
if not row:
env_or = _env_openrouter_provider()
if env_or:
return env_or
return {
"id": 0,
"label": "Ollama lokaal",
@@ -136,6 +189,19 @@ def resolve_provider(provider_id: int | None = None) -> dict[str, Any]:
"model": settings.OLLAMA_MODEL,
"extra_config": {},
}
env_or = _env_openrouter_provider()
ptype = (row.get("provider_type") or "").lower()
if env_or and ptype == "openrouter" and not (row.get("api_key") or "").strip():
merged = dict(row)
merged["api_key"] = env_or["api_key"]
if not (merged.get("model") or "").strip():
merged["model"] = env_or["model"]
return merged
if env_or and ptype == "ollama" and row.get("is_default") and not (row.get("api_key") or "").strip():
# Optional: prefer env OpenRouter over default Ollama when key is set
from app.config import settings as app_settings
if (app_settings.HERMAN_LLM_BACKEND or "").lower() in ("router", "openrouter"):
return env_or
return row