trustgraph/docs/tech-specs/embeddings-batch-processing.pt.md
Alex Jenkins 8954fa3ad7 Feat: TrustGraph i18n & Documentation Translation Updates (#781)
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.
2026-04-14 12:08:32 +01:00

22 KiB

layout title parent
default Especificação Técnica de Processamento em Lote de Embeddings Portuguese (Beta)

Especificação Técnica de Processamento em Lote de Embeddings

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 descreve otimizações para o serviço de embeddings para suportar o processamento em lote de vários textos em um único pedido. A implementação atual processa um texto por vez, perdendo os benefícios de desempenho significativos que os modelos de embedding oferecem ao processar lotes.

  1. Ineficiência no Processamento de Texto Único: A implementação atual envolve textos únicos em uma lista, subutilizando as capacidades de lote do FastEmbed.
  2. Sobrecarga de Pedido por Texto: Cada texto requer uma viagem de ida e volta separada do Pulsar.
  3. Ineficiência na Inferência do Modelo: Os modelos de embedding têm uma sobrecarga fixa por lote; lotes pequenos desperdiçam recursos de GPU/CPU.
  4. Processamento Serial nos Clientes: Serviços importantes fazem loop sobre itens e chamam embeddings um de cada vez.

Objetivos

Suporte para API de Lote: Permitir o processamento de vários textos em um único pedido. Compatibilidade com Versões Anteriores: Manter o suporte para pedidos de texto único. Melhora Significativa no Desempenho: Almejar uma melhora de desempenho de 5 a 10 vezes para operações em lote. Latência Reduzida por Texto: Diminuir a latência amortizada ao incorporar vários textos. Eficiência de Memória: Processar lotes sem consumo excessivo de memória. Independente do Provedor: Suportar o processamento em lote em FastEmbed, Ollama e outros provedores. Migração do Cliente: Atualizar todos os clientes de embedding para usar a API de lote sempre que for benéfico.

Contexto

Implementação Atual - Serviço de Embeddings

A implementação de embeddings em trustgraph-flow/trustgraph/embeddings/fastembed/processor.py apresenta uma ineficiência de desempenho significativa:

# fastembed/processor.py line 56
async def on_embeddings(self, text, model=None):
    use_model = model or self.default_model
    self._load_model(use_model)

    vecs = self.embeddings.embed([text])  # Single text wrapped in list

    return [v.tolist() for v in vecs]

Problemas:

  1. Tamanho do Lote 1: O método embed() do FastEmbed é otimizado para processamento em lote, mas sempre o chamamos com [text] - um lote de tamanho 1.

  2. Sobrecarga por Requisição: Cada solicitação de incorporação incorre em: Serialização/desserialização de mensagens Pulsar Latência de ida e volta da rede Sobrecarga de inicialização da inferência do modelo Sobrecarga de agendamento assíncrono do Python

  3. Limitação do Esquema: O esquema EmbeddingsRequest suporta apenas um único texto:

    @dataclass
    class EmbeddingsRequest:
        text: str = ""  # Single text only
    

Clientes Atuais - Processamento Serial

1. Gateway de API

Arquivo: trustgraph-flow/trustgraph/gateway/dispatch/embeddings.py

O gateway aceita solicitações de incorporação de texto único via HTTP/WebSocket e as encaminha para o serviço de incorporação. Atualmente, não existe um endpoint para processamento em lote.

class EmbeddingsRequestor(ServiceRequestor):
    # Handles single EmbeddingsRequest -> EmbeddingsResponse
    request_schema=EmbeddingsRequest,  # Single text only
    response_schema=EmbeddingsResponse,

Impacto: Clientes externos (aplicativos web, scripts) devem fazer N requisições HTTP para incorporar N textos.

2. Serviço de Incorporação de Documentos

Arquivo: trustgraph-flow/trustgraph/embeddings/document_embeddings/embeddings.py

Processa blocos de documentos um de cada vez:

async def on_message(self, msg, consumer, flow):
    v = msg.value()

    # Single chunk per request
    resp = await flow("embeddings-request").request(
        EmbeddingsRequest(text=v.chunk)
    )
    vectors = resp.vectors

Impacto: Cada trecho de documento requer uma chamada de embedding separada. Um documento com 100 trechos = 100 requisições de embedding.

3. Serviço de Embeddings de Grafos

Arquivo: trustgraph-flow/trustgraph/embeddings/graph_embeddings/embeddings.py

Percorre as entidades e incorpora cada uma sequencialmente:

async def on_message(self, msg, consumer, flow):
    for entity in v.entities:
        # Serial embedding - one entity at a time
        vectors = await flow("embeddings-request").embed(
            text=entity.context
        )
        entities.append(EntityEmbeddings(
            entity=entity.entity,
            vectors=vectors,
            chunk_id=entity.chunk_id,
        ))

Impacto: Uma mensagem com 50 entidades = 50 solicitações de incorporação serial. Isso é um grande gargalo durante a construção do grafo de conhecimento.

4. Serviço de Incorporação de Linhas

Arquivo: trustgraph-flow/trustgraph/embeddings/row_embeddings/embeddings.py

Percorre textos únicos e incorpora cada um serialmente:

async def on_message(self, msg, consumer, flow):
    for text, (index_name, index_value) in texts_to_embed.items():
        # Serial embedding - one text at a time
        vectors = await flow("embeddings-request").embed(text=text)

        embeddings_list.append(RowIndexEmbedding(
            index_name=index_name,
            index_value=index_value,
            text=text,
            vectors=vectors
        ))

Impacto: Processar uma tabela com 100 valores indexados únicos = 100 solicitações de incorporação seriais.

5. EmbeddingsClient (Cliente Base)

Arquivo: trustgraph-base/trustgraph/base/embeddings_client.py

O cliente usado por todos os processadores de fluxo suporta apenas a incorporação de texto único:

class EmbeddingsClient(RequestResponse):
    async def embed(self, text, timeout=30):
        resp = await self.request(
            EmbeddingsRequest(text=text),  # Single text
            timeout=timeout
        )
        return resp.vectors

Impacto: Todos os clientes que utilizam este componente estão limitados a operações de texto único.

6. Ferramentas de Linha de Comando

Arquivo: trustgraph-cli/trustgraph/cli/invoke_embeddings.py

A ferramenta de linha de comando aceita um único argumento de texto:

def query(url, flow_id, text, token=None):
    result = flow.embeddings(text=text)  # Single text
    vectors = result.get("vectors", [])

Impacto: Os usuários não podem realizar incorporações em lote a partir da linha de comando. O processamento de um arquivo de textos requer N invocações.

7. SDK Python

O SDK Python fornece duas classes de cliente para interagir com os serviços da TrustGraph. Ambas suportam apenas a incorporação de um único texto.

Arquivo: trustgraph-base/trustgraph/api/flow.py

class FlowInstance:
    def embeddings(self, text):
        """Get embeddings for a single text"""
        input = {"text": text}
        return self.request("service/embeddings", input)["vectors"]

Arquivo: trustgraph-base/trustgraph/api/socket_client.py

class SocketFlowInstance:
    def embeddings(self, text: str, **kwargs: Any) -> Dict[str, Any]:
        """Get embeddings for a single text via WebSocket"""
        request = {"text": text}
        return self.client._send_request_sync(
            "embeddings", self.flow_id, request, False
        )

Impacto: Desenvolvedores Python que usam o SDK precisam iterar sobre textos e fazer N chamadas de API separadas. Não existe suporte para processamento em lote de embeddings para usuários do SDK.

Impacto no Desempenho

Para ingestão típica de documentos (1000 trechos de texto): Atual: 1000 requisições separadas, 1000 chamadas de inferência de modelo Em lote (batch_size=32): 32 requisições, 32 chamadas de inferência de modelo (redução de 96,8%)

Para embedding de grafos (mensagem com 50 entidades): Atual: 50 chamadas await sequenciais, ~5-10 segundos Em lote: 1-2 chamadas em lote, ~0,5-1 segundo (melhora de 5-10x)

Bibliotecas como FastEmbed e similares alcançam escalabilidade quase linear no throughput com o tamanho do lote, até os limites do hardware (tipicamente 32-128 textos por lote).

Design Técnico

Arquitetura

A otimização de processamento em lote de embeddings requer alterações nos seguintes componentes:

1. Aprimoramento do Esquema

Estender EmbeddingsRequest para suportar múltiplos textos Estender EmbeddingsResponse para retornar múltiplos conjuntos de vetores Manter a compatibilidade retroativa com requisições de texto único

Módulo: trustgraph-base/trustgraph/schema/services/llm.py

2. Aprimoramento do Serviço Base

Atualizar EmbeddingsService para lidar com requisições em lote Adicionar configuração do tamanho do lote Implementar tratamento de requisições com suporte a lote

Módulo: trustgraph-base/trustgraph/base/embeddings_service.py

3. Atualizações do Processador do Provedor

Atualizar o processador FastEmbed para passar o lote completo para embed() Atualizar o processador Ollama para lidar com lotes (se suportado) Adicionar processamento sequencial como fallback para provedores sem suporte a lote

Módulos: trustgraph-flow/trustgraph/embeddings/fastembed/processor.py trustgraph-flow/trustgraph/embeddings/ollama/processor.py

4. Aprimoramento do Cliente

Adicionar método de embedding em lote para EmbeddingsClient Suportar APIs de texto único e em lote Adicionar agrupamento automático para grandes entradas

Módulo: trustgraph-base/trustgraph/base/embeddings_client.py

5. Atualizações do Chamador - Processadores de Fluxo

Atualizar graph_embeddings para agrupar contextos de entidades Atualizar row_embeddings para agrupar textos de índice Atualizar document_embeddings se o agrupamento de mensagens for viável

Módulos: trustgraph-flow/trustgraph/embeddings/graph_embeddings/embeddings.py trustgraph-flow/trustgraph/embeddings/row_embeddings/embeddings.py trustgraph-flow/trustgraph/embeddings/document_embeddings/embeddings.py

6. Aprimoramento do Gateway de API

Adicionar endpoint de embedding em lote Suportar array de textos no corpo da requisição

Módulo: trustgraph-flow/trustgraph/gateway/dispatch/embeddings.py

7. Aprimoramento da Ferramenta de Linha de Comando (CLI)

Adicionar suporte para múltiplos textos ou entrada de arquivo Adicionar parâmetro de tamanho do lote

Módulo: trustgraph-cli/trustgraph/cli/invoke_embeddings.py

8. Aprimoramento do SDK Python

Adicionar método embeddings_batch() para FlowInstance Adicionar método embeddings_batch() para SocketFlowInstance Suportar APIs de texto único e em lote para usuários do SDK

Módulos: trustgraph-base/trustgraph/api/flow.py trustgraph-base/trustgraph/api/socket_client.py

Modelos de Dados

EmbeddingsRequest

@dataclass
class EmbeddingsRequest:
    texts: list[str] = field(default_factory=list)

Uso: Texto único: EmbeddingsRequest(texts=["hello world"]) Lote: EmbeddingsRequest(texts=["text1", "text2", "text3"])

EmbeddingsResponse

@dataclass
class EmbeddingsResponse:
    error: Error | None = None
    vectors: list[list[list[float]]] = field(default_factory=list)

Estrutura da resposta: vectors[i] contém o conjunto de vetores para texts[i] Cada conjunto de vetores é list[list[float]] (os modelos podem retornar vários vetores por texto) Exemplo: 3 textos → vectors tem 3 entradas, cada uma contendo os embeddings desse texto

APIs

EmbeddingsClient

class EmbeddingsClient(RequestResponse):
    async def embed(
        self,
        texts: list[str],
        timeout: float = 300,
    ) -> list[list[list[float]]]:
        """
        Embed one or more texts in a single request.

        Args:
            texts: List of texts to embed
            timeout: Timeout for the operation

        Returns:
            List of vector sets, one per input text
        """
        resp = await self.request(
            EmbeddingsRequest(texts=texts),
            timeout=timeout
        )
        if resp.error:
            raise RuntimeError(resp.error.message)
        return resp.vectors

Endpoint de Incorporação do Gateway de API

Endpoint atualizado que suporta incorporação única ou em lote:

POST /api/v1/embeddings
Content-Type: application/json

{
    "texts": ["text1", "text2", "text3"],
    "flow_id": "default"
}

Response:
{
    "vectors": [
        [[0.1, 0.2, ...]],
        [[0.3, 0.4, ...]],
        [[0.5, 0.6, ...]]
    ]
}

Detalhes de Implementação

Fase 1: Alterações no Esquema

EmbeddingsRequest:

@dataclass
class EmbeddingsRequest:
    texts: list[str] = field(default_factory=list)

EmbeddingsResponse:

@dataclass
class EmbeddingsResponse:
    error: Error | None = None
    vectors: list[list[list[float]]] = field(default_factory=list)

Atualizado EmbeddingsService.on_request:

async def on_request(self, msg, consumer, flow):
    request = msg.value()
    id = msg.properties()["id"]
    model = flow("model")

    vectors = await self.on_embeddings(request.texts, model=model)
    response = EmbeddingsResponse(error=None, vectors=vectors)

    await flow("response").send(response, properties={"id": id})

Fase 2: Atualização do Processador FastEmbed

Atual (Ineficiente):

async def on_embeddings(self, text, model=None):
    use_model = model or self.default_model
    self._load_model(use_model)
    vecs = self.embeddings.embed([text])  # Batch of 1
    return [v.tolist() for v in vecs]

Atualizado:

async def on_embeddings(self, texts: list[str], model=None):
    """Embed texts - processes all texts in single model call"""
    if not texts:
        return []

    use_model = model or self.default_model
    self._load_model(use_model)

    # FastEmbed handles the full batch efficiently
    all_vecs = list(self.embeddings.embed(texts))

    # Return list of vector sets, one per input text
    return [[v.tolist()] for v in all_vecs]

Fase 3: Atualização do Serviço de Incorporação de Grafos

Atual (Serial):

async def on_message(self, msg, consumer, flow):
    entities = []
    for entity in v.entities:
        vectors = await flow("embeddings-request").embed(text=entity.context)
        entities.append(EntityEmbeddings(...))

Atualizado (Lote):

async def on_message(self, msg, consumer, flow):
    # Collect all contexts
    contexts = [entity.context for entity in v.entities]

    # Single batch embedding call
    all_vectors = await flow("embeddings-request").embed(texts=contexts)

    # Pair results with entities
    entities = [
        EntityEmbeddings(
            entity=entity.entity,
            vectors=vectors[0],  # First vector from the set
            chunk_id=entity.chunk_id,
        )
        for entity, vectors in zip(v.entities, all_vectors)
    ]

Fase 4: Atualização do Serviço de Incorporação de Dados

Atual (Serial):

for text, (index_name, index_value) in texts_to_embed.items():
    vectors = await flow("embeddings-request").embed(text=text)
    embeddings_list.append(RowIndexEmbedding(...))

Atualizado (Lote):

# Collect texts and metadata
texts = list(texts_to_embed.keys())
metadata = list(texts_to_embed.values())

# Single batch embedding call
all_vectors = await flow("embeddings-request").embed(texts=texts)

# Pair results
embeddings_list = [
    RowIndexEmbedding(
        index_name=meta[0],
        index_value=meta[1],
        text=text,
        vectors=vectors[0]  # First vector from the set
    )
    for text, meta, vectors in zip(texts, metadata, all_vectors)
]

Fase 5: Melhoria da Ferramenta de Linha de Comando (CLI)

CLI Atualizado:

def main():
    parser = argparse.ArgumentParser(...)

    parser.add_argument(
        'text',
        nargs='*',  # Zero or more texts
        help='Text(s) to convert to embedding vectors',
    )

    parser.add_argument(
        '-f', '--file',
        help='File containing texts (one per line)',
    )

    parser.add_argument(
        '--batch-size',
        type=int,
        default=32,
        help='Batch size for processing (default: 32)',
    )

Uso:

# Single text (existing)
tg-invoke-embeddings "hello world"

# Multiple texts
tg-invoke-embeddings "text one" "text two" "text three"

# From file
tg-invoke-embeddings -f texts.txt --batch-size 64

Fase 6: Melhoria do SDK Python

FlowInstance (cliente HTTP):

class FlowInstance:
    def embeddings(self, texts: list[str]) -> list[list[list[float]]]:
        """
        Get embeddings for one or more texts.

        Args:
            texts: List of texts to embed

        Returns:
            List of vector sets, one per input text
        """
        input = {"texts": texts}
        return self.request("service/embeddings", input)["vectors"]

SocketFlowInstance (cliente WebSocket):

class SocketFlowInstance:
    def embeddings(self, texts: list[str], **kwargs: Any) -> list[list[list[float]]]:
        """
        Get embeddings for one or more texts via WebSocket.

        Args:
            texts: List of texts to embed

        Returns:
            List of vector sets, one per input text
        """
        request = {"texts": texts}
        response = self.client._send_request_sync(
            "embeddings", self.flow_id, request, False
        )
        return response["vectors"]

Exemplos de uso do SDK:

# Single text
vectors = flow.embeddings(["hello world"])
print(f"Dimensions: {len(vectors[0][0])}")

# Batch embedding
texts = ["text one", "text two", "text three"]
all_vectors = flow.embeddings(texts)

# Process results
for text, vecs in zip(texts, all_vectors):
    print(f"{text}: {len(vecs[0])} dimensions")

Considerações de Segurança

Limites de Tamanho da Requisição: Impor o tamanho máximo do lote para evitar o esgotamento de recursos. Tratamento de Tempo Limite (Timeout): Ajustar os tempos limite de acordo com o tamanho do lote. Limites de Memória: Monitorar o uso de memória para grandes lotes. Validação de Entrada: Validar todos os textos no lote antes do processamento.

Considerações de Desempenho

Melhorias Esperadas

Taxa de Transferência (Throughput): Texto único: ~10-50 textos/segundo (dependendo do modelo) Lote (tamanho 32): ~200-500 textos/segundo (melhora de 5 a 10 vezes)

Latência por Texto: Texto único: 50-200ms por texto Lote (tamanho 32): 5-20ms por texto (média)

Melhorias Específicas do Serviço:

Serviço Atual Em Lote Melhoria
Incorporações de Grafos (50 entidades) 5-10s 0,5-1s 5-10x
Incorporações de Linhas (100 textos) 10-20s 1-2s 5-10x
Ingestão de Documentos (1000 fragmentos) 100-200s 10-30s 5-10x

Parâmetros de Configuração

# Recommended defaults
DEFAULT_BATCH_SIZE = 32
MAX_BATCH_SIZE = 128
BATCH_TIMEOUT_MULTIPLIER = 2.0

Estratégia de Testes

Testes Unitários

Incorporação de texto única (compatibilidade com versões anteriores) Tratamento de lotes vazios Imposição do tamanho máximo do lote Tratamento de erros para falhas parciais do lote

Testes de Integração

Incorporação de lote de ponta a ponta através do Pulsar Processamento de lote do serviço de incorporação de grafos Processamento de lote do serviço de incorporação de linhas Endpoint de lote do gateway de API

Testes de Desempenho

Comparação de benchmark de taxa de transferência única versus lote Uso de memória sob vários tamanhos de lote Análise da distribuição de latência

Plano de Migração

Esta é uma versão que introduz alterações significativas. Todas as fases são implementadas juntas.

Fase 1: Alterações no Esquema

Substituir text: str por texts: list[str] em EmbeddingsRequest Alterar o tipo de vectors para list[list[list[float]]] em EmbeddingsResponse

Fase 2: Atualizações do Processador

Atualizar a assinatura de on_embeddings nos processadores FastEmbed e Ollama Processar o lote completo em uma única chamada de modelo

Fase 3: Atualizações do Cliente

Atualizar EmbeddingsClient.embed() para aceitar texts: list[str]

Fase 4: Atualizações do Chamador

Atualizar graph_embeddings para incorporar contextos de entidades em lote Atualizar row_embeddings para incorporar textos de índice em lote Atualizar document_embeddings para usar o novo esquema Atualizar a ferramenta de linha de comando (CLI)

Fase 5: Gateway de API

Atualizar o endpoint de incorporação para o novo esquema

Fase 6: SDK Python

Atualizar a assinatura de FlowInstance.embeddings() Atualizar a assinatura de SocketFlowInstance.embeddings()

Perguntas Abertas

Incorporação de Lotes Grandes em Streaming: Devemos suportar o streaming de resultados para lotes muito grandes (>100 textos)? Limites Específicos do Provedor: Como devemos lidar com provedores com tamanhos máximos de lote diferentes? Tratamento de Falhas Parciais: Se um texto em um lote falhar, devemos falhar o lote inteiro ou retornar resultados parciais? Incorporação em Lote de Documentos: Devemos incorporar em lote em várias mensagens de Chunk ou manter o processamento por mensagem?

Referências

Documentação do FastEmbed API de Incorporação do Ollama Implementação do Serviço de Incorporação Otimização de Desempenho do GraphRAG