SiCore TokenWorks
LLM APIAPI GatewayAggregation

Fábrica de Tokens 8341: após integrar APIs de quatro grandes modelos, travamentos no streaming — a causa raiz está na camada de gateway

SiCore TokenWorks Team·2026-10-24

No inverno passado, ajudei uma equipe que desenvolvia atendimento inteligente ao cliente a investigar um problema em produção. O backend deles acessava simultaneamente as APIs de quatro grandes modelos, e o frontend usava SSE para saída em streaming. No terceiro dia após o lançamento, os usuários começaram a relatar que "as respostas saíam como pasta de dente sendo espremida" — às vezes um bloco inteiro de texto ficava travado sem se mover e de repente jorrava um monte; às vezes uma frase era cortada em duas, com blocos vazios estranhos no meio. Não havia 5xx nos logs do backend, e o monitoramento de disponibilidade de cada API estava todo verde.

Esse tipo de problema é o mais difícil de investigar, porque cada etapa parece normal. Acompanhei a equipe desde o painel Network do navegador, descascando camada por camada, e no final descobrimos que o problema não estava em nenhuma API específica, mas no próprio fato de "misturar quatro provedores".

Primeiro, a conclusão: protocolos de streaming não unificados são a armadilha invisível da integração agregada

A saída em streaming das APIs de grandes modelos, no mainstream, usa SSE (Server-Sent Events). Mas "todas usam SSE" não significa "comportamento consistente". Os quatro provedores têm implementações próprias em granularidade de chunking, marcador de término e estratégia de heartbeat. O fluxo de bytes que chega ao frontend parece todo começar com data:, mas a lógica de parsing não pode ser reaproveitada de forma alguma. Essa é a raiz dos travamentos e das anomalias nos limites dos blocos.

Investigação camada por camada: do frontend ao gateway, onde está o problema

Primeira camada, o frontend. O parsing de SSE deles usava o EventSource padrão, cortando eventos por \n\n. Isso em si não é problema.

Segunda camada, o repasse no backend. O backend usava Node para fazer uma camada de passthrough, fazendo pipe direto do ReadableStream upstream para a resposta. Aqui estava a primeira mina: o tamanho dos chunks varia muito entre provedores — alguns enviam por token, outros acumulam alguns tokens antes de enviar. Se durante o pipe for feito Buffer.concat sem tratar corretamente o limite entre chunks de caracteres multibyte UTF-8, um meio caractere chinês é cortado, e o frontend renderiza como um quadrado de caractere inválido.

Terceira camada, timeout e retry. O backend configurou um timeout unificado de 30 segundos para os quatro. Mas, em cenários de streaming, "timeout de 30 segundos" se refere ao tempo até o primeiro byte ou ao tempo do fluxo inteiro? Eles não distinguiram, o que fazia respostas longas serem cortadas no meio, aparecendo no frontend como "parou de repente no meio da fala".

Quarta camada, códigos de erro. Os códigos de status HTTP e as estruturas de body retornados pelos quatro provedores em casos de rate limit, falha de autenticação e bloqueio por moderação de conteúdo são todos diferentes. O backend extraía uniformemente via error.message; o resultado foi que em dois provedores conseguia extrair, e nos outros dois vinha undefined, e o frontend exibia uma mensagem de erro em branco.

Chegando aqui ficou claro: o problema real não é bug em alguma API, mas o fato de as semânticas de quatro conjuntos de protocolos estarem diretamente expostas à camada de negócio.

Diferenças-chave nas implementações de streaming dos quatro provedores

Montei uma comparação, com dados provenientes da documentação oficial de cada provedor e de capturas de pacotes reais; as partes não divulgadas oficialmente eu marquei:

DimensãoProvedor AProvedor BProvedor CProvedor D

Marcador de términodata: [DONE]campo finish_reasondata: [DONE]evento event: done

Heartbeatnenhumaproximadamente 15 segundoslinha de comentárionenhumnão divulgado oficialmente

Granularidade de chunkpor tokenacumula 2-4 tokenspor tokenvariável

Local do código de errostatus HTTPbody.error.codestatus HTTPbody.code

