O Problema
Natural Language to SQL++ (NL2SQL++) é tão bom quanto o conhecimento de esquema por trás dele. Dê a um modelo de linguagem grande (LLM) uma pergunta como “Quantos clientes com uma solicitação anterior ativa tornaram-se inadimplentes?” e ele precisa saber quais tabelas existem, o que cada coluna significa e como as tabelas se relacionam. A maioria dos sistemas resolve isso incorporar o esquema diretamente no prompt. Isso quebra no momento em que o esquema muda, e desmorona completamente quando os nomes das colunas não carregam nenhum significado por si mesmos – a norma em dados empresariais reais.
Considere as colunas que um modelo realmente enfrenta em um esquema de produção. Alguns nomes são simplesmente opacos: CUST_VAL_04 é a faixa de receita vitalícia de um cliente, mas o nome não diz nada sobre o que ele contém. Outros são piores do que opacos, porque enganam: CLOSE_DT soa como uma data de encerramento de conta, mas armazena uma código de status (0 = aberto, 1 = congelado, 2 = fechado) – um modelo que filtra isso como uma data gerará silenciosamente um SQL++ incorreto. E depois há quase-duplicatas que um nome sozinho não consegue separar: BAL_CUR, BAL_AVG_30 e BAL_AVG_90 parecem “saldo”, mas apenas um é o atual saldo, enquanto as outras são médias de 30 e 90 dias. Perguntar “Qual é o saldo atual do cliente?” e apenas a da coluna descrição – e não o nome dele – revela que BAL_CUR é o correto e que os outros dois são armadilhas. Um analista humano recorre a um dicionário de dados para interpretar colunas como essas. Um LLM precisa da mesma coisa.
Esse dicionário já existe dentro da maioria das organizações, curado em catálogos de dados como DataHub e OpenMetadata: descrições de colunas, tipos de dados e os relacionamentos de chave estrangeira entre as tabelas. O problema é que esse conhecimento vive em um silo, desconectado do banco de dados operacional onde as consultas realmente são executadas.
Este projeto conecta catálogos de dados corporativos ao Couchbase para que o NL2SQL++ possa ler o que as colunas realmente significam em vez de adivinhar pelos seus nomes. Ele extrai metadados de colunas de um catálogo, os incorpora ao Couchbase e recupera o contexto correto no momento da consulta por meio de busca vetorial – de modo que o esquema nunca é fixado no código (hardcoded). E como clientes diferentes usam catálogos diferentes, a integração é construída em torno de uma padrão de provedor plugável: a alternância entre os catálogos compatíveis é uma alteração na configuração, e a adição de um novo catálogo implica a inclusão de um pequeno adaptador independente — sem afetar o restante do sistema.
A Grande Ideia: Uma Interface, Qualquer Catálogo
No coração deste design está um contrato único que todo catálogo de dados implementa. Independentemente da origem, ele expõe as mesmas três capacidades para o resto do pipeline:
- Buscar metadados da coluna – Cada coluna em todas as tabelas no escopo, cada uma com seu nome, descrição, tipo de dado e localização totalmente qualificada.
- Carregar relacionamentos com fetch join – Os vínculos de chave estrangeira ou de linhagem entre tabelas, expressos como predicados de junção reutilizáveis.
- Transmitir alterações ao vivo – Um feed opcional de alterações no esquema, para que você possa manter os metadados atualizados sem precisar fazer uma recarga completa.
Uma única configuração seleciona qual catálogo está ativo. Tudo o que está a jusante – o carregador, o mecanismo de consulta, a sincronização em tempo real – conversa com esse contrato comum e nunca com um catálogo específico. Para dar suporte ao próximo catálogo (Atlan, um data lake S3 ou algo ainda não construído), um adaptador é escrito para atender ao contrato; nada mais muda.
O conjunto de dados utilizado ao longo do texto é o conjunto de dados público Dados sobre o risco de inadimplência da Home Credit do Kaggle: sete tabelas relacionadas – solicitações de empréstimo, registros de órgãos de proteção ao crédito, solicitações anteriores e históricos de saldo mensal – coexistindo no Couchbase.
Como funciona, do início ao fim
Existem dois ciclos de vida. Um único configuração carrega os metadados do catálogo no Couchbase e os torna pesquisáveis. O consulta fluxo e, em seguida, responde a perguntas com base nele. Um ouvinte ao vivo opcional mantém os metadados atualizados à medida que o catálogo evolui.
Configuração: catalog no Couchbase
A instalação ocorre em quatro etapas, todas determinadas pelo catálogo que estiver ativo:
- Obter os metadutos da coluna e armazene um registro por coluna no Couchbase – sua descrição, tipo de dados e localização.
- Inserir a descrição de cada coluna em um vetor de 384 dimensões usando um modelo sentence-transformer. Apenas a descrição é vetorizada, não o nome críptico – é isso que faz uma pergunta em inglês simples corresponder ao significado de uma coluna em vez do seu rótulo.
- Criar um índice vetorial sobre esses embeddings (similaridade de cosseno) para que as buscas sejam rápidas.
- Puxe os relacionamentos de junção e armazene-os como um conjunto de predicados de junção deduplicados, por exemplo: bureau.SK_ID_CURR = application_train.SK_ID_CURR.
Cada catálogo deriva essas junções de seu próprio modelo nativo – o DataHub a partir da linhagem em nível de coluna, o OpenMetadata a partir de restrições de chave estrangeira – mas ambos produzem as strings de predicado idênticas que o mecanismo de consulta espera. Notavelmente, o sistema não pré-computa um grafo de junção rígido; ele retém os predicados brutos e permite que o modelo em tempo de consulta monte o caminho de junção válido mínimo sob demanda.
Consulta para SQL++
Responder a uma pergunta ocorre em três etapas:
- Entendido – A pergunta é dividida em subconsultas focadas para que cada conceito possa ser correspondido independentemente.
- Recuperar – Cada subconsulta é incorporada e executada por meio de uma pesquisa vetorial sobre os metadados de coluna no Couchbase, mantendo apenas as correspondências mais próximas por distância de cosseno. É aqui que EXT_SOURCE_2 aparece para uma pergunta sobre pontuações de risco, puramente porque seu descrição fósforos – nunca o seu nome.
- Refinar – A busca vetorial lança uma rede ampla, de modo que um modelo de linguagem reduz os candidatos às colunas genuinamente necessárias para responder à pergunta, inclinando-se a manter qualquer coisa útil.
- Gerar – Por fim, a pergunta, as colunas escolhidas e os relacionamentos de junção vão para um modelo Claude em AWS Bedrock, que é restrito a usar apenas as junções fornecidas e o caminho mínimo conectando as tabelas necessárias. O resultado é um SQL++ executável.
Como as chaves de junção viajam separadamente como predicados, o gerador ainda pode configurar junções corretas entre várias tabelas, mesmo quando essas colunas identificadoras foram descartadas durante o refinamento.
Mantendo-se atualizado: sincronização de metadados ao vivo
Os catálogos mudam; por exemplo, colunas são renomeadas, descrições são corrigidas e tabelas aparecem e desaparecem. Um ouvinte opcional de longa execução mantém o Couchbase sincronizado sem precisar executar a configuração novamente. Cada catálogo monitora sua origem de maneira nativa — o DataHub consome um fluxo de eventos de registro de alterações, o OpenMetadata pesquisa seu endpoint de eventos — e ambos traduzem o que veem no mesmo seis eventos de alteração normalizados. Um pool de workers compartilhados aplica então cada evento aos metadados armazenados:
| Alterar | O que Acontece no Couchbase | Reincorporar? |
| Nova tabela adicionada | Armazene um registro para cada nova coluna | Sim |
| Coluna adicionada | Armazenar a nova coluna | Sim |
| Descrição atualizada | Atualizar a descrição e é um vetor | Sim |
| Tipo de dado alterado | Atualize apenas o tipo | Não |
| Coluna removida | Exclua o registro daquela coluna | Não |
| Tabela removida | Exclua todos os registros dessa tabela | Não |
Execuções de reincorporação apenas quando o texto atrás do vetor realmente mudar – uma nova coluna ou uma descrição editada. Uma alteração puramente de tipo de dados a ignora, portanto nada é recalculado sem motivo.
Por Que Isso Importa
Os testes com os dados da Home Credit confirmaram a tese central: o NL2SQL++ aprimorado por metadados supera significativamente uma abordagem baseada apenas no esquema. Faced with opaque column names, supplying the catalog’s descriptions produced correct SQL++; without them, the model made systematic errors in which fields it mapped and how it filtered.
The broader payoff is architectural. By separating where metadata comes from – the catalog – from how it’s used – embed, search, generate – any Couchbase customer can plug their existing catalog into AI-driven query generation and get schema-aware results without hand-maintaining a schema. The pluggable design means the next catalog is a small, self-contained addition rather than a rewrite.
Two examples, side by side
The gap is easiest to see in SQL++. Both queries below are syntactically valid and execute cleanly – the schema-only version simply returns the wrong answer, because the model guessed a column’s meaning from its name.
Example A – a value the name can’t reveal: STATUS
“How many monthly bureau records show a loan that was written off or over 120 days past due?”
STATUS in bureau_balance sounds self-explanatory, but it stores single-character codes, not readable labels. The catalog description spells out the encoding: “C means closed, X means status unknown, 0 means no DPD, 1 means maximal did during month between 1-30, 2 means DPD 31-60, 5 means DPD 120+ or sold or written off.” “Written off or 120+ days past due” is the code ‘5’ – and there is no way to know that from the name.
Without the catalog, the model invents plausible-looking string values that match nothing in the data:
-- Without catalog: runs fine, returns 0 — the guessed labels don't exist
SELECT COUNT(*) AS num_records
FROM creditrisk.sampleScope.bureau_balance AS bb
WHERE bb.STATUS IN ['WRITTEN_OFF', 'DPD_120_PLUS', 'written_off'];
With the catalog, the encoding is in the description, so it maps to the real code:
-- With catalog: correct — '5' = DPD 120+ / sold / written off
SELECT COUNT(*) AS num_records
FROM creditrisk.sampleScope.bureau_balance AS bb
WHERE bb.STATUS = '5';
Example B – a suffix that means nothing on its own: SK_DPD vs SK_DPD_DEF
“Which POS/cash loans were past due during a month, ignoring trivial low-amount debts?”
Two near-identical columns sit side by side in POS_CASH_balance. SK_DPD is “DPD (days past due) during the month of previous credit.” SK_DPD_DEF is “DPD during the month with tolerance (debts with low loan amounts are ignored).” The only clue is the _DEF suffix – meaningless by name. “Ignoring trivial low-amount debts” is precisely SK_DPD_DEF, but nothing in the name says so.
Without the catalog, the model picks the plainer-named column and quietly counts the debts the question asked to exclude:
-- Without catalog: runs fine, but SK_DPD does NOT ignore low-amount debts
SELECT pc.SK_ID_PREV,
pc.MONTHS_BALANCE,
pc.SK_DPD
FROM creditrisk.sampleScope.POS_CASH_balance AS pc
WHERE pc.SK_DPD > 0;
With the catalog, the tolerance clause in the description points to the right column:
-- With catalog: correct — SK_DPD_DEF applies the low-amount tolerance
SELECT pc.SK_ID_PREV,
pc.MONTHS_BALANCE,
pc.SK_DPD_DEF
FROM creditrisk.sampleScope.POS_CASH_balance AS pc
WHERE pc.SK_DPD_DEF > 0;
In both cases the failure is invisible at the SQL level – nothing produces errors, and nothing looks off. Only the column’s descrição separates a correct query from a confidently wrong one.
Rich metadata already lives in the enterprise, this project simply teaches the database to understand it. Curious where this is headed? Get in touch with Couchbase to talk about conversational querying on your data.
Deixe um comentário
Você precisa fazer o login para publicar um comentário.