diff --git a/README.fr.md b/README.fr.md index eba3959..f9f6544 100644 --- a/README.fr.md +++ b/README.fr.md @@ -8,19 +8,21 @@ > publiée. Un plugin **Discourse** + un pipeline RAG auto-hébergé, conçu pour un forum -associatif francophone (Depiedencap — souliers pour homme, ~428k messages). -Principe produit : **le bot oriente vers les fils existants, il ne répond -pas à la place des membres.** +associatif francophone (Depiedencap — souliers pour homme, 424 835 messages +/ 22 230 sujets). Principe produit : **le bot oriente vers les fils +existants, il ne répond pas à la place des membres.** ## Ce que fait le plugin - Retrieval au **grain post** (et non au grain topic) : embeddings `bge-m3` - dans `ai_posts_embeddings` (pgvector dans la Postgres du forum), top-25 - par cosine puis **rerank cross-encoder** (TEI), déduplication par fil. + dans `ai_posts_embeddings` (pgvector dans la Postgres du forum, **sur le + VPS**), top-25 par cosine puis **rerank cross-encoder** (TEI **sur le mini + PC**), déduplication par fil. - **Réécriture de requête multi-tour** : le dernier message + les tours - précédents sont condensés en une requête autonome par un petit modèle - dédié avant retrieval (pattern *condense question* / history-aware - retriever). + précédents sont condensés en une requête autonome (pattern *condense + question* / history-aware retriever). Même modèle résident que la + génération : `qwen3:30b-a3b-q6k` (MoE, ~3B actifs, `think:false`) — plus + de petit modèle dédié à la réécriture. - **Citations vérifiées côté serveur** : le LLM n'écrit que des marqueurs `[[n]]` ; le plugin les transforme en liens `/t/slug/id/post_number` et jette tout lien qui n'existe pas réellement en base (whitelist Postgres, @@ -33,18 +35,41 @@ pas à la place des membres.** ## Architecture +Le plugin tourne **dans Discourse, sur le VPS**. Le mini PC n'est que le +backend d'inférence (Ollama + TEI), joignable via Tailscale. Le gate, +pgvector et le sanitizer de citations ne quittent jamais le VPS. + ``` -Discourse (plugin Ruby, dans le process du forum) - │ question → gate → réécriture (modèle léger) → embed → pgvector → rerank → LLM → sanitizer - ▼ -Mini PC auto-hébergé (k3s) VPS - Ollama : bge-m3 (embeddings) Discourse + Postgres/pgvector - qwen3 30B MoE (~3B actifs) (~428k posts embeddés) - — génération ET réécriture de - requête (un seul modèle résident) - TEI : cross-encoder mmarco-mMiniLMv2 (rerank) +Question membre + │ + ▼ +[VPS] Discourse + ce plugin Ruby + │ + ├─ 1. gate regex locale, 0 LLM + │ + ├─ 2. réécriture ──Tailscale──► [mini PC] Ollama qwen3:30b-a3b-q6k + │ + ├─ 3. embed ──Tailscale──► [mini PC] Ollama bge-m3 + │ + ├─ 4. pgvector Postgres locale, 424 835 posts, cosine top-25 + │ + ├─ 5. rerank ──Tailscale──► [mini PC] TEI mmarco-mMiniLMv2 + │ + ├─ 6. génération ──Tailscale──► [mini PC] Ollama qwen3:30b-a3b-q6k + │ + └─ 7. sanitizer whitelist URL, Postgres locale + │ + ▼ + réponse + liens /t/slug/id/post_number ``` +| Sur le VPS | Sur le mini PC (k3s, iGPU) | +|---|---| +| Discourse, ce plugin, Postgres/pgvector | Ollama (`bge-m3` + `qwen3:30b-a3b-q6k`) et TEI (`mmarco`) | + +`qwen3:30b-a3b-q6k` est un seul MoE résident (~3B actifs) pour la +réécriture (`think:false`) **et** la génération. + Le choix délibéré : **pas de framework RAG** (LangChain & co.) — pipeline linéaire en code direct, instrumenté pour l'évaluation. Le raisonnement complet : [`docs/rag/DESIGN.md`](docs/rag/DESIGN.md) §9. diff --git a/README.md b/README.md index e573a6b..25624ff 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,21 @@ > This repository documents the work — no demo URL is published. A **Discourse plugin** + a self-hosted RAG pipeline, built for a French -community forum (Depiedencap — men's shoes, ~428k posts). Product -principle: **the bot points members to existing threads instead of -answering in their place.** +community forum (Depiedencap — men's shoes, 424 835 posts / 22 230 +topics). Product principle: **the bot points members to existing threads +instead of answering in their place.** ## What it does - **Post-grain retrieval** (not topic-grain): `bge-m3` embeddings in - `ai_posts_embeddings` (pgvector inside the forum's Postgres), top-25 by - cosine then **cross-encoder rerank** (TEI), per-thread dedup. + `ai_posts_embeddings` (pgvector inside the forum's Postgres on the VPS), + top-25 by cosine then **cross-encoder rerank** (TEI on the mini PC), + per-thread dedup. - **Multi-turn query rewriting**: the latest message plus earlier turns - are condensed into a standalone query by a small dedicated model before - retrieval (*condense question* / history-aware retriever pattern). + are condensed into a standalone query (*condense question* / + history-aware retriever). Same resident model as generation: + `qwen3:30b-a3b-q6k` (MoE, ~3B active, `think:false`) — not a separate + small rewriter. - **Server-side verified citations**: the LLM only emits bare `[[n]]` markers; the plugin maps them to `/t/slug/id/post_number` links and discards any link that does not actually exist in Postgres (URL @@ -31,18 +34,41 @@ answering in their place.** ## Architecture +The plugin runs **inside Discourse on the VPS**. The mini PC is only the +inference backend (Ollama + TEI), reached over Tailscale. Gate, pgvector +and the citation sanitizer never leave the VPS. + ``` -Discourse (Ruby plugin, inside the forum process) - │ question → gate → rewrite (small model) → embed → pgvector → rerank → LLM → sanitizer - ▼ -Self-hosted mini PC (k3s) VPS - Ollama : bge-m3 (embeddings) Discourse + Postgres/pgvector - qwen3 30B MoE (~3B active) (~428k embedded posts) - — generation AND query rewriting - (single resident model) - TEI : mmarco-mMiniLMv2 cross-encoder (rerank) +Member question + │ + ▼ +[VPS] Discourse + this Ruby plugin + │ + ├─ 1. gate regex, local, 0 LLM + │ + ├─ 2. rewrite ──Tailscale──► [mini PC] Ollama qwen3:30b-a3b-q6k + │ + ├─ 3. embed ──Tailscale──► [mini PC] Ollama bge-m3 + │ + ├─ 4. pgvector Postgres local, 424 835 posts, cosine top-25 + │ + ├─ 5. rerank ──Tailscale──► [mini PC] TEI mmarco-mMiniLMv2 + │ + ├─ 6. generate ──Tailscale──► [mini PC] Ollama qwen3:30b-a3b-q6k + │ + └─ 7. sanitizer URL whitelist, local Postgres + │ + ▼ + answer + /t/slug/id/post_number links ``` +| Lives on the VPS | Lives on the mini PC (k3s, iGPU) | +|---|---| +| Discourse, this plugin, Postgres/pgvector | Ollama (`bge-m3` + `qwen3:30b-a3b-q6k`) and TEI (`mmarco`) | + +`qwen3:30b-a3b-q6k` is a single resident MoE (~3B active) used for both +rewrite (`think:false`) and generation. + Deliberate choice: **no RAG framework** (LangChain & co.) — a linear pipeline in direct code, instrumented for evaluation. Full reasoning: [`docs/rag/DESIGN.md`](docs/rag/DESIGN.md) §9. diff --git a/docs/rag/DESIGN.md b/docs/rag/DESIGN.md index ade2e7b..e9e47c9 100644 --- a/docs/rag/DESIGN.md +++ b/docs/rag/DESIGN.md @@ -1,11 +1,12 @@ # Design — RAG grain post (Depiedencap) -> **Dernière màj** : 14/09/2026 · **Statut** : toutes les configs A-G -> mesurées, config gagnante identifiée (E : posts natifs + rerank), -> sécurité testée à l'échelle. **Rien déployé en prod.** +> **Dernière màj** : 17/09/2026 · **Statut** : config gagnante (E : posts +> natifs + rerank) mesurée ; code en prod sur le VPS, flags membre encore +> **OFF**, agent limité à un groupe démo. Le §0 ci-dessous est un +> **instantané historique** (grain topic) — l'architecture actuelle : +> [`README.md`](../../README.md) · [`PIPELINE.md`](PIPELINE.md). > Résultats : [`EVAL.md`](EVAL.md) · Sécurité : [`SAFETY.md`](SAFETY.md) · -> Architecture : [`PIPELINE.md`](PIPELINE.md) -> Références techniques : [`REFERENCES.md`](REFERENCES.md) +> Références : [`REFERENCES.md`](REFERENCES.md) ## 0. État actuel (rappel) diff --git a/docs/rag/README.md b/docs/rag/README.md index b649740..3f7b2b2 100644 --- a/docs/rag/README.md +++ b/docs/rag/README.md @@ -1,11 +1,12 @@ # Chantier RAG — granularité post, retrieval avancé, évaluation -> **Statut (15/09/2026)** : **déploiement prod en cours**, flags encore **OFF** -> (comportement live inchangé). Code v2 déployé, reranker en service, backfill -> natif posts en cours sur le VPS. Reprise / prochaine étape exacte : -> [`PLAN_RAG_PROD_VPS.md`](../PLAN_RAG_PROD_VPS.md) § « Reprise session » (tout en haut). -> Éval locale : config gagnante mesurée (posts+rerank, Hit@5 0.97), sécurité -> testée (red-team + gate + clause) — `EVAL.md` / `SAFETY.md`. +> **Statut (17/09/2026)** : plugin + pipeline en prod sur le **VPS** +> (Discourse + Postgres/pgvector, 424 835 posts). Inférence sur le **mini +> PC** (Ollama `bge-m3` + `qwen3:30b-a3b-q6k`, TEI mmarco) via Tailscale. +> Flags membre encore **OFF** ; agent limité à un groupe démo. Schéma +> public : [`README.md`](../../README.md). Reprise : +> [`PLAN_RAG_PROD_VPS.md`](../PLAN_RAG_PROD_VPS.md) § « Reprise session ». +> Éval : posts+rerank, Hit@5 0.97 — `EVAL.md` / `SAFETY.md`. ## Pourquoi ce chantier diff --git a/lib/retrieval.rb b/lib/retrieval.rb index 1325d75..087fdcc 100644 --- a/lib/retrieval.rb +++ b/lib/retrieval.rb @@ -25,9 +25,9 @@ module DepiedencapAiCitations RERANK_READ_TIMEOUT = 4 POSTS_SQL_LIMIT = 25 - # Réécriture de requête multi-tour (condense_query) — modèle léger dédié, - # distinct du LLM de génération (Qwen 30B) : latence courte (~2-3s mesurées), - # pas de mode "thinking" à filtrer (contrairement à qwen3:4b sur ce serving). + # Réécriture de requête multi-tour (condense_query) — même LLM résident + # que la génération (qwen3:30b-a3b-q6k, MoE ~3B actifs, ~1 s à chaud). + # think:false pour éviter le budget englouti dans reasoning (vu sur qwen3:4b). HISTORY_TURNS = 3 CONDENSE_MAX_TOKENS = 80 CONDENSE_OPEN_TIMEOUT = 2