> **Beta Translation:** This document was translated via Machine Learning and as such may not be 100% accurate. All non-English languages are currently classified as Beta.
Esta especificação aborda problemas e lacunas identificadas durante a implementação inicial da integração de dados estruturados do TrustGraph, conforme descrito em `structured-data.md`.
## Declarações do Problema
### 1. Inconsistência na Nomenclatura: "Objeto" vs "Linha"
A implementação atual usa a terminologia "objeto" em todo o código (por exemplo, `ExtractedObject`, extração de objetos, incorporações de objetos). Essa nomenclatura é muito genérica e causa confusão:
"Objeto" é um termo sobrecarregado em software (objetos Python, objetos JSON, etc.)
Os dados que estão sendo processados são fundamentalmente tabulares - linhas em tabelas com esquemas definidos
"Linha" descreve com mais precisão o modelo de dados e está alinhado com a terminologia de banco de dados
Essa inconsistência aparece em nomes de módulos, nomes de classes, tipos de mensagens e documentação.
### 2. Limitações de Consulta do Armazenamento de Linhas
A implementação atual do armazenamento de linhas tem limitações significativas de consulta:
**Incompatibilidade com a Linguagem Natural**: As consultas têm dificuldades com variações de dados do mundo real. Por exemplo:
É difícil encontrar uma base de dados de ruas contendo `"CHESTNUT ST"` quando se pergunta sobre `"Chestnut Street"`
Abreviaturas, diferenças de maiúsculas e minúsculas e variações de formatação interrompem as consultas de correspondência exata
Os usuários esperam compreensão semântica, mas o armazenamento fornece correspondência literal
**Problemas de Evolução do Esquema**: Alterar os esquemas causa problemas:
Os dados existentes podem não estar em conformidade com os esquemas atualizados
Alterações na estrutura da tabela podem quebrar consultas e a integridade dos dados
Não há um caminho de migração claro para atualizações de esquema
### 3. Incorporações de Linhas Necessárias
Relacionado ao problema 2, o sistema precisa de incorporações vetoriais para dados de linha para permitir:
Pesquisa semântica em dados estruturados (encontrando "Chestnut Street" quando os dados contêm "CHESTNUT ST")
Correspondência de similaridade para consultas aproximadas
Pesquisa híbrida combinando filtros estruturados com similaridade semântica
Melhor suporte para consultas em linguagem natural
O serviço de incorporação foi especificado, mas não implementado.
### 4. Ingestão de Dados de Linha Incompleta
O pipeline de ingestão de dados estruturados não está totalmente operacional:
Existem prompts de diagnóstico para classificar formatos de entrada (CSV, JSON, etc.)
O serviço de ingestão que usa esses prompts não está integrado ao sistema
Não há um caminho de ponta a ponta para carregar dados pré-estruturados no armazenamento de linhas
## Objetivos
**Flexibilidade de Esquema**: Permitir a evolução do esquema sem quebrar dados existentes ou exigir migrações
**Nomenclatura Consistente**: Padronizar na terminologia "linha" em todo o código
**Consultabilidade Semântica**: Suportar correspondência aproximada/semântica por meio de incorporações de linhas
**Pipeline de Ingestão Completo**: Fornecer um caminho de ponta a ponta para carregar dados estruturados
## Design Técnico
### Esquema de Armazenamento de Linhas Unificado
A implementação anterior criou uma tabela Cassandra separada para cada esquema. Isso causou problemas quando os esquemas evoluíram, pois as alterações na estrutura da tabela exigiram migrações.
O novo design usa uma única tabela unificada para todos os dados de linha:
| `collection` | `text` | Identificador de coleta/importação de dados (a partir de metadados) |
| `schema_name` | `text` | Nome do esquema ao qual esta linha está em conformidade |
| `index_name` | `text` | Nome(s) do(s) campo(s) indexado(s), unidos por vírgula para compostos |
| `index_value` | `frozen<list<text>>` | Valor(es) do índice como uma lista |
| `data` | `map<text, text>` | Dados da linha como pares chave-valor |
| `source` | `text` | URI opcional que faz referência a informações de procedência no grafo de conhecimento. Uma string vazia ou NULL indica que não há fonte. |
#### Tratamento de Índices
Cada linha é armazenada várias vezes - uma vez por campo indexado definido no esquema. Os campos de chave primária são tratados como um índice sem um marcador especial, proporcionando flexibilidade futura.
**Exemplo de índice de campo único:**
O esquema define `email` como indexado
`index_name = "email"`
`index_value = ['foo@bar.com']`
**Exemplo de índice composto:**
O esquema define um índice composto em `region` e `status`
`index_name = "region,status"` (nomes dos campos ordenados e unidos por vírgula)
`index_value = ['US', 'active']` (valores na mesma ordem dos nomes dos campos)
**Exemplo de chave primária:**
O esquema define `customer_id` como chave primária
`index_name = "customer_id"`
`index_value = ['CUST001']`
#### Padrões de Consulta
Todas as consultas seguem o mesmo padrão, independentemente de qual índice é usado:
```sql
SELECT * FROM rows
WHERE collection = 'import_2024'
AND schema_name = 'customers'
AND index_name = 'email'
AND index_value = ['foo@bar.com']
```
#### Compensações no Design
**Vantagens:**
Alterações no esquema não exigem alterações na estrutura da tabela
Os dados das linhas são opacos para o Cassandra - adições/remoções de campos são transparentes
Padrão de consulta consistente para todos os métodos de acesso
Sem índices secundários do Cassandra (que podem ser lentos em grande escala)
Tipos nativos do Cassandra em todo o sistema (`map`, `frozen<list>`)
**Compensações:**
Amplificação de escrita: cada inserção de linha = N inserções (uma por campo indexado)
Sobrecarga de armazenamento devido à duplicação de dados das linhas
Informações de tipo armazenadas na configuração do esquema, conversão na camada de aplicação
#### Modelo de Consistência
O design aceita certas simplificações:
1.**Sem atualizações de linha**: O sistema é somente de anexação. Isso elimina as preocupações com a consistência ao atualizar várias cópias da mesma linha.
2.**Tolerância a alterações de esquema**: Quando os esquemas são alterados (por exemplo, índices adicionados/removidos), as linhas existentes mantêm seu índice original. Linhas antigas não serão encontradas por meio de novos índices. Os usuários podem excluir e recriar um esquema para garantir a consistência, se necessário.
### Rastreamento e Exclusão de Partições
#### O Problema
Com a chave de partição `(collection, schema_name, index_name)`, a exclusão eficiente requer o conhecimento de todas as chaves de partição a serem excluídas. Excluir apenas por `collection` ou `collection + schema_name` requer o conhecimento de todos os valores de `index_name` que contêm dados.
#### Tabela de Rastreamento de Partições
Uma tabela de consulta secundária rastreia quais partições existem:
Isso permite a descoberta eficiente de partições para operações de exclusão.
#### Comportamento do Escritor de Linhas
O escritor de linhas mantém um cache na memória de pares `(collection, schema_name)` registrados. Ao processar uma linha:
1. Verifique se `(collection, schema_name)` está no cache.
2. Se não estiver no cache (primeira linha para este par):
Consulte a configuração do esquema para obter todos os nomes de índice.
Insira entradas em `row_partitions` para cada `(collection, schema_name, index_name)`.
Adicione o par ao cache.
3. Continue com a escrita dos dados da linha.
O escritor de linhas também monitora eventos de alteração da configuração do esquema. Quando um esquema é alterado, as entradas relevantes do cache são limpas para que a próxima linha acione o re-registro com os nomes de índice atualizados.
Essa abordagem garante:
As escritas na tabela de pesquisa ocorrem apenas uma vez por par `(collection, schema_name)`, e não por linha.
A tabela de pesquisa reflete os índices que estavam ativos quando os dados foram escritos.
As alterações no esquema durante a importação são detectadas corretamente.
#### Operações de Exclusão
**Excluir coleção:**
```sql
-- 1. Discover all partitions
SELECT schema_name, index_name FROM row_partitions WHERE collection = 'X';
-- 2. Delete each partition from rows table
DELETE FROM rows WHERE collection = 'X' AND schema_name = '...' AND index_name = '...';
-- (repeat for each discovered partition)
-- 3. Clean up the lookup table
DELETE FROM row_partitions WHERE collection = 'X';
```
**Excluir coleção + esquema:**
```sql
-- 1. Discover partitions for this schema
SELECT index_name FROM row_partitions WHERE collection = 'X' AND schema_name = 'Y';
-- 2. Delete each partition from rows table
DELETE FROM rows WHERE collection = 'X' AND schema_name = 'Y' AND index_name = '...';
-- (repeat for each discovered partition)
-- 3. Clean up the lookup table entries
DELETE FROM row_partitions WHERE collection = 'X' AND schema_name = 'Y';
```
### Incorporações de Linhas
As incorporações de linhas permitem a correspondência semântica/aproximada em valores indexados, resolvendo o problema de incompatibilidade de linguagem natural (por exemplo, encontrar "CHESTNUT ST" ao pesquisar por "Chestnut Street").
#### Visão Geral do Design
Cada valor indexado é incorporado e armazenado em um armazenamento vetorial (Qdrant). No momento da consulta, a consulta é incorporada, vetores semelhantes são encontrados e os metadados associados são usados para pesquisar as linhas reais no Cassandra.
#### Estrutura da Coleção Qdrant
Uma coleção Qdrant por tupla `(user, collection, schema_name, dimension)`:
**Nome da coleção:** `rows_{user}_{collection}_{schema_name}_{dimension}`
Os nomes são higienizados (caracteres não alfanuméricos substituídos por `_`, em letras minúsculas, os prefixos numéricos recebem o prefixo `r_`)
**Justificativa:** Permite a exclusão limpa de uma instância `(user, collection, schema_name)`, descartando as coleções Qdrant correspondentes; o sufixo de dimensão permite que diferentes modelos de incorporação coexistam.
#### O Que é Incorporado
A representação de texto dos valores do índice:
| Tipo de Índice | Exemplo `index_value` | Texto a Incorporar |
| `index_name` | Os campos indexados que esta incorporação representa |
| `index_value` | A lista original de valores (para pesquisa no Cassandra) |
| `text` | O texto que foi incorporado (para depuração/exibição) |
Nota: `user`, `collection` e `schema_name` são implícitos do nome da coleção Qdrant.
#### Fluxo de Consulta
1. O usuário consulta por "Chestnut Street" dentro do usuário U, coleção X, esquema Y
2. Incorpore o texto da consulta
3. Determine o(s) nome(s) da coleção Qdrant que correspondem ao prefixo `rows_U_X_Y_`
4. Pesquise na(s) coleção(ões) Qdrant correspondente(s) pelos vetores mais próximos
5. Obtenha os pontos correspondentes com payloads contendo `index_name` e `index_value`
6. Consulte o Cassandra:
```sql
SELECT * FROM rows
WHERE collection = 'X'
AND schema_name = 'Y'
AND index_name = '<frompayload>'
AND index_value = <frompayload>
```
7. Retornar linhas correspondentes
#### Opcional: Filtragem por Nome do Índice
As consultas podem opcionalmente filtrar por `index_name` no Qdrant para pesquisar apenas campos específicos:
**"Encontrar qualquer campo que corresponda a 'Chestnut'"** → pesquisa todos os vetores na coleção
**"Encontrar street_name que corresponda a 'Chestnut'"** → filtrar onde `payload.index_name = 'street_name'`
#### Arquitetura
Os embeddings das linhas seguem o **padrão de duas etapas** usado pelo GraphRAG (embeddings do grafo, embeddings do documento):
**Etapa 1: Computação de embeddings** (`trustgraph-flow/trustgraph/embeddings/row_embeddings/`) - Consome `ExtractedObject`, calcula embeddings através do serviço de embeddings, gera `RowEmbeddings`
**Etapa 2: Armazenamento de embeddings** (`trustgraph-flow/trustgraph/storage/row_embeddings/qdrant/`) - Consome `RowEmbeddings`, escreve vetores no Qdrant
O escritor de linhas do Cassandra é um consumidor paralelo separado:
**Escritor de linhas do Cassandra** (`trustgraph-flow/trustgraph/storage/rows/cassandra`) - Consome `ExtractedObject`, escreve linhas no Cassandra
Todos os três serviços consomem do mesmo fluxo, mantendo-os desacoplados. Isso permite:
Escalonamento independente das escritas do Cassandra em relação à geração de embeddings em relação ao armazenamento de vetores
Os serviços de embeddings podem ser desativados se não forem necessários
Falhas em um serviço não afetam os outros
Arquitetura consistente com os pipelines do GraphRAG
#### Caminho de Escrita
**Etapa 1 (processador de embeddings das linhas):** Ao receber um `ExtractedObject`:
1. Consultar o esquema para encontrar campos indexados
2. Para cada campo indexado:
Construir a representação de texto do valor do índice
Calcular o embedding através do serviço de embeddings
3. Gerar uma mensagem `RowEmbeddings` contendo todos os vetores calculados
**Etapa 2 (escritor de embeddings-Qdrant):** Ao receber um `RowEmbeddings`:
1. Para cada embedding na mensagem:
Determinar a coleção do Qdrant a partir de `(user, collection, schema_name, dimension)`
Criar a coleção, se necessário (criação preguiçosa na primeira escrita)
A consulta de incorporações de linhas é uma **API separada** do serviço de consulta de linhas GraphQL:
| API | Propósito | Backend |
|-----|---------|---------|
| Consulta de Linhas (GraphQL) | Correspondência exata em campos indexados | Cassandra |
| Consulta de Incorporações de Linhas | Correspondência aproximada/semântica | Qdrant |
Essa separação mantém as responsabilidades bem definidas:
O serviço GraphQL se concentra em consultas estruturadas e exatas
A API de incorporações lida com a similaridade semântica
Fluxo de trabalho do usuário: pesquisa aproximada por meio de incorporações para encontrar candidatos e, em seguida, consulta exata para obter os dados completos da linha
Encontrar "CHESTNUT ST" quando o usuário pesquisa por "Chestnut Street"
Recuperar dados completos da linha com todos os campos
Combinar similaridade semântica com acesso a dados estruturados
### Ingestão de Dados da Linha
Será adiada para uma fase posterior. Será projetada juntamente com outras alterações de ingestão.
## Impacto na Implementação
### Análise do Estado Atual
A implementação existente possui dois componentes principais:
| Componente | Localização | Linhas | Descrição |
|-----------|----------|-------|-------------|
| Serviço de Consulta | `trustgraph-flow/trustgraph/query/objects/cassandra/service.py` | ~740 | Monolítico: geração de esquema GraphQL, análise de filtros, consultas Cassandra, tratamento de solicitações |
| Escritor | `trustgraph-flow/trustgraph/storage/objects/cassandra/write.py` | ~540 | Criação de tabelas por esquema, índices secundários, inserção/exclusão |
**Padrão de Consulta Atual:**
```sql
SELECT * FROM {keyspace}.o_{schema_name}
WHERE collection = 'X' AND email = 'foo@bar.com'
ALLOW FILTERING
```
**Novo Padrão de Consulta:**
```sql
SELECT * FROM {keyspace}.rows
WHERE collection = 'X' AND schema_name = 'customers'
AND index_name = 'email' AND index_value = ['foo@bar.com']
```
### Mudanças Principais
1.**A semântica das consultas é simplificada**: O novo esquema suporta apenas correspondências exatas em `index_value`. Os filtros GraphQL atuais (`gt`, `lt`, `contains`, etc.) ou:
Tornam-se pós-filtragem nos dados retornados (se ainda forem necessários)
São removidos em favor do uso da API de embeddings para correspondências aproximadas
2.**O código GraphQL está fortemente acoplado**: O código `service.py` atual combina a geração de tipos Strawberry, a análise de filtros e as consultas específicas do Cassandra. Adicionar outro backend de armazenamento de linhas duplicaria cerca de 400 linhas de código GraphQL.
### Refatoração Proposta
A refatoração tem duas partes:
#### 1. Separar o Código GraphQL
Extrair componentes GraphQL reutilizáveis para um módulo compartilhado: