Skip to main content
Lesson 17 of 20 · API Tools: Search, Files, and Code Interpreter

Skills: Encapsulating Capabilities

Lesson 17 of 20 Updated November 2, 2026 Developer
On this page
  1. Skills: Encapsulating Capabilities
  2. The Skill Concept
  3. Structuring a Skill Library
  4. Composing Skills
  5. Stateful Skills
  6. Skill Registry
  7. Key Takeaways

Lesson updated November 2, 2026 based on OpenAI documentation: the Code Interpreter tool now requires a container parameter (added to all examples), text input parts are of type input_text, and web search uses the web_search tool (web_search_preview is now a legacy tool without domain filters). Generated files are retrieved using the fichiers_generes and telecharger functions from Lesson 10. Sources: Code Interpreter · Web search.

Skills: Encapsulating Capabilities

A Skill solves a problem everyone hits by week two: the same system prompt copied into four files, diverging as soon as you improve just one. The idea is to treat a capability as a single unit—its instructions, its tools, its reference data—and call it by name rather than rewriting it.

The benefit isn’t just avoiding repetition. A named capability becomes an object you can version, test, and hand off to someone else. When a lawyer tells you a standard clause has changed, you modify one skill and every call benefits—instead of hunting down where the prompt was duplicated.

The Skill Concept

A Skill encapsulates everything a model needs to accomplish a specific task. The three elements below are not interchangeable: instructions define the role and format, tools determine what the model can fetch, and context anchors responses in your own references rather than the model’s general knowledge.

  • Instructions: The specialized system prompt
  • Tools: The necessary built-in functions and tools
  • Context: Reference data and examples
from openai import OpenAI

client = OpenAI()

# Define a financial analysis skill
skill_analyse_financiere = {
    "instructions": (
        "You are a financial analyst specializing in French SMEs. "
        "You analyze accounting documents and produce structured reports "
        "with key indicators, trends, and recommendations. "
        "Use French accounting standards (PCG). "
        "Format amounts in euros with thousands separators."
    ),
    "tools": [
        {"type": "file_search", "vector_store_ids": ["vs_normes_comptables"]},
        {"type": "code_interpreter", "container": {"type": "auto"}}
    ]
}

# Use the skill
response = client.responses.create(
    model="gpt-5.6-terra",
    instructions=skill_analyse_financiere["instructions"],
    input=[{
        "role": "user",
        "content": [
            {"type": "input_text", "text": "Analyze this balance sheet and produce a report"},
            {"type": "input_file", "file_id": fichier_bilan.id}
        ]
    }],
    tools=skill_analyse_financiere["tools"]
)

Structuring a Skill Library

Moving from a dictionary to a class isn’t just cosmetic: it lets you attach examples to a skill and inject them systematically. Two or three well-chosen input/output pairs do more for format consistency than an extra paragraph of instructions, because they show rather than describe. The implementation below concatenates them to the instructions on every call.

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": "input_text", "text": requete}]
        if fichiers:
            for fid in fichiers:
                content.append({"type": "input_file", "file_id": fid})

        # Add examples to context
        instructions = self.instructions
        if self.exemples:
            instructions += "\n\nReference examples:\n"
            for ex in self.exemples:
                instructions += f"- Input: {ex['input']}\n  Output: {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


# Define skills
skill_redaction_contrat = Skill(
    nom="redaction-contrat",
    instructions=(
        "You are a lawyer specializing in contract law. "
        "You draft clear, precise clauses compliant with French law. "
        "Each clause must be numbered and structured."
    ),
    tools=[
        {"type": "file_search", "vector_store_ids": ["vs_modeles_contrats"]},
        {"type": "web_search"}
    ],
    exemples=[
        {
            "input": "Confidentiality clause for a contractor",
            "output": "Article X - Confidentiality\nX.1 The Contractor undertakes..."
        }
    ]
)

skill_support_technique = Skill(
    nom="support-technique",
    instructions=(
        "You are a level 2 technical support agent. "
        "You diagnose problems by asking targeted questions. "
        "Search the knowledge base first before proposing solutions. "
        "Document every resolution."
    ),
    tools=[
        {"type": "file_search", "vector_store_ids": ["vs_base_connaissances"]},
        {
            "type": "function",
            "name": "creer_ticket_escalade",
            "description": "Escalate an issue to level 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
        }
    ]
)

Composing Skills

Composition chains skills by passing the result of one as context to the next. It’s powerful, and that’s exactly where trouble starts, for a specific reason: at each step, the transmitted context is the output of the previous step, not the original data. Any information the first skill deemed secondary disappears for all subsequent ones.

Two practical consequences. Order steps from most general to most specific, so filtering happens as late as possible. And when a downstream step needs raw data, pass the files again rather than relying on the summary—that’s precisely what the fichiers parameter in the example does, which is passed only to the first step but should be passed to all that depend on it.

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\nPrevious context: {contexte}"

            resultat = skill.executer(self.client, prompt, fichiers)
            resultats[etape["nom"]] = resultat
            contexte = resultat
            fichiers = None  # Files used only at the first step

        return resultats


# Workflow: contract analysis followed by summary drafting
workflow_audit_contrat = SkillComposee(
    nom="audit-contrat",
    etapes=[
        {
            "nom": "analyse",
            "skill": skill_analyse_financiere,
            "instruction": "Analyze the financial aspects of this contract"
        },
        {
            "nom": "juridique",
            "skill": skill_redaction_contrat,
            "instruction": "Identify legal risks and propose improvements"
        }
    ]
)

Stateful Skills

A skill that remembers its previous calls enables long conversations—a technical diagnosis, a negotiation, case tracking. It also introduces the only cost that grows unnoticed: at every turn, the entire history is resent and rebilled. Plan a truncation or periodic summarization policy from the start, or the twentieth question will cost ten times the first for quality that hasn’t improved tenfold.

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:
        # Build prompt with history
        instructions = self.skill.instructions
        if self.contexte_accumule:
            instructions += f"\n\nContext from previous interactions:\n{self.contexte_accumule}"

        content = [{"type": "input_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

# Usage: technical support with conversation tracking
session = SkillAvecEtat(skill_support_technique)
r1 = session.appeler("My application crashes at startup")
r2 = session.appeler("I have version 3.2.1 on Ubuntu 24.04")
r3 = session.appeler("The log shows a database connection error")

Skill Registry

Centralize your skill management:

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}' not found. "
                          f"Available: {list(self.skills.keys())}")
        return self.skills[nom]

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

# Global registry
registre = RegistreSkills()
registre.enregistrer(skill_analyse_financiere_obj)
registre.enregistrer(skill_redaction_contrat)
registre.enregistrer(skill_support_technique)

# Usage
skill = registre.obtenir("support-technique")
resultat = skill.executer(client, "My printer isn't working")

Key Takeaways

  • A Skill encapsulates instructions, tools, and context in a reusable package
  • Compose skills for multi-step workflows
  • Use state for conversational skills
  • Centralize your skills in a registry to simplify discovery
  • Examples in instructions improve model accuracy