· 10 min di lettura

Come abbiamo reso gli LLM economici affidabili su DynamoDB: validatori di strumenti + eval

L'assistente AI di DynoTable gira sulle tue credenziali , il che significa che scegli tu il modello — e molte persone ne scelgono uno economico. Quindi non tariamo l'assistente su un modello di frontiera. Il nostro pavimento è Amazon Nova Lite, un modello che costa un errore di arrotondamento per query e fallisce in modi in cui i modelli di frontiera non falliscono.

Ecco la morale di tutto questo articolo: su uno scenario — un join che ha bisogno della tabella a chiave composita come base — Nova Lite ha ottenuto lo 0%. Non abbiamo cambiato modello, non abbiamo aggiunto esempi few-shot, non abbiamo fatto fine-tuning. Abbiamo riscritto il messaggio di errore di un validatore perché dicesse esattamente cosa fare invece. È passato al 100%, ribaltando il join proprio al passo successivo.

Il modello non è mai stato il collo di bottiglia. Lo era il messaggio.

Questa è la storia di ingegneria dietro quella lezione: i validatori che stanno attorno a ogni chiamata di strumento, le cose che deliberatamente non validiamo, e l'eval harness che arbitra ogni cambiamento. Se stai costruendo un su un modello economico — su DynamoDB o su qualsiasi altra cosa — gran parte di tutto ciò è trasferibile.

Perché i modelli economici falliscono proprio su DynamoDB

DynamoDB è un bersaglio ostile per un modello piccolo perché la superficie di query sembra SQL e non lo è. PartiQL accetta la sintassi SELECT ma non supporta né JOIN, né GROUP BY, né DISTINCT, né LIKE — e un modello che ha imparato SQL da internet li emette tutti con sicurezza.

I fallimenti che abbiamo effettivamente osservato, ciascuno da un'esecuzione reale registrata:

  • Argomenti limit scartati. I omettono di routine il limite di righe opzionale su uno strumento di query. Il backend restituisce allegramente tutto, un risultato di strumento da 200 KB atterra nella conversazione, e il turno successivo del modello muore sul soffitto di contesto — una ValidationException a metà ciclo, imputata al componente sbagliato.
  • Costrutti non supportati. JOIN, GROUP BY, aggregati in PartiQL — istruzioni che DynamoDB non eseguirà mai, emesse perché sarebbero corrette su Postgres.
  • Scambi di identificatori. Nova Lite, dato SELECT * FROM customers e l'istruzione «aggiungi un WHERE per la Germania», a volte restituiva SELECT * FROM orders WHERE … — una tabella che l'utente non aveva mai menzionato. L' istruzione si parsa perfettamente. Niente di sintattico la intercetta.
  • Instradamento errato degli strumenti. Interrogato su un file allegato, Nova Lite chiamava lo strumento di elenco delle schede aperte cercando file nelle schede — usava di rado il meccanismo di ricerca degli strumenti, così qualsiasi cosa non direttamente visibile non esisteva.

Nessuno di questi è ipotetico. Ciascuno ha spinto un guardrail specifico qui sotto.

Valida al confine dello strumento, e rendi l'errore una lezione

La singola decisione architetturale più importante: la validazione vive dentro lo step di esecuzione dello strumento, non nello schema di input. Un rifiuto di schema è un vicolo cieco — il ciclo vede un errore di tipo e non impara nulla. Uno strumento che esegue, verifica e restituisce un errore strutturato dà al modello un normale risultato di strumento a cui può reagire al passo successivo.

E quell'errore è scritto per il modello, non per un log umano. Gli errori del nostro strumento di esportazione recitano così, alla lettera:

startExport requires exactly one of {tabId} or {sql}.
FIX: call listOpenTabs and pass {tabId}, or pass {sql} with a single SELECT.
startExport {sql} must be a read-only single SELECT.
FIX: remove any INSERT/UPDATE/DELETE/DDL and pass one SELECT statement.

I fallimenti di validazione delle query portano un prefisso validation:validation: parse-error: …, validation: partiql-unsupported: JOIN is not supported by DynamoDB PartiQL. — che il system prompt stabilisce come il segnale per correggere l'istruzione anziché riprovare lo stesso input. Un messaggio di rifiuto è un segnale didattico. Prima di adottare la convenzione FIX:, gli stessi fallimenti finivano con il modello che si scusava e diceva all'utente di fare clic lui stesso sul pulsante di esportazione. Dopo, si autocorregge e completa il compito.

Il corollario ha richiesto più tempo per essere imparato: dove vive l'esempio concreto è un asse di progettazione. Se un errore è intercettato da un validatore a runtime, mantieni il system prompt generico e lascia che il messaggio d'errore dinamico porti le tabelle specifiche e la correzione — il prompt resta piccolo e la lezione arriva esattamente quando serve. Solo gli errori che nessun validatore può intercettare (come instradare una domanda di aggregazione allo strumento giusto in primo luogo) si guadagnano un esempio concreto nel prompt stesso.

