mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-09 00:44:42 +02:00
Cause de la latence 30-45s : l'endpoint d'embeddings (gpu-01:1238) peut se geler ; recall ET remember l'attendaient ~20s chacun (timeout → dégradation silencieuse), s'ajoutant à la réponse. Refactor : - store (ex-remember) en BackgroundTasks → APRÈS la réponse, hors latence perçue ; suppression de `?wait=true` (pas d'attente du flush Qdrant) - recall renvoie aussi le vecteur de la requête → store le réutilise (1 embed/tour au lieu de 2, le 2ᵉ portait sur le même texte) - timeout recall serré (4s, STT_MEMORY_RECALL_TIMEOUT) : un embed lent/mort dégrade vite (souvenirs vides) au lieu de bloquer ; store tolère 20s en arrière-plan - clients httpx persistants (pooling/keep-alive) côté brain + longterm, fermés via lifespan (plus de handshake TCP par appel) - log de timing par requête (recall/gen/total/mem) pour diagnostiquer - bump serveur 0.1.0 → 0.2.0 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
70 lines
2.6 KiB
Python
70 lines
2.6 KiB
Python
"""Orchestration AI : route les requêtes des clients vers LiteLLM (s01).
|
|
|
|
LiteLLM (:4000) est OpenAI-compatible et route lui-même vers Qwen3 (g01) ou Claude
|
|
selon l'alias `hermes-default` / `hermes-switch`. L'intégration des outils Hermes
|
|
(« agir sur Funk » via le gateway :8080) est une étape ultérieure.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import httpx
|
|
|
|
from stt_server.config import settings
|
|
|
|
# Client persistant (pooling + keep-alive) : évite un handshake TCP vers LiteLLM à chaque tour.
|
|
_client: httpx.AsyncClient | None = None
|
|
|
|
|
|
def _get_client() -> httpx.AsyncClient:
|
|
global _client
|
|
if _client is None or _client.is_closed:
|
|
_client = httpx.AsyncClient(timeout=settings.request_timeout)
|
|
return _client
|
|
|
|
|
|
async def aclose() -> None:
|
|
global _client
|
|
if _client is not None and not _client.is_closed:
|
|
await _client.aclose()
|
|
_client = None
|
|
|
|
|
|
async def ask(
|
|
text: str,
|
|
model: str | None = None,
|
|
history: list[dict] | None = None,
|
|
memories: list[str] | None = None,
|
|
) -> str:
|
|
system = settings.system_prompt
|
|
if settings.disable_thinking:
|
|
# Qwen3 est un modèle « thinking » : sans ça il dépense tout le budget max_tokens
|
|
# en raisonnement (`reasoning_content`) → `content` vide, ou part en réflexion longue
|
|
# → timeout (502). Le token de contrôle `/no_think` désactive le mode raisonnement.
|
|
# Inoffensif pour les modèles non-Qwen (simple texte ignoré).
|
|
system += "\n/no_think"
|
|
if memories:
|
|
souvenirs = "\n".join(f"- {m}" for m in memories)
|
|
system += (
|
|
"\n\nÉléments de mémoire long-terme (peuvent aider, ignore si hors-sujet) :\n"
|
|
+ souvenirs
|
|
)
|
|
messages = [{"role": "system", "content": system}]
|
|
if history:
|
|
messages += history
|
|
messages.append({"role": "user", "content": text})
|
|
payload = {
|
|
"model": model or settings.model,
|
|
"messages": messages,
|
|
"max_tokens": settings.max_tokens,
|
|
"temperature": settings.temperature,
|
|
}
|
|
headers = {"Authorization": f"Bearer {settings.litellm_key}"}
|
|
r = await _get_client().post(settings.litellm_url, json=payload, headers=headers)
|
|
r.raise_for_status()
|
|
msg = r.json()["choices"][0]["message"]
|
|
# Filet de sécurité : si un modèle « thinking » renvoie un content vide (tout parti
|
|
# en reasoning_content), on récupère le raisonnement plutôt que de renvoyer "".
|
|
content = (msg.get("content") or "").strip()
|
|
if not content:
|
|
content = (msg.get("reasoning_content") or "").strip()
|
|
return content
|