← SCRAM AI Lab
Descubre cómo el patrón 2-pass con Prisma elimina alucinaciones en NL-to-SQL mediante validación intermedia de esquema y optimización de costos con LLMs.
May 21, 2026
425 lecturas

El demo es seductor: usuario pregunta "dame los 10 deals más grandes del trimestre" y el LLM escupe SQL. Funciona en el demo. En producción con un schema de 80 tablas, joins de tres niveles y nombres de columnas en español mexicano (estatus, monto_neto, fecha_alta), el modelo inventa tablas que no existen, joins que no se sostienen, y filtros sintácticamente válidos pero semánticamente equivocados. Y como la SQL parsea bien, el sistema corre la query y devuelve resultados que parecen correctos pero no lo son. Eso es peor que un error.
Estás pidiendo al modelo que haga tres cosas a la vez: (1) entender el intent del usuario, (2) mapearlo al schema correcto, (3) producir SQL válido. Cada paso multiplica la oportunidad de alucinar. El modelo prioriza "producir SQL que se vea correcto" sobre "asegurarse de que el intent es ese". Y el output es un blob de texto que es difícil validar sin ejecutar.
Separar el proceso en dos pasos desacopla la comprensión semántica de la sintaxis técnica, reduciendo los errores lógicos en bases de datos complejas. Al generar primero una especificación intermedia en JSON validable contra el esquema, el sistema filtra alucinaciones de tablas y campos antes de compilar a Prisma, garantizando consistencia analítica e integridad relacional.
Pass 1 requiere razonamiento sobre intent ambiguo y conocimiento del dominio. Aquí Sonnet 4.6 sobresale. Pass 2 es traducción mecánica de un JSON bien definido a código Prisma. gpt-4o-mini hace esto bien y cuesta una fracción. Ahorras 60-70% del costo total sin perder calidad.
Evaluar el mecanismo de consulta implica sopesar costos de inferencia reportados por proveedores como Anthropic y OpenAI, frente a la seguridad operativa en la base de datos empresarial:
| Criterio | Single-pass Directo (SQL Raw) | Single-pass con ORM | Patrón 2-Pass con Prisma |
|---|---|---|---|
| Tasa de alucinación semántica | Muy alta en esquemas de más de 30 tablas | Media a alta; inventa campos del modelo | Baja; bloqueada por capa de validación intermedia |
| Riesgo de inyección o mutación | Crítico; requiere sanitización manual y permisos read-only | Bajo; el ORM parametriza automáticamente | Nulo; el parseador valida métodos de sólo lectura |
| Costo relativo por consulta | Alto (requiere modelos de razonamiento superior como Claude 3.5 Sonnet o GPT-4o para todo) | Alto (modelo de frontera generando sintaxis completa) | Optimizado (modelo avanzado en Pass 1, modelo ligero como GPT-4o mini en Pass 2) |
| Mantenibilidad ante cambios | Frágil; cualquier renombramiento rompe cadenas sin alertar | Media; depende del tipado dinámico | Alta; la introspección de Prisma alerta errores en compilación |
Eres un analista que traduce preguntas a query specs estructurados.
Schema disponible (formato resumido):
- Contact { id, name, email, score, status, ownerId, createdAt }
- Deal { id, amount, stage, probability, contactId, closeDate, createdAt }
- Pipeline { id, name }
- ... (introspección automática del Prisma schema)
Pregunta del usuario:
{userQuestion}
Responde SOLO con JSON válido siguiendo este shape:
{
"entity": "Contact" | "Deal" | "Pipeline" | ...,
"operation": "list" | "count" | "sum" | "avg" | "groupBy",
"filters": [{ "field": string, "op": "eq"|"gt"|"lt"|"contains"|"in", "value": any }],
"joins": [string],
"orderBy": { "field": string, "direction": "asc"|"desc" } | null,
"limit": number | null,
"groupBy": string | null,
"confidence": 0..1,
"clarificationNeeded": string | null
}
Si la pregunta es ambigua, pon clarificationNeeded en texto natural.
Antes de pasar a Pass 2, valida el spec contra schema introspection. Esto atrapa hallucinations sin necesidad de ejecutar la query:
function validateSpec(spec, schema) {
const entity = schema.models[spec.entity];
if (!entity) return { ok: false, error: `Unknown entity: ${spec.entity}` };
for (const f of spec.filters) {
if (!entity.fields[f.field]) {
return { ok: false, error: `Field ${f.field} not in ${spec.entity}` };
}
}
if (spec.orderBy && !entity.fields[spec.orderBy.field]) {
return { ok: false, error: `OrderBy field ${spec.orderBy.field} invalid` };
}
for (const j of spec.joins) {
if (!entity.relations[j]) {
return { ok: false, error: `Relation ${j} not in ${spec.entity}` };
}
}
return { ok: true };
}
Si falla, retroalimenta al Pass 1 con el error y pídele que corrija. Dos intentos máximo; si no cuadra, devuelve clarification al usuario.
Aquí el modelo recibe el spec validado y produce código Prisma. Como el shape está acotado y Prisma es type-safe, las opciones de error se desploman:
// Spec validado
{
entity: "Deal",
operation: "list",
filters: [
{ field: "stage", op: "in", value: ["negotiation", "proposal"] },
{ field: "amount", op: "gt", value: 50000 }
],
joins: ["contact"],
orderBy: { field: "amount", direction: "desc" },
limit: 10
}
// Pass 2 output
prisma.deal.findMany({
where: { stage: { in: ["negotiation", "proposal"] }, amount: { gt: 50000 } },
include: { contact: true },
orderBy: { amount: "desc" },
take: 10
});
Prisma te da type-safety en el output, validación automática contra el schema, y protección contra SQL injection sin que el modelo tenga que pensarlo. Además, el output es código que tu equipo lee y mantiene, no SQL ad-hoc que nadie quiere tocar. Para queries que Prisma no soporta (window functions, CTEs complejos), aíslalas en raw queries explícitas con allowlist.
En el contexto corporativo de México y América Latina, las bases de datos raras veces están normalizadas en un inglés canónico. Es común interactuar con sistemas legados montados sobre SAP, Microsip, Intelisis o desarrollos internos donde conviven campos como rfc_emisor, folio_fiscal, importe_sin_iva o acrónimos contables no documentados. El patrón single-pass colapsa de forma estrepitosa cuando un usuario de finanzas formula una pregunta usando modismos locales como "cuánto cobramos de anticipos esta quincena".
El Pass 1 actúa como una barrera semántica indispensable. Permite incorporar en el prompt reglas de negocio específicas de la región sin alterar la base de datos subyacente. Por ejemplo, definir qué significa un corte quincenal en México (días 15 y último día del mes) o mapear conceptos impositivos locales sin exponer credenciales ni esquemas completos al exterior, cumpliendo de mejor manera con regulaciones de privacidad de datos locales como la Ley Federal de Protección de Datos Personales en Posesión de los Particulares (LFPDPPP).
include profundo sin discriminación, la base de datos sufrirá bloqueos de I/O. Limita en el spec los joins a un nivel máximo de profundidad y prioriza select explícitos sobre colecciones completas.take por defecto (por ejemplo, 50 registros) si el usuario no especificó un tope.¿Tu NL-to-SQL actual valida contra el schema antes de ejecutar, o confía en que el SQL "se ve correcto"? Si es lo segundo, ya tienes resultados incorrectos en producción; solo no los has detectado.
Artículos relacionados
¿Quién autoriza a un agente? Permisos automáticos, aprobación humana y dos turnos
Claude, OpenAI y Microsoft ya traen controles para decidir qué ejecuta un agente. Los comparamos con el nuestro: la llave identifica y el código autoriza.
Traefik v2.10 con auto-renewal certs para 94 containers
Wildcard *.scram2k.com cubre la mayoría, certs individuales para el resto. acme.json shared, DNS-01 para wildcards, HTTP-01 para subdomains. Anti-patrón: cert por container.
OpenTelemetry tracing para pipelines LLM
Domina el tracing de pipelines LLM con OpenTelemetry y GenAI semantic conventions. Optimiza latencias en producción y evita sobrecostos con tail sampling.