Respostas de LLM em streaming no Delphi: token a token com Server-Sent Events

· Componentes

Resposta rápida: para receber uma resposta de LLM em streaming no Delphi com o sgcWebSockets, atribua o evento OnHTTPAPISSE do componente e, em seguida, chame o método de streaming — _CreateChatCompletion com Stream := True para a OpenAI, _CreateMessageStream para a Anthropic Claude e o Ollama, _CreateContentStream para a Gemini. Cada delta chega no handler como um Server-Sent Event; acrescente aData a um Memo e a resposta se digita sozinha conforme o modelo a gera.

Uma chamada de LLM sem streaming bloqueia até que toda a resposta esteja pronta. Para uma resposta de um parágrafo, tudo bem, mas para uma conclusão longa o usuário fica olhando para uma janela congelada por vários segundos sem nenhum feedback. O streaming resolve isso: o modelo retorna a saída como uma sequência de pequenos blocos por uma única conexão HTTP, e você renderiza cada bloco no instante em que ele chega. O resultado é o familiar efeito de "digitação" que você vê no ChatGPT, e uma interface que parece responsiva mesmo quando a resposta completa demora um pouco.

Como o streaming funciona: OnHTTPAPISSE

Por baixo dos panos, todo componente de IA do sgcWebSockets faz streaming usando Server-Sent Events (SSE). O provedor mantém a resposta aberta e envia eventos conforme os tokens são produzidos. O componente analisa esses eventos e dispara um evento por bloco através de um único handler com a mesma assinatura em todos os componentes:

procedure TForm1.HandleSSE(Sender: TObject;
  const aEvent, aData: string; var Cancel: Boolean);
begin
  // aEvent  -> the SSE event name (provider-specific)
  // aData   -> the payload for this chunk (the token delta)
  // Cancel  -> set True to abort the stream early
  Memo1.Lines.Add(aData);
end;

Esse é todo o contrato. aData carrega o conteúdo incremental, aEvent informa qual é o tipo de evento (útil com provedores que emitem vários tipos de evento), e definir Cancel := True interrompe o stream, por exemplo quando o usuário clica em um botão Parar. O mesmo formato de handler funciona para OpenAI, Anthropic, Gemini e Ollama, então, uma vez que você o tenha escrito, pode reutilizá-lo entre os provedores.

OpenAI: Stream := True

Com o TsgcHTTP_API_OpenAI, você opta pelo streaming na requisição e conecta o evento. Defina OpenAIOptions.ApiKey, atribua OnHTTPAPISSE e a conclusão do chat é entregue bloco a bloco em vez de em um único pedaço:

uses
  sgcHTTP_API_OpenAI;

var
  OpenAI: TsgcHTTP_API_OpenAI;
begin
  OpenAI := TsgcHTTP_API_OpenAI.Create(nil);
  OpenAI.OpenAIOptions.ApiKey := 'sk-...';

  // Each token delta arrives in HandleSSE
  OpenAI.OnHTTPAPISSE := HandleSSE;

  // Build a typed request, set Stream := True, then call
  OpenAI._CreateChatCompletion('gpt-4o-mini', 'Explain WebSockets in detail.');
end;

A requisição tipada expõe uma propriedade Stream, então, quando você constrói o objeto da requisição você mesmo, define Stream := True antes de enviar. Os tokens então aparecem através de OnHTTPAPISSE conforme são gerados, e você acrescenta cada um ao seu Memo.

Anthropic Claude: _CreateMessageStream

O Claude expõe um helper de streaming dedicado, então não há nenhum flag separado para acionar: chamar _CreateMessageStream no TsgcHTTP_API_Anthropic ativa o SSE para aquela requisição. Defina a chave de API e a versão, atribua o handler e chame-o:

uses
  sgcHTTP_API_Anthropic;

var
  Anthropic: TsgcHTTP_API_Anthropic;
begin
  Anthropic := TsgcHTTP_API_Anthropic.Create(nil);
  Anthropic.AnthropicOptions.ApiKey := 'sk-ant-...';
  Anthropic.AnthropicOptions.AnthropicVersion := '2023-06-01';

  Anthropic.OnHTTPAPISSE := HandleSSE;
  Anthropic._CreateMessageStream(
    'claude-3-5-sonnet-latest',
    'Summarise RFC 6455',
    1024);
end;

O Claude emite vários tipos de evento SSE enquanto faz streaming (início de blocos de conteúdo, deltas e depois parada). O argumento aEvent permite distingui-los se você precisar; para uma interface simples de "mostrar o texto conforme chega", acrescentar aData é suficiente.

Gemini e Ollama: o mesmo formato

A Google Gemini segue o padrão idêntico com o seu próprio método de streaming, _CreateContentStream no TsgcHTTP_API_Gemini:

Gemini.OnHTTPAPISSE := HandleSSE;
Gemini._CreateContentStream(
  'gemini-2.0-flash',
  'Explain quantum entanglement',
  1024);

Modelos locais funcionam exatamente da mesma forma. O TsgcHTTP_API_Ollama não precisa de chave de API — aponte OllamaOptions.BaseUrl para http://localhost:11434/api e chame _CreateMessageStream, e o modelo aberto no seu próprio hardware faz streaming de volta pelo mesmo handler OnHTTPAPISSE:

Ollama.OllamaOptions.BaseUrl := 'http://localhost:11434/api';
Ollama.OnHTTPAPISSE := HandleSSE;
Ollama._CreateMessageStream('llama3', 'Summarise RFC 6455');

Quatro provedores, um evento, uma chamada de método cada. Trocar o backend de streaming é uma edição localizada, não uma reescrita.

Atualizando a interface com segurança

Algumas notas práticas para o handler. Primeiro, mantenha o trabalho dentro de OnHTTPAPISSE pequeno — acrescente o delta e retorne. Processamento pesado por token deixará o stream com aparência irregular, então acumule o texto e faça a formatação custosa quando o stream terminar. Segundo, atente-se ao contexto de thread. Se você iniciar a requisição a partir de uma thread em segundo plano, o evento SSE dispara nessa thread, e tocar em controles VCL ou FMX fora da thread principal não é seguro. Nesse caso, encaminhe a atualização de volta com TThread.Synchronize (ou TThread.Queue para um acréscimo não bloqueante):

procedure TForm1.HandleSSE(Sender: TObject;
  const aEvent, aData: string; var Cancel: Boolean);
begin
  TThread.Queue(nil,
    procedure
    begin
      Memo1.SelStart := Length(Memo1.Text);
      Memo1.SelText := aData; // append at the caret, no full repaint
    end);
end;

Acrescentar com SelText em vez de Lines.Add evita reorganizar todo o Memo a cada token, o que mantém um stream longo suave. Se você chamar a API a partir da thread principal, pode dispensar o wrapper TThread.Queue e atualizar o controle diretamente.

Primeiros passos

Todos esses componentes vêm no sgcWebSockets. Baixe a versão de avaliação gratuita, adicione o componente do provedor que você quiser, atribua OnHTTPAPISSE e chame o método de streaming — você terá uma interface token a token em poucas linhas. Veja a página do componente OpenAI e a página do componente Anthropic para a referência completa dos métodos, ou explore todos os modelos no hub de componentes de IA & LLM.

Dúvidas ou comentários? Entre em contato — você receberá uma resposta das pessoas que escreveram o código.

Relacionados