DynamoDBTool boundaryModelDynamoDBTool boundaryModelrunPartiQL("SELECT … JOIN …")validation: partiql-unsupported:JOIN is not supported by DynamoDB PartiQL.runWorkbenchSql("SELECT … JOIN …")streamed pages (capped)rows10 rows / 5 KB + "Showing 10 of 4,200…"

Parsa le cose che hai intenzione di rifiutare

Rifiutare JOIN con un generico syntax error at offset 27 non insegna nulla. Perciò il nostro parser PartiQL scritto a mano fa qualcosa di lievemente perverso: parsa deliberatamente in un albero sintattico valido i costrutti che DynamoDB non supporta — solo perché un walker possa emettere una diagnostica specifica e insegnabile. Tutto alla lettera, tutto rivolto al modello:

  • JOIN is not supported by DynamoDB PartiQL.
  • GROUP BY is not supported by DynamoDB PartiQL.
  • TOP N is not supported. Use the API limit parameter.
  • IN list has 120 items. DynamoDB PartiQL caps IN at 50 values (PK column) or 100 values (non-key column).
  • IS NOT NULL is not supported in DynamoDB PartiQL. Use attribute_exists.
  • lower(path) is not a DynamoDB PartiQL function. Use … instead. — con la sostituzione risolta per funzione.

Ogni messaggio nomina il costrutto e la via d'uscita. Abbinato alla regola del prompt «se PartiQL rifiuta un costrutto non supportato, passa al workbench SQL immediatamente», un modello economico si riprende dai suoi istinti da Postgres in un solo passo invece di annaspare.

Sappi cosa NON validare

Il livello schema-aware del nostro validatore SQL risolve i riferimenti a tabelle e colonne rispetto alle vere descrizioni di tabella che l'app già possiede. All'inizio segnalava anche i riferimenti ad attributi che DynamoDB non dichiarava — il che sembra giusto ed è esattamente sbagliato. Gli attributeDefinitions di DynamoDB elencano solo gli attributi chiave, così il controllo rifiutava WHERE <non-key> = … — la forma di query legittima più comune in assoluto. Quel controllo ora è un warning che non blocca mai.

Ogni validatore è una scommessa che la forma rifiutata sia più probabilmente errata che corretta. Quando il modello dei dati non può sostenere il controllo, non fare la scommessa.

I limiti sui risultati come comportamento, non solo come sicurezza

La correzione per l'esplosione del limit scartato non è stata «aggiungere il limite al prompt» (i modelli economici lo scartano comunque). I risultati degli strumenti di query sono limitati con decisione — 10 righe o 5 KB, quello che viene prima, mantenendo sempre almeno la prima riga — e l'avviso di troncamento è a sua volta prescrittivo:

Showing 10 of 4,200. To filter: re-run with WHERE.
To view in UI: emit proposeOpenTable.
To aggregate: use runWorkbenchSql GROUP BY.

I limiti sono intenzionalmente ben al di sotto di ciò che il contesto potrebbe contenere. L'obiettivo è comportamentale: costringere il modello a consegnare all'utente una vera vista di tabella o un aggregato esatto, mai a enumerare righe nella chat. Il suggerimento di recupero nomina gli strumenti esatti, così il limite insegna l'instradamento nel momento in cui il modello ne ha bisogno. Lo stesso involucro è rispecchiato byte per byte nell'eval harness, e uno scenario di regressione fissa truncated: true contro una tabella da 10.000 elementi così che il limite non possa mai sparire in silenzio.

Invece di enumerare righe nella chat, l'assistente emette un chip su cui fai clic per aprire il risultato come una vera scheda di DynoTable.
Invece di enumerare righe nella chat, l'assistente emette un chip su cui fai clic per aprire il risultato come una vera scheda di DynoTable.

Gli eval sono l'arbitro

Nulla di quanto sopra è stato progettato per intuizione e mantenuto per fede. Ogni clausola del prompt, messaggio di validatore e limite è vagliato da una suite di eval che esegue veri cicli di agente contro un DynamoDB Local seminato — strumenti reali, esecuzione reale delle query, i costruttori di prompt e i validatori di produzione importati direttamente anziché reimplementati — valutati da controlli binari sulla traccia degli strumenti, sullo stato finale del database e sul testo.

