Wie wir günstige LLMs bei DynamoDB zuverlässig gemacht haben: Tool-Validatoren + Evals
DynoTables KI-Assistent läuft auf deinen eigenen -Zugangsdaten, was bedeutet, dass du das Modell wählst — und viele Leute wählen ein günstiges. Deshalb stimmen wir den Assistenten nicht auf ein Frontier-Modell ab. Unsere Untergrenze ist Amazon Nova Lite, ein Modell, das pro Query einen Rundungsfehler kostet und auf eine Weise scheitert, wie es Frontier-Modelle nicht tun.
Hier ist die Pointe dieses ganzen Beitrags: In einem -Szenario — ein Join, der die Tabelle mit dem zusammengesetzten Schlüssel als Basis braucht — erreichte Nova Lite 0 %. Wir haben nicht das Modell gewechselt, keine Few-Shot-Beispiele hinzugefügt, nicht feinjustiert. Wir haben eine einzige Validator-Fehlermeldung umgeschrieben, damit sie genau sagt, was stattdessen zu tun ist. Sie ging auf 100 %, und der Join klappte im allernächsten Schritt.
Das Modell war nie der Engpass. Die Nachricht war es.
Das ist die Engineering-Geschichte hinter dieser Lektion: die Validatoren, die um jeden Tool-Aufruf herum sitzen, die Dinge, die wir bewusst nicht validieren, und das Eval-Harness, das über jede Änderung entscheidet. Wenn du einen auf einem Budget-Modell baust — über DynamoDB oder irgendetwas anderes — lässt sich das meiste davon übertragen.
Warum günstige Modelle gerade bei DynamoDB scheitern
DynamoDB ist ein feindliches Ziel für ein kleines Modell, weil die Query-Oberfläche
wie SQL aussieht und es nicht ist. PartiQL akzeptiert
SELECT-Syntax, unterstützt aber kein JOIN, kein GROUP BY, kein DISTINCT,
kein LIKE — und ein Modell, das SQL aus dem Internet gelernt hat, gibt sie alle
selbstbewusst aus.
Die Fehler, die wir tatsächlich beobachtet haben, jeder aus einem echten protokollierten Lauf:
- Weggelassene
limit-Argumente. lassen das optionale Zeilenlimit bei einem Query-Tool routinemäßig weg. Das Backend gibt bereitwillig alles zurück, ein 200 KB großes Tool-Ergebnis landet in der Konversation, und der nächste Modell-Zug stirbt an der Kontextgrenze — eineValidationExceptionmitten in der Schleife, der falschen Komponente angelastet. - Nicht unterstützte Konstrukte.
JOIN,GROUP BY, Aggregate in PartiQL — Statements, die DynamoDB nie ausführen wird, ausgegeben, weil sie auf Postgres korrekt wären. - Vertauschte Bezeichner. Nova Lite gab, ausgehend von
SELECT * FROM customersund der Anweisung „füge ein WHERE für Deutschland hinzu“, manchmalSELECT * FROM orders WHERE …zurück — eine Tabelle, die der Nutzer nie erwähnt hat. Das Statement parst einwandfrei. Nichts Syntaktisches fängt es ab. - Falsches Tool-Routing. Nach einer angehängten Datei gefragt, rief Nova Lite das Tool zur Auflistung offener Tabs auf und suchte Dateien in Tabs — es nutzte den Tool-Suchmechanismus kaum, sodass alles, was nicht direkt sichtbar war, nicht existierte.
Nichts davon ist hypothetisch. Jeder Fall hat eine bestimmte Leitplanke weiter unten motiviert.
Validiere an der Tool-Grenze und mach den Fehler zur Lektion
Die mit Abstand wichtigste Architekturentscheidung: Die Validierung lebt im Execute-Schritt des Tools, nicht im Input-Schema. Eine Schema-Ablehnung ist eine Sackgasse — die Schleife sieht einen Typfehler und lernt nichts. Ein Tool, das läuft, prüft und einen strukturierten Fehler zurückgibt, liefert dem Modell ein normales Tool-Ergebnis, auf das es im nächsten Schritt reagieren kann.
Und dieser Fehler ist für das Modell geschrieben, nicht für ein menschliches Log. Die Fehler unseres Export-Tools lesen sich so, wörtlich:
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.Query-Validierungsfehler tragen einen validation:-Präfix —
validation: parse-error: …, validation: partiql-unsupported: JOIN is not supported by DynamoDB PartiQL. —, den der System-Prompt als Signal etabliert, das
Statement zu korrigieren, statt dieselbe Eingabe erneut zu versuchen. Eine
Ablehnungsnachricht ist ein Lehrsignal. Bevor wir die FIX:-Konvention eingeführt
haben, endeten dieselben Fehler damit, dass sich das Modell entschuldigte und dem
Nutzer sagte, er solle den Export-Button selbst klicken. Danach korrigiert es sich
selbst und schließt die Aufgabe ab.
Die Folgerung zu lernen dauerte länger: Wo das konkrete Beispiel lebt, ist eine Design-Achse. Wird ein Fehler zur Laufzeit von einem Validator abgefangen, halte den System-Prompt generisch und lass die dynamische Fehlermeldung die konkreten Tabellen und den Fix tragen — der Prompt bleibt klein und die Lektion kommt genau dann, wenn sie gebraucht wird. Nur Fehler, die kein Validator abfangen kann (wie eine Aggregat-Frage überhaupt an das richtige Tool zu leiten), verdienen ein konkretes Beispiel im Prompt selbst.
Parse die Dinge, die du ablehnen wirst
JOIN mit einem generischen syntax error at offset 27 abzulehnen lehrt nichts.
Also tut unser handgeschriebener PartiQL-Parser etwas leicht Perverses: Er parst
bewusst Konstrukte, die DynamoDB nicht unterstützt, in einen gültigen Syntaxbaum
— nur damit ein Walker eine spezifische, lehrreiche Diagnose ausgeben kann. Alles
wörtlich, alles modellseitig:
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.— mit dem pro Funktion aufgelösten Ersatz.
Jede Nachricht nennt das Konstrukt und den Ausweg. Gepaart mit der Prompt-Regel „wenn PartiQL ein nicht unterstütztes Konstrukt ablehnt, wechsle sofort zur SQL-Workbench“ erholt sich ein günstiges Modell in einem Schritt von seinen Postgres-Instinkten, statt sich festzufahren.
Wisse, was NICHT zu validieren ist
Die schema-bewusste Ebene unseres SQL-Validators löst Tabellen- und
Spaltenreferenzen gegen die echten Tabellenbeschreibungen auf, die die App bereits
hält. Anfangs markierte sie auch Referenzen auf Attribute, die DynamoDB nicht
deklariert hatte — was richtig klingt und genau falsch ist. DynamoDBs
attributeDefinitions listet nur Schlüssel-Attribute, deshalb lehnte die
Prüfung WHERE <non-key> = … ab — die mit Abstand häufigste legitime Query-Form
überhaupt. Diese Prüfung ist jetzt eine Warnung, die nie blockiert.
Jeder Validator ist eine Wette, dass die abgelehnte Form eher falsch als richtig ist. Wenn das Datenmodell die Prüfung nicht tragen kann, geh die Wette nicht ein.
Ergebnis-Limits als Verhalten, nicht nur als Sicherung
Der Fix für den durch das weggelassene limit verursachten Ausfall war nicht „füge
das Limit dem Prompt hinzu“ (günstige Modelle lassen es ohnehin weg).
Query-Tool-Ergebnisse sind hart begrenzt — 10 Zeilen oder 5 KB, je nachdem, was
zuerst kommt, wobei immer mindestens die erste Zeile erhalten bleibt — und der
Kürzungshinweis ist selbst präskriptiv:
Showing 10 of 4,200. To filter: re-run with WHERE.
To view in UI: emit proposeOpenTable.
To aggregate: use runWorkbenchSql GROUP BY.Die Limits liegen bewusst weit unter dem, was der Kontext fassen könnte. Das Ziel
ist verhaltensbezogen: das Modell zwingen, dem Nutzer eine echte Tabellenansicht
oder ein exaktes Aggregat zu übergeben, statt Zeilen in den Chat aufzuzählen. Der
Recovery-Hinweis nennt die exakten Tools, sodass das Limit das Routing genau in dem
Moment lehrt, in dem das Modell es braucht. Dieselbe Hülle wird im Eval-Harness
Byte für Byte gespiegelt, und ein Regressionsszenario fixiert truncated: true
gegen eine Tabelle mit 10.000 Items, sodass das Limit nie stillschweigend
verschwinden kann.

