ILe procès du SQL généré

Le modèle ne devrait pas
écrire le SQL.

Le text-to-SQL est la voie évidente pour pointer un agent vers une base de données, et la démo est franchement impressionnante. C'est en production que tout bascule : la même question compile en SQL différent, une erreur de jointure silencieuse déplace le chiffre d'affaires de 15 %, et la sortie du modèle devient une surface d'attaque. Il existe une autre voie.

IITransparence d'abord

Le Text-to-SQL mérite sa réputation. Dans son domaine.

Là où c'est le bon outil

  • Prototypes sur des données jetables
  • Exploration d'un schéma inconnu, sous supervision humaine
  • Questions ponctuelles qu'un humain vérifiera
  • La démo — il excelle vraiment en démo

Là où ça casse

  • Des chiffres sur lesquels on va agir
  • Du texte non fiable n'importe où dans le contexte
  • Des identifiants de production sur la connexion
  • Des réponses qui doivent être reproductibles ou auditables
  • Des agents qui s'exécutent sans supervision

Le pattern n'est pas en cause. Le rayon de souffle, si. Chaque risque de cette page découle d'un seul choix de conception : la sortie du modèle est exécutée comme du code.

IIILes deux voies

Même question. Deux architectures.

Suivez une question sur les deux voies — une chaîne SQL libre à gauche, un plan typé à droite. Les numéros renvoient au registre des risques ci-dessous.

la question

Quelle région affiche le chiffre d'affaires total le plus élevé ?

Voie A

Le modèle écrit le SQL

  1. le modèle improvise une chaîne 010304

    Tout ce que le modèle a lu — un message utilisateur, une ligne récupérée — peut façonner cette chaîne.

    exécution 1SELECT region, SUM(total) FROM orders GROUP BY region;exécution 2 · même questionSELECT o.region, SUM(i.amount) FROM orders o LEFT JOIN order_items i ON i.order_id = o.id GROUP BY 1;
  2. votre connexion l'exécute 05

    Avec tous les droits de la connexion. Lecture, jointure — et sauf si quelqu'un a pensé à l'option, écriture.

la réponse

run 1 → east · 2130.50

run 2 → east · 2450.08+15% — fan-out de jointure 02

Deux exécutions, deux chiffres. Les deux plausibles. Aucun signal pour savoir lequel — si tant est que l'un d'eux — est juste.

Voie B

Le modèle soumet un plan typé

  1. intention typée

    Pas une chaîne — une valeur avec un schéma. Elle ne peut nommer que les opérations définies par le contrat.

    { "kind": "query", "version": "1", "source": "revenue", "op": "sum", "group_by": "region" }
  2. vérification du contrat

    Ancré par hash. Une opération inconnue retourne unsupported_operation avec les correspondances les plus proches — jamais une supposition.

  3. vérification de politique

    Votre liste d'autorisation au niveau du code. Une requête peut restreindre la surface, jamais l'élargir.

  4. exécution déterministe

    float64, thread unique, runtime ancré. Même plan, mêmes octets, même réponse.

la réponse

east · 2130.50

plan_hash f87610d8afeb…

decision_path "exact_spec"

Une seule réponse, accompagnée des hashes pour la rejouer — la semaine prochaine, le trimestre suivant, dans l'un ou l'autre langage.

IVLe registre des risques