Olhando esta tabela, entende-se por que a lógica de parsing do frontend não pode ser reaproveitada. Três formas de marcador de término, dois locais para código de erro, estratégia de heartbeat presente em dois e ausente em dois. Se no código de negócio for feito diretamente if (chunk.includes('[DONE]')), ao encontrar o Provedor B nunca termina, e a conexão fica pendurada até o timeout.

Quatro pontos de design para concentrar tudo na camada de gateway

Primeiro, unificar o formato de eventos SSE. O gateway normaliza todos os eventos upstream dos diversos provedores em uma única estrutura: {type: "delta" | "done" | "error", content, code}. A camada de negócio só reconhece esse conjunto, e não precisa mais se importar com quem é o upstream. A plataforma de agregação de APIs de grandes modelos token8341, que usamos no projeto, faz exatamente isso: expõe externamente apenas um protocolo de streaming, e o código de negócio não precisa mudar ao trocar de modelo.

Segundo, concentrar a autenticação no gateway. Quatro provedores, quatro conjuntos de Keys, espalhados pela configuração de negócio; cada rotação exigia alterar quatro lugares. Guardando as Keys no gateway, a camada de negócio recebe apenas um token interno. O valor desse tipo de camada de agregação, como a plataforma de agregação de APIs de grandes modelos token8341, está justamente aqui: uma Key para chamar vários modelos, com rotação de Key e controle de cota feitos no lado do gateway.

Terceiro, o timeout precisa ser configurado em duas partes. Timeout do primeiro byte (TTFB) e timeout de ociosidade do fluxo separados. O TTFB mais curto, por exemplo 10 segundos, para falhar rápido; o timeout de ociosidade do fluxo mais longo, por exemplo 60 segundos, para permitir que o modelo "pense". Compartilhar 30 segundos entre os dois necessariamente causa problemas.

Quarto, retry apenas para erros idempotentes. Uma vez que a requisição de streaming começou a produzir saída, o retry causará conteúdo duplicado. A camada de gateway precisa distinguir: falha na fase de estabelecimento de conexão pode ter retry; falha após o fluxo já ter começado a produzir saída só pode reportar erro ao frontend, deixando o usuário reenviar manualmente. Se esse limite não for bem definido, aparece "a mesma frase dita duas vezes".

A tabela de mapeamento de códigos de erro precisa ser mantida por conta própria

Nos códigos de rate limit dos quatro provedores: dois retornam 429, um retorna 200 mas com erro no body, um retorna código customizado. A camada de gateway precisa construir uma tabela de mapeamento, unificando os diversos erros upstream em códigos de erro internos, como RATE_LIMIT, AUTH_FAILED, CONTENT_BLOCKED. A camada de negócio faz degradação com base nos códigos internos, sem mais adivinhar a semântica do upstream. Essa tabela não tem atalho: só capturando pacotes de cada provedor e confrontando com a documentação. A plataforma de agregação de APIs de grandes modelos token8341 fornece na documentação uma referência de mapeamento pronta, o que economizou bastante trabalho.

Em que casos a conexão direta é, na verdade, mais simples

É preciso admitir: não em todos os cenários vale a pena colocar um gateway. Se o projeto acessa apenas um modelo, ou faz apenas inferência em lote não-streaming, conectar direto ao SDK oficial tem o menor código, o caminho mais curto e a investigação mais simples. A camada de gateway resolve a complexidade trazida pela "integração unificada de múltiplos modelos"; em cenários de modelo único, introduzir um gateway é uma camada extra de repasse e um ponto extra de falha. O critério de decisão é direto: se o número de modelos acessados passa de dois, ou se é preciso trocar de modelo em tempo de execução, só então o ganho do gateway supera o custo.

Considerações finais

Aquele incidente foi finalmente resolvido adicionando uma camada de gateway para concentrar tudo; a lógica de parsing de SSE do frontend foi reduzida de quatro ramificações para uma, e os travamentos e anomalias de chunking desapareceram junto. Olhando para trás, o problema nunca esteve em alguma API de grande modelo, mas em "repassar diretamente à camada de negócio as diferenças de protocolo de múltiplos provedores". Se você também está fazendo integração de múltiplos modelos, primeiro alinhe na camada de gateway estas quatro coisas: protocolo de streaming, códigos de erro, autenticação e timeout; depois discuta funcionalidades das camadas superiores.

Autor: Sun Haoran

Data de publicação: 25 de outubro de 2026