UE-2
bot WhatsAPP Agro UE-2 WhatsApp “agro” sans Odoo,
Vers un bot WhatsApp “agro” sans Odoo, qui fait uniquement de la recherche dans vos textes + réponse via API OpenAI.
⚙️ Architecture MVP (sans Odoo)
WhatsApp (Cloud API Meta) ↓ webhook (POST) Mini-serveur (Node.js + Express) ↓ RAG ~ simple fichier (vectors.json) ou SQLite ↓ OpenAI API (embeddings + réponses FR/MG) ↓ Envoi de la réponse → WhatsApp
📦 Stack & prérequis
- Node.js 18+
- Meta WhatsApp Cloud API (numéro, token, verify token)
- OpenAI API key
- Données en .md/.txt/.pdf (converties en texte)
🧱 Données & ingestion (ultra-léger)
- Découpage : 500–1000 tokens avec 100 tokens d’overlap.
- Métadonnées mini : title, topic, lang, source, chunk_id.
- Stockage : un JSON de vecteurs (MVP) ou SQLite.
🔐 Variables d’environnement (.env)
OPENAI_API_KEY=sk-... WHATSAPP_TOKEN=EAAG... WHATSAPP_PHONE_ID=YOUR_PHONE_NUMBER_ID WHATSAPP_VERIFY_TOKEN=some-secret-string
🧪 Script d’ingestion (Node.js, minimal)
Crée ingest.js (placez vos fichiers texte dans ./docs).
// ingest.js import fs from "fs"; import path from "path"; import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); function chunk(text, size=1800, overlap=200) { const words = text.split(/\s+/); const chunks = []; for (let i=0; i<words.length; i += (size - overlap)) { chunks.push(words.slice(i, i+size).join(" ")); if (i + size >= words.length) break; } return chunks; } async function embedAll() { const dir = "./docs"; const files = fs.readdirSync(dir).filter(f => /\.(txt|md)$/i.test(f)); const out = []; for (const f of files) { const body = fs.readFileSync(path.join(dir, f), "utf8"); const parts = chunk(body); for (let i=0; i<parts.length; i++) { const input = parts[i]; const emb = await openai.embeddings.create({ model: "text-embedding-3-large", input }); out.push({ id: `${f}#${i}`, title: f, text: input, embed: emb.data[0].embedding }); } } fs.writeFileSync("./vectors.json", JSON.stringify(out)); console.log(`OK: ${out.length} chunks`); } embedAll().catch(console.error);
Si vous avez des PDF, convertissez-les d’abord en texte (ex. pdftotext) puis mettez-les dans ./docs.
🔎 Similarité (cosine) utilitaire
Créez similarity.js :
// similarity.js export function cosine(a, b) { let dot=0, na=0, nb=0; for (let i=0; i<a.length; i++) { dot += a[i]*b[i]; na += a[i]*a[i]; nb += b[i]*b[i]; } return dot / (Math.sqrt(na) * Math.sqrt(nb) + 1e-9); }
🧠 Serveur WhatsApp + RAG + OpenAI
Créez server.js :
// server.js import "dotenv/config"; import express from "express"; import bodyParser from "body-parser"; import fetch from "node-fetch"; import OpenAI from "openai"; import { cosine } from "./similarity.js"; import fs from "fs"; const app = express(); app.use(bodyParser.json()); const WAPP_TOKEN = process.env.WHATSAPP_TOKEN; const PHONE_ID = process.env.WHATSAPP_PHONE_ID; const VERIFY = process.env.WHATSAPP_VERIFY_TOKEN; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const VECTORS = JSON.parse(fs.readFileSync("./vectors.json", "utf8")); // ——— Webhook vérif Meta app.get("/webhook", (req, res) => { const mode = req.query["hub.mode"]; const token = req.query["hub.verify_token"]; const challenge = req.query["hub.challenge"]; if (mode === "subscribe" && token === VERIFY) return res.status(200).send(challenge); return res.sendStatus(403); }); // ——— Réception messages app.post("/webhook", async (req, res) => { try { const entry = req.body.entry?.[0]?.changes?.[0]?.value; const msg = entry?.messages?.[0]; if (!msg || msg.type !== "text") return res.sendStatus(200); const from = msg.from; const text = msg.text.body.trim(); // 1) Embedding de la question const qEmb = await openai.embeddings.create({ model: "text-embedding-3-large", input: text }); const qvec = qEmb.data[0].embedding; // 2) Top-K (K=8) par cosine const scored = VECTORS .map(r => ({ ...r, score: cosine(qvec, r.embed) })) .sort((a,b) => b.score - a.score) .slice(0, 8); const context = scored.map(s => `# ${s.title}\n${s.text}`).join("\n\n"); // 3) Prompt FR/MG (bilingue) const system = `Vous êtes un tuteur agroécologie (UE2) pour cultures tropicales. Répondez avec étapes numérotées, doses (kg/ha, L/ha), densités, calendrier. Si incertain, proposez une vérification terrain. Répondez en français clair. Ajoutez une synthèse en malgache (MG) en 3–4 phrases à la fin.`; const user = `Question utilisateur : "${text}" Connaissances pertinentes : ${context} Exigences de sortie : 1) Résumé opérationnel (3–5 lignes) 2) Étapes numérotées + doses/densités 3) Check-list outils/EPI 4) Variante saison humide/sèche si utile 5) Sources (titres chunk: id) → ${scored.map(s=>s.id).join(", ")}`; // 4) Réponse OpenAI const chat = await openai.chat.completions.create({ model: "gpt-5-turbo", temperature: 0.2, messages: [ { role: "system", content: system }, { role: "user", content: user } ] }); const answer = chat.choices[0].message.content?.slice(0, 4090) ?? "…"; // 5) Envoi WhatsApp await fetch(`https://graph.facebook.com/v20.0/${PHONE_ID}/messages`, { method: "POST", headers: { "Authorization": `Bearer ${WAPP_TOKEN}`, "Content-Type": "application/json" }, body: JSON.stringify({ messaging_product: "whatsapp", to: from, text: { body: answer } }) }); res.sendStatus(200); } catch (e) { console.error(e); res.sendStatus(200); } }); app.listen(3000, () => console.log("Bot WhatsApp agro prêt sur :3000"));
🧰 Démarrage
npm init -y npm i express body-parser node-fetch openai dotenv node ingest.js # génère vectors.json node server.js # expose /webhook
- Configurez l’URL publique (ngrok, tunnel) dans WhatsApp Cloud API (Mode Sandbox Meta).
- Ajoutez /webhook comme Callback URL + VERIFY TOKEN.
🗣️ Conseils d’usage (RAG utile dès J0)
-
Commencez avec 10–20 fiches très concrètes :
“Compost chaud tropical”, “Rotation tomate-niébé”, “Paillage saison humide”, “Micro-irrigation tomate plein champ”, “Densités semis arachide/maïs”, etc. - Ajoutez 5–10 check-lists (EPI, semis, repiquage, récolte).
- Réduisez la longueur des chunks si les réponses s’égarent (>1200 tokens).
🧭 Qualité & garde-fous
- Température 0.2 (réponses stables et opératoires).
- Toujours imposer les doses & étapes via le prompt.
- Ajouter un préfixe “⚠️ Si doute, valider au champ (loupe, photo rapprochée recto/verso).”
💶 Coûts (ordre de grandeur)
- Embeddings : ~0,13–0,40 € / 1 000 chunks (selon modèle & taille).
- Chat : dépend du volume ; pour des réponses courtes et un contexte restreint, quelques centimes par échange.
- WhatsApp Cloud API : facturation par message côté Meta (tarifs Business Initiated vs User Initiated, selon pays).
📈 Roadmap rapide
- J1 : 20 fiches → ./docs → node ingest.js
- J1 : Lier WhatsApp Cloud API → server.js
- J2 : Ajuster prompt (format FR/MG), ajouter 10 images (texte descriptif pour l’instant)
- J3 : Passer les vectors en SQLite si >5 000 chunks
Pack de 20 fiches thématiques
Voici un pack de 20 fiches thématiques (FR + résumé MG) adaptées à l’UE2 – Techniques culturales & agroécologie, prêtes à être mises dans ./docs/ pour ingestion RAG.
Chaque fiche est courte, structurée, opérationnelle et peut être stockée en .md (Markdown).