Cosa hanno intercettato gli eval che l'intuizione ha mancato:

  • Un prompt reso «più pulito» e generico ha fatto regredire uno scenario dal 100% allo 0%. A noi si leggeva meglio. Il modello pavimento non era d'accordo. Solo l'eval se n'è accorto.
  • I consigli sui prompt dei modelli di frontiera sono attivamente sbagliati per i modelli economici. Le linee guida pubblicate dicono di ammorbidire il linguaggio CRITICAL/MUST perché i modelli grandi vi si sovreccitano. I modelli economici sotto-obbediscono e hanno bisogno della versione ferma. Ora vagliamo con eval qualsiasi consiglio preso in prestito invece di applicarlo per fede.
  • Asserzioni di strumento rigide punivano il comportamento corretto. Uno scorer che richiedeva al modello di chiamare lo strumento ask per un chiarimento passava il 19% lungo la scala dei modelli — quasi ogni «fallimento» era un modello che aveva correttamente chiesto un chiarimento nel testo. Lo scorer ora accetta entrambi. Valuta l'intento rivolto all'utente, non il meccanismo.
  • La scala dei modelli si è guadagnata la sua riduzione. Siamo partiti ampi — Mistral, AI21, GLM, Qwen, Nova Pro, altri — e l'abbiamo tagliata a due pioli (Nova Lite, Claude Haiku) per motivi che solo le esecuzioni reali fanno emergere: una famiglia costava ~10× per chiamata perché Bedrock non le offriva alcun prompt caching; Nova Pro allucinava nomi di attributi concatenati come customers.customerName sui join a domanda aperta. E una famiglia ha dovuto essere messa del tutto in blocklist — l'API unificata di Bedrock accetta allegramente definizioni di strumenti per modelli il cui adapter poi emette le chiamate di funzione come testo semplice, qualcosa che i metadati dell'API non ti diranno. L'abbiamo scoperto con una sonda smoke a pagamento, non con una spec.

Due discipline mantengono la suite onesta. Le tabelle degli esempi non sono mai uguali alle tabelle di test: gli esempi del prompt usano nomi neutri che non esistono nei dati seminati, e gli eval girano su tabelle diverse — così un successo dimostra che il modello ha generalizzato il pattern, non che ha memorizzato il nostro esempio. E la modalità watch a $0 rifiuta le fixture registrate: gli output di modello riprodotti supererebbero in verde asserzioni che dipendono da un'attività reale degli strumenti, così il livello gratuito valida solo il cablaggio, e qualsiasi cosa reclami un punteggio ha dovuto spendere token reali su un modello reale.

La checklist, se ne stai costruendo uno

  • Scegli prima il tuo modello pavimento e valuta sempre rispetto a lui; i modelli di frontiera nascondono i tuoi bug.
  • Metti la validazione nello step di esecuzione dello strumento; restituisci errori su cui il ciclo può agire, mai vicoli ciechi di schema.
  • Scrivi ogni rifiuto come cosa c'è di sbagliato + FIX: azione successiva esatta, e interpola le vere tabelle dell'utente al suo interno.
  • Parsa ciò che intendi rifiutare, così che la diagnostica possa essere specifica.
  • Verifica ogni validatore rispetto al tuo modello dei dati — elimina quelli che rifiutano forme legittime.
  • Limita i risultati degli strumenti al di sotto di ciò che entra, e fa' che l'avviso di troncamento instradi il modello verso lo strumento successivo giusto.
  • Esegui gli eval come veri cicli contro un backend reale (locale), riusando i percorsi di codice di produzione; mantieni i dati degli esempi disgiunti dai dati di test; rendi l'eval — non il gusto — l'arbitro di ogni cambiamento di prompt e messaggio.

Qualcosa di tutto ciò conta se usi un modello di frontiera?

Meno spesso, ma sì. I validatori sono portanti a ogni livello — un modello di frontiera comunque non può eseguire JOIN su DynamoDB, e gli errori prescrittivi vengono semplicemente consumati più in fretta. Nelle nostre esecuzioni il comportamento di emissione dei chip di un modello di classe frontiera è una volta sceso allo 0% sotto una formulazione di prompt che i modelli più piccoli gestivano — anche quello l'eval harness l'ha intercettato. L'ingegneria dell'affidabilità non è una tassa sui modelli piccoli; i modelli piccoli fanno solo arrivare la fattura prima.

Dove gira tutto questo

Tutto questo viene distribuito dentro l'assistente di DynoTable e il suo catalogo di strumenti protetto: l'interrogazione in linguaggio naturale schema-aware sulle tue credenziali Bedrock, aggregati esatti sull'intera tabella e scritture che atterrano sempre e solo in un'area di staging revisionabile. Gli agenti esterni ottengono lo stesso toolkit validato tramite il server MCP — come abbiamo costruito quello in sicurezza è una storia a sé, da OAuth all'isolamento delle credenziali.

E quando vuoi il determinismo invece della generazione — la query che consegnerai al codice — salta del tutto il modello: l' Expression Builder assembla la richiesta esatta a mano.

Lavora con DynamoDB senza la Console

DynoTable è un client desktop veloce per DynamoDB — sfoglia le tabelle, esegui query in stile SQL e modifica gli Item localmente.