· 10 min de leitura

Como tornamos LLMs baratos confiáveis no DynamoDB: validadores de ferramentas + evals

O assistente de IA do DynoTable roda com suas próprias credenciais do , o que significa que você escolhe o modelo — e muita gente escolhe um barato. Então não ajustamos o assistente contra um modelo de fronteira. Nosso piso é o Amazon Nova Lite, um modelo que custa um erro de arredondamento por query e falha de formas que os modelos de fronteira não falham.

Aqui está o resumo de todo este post: em um cenário de — um join que precisa da tabela de chave composta como base — o Nova Lite marcou 0%. Nós não trocamos de modelo, não adicionamos exemplos few-shot, não fizemos fine-tune. Nós reescrevemos a mensagem de erro de um validador para dizer exatamente o que fazer em vez disso. Ele foi para 100%, virando o join no passo seguinte.

O modelo nunca foi o gargalo. A mensagem era.

Esta é a história de engenharia por trás dessa lição: os validadores que ficam em volta de cada chamada de ferramenta, as coisas que deliberadamente não validamos, e o harness de evals que arbitra cada mudança. Se você está construindo um com um modelo econômico — sobre o DynamoDB ou qualquer outra coisa — a maior parte disso se transfere.

Por que modelos baratos falham no DynamoDB especificamente

O DynamoDB é um alvo hostil para um modelo pequeno porque a superfície de query parece SQL e não é. O PartiQL aceita a sintaxe SELECT mas não suporta JOIN, GROUP BY, DISTINCT, nem LIKE — e um modelo que aprendeu SQL na internet emite todos eles com confiança.

As falhas que de fato observamos, cada uma de uma execução real registrada:

  • Argumentos limit descartados. Os rotineiramente omitem o limite opcional de linhas em uma ferramenta de query. O backend alegremente retorna tudo, um resultado de ferramenta de 200 KB cai na conversa, e o próximo turno do modelo morre no teto de contexto — uma ValidationException no meio do loop, atribuída ao componente errado.
  • Construções não suportadas. JOIN, GROUP BY, agregados em PartiQL — instruções que o DynamoDB nunca vai rodar, emitidas porque estariam corretas no Postgres.
  • Trocas de identificador. O Nova Lite, dado SELECT * FROM customers e a instrução "adicione um WHERE para a Alemanha", às vezes retornava SELECT * FROM orders WHERE … — uma tabela que o usuário nunca mencionou. A instrução faz parse perfeitamente. Nada sintático a pega.
  • Roteamento errado de ferramenta. Perguntado sobre um arquivo anexado, o Nova Lite chamou a ferramenta de listagem de abas abertas procurando arquivos em abas — ele raramente usava o mecanismo de tool-search, então qualquer coisa não diretamente visível não existia.

Nenhuma dessas é hipotética. Cada uma motivou um guardrail específico abaixo.

Valide na fronteira da ferramenta e faça do erro uma lição

A decisão arquitetural mais importante: a validação vive dentro da etapa de execução da ferramenta, não no schema de entrada. Uma rejeição de schema é um beco sem saída — o loop vê um erro de tipo e não aprende nada. Uma ferramenta que roda, verifica e retorna um erro estruturado dá ao modelo um resultado de ferramenta normal ao qual ele pode reagir no passo seguinte.

E esse erro é escrito para o modelo, não para um log humano. Os erros da nossa ferramenta de exportação são assim, verbatim:

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.

As falhas de validação de query carregam um prefixo validation:validation: parse-error: …, validation: partiql-unsupported: JOIN is not supported by DynamoDB PartiQL. — que o system prompt estabelece como o sinal para corrigir a instrução em vez de repetir a mesma entrada. Uma mensagem de rejeição é um sinal de ensino. Antes de adotarmos a convenção FIX:, as mesmas falhas terminavam com o modelo se desculpando e dizendo ao usuário para clicar ele mesmo no botão de exportação. Depois, ele se autocorrige e conclui a tarefa.

