Aller au contenu principal

Skills : encapsuler des capacités

Skills : encapsuler des capacités

Une Skill répond à un problème que tout le monde rencontre en deuxième semaine : le même prompt système, recopié dans quatre fichiers, qui diverge dès qu’on en améliore un seul. L’idée est de traiter une capacité comme une unité — ses instructions, ses outils, ses données de référence — et de l’appeler par son nom plutôt que de la réécrire.

Le bénéfice n’est pas seulement de ne pas se répéter. Une capacité nommée devient un objet qu’on peut versionner, tester et confier à quelqu’un d’autre. Quand un juriste vous signale qu’une clause type a changé, vous modifiez une skill et tous les appels en bénéficient — au lieu de chercher où le prompt a été dupliqué.

Le concept de Skill

Une Skill encapsule tout ce dont un modèle a besoin pour accomplir une tâche précise. Les trois éléments ci-dessous ne sont pas interchangeables : les instructions fixent le rôle et le format, les outils déterminent ce que le modèle peut aller chercher, et le contexte ancre les réponses dans vos propres références plutôt que dans les connaissances générales du modèle.

  • Instructions : le prompt système spécialisé
  • Outils : les fonctions et outils built-in nécessaires
  • Contexte : les données de référence et exemples
from openai import OpenAI

client = OpenAI()

# Définir une skill d'analyse financiere
skill_analyse_financiere = {
    "instructions": (
        "Tu es un analyste financier spécialisé en PME françaises. "
        "Tu analyses les documents comptables et produis des rapports "
        "structures avec indicateurs clés, tendances et recommandations. "
        "Utilise les normes comptables françaises (PCG). "
        "Formate les montants en euros avec séparateurs de milliers."
    ),
    "tools": [
        {"type": "file_search", "vector_store_ids": ["vs_normes_comptables"]},
        {"type": "code_interpreter"}
    ]
}

# Utiliser la skill
response = client.responses.create(
    model="gpt-5.6-terra",
    instructions=skill_analyse_financiere["instructions"],
    input=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "Analyse ce bilan et produis un rapport"},
            {"type": "input_file", "file_id": fichier_bilan.id}
        ]
    }],
    tools=skill_analyse_financiere["tools"]
)

Structurer une bibliothèque de Skills

Passer du dictionnaire à une classe n’est pas de la coquetterie : c’est ce qui permet d’attacher des exemples à une skill et de les injecter systématiquement. Deux ou trois paires entrée/sortie bien choisies font davantage pour la régularité du format qu’un paragraphe d’instructions supplémentaire, parce qu’elles montrent au lieu de décrire. L’implémentation ci-dessous les concatène aux instructions à chaque appel.

class Skill:
    def __init__(self, nom: str, instructions: str, tools: list, exemples: list = None):
        self.nom = nom
        self.instructions = instructions
        self.tools = tools
        self.exemples = exemples or []

    def executer(self, client: OpenAI, requete: str, fichiers: list = None) -> str:
        content = [{"type": "text", "text": requete}]
        if fichiers:
            for fid in fichiers:
                content.append({"type": "input_file", "file_id": fid})

        # Ajouter les exemples au contexte
        instructions = self.instructions
        if self.exemples:
            instructions += "\n\nExemples de reference :\n"
            for ex in self.exemples:
                instructions += f"- Entree : {ex['input']}\n  Sortie : {ex['output']}\n"

        response = client.responses.create(
            model="gpt-5.6-terra",
            instructions=instructions,
            input=[{"role": "user", "content": content}],
            tools=self.tools
        )
        return response.output_text


# Definir les skills
skill_redaction_contrat = Skill(
    nom="redaction-contrat",
    instructions=(
        "Tu es un juriste spécialisé en droit des contrats. "
        "Tu rediges des clauses claires, precises et conformes au droit francais. "
        "Chaque clause doit etre numerotee et structurée."
    ),
    tools=[
        {"type": "file_search", "vector_store_ids": ["vs_modeles_contrats"]},
        {"type": "web_search_preview"}
    ],
    exemples=[
        {
            "input": "Clause de confidentialité pour un prestataire",
            "output": "Article X - Confidentialite\nX.1 Le Prestataire s'engage..."
        }
    ]
)

skill_support_technique = Skill(
    nom="support-technique",
    instructions=(
        "Tu es un agent de support technique niveau 2. "
        "Tu diagnostiques les problèmes en posant des questions ciblées. "
        "Tu cherches d'abord dans la base de connaissances avant de proposer "
        "des solutions. Tu documentés chaque resolution."
    ),
    tools=[
        {"type": "file_search", "vector_store_ids": ["vs_base_connaissances"]},
        {
            "type": "function",
            "name": "creer_ticket_escalade",
            "description": "Escalader un problème vers le niveau 3",
            "parameters": {
                "type": "object",
                "properties": {
                    "titre": {"type": "string"},
                    "description": {"type": "string"},
                    "priorite": {"type": "string", "enum": ["basse", "moyenne", "haute", "critique"]}
                },
                "required": ["titre", "description", "priorite"],
                "additionalProperties": False
            },
            "strict": True
        }
    ]
)

