Extraction d'entités structurées
Extraction d’entités structurées
L’extraction d’entités structurées est l’un des cas d’usage les plus demandés en production : transformer du texte libre (emails, documents, pages web) en données exploitables par votre application. En combinant le Structured Output avec des prompts spécialisés, vous pouvez construire des pipelines d’extraction fiables sans entraîner de modèle NER dédié.
Pipeline d’extraction de base
import json
from openai import OpenAI
client = OpenAI()
def extraire_entites(texte: str, schema: dict,
instructions: str = "") -> dict:
"""Extrait des entités structurées d'un texte libre."""
system = (
"Tu es un extracteur d'entités spécialisé. "
"Extrais UNIQUEMENT les informations présentes dans le texte. "
"Si une information est absente, utilise null. "
"Ne déduis JAMAIS d'informations non explicitement mentionnées."
)
if instructions:
system += f"\n\nInstructions supplémentaires : {instructions}"
response = client.responses.create(
model="gpt-5.3",
instructions=system,
input=f"Texte à analyser :\n\n{texte}",
text={
"format": {
"type": "json_schema",
"name": "extraction",
"schema": schema,
"strict": True
}
},
temperature=0.0
)
return json.loads(response.output_text)
Exemple 1 : extraction de CV
cv_schema = {
"type": "object",
"properties": {
"identite": {
"type": "object",
"properties": {
"nom_complet": {"type": "string"},
"email": {"type": ["string", "null"]},
"telephone": {"type": ["string", "null"]},
"localisation": {"type": ["string", "null"]},
"linkedin": {"type": ["string", "null"]}
},
"required": ["nom_complet", "email", "telephone",
"localisation", "linkedin"],
"additionalProperties": False
},
"titre_actuel": {"type": ["string", "null"]},
"annees_experience": {"type": ["integer", "null"]},
"competences": {
"type": "array",
"items": {
"type": "object",
"properties": {
"nom": {"type": "string"},
"niveau": {
"type": "string",
"enum": ["debutant", "intermediaire",
"avance", "expert"]
},
"categorie": {
"type": "string",
"enum": ["langage", "framework", "outil",
"methodologie", "soft_skill"]
}
},
"required": ["nom", "niveau", "categorie"],
"additionalProperties": False
}
},
"experiences": {
"type": "array",
"items": {
"type": "object",
"properties": {
"entreprise": {"type": "string"},
"poste": {"type": "string"},
"date_debut": {"type": "string"},
"date_fin": {"type": ["string", "null"]},
"description": {"type": "string"}
},
"required": ["entreprise", "poste", "date_debut",
"date_fin", "description"],
"additionalProperties": False
}
},
"formations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"etablissement": {"type": "string"},
"diplome": {"type": "string"},
"annee": {"type": ["string", "null"]}
},
"required": ["etablissement", "diplome", "annee"],
"additionalProperties": False
}
},
"langues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"langue": {"type": "string"},
"niveau": {
"type": "string",
"enum": ["a1", "a2", "b1", "b2",
"c1", "c2", "natif"]
}
},
"required": ["langue", "niveau"],
"additionalProperties": False
}
}
},
"required": ["identite", "titre_actuel", "annees_experience",
"competences", "experiences", "formations", "langues"],
"additionalProperties": False
}
cv_texte = """
Marie Dupont - Développeuse Full Stack Senior
Email: [email protected] | Tel: +33 6 12 34 56 78
Paris, France | linkedin.com/in/mariedupont
8 ans d'expérience en développement web.
Compétences : Python (expert), TypeScript (avancé), React (avancé),
Django (expert), PostgreSQL (avancé), Docker (intermédiaire),
Scrum (avancé)
Expérience :
- TechCorp (2022-présent) : Lead Developer Full Stack
Architecte principal d'une plateforme SaaS B2B, équipe de 6 dev.
- StartupAI (2019-2022) : Développeuse Senior
Développement d'APIs ML en Python/FastAPI.
Formation :
- EPITECH Paris, Master Informatique (2017)
Langues : Français (natif), Anglais (C1), Espagnol (B1)
"""
result = extraire_entites(cv_texte, cv_schema)
Exemple 2 : extraction d’emails commerciaux
email_schema = {
"type": "object",
"properties": {
"intention": {
"type": "string",
"enum": ["demande_devis", "reclamation", "information",
"commande", "partenariat", "autre"]
},
"urgence": {
"type": "string",
"enum": ["critique", "haute", "normale", "basse"]
},
"expediteur": {
"type": "object",
"properties": {
"nom": {"type": ["string", "null"]},
"entreprise": {"type": ["string", "null"]},
"email": {"type": ["string", "null"]}
},
"required": ["nom", "entreprise", "email"],
"additionalProperties": False
},
"produits_mentionnes": {
"type": "array",
"items": {"type": "string"}
},
"montants_mentionnes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"valeur": {"type": "number"},
"devise": {"type": "string"},
"contexte": {"type": "string"}
},
"required": ["valeur", "devise", "contexte"],
"additionalProperties": False
}
},
"dates_cles": {
"type": "array",
"items": {
"type": "object",
"properties": {
"date": {"type": "string"},
"signification": {"type": "string"}
},
"required": ["date", "signification"],
"additionalProperties": False
}
},
"actions_requises": {
"type": "array",
"items": {"type": "string"}
},
"resume": {"type": "string"}
},
"required": ["intention", "urgence", "expediteur",
"produits_mentionnes", "montants_mentionnes",
"dates_cles", "actions_requises", "resume"],
"additionalProperties": False
}
Extraction en batch
Pour traiter de gros volumes, parallélisez les extractions :
import asyncio
from openai import AsyncOpenAI
async_client = AsyncOpenAI()
async def extraire_batch(textes: list[str], schema: dict,
max_concurrent: int = 10) -> list[dict]:
"""Extraction parallèle avec contrôle de concurrence."""
semaphore = asyncio.Semaphore(max_concurrent)
async def extract_one(texte: str) -> dict:
async with semaphore:
response = await async_client.responses.create(
model="gpt-5.3",
instructions="Extrais les entités du texte fourni. "
"Utilise null pour les champs absents.",
input=texte,
text={
"format": {
"type": "json_schema",
"name": "extraction",
"schema": schema,
"strict": True
}
},
temperature=0.0
)
return json.loads(response.output_text)
tasks = [extract_one(t) for t in textes]
return await asyncio.gather(*tasks, return_exceptions=True)
Évaluer la qualité de l’extraction
def evaluer_extraction(attendu: dict, obtenu: dict,
champs: list[str]) -> dict:
"""Compare l'extraction obtenue avec la référence."""
correct = 0
total = len(champs)
erreurs = []
for champ in champs:
val_attendue = attendu.get(champ)
val_obtenue = obtenu.get(champ)
if val_attendue == val_obtenue:
correct += 1
else:
erreurs.append({
"champ": champ,
"attendu": val_attendue,
"obtenu": val_obtenue
})
return {
"precision": correct / total if total > 0 else 0,
"correct": correct,
"total": total,
"erreurs": erreurs
}
Mise en pratique
- Choisissez un type de document de votre domaine (contrats, factures, rapports)
- Définissez un schéma d’extraction avec au moins 10 champs
- Créez 5 documents d’exemple et leurs extractions attendues
- Implémentez le pipeline et mesurez la précision
- Identifiez les champs où le modèle fait le plus d’erreurs et ajustez le prompt
Points clés à retenir
- L’extraction structurée remplace les modèles NER dédiés pour la plupart des cas
- Utilisez
temperature=0.0pour maximiser la cohérence - Rendez les champs facultatifs nullable plutôt qu’optionnels
- Parallélisez avec asyncio pour les gros volumes
- Évaluez systématiquement la qualité avec un jeu de test annoté
- Les instructions du system prompt guident le contenu, le schéma contraint la structure