Invita y gana

Cómo funcionan las recompensas

Comparte tu enlace. Cuando un amigo se registre con él y recargue saldo, recibirás la recompensa indicada por sus recargas posteriores.

Extracción de JSON de correos: por qué la validez del esquema no garantiza la verdad y cómo proteger la automatización

Por qué el cumplimiento sintáctico con un esquema JSON no garantiza la precisión fáctica de los datos extraídos y cómo construir un pipeline de validación a nivel de aplicación en dos etapas antes de enrutar valores hacia los sistemas internos de producción.

Índice
Extracción de JSON de correos: por qué la validez del esquema no garantiza la verdad y cómo proteger la automatización

El modo Structured Outputs en los LLM modernos resuelve un problema de ingeniería fundamental: garantiza que las respuestas del modelo se adhieran estrictamente al contrato de un JSON Schema o a un modelo de Pydantic. Sin embargo, un esquema válido solo garantiza la forma sintáctica del documento, no la veracidad fáctica de su contenido.

La documentación oficial de la Google Gemini API sobre Structured Outputs advierte de forma explícita que, incluso cuando la salida es sintácticamente correcta, los desarrolladores deben validar los valores del lado de la aplicación y gestionar las discrepancias semánticas. Un esquema protege a su analizador frente a excepciones JSONDecodeError, pero resulta ineficaz contra alucinaciones, inferencias erróneas y matices modales omitidos.

A continuación, se presenta un escenario integral para el procesamiento de correos electrónicos entrantes del servicio de soporte técnico: desde el envío del texto sin procesar a la API hasta la validación semántica en la aplicación y el enrutamiento seguro de los campos.


1. Datos de entrada: un correo de soporte ambiguo

Considere un correo de soporte sintético e ilustrativo, diseñado deliberadamente con las ambigüedades típicas del lenguaje natural: plazos condicionales y suposiciones no verificadas.

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

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

Este texto contiene dos matices críticos:

  1. Plazo condicional: la fecha límite depende de un factor externo (un retraso en la versión 2.4) y está formulada de manera relativa (“следующий вторник”, el próximo martes) en lugar de ser una fecha de calendario absoluta.
  2. Hecho sin confirmar: el clúster eu-central-1 se menciona simplemente como una pregunta abierta y una hipótesis pendiente de verificación mediante trazas. La cadena está formateada sin saltos de línea internos, lo que permite realizar coincidencias exactas de subcadenas.

2. Esquema de Pydantic e invocación mediante la API de Interactions

Definamos el contrato de extracción utilizando Pydantic e invoquemos el modelo mediante la interfaz oficial del SDK de 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. Fallo ilustrativo: éxito sintáctico con error semántico

El fragmento que figura a continuación ilustra un fallo lógico hipotético y típico, más que el resultado garantizado de una ejecución concreta de la API. Demuestra cómo un modelo puede devolver un JSON formalmente válido ignorando al mismo tiempo las restricciones semánticas definidas en el prompt.

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

Desde la perspectiva del validador de JSON Schema y de la biblioteca Pydantic, este objeto es impecable: todas las claves están presentes y los tipos coinciden. Sin embargo, para la automatización posterior, contiene errores críticos:

  1. Por qué los tipos de cadena permiten texto arbitrario: El campo Optional[str] en el esquema generado se convierte en una construcción anyOf o unión de tipos (string y null). Para el validador de esquemas, el valor "следующий вторник" es una cadena completamente válida. Las instrucciones textuales dentro de description no imponen restricciones a nivel de analizador, lo que permite que cualquier cadena no vacía supere la validación.
  2. Convertir una hipótesis en un hecho establecido: El modelo extrajo el identificador eu-central-1 ignorando la naturaleza interrogativa y especulativa de la afirmación.
  3. La falta de fiabilidad de los valores null guiados por instrucciones: Incluso las instrucciones estrictas en el prompt para devolver null ante la incertidumbre suelen ceder ante la tendencia a extraer entidades cuando el nombre de una entidad aparece de forma explícita en el texto de origen.

4. Limitaciones de las comprobaciones ingenuas: citas, palabras de parada y registros de infraestructura