O corolário levou mais tempo para aprender: onde o exemplo concreto vive é um eixo de design. Se um erro é pego por um validador em tempo de execução, mantenha o system prompt genérico e deixe a mensagem de erro dinâmica carregar as tabelas e a correção específicas — o prompt fica pequeno e a lição chega exatamente quando é necessária. Só erros que nenhum validador pode pegar (como rotear uma pergunta de agregação para a ferramenta certa em primeiro lugar) merecem um exemplo concreto no próprio prompt.

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

Faça parse do que você vai rejeitar

Rejeitar JOIN com um genérico syntax error at offset 27 não ensina nada. Então nosso parser de PartiQL escrito à mão faz algo levemente perverso: ele deliberadamente faz parse de construções que o DynamoDB não suporta em uma árvore de sintaxe válida — só para que um walker possa emitir um diagnóstico específico e didático. Tudo verbatim, tudo voltado ao modelo:

  • 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. — com a substituição resolvida por função.

Cada mensagem nomeia a construção e a saída de emergência. Combinada com a regra do prompt "se o PartiQL rejeitar uma construção não suportada, mude para o SQL workbench imediatamente", um modelo barato se recupera de seus instintos de Postgres em um passo em vez de patinar.

Saiba o que NÃO validar

O nível schema-aware do nosso validador de SQL resolve referências de tabela e coluna contra as descrições reais de tabela que o app já tem. No começo ele também sinalizava referências a atributos que o DynamoDB não declarava — o que soa certo e é exatamente o errado. O attributeDefinitions do DynamoDB só lista atributos de chave, então a verificação rejeitava WHERE <non-key> = … — o formato de query legítimo mais comum que existe. Essa verificação agora é um aviso que nunca bloqueia.

Todo validador é uma aposta de que o formato rejeitado tem mais chance de estar errado do que certo. Quando o modelo de dados não suporta a verificação, não faça a aposta.

Limites de resultado como comportamento, não apenas segurança

A correção para a explosão do limit descartado não foi "adicionar o limite ao prompt" (modelos baratos o descartam de qualquer jeito). Os resultados das ferramentas de query são limitados de forma rígida — 10 linhas ou 5 KB, o que vier primeiro, sempre mantendo pelo menos a primeira linha — e o aviso de truncamento é ele mesmo prescritivo:

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

Os limites são intencionalmente muito abaixo do que o contexto poderia comportar. O objetivo é comportamental: forçar o modelo a entregar ao usuário uma visão de tabela real ou uma agregação exata, nunca a enumerar linhas no chat. A dica de recuperação nomeia as ferramentas exatas, então o limite ensina o roteamento no momento em que o modelo precisa. O mesmo envelope é espelhado byte a byte no harness de evals, e um cenário de regressão fixa truncated: true contra uma tabela de 10.000 itens para que o limite nunca possa desaparecer silenciosamente.

Em vez de enumerar linhas no chat, o assistente emite um chip em que você clica para abrir o resultado como uma aba real do DynoTable.
Em vez de enumerar linhas no chat, o assistente emite um chip em que você clica para abrir o resultado como uma aba real do DynoTable.

Os evals são o árbitro

Nada do que está acima foi projetado por intuição e mantido por fé. Cada cláusula de prompt, mensagem de validador e limite é controlado por uma suíte de evals que roda loops reais de agente contra um DynamoDB Local com dados semeados — ferramentas reais, execução real de query, os construtores de prompt e validadores de produção importados diretamente em vez de reimplementados — pontuados por verificações binárias no trace de ferramentas, no estado final do banco de dados e no texto.