Cinq façons dont le SQL généré déraille

  1. 01

    P2SQLarXiv 2308.01990

    L'injection déplacée vers le canal de sortie

    La désinfection des entrées inspecte ce qui entre dans le modèle. Les attaques P2SQL arrivent dans ce qui en sort : du SQL bien formé et malveillant, assemblé à partir d'instructions dissimulées dans un message utilisateur ou une ligne récupérée. Aucun filtre d'entrée ne le voit jamais — l'attaque, c'est la sortie.

  2. 02

    −15%le tableau de bord qui mentait

    Les mauvaises jointures échouent en silence

    Une jointure avec fan-out double-compte les lignes, et le chiffre d'affaires affiche 15% d'écart. Aucune exception, aucun avertissement — un SQL erroné ne plante pas, il rapporte. Le résultat est toujours un nombre, et un nombre plausible ne porte aucun signal indiquant qu'il est faux.

  3. 03

    1 → nune question, n requêtes

    Même question, SQL différent

    Posez la question deux fois et le modèle peut la compiler de deux façons différentes — parfois vers deux réponses différentes. Il n'existe pas de requête canonique à examiner, mettre en cache ou rejouer. Le chiffre d'hier ne peut pas être reproduit, même simplement pour le vérifier.

  4. 04

    91,2 → 21,3% correct · benchmark → schéma d'entreprise

    La falaise de précision

    Sur des schémas de benchmark propres, un modèle de pointe a produit du SQL correct dans 91,2% des cas. Sur des schémas d'entreprise réels : 21,3%. Dans la même vague de recherche, environ 40% des exécutions d'agents text-to-SQL ont échoué complètement ou retourné des résultats incorrects. Les benchmarks sont ordonnés. Votre schéma ne l'est pas.

  5. 05

    1 base de prodsupprimée par un agent — l'incident Replit

    Le chemin d'écriture a toujours existé

    L'agent de code de Replit a supprimé une base de données de production malgré des instructions explicites de ne pas y toucher. La leçon est là : une instruction en lecture seule est une requête. Si la connexion peut écrire, le chemin d'écriture existe, et une mauvaise complétion finit toujours par l'emprunter. La lecture seule doit être une propriété de l'outil, pas une ligne dans le prompt.

Ne filtrez pas la sortie.
Ne la générez pas.

Pas de chaîne SQL, pas d'injection · pas de conjecture, pas de dérive

VLe certificat

Cinq dimensions, côte à côte

Pas un générateur LLM-vers-SQLREADME du moteur

Certificat de différence

surface d'authoringSQL généréune chaîne SQL libreplan typéune intention typée et versionnée
surface d'exécutionSQL générétout ce que la connexion autoriseplan typé4 574 capacités en lecture seule
chemin d'écritureSQL généréprésent sauf blocage expliciteplan typéabsent, par construction
gouvernanceSQL généréau niveau du prompt, au mieuxplan typélistes d'autorisation au niveau du code, non extensibles par le modèle
reproductibilitéSQL généréaucuneplan typéun hash de rejeu sur chaque résultat

colonne droite ancrée au contratsha256:79f1c5a6…924be9a1

Les petits caractères tiennent la route : la seule échappatoire du moteur — getUnsafeRuntime — est absente de toutes les surfaces exposées au modèle, qui ne peut donc pas l'atteindre. Et le README énonce la doctrine sans détour : ceci n'est pas un générateur LLM-vers-SQL.

VIQuestions

Posées en production

Comment bloquer DELETE et DROP dans du SQL généré par un LLM ?

Ne filtrez pas le SQL — cessez d'en générer. Les listes de refus inspectent des chaînes, et les modèles sont infiniment inventifs pour en produire de nouvelles. SQAI supprime la classe entière : le modèle dépose un plan typé contre 4 574 capacités en lecture seule, et aucune capacité d'écriture n'existe qu'un plan puisse nommer.

Qu'est-ce que l'injection P2SQL ?

L'injection prompt-vers-SQL : du SQL malveillant qui apparaît dans la sortie du modèle, assemblé à partir d'instructions dissimulées dans ce que le modèle a lu — un message utilisateur, un document, une ligne récupérée. La désinfection des entrées ne le voit jamais, car l'attaque réside dans le canal de sortie. Elle est documentée dans arXiv 2308.01990.

Le text-to-SQL est-il parfois le bon choix ?

Oui — pour les prototypes et l'exploration supervisée sur des données hors production, c'est rapide et réellement utile. C'est le mauvais choix dès lors que le résultat sera actionné, que le contexte contient du texte non fiable, ou que la réponse doit être reproductible.

Comment donner à un agent IA un accès en lecture seule à une base de données ?

Rendez la lecture seule structurelle, pas configurée. Un indicateur de lecture seule sur une connexion est un paramètre que quelqu'un peut modifier. La surface d'exécution de SQAI ne contient que des capacités de lecture — le chemin d'écriture est absent par construction, et même l'échappatoire du moteur est inaccessible depuis tout outil exposé au modèle.

En quoi un plan typé diffère-t-il du SQL généré ?

Une chaîne SQL peut exprimer tout ce que la grammaire autorise. Un plan typé ne peut exprimer que ce que le contrat définit. Chaque plan est validé contre un contrat ancré par hash, vérifié contre votre politique de liste d'autorisation, et exécuté sur un moteur déterministe — avec un hash de rejeu sur chaque résultat.

Prenez l'autre voie.

Pas de compte. Pas de clé. Les données locales restent locales.