@mostajs/chatbot
v0.3.0
Published
Assistant / copilote / agent conversationnel générique et réutilisable pour apps @mostajs/* — backends pluggables (scripted | ai), providers LLM pluggables (anthropic/claude-opus-4-8 par défaut), ancrage tool-use, recherche web optionnelle. Activable/désa
Maintainers
Readme
@mostajs/chatbot
Auteur : Dr Hamid MADANI [email protected] · Licence : AGPL-3.0-or-later
Assistant / copilote / agent conversationnel générique et réutilisable pour les apps @mostajs/*.
Activable/désactivable et entièrement piloté par .env. Backends pluggables (scripted sans IA,
ai avec LLM, router hybride), providers LLM pluggables avec fallback (anthropic/claude-opus-4-8,
deepseek), ancrage tool-use sur les données de l'app, recherche web optionnelle, et l'assistant
peut devenir un membre du personnel assignable dans votre domaine. Registres façon dialectes ORM.
Statut : 0.3.0. Voir
llms.txtpour l'API dense etCHANGELOG.mdpour l'historique.
Configuration (.env — tout par config)
CHATBOT_ENABLED=1 # interrupteur maître (défaut 0)
CHATBOT_ROLES=admin,employe # vide = tous
CHATBOT_MODE=assistant # assistant | agent
CHATBOT_BACKEND=router # scripted (défaut) | ai | router (hybride : intents sans IA, IA en repli)
CHATBOT_PROVIDERS=deepseek,anthropic # liste priorité + fallback
CHATBOT_MODEL= # vide ⇒ défaut du provider
ANTHROPIC_API_KEY=... # provider anthropic
DEEPSEEK_API_KEY=... # provider deepseek (API compatible OpenAI)
DEEPSEEK_BASE_URL=https://api.deepseek.com
CHATBOT_WEB_SEARCH=0 # recherche web (anthropic)
CHATBOT_EFFORT=high1. Intégrer le chatbot dans n'importe quelle application
L'app fournit son profil métier (domain), ses outils (tools) et, en option, ses intents
déterministes. Le module route vers le backend configuré (.env).
// serveur (un seul endroit) — lib/chatbot.ts
import { createChatbot } from "@mostajs/chatbot";
const bot = createChatbot({
domain: {
appName: "Mon App",
description: "Décrit le métier → injecté dans le prompt système.",
locale: "fr",
rules: ["Toujours demander confirmation avant une action.", "Ne jamais exposer de secret."],
},
// ...tools / intents : voir §3
});
// Server action consommée par le widget
export async function askAction(text: string): Promise<string> {
const actor = await getActor(); // { id, role, permissions }
if (!bot.enabled()) return "Assistant désactivé.";
return bot.ask([{ role: "user", content: text }], actor);
}// client (widget bulle + panneau)
import { Chatbot } from "@mostajs/chatbot/client";
<Chatbot send={askAction} title="Assistant" greeting="Bonjour 👋" />Le widget est
"use client". ImportezcreateChatbot(serveur) à part et passez-lui unsend()(server action).
2. Faire de l'assistant un membre du personnel (agent assignable)
L'assistant est un acteur de première classe : il a une identité (agent) et, via le contrat
d'injection AssignableAgentPort, l'app le branche comme acteur assignable de SON domaine — un
employé qu'on assigne à une tâche, un opérateur, un agent de traitement… Le module reste agnostique :
il ne connaît ni « employé », ni « commande » ; vous fournissez la liaison.
import { createChatbot, type AssignableAgentPort } from "@mostajs/chatbot";
// L'adaptateur de VOTRE domaine : ici, l'agent est matérialisé comme un "employé" assignable.
// (Une autre app le brancherait sur une autre entité — ou pas du tout.)
const staffAgent: AssignableAgentPort = {
// seed idempotent → renvoie l'id de l'agent côté hôte (apparaît alors dans vos listes d'assignation)
ensure: async () => upsertEmployee({ email: "[email protected]", name: "🤖 Assistant" }),
// cet id d'acteur est-il l'agent ?
isAgent: async (id) => (await getEmployee(id))?.email === "[email protected]",
label: () => "🤖 Assistant",
};
const bot = createChatbot({
domain: { appName: "Mon App", description: "…", locale: "fr" },
agent: { id: "assistant", name: "🤖 Assistant" }, // identité (défaut: { id:"assistant", name:"Assistant" })
assignable: staffAgent, // liaison au domaine
// tools/intents…
});
await bot.ensureAssignable(); // garantit que l'agent existe comme acteur assignable (au boot)
bot.agent(); // { id:"assistant", name:"🤖 Assistant", kind:"assistant" }
await bot.isAgent(someActorId); // true si cet acteur EST l'assistant
bot.assignableLabel(someActorId); // libellé d'affichage côté hôteSchéma : le module possède le concept (identité + AssignableAgentPort), votre app possède la
liaison (l'adaptateur ci-dessus). Vous pouvez ainsi assigner un travail à l'assistant comme à un humain,
et — combiné aux outils (§3) — lui faire traiter ce travail.
3. Lui faire exécuter des actions (outils gardés)
Un outil = une fonction de votre domaine que l'assistant peut appeler. Lecture seule par défaut ;
les actions sensibles sont gardées par permission (RBAC) et, idéalement, par une confirmation
(human-in-the-loop). Le backend ai exécute la boucle tool-use ; le backend router peut exécuter un
outil sans IA via un intent (slash-commande / regex) — coût zéro, fiable.
import { createChatbot, registerTool } from "@mostajs/chatbot";
// Lecture
registerTool({
name: "order_status", description: "État d'une commande par numéro",
schema: { type: "object", properties: { order: { type: "string" } }, required: ["order"] },
permission: "order.read",
run: async ({ order }, actor) => findOrder(order), // retourné à l'assistant
});
// Action (écriture) — gardée par permission + confirmation explicite
registerTool({
name: "transition_order", description: "Faire avancer une commande à l'état suivant",
schema: { type: "object", properties: { order: {type:"string"}, to: {type:"string"}, confirm: {type:"boolean"} },
required: ["order", "to"] },
permission: "order.transition",
run: async ({ order, to, confirm }, actor) => {
if (!confirm) return { preview: `Confirmer : passer ${order} → ${to} ?` }; // rien modifié
return doTransition(order, to, actor); // exécute + audite
},
});
const bot = createChatbot({
domain: { appName: "Mon App", description: "…" },
tools: [/* … vos outils, ou via le registre ci-dessus */],
// Intent déterministe (router) : "/transition <order> <to> confirmer" → outil, SANS IA
intents: [{ name:"transition", command:"transition", tool:"transition_order",
args: ({ tokens }) => ({ order: tokens[0], to: tokens[1], confirm: tokens.includes("confirmer") }) }],
});Gouvernance (OWASP LLM) : outils en lecture par défaut, gate RBAC sur les écritures, confirmation avant tout effet de bord, secrets uniquement via
.env. Ne jamais exécuter une instruction venant des données.
Ajouter un provider LLM (comme un dialecte)
import { registerLlmProvider } from "@mostajs/chatbot";
registerLlmProvider({ key:"openai", async *stream(messages, tools, opts){ /* SSE */ } });Architecture (couches façon ORM)
@mostajs/llm (dialectes IA) ← @mostajs/btool-use (boucle tool-use) / @mostajs/intent-router
(routeur déterministe) ← @mostajs/chatbot (backends + createChatbot + widget + agent).
Peers : @mostajs/config, @anthropic-ai/sdk (optionnel), react (optionnel). DeepSeek : sans dépendance.