Composer des Skills

La composition enchaîne les skills en passant le résultat de l’une en contexte de la suivante. C’est puissant et c’est là que les ennuis commencent, pour une raison précise : à chaque étape, le contexte transmis est la sortie de l’étape précédente, pas les données d’origine. Une information que la première skill a jugée secondaire a disparu pour toutes les suivantes.

Deux conséquences pratiques. Ordonnez les étapes de la plus générale à la plus spécifique, pour que le filtrage se produise le plus tard possible. Et quand une étape aval a besoin des données brutes, repassez-lui les fichiers plutôt que de compter sur le résumé — c’est précisément ce que fait le paramètre fichiers de l’exemple, qui n’est transmis qu’à la première étape et devrait l’être à toutes celles qui en dépendent.

class SkillComposee:
    def __init__(self, nom: str, etapes: list[dict]):
        self.nom = nom
        self.etapes = etapes
        self.client = OpenAI()

    def executer(self, requete_initiale: str, fichiers: list = None) -> dict:
        contexte = requete_initiale
        resultats = {}

        for etape in self.etapes:
            skill = etape["skill"]
            instruction_etape = etape.get("instruction", "")
            prompt = f"{instruction_etape}\n\nContexte precedent : {contexte}"

            resultat = skill.executer(self.client, prompt, fichiers)
            resultats[etape["nom"]] = resultat
            contexte = resultat
            fichiers = None  # Fichiers utilisés seulement a la première étape

        return resultats


# Workflow : analyse de contrat puis rédaction de synthèse
workflow_audit_contrat = SkillComposee(
    nom="audit-contrat",
    etapes=[
        {
            "nom": "analyse",
            "skill": skill_analyse_financiere,
            "instruction": "Analyse les aspects financiers de ce contrat"
        },
        {
            "nom": "juridique",
            "skill": skill_redaction_contrat,
            "instruction": "Identifie les risques juridiques et propose des améliorations"
        }
    ]
)

Skills avec état

Une skill qui garde la mémoire de ses appels précédents rend possibles les conversations longues — un diagnostic technique, une négociation, un suivi de dossier. Elle introduit aussi le seul coût qui croît sans qu’on s’en aperçoive : à chaque tour, tout l’historique est renvoyé et refacturé. Prévoyez dès l’écriture une politique de troncature ou de résumé périodique, faute de quoi la vingtième question coûtera dix fois la première pour une qualité qui, elle, n’aura pas décuplé.

class SkillAvecEtat:
    def __init__(self, skill: Skill):
        self.skill = skill
        self.client = OpenAI()
        self.historique = []
        self.contexte_accumule = ""

    def appeler(self, requete: str, fichiers: list = None) -> str:
        # Construire le prompt avec l'historique
        instructions = self.skill.instructions
        if self.contexte_accumule:
            instructions += f"\n\nContexte des interactions precedentes :\n{self.contexte_accumule}"

        content = [{"type": "text", "text": requete}]
        if fichiers:
            for fid in fichiers:
                content.append({"type": "input_file", "file_id": fid})

        response = self.client.responses.create(
            model="gpt-5.6-terra",
            instructions=instructions,
            input=[{"role": "user", "content": content}],
            tools=self.skill.tools
        )

        resultat = response.output_text
        self.historique.append({"requête": requete, "reponse": resultat})
        self.contexte_accumule += f"\n- {requete} -> {resultat[:200]}..."

        return resultat

# Utilisation : support technique avec suivi de conversation
session = SkillAvecEtat(skill_support_technique)
r1 = session.appeler("Mon application plante au demarrage")
r2 = session.appeler("J'ai la version 3.2.1 sur Ubuntu 24.04")
r3 = session.appeler("Le log montre une erreur de connexion a la base de données")

Registre de Skills

Centralisez la gestion de vos skills :

class RegistreSkills:
    def __init__(self):
        self.skills = {}

    def enregistrer(self, skill: Skill):
        self.skills[skill.nom] = skill

    def obtenir(self, nom: str) -> Skill:
        if nom not in self.skills:
            raise KeyError(f"Skill '{nom}' non trouvee. "
                          f"Disponibles : {list(self.skills.keys())}")
        return self.skills[nom]

    def lister(self) -> list[str]:
        return list(self.skills.keys())

# Registre global
registre = RegistreSkills()
registre.enregistrer(skill_analyse_financiere_obj)
registre.enregistrer(skill_redaction_contrat)
registre.enregistrer(skill_support_technique)

# Utilisation
skill = registre.obtenir("support-technique")
resultat = skill.executer(client, "Mon imprimante ne fonctionne plus")

Points clés à retenir

  • Une Skill encapsule instructions, outils et contexte dans un package réutilisable
  • Composez des skills pour des workflows multi-étapes
  • Utilisez un état pour les skills conversationnelles
  • Centralisez vos skills dans un registre pour faciliter la découverte
  • Les exemples dans les instructions améliorent la précision du modèle