Los desarrolladores suelen intentar compensar la falta de fiabilidad semántica de los LLM mediante heurísticas. A continuación, se explica por qué las técnicas básicas no ofrecen garantías:

  1. Verificación de la presencia de la cita (raw_quote in email_text): En nuestro ejemplo, la frase "затронут ли европейский кластер платежей eu-central-1, мы пока точно не знаем" es una subcadena exacta y continua del correo, por lo que esta comprobación se evalúa como True. Sin embargo, la presencia literal en el texto solo demuestra que la cita no fue una alucinación; semánticamente, demuestra exactamente lo contrario: el autor no está seguro de si realmente se produjo una interrupción del servicio.
  2. Búsqueda heurística de palabras de parada: Buscar palabras marcadoras (como partículas del tipo "ли" o frases como "возможно", "не знаем") resulta frágil. Los marcadores breves generan fácilmente falsos positivos dentro de otras palabras o en oraciones que transmiten significados modales completamente distintos.
  3. Validación frente a una lista de permitidos de infraestructura (Allowed Clusters): Verificar eu-central-1 frente a un registro interno de servidores solo confirma que dicho clúster existe (identificación de entidad). No demuestra que haya ocurrido un incidente o una caída real en ese nodo (evento de fallo). Sin pruebas externas de confianza (como señales de monitorización o alertas confirmadas), cualquier mención de un clúster en un correo electrónico entrante sigue siendo una hipótesis sin verificar.

5. Pipeline de validación en la aplicación y enrutamiento seguro

Para prevenir fallos en los sistemas internos posteriores, las aplicaciones deben implementar una segunda capa de validación. Esta capa verifica de manera independiente los formatos de fecha, filtra entidades cuestionables y enruta los datos incompletos o ambiguos a un revisor humano.

La función auxiliar date.fromisoformat() de Python no garantiza por sí sola el estricto cumplimiento del formato YYYY-MM-DD (las versiones modernas de Python admiten cadenas ISO extendidas). Por lo tanto, las comprobaciones de formato deben combinar una expresión regular estricta con un análisis sintáctico de calendario posterior.

Para demostrar este validador en acción, utilizamos un objeto independiente demo_extraction, inicializado a partir de nuestro JSON hipotético e inválido.

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. Estado final: revisión manual y aislamiento de campos

Tras ejecutarse la validación en la aplicación, el objeto decision pasa a un estado estrictamente controlado:

{
  "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"
  }
}

Triaje de campos por nivel de confianza

CampoEstado de enrutamientoJustificación
summaryBorrador de ticketLa incidencia en la generación de PDF de facturación está explícitamente establecida. Es seguro utilizar este campo para rellenar la descripción preliminar del ticket en el gestor de incidencias.
clusterBloqueado (null)La mención del clúster es una suposición sin confirmar del autor. Se bloquea la asignación automática a la guardia de infraestructura para evitar falsas alarmas.
deadlineBloqueado (null)La frase no estructurada no es una fecha de calendario y el propio plazo es condicional. Se bloquea su vinculación automática a un SLA estricto.
routing_statusCola de operadoresEl estado MANUAL_REVIEW indica que los disparadores automáticos están suspendidos y el ticket se enruta a un operador de primer nivel junto con la lista detallada de motivos de revisión.

Se requiere verificación humana antes de realizar cualquier cálculo de SLA, escalado de prioridad o enrutamiento a equipos de ingeniería especializados. La aplicación puede generar un borrador de ticket con el resumen del problema notificado, pero los atributos críticos del incidente permanecen sin asignar hasta que un operador los confirme de forma explícita.

Resumen

Structured Outputs garantiza la estabilidad del formato a nivel de esquema, protegiendo el código posterior de errores sintácticos imprevistos. Sin embargo, cumplir el contrato de un esquema no equivale a la veracidad de los datos. Para integrar LLM de forma segura en los procesos de negocio se necesita una arquitectura en dos niveles: control sintáctico en el límite de la API, validación estricta de tipos y expresiones regulares dentro del código de la aplicación, y una cuarentena obligatoria de los campos dudosos en colas de revisión con intervención humana.

¿Quieres optimizar tu flujo de trabajo con LLM?

Conecta modelos mediante una API, gestiona claves y controla el gasto en IA.

Empezar gratis