Invitez et gagnez

Fonctionnement des récompenses

Partagez votre lien. Lorsqu’un ami s’inscrit avec ce lien et recharge son solde, vous recevez la récompense affichée sur ses recharges ultérieures.

Extraire du JSON depuis des e-mails : pourquoi la validité du schéma ne garantit pas la vérité et comment protéger l'automatisation

Pourquoi la conformité syntaxique avec un schéma JSON ne garantit pas l'exactitude factuelle des données extraites, et comment concevoir un pipeline applicatif de validation en deux étapes avant d'acheminer les valeurs vers les systèmes internes de production.

Sommaire
Extraire du JSON depuis des e-mails : pourquoi la validité du schéma ne garantit pas la vérité et comment protéger l'automatisation

Le mode Structured Outputs des LLM modernes résout un problème d’ingénierie fondamental : il garantit que les réponses du modèle respectent rigoureusement un contrat JSON Schema ou un modèle Pydantic. Pourtant, un schéma valide n’assure que la conformité syntaxique du document, et non la véracité factuelle de son contenu.

La documentation officielle de l’API Google Gemini sur les Structured Outputs met explicitement en garde : même lorsqu’une sortie est syntaxiquement correcte, les développeurs doivent impérativement valider les valeurs côté applicatif et gérer les divergences sémantiques. Un schéma protège votre analyseur des exceptions JSONDecodeError, mais il reste totalement impuissant face aux hallucinations, aux déductions erronées et aux nuances modales ignorées.

Nous détaillons ci-dessous un scénario de bout en bout illustrant le traitement d’un e-mail entrant adressé au support client : de la transmission du texte brut à l’API jusqu’à la validation sémantique applicative et au routage sécurisé des champs.


1. Données d’entrée : un e-mail de support ambigu

Examinons un exemple synthétique d’e-mail de support, conçu délibérément pour refléter les ambiguïtés classiques du langage naturel : des échéances conditionnelles et des hypothèses non vérifiées.

email_text = """От: alex.m@partner-example.com
Тема: Проблема с выгрузкой отчетов и планы по релизу

Привет! У нас со вчерашнего дня сбоит генерация PDF в биллинге.
Мы хотели бы закрыть этот вопрос как можно быстрее, в идеале к концу недели,
но если ваш релиз 2.4 задерживается, то крайний срок сдвигается на следующий вторник.
Кстати, затронут ли европейский кластер платежей eu-central-1, мы пока точно не знаем — коллеги еще перепроверяют трейсы."""

Ce message comporte deux nuances critiques :

  1. Échéance conditionnelle : la date limite dépend d’un facteur externe (un éventuel retard de la version 2.4) et s’exprime de manière relative (« mardi prochain ») plutôt que sous la forme d’une date calendaire absolue.
  2. Fait non confirmé : le cluster eu-central-1 n’est mentionné que comme une interrogation et une hypothèse en attente d’analyse des traces. La chaîne est formatée sans saut de ligne interne, ce qui autorise une recherche exacte de sous-chaîne.

2. Schéma Pydantic et appel via l’Interactions API

Définissons le contrat d’extraction à l’aide de Pydantic et appelons le modèle par l’intermédiaire de l’interface officielle du SDK Google GenAI.

from typing import Optional
from google import genai
from pydantic import BaseModel, Field


class TicketExtraction(BaseModel):
    issue_summary: str = Field(
        description="Краткая суть проблемы."
    )
    deadline_iso: Optional[str] = Field(
        default=None,
        description="Однозначно зафиксированный срок в формате YYYY-MM-DD. Если дата не определена точно или зависит от условий, вернуть null."
    )
    deadline_context: Optional[str] = Field(
        default=None,
        description="Оговорки, условия или контекст, сопровождающие срок."
    )
    affected_cluster: Optional[str] = Field(
        default=None,
        description="Точно подтвержденный затронутый кластер или сервис. Если факт не подтвержден или выражено сомнение, вернуть null."
    )
    raw_quote: Optional[str] = Field(
        default=None,
        description="Точная цитата из текста, обосновывающая извлеченные факты."
    )


client = genai.Client()

prompt = f"""Извлеки параметры инцидента из письма.
Строго следуй правилу: если автор выражает сомнение или значение зависит от условий,
записывай в соответствующее поле null, а контекст выноси в deadline_context.

Письмо:
{email_text}"""

interaction = client.interactions.create(
    model="gemini-3.8-flash",
    input=prompt,
    response_format={
        "type": "text",
        "mime_type": "application/json",
        "schema": TicketExtraction.model_json_schema(),
    },
)

live_extraction = TicketExtraction.model_validate_json(interaction.output_text)

3. Exemple d’échec : succès syntaxique mais défaillance sémantique

