Native CLI i18n: The TrustGraph CLI has built-in translation support that dynamically loads language strings. You can test and use different languages by simply passing the --lang flag (e.g., --lang es for Spanish, --lang ru for Russian) or by configuring your environment's LANG variable. Automated Docs Translations: This PR introduces autonomously translated Markdown documentation into several target languages, including Spanish, Swahili, Portuguese, Turkish, Hindi, Hebrew, Arabic, Simplified Chinese, and Russian.
25 KiB
| layout | title | parent |
|---|---|---|
| default | Especificação Técnica de Dados Estruturados (Parte 2) | Portuguese (Beta) |
Especificação Técnica de Dados Estruturados (Parte 2)
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.
Visão Geral
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:
CREATE TABLE rows (
collection text,
schema_name text,
index_name text,
index_value frozen<list<text>>,
data map<text, text>,
source text,
PRIMARY KEY ((collection, schema_name, index_name), index_value)
)
Definições das Colunas
| Coluna | Tipo | Descrição |
|---|---|---|
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:
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:
-
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.
-
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:
CREATE TABLE row_partitions (
collection text,
schema_name text,
index_name text,
PRIMARY KEY ((collection), schema_name, index_name)
)
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:
- Verifique se
(collection, schema_name)está no cache. - 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_partitionspara cada(collection, schema_name, index_name). Adicione o par ao cache. - 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:
-- 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:
-- 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 |
|---|---|---|
| Campo único | ['foo@bar.com'] |
"foo@bar.com" |
| Composto | ['US', 'active'] |
"US active" (juntados por espaço) |
Estrutura do Ponto
Cada ponto Qdrant contém:
{
"id": "<uuid>",
"vector": [0.1, 0.2, ...],
"payload": {
"index_name": "street_name",
"index_value": ["CHESTNUT ST"],
"text": "CHESTNUT ST"
}
}
| Campo de Payload | Descrição |
|---|---|
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
- O usuário consulta por "Chestnut Street" dentro do usuário U, coleção X, esquema Y
- Incorpore o texto da consulta
- Determine o(s) nome(s) da coleção Qdrant que correspondem ao prefixo
rows_U_X_Y_ - Pesquise na(s) coleção(ões) Qdrant correspondente(s) pelos vetores mais próximos
- Obtenha os pontos correspondentes com payloads contendo
index_nameeindex_value - Consulte o Cassandra:
SELECT * FROM rows WHERE collection = 'X' AND schema_name = 'Y' AND index_name = '<from payload>' AND index_value = <from payload> - 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:
- Consultar o esquema para encontrar campos indexados
- Para cada campo indexado: Construir a representação de texto do valor do índice Calcular o embedding através do serviço de embeddings
- Gerar uma mensagem
RowEmbeddingscontendo todos os vetores calculados
Etapa 2 (escritor de embeddings-Qdrant): Ao receber um RowEmbeddings:
- 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) Inserir/atualizar o ponto com o vetor e o payload
Tipos de Mensagens
@dataclass
class RowIndexEmbedding:
index_name: str # The indexed field name(s)
index_value: list[str] # The field value(s)
text: str # Text that was embedded
vectors: list[list[float]] # Computed embedding vectors
@dataclass
class RowEmbeddings:
metadata: Metadata
schema_name: str
embeddings: list[RowIndexEmbedding]
Integração de Exclusão
As coleções Qdrant são descobertas por correspondência de prefixo no padrão de nome da coleção:
Excluir (user, collection):
- Listar todas as coleções Qdrant que correspondem ao prefixo
rows_{user}_{collection}_ - Excluir cada coleção correspondente
- Excluir partições de linhas do Cassandra (como documentado acima)
- Limpar as entradas
row_partitions
Excluir (user, collection, schema_name):
- Listar todas as coleções Qdrant que correspondem ao prefixo
rows_{user}_{collection}_{schema_name}_ - Excluir cada coleção correspondente (lida com múltiplas dimensões)
- Excluir partições de linhas do Cassandra
- Limpar
row_partitions
Localizações dos Módulos
| Estágio | Módulo | Ponto de Entrada |
|---|---|---|
| Estágio 1 | trustgraph-flow/trustgraph/embeddings/row_embeddings/ |
row-embeddings |
| Estágio 2 | trustgraph-flow/trustgraph/storage/row_embeddings/qdrant/ |
row-embeddings-write-qdrant |
API de Consulta de Incorporações de Linhas
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
Esquema de Solicitação/Resposta
@dataclass
class RowEmbeddingsRequest:
vectors: list[list[float]] # Query vectors (pre-computed embeddings)
user: str = ""
collection: str = ""
schema_name: str = ""
index_name: str = "" # Optional: filter to specific index
limit: int = 10 # Max results per vector
@dataclass
class RowIndexMatch:
index_name: str = "" # The matched index field(s)
index_value: list[str] = [] # The matched value(s)
text: str = "" # Original text that was embedded
score: float = 0.0 # Similarity score
@dataclass
class RowEmbeddingsResponse:
error: Error | None = None
matches: list[RowIndexMatch] = []
Processador de Consultas
Módulo: trustgraph-flow/trustgraph/query/row_embeddings/qdrant
Ponto de entrada: row-embeddings-query-qdrant
O processador:
- Recebe
RowEmbeddingsRequestcom vetores de consulta - Encontra a coleção Qdrant apropriada por correspondência de prefixo
- Procura os vetores mais próximos com filtro opcional
index_name - Retorna
RowEmbeddingsResponsecom informações do índice correspondente
Integração com o Gateway de API
O gateway expõe consultas de incorporações de linhas através do padrão padrão de solicitação/resposta:
| Componente | Localização |
|---|---|
| Dispatcher | trustgraph-flow/trustgraph/gateway/dispatch/row_embeddings_query.py |
| Registro | Adicione "row-embeddings" a request_response_dispatchers em manager.py |
Nome da interface do fluxo: row-embeddings
Definição da interface no blueprint do fluxo:
{
"interfaces": {
"row-embeddings": {
"request": "non-persistent://tg/request/row-embeddings:{id}",
"response": "non-persistent://tg/response/row-embeddings:{id}"
}
}
}
Suporte ao SDK Python
O SDK fornece métodos para consultas de incorporações de linhas:
# Flow-scoped query (preferred)
api = Api(url)
flow = api.flow().id("default")
# Query with text (SDK computes embeddings)
matches = flow.row_embeddings_query(
text="Chestnut Street",
collection="my_collection",
schema_name="addresses",
index_name="street_name", # Optional filter
limit=10
)
# Query with pre-computed vectors
matches = flow.row_embeddings_query(
vectors=[[0.1, 0.2, ...]],
collection="my_collection",
schema_name="addresses"
)
# Each match contains:
for match in matches:
print(match.index_name) # e.g., "street_name"
print(match.index_value) # e.g., ["CHESTNUT ST"]
print(match.text) # e.g., "CHESTNUT ST"
print(match.score) # e.g., 0.95
Utilitário de Linha de Comando (CLI)
Comando: tg-invoke-row-embeddings
# Query by text (computes embedding automatically)
tg-invoke-row-embeddings \
--text "Chestnut Street" \
--collection my_collection \
--schema addresses \
--index street_name \
--limit 10
# Query by vector file
tg-invoke-row-embeddings \
--vectors vectors.json \
--collection my_collection \
--schema addresses
# Output formats
tg-invoke-row-embeddings --text "..." --format json
tg-invoke-row-embeddings --text "..." --format table
Padrão de Uso Típico
A consulta de incorporações de linhas é normalmente usada como parte de um fluxo de pesquisa aproximada para correspondência exata:
# Step 1: Fuzzy search via embeddings
matches = flow.row_embeddings_query(
text="chestnut street",
collection="geo",
schema_name="streets"
)
# Step 2: Exact lookup via GraphQL for full row data
for match in matches:
query = f'''
query {{
streets(where: {{ {match.index_name}: {{ eq: "{match.index_value[0]}" }} }}) {{
street_name
city
zip_code
}}
}}
'''
rows = flow.rows_query(query, collection="geo")
Este padrão de duas etapas permite: 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:
SELECT * FROM {keyspace}.o_{schema_name}
WHERE collection = 'X' AND email = 'foo@bar.com'
ALLOW FILTERING
Novo Padrão de Consulta:
SELECT * FROM {keyspace}.rows
WHERE collection = 'X' AND schema_name = 'customers'
AND index_name = 'email' AND index_value = ['foo@bar.com']
Mudanças Principais
-
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 -
O código GraphQL está fortemente acoplado: O código
service.pyatual 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:
trustgraph-flow/trustgraph/query/graphql/
├── __init__.py
├── types.py # Filter types (IntFilter, StringFilter, FloatFilter)
├── schema.py # Dynamic schema generation from RowSchema
└── filters.py # Filter parsing utilities
Isso permite: Reutilização em diferentes backends de armazenamento de dados. Separação mais clara de responsabilidades. Teste mais fácil da lógica GraphQL de forma independente.
2. Implementar Novo Esquema de Tabela
Refatorar o código específico do Cassandra para usar a tabela unificada:
Escritor (trustgraph-flow/trustgraph/storage/rows/cassandra/):
Uma única tabela rows em vez de tabelas por esquema.
Escrever N cópias por linha (uma por índice).
Registrar na tabela row_partitions.
Criação de tabela mais simples (configuração única).
Serviço de Consulta (trustgraph-flow/trustgraph/query/rows/cassandra/):
Consultar a tabela rows unificada.
Usar o módulo GraphQL extraído para geração de esquema.
Tratamento de filtros simplificado (apenas correspondência exata no nível do banco de dados).
Renomeação de Módulos
Como parte da limpeza de nomes de "objeto" para "linha":
| Atual | Novo |
|---|---|
storage/objects/cassandra/ |
storage/rows/cassandra/ |
query/objects/cassandra/ |
query/rows/cassandra/ |
embeddings/object_embeddings/ |
embeddings/row_embeddings/ |
Novos Módulos
| Módulo | Propósito |
|---|---|
trustgraph-flow/trustgraph/query/graphql/ |
Utilitários GraphQL compartilhados |
trustgraph-flow/trustgraph/query/row_embeddings/qdrant/ |
API de consulta de incorporações de linhas |
trustgraph-flow/trustgraph/embeddings/row_embeddings/ |
Cálculo de incorporações de linhas (Etapa 1) |
trustgraph-flow/trustgraph/storage/row_embeddings/qdrant/ |
Armazenamento de incorporações de linhas (Etapa 2) |