AI Engineering Academy · Aula

Transmitindo a saída em LangChain

Implemente a transmissão de tokens por meio de cadeias LCEL para que sua aplicação exiba cada palavra assim que ela chegar, em vez de esperar a resposta completa, melhorando a latência percebida.

Aula 4 de 413 etapas

Transmitindo a saída em LangChain é uma aula grátis de AI Engineering Academy no CoddyKit. Esta é a aula 4 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de AI Engineering Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de AI Engineering Academy inclui 4 aulas no total.

Por que a transmissão gradual é importante

Sem transmissão gradual, os usuários ficam olhando para uma tela em branco enquanto esperam a LLM terminar de gerar a resposta — o que pode levar de 5 a 30 segundos para respostas longas. Com transmissão gradual, os tokens aparecem à medida que são gerados, oferecendo feedback imediato. Isso melhora muito a sensação de rapidez da aplicação. O LCEL do LangChain propaga a transmissão por toda a cadeia automaticamente quando você chama .stream().

Transmissão gradual básica com .stream()

Toda cadeia LCEL expõe um método .stream() que retorna um iterador de fragmentos. Para uma cadeia terminada em StrOutputParser, cada fragmento é uma parte de uma string. Você itera pelos fragmentos e os imprime ou fornece à medida que chegam. A transmissão ocorre no nível HTTP — cada token da API da OpenAI é encaminhado pelo analisador assim que chega.

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

chain = (
    ChatPromptTemplate.from_template('Explain {topic} in detail.')
    | ChatOpenAI(model='gpt-4o-mini')
    | StrOutputParser()
)

# Stream tokens to stdout
for chunk in chain.stream({'topic': 'quantum entanglement'}):
    print(chunk, end='', flush=True)
print()  # final newline

Transmissão gradual assíncrona com .astream()

.astream() é a versão assíncrona de .stream(). Ela retorna um iterador assíncrono que você consome com async for. Essa é a abordagem correta no FastAPI, no Starlette e em outras estruturas web assíncronas, nas quais o manipulador de requisições é uma corrotina. Usar transmissão síncrona em um manipulador assíncrono bloquearia o loop de eventos.

import asyncio
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate

chain = (
    ChatPromptTemplate.from_template('Write a poem about {subject}')
    | ChatOpenAI(model='gpt-4o-mini')
    | StrOutputParser()
)

async def stream_response():
    async for chunk in chain.astream({'subject': 'the ocean'}):
        print(chunk, end='', flush=True)

asyncio.run(stream_response())

Transmissão gradual no FastAPI com StreamingResponse

No FastAPI, envolva um gerador assíncrono em StreamingResponse com media_type='text/plain' para transmitir tokens de texto ao navegador. Para eventos enviados pelo servidor (SSE), use media_type='text/event-stream' e formate cada fragmento como data: ...\n\n. Assim, o navegador recebe os tokens à medida que são gerados, sem esperar a resposta completa.

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()

async def generate_stream(topic: str):
    async for chunk in chain.astream({'topic': topic}):
        yield chunk

@app.get('/stream')
async def stream_endpoint(topic: str):
    return StreamingResponse(
        generate_stream(topic),
        media_type='text/plain'
    )

# SSE format for frontend EventSource
async def sse_stream(topic: str):
    async for chunk in chain.astream({'topic': topic}):
        yield f'data: {chunk}\n\n'

Transmissão gradual por etapas intermediárias

As cadeias LCEL propagam a transmissão por cada etapa que oferece suporte a ela. O StrOutputParser reconhece transmissões e encaminha os fragmentos imediatamente. No entanto, alguns analisadores — como JsonOutputParser — precisam armazenar toda a saída antes de analisá-la, interrompendo a transmissão. O LangChain deixa isso claro: se uma etapa não for compatível com transmissão, ela acumulará a saída antes de encaminhá-la para as etapas seguintes.

from langchain_core.output_parsers import JsonOutputParser