L’extrait ci-dessous illustre un échec logique hypothétique et représentatif, plutôt qu’une réponse garantie d’une exécution particulière de l’API. Il démontre comment un modèle peut renvoyer un JSON formellement valide tout en ignorant les contraintes sémantiques imposées par le prompt.

{
  "issue_summary": "Сбой генерации PDF в биллинге",
  "deadline_iso": "следующий вторник",
  "deadline_context": "в идеале к концу недели, но если релиз 2.4 задерживается, то следующий вторник",
  "affected_cluster": "eu-central-1",
  "raw_quote": "затронут ли европейский кластер платежей eu-central-1, мы пока точно не знаем"
}

Du point de vue du validateur JSON Schema et de la bibliothèque Pydantic, cet objet est irréprochable : toutes les clés sont présentes et les types concordent. En revanche, pour l’automatisation en aval, il comporte des erreurs critiques :

  1. Pourquoi le type chaîne autorise un texte arbitraire : Le champ Optional[str] du schéma généré correspond à une construction anyOf ou une union de types (string et null). Pour le validateur de schéma, la valeur "следующий вторник" constitue une chaîne parfaitement licite. Les instructions textuelles placées dans description n’exercent aucune contrainte au niveau de l’analyseur syntaxique, laissant ainsi passer n’importe quelle chaîne non vide.
  2. Transformation d’une hypothèse en fait avéré : Le modèle a extrait l’identifiant eu-central-1 en occultant la nature interrogative et spéculative de la déclaration.
  3. Le manque de fiabilité de la valeur null guidée par consigne : Même lorsqu’une consigne stricte exige de renvoyer null en cas d’incertitude, la propension du modèle à extraire des entités prend souvent le dessus dès lors qu’un nom d’entité apparaît explicitement dans le texte source.

4. Limites des vérifications naïves : citations, mots d’arrêt et registres d’infrastructure

Les développeurs tentent fréquemment de compenser le manque de fiabilité sémantique des LLM par des heuristiques. Voici pourquoi ces techniques élémentaires n’offrent aucune garantie :

  1. Vérification de présence de citation (raw_quote in email_text) : Dans notre exemple, l’expression "затронут ли европейский кластер платежей eu-central-1, мы пока точно не знаем" correspond à une sous-chaîne exacte et continue du message, de sorte que ce test renvoie True. Toutefois, la présence textuelle atteste uniquement de l’absence d’hallucination littérale ; sur le plan sémantique, elle démontre précisément l’inverse : l’auteur n’est pas certain qu’un incident réel se soit produit.
  2. Détection heuristique par mots d’arrêt (stop words) : Traquer des mots-repères (tels que la particule russe "ли", ou des expressions comme "возможно", "не знаем") demeure très fragile. Des marqueurs courts génèrent aisément des faux positifs au sein d’autres mots ou dans des phrases dont la valeur modale est totalement différente.
  3. Validation par liste blanche d’infrastructure (Allowed Clusters) : Vérifier l’identifiant eu-central-1 dans un référentiel interne de serveurs confirme simplement l’existence d’un tel cluster (identification d’entité). Cela ne prouve en rien qu’une panne ou un incident soit réellement survenu sur ce nœud (événement de défaillance). Sans preuve externe digne de confiance (telle qu’un signal de supervision ou une alerte confirmée), toute mention d’un cluster dans un e-mail entrant reste une hypothèse non vérifiée.

5. Pipeline de validation applicative et routage sécurisé

Pour prévenir tout dysfonctionnement dans les systèmes internes en aval, l’application doit mettre en œuvre une seconde couche de validation. Ce palier vérifie de manière autonome le format des dates, écarte les entités douteuses et oriente les données incomplètes ou ambiguës vers un réviseur humain.

L’utilitaire date.fromisoformat() de Python ne garantit pas à lui seul le respect exclusif du format YYYY-MM-DD (les versions récentes de Python acceptent des chaînes ISO étendues). C’est pourquoi le contrôle de forme doit associer une expression régulière stricte à une analyse calendaire ultérieure.

Afin de démontrer le fonctionnement de ce validateur, nous utilisons un objet distinct demo_extraction, initialisé à partir de notre JSON invalide hypothétique.

import re
from datetime import date
from typing import Any, Optional
from pydantic import BaseModel, Field


class ValidationResult(BaseModel):
    is_safe_for_automation: bool
    confirmed_deadline: Optional[date] = None
    confirmed_cluster: Optional[str] = None
    review_reasons: list[str] = Field(default_factory=list)
    downstream_payload: dict[str, Any] = Field(default_factory=dict)


def validate_and_route_ticket(
    extracted: TicketExtraction,
    source_text: str
) -> ValidationResult:
    reasons: list[str] = []

    if extracted.raw_quote and extracted.raw_quote not in source_text:
        reasons.append("Указанная цитата отсутствует в исходном письме.")

    parsed_date: Optional[date] = None
    if extracted.deadline_iso:
        if not re.fullmatch(r"\d{4}-\d{2}-\d{2}", extracted.deadline_iso):
            reasons.append(
                f"Значение deadline_iso не соответствует строгому формату YYYY-MM-DD: '{extracted.deadline_iso}'."
            )
        else:
            try:
                parsed_date = date.fromisoformat(extracted.deadline_iso)
            except ValueError:
                reasons.append(
                    f"Значение deadline_iso содержит недопустимую календарную дату: '{extracted.deadline_iso}'."
                )
                parsed_date = None

    if parsed_date and extracted.deadline_context:
        reasons.append(
            f"Срок {parsed_date.isoformat()} сопровождается оговорками ('{extracted.deadline_context}') и требует ручного согласования."
        )
        parsed_date = None

    validated_cluster: Optional[str] = None
    if extracted.affected_cluster:
        reasons.append(
            f"Кластер '{extracted.affected_cluster}' упомянут в письме как предположение; без внешнего подтверждения из мониторинга требуется проверка оператором."
        )

    needs_manual_review = len(reasons) > 0 or parsed_date is None or validated_cluster is None

    safe_payload = {
        "summary": extracted.issue_summary,
        "deadline": parsed_date.isoformat() if parsed_date else None,
        "cluster": validated_cluster,
        "routing_status": "MANUAL_REVIEW" if needs_manual_review else "AUTO_APPROVED",
    }

    return ValidationResult(
        is_safe_for_automation=not needs_manual_review,
        confirmed_deadline=parsed_date,
        confirmed_cluster=validated_cluster,
        review_reasons=reasons,
        downstream_payload=safe_payload,
    )


hypothetical_bad_json = """{
  "issue_summary": "Сбой генерации PDF в биллинге",
  "deadline_iso": "следующий вторник",
  "deadline_context": "в идеале к концу недели, но если релиз 2.4 задерживается, то следующий вторник",
  "affected_cluster": "eu-central-1",
  "raw_quote": "затронут ли европейский кластер платежей eu-central-1, мы пока точно не знаем"
}"""

demo_extraction = TicketExtraction.model_validate_json(hypothetical_bad_json)
decision = validate_and_route_ticket(demo_extraction, email_text)

6. État final : examen manuel et isolement des champs

À l’issue de la validation applicative, l’objet decision bascule dans un état rigoureusement contrôlé :

{
  "is_safe_for_automation": false,
  "confirmed_deadline": null,
  "confirmed_cluster": null,
  "review_reasons": [
    "Значение deadline_iso не соответствует строгому формату YYYY-MM-DD: 'следующий вторник'.",
    "Кластер 'eu-central-1' упомянут в письме как предположение; без внешнего подтверждения из мониторинга требуется проверка оператором."
  ],
  "downstream_payload": {
    "summary": "Сбой генерации PDF в биллинге",
    "deadline": null,
    "cluster": null,
    "routing_status": "MANUAL_REVIEW"
  }
}

Tri des champs selon le niveau de confiance

ChampStatut de routageJustification
summaryBrouillon de ticketLe dysfonctionnement lié à la génération des PDF de facturation est clairement formulé. Ce champ peut être inséré en toute sécurité comme description préliminaire de ticket dans le gestionnaire d’incidents.
clusterBloqué (null)La mention du cluster repose sur une supposition non vérifiée de l’auteur. L’assignation automatique à l’équipe d’astreinte d’infrastructure est bloquée pour éviter les fausses alertes.
deadlineBloqué (null)Cette formulation non structurée ne constitue pas une date calendaire, et le calendrier est lui-même conditionnel. L’association automatique à un SLA contraignant est bloquée.
routing_statusFile opérateurLe statut MANUAL_REVIEW indique que les déclencheurs automatiques sont suspendus, réorientant le ticket vers un agent de niveau 1 accompagné du détail des motifs de révision.

Une vérification humaine demeure indispensable avant tout calcul de SLA, escalade de priorité ou transfert vers des équipes d’ingénierie spécialisées. L’application peut générer un ticket préliminaire comportant le résumé du problème signalé, mais les attributs critiques de l’incident restent non attribués tant qu’un opérateur ne les a pas explicitement validés.

Synthèse

Les Structured Outputs garantissent la stabilité du format à l’échelle du schéma, préservant ainsi le code en aval des erreurs de syntaxe inattendues. Toutefois, la conformité à un contrat de schéma n’équivaut nullement à la véracité des données. L’intégration sûre des LLM au sein de processus métiers exige une architecture à double niveau : un contrôle syntaxique à la frontière de l’API, une validation stricte par expressions régulières et vérification de types dans le code applicatif, ainsi qu’une mise en quarantaine systématique des champs incertains vers des files de révision humaine (human-in-the-loop).

Prêt à optimiser votre workflow LLM ?

Connectez vos modèles via une API unique, gérez les clés et maîtrisez vos dépenses d’IA.

Commencer gratuitement