mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-08 12:24:43 +02:00
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:
parent
c9e89c91b9
commit
73b52cde2c
14 changed files with 270 additions and 16 deletions
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "stt-server"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
description = "STT-server — orchestrateur AI du homelab Funk (API pour les clients STT)"
|
||||
requires-python = ">=3.11"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
"""STT-server — orchestrateur AI in-cluster pour les clients STT."""
|
||||
|
||||
__version__ = "0.7.0"
|
||||
__version__ = "0.8.0"
|
||||
|
|
|
|||
|
|
@ -85,6 +85,10 @@ class Settings:
|
|||
tool_max_tokens: int = int(os.getenv("STT_TOOL_MAX_TOKENS", "512"))
|
||||
# Température basse pour le tool-use (sélection d'outil + arguments déterministes).
|
||||
tool_temperature: float = float(os.getenv("STT_TOOL_TEMPERATURE", "0.2"))
|
||||
# Recherche web (outil web_search) — SearXNG self-host in-cluster (namespace ai), API JSON.
|
||||
searxng_url: str = os.getenv("STT_SEARXNG_URL", "http://searxng:8080")
|
||||
web_search_results: int = int(os.getenv("STT_WEB_SEARCH_RESULTS", "5"))
|
||||
web_search_timeout: float = float(os.getenv("STT_WEB_SEARCH_TIMEOUT", "8"))
|
||||
|
||||
# Actions via Hermes (contexte « agent ») — OPT-IN, désactivé par défaut.
|
||||
# Le contexte « agent » n'est exposé que si actions_enabled ET un jeton est présent.
|
||||
|
|
|
|||
|
|
@ -42,12 +42,13 @@ CONTEXTS: dict[str, Context] = {
|
|||
id="asa", label="Asa · agent", icon="✨",
|
||||
description="Asa répond en s'appuyant sur des outils (doc + état live du homelab).",
|
||||
system_prompt=_BASE + " Tu disposes d'outils pour obtenir l'état RÉEL et LIVE du "
|
||||
"homelab (santé des machines, état du cluster, métriques Prometheus) et pour chercher "
|
||||
"dans la documentation Funk. Dès qu'une question porte sur l'état courant, un fait "
|
||||
"précis ou la config du homelab, APPELLE l'outil adéquat puis réponds à partir de son "
|
||||
"résultat — n'invente jamais. Si les outils ne donnent pas l'information, dis-le "
|
||||
"simplement. Pour le bavardage général, réponds directement sans outil.",
|
||||
tools=("search_docs", "host_health", "cluster_status", "prometheus_query"),
|
||||
"homelab (santé des machines, état du cluster, métriques Prometheus), chercher dans la "
|
||||
"documentation Funk, et faire une recherche sur INTERNET (web_search). Dès qu'une "
|
||||
"question porte sur l'état courant, un fait précis du homelab, ou une info externe / "
|
||||
"récente, APPELLE l'outil adéquat puis réponds à partir de son résultat — n'invente "
|
||||
"jamais. Si les outils ne donnent pas l'information, dis-le simplement. Pour le "
|
||||
"bavardage général, réponds directement sans outil.",
|
||||
tools=("search_docs", "host_health", "cluster_status", "prometheus_query", "web_search"),
|
||||
),
|
||||
"funk": Context(
|
||||
id="funk", label="Funk · cluster", icon="🛠️",
|
||||
|
|
|
|||
|
|
@ -137,8 +137,13 @@ async def alerts_block(client: httpx.AsyncClient) -> str:
|
|||
async def cluster_block(client: httpx.AsyncClient) -> str:
|
||||
try:
|
||||
nodes = await _prom_query(client, 'up{job=~"storage-01|gpu-01-node"}')
|
||||
# « Non prêt » = ready false ET pod ENCORE actif (Running/Pending). Sans le filtre de
|
||||
# phase, les pods de CronJob terminés (Succeeded/Failed — ex. sacrifice-assign-renfort)
|
||||
# comptent comme « non prêts » → fausse alarme. Le join exclut les pods terminés.
|
||||
not_ready = await _prom_query(
|
||||
client, 'kube_pod_status_ready{condition="false"} == 1'
|
||||
client,
|
||||
'kube_pod_status_ready{condition="false"} == 1'
|
||||
' and on(namespace,pod) kube_pod_status_phase{phase=~"Running|Pending"} == 1',
|
||||
)
|
||||
ready = await _prom_query(client, 'count(kube_pod_status_ready{condition="true"} == 1)')
|
||||
except httpx.HTTPError:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue