· 10 分で読めます

安価な LLM を DynamoDB で確実に動かした方法: ツールバリデーター + eval

DynoTable の AI アシスタントはあなた自身の の認証情報上で動くので、モデルはあなたが選びます — そして 多くの人は安価なものを選びます。だから私たちはアシスタントをフロンティアモデルに合わせてチューニングしません。私たちの下限は Amazon Nova Lite です。1クエリあたり端数の丸め程度のコストで、フロンティアモデルにはない失敗の仕方をするモデルです。

この記事全体の要点を先に言うと — ある のシナリオ、つまりベースとして複合キーのテーブルを必要とする結合において、Nova Lite は 0% のスコアを出しました。私たちはモデルを切り替えず、few-shot の例も追加せず、ファインチューニングもしませんでした。代わりに何をすべきかを正確に伝えるよう、1つのバリデーターのエラーメッセージを書き直しただけです。それが 100% になり、ほんの次のステップで結合を反転させました。

モデルは一度もボトルネックではありませんでした。メッセージがそうだったのです。

これはその教訓の舞台裏にあるエンジニアリングの物語です — あらゆるツール呼び出しの周りに座るバリデーター、私たちがあえて 検証しない こと、そしてすべての変更を裁定する eval ハーネスについて。もしあなたが予算モデルの上でを構築しているなら — DynamoDB であれ、それ以外であれ — そのほとんどは応用が利きます。

なぜ安価なモデルは特に DynamoDB で失敗するのか

DynamoDB は小さなモデルにとって手強い標的です。クエリの表面が SQL に 見えて、そうではないからです。PartiQLSELECT 構文を受け付けますが、JOIN も、GROUP BY も、DISTINCT も、LIKE もサポートしません — そしてインターネットから SQL を学んだモデルは、それらすべてを自信たっぷりに吐き出します。

私たちが実際に観測した失敗を、それぞれ本物のログに残された実行から挙げます:

  • limit 引数の欠落。 は、クエリツールの任意の行数上限を日常的に省きます。バックエンドは平気ですべてを返し、200 KB のツール結果が会話に着地し、次のモデルのターンはコンテキストの上限で死にます — ループ途中の ValidationException が、間違ったコンポーネントのせいにされるのです。
  • サポートされない構文。 PartiQL における JOINGROUP BY、集計 — DynamoDB が決して実行しないステートメントで、Postgres なら正しいために吐き出されます。
  • 識別子の取り違え。 Nova Lite は、SELECT * FROM customers と「ドイツ向けの WHERE を追加して」という指示を与えられると、ときどき SELECT * FROM orders WHERE … を返しました — ユーザーが一度も言及していないテーブルです。そのステートメントは完璧にパースされます。構文的には何も引っかかりません。
  • ツールの誤ルーティング。 添付ファイルについて尋ねられると、Nova Lite はタブの中にファイルを探して開いているタブの一覧ツールを呼び出しました — ツール検索の仕組みをほとんど使わなかったので、直接見えないものは存在しないのと同じでした。

これらのどれも仮定の話ではありません。それぞれが下記の具体的なガードレールを生みました。

ツール境界で検証し、エラーを教訓にする

最も重要なアーキテクチャ上の決定は — 検証は入力スキーマではなく、ツールの実行ステップの内側に置く ということです。スキーマの拒否は行き止まりです — ループは型エラーを見て、何も学びません。実行し、チェックし、構造化されたエラーを返すツールは、次のステップで反応できる通常のツール結果をモデルに与えます。

そしてそのエラーは、人間のログのためではなく、モデルのために書かれています。私たちのエクスポートツールのエラーは、一字一句こう読めます:

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.

クエリの検証失敗には validation: の接頭辞が付きます — validation: parse-error: …validation: partiql-unsupported: JOIN is not supported by DynamoDB PartiQL. — システムプロンプトはこれを、同じ入力で再試行するのではなく ステートメントを修正する シグナルとして確立します。拒否メッセージは教育のシグナルです。FIX: の慣習を採用する前は、同じ失敗はモデルがユーザーに謝り、自分でエクスポートボタンをクリックするよう伝えて終わっていました。採用後は、自己修正してタスクを完了します。

