diff --git a/stt/server/README.md b/stt/server/README.md index 6dd82c2..a725626 100644 --- a/stt/server/README.md +++ b/stt/server/README.md @@ -9,6 +9,7 @@ STT et route l'inférence vers LiteLLM (s01) → Qwen3 (g01) / Claude. |---|---|---|---| | `GET` | `/healthz` | — | `{status, version}` | | `GET` | `/v1/models` | — | `{default, available[]}` | +| `GET` | `/v1/memory/health` | — | diagnostic mémoire long-terme (embed/Qdrant/collection, erreurs exposées) | | `POST` | `/v1/ask` | `{text, model?, session_id?}` | `{reply, model}` | | `POST` | `/v1/reset` | `{session_id}` | `{status}` | diff --git a/stt/server/stt_server/app.py b/stt/server/stt_server/app.py index 9cd936c..9865e72 100644 --- a/stt/server/stt_server/app.py +++ b/stt/server/stt_server/app.py @@ -43,6 +43,14 @@ async def v1_models() -> dict: return {"default": settings.model, "available": settings.allowed_models} +@app.get("/v1/memory/health") +async def v1_memory_health() -> dict: + """État de la mémoire long-terme (embeddings + Qdrant + collection), erreurs exposées.""" + if not longterm: + return {"enabled": False} + return await longterm.health() + + @app.post("/v1/reset") async def v1_reset(req: AskRequest) -> dict: if req.session_id: diff --git a/stt/server/stt_server/longterm.py b/stt/server/stt_server/longterm.py index fd5f580..c917005 100644 --- a/stt/server/stt_server/longterm.py +++ b/stt/server/stt_server/longterm.py @@ -66,13 +66,50 @@ class LongTermMemory: except Exception: # noqa: BLE001 — dégrade silencieusement return [] + async def health(self) -> dict: + """Diagnostic actif : sonde embeddings + Qdrant + collection, sans rien avaler. + + Contrairement à recall/remember (qui dégradent en silence), expose les erreurs + pour pouvoir déboguer la mémoire long-terme sans `kubectl exec`. + """ + out: dict = { + "enabled": True, + "qdrant_url": self.qdrant, + "embed_url": self.embed_url, + "collection": self.collection, + "embed": {"ok": False}, + "qdrant": {"ok": False}, + } + async with httpx.AsyncClient(timeout=20) as client: + # 1) embeddings + try: + vec = await self._embed(client, "ping mémoire") + out["embed"] = {"ok": True, "dim": len(vec)} + except Exception as e: # noqa: BLE001 — on veut l'erreur + out["embed"] = {"ok": False, "error": f"{type(e).__name__}: {e}"} + # 2) Qdrant + collection + try: + r = await client.get(f"{self.qdrant}/collections") + r.raise_for_status() + names = [c["name"] for c in r.json().get("result", {}).get("collections", [])] + exists = self.collection in names + qdrant: dict = {"ok": True, "collections": names, "collection_exists": exists} + if exists: + cr = await client.get(f"{self.qdrant}/collections/{self.collection}") + if cr.status_code == 200: + qdrant["points_count"] = cr.json().get("result", {}).get("points_count") + out["qdrant"] = qdrant + except Exception as e: # noqa: BLE001 + out["qdrant"] = {"ok": False, "error": f"{type(e).__name__}: {e}"} + return out + async def remember(self, session_id: str, text: str) -> None: try: async with httpx.AsyncClient(timeout=20) as client: vec = await self._embed(client, text) await self._ensure_collection(client, len(vec)) await client.put( - f"{self.qdrant}/collections/{self.collection}/points", + f"{self.qdrant}/collections/{self.collection}/points?wait=true", json={ "points": [ {