bot WhatsApp “multi-RAG”.
Architecture cible (maximiser Odoo)
Super idée 👍 Passons à un empilement 100% Odoo-centric (autant que possible) pour votre bot WhatsApp “multi-RAG”.
Architecture cible (maximiser Odoo)
- Backend applicatif : Odoo (modules custom en Python + ORM).
-
Réception WhatsApp :
- soit via un contrôleur Odoo (@http.route) exposant un webhook compatible Meta/agrégateur,
- soit via l’app WhatsApp native d’Odoo (si disponible/éligible) ; sinon 360dialog/Twilio.
- DB principale : PostgreSQL d’Odoo (modèles Odoo).
-
Recherche sémantique :
- Option A (idéal) : activer pgvector dans la base Odoo et stocker les embeddings en VECTOR.
- Option B (fallback) : stocker les embeddings en JSONB + recherche cosine en Python (moins rapide).
- Option C (mixte) : Qdrant externe pour le vecteur, Odoo pour le référentiel et l’orchestration.
- Stockage documents : Odoo Documents / ir.attachment (fichiers sources) + S3 compatible (MinIO) en backend si vous avez besoin d’objets.
- Ingestion : cron Odoo (Scheduled Actions) → découpe, nettoyage, extraction figures/tables, embeddings, versioning.
- Observabilité : modèle Odoo “rag.log” + OpenTelemetry exporter (si vous avez un agent) + tableaux de bord Odoo Spreadsheet ; et/ou Prometheus/Grafana si vous avez une stack infra.
- Sécurité/RGPD : données étudiants minimisées dans Odoo, clés API chiffrées en ir.config_parameter.
Choix d’infrastructure (résumé pratique)
Contexte | Recommandation |
---|---|
Odoo Online (SaaS) | ⚠️ Pas d’extension DB custom → pgvector indisponible. Préférez Qdrant externe (Docker/Cloud) pour la partie vecteur. |
Odoo.sh | Vous contrôlez plus l’instance : possible d’activer pgvector selon plan/paramètres. Si indisponible, Qdrant externe. |
On-premise | ✅ Plein contrôle : activez pgvector et index HNSW/IVFFlat ; stockage S3 (MinIO) facultatif. |
Si vous visez “tout-en-Odoo” sans service externe, Odoo.sh ou On-prem sont les meilleurs choix.
Schéma fonctionnel dans Odoo
Modèles (minimalistes)
- agro.namespace : 1 par UE/sujet (≈ vos “10 RAG”).
- agro.document : métadonnées du document (lien vers ir.attachment).
- agro.chunk : un passage chunké (500–1 200 tokens) + embedding.
- agro.answer_log : traçabilité des réponses WhatsApp (qualité, citations, feedback).
Exemple (déclaration modèle)
# models/agro_namespace.py from odoo import models, fields class AgroNamespace(models.Model): _name = "agro.namespace" _description = "Espace de connaissance (UE/Sujet)" name = fields.Char(required=True, index=True) # ex: "UE5 Permaculture" code = fields.Char(required=True, index=True) # ex: "agro:UE5_permaculture" lang = fields.Selection([('fr','Français'),('mg','Malagasy'),('mix','Mix')], default='mix')
# models/agro_document.py class AgroDocument(models.Model): _name = "agro.document" _description = "Document source RAG" name = fields.Char(required=True) namespace_id = fields.Many2one("agro.namespace", required=True) attachment_id = fields.Many2one("ir.attachment", required=True) url = fields.Char() # si miroir S3/MinIO checksum = fields.Char(index=True) # pour éviter double ingestion page_count = fields.Integer() updated_at = fields.Datetime()
# models/agro_chunk.py class AgroChunk(models.Model): _name = "agro.chunk" _description = "Passage chunké + embedding" document_id = fields.Many2one("agro.document", required=True, index=True) namespace_id = fields.Many2one(related="document_id.namespace_id", store=True) seq = fields.Integer() # ordre dans le doc text = fields.Text(required=True) # Option A (pgvector) : champ SQL brut + colonne custom (voir plus bas) embedding = fields.Binary() # si JSONB fallback : stocker bytes JSON (ou texte) token_len = fields.Integer() page = fields.Char() # ex: "p.12" section = fields.Char() # ex: "2.1"
Avec pgvector, on crée la colonne via SQL (server action init_pgvector) et on by-passe fields.Binary() pour requêter en SQL natif (voir requêtes ci-dessous).
Activation pgvector (Option A)
SQL d’initialisation (on-prem/Odoo.sh)
CREATE EXTENSION IF NOT EXISTS vector; -- Colonne embedding sur 1 536 dims (ex. OpenAI text-embedding-3-large) ALTER TABLE agro_chunk ADD COLUMN IF NOT EXISTS embedding_vec vector(1536); -- Index (ex: HNSW pour ANN) CREATE INDEX IF NOT EXISTS idx_chunk_embed ON agro_chunk USING hnsw (embedding_vec vector_l2_ops);
Upsert embedding (pseudo-Python Odoo)
# Après avoir généré l'embedding (list[float] de taille 1536) self.env.cr.execute( "UPDATE agro_chunk SET embedding_vec = %s WHERE id = %s", (f"[{','.join(str(x) for x in embedding)}]", chunk_id) )
Recherche top-K (SQL)
def search_topk_pgvector(self, namespace_code, query_embedding, k=6): qvec = f"[{','.join(str(x) for x in query_embedding)}]" sql = """ SELECT c.id, c.text, c.page, c.section, d.name as title FROM agro_chunk c JOIN agro_document d ON d.id = c.document_id JOIN agro_namespace n ON n.id = c.namespace_id WHERE n.code = %s ORDER BY c.embedding_vec <-> %s LIMIT %s """ self.env.cr.execute(sql, (namespace_code, qvec, k)) rows = self.env.cr.fetchall() # formater dicts [{text,title,page,section},...] ...
Remplacez <-> (L2) par <#> (cosine) selon l’opérateur indexé.
Fallback sans pgvector (Option B)
- Stocker l’embedding en JSON (fields.Text) ou Binary (bytes).
- Charger en Python, calculer cosine/L2 sur un np.array et trier en mémoire (OK pour quelques dizaines/centaines de milliers de chunks si vous batcher et mettez un cache).
import numpy as np def cosine(a, b): a = np.array(a); b = np.array(b) return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-9))
Ajoutez un cache LRU sur namespace_code + hash(question) pour limiter les coûts.
Pipeline d’ingestion (Scheduled Action Odoo)
- Détection des nouveaux fichiers (dans Documents / ir.attachment, balisés par agro.namespace).
- Extraction texte + tableaux/figures (pdfminer/pymupdf + heuristiques), nettoyage.
- Chunking (longueur 500–1 200 tokens, overlap 50–150) ; stocker seq, page, section.
- Embeddings (OpenAI) → stocker + upsert embedding_vec (pgvector) ou JSON fallback.
- Versioning : si checksum change, invalidation des anciens chunks obsolètes.
Orchestration WhatsApp dans Odoo
Contrôleur webhook
from odoo import http class WhatsAppWebhook(http.Controller): @http.route(['/whatsapp/webhook'], type='json', auth='public', methods=['POST'], csrf=False) def inbound(self, **payload): # 1) extraire message, user_id, langue détectée # 2) router vers namespace (LLM "router" ou règles simples) # 3) retrieval top-K # 4) appel OpenAI chat avec SYSTEM PROMPT FR/MG fourni # 5) formater réponse + citations # 6) envoyer via connecteur WhatsApp (natif Odoo si dispo, sinon API agrégateur) # 7) logger dans agro.answer_log return {"status": "ok"}
Modèle de log qualité
class AgroAnswerLog(models.Model): _name = "agro.answer_log" _description = "Traçabilité réponses RAG" question = fields.Text() answer = fields.Text() user_hash = fields.Char() # hash phone namespace_code = fields.Char() topk_ids = fields.Many2many("agro.chunk", string="Chunks utilisés") eval_score = fields.Float() # overall_score eval_decision = fields.Selection([('accept','Accept'),('revise','Revise'),('reject','Reject')]) lang = fields.Selection([('fr','FR'),('mg','MG')]) thumbs = fields.Selection([('up','👍'),('down','👎')])
Monitoring & back-office
- Tableau “Questions fréquentes” (pivot Odoo) par namespace_code.
- Qualité : intégrer votre évaluateur automatique (déjà fourni) comme cron post-réponse → remplir eval_score/decision.
- Annotation enseignants : petite vue kanban/liste sur agro.answer_log avec boutons Corriger, Valider, Marquer “bon passage” (écrit sur agro.chunk un champ gold=True).
- A/B prompts : modèle agro.prompt pour tester prompts système, températures.
Intégration S3/MinIO (optionnel)
- Utiliser ir.attachment en mode “url” pointant vers l’objet S3.
- Garder un miroir local si vous avez besoin d’OCR/parsings lourds.
Gouvernance & RGPD
- Clés OpenAI en ir.config_parameter (cryptées).
- Stocker un hash de numéro WhatsApp, jamais le numéro brut.
- Purge programmée des logs > X mois.
- Politique d’information étudiants (page Odoo Website).
Plan de mise en œuvre (Odoo)
- Module agro_rag_core : modèles, menus, sécurité, vues.
- Module agro_rag_ingest : actions planifiées, extracteurs PDF/Docx, embeddings.
- Module agro_rag_bot : contrôleur webhook, routage, connecteur WhatsApp, prompts FR/MG, évaluateur + réécriture.
- Option agro_rag_pgvector : scripts d’init extension + requêtes SQL dédiées.
- Back-office qualité : vues, filtres, KPIs (pivot/graph).
Points d’attention
- Odoo Online : privilégiez Qdrant pour le vecteur.
- Latence : mettez un cache (question→top-K→réponse) et limitez la réponse WhatsApp à ~900–1 100 caractères (envoyer la version longue en doc).
- Multilingue : stocker lang au niveau agro.namespace et pré-filtrer les chunks par langue.
- Coûts : mutualisez embeddings FR/MG (les embeddings multilingues couvrent les deux).
Si vous voulez, je vous prépare l’ossature d’un module Odoo (__manifest__.py, models/, controllers/, data/cron.xml) avec les classes ci-dessus et un exemple de requête top-K pour pgvector et pour Qdrant — dites-moi simplement si vous êtes Odoo Online, Odoo.sh ou On-premise et je l’adapte.