feat(stt): recherche web (SearXNG in-cluster) + fix cluster_status (#49)

Phase 2 d'Asa agentique : nouvel outil web_search adossé à un SearXNG
self-host in-cluster, + correction du faux positif de cluster_status.

- SearXNG (k8s/apps/searxng/) : Deployment + Service + ConfigMap settings.yml
  + IngressRoute searxng.lab.local, Application ArgoCD. Namespace ai, interne
  (l'outil tape http://searxng:8080). use_default_settings + search.formats
  inclut json (sinon API JSON 403) ; limiter/image_proxy off ; image pinnée ;
  conf copiée dans un emptyDir via initContainer (contourne le mount RO) ;
  PodSecurity restricted.
- Outil web_search (tools._web_search) ajouté au contexte asa + STT_SEARXNG_URL.
- fix(cluster_status) : les pods de CronJob TERMINÉS (Succeeded/Failed, ex.
  sacrifice-assign-renfort) comptaient comme « non prêts » → fausse alarme.
  Join kube_pod_status_phase{phase=~"Running |Pending"} (3 faux positifs → 0,
  validé in-cluster).
- Serveur 0.8.0 ; doc stt.md + journal mis à jour.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ALI YESILKAYA 2026-06-22 22:10:59 +02:00 committed by GitHub
parent c9e89c91b9
commit 73b52cde2c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 270 additions and 16 deletions

View file

@ -114,6 +114,38 @@ async def _prometheus_query(args: dict, deps: ToolDeps) -> str:
return _fmt_series(rows)
async def _web_search(args: dict, deps: ToolDeps) -> str:
query = (args.get("query") or "").strip()
if not query:
return "Aucune requête de recherche fournie."
url = settings.searxng_url.rstrip("/") + "/search"
try:
r = await deps.client.get(
url,
params={"q": query, "format": "json", "safesearch": 0, "language": "fr"},
timeout=settings.web_search_timeout,
)
r.raise_for_status()
data = r.json()
except httpx.HTTPError:
return "Recherche web indisponible : SearXNG injoignable."
except ValueError:
return "Recherche web indisponible : réponse SearXNG inattendue (API JSON activée ?)."
results = data.get("results") or []
if not results:
answers = data.get("answers") or []
if answers:
return " ".join(str(a) for a in answers[:2])
return "Aucun résultat web pour cette recherche."
lines = []
for res in results[: settings.web_search_results]:
title = (res.get("title") or "").strip()
content = (res.get("content") or "").strip()
src = res.get("url") or ""
lines.append(f"- {title} : {content} ({src})" if content else f"- {title} ({src})")
return "\n".join(lines)
# --- Registre : nom → (schéma OpenAI, exécuteur) --------------------------------
_TOOLS: dict[str, tuple[dict, object]] = {
@ -191,6 +223,26 @@ _TOOLS: dict[str, tuple[dict, object]] = {
},
_prometheus_query,
),
"web_search": (
{
"type": "function",
"function": {
"name": "web_search",
"description": "Recherche sur INTERNET (méta-moteur SearXNG) : actualité, faits "
"généraux, documentation externe, sujets hors du homelab Funk. À utiliser quand "
"l'information n'est ni dans le homelab ni dans ta connaissance, ou pour vérifier "
"un fait récent.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Termes de recherche"}
},
"required": ["query"],
},
},
},
_web_search,
),
}