その系はもっと学ぶのに時間がかかりました — 具体的な例をどこに置くかは設計の軸である ということです。ある間違いが実行時にバリデーターで捕まえられるなら、システムプロンプトは汎用的なままにし、動的なエラーメッセージに具体的なテーブルと修正を運ばせます — プロンプトは小さいままで、教訓はまさに必要なときに届きます。どのバリデーターも捕まえられない 間違い(そもそも集計の質問を正しいツールにルーティングするようなもの)だけが、プロンプト自体の中に具体的な例を得るに値します。

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…"

拒否するものはパースする

JOIN を汎用的な syntax error at offset 27 で拒否しても何も教えられません。だから私たちの手書きの PartiQL パーサーは、少し倒錯したことをします — DynamoDB がサポートしない構文をあえて有効な構文木にパースする のです。ウォーカーが具体的で教育可能な診断を吐けるようにするためだけに。すべて一字一句、すべてモデルに向けたものです:

  • 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. — 置き換えは関数ごとに解決されます。

各メッセージは構文 抜け道の両方に名前を与えます。「PartiQL がサポートされない構文を拒否したら、ただちに SQL ワークベンチに切り替える」というプロンプトルールと組み合わせれば、安価なモデルは thrash するのではなく、Postgres の本能から1ステップで回復します。

検証しないものを知る

私たちの SQL バリデーターのスキーマを意識した層は、アプリがすでに保持している本物のテーブル記述に照らして、テーブルとカラムの参照を解決します。当初はさらに、DynamoDB が宣言していない属性への参照にもフラグを立てていました — 正しそうに聞こえて、まさに間違っています。DynamoDB の attributeDefinitionsキー 属性しか列挙しないので、そのチェックは WHERE <non-key> = … を拒否していました — 存在する中で最も一般的な正規のクエリ形状です。そのチェックは今では、決してゲートしない警告になっています。

すべてのバリデーターは、拒否された形状が正しいよりも間違っている可能性が高い、という賭けです。データモデルがそのチェックを支えられないなら、その賭けをしてはいけません。

安全策としてだけでなく、振る舞いとしての結果の上限

limit の欠落による破裂への修正は、「プロンプトに limit を追加する」ことではありませんでした(安価なモデルはどのみち省きます)。クエリツールの結果はハードに上限を設けられています — 10 行または 5 KB のいずれか先に来たほう で、常に少なくとも最初の行は残す — そして切り詰めの通知自体が指示的です:

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

この上限は、コンテキストが保持できる量よりも意図的にはるかに低く設定されています。目的は振る舞いにあります — モデルに、行をチャットに列挙するのではなく、本物のテーブルビューか正確な集計をユーザーに渡すよう強制する ことです。回復のヒントは正確なツールに名前を与えるので、上限はモデルがそれを必要とするまさにその瞬間にルーティングを教えます。同じエンベロープは eval ハーネスの中でバイト単位まで鏡写しにされ、あるリグレッションのシナリオは1万件のテーブルに対して truncated: true を固定するので、この上限が黙って消えることは決してありません。

行をチャットに列挙する代わりに、アシスタントはクリックすると結果を本物の DynoTable のタブとして開けるチップを吐き出します。
行をチャットに列挙する代わりに、アシスタントはクリックすると結果を本物の DynoTable のタブとして開けるチップを吐き出します。

eval が裁定者である

上記のどれも、直感で設計して信仰で守られたものではありません。あらゆるプロンプトの節、バリデーターのメッセージ、上限は、seed された DynamoDB Local に対して本物のエージェントループを走らせる eval スイートによってゲートされています — 本物のツール、本物のクエリ実行、本番のプロンプトビルダーとバリデーターを再実装ではなく直接インポートし — ツールのトレース、データベースの最終状態、そしてテキストに対するバイナリのチェックで採点されます。

