From a1dd9d7c4478e9d6bc24731dbd9d414625fcd006 Mon Sep 17 00:00:00 2001 From: ALI YESILKAYA Date: Wed, 17 Jun 2026 22:20:26 +0200 Subject: [PATCH] =?UTF-8?q?fix(stt):=20d=C3=A9sactiver=20le=20thinking=20d?= =?UTF-8?q?e=20Qwen3=20(/no=5Fthink)=20=E2=80=94=20corrige=20content=20vid?= =?UTF-8?q?e=20/=20502=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(stt): mémoire long-terme sémantique via Qdrant (5b) Serveur : longterm.py — collection Qdrant stt-memory (embeddings Qwen3 gpu-01, dim auto, Cosine), recall top-k injecté au prompt, remember des tours user. Tout dégrade proprement si Qdrant/embeddings injoignables (la mémoire court-terme tient). Env STT_MEMORY_LONGTERM, STT_QDRANT_URL, STT_EMBED_URL, STT_MEMORY_TOPK. Testé en process : dégradation OK (Qdrant down → mem=0, pas de crash, court-terme tient). Qdrant réparé le 17/06 (5c). Recherche sémantique réelle à valider sur cluster. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT * feat(stt): endpoint /v1/memory/health + upsert Qdrant synchrone - /v1/memory/health sonde activement embeddings + Qdrant + collection et expose les erreurs (recall/remember dégradent en silence → indébogables). Permet de diagnostiquer la mémoire long-terme sans kubectl exec. - remember() : upsert avec ?wait=true → le souvenir est immédiatement cherchable (sans wait, Qdrant met l'écriture en file → un recall cross-session immédiat pouvait le rater). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT * docs(stt): 5b mémoire long-terme validée en prod + backlog nomic-embed-text - Rappel cross-session confirmé (« Felix » retrouvé dans une nouvelle session), points_count vérifié via /v1/memory/health. - Note du fix upsert ?wait=true et de l'endpoint de diagnostic. - Roadmap : 5d (nomic-embed-text dim 768) en backlog qualité ; états haut/bas du doc mis à jour (déployé + validé sur cible). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT * feat(stt): embeddings dédiés nomic-embed-text sur gpu-01 (:1238) + migration auto Remplace Qwen3 (chat réutilisé, dim 4096, peu discriminant) par un modèle d'embedding spécialisé nomic-embed-text (dim 768) pour la mémoire long-terme. Ansible (rôle llama_server) : - nouvelle instance optionnelle `llama-embed` (llama_embed_enabled) servant un modèle d'embedding dédié sur :1238, GPU ; télécharge le GGUF si absent. - activée sur gpu-01 (host_vars) : nomic-embed-text-v1.5 f16. STT-server : - STT_EMBED_URL → :1238, STT_EMBED_MODEL → nomic-embed-text (deployment + config). - _ensure_collection détecte le changement de dimension (4096→768) et recrée automatiquement la collection stt-memory (anciens vecteurs incomparables) — pas de drop manuel. Docs : llama_server README, rag.md, stt.md (5d ✅), CLAUDE.md. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT * fix(stt): désactiver le mode thinking de Qwen3 (/no_think) — content vide / 502 Qwen3 est un modèle « thinking » : il dépensait tout le budget max_tokens en reasoning_content → content vide, ou partait en raisonnement long → timeout (502 "upstream LiteLLM : " avec message vide). Diagnostic : appel direct LiteLLM en 0.87s mais content="" et tout dans reasoning_content. - brain.py : ajoute le token de contrôle `/no_think` au system prompt (configurable STT_DISABLE_THINKING, défaut true ; inoffensif pour non-Qwen). - brain.py : filet de sécurité — si content vide, récupère reasoning_content au lieu de renvoyer "". Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT --------- Co-authored-by: Claude --- stt/server/stt_server/brain.py | 14 +++++++++++++- stt/server/stt_server/config.py | 3 +++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/stt/server/stt_server/brain.py b/stt/server/stt_server/brain.py index 7cfd3b9..098dbf4 100644 --- a/stt/server/stt_server/brain.py +++ b/stt/server/stt_server/brain.py @@ -19,6 +19,12 @@ async def ask( 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 += ( @@ -39,4 +45,10 @@ async def ask( async with httpx.AsyncClient(timeout=settings.request_timeout) as client: r = await client.post(settings.litellm_url, json=payload, headers=headers) r.raise_for_status() - return r.json()["choices"][0]["message"]["content"].strip() + 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 diff --git a/stt/server/stt_server/config.py b/stt/server/stt_server/config.py index 13297f9..c1c7778 100644 --- a/stt/server/stt_server/config.py +++ b/stt/server/stt_server/config.py @@ -31,6 +31,9 @@ class Settings: max_tokens: int = int(os.getenv("STT_MAX_TOKENS", "200")) temperature: float = float(os.getenv("STT_TEMPERATURE", "0.7")) request_timeout: float = float(os.getenv("STT_REQUEST_TIMEOUT", "60")) + # Qwen3 est un modèle « thinking » → ajoute `/no_think` pour désactiver le raisonnement + # (sinon content vide / timeout). Inoffensif pour les modèles non-Qwen. + disable_thinking: bool = os.getenv("STT_DISABLE_THINKING", "true").lower() == "true" # Mémoire long-terme (Qdrant) — dégrade proprement si Qdrant/embeddings injoignables memory_longterm: bool = os.getenv("STT_MEMORY_LONGTERM", "true").lower() == "true"