trustgraph/docs/tech-specs/streaming-llm-responses.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

20 KiB

layout title parent
default Especificação Técnica de Respostas de LLM em Streaming Portuguese (Beta)

Especificação Técnica de Respostas de LLM em Streaming

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 a implementação do suporte a streaming para respostas de LLM no TrustGraph. O streaming permite a entrega em tempo real de tokens gerados à medida que são produzidos pelo LLM, em vez de esperar pela geração completa da resposta.

Esta implementação suporta os seguintes casos de uso:

  1. Interfaces de Usuário em Tempo Real: Transmita tokens para a interface do usuário à medida que são gerados, fornecendo feedback visual imediato.
  2. Tempo de Primeiro Token Reduzido: Os usuários veem a saída começando imediatamente em vez de esperar pela geração completa.
  3. Tratamento de Respostas Longas: Lidar com saídas muito longas que, de outra forma, poderiam causar timeout ou exceder os limites de memória.
  4. Aplicações Interativas: Permitir interfaces de bate-papo e agentes responsivas.

Objetivos

Compatibilidade com Versões Anteriores: Os clientes existentes que não utilizam streaming continuam a funcionar sem modificação. Design de API Consistente: O streaming e o uso sem streaming utilizam os mesmos padrões de esquema com mínima divergência. Flexibilidade do Provedor: Suporte ao streaming quando disponível, com uma alternativa suave quando não disponível. Implementação Gradual: Implementação incremental para reduzir o risco. Suporte de Ponta a Ponta: Streaming do provedor de LLM até as aplicações do cliente via Pulsar, Gateway API e Python API.

Contexto

Arquitetura Atual

O fluxo atual de conclusão de texto de LLM opera da seguinte forma:

  1. O cliente envia TextCompletionRequest com os campos system e prompt.
  2. O serviço de LLM processa a solicitação e espera pela geração completa.
  3. Um único TextCompletionResponse é retornado com a string response completa.

Esquema atual (trustgraph-base/trustgraph/schema/services/llm.py):

class TextCompletionRequest(Record):
    system = String()
    prompt = String()

class TextCompletionResponse(Record):
    error = Error()
    response = String()
    in_token = Integer()
    out_token = Integer()
    model = String()

Limitações Atuais

Latência: Os usuários devem esperar pela geração completa antes de ver qualquer resultado. Risco de Timeout: Gerações longas podem exceder os limites de tempo de espera do cliente. Má Experiência do Usuário: A falta de feedback durante a geração cria a percepção de lentidão. Uso de Recursos: As respostas completas devem ser armazenadas em memória.

Esta especificação aborda essas limitações, permitindo a entrega incremental de respostas, mantendo total compatibilidade com versões anteriores.

Design Técnico

Fase 1: Infraestrutura

A Fase 1 estabelece a base para o streaming, modificando esquemas, APIs e ferramentas de linha de comando.

Alterações no Esquema

Esquema LLM (trustgraph-base/trustgraph/schema/services/llm.py)

Alterações na Requisição:

class TextCompletionRequest(Record):
    system = String()
    prompt = String()
    streaming = Boolean()  # NEW: Default false for backward compatibility

streaming: Quando true, solicita a entrega de resposta em fluxo. Padrão: false (o comportamento existente é preservado).

Alterações na Resposta:

class TextCompletionResponse(Record):
    error = Error()
    response = String()
    in_token = Integer()
    out_token = Integer()
    model = String()
    end_of_stream = Boolean()  # NEW: Indicates final message

end_of_stream: Quando true, indica que esta é a resposta final (ou única). Para solicitações não em fluxo contínuo: Resposta única com end_of_stream=true. Para solicitações em fluxo contínuo: Múltiplas respostas, todas com end_of_stream=false exceto a última.

Esquema do Prompt (trustgraph-base/trustgraph/schema/services/prompt.py)

O serviço de prompt envolve a conclusão de texto, portanto, ele segue o mesmo padrão:

Alterações na Solicitação:

class PromptRequest(Record):
    id = String()
    terms = Map(String())
    streaming = Boolean()  # NEW: Default false

Alterações na Resposta:

class PromptResponse(Record):
    error = Error()
    text = String()
    object = String()
    end_of_stream = Boolean()  # NEW: Indicates final message

Alterações na API Gateway

A API Gateway deve expor capacidades de streaming para clientes HTTP/WebSocket.

Atualizações da API REST:

POST /api/v1/text-completion: Aceitar o parâmetro streaming no corpo da requisição O comportamento da resposta depende da flag de streaming: streaming=false: Resposta JSON única (comportamento atual) streaming=true: Fluxo de eventos enviados pelo servidor (SSE) ou mensagens WebSocket

Formato da Resposta (Streaming):

Cada bloco transmitido segue a mesma estrutura de esquema:

