feat(skills): 5 méthodes expertes auto-injectées (routeur lexical)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 4.8 committed 2026-07-18 17:02:40 +02:00
1 parent afe0a837d4
commit 31dce6036b
9 files changed
+159 -1

No files matched your search

+3
View File
@@ -64,6 +64,7 @@ PROFILE_FIELDS = {
"self_review",
"rag_enabled",
"embed_model",
"skills_enabled",
"keep_alive",
*GENERATION_FIELDS,
}
@@ -107,6 +108,8 @@ DEFAULT_CONFIG: dict = {
# Mémoire long-terme (RAG) entre sessions, via un modèle d'embedding.
"rag_enabled": True,
"embed_model": "auto",
# Skills : méthodes expertes injectées automatiquement selon la tâche.
"skills_enabled": True,
# Durée de maintien du modèle en VRAM (préchargement). "0" = décharge
# aussitôt, "-1" = jamais, "30m" = 30 minutes.
"keep_alive": "30m",
+14 -1
View File
@@ -18,7 +18,8 @@ from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from .. import agent_config, coder, db, enhance, memory, rag, router as msg_router
from .. import agent_config, coder, db, enhance, memory, rag, skills
from .. import router as msg_router
from ..tools import check_html, _safe_path
from ..agent import run_agent
from ..config import settings
@@ -237,6 +238,18 @@ async def chat(req: ChatRequest) -> StreamingResponse:
+ "\n---\n".join(memories),
})
# Skill : méthode experte injectée pour ce tour (jamais persistée).
if cfg.get("skills_enabled", True):
skill = skills.pick_skill(req.content)
if skill:
convo.insert(1, {
"role": "system",
"content": (
"Méthode à suivre pour cette tâche :\n" + skill["body"]
),
})
yield _sse("notice", {"message": f"📘 Méthode : {skill['title']}"})
if plan:
yield _sse("plan", {"steps": plan})
+61
View File
@@ -0,0 +1,61 @@
"""Skills : méthodes expertes injectées automatiquement selon la tâche.
Sélection lexicale instantanée (aucun appel LLM) : au plus UNE skill par
message, injectée en message système pour ce tour uniquement.
"""
from __future__ import annotations
import os
import re
_SKILLS_DIR = os.path.join(os.path.dirname(__file__), "..", "skills")
# Seuil : nombre minimal de mots-clés distincts trouvés dans le message.
_MIN_HITS = 2
def _load() -> dict[str, dict]:
out: dict[str, dict] = {}
if not os.path.isdir(_SKILLS_DIR):
return out
for fname in sorted(os.listdir(_SKILLS_DIR)):
if not fname.endswith(".md"):
continue
with open(os.path.join(_SKILLS_DIR, fname), encoding="utf-8") as f:
raw = f.read()
m = re.match(r"^---\n(.*?)\n---\n(.*)$", raw, re.S)
if not m:
continue
meta: dict[str, str] = {}
for line in m.group(1).splitlines():
if ":" in line:
key, _, value = line.partition(":")
meta[key.strip()] = value.strip()
keywords = [
k.strip().lower()
for k in meta.get("keywords", "").split(",")
if k.strip()
]
out[meta.get("name", fname[:-3])] = {
"name": meta.get("name", fname[:-3]),
"title": meta.get("title", fname[:-3]),
"keywords": keywords,
"body": m.group(2).strip(),
}
return out
ALL_SKILLS: dict[str, dict] = _load()
def pick_skill(message: str) -> dict | None:
"""Meilleure skill pour ce message, ou None si rien d'assez net."""
low = message.lower()
best, best_hits = None, 0
for skill in ALL_SKILLS.values():
hits = sum(1 for kw in skill["keywords"] if kw in low)
if hits > best_hits:
best, best_hits = skill, hits
if best is None or best_hits < _MIN_HITS:
return None
return {"name": best["name"], "title": best["title"], "body": best["body"]}
+11
View File
@@ -0,0 +1,11 @@
---
name: analyse-donnees
title: Analyse de données
keywords: csv, json, données, tableau, statistique, moyenne, analyse, colonnes, tri, filtre, graphique, export
---
Méthode d'analyse de données :
1. EXAMINER : lis un échantillon du fichier réel (read_file) AVANT tout traitement. Identifie séparateur, encodage, en-têtes, types de colonnes.
2. VALIDER : repère valeurs manquantes, doublons, incohérences de type. Signale-les explicitement au lieu de les masquer.
3. TRANSFORMER : script clair et borné (pas de dépendance exotique) ; garde les données d'origine intactes, écris le résultat dans un nouveau fichier.
4. PRÉSENTER : résumé chiffré (compte, min/max, moyennes pertinentes) + limites de l'analyse (données ignorées, hypothèses faites).
Interdit : supposer le format sans avoir lu le fichier ; modifier les données sources ; présenter des chiffres sans dire comment ils sont calculés.
+11
View File
@@ -0,0 +1,11 @@
---
name: creation-web
title: Création web
keywords: site, page, html, css, landing, formulaire, portfolio, interface, responsive, boutique, vitrine, menu, header, footer, animation
---
Méthode de création web, dans cet ordre :
1. STRUCTURE : écris d'abord le HTML sémantique COMPLET (header, main, sections, footer). Contenu réel, pas de lorem ipsum si le sujet est connu.
2. STYLE : CSS cohérent — palette limitée (3-4 couleurs), typographie lisible, espacements réguliers. Mobile-first, responsive (flexbox/grid, max-width sur images).
3. INTERACTIVITÉ : JavaScript minimal et sans dépendance externe. Chaque interaction doit fonctionner hors ligne.
4. VÉRIFIER : run_check sur chaque fichier produit ; contrôle les liens internes. Si les outils navigateur (mcp_playwright_*) sont disponibles, ouvre la page et lis la console pour vérifier qu'elle est propre.
Interdit : livrer sans vérification ; référencer des images ou CDN externes non demandés ; produire un fichier tronqué (utilise write_file en plusieurs morceaux).
+12
View File
@@ -0,0 +1,12 @@
---
name: debogage-systematique
title: Débogage systématique
keywords: bug, plante, erreur, exception, traceback, crash, échoue, marche pas, fonctionne pas, cassé, debug, débogue, corrige le bug, ne s'affiche pas, undefined, null, NaN
---
Méthode de débogage à suivre STRICTEMENT, étape par étape :
1. REPRODUIRE : identifie l'entrée exacte et le comportement observé vs attendu. Si le message d'erreur est fourni, cite-le et pars de là.
2. LOCALISER : lis le code concerné (read_file / grep_search) AVANT toute modification. Trouve la ligne qui produit le symptôme.
3. HYPOTHÈSE : formule UNE cause précise. Vérifie-la en lisant le code, pas en devinant.
4. CORRIGER : modification minimale et ciblée (edit_file). Ne réécris pas tout le fichier. Ne corrige qu'une cause à la fois.
5. VÉRIFIER : relis le code modifié (run_check) et explique pourquoi le symptôme disparaît. Signale tout autre problème repéré sans le corriger.
Interdit : proposer une correction sans avoir lu le code ; corriger plusieurs choses à la fois ; conclure « ça devrait marcher » sans vérification.
+10
View File
@@ -0,0 +1,10 @@
---
name: redaction-structuree
title: Rédaction structurée
keywords: rédige, écris un texte, article, documentation, readme, rapport, résumé, lettre, mail, présentation, plan
---
Méthode de rédaction :
1. PLAN : annonce la structure (titres/sections) avant de rédiger. Adapte la longueur à la demande — court par défaut.
2. RÉDACTION : paragraphes courts, une idée par paragraphe, français précis. Concret d'abord, généralités interdites. Markdown propre (titres, listes, gras parcimonieux).
3. RELECTURE : traque répétitions, phrases creuses, incohérences de ton. Coupe tout ce qui n'apporte rien.
Interdit : remplissage, formules toutes faites (« dans un monde où… »), conclusion qui répète l'introduction.
+11
View File
@@ -0,0 +1,11 @@
---
name: refactor-sur
title: Refactor sûr
keywords: refactor, refactorise, réorganise, nettoie, simplifie, renomme, découpe, extrait, duplication, dette
---
Méthode de refactoring, comportement STRICTEMENT identique avant/après :
1. COMPRENDRE : lis TOUT le code concerné et ses usages (grep_search sur chaque symbole touché) avant de modifier quoi que ce soit.
2. PETITS PAS : une seule transformation à la fois (renommage, extraction, déplacement). Jamais plusieurs changements mélangés.
3. VÉRIFIER après CHAQUE pas : run_check sur les fichiers modifiés ; re-grep pour confirmer qu'aucun usage n'est resté sur l'ancien nom.
4. RÉCAPITULER : liste ce qui a changé et pourquoi le comportement est inchangé.
Interdit : changer le comportement ou l'API publique sans le signaler ; renommer sans vérifier tous les usages ; réécrire un fichier entier quand edit_file suffit.
+26
View File
@@ -0,0 +1,26 @@
import os
import tempfile
os.environ.setdefault("DATA_DIR", tempfile.mkdtemp())
os.environ.setdefault("WORKSPACE_DIR", tempfile.mkdtemp())
from app import skills # noqa: E402
def test_five_skills_loaded():
assert len(skills.ALL_SKILLS) == 5
assert all(s["title"] and s["body"] for s in skills.ALL_SKILLS.values())
def test_bug_message_picks_debug():
s = skills.pick_skill("mon script plante avec une erreur TypeError au démarrage")
assert s and s["name"] == "debogage-systematique"
def test_web_message_picks_web():
s = skills.pick_skill("crée une page html responsive pour ma boutique")
assert s and s["name"] == "creation-web"
def test_banal_message_picks_nothing():
assert skills.pick_skill("bonjour, quelle heure est-il ?") is None