Guía de prompts y schemas
Una extracción tiene dos piezas que tú defines:
| Pieza | Para qué sirve |
|---|---|
custom_instruction |
Qué extraer y cómo interpretarlo: tipo de documento, formatos y reglas de negocio. |
response_schema |
Forma exacta del JSON de salida. Es un JSON Schema (subconjunto) y el resultado se valida contra él. |
Esta guía explica cómo escribir las dos, trae plantillas listas para copiar y cómo corregir cada error.
Lo que la plataforma ya hace (no lo repitas)
Cada extracción envía al modelo un prompt de sistema fijo con estas reglas:
- Devolver solo JSON válido que cumpla exactamente el schema.
- Sin bloques markdown ni explicaciones.
- No inventar datos que no estén en el OCR.
- Usar
nullcuando un dato falta o no se puede verificar.
Después se envían, en este orden: tu custom_instruction, tu response_schema y el contenido OCR del documento (en el formato de input_result_type).
Si la respuesta no es JSON o no cumple el schema, la plataforma reintenta automáticamente (por defecto hasta 3 intentos) antes de marcar validation_failed.
No escribas "responde en JSON" ni "no inventes datos" en tu instrucción: ya está cubierto. Usa ese espacio para lo que solo tú sabes del documento.
Reglas del response_schema
- La raíz debe ser
"type": "object". - Cada nodo (cada propiedad y cada
items) debe declarartype. - Solo se aceptan estas keywords:
| Keyword | Uso |
|---|---|
type |
string, number, integer, boolean, object, array, null. Combina tipos con un arreglo: ["string", "null"]. |
properties |
Campos de un objeto. |
required |
Claves que deben existir en la respuesta (el valor puede ser null si el tipo lo permite). |
items |
Schema de cada elemento de un array. |
enum |
Lista cerrada de valores. Si el campo acepta null, incluye null en el enum. |
additionalProperties |
false para rechazar claves que no definiste. |
description |
Texto con instrucciones para ese campo (formato, significado, dónde buscarlo). |
Cualquier otra keyword (format, pattern, minimum, maxLength, oneOf, anyOf, $ref, default, examples…) responde 422 validation_error. Si necesitas esa regla, escríbela en description o en custom_instruction.
Formato abreviado: no soportado
❌ { "total": "number|null" }
✅ { "type": "object", "properties": { "total": { "type": ["number", "null"] } } }
Patrón recomendado
- Pon todos los campos en
requiredy hazlos anulables (["string", "null"]). Así la respuesta siempre tiene las mismas claves y lo que no aparece llega comonull. - Usa
"additionalProperties": falsepara que tu código reciba siempre la misma forma. - Usa
numberpara montos;integersolo acepta enteros (116.5no pasa). - Para listas, usa
"type": "array"conitems. Una lista vacía[]es válida.
description: instrucciones por campo
Es la forma más efectiva de guiar al modelo, porque la regla queda junto al campo:
"fecha_emision": {
"type": ["string", "null"],
"description": "Fecha de emisión en formato YYYY-MM-DD."
},
"total": {
"type": ["number", "null"],
"description": "Total con impuestos. Número decimal con punto, sin símbolo de moneda ni separador de miles."
}
Con buenas description, la custom_instruction puede quedar corta.
Cómo escribir la custom_instruction
- Di qué documento es. "Factura electrónica mexicana (CFDI 4.0)".
- Desambigua roles. "Emisor es quien factura; receptor es el cliente".
- Fija formatos globales. Fechas
YYYY-MM-DD, montos como número decimal, RFC y CURP en mayúsculas sin espacios. - Resuelve casos ambiguos. "Si hay varios totales, usa el total a pagar"; "si hay varias direcciones, usa la del titular".
- Explica las listas. "En
conceptosincluye cada renglón de la tabla de conceptos, en el mismo orden". - Sé breve. Máximo 8000 caracteres; lo específico de cada campo va en
description.
| ❌ Poco útil | ✅ Útil |
|---|---|
| "Extrae los datos de la factura en JSON y no inventes nada." | "Factura CFDI 4.0. Emisor es quien factura. Fechas YYYY-MM-DD. Montos como número decimal sin símbolo. Si hay varios totales, usa el total a pagar." |
Plantillas listas para copiar
Sustituye document_id y ajusta campos a tu caso. Todas son válidas tal cual.
Factura CFDI (cfdi_invoice)
{
"document_id": "doc_01J...",
"custom_instruction": "Documento: factura electrónica mexicana (CFDI 4.0). Emisor es quien factura; receptor es el cliente. Fechas en formato YYYY-MM-DD. Montos como número decimal con punto, sin símbolo de moneda ni separador de miles. RFC en mayúsculas, sin espacios ni guiones.",
"response_schema": {
"type": "object",
"additionalProperties": false,
"required": ["uuid", "rfc_emisor", "nombre_emisor", "rfc_receptor", "nombre_receptor", "fecha_emision", "moneda", "subtotal", "iva", "total", "uso_cfdi", "conceptos"],
"properties": {
"uuid": { "type": ["string", "null"], "description": "Folio fiscal (UUID) de 36 caracteres." },
"rfc_emisor": { "type": ["string", "null"] },
"nombre_emisor": { "type": ["string", "null"], "description": "Nombre o razón social del emisor." },
"rfc_receptor": { "type": ["string", "null"] },
"nombre_receptor": { "type": ["string", "null"] },
"fecha_emision": { "type": ["string", "null"], "description": "Formato YYYY-MM-DD." },
"moneda": { "type": ["string", "null"], "description": "Código ISO de 3 letras, por ejemplo MXN." },
"subtotal": { "type": ["number", "null"] },
"iva": { "type": ["number", "null"], "description": "Total de IVA trasladado." },
"total": { "type": ["number", "null"], "description": "Total a pagar con impuestos." },
"uso_cfdi": { "type": ["string", "null"], "description": "Clave de uso CFDI, por ejemplo G03." },
"conceptos": {
"type": "array",
"description": "Un elemento por renglón de la tabla de conceptos. Lista vacía si no hay tabla.",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["descripcion", "cantidad", "valor_unitario", "importe"],
"properties": {
"descripcion": { "type": ["string", "null"] },
"cantidad": { "type": ["number", "null"] },
"valor_unitario": { "type": ["number", "null"] },
"importe": { "type": ["number", "null"] }
}
}
}
}
},
"input_result_type": "markdown"
}
Credencial INE (ine_document)
{
"document_id": "doc_01J...",
"custom_instruction": "Documento: credencial para votar INE/IFE de México (frente y, si existe, reverso). Nombres y CURP en mayúsculas, tal como aparecen. Fechas en formato YYYY-MM-DD.",
"response_schema": {
"type": "object",
"additionalProperties": false,
"required": ["nombre", "apellido_paterno", "apellido_materno", "curp", "clave_elector", "fecha_nacimiento", "sexo", "domicilio", "seccion", "vigencia"],
"properties": {
"nombre": { "type": ["string", "null"], "description": "Solo nombre(s), sin apellidos." },
"apellido_paterno": { "type": ["string", "null"] },
"apellido_materno": { "type": ["string", "null"] },
"curp": { "type": ["string", "null"], "description": "18 caracteres." },
"clave_elector": { "type": ["string", "null"], "description": "18 caracteres." },
"fecha_nacimiento": { "type": ["string", "null"], "description": "Formato YYYY-MM-DD." },
"sexo": { "type": ["string", "null"], "enum": ["H", "M", null] },
"domicilio": { "type": ["string", "null"], "description": "Domicilio completo en una sola línea." },
"seccion": { "type": ["string", "null"], "description": "Sección electoral de 4 dígitos, como texto." },
"vigencia": { "type": ["integer", "null"], "description": "Año de vigencia, por ejemplo 2031." }
}
},
"input_result_type": "markdown"
}
Constancia de Situación Fiscal (csf_document)
{
"document_id": "doc_01J...",
"custom_instruction": "Documento: Constancia de Situación Fiscal del SAT. RFC en mayúsculas. Fechas en formato YYYY-MM-DD. En regimenes incluye cada régimen listado en la sección de regímenes.",
"response_schema": {
"type": "object",
"additionalProperties": false,
"required": ["rfc", "nombre_o_razon_social", "codigo_postal", "estatus_padron", "fecha_emision", "regimenes"],
"properties": {
"rfc": { "type": ["string", "null"] },
"nombre_o_razon_social": { "type": ["string", "null"], "description": "Persona física: nombre completo. Persona moral: razón social sin régimen capital." },
"codigo_postal": { "type": ["string", "null"], "description": "5 dígitos del domicilio fiscal, como texto." },
"estatus_padron": { "type": ["string", "null"], "description": "Por ejemplo ACTIVO." },
"fecha_emision": { "type": ["string", "null"], "description": "Fecha en que se emitió la constancia, formato YYYY-MM-DD." },
"regimenes": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["descripcion", "fecha_inicio"],
"properties": {
"descripcion": { "type": ["string", "null"] },
"fecha_inicio": { "type": ["string", "null"], "description": "Formato YYYY-MM-DD." }
}
}
}
}
},
"input_result_type": "markdown"
}
Comprobante de domicilio (proof_of_address)
{
"document_id": "doc_01J...",
"custom_instruction": "Documento: recibo de servicio usado como comprobante de domicilio (CFE, agua, teléfono, internet). La dirección es la del titular del servicio, no la de la empresa que emite el recibo. Fechas en formato YYYY-MM-DD.",
"response_schema": {
"type": "object",
"additionalProperties": false,
"required": ["emisor", "titular", "calle_y_numero", "colonia", "municipio", "estado", "codigo_postal", "fecha_emision", "total_a_pagar"],
"properties": {
"emisor": { "type": ["string", "null"], "description": "Empresa que emite el recibo, por ejemplo CFE." },
"titular": { "type": ["string", "null"] },
"calle_y_numero": { "type": ["string", "null"] },
"colonia": { "type": ["string", "null"] },
"municipio": { "type": ["string", "null"], "description": "Municipio o alcaldía." },
"estado": { "type": ["string", "null"] },
"codigo_postal": { "type": ["string", "null"], "description": "5 dígitos, como texto." },
"fecha_emision": { "type": ["string", "null"], "description": "Fecha de emisión o de corte del recibo, formato YYYY-MM-DD." },
"total_a_pagar": { "type": ["number", "null"] }
}
},
"input_result_type": "markdown"
}
Límites
Valores por defecto (el equipo de plataforma puede ajustarlos):
| Límite | Valor |
|---|---|
custom_instruction |
1 a 8000 caracteres |
Tamaño del response_schema |
64 KB |
| Profundidad del schema | 8 niveles (la raíz cuenta como 1) |
| Propiedades en todo el schema | 100 |
Nodos items en todo el schema |
50 |
| Contenido OCR enviado al modelo | 256 KB. Si se excede, la extracción termina failed con extraction_input_unavailable; prueba otro input_result_type (normalmente text es el más compacto). |
Errores y cómo corregirlos
Al crear la extracción (422 validation_error)
Si el schema es inválido, error.details.response_schema lista los problemas:
| Mensaje | Causa | Solución |
|---|---|---|
Root schema type must be object. |
Raíz sin "type": "object" o formato abreviado. |
Envuelve tus campos en { "type": "object", "properties": { ... } }. |
Schema node must define type. |
Alguna propiedad o items sin type. |
Agrega type a cada nodo. |
Schema contains unsupported keywords or types. |
Keyword no soportada (format, pattern…), tipo inexistente o description que no es texto. |
Quita la keyword y mueve la regla a description. |
Property names must be non-empty strings. |
Una propiedad con nombre vacío ("": {...}). |
Dale un nombre a cada campo. |
Property {nombre} must be an object. |
Un campo definido como texto ("total": "number"). |
Usa "total": { "type": "number" }. |
Schema exceeds maximum allowed ... |
Supera tamaño, profundidad, propiedades o items. |
Divide en dos extracciones o simplifica. |
Después de procesar (validation_failed)
El modelo respondió, pero su JSON no cumplió el schema en ningún intento. Consulta GET /extractions/{extraction_id}: failure.validation_errors trae los errores del último intento.
{
"data": {
"id": "ext_01J...",
"status": "validation_failed",
"failure": {
"code": "schema_validation_failed",
"message": "The extraction response could not be validated against the requested schema.",
"validation_errors": [
"$.total: value does not match expected type.",
"$.conceptos[0].cantidad: is required."
]
}
}
}
| Error | Qué pasó | Solución |
|---|---|---|
$.campo: is required. |
El modelo omitió la clave. | Hazla anulable (["string", "null"]) y explica en description dónde está el dato. |
$.campo: value does not match expected type. |
Tipo distinto, por ejemplo "11,600.00" (texto) donde esperabas number. |
Indica el formato en description o acepta el otro tipo. |
$.campo: value is not in enum. |
Valor fuera de la lista. | Amplía el enum, incluye null o explica el mapeo (por ejemplo "Hombre → H"). |
$.campo: additional property not allowed. |
El modelo agregó una clave que no definiste. | Revisa nombres confusos o quita "additionalProperties": false. |
Invalid JSON response from provider. |
La respuesta no fue JSON. | Suele ser un OCR muy largo o ruidoso: prueba otro input_result_type o reduce campos. |
Tras ajustar, crea una nueva extracción con una nueva Idempotency-Key.
Iterar rápido
- Sube el documento una vez y reutiliza su
document_iden cada prueba de extracción. - Lee lo que ve el modelo:
GET /documents/{document_id}/result?format=markdown. Si un dato no está en el OCR, ningún prompt lo va a extraer. - Empieza con pocos campos, confirma que salen bien y agrega el resto.
- El resultado final está en
data.resultdeGET /extractions/{extraction_id}/result.
Checklist
- Raíz
"type": "object"ytypeen cada nodo. - Solo keywords soportadas; reglas de formato en
description. - Campos anulables en
requiredy"additionalProperties": false. -
enumanulables incluyennull. - Instrucción con tipo de documento, roles, formatos y casos ambiguos, sin repetir reglas de plataforma.
- Manejo de
validation_failedleyendofailure.validation_errors.