{
  "response": "partial text...",
  "end_of_stream": false,
  "model": "model-name"
}

Trecho final:

{
  "response": "final text chunk",
  "end_of_stream": true,
  "in_token": 150,
  "out_token": 500,
  "model": "model-name"
}

Alterações na API Python

A API do cliente Python deve suportar tanto o modo de streaming quanto o modo não-streaming, mantendo a compatibilidade com versões anteriores.

Atualizações do LlmClient (trustgraph-base/trustgraph/clients/llm_client.py):

class LlmClient(BaseClient):
    def request(self, system, prompt, timeout=300, streaming=False):
        """
        Non-streaming request (backward compatible).
        Returns complete response string.
        """
        # Existing behavior when streaming=False

    async def request_stream(self, system, prompt, timeout=300):
        """
        Streaming request.
        Yields response chunks as they arrive.
        """
        # New async generator method

Atualizações do PromptClient (trustgraph-base/trustgraph/base/prompt_client.py):

Padrão semelhante com o parâmetro streaming e a variante de gerador assíncrono.

Alterações na Ferramenta de Linha de Comando (CLI)

tg-invoke-llm (trustgraph-cli/trustgraph/cli/invoke_llm.py):

tg-invoke-llm [system] [prompt] [--no-streaming] [-u URL] [-f flow-id]

Streaming habilitado por padrão para uma melhor experiência de usuário interativa. A flag --no-streaming desabilita o streaming. Quando o streaming está habilitado: Envie os tokens para a saída padrão (stdout) à medida que chegam. Quando o streaming não está habilitado: Aguarde a resposta completa e, em seguida, envie.

tg-invoke-prompt (trustgraph-cli/trustgraph/cli/invoke_prompt.py):

tg-invoke-prompt [template-id] [var=value...] [--no-streaming] [-u URL] [-f flow-id]

Mesmo padrão que tg-invoke-llm.

Alterações na Classe Base do Serviço LLM

LlmService (trustgraph-base/trustgraph/base/llm_service.py):

class LlmService(FlowProcessor):
    async def on_request(self, msg, consumer, flow):
        request = msg.value()
        streaming = getattr(request, 'streaming', False)

        if streaming and self.supports_streaming():
            async for chunk in self.generate_content_stream(...):
                await self.send_response(chunk, end_of_stream=False)
            await self.send_response(final_chunk, end_of_stream=True)
        else:
            response = await self.generate_content(...)
            await self.send_response(response, end_of_stream=True)

    def supports_streaming(self):
        """Override in subclass to indicate streaming support."""
        return False

    async def generate_content_stream(self, system, prompt, model, temperature):
        """Override in subclass to implement streaming."""
        raise NotImplementedError()

--

Fase 2: Prova de Conceito do VertexAI

A Fase 2 implementa o streaming em um único provedor (VertexAI) para validar a infraestrutura e permitir testes de ponta a ponta.

Implementação do VertexAI

Módulo: trustgraph-vertexai/trustgraph/model/text_completion/vertexai/llm.py

Alterações:

  1. Substituir supports_streaming() para retornar True
  2. Implementar gerador assíncrono generate_content_stream()
  3. Lidar com modelos Gemini e Claude (via API Anthropic do VertexAI)

Streaming do Gemini:

async def generate_content_stream(self, system, prompt, model, temperature):
    model_instance = self.get_model(model, temperature)
    response = model_instance.generate_content(
        [system, prompt],
        stream=True  # Enable streaming
    )
    for chunk in response:
        yield LlmChunk(
            text=chunk.text,
            in_token=None,  # Available only in final chunk
            out_token=None,
        )
    # Final chunk includes token counts from response.usage_metadata

Claude (via VertexAI Anthropic) Streaming:

async def generate_content_stream(self, system, prompt, model, temperature):
    with self.anthropic_client.messages.stream(...) as stream:
        for text in stream.text_stream:
            yield LlmChunk(text=text)
    # Token counts from stream.get_final_message()

Testes

Testes unitários para a montagem da resposta em streaming Testes de integração com o VertexAI (Gemini e Claude) Testes de ponta a ponta: CLI -> Gateway -> Pulsar -> VertexAI -> de volta Testes de compatibilidade com versões anteriores: as solicitações não em streaming ainda funcionam

--

Fase 3: Todos os Provedores de LLM

A Fase 3 estende o suporte a streaming para todos os provedores de LLM no sistema.

Status de Implementação do Provedor

Cada provedor deve:

  1. Suporte Completo a Streaming: Implementar generate_content_stream()
  2. Modo de Compatibilidade: Lidar com a flag end_of_stream corretamente (retornar uma única resposta com end_of_stream=true)