Evals sind der Schiedsrichter
Nichts davon wurde aus dem Bauch heraus entworfen und auf gut Glück beibehalten. Jede Prompt-Klausel, jede Validator-Nachricht und jedes Limit wird von einer Eval-Suite abgesichert, die echte Agentenschleifen gegen ein geseedetes DynamoDB Local ausführt — echte Tools, echte Query-Ausführung, die Produktions-Prompt-Builder und -Validatoren direkt importiert statt neu implementiert — bewertet durch binäre Prüfungen des Tool-Traces, des Endzustands der Datenbank und des Texts.
Was die Evals aufdeckten, das die Intuition übersah:
- Eine „sauberere“ Verallgemeinerung des Prompts ließ ein Szenario von 100 % auf 0 % zurückfallen. Für uns las es sich besser. Das Untergrenzen-Modell sah das anders. Nur die Eval bemerkte es.
- Prompt-Ratschläge für Frontier-Modelle sind für günstige Modelle aktiv falsch. Veröffentlichte Leitlinien sagen, man solle CRITICAL/MUST-Formulierungen abschwächen, weil große Modelle darauf überreagieren. Günstige Modelle befolgen sie zu wenig und brauchen die feste Version. Wir sichern übernommene Ratschläge jetzt per Eval ab, statt sie auf gut Glück anzuwenden.
- Strikte Tool-Assertions bestraften korrektes Verhalten. Ein Scorer, der
verlangte, dass das Modell das
ask-Tool zur Klärung aufruft, bestand über die Modell-Leiter hinweg zu 19 % — fast jeder „Fehlschlag“ war ein Modell, das korrekt im Text um Klärung gebeten hatte. Der Scorer akzeptiert jetzt beides. Bewerte die nutzerseitige Absicht, nicht den Mechanismus. - Die Modell-Leiter hat ihre Verschmälerung verdient. Wir starteten breit —
Mistral, AI21, GLM, Qwen, Nova Pro und mehr — und kürzten sie auf zwei Sprossen
(Nova Lite, Claude Haiku) aus Gründen, die nur echte Läufe zutage fördern: Eine
Familie kostete pro Aufruf rund das 10-Fache, weil Bedrock ihr kein
Prompt-Caching anbot; Nova Pro halluzinierte verkettete Attributnamen wie
customers.customerNamebei offenen Joins. Und eine Familie musste komplett auf die Blockliste — Bedrocks einheitliche API akzeptiert bereitwillig Tool-Definitionen für Modelle, deren Adapter Function Calls dann als reinen Text ausgibt, etwas, das dir die API-Metadaten nicht verraten. Wir fanden es mit einem bezahlten Smoke-Test, nicht mit einer Spezifikation.
Zwei Disziplinen halten die Suite ehrlich. Beispiel-Tabellen sind nie gleich den Test-Tabellen: Prompt-Beispiele verwenden neutrale Namen, die in den Seed-Daten nicht existieren, und die Evals laufen auf anderen Tabellen — sodass ein Bestehen beweist, dass das Modell das Muster verallgemeinert hat, nicht dass es unser Beispiel auswendig gelernt hat. Und der 0-Dollar-Watch-Modus verweigert aufgezeichnete Fixtures: Wiedergegebene Modell-Ausgaben würden Assertions grün durchwinken, die von echter Tool-Aktivität abhängen, deshalb validiert die kostenlose Stufe nur die Verdrahtung, und alles, was einen Score beansprucht, musste echte Tokens an einem echten Modell ausgeben.
Die Checkliste, falls du so etwas baust
- Wähle zuerst dein Untergrenzen-Modell und evaluiere immer gegen es; Frontier-Modelle verbergen deine Bugs.
- Leg die Validierung in den Execute-Schritt des Tools; gib Fehler zurück, auf die die Schleife reagieren kann, nie Schema-Sackgassen.
- Schreibe jede Ablehnung als
what's wrong+FIX: exact next actionund interpoliere die tatsächlichen Tabellen des Nutzers hinein. - Parse, was du abzulehnen gedenkst, damit die Diagnose spezifisch sein kann.
- Prüfe jeden Validator gegen dein Datenmodell — lösche die, die legitime Formen ablehnen.
- Begrenze Tool-Ergebnisse unter das, was passt, und lass den Kürzungshinweis das Modell zum richtigen nächsten Tool leiten.
- Führe Evals als echte Schleifen gegen ein echtes (lokales) Backend aus und nutze Produktions-Codepfade wieder; halte Beispieldaten von Testdaten getrennt; mach die Eval — nicht den Geschmack — zur entscheidenden Instanz für jede Änderung an Prompt und Nachricht.
Spielt das alles eine Rolle, wenn du ein Frontier-Modell nutzt?
Seltener, aber ja. Die Validatoren sind auf jeder Stufe tragend — ein
Frontier-Modell kann JOIN auf DynamoDB immer noch nicht ausführen, und
präskriptive Fehler werden nur schneller verarbeitet. In unseren eigenen Läufen
fiel das Chip-Emissions-Verhalten eines Modells der Frontier-Klasse einmal auf
0 % unter einer Prompt-Formulierung, die kleinere Modelle bewältigten — das fing
das Eval-Harness ebenfalls ab. Zuverlässigkeits-Engineering ist keine Steuer für
kleine Modelle; kleine Modelle lassen die Rechnung nur früher eintreffen.
Wo das läuft
All das wird im Assistenten von DynoTable und seinem abgesicherten Tool-Katalog ausgeliefert: schema-bewusstes Abfragen in natürlicher Sprache auf deinen eigenen Bedrock-Zugangsdaten, exakte Aggregate über ganze Tabellen und Schreibvorgänge, die ausschließlich in einem überprüfbaren Staging-Bereich landen. Externe Agenten erhalten dasselbe validierte Toolkit über den MCP-Server — wie wir den sicher gebaut haben, ist eine eigene Geschichte, von OAuth bis zur Isolation der Zugangsdaten.
Und wenn du Determinismus statt Generierung willst — die Query, die du in Code festschreibst — überspringe das Modell ganz: Der Expression Builder setzt den exakten Request von Hand zusammen.