# This chain does NOT stream token by token
# JsonOutputParser must buffer the full response before parsing JSON
json_chain = (
    ChatPromptTemplate.from_template('Return JSON: {task}')
    | ChatOpenAI(model='gpt-4o-mini')
    | JsonOutputParser()  # buffers until complete
)

# But partial JSON streaming IS possible with streaming_json_parser
for partial in json_chain.stream({'task': 'list 3 colors'}):
    print(partial)  # prints partial dict as it fills in

astream_events para controle detalhado

.astream_events() oferece uma API de transmissão mais detalhada, que emite eventos para cada etapa da cadeia, e não apenas para a saída final. Cada evento tem um campo kind (on_chain_start, on_llm_stream, on_chain_end) e uma carga data. Isso permite transmitir separadamente os resultados de chamadas de ferramentas, o raciocínio intermediário e a saída final para diferentes partes de uma interface.

async def stream_with_events(question: str):
    async for event in chain.astream_events(
        {'question': question},
        version='v2'
    ):
        kind = event['event']
        if kind == 'on_llm_stream':
            chunk = event['data']['chunk'].content
            print(chunk, end='', flush=True)
        elif kind == 'on_chain_end':
            print('\n[Done]')
        elif kind == 'on_tool_start':
            print(f'\n[Tool: {event["name"]}]')

Armazenando a saída transmitida

Às vezes, você precisa tanto transmitir tokens ao usuário quanto capturar a resposta completa para registro ou processamento posterior. Use .astream() com um acumulador de lista. Una os fragmentos depois do loop para obter o texto completo. Esse padrão permite exibir a saída em tempo real e, ao mesmo tempo, armazenar a resposta completa para análise, armazenamento em cache ou avaliação.

async def stream_and_capture(question: str) -> str:
    full_response = []
    async for chunk in chain.astream({'question': question}):
        print(chunk, end='', flush=True)  # stream to user
        full_response.append(chunk)        # also collect
    print()  # newline
    complete = ''.join(full_response)
    await log_response(question, complete)  # log full text
    return complete

Transmissão gradual com chamadas de ferramentas

Quando um modelo gera uma chamada de ferramenta em uma resposta transmitida, os argumentos da função chegam como fragmentos de tokens. Você precisa armazenar a string JSON dos argumentos até que a chamada da ferramenta seja concluída antes de executá-la. O LangChain faz isso automaticamente em seus executores de agentes, mas, se você estiver criando um loop de transmissão personalizado, deverá verificar finish_reason e acumular os fragmentos de tool_call.function.arguments.

from openai import AsyncOpenAI

client = AsyncOpenAI()

async def stream_with_tools(prompt: str):
    tool_call_buffer = {}
    async with client.chat.completions.stream(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': prompt}],
        tools=[weather_tool_schema]
    ) as stream:
        async for chunk in stream:
            delta = chunk.choices[0].delta
            if delta.tool_calls:
                for tc in delta.tool_calls:
                    idx = tc.index
                    if idx not in tool_call_buffer:
                        tool_call_buffer[idx] = ''
                    if tc.function.arguments:
                        tool_call_buffer[idx] += tc.function.arguments

Cancelamento e tempo limite com transmissão gradual

Respostas transmitidas longas precisam de suporte a cancelamento. Em Python assíncrono, você pode cancelar uma asyncio.Task que envolva a transmissão. No FastAPI, a estrutura trata automaticamente o cancelamento causado pela desconexão do cliente ao usar StreamingResponse. Defina um tempo limite por meio do parâmetro timeout do cliente da OpenAI ou envolva a transmissão em asyncio.wait_for() para interrompê-la após uma duração máxima.

import asyncio

async def stream_with_timeout(question: str, timeout: float = 30.0):
    async def _stream():
        async for chunk in chain.astream({'question': question}):
            yield chunk

    try:
        async for chunk in asyncio.timeout(_stream(), timeout):
            print(chunk, end='', flush=True)
    except asyncio.TimeoutError:
        print('\n[Stream timed out after 30 seconds]')
    except asyncio.CancelledError:
        print('\n[Stream cancelled by client disconnect]')