Provedor Pacote Suporte a Streaming
OpenAI trustgraph-flow Completo (API de streaming nativa)
Claude/Anthropic trustgraph-flow Completo (API de streaming nativa)
Ollama trustgraph-flow Completo (API de streaming nativa)
Cohere trustgraph-flow Completo (API de streaming nativa)
Mistral trustgraph-flow Completo (API de streaming nativa)
Azure OpenAI trustgraph-flow Completo (API de streaming nativa)
Google AI Studio trustgraph-flow Completo (API de streaming nativa)
VertexAI trustgraph-vertexai Completo (Fase 2)
Bedrock trustgraph-bedrock Completo (API de streaming nativa)
LM Studio trustgraph-flow Completo (compatível com OpenAI)
LlamaFile trustgraph-flow Completo (compatível com OpenAI)
vLLM trustgraph-flow Completo (compatível com OpenAI)
TGI trustgraph-flow A ser definido
Azure trustgraph-flow A ser definido

Padrão de Implementação

Para provedores compatíveis com OpenAI (OpenAI, LM Studio, LlamaFile, vLLM):

async def generate_content_stream(self, system, prompt, model, temperature):
    response = await self.client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": system},
            {"role": "user", "content": prompt}
        ],
        temperature=temperature,
        stream=True
    )
    async for chunk in response:
        if chunk.choices[0].delta.content:
            yield LlmChunk(text=chunk.choices[0].delta.content)

--

Fase 4: API do Agente

A Fase 4 estende o streaming para a API do Agente. Isso é mais complexo porque a API do Agente já é inerentemente multi-mensagem (pensamento → ação → observação → repetir → resposta final).

Esquema Atual do Agente

class AgentStep(Record):
    thought = String()
    action = String()
    arguments = Map(String())
    observation = String()
    user = String()

class AgentRequest(Record):
    question = String()
    state = String()
    group = Array(String())
    history = Array(AgentStep())
    user = String()

class AgentResponse(Record):
    answer = String()
    error = Error()
    thought = String()
    observation = String()

Alterações Propostas no Esquema do Agente

Solicitar Alterações:

class AgentRequest(Record):
    question = String()
    state = String()
    group = Array(String())
    history = Array(AgentStep())
    user = String()
    streaming = Boolean()  # NEW: Default false

Alterações na Resposta:

O agente produz múltiplos tipos de saída durante seu ciclo de raciocínio: Pensamentos (raciocínio) Ações (chamadas de ferramentas) Observações (resultados das ferramentas) Resposta (resposta final) Erros

Como chunk_type identifica o tipo de conteúdo que está sendo enviado, os campos separados answer, error, thought e observation podem ser combinados em um único campo content:

class AgentResponse(Record):
    chunk_type = String()       # "thought", "action", "observation", "answer", "error"
    content = String()          # The actual content (interpretation depends on chunk_type)
    end_of_message = Boolean()  # Current thought/action/observation/answer is complete
    end_of_dialog = Boolean()   # Entire agent dialog is complete

Semântica dos Campos:

chunk_type: Indica o tipo de conteúdo presente no campo content "thought": Raciocínio/pensamento do agente "action": Ferramenta/ação sendo invocada "observation": Resultado da execução da ferramenta "answer": Resposta final à pergunta do usuário "error": Mensagem de erro

content: O conteúdo transmitido, interpretado com base em chunk_type

end_of_message: Quando true, o tipo de bloco atual está completo Exemplo: Todos os tokens para o pensamento atual foram enviados Permite que os clientes saibam quando avançar para a próxima etapa

end_of_dialog: Quando true, toda a interação do agente está completa Esta é a mensagem final no fluxo

Comportamento de Streaming do Agente

Quando streaming=true:

  1. Streaming de pensamento: Múltiplos blocos com chunk_type="thought", end_of_message=false O bloco final do pensamento tem end_of_message=true
  2. Notificação de ação: Um único bloco com chunk_type="action", end_of_message=true
  3. Observação: Bloco(s) com chunk_type="observation", o final tem end_of_message=true
  4. Repita as etapas 1-3 enquanto o agente raciocina
  5. Resposta final: chunk_type="answer" com a resposta final em content O último bloco tem end_of_message=true, end_of_dialog=true

Exemplo de Sequência de Streaming:

{chunk_type: "thought", content: "I need to", end_of_message: false, end_of_dialog: false}
{chunk_type: "thought", content: " search for...", end_of_message: true, end_of_dialog: false}
{chunk_type: "action", content: "search", end_of_message: true, end_of_dialog: false}
{chunk_type: "observation", content: "Found: ...", end_of_message: true, end_of_dialog: false}
{chunk_type: "thought", content: "Based on this", end_of_message: false, end_of_dialog: false}
{chunk_type: "thought", content: " I can answer...", end_of_message: true, end_of_dialog: false}
{chunk_type: "answer", content: "The answer is...", end_of_message: true, end_of_dialog: true}