直感が見逃し、eval が捕まえたもの:

  • 「よりきれいな」プロンプトの汎用化が、あるシナリオを 100% から 0% に退行させた。 私たちにはそのほうがよく読めました。下限モデルは同意しませんでした。eval だけが気づきました。
  • フロンティアモデル向けのプロンプトの助言は、安価なモデルには積極的に間違っている。 公開されている指針は、大きなモデルが過剰に反応するので CRITICAL/MUST のような言葉を和らげるよう言います。安価なモデルは 過小に 従うので、強い版が必要です。私たちは今、借り物の助言を信仰で適用するのではなく、eval でゲートします。
  • 厳格なツールアサーションは正しい振る舞いを罰した。 明確化のためにモデルが ask ツールを呼ぶことを要求する採点者は、モデルのラダー全体で 19% しか合格しませんでした — ほとんどすべての「失敗」は、明確化を テキストで 正しく求めていたモデルでした。採点者は今ではどちらも受け入れます。仕組みではなく、ユーザーに向けた意図を採点するのです。
  • モデルのラダーはその絞り込みに値した。 私たちは広く始めました — Mistral、AI21、GLM、Qwen、Nova Pro、その他 — そして本物の実行だけが浮かび上がらせる理由から、2つの段(Nova Lite、Claude Haiku)に切り詰めました。ある系統は Bedrock がプロンプトキャッシュを提供しなかったので呼び出しごとに約10倍のコストがかかり、Nova Pro は開かれた結合で customers.customerName のような連結された属性名を幻覚しました。そして1つの系統はまるごとブロックリスト入りにせざるを得ませんでした — Bedrock の統一 API は、アダプターが関数呼び出しを プレーンテキスト として吐き出すモデルに対してもツール定義を平気で受け付け、これは API のメタデータからは分かりません。私たちはそれを、仕様ではなく有料のスモークプローブで見つけました。

2つの規律がスイートを正直に保ちます。例のテーブルは決してテストのテーブルと等しくならない — プロンプトの例は seed データに存在しない中立的な名前を使い、eval は異なるテーブルで走ります — なので合格は、モデルが私たちの例を暗記したのではなく、パターンを一般化したことを証明します。そして $0 のウォッチモードは録画されたフィクスチャを拒否します — 再生されたモデルの出力は、本物のツール活動に依存するアサーションをグリーンで通してしまうので、無料の段は配線だけを検証し、スコア を主張するものはすべて、本物のモデルに本物のトークンを費やさなければなりませんでした。

こういうものを構築しているなら、チェックリスト

  • 下限モデルを最初に選び、常にそれに対して eval する。フロンティアモデルはあなたのバグを隠す。
  • 検証をツールの実行ステップに置く。ループが行動できるエラーを返し、スキーマの行き止まりは決して返さない。
  • あらゆる拒否を「何が間違っているか」+「FIX: 正確な次のアクション」として書き、ユーザーの実際のテーブルをそこに補間する。
  • 拒否するつもりのものをパースし、診断が具体的になれるようにする。
  • 各バリデーターをデータモデルに照らして監査する — 正規の形状を拒否するものは削除する。
  • ツール結果を収まる量より下で上限を設け、切り詰めの通知がモデルを正しい次のツールへルーティングするようにする。
  • eval を、本番のコードパスを再利用しながら、本物の(ローカルの)バックエンドに対する本物のループとして走らせる。例データをテストデータから切り離しておく。あらゆるプロンプトとメッセージの変更の裁定者を、好みではなく eval にする。

フロンティアモデルを使うなら、これのどれかが関係あるのか?

頻度は下がりますが、あります。バリデーターはあらゆる段で土台を支えます — フロンティアモデルでも DynamoDB で JOIN は実行できず、指示的なエラーはただより速く消化されるだけです。私たち自身の実行では、フロンティア級 のモデルのチップ吐き出しの振る舞いが、より小さなモデルが扱えたプロンプトの言い回しの下で一度 0% に落ちました — eval ハーネスはそれも捕まえました。信頼性エンジニアリングは小さなモデルへの税金ではありません。小さなモデルは、その請求書をより早く届かせるだけです。

これがどこで動くのか

このすべては DynoTable のアシスタントと、そのゲートされたツールカタログの中に出荷されています — あなた自身の Bedrock 認証情報の上でのスキーマを意識した自然言語クエリ、正確なテーブル全体の集計、そして常にレビュー可能なステージングエリアにのみ着地する書き込みです。外部エージェントは同じ検証済みのツールキットをMCP サーバー経由で手にします — それ をどう安全に構築したかは、OAuth から認証情報の分離まで、それ自体が別の物語です。

そして生成ではなく決定論が欲しいとき — コードにコミットするクエリ — にはモデルを完全に飛ばしてください。Expression Builderが正確なリクエストを手作業で組み立てます。

Console なしで DynamoDB を扱う

DynoTable は DynamoDB 向けの高速なデスクトップクライアントです — テーブルを閲覧し、SQL スタイルのクエリを実行し、アイテムをローカルで編集できます。