SSE no cliente com JavaScript

No frontend, a API EventSource nativa do navegador consome eventos enviados pelo servidor. Quando o endpoint do FastAPI emite fragmentos data: token\n\n, o EventSource dispara um evento message para cada um deles. Acrescente cada token ao DOM assim que ele chegar para criar um efeito de máquina de escrever. Para ter mais controle, fetch() com response.body.getReader() oferece acesso completo à transmissão.

// Frontend JavaScript (not Python)
const source = new EventSource('/stream?topic=quantum+computing');
const outputDiv = document.getElementById('output');

source.onmessage = (event) => {
    outputDiv.textContent += event.data;
};

source.onerror = () => {
    source.close();
    outputDiv.textContent += ' [done]';
};

// Alternative: fetch with ReadableStream
const response = await fetch('/stream?topic=ai');
const reader = response.body.getReader();
while (true) {
    const {done, value} = await reader.read();
    if (done) break;
    outputDiv.textContent += new TextDecoder().decode(value);
}

Práticas recomendadas para transmissão gradual

Siga estas práticas recomendadas ao implementar transmissões: sempre use flush=True ao imprimir em stdout para evitar o armazenamento temporário. Defina stream_usage=True se precisar de contagens precisas de tokens durante a transmissão. Emita um marcador data: [DONE]\n\n ao final dos fluxos SSE para que o cliente saiba quando fechar a conexão. Teste os endpoints de transmissão com curl --no-buffer para verificar se os tokens chegam gradualmente.

# Complete SSE endpoint with DONE sentinel
async def sse_generator(question: str):
    try:
        async for chunk in chain.astream({'question': question}):
            # Escape any newlines in the chunk
            safe_chunk = chunk.replace('\n', ' ')
            yield f'data: {safe_chunk}\n\n'
    finally:
        yield 'data: [DONE]\n\n'

@app.get('/chat/stream')
async def chat_stream(question: str):
    return StreamingResponse(
        sse_generator(question),
        media_type='text/event-stream',
        headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'}
    )

Verificação rápida

Teste sua compreensão sobre a transmissão gradual de saída no LangChain.

Recapitulação da lição

Nesta lição, você aprendeu que stream() e astream() permitem iterar pelos fragmentos de tokens à medida que são gerados, eliminando a longa espera pela resposta completa; StreamingResponse no FastAPI, com o formato SSE, entrega tokens aos clientes do navegador em tempo real; e astream_events() oferece pontos de extensão detalhados para eventos de cada etapa da cadeia, incluindo chamadas de ferramentas e saídas intermediárias. A seguir, exploraremos o gerenciamento de memória para conversas com várias interações.

Grátis para começar

Aprenda Python com um tutor de IA — grátis

Escreva e execute código real no seu navegador, obtenha ajuda instantânea de um tutor de IA 24/7 e continue de onde parou na web ou no app.

Cursos
30
Aulas
120

Perguntas Frequentes

A aula “Transmitindo a saída em LangChain” é grátis?

Sim — o texto completo de “Transmitindo a saída em LangChain” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de AI Engineering Academy, atualize para CoddyKit PRO. O curso de AI Engineering Academy inclui 4 aulas no total.

O que vou aprender em “Transmitindo a saída em LangChain”?

Implemente a transmissão de tokens por meio de cadeias LCEL para que sua aplicação exiba cada palavra assim que ela chegar, em vez de esperar a resposta completa, melhorando a latência percebida. Você pratica AI Engineering Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar AI Engineering Academy?

Nenhuma experiência prévia é necessária. AI Engineering Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 4 de 4.

Quanto tempo leva a aula “Transmitindo a saída em LangChain”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de AI Engineering Academy?

Sim. Cada aula de AI Engineering Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Arquitetura do LangChain e abstrações principais
  2. Criando cadeias com LCEL
  3. Cadeias ramificadas e paralelas
  4. Transmitindo a saída em LangChain
← Voltar para AI Engineering Academy