Quando streaming=false: Comportamento atual preservado Resposta única com resposta completa end_of_message=true, end_of_dialog=true

Gateway e API Python

Gateway: Novo endpoint SSE/WebSocket para streaming de agentes API Python: Novo método gerador assíncrono agent_stream()

--

Considerações de Segurança

Nenhuma nova superfície de ataque: O streaming usa a mesma autenticação/autorização Limitação de taxa: Aplique limites de taxa por token ou por bloco, se necessário Gerenciamento de conexão: Termine corretamente os streams em caso de desconexão do cliente Gerenciamento de tempo limite: As solicitações de streaming precisam de um tratamento de tempo limite adequado

Considerações de Desempenho

Memória: O streaming reduz o uso máximo de memória (sem bufferização completa da resposta) Latência: O tempo para o primeiro token é significativamente reduzido Sobrecarga de conexão: As conexões SSE/WebSocket têm uma sobrecarga de keep-alive Throughput do Pulsar: Múltiplas mensagens pequenas vs. uma única mensagem grande tradeoff

Estratégia de Testes

Testes Unitários

Serialização/desserialização de esquema com novos campos Compatibilidade com versões anteriores (campos ausentes usam valores padrão) Lógica de montagem de blocos

Testes de Integração

Implementação de streaming de cada provedor de LLM Pontos finais de streaming da API Gateway Métodos de streaming do cliente Python

Testes de Ponta a Ponta

Saída de streaming da ferramenta CLI Fluxo completo: Cliente → Gateway → Pulsar → LLM → de volta Cargas de trabalho mistas de streaming/não streaming

Testes de Compatibilidade com Versões Anteriores

Clientes existentes funcionam sem modificação As solicitações não de streaming se comportam de forma idêntica

Plano de Migração

Fase 1: Infraestrutura

Implante as alterações de esquema (compatível com versões anteriores) Implante as atualizações da API Gateway Implante as atualizações da API Python Lance as atualizações da ferramenta CLI

Fase 2: VertexAI

Implementar a implementação de streaming do VertexAI Validar com cargas de trabalho de teste

Fase 3: Todos os Provedores

Implementar as atualizações do provedor de forma incremental Monitorar para identificar problemas

Fase 4: API do Agente

Implementar as alterações do esquema do agente Implementar a implementação de streaming do agente Atualizar a documentação

Cronograma

Fase Descrição Dependências
Fase 1 Infraestrutura Nenhum
Fase 2 Prova de Conceito do VertexAI Fase 1
Fase 3 Todos os Provedores Fase 2
Fase 4 API do Agente Fase 3

Decisões de Design

As seguintes perguntas foram resolvidas durante a especificação:

  1. Contagem de Tokens no Streaming: As contagens de tokens são diferenças, não totais cumulativos. Os consumidores podem somá-las, se necessário. Isso corresponde à forma como a maioria dos provedores relata o uso e simplifica a implementação.

  2. Tratamento de Erros em Streams: Se ocorrer um erro, o campo error é preenchido e nenhum outro campo é necessário. Um erro é sempre a comunicação final - nenhuma mensagem subsequente é permitida ou esperada após isso. um erro. Para fluxos de LLM/Prompt, end_of_stream=true. Para fluxos de Agente, chunk_type="error" com end_of_dialog=true.

  3. Recuperação Parcial de Respostas: O protocolo de mensagens (Pulsar) é resiliente, portanto, a repetição em nível de mensagem não é necessária. Se um cliente perder o controle do fluxo ou desconectar, ele deve repetir a solicitação completa do zero.

  4. Streaming de Respostas Rápidas: O streaming é suportado apenas para respostas de texto (text). As respostas estruturadas (object) não são suportadas. O serviço de respostas rápidas sabe, desde o início, se a saída será JSON ou texto, com base no modelo da solicitação. Se uma solicitação de streaming for feita para uma solicitação de saída JSON, o serviço deve: Retornar o JSON completo em uma única resposta com end_of_stream=true, ou Rejeitar a solicitação de streaming com um erro.

Perguntas Abertas

Nenhum neste momento.

Referências

Esquema atual do LLM: trustgraph-base/trustgraph/schema/services/llm.py Esquema atual do prompt: trustgraph-base/trustgraph/schema/services/prompt.py Esquema atual do agente: trustgraph-base/trustgraph/schema/services/agent.py Serviço base do LLM: trustgraph-base/trustgraph/base/llm_service.py Provedor VertexAI: trustgraph-vertexai/trustgraph/model/text_completion/vertexai/llm.py API de gateway: trustgraph-base/trustgraph/api/ Ferramentas de linha de comando: trustgraph-cli/trustgraph/cli/