O que os evals pegaram que a intuição deixou passar:

  • Uma "limpeza" do prompt para torná-lo genérico regrediu um cenário de 100% para 0%. Para nós, lia melhor. O modelo de piso discordou. Só o eval percebeu.
  • Conselho de prompt de modelo de fronteira é ativamente errado para modelos baratos. A orientação publicada diz para suavizar a linguagem CRITICAL/MUST porque modelos grandes reagem em excesso a ela. Modelos baratos sub-cumprem e precisam da versão firme. Agora fazemos eval-gate em qualquer conselho emprestado em vez de aplicá-lo por fé.
  • Asserções estritas de ferramenta puniam comportamento correto. Um scorer que exigia que o modelo chamasse a ferramenta ask para pedir esclarecimento passou em 19% em toda a escada de modelos — quase toda "falha" era um modelo que tinha corretamente pedido esclarecimento em texto. Agora o scorer aceita qualquer um dos dois. Pontue a intenção voltada ao usuário, não o mecanismo.
  • A escada de modelos ganhou seu estreitamento. Começamos amplos — Mistral, AI21, GLM, Qwen, Nova Pro, mais — e a cortamos para dois degraus (Nova Lite, Claude Haiku) por razões que só execuções reais revelam: uma família custava ~10× por chamada porque o Bedrock não lhe oferecia prompt caching; o Nova Pro alucinava nomes de atributo concatenados como customers.customerName em joins abertos. E uma família teve que ser colocada em blocklist de vez — a API unificada do Bedrock aceita alegremente definições de ferramenta para modelos cujo adapter então emite chamadas de função como texto puro, algo que os metadados da API não vão lhe contar. Encontramos isso com uma smoke probe paga, não com uma spec.

Duas disciplinas mantêm a suíte honesta. Tabelas de exemplo nunca são iguais a tabelas de teste: os exemplos do prompt usam nomes neutros que não existem nos dados semeados, e os evals rodam em tabelas diferentes — então uma aprovação prova que o modelo generalizou o padrão, não que memorizou nosso exemplo. E o modo watch de $0 recusa fixtures gravadas: saídas de modelo reproduzidas passariam em verde asserções que dependem de atividade real de ferramenta, então o nível gratuito valida apenas a fiação, e qualquer coisa que reivindique uma pontuação teve que gastar tokens reais em um modelo real.

O checklist, se você está construindo um destes

  • Escolha seu modelo de piso primeiro e faça eval sempre contra ele; modelos de fronteira escondem seus bugs.
  • Ponha a validação na etapa de execução da ferramenta; retorne erros nos quais o loop possa agir, nunca becos sem saída de schema.
  • Escreva cada rejeição como o que está errado + FIX: próxima ação exata, e interpole as tabelas reais do usuário nela.
  • Faça parse do que você pretende rejeitar, para que o diagnóstico possa ser específico.
  • Audite cada validador contra seu modelo de dados — delete os que rejeitam formatos legítimos.
  • Limite os resultados de ferramenta abaixo do que cabe, e faça o aviso de truncamento rotear o modelo para a próxima ferramenta certa.
  • Rode os evals como loops reais contra um backend real (local), reutilizando os caminhos de código de produção; mantenha os dados de exemplo disjuntos dos dados de teste; faça do eval — e não do gosto — o árbitro de cada mudança de prompt e mensagem.

Algo disso importa se você usa um modelo de fronteira?

Menos vezes, mas sim. Os validadores são estruturais em todos os níveis — um modelo de fronteira ainda não pode rodar JOIN no DynamoDB, e os erros prescritivos só são consumidos mais rápido. Em nossas próprias execuções, o comportamento de emissão de chip de um modelo de classe fronteira certa vez caiu para 0% sob uma redação de prompt que modelos menores lidavam — o harness de evals pegou isso também. A engenharia de confiabilidade não é um imposto de modelos pequenos; modelos pequenos só fazem a fatura chegar mais cedo.

Onde isso roda

Tudo isso é entregue dentro do assistente do DynoTable e de seu catálogo de ferramentas restrito: consultas em linguagem natural schema-aware com suas próprias credenciais do Bedrock, agregações exatas de tabela inteira, e escritas que só chegam a uma área de staging revisável. Agentes externos recebem o mesmo conjunto de ferramentas validado pelo servidor MCP — como construímos aquilo com segurança é sua própria história, do OAuth ao isolamento de credenciais.

E quando você quer determinismo em vez de geração — a query que você vai gravar no código — pule o modelo por completo: o Construtor de Expressões monta a requisição exata à mão.

Trabalhe com o DynamoDB sem o Console

O DynoTable é um cliente desktop rápido para o DynamoDB — navegue pelas tabelas, execute consultas no estilo SQL e edite itens localmente.