AWS Builder Center
Streaming de Respostas LLM Através do API Gateway REST com Lambda Web Adapter

Streaming de Respostas LLM Através do API Gateway REST com Lambda Web Adapter

Aprenda a transmitir tokens do Amazon Bedrock em tempo real para o navegador usando responseTransferMode do API Gateway REST, Lambda Web Adapter e uma aplicação Flask simples.

AWS Solutions Architect

Introdução

Ao construir aplicações com inteligência artificial, os usuários esperam ver as respostas aparecendo token por token — e não esperar mais de 10 segundos por uma resposta completa. Essa experiência de "streaming" é padrão em ferramentas como ChatGPT, Claude e Gemini, mas implementá-la na AWS exige conectar diversas peças: Amazon Bedrock para o LLM, uma função Lambda para orquestrar a chamada e o API Gateway para entregar a resposta ao navegador.
O desafio é que as integrações tradicionais do API Gateway REST armazenam em buffer toda a resposta da Lambda antes de enviá-la ao cliente. Com respostas de LLM que levam de 10 a 30 segundos para serem geradas, isso cria uma experiência de usuário terrível.
Desde novembro de 2025, as APIs REST do API Gateway suportam response streaming  por meio de uma integração Lambda proxy com responseTransferMode definido como STREAM. Combinado com o Lambda Web Adapter  — que permite executar frameworks HTTP padrão (Flask, Express, Spring) dentro da Lambda — você pode transmitir tokens do LLM diretamente para o navegador com código mínimo.
Neste guia, você construirá um endpoint de chat com streaming mínimo: uma Lambda Python executando Flask por trás do API Gateway REST, transmitindo respostas do Amazon Bedrock (Claude) token por token para uma página HTML estática.

Arquitetura da Solução

Diagrama mostrando o fluxo de dados desde o navegador passando pelo API Gateway REST com integração proxy streaming, invocando uma função Lambda com Python 3.13 e arm64 executando Flask via Lambda Web Adapter, que chama o Amazon Bedrock converse_stream para transmitir tokens de volta ao cliente
Arquitetura completa da solução demonstrando como os tokens do LLM fluem do Amazon Bedrock através da Lambda e API Gateway até o navegador do usuário em tempo real
Os componentes principais:
  • Lambda Web Adapter: Uma layer Lambda (escrita em Rust) que faz a ponte entre a Lambda Runtime API e um servidor HTTP padrão executando dentro da função. Ela encaminha requisições para sua aplicação Flask em localhost:8000 e transmite a resposta de volta.
  • API Gateway responseTransferMode: STREAM: Instrui o API Gateway a usar a API InvokeWithResponseStream  em vez de armazenar a resposta completa em buffer.
  • Flask com chunked transfer encoding: A aplicação Flask produz chunks NDJSON conforme o Bedrock gera tokens.

Pré-requisitos

  • Conta AWS
  • Familiaridade básica com Lambda e API Gateway

Step 1: Criar a IAM Role para a Lambda

1.1 Criar a Execution Role

1.1.1. Abra o console do IAM 
1.1.2. Na navegação lateral, clique em Roles e depois em Create role
1.1.3. Em Trusted entity type, selecione AWS service
1.1.4. Em Use case, selecione Lambda e clique em Next
1.1.5. Na caixa de busca de permissões, digite AWSLambdaBasicExecutionRole, marque a caixa e clique em Next
1.1.6. Em Role name, insira lambda-streaming-demo-role
1.1.7. Clique em Create role

1.2 Adicionar Permissões do Bedrock

1.2.1. Após a criação, clique no nome da role lambda-streaming-demo-role para abri-la
1.2.2. Clique na aba Permissions e depois em Add permissions > Create inline policy
1.2.3. Clique na aba JSON e cole:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
],
"Resource": [
"arn:aws:bedrock:*::foundation-model/*",
"arn:aws:bedrock:*:<your-account-id>:inference-profile/*"
]
},
{
"Effect": "Allow",
"Action": [
"aws-marketplace:ViewSubscriptions",
"aws-marketplace:Subscribe"
],
"Resource": "*"
}
]
}
1.2.4. Clique em Next
1.2.5. Em Policy name, insira bedrock-invoke
1.2.6. Clique em Create policy

Step 2: Preparar o Código da Lambda

2.1 Criar o Pacote de Deploy

Você precisa criar um arquivo .zip contendo a aplicação Flask e suas dependências. Na sua máquina local:
2.1.1. Crie uma pasta de projeto e os seguintes arquivos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import json
import os
import boto3
from flask import Flask, Response, request

app = Flask(__name__)
bedrock = boto3.client("bedrock-runtime")

MODEL_ID = os.environ.get("MODEL_ID", "us.anthropic.claude-sonnet-4-5-20250929-v1:0")

@app.route("/chat", methods=["POST"])
def chat():
body = request.get_json(force=True) or {}
message = (body.get("message") or "").strip()

if not message:
return Response(
json.dumps({"error": "message is required"}) + "\n",
status=400,
mimetype="application/json",
)

def generate():
response = bedrock.converse_stream(
modelId=MODEL_ID,
messages=[{"role": "user", "content": [{"text": message}]}],
)
for event in response["stream"]:
if "contentBlockDelta" in event:
delta = event["contentBlockDelta"].get("delta", {})
if "text" in delta:
chunk = json.dumps({"type": "TOKEN", "text": delta["text"]})
yield chunk + "\n"
yield json.dumps({"type": "END"}) + "\n"

return Response(generate(), mimetype="text/event-stream", headers={
"Access-Control-Allow-Origin": "*",
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
})

@app.route("/", methods=["GET"])
def health():
return "OK"
Uma nota sobre a escolha do modelo: Este tutorial usa o Claude Sonnet 4.5 (us.anthropic.claude-sonnet-4-5-20250929-v1:0), que é um modelo maior que gera tokens em um ritmo moderado — tornando o efeito de streaming claramente visível durante os testes. Modelos menores como o Claude Haiku respondem tão rápido que o streaming pode parecer instantâneo, anulando o propósito desta demonstração. No entanto, modelos maiores são significativamente mais caros por token. Para workloads de produção, sempre prefira o menor modelo que atenda aos seus requisitos de qualidade. A escolha do modelo é inteiramente sua — basta alterar a variável de ambiente MODEL_ID na configuração da Lambda.
1
2
#!/bin/bash
exec python -m gunicorn -b :${PORT:-8000} -w 1 --timeout 900 app:app
Por que um shell script em vez de um arquivo Python? Ao usar o Lambda Web Adapter com pacotes zip, o campo handler deve ser um script de inicializacao que inicia o servidor HTTP — e nao um callable Python. O adapter executa esse script para subir a aplicacao web e entao encaminha as requisicoes do Lambda para ela via HTTP. Este e o padrao documentado pela AWS  e utilizado em todos os exemplos oficiais .
requirements.txt:
1
2
3
flask==3.1.1
gunicorn==23.0.0
boto3==1.38.0
2.1.2. Instale as dependências e crie o zip:
1
2
3
4
5
6
7
pip3 install --platform manylinux2014_aarch64 \
--target ./build --implementation cp --python-version 3.13 \
--only-binary=:all: -r requirements.txt --quiet

cp app.py run.sh ./build/
chmod +x ./build/run.sh
cd build && zip -qr ../lambda-streaming.zip .
Isso produz o arquivo lambda-streaming.zip pronto para upload.

Step 3: Criar a Função Lambda

3.1 Criar a Função

3.1.1. Abra o console do Lambda 
3.1.2. Clique em Create function
3.1.3. Selecione Author from scratch
3.1.4. Em Function name, insira streaming-demo
3.1.5. Em Runtime, selecione Python 3.13
3.1.6. Em Architecture, selecione arm64
3.1.7. Expanda Change default execution role, selecione Use an existing role e escolha lambda-streaming-demo-role
3.1.8. Clique em Create function

3.2 Fazer Upload do Código

3.2.1. Na página da função, role até a seção Code source
3.2.2. Clique em Upload from > .zip file
Tela do console AWS Lambda mostrando o menu Upload from expandido com a opção .zip file selecionada na seção Code source
Acesso à opção de upload de arquivo zip para enviar o pacote de deploy contendo a aplicação Flask e suas dependências
3.2.3. Selecione o arquivo lambda-streaming.zip que você criou e clique em Save

3.3 Configurar o Handler e o Timeout

3.3.1. Clique na aba Configuration
3.3.2. Clique em General configuration > Edit
3.3.3. Defina Timeout para 15 min 0 sec
3.3.4. Defina Memory para 512 MB
3.3.5. Clique em Save
3.3.6. Vá para a aba Code e clique em Edit ao lado de Runtime settings
Tela do console AWS Lambda mostrando o botão Edit ao lado de Runtime settings na aba Code da função
Acesso às configurações de runtime para alterar o handler padrão da função Lambda
3.3.7. Em Handler, altere para run.sh
3.3.8. Clique em Save
Tela de edição das runtime settings mostrando o campo Handler preenchido com run.sh em vez do handler Python padrão
Configuração do handler como run.sh que será executado pelo Lambda Web Adapter para iniciar o gunicorn com a aplicação Flask
Por que run.sh como handler com um runtime Python? O runtime python3.13 define o ambiente de execução — ele garante que o Python esteja disponível dentro do container. Porém, ao usar o Lambda Web Adapter , a invocação normal do handler é interceptada. O Web Adapter (configurado pela variável de ambiente AWS_LAMBDA_EXEC_WRAPPER no passo 3.5) executa o arquivo especificado no campo Handler como um shell script. Este run.sh inicia o gunicorn, que lança a aplicação Flask na porta 8000. O Web Adapter então encaminha as requisições Lambda recebidas para localhost:8000 como HTTP padrão — fazendo a ponte entre a Lambda Runtime API e seu framework web.

3.4 Adicionar a Layer do Lambda Web Adapter

3.4.1. Na página da função, role até a seção Layers
3.4.2. Clique em Add a layer
Tela do console AWS Lambda mostrando a seção Layers com o botão Add a layer disponível para adicionar novas layers à função
Acesso à seção de layers para adicionar o Lambda Web Adapter como uma layer à função
3.4.3. Selecione Specify an ARN
3.4.4. Insira o seguinte ARN (substitua <your-region> pela sua região, ex.: us-east-1):
1
arn:aws:lambda:<your-region>:753240598075:layer:LambdaAdapterLayerArm64:27
3.4.5. Clique em Verify e depois em Add
Tela de adição de layer mostrando o campo ARN preenchido com o ARN da layer LambdaAdapterLayerArm64 versão 27 após verificação bem-sucedida
Verificação e adição da layer do Lambda Web Adapter na arquitetura arm64 que faz a ponte entre a Runtime API e o servidor HTTP Flask

3.5 Configurar Variáveis de Ambiente

3.5.1. Clique na aba Configuration > Environment variables > Edit
3.5.2. Adicione as seguintes três variáveis:
  • Key: AWS_LAMBDA_EXEC_WRAPPER — Value: /opt/bootstrap
  • Key: AWS_LWA_INVOKE_MODE — Value: response_stream
  • Key: PORT — Value: 8000
3.5.3. Clique em Save
O que cada variável faz:
  • AWS_LAMBDA_EXEC_WRAPPER — Uma variável nativa do runtime Lambda  que instrui o runtime a executar o script especificado antes do handler. O valor /opt/bootstrap aponta para o binário do Lambda Web Adapter instalado pela layer no passo 3.4. Ele intercepta a invocação normal do handler e assume o controle do ciclo de vida da requisição.
  • AWS_LWA_INVOKE_MODE — Uma configuração do Lambda Web Adapter  que define o modo de resposta. O valor response_stream faz o adapter produzir o formato de streaming (JSON de metadados + 8 bytes nulos como delimitador + payload) que o API Gateway espera. Sem isso, o adapter armazena a resposta inteira em buffer antes de retorná-la.
  • PORT — Informa ao Lambda Web Adapter em qual porta encaminhar as requisições. O script run.sh inicia o gunicorn na porta 8000, e o adapter conecta em localhost:8000 para retransmitir as requisições recebidas como HTTP padrão.

Step 4: Criar a API REST no API Gateway

4.1 Criar a API

4.1.2. Clique em Create API
4.1.3. Em REST API (não HTTP API), clique em Build
4.1.4. Selecione New API
4.1.5. Em API name, insira streaming-demo-api
4.1.6. Em API endpoint type, selecione Regional
4.1.7. Em Security policy, selecione SecurityPolicy_TLS13_1_3_2025_09 (recomendado pela AWS — aplica apenas TLS 1.3)
4.1.8. Clique em Create API

4.2 Criar o Resource /chat

4.2.1. No painel Resources, selecione o resource raiz /
4.2.2. Clique em Create resource
4.2.3. Em Resource name, insira chat
4.2.4. Marque CORS (Cross Origin Resource Sharing)
4.2.5. Clique em Create resource

4.3 Criar o Método POST com Integração Streaming

4.3.1. Selecione o resource /chat
4.3.2. Clique em Create method
4.3.3. Em Method type, selecione POST
4.3.4. Em Integration type, selecione Lambda function
4.3.5. Marque Lambda proxy integration
4.3.6. Em Response transfer mode, selecione Stream
4.3.7. Em Lambda function, selecione sua região e insira streaming-demo
4.3.8. Em Integration timeout, insira 900000
4.3.9. Clique em Create method
Nota: O console exibe uma mensagem dizendo que concederá permissão ao API Gateway via resource-based policy. Isso não é suficiente para streaming — precisamos adicionar a IAM Role no próximo passo.

4.4 Fazer Deploy da API

4.4.1. Clique em Deploy API
4.4.2. Em Stage, selecione New stage
4.4.3. Em Stage name, insira demo
4.4.4. Clique em Deploy
4.4.5. Copie a Invoke URL exibida no topo. Ela se parece com: https://xxxxxxxxxx.execute-api.<your-region>.amazonaws.com/demo
Tela de detalhes do stage mostrando a Invoke URL completa no formato https://xxxxxxxxxx.execute-api.region.amazonaws.com/demo
URL de invocação gerada após o deploy que será usada para testar o endpoint de streaming via cURL e navegador

Step 5: Testar o Endpoint de Streaming

5.1 Testar com cURL

5.1.1. Abra um terminal e execute:
1
2
3
curl -s -X POST "https://xxxxxxxxxx.execute-api.<your-region>.amazonaws.com/demo/chat" \
-H "Content-Type: application/json" \
-d '{"message": "What is serverless computing? Answer in 2 sentences."}'
Você deverá ver linhas NDJSON aparecendo uma por uma conforme o LLM gera tokens:
1
2
3
4
5
6
{"type": "TOKEN", "text": "Server"}
{"type": "TOKEN", "text": "less computing"}
{"type": "TOKEN", "text": " is a cloud"}
{"type": "TOKEN", "text": " execution model"}
...
{"type": "END"}
Terminal mostrando a saída do comando cURL com linhas NDJSON aparecendo sequencialmente, cada uma contendo um objeto JSON com type TOKEN e fragmentos de texto do LLM
Resultado do teste via cURL demonstrando os tokens sendo recebidos um por um em formato NDJSON conforme o modelo gera a resposta

5.2 Testar no Navegador

Você pode testar o endpoint de streaming ao vivo usando o frontend complementar:
5.2.1. No campo API Gateway URL, insira: https://xxxxxxxxxx.execute-api.<your-region>.amazonaws.com/demo
5.2.2. No campo Message, insira: What is serverless computing? Answer in 2 sentences.
5.2.3. Clique em Send e observe a resposta aparecendo token por token
conforme são recebidos do endpoint de streaming
Demonstração do frontend Angular consumindo o stream de tokens e exibindo a resposta do LLM caractere por caractere em tempo real

5.3 Como o Navegador Consome o Stream

O navegador usa a API padrão fetch() com ReadableStream — nenhuma biblioteca adicional é necessária. O padrão principal:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const response = await fetch(apiUrl + "/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: userMessage }),
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
const { done, value } = await reader.read();
if (done) break;

const text = decoder.decode(value, { stream: true });
const lines = text.split("\n").filter((line) => line.trim());

for (const line of lines) {
try {
const chunk = JSON.parse(line);
if (chunk.type === "TOKEN" && chunk.text) {
this.response += chunk.text;
}
} catch {
// pula linhas que não são JSON
}
}
this.cdr.detectChanges(); // força atualização da UI após cada chunk
}
Este padrão:
  • Usa response.body.getReader() para obter um reader do ReadableStream
  • Lê chunks conforme chegam (sem buffering)
  • Faz parse de cada linha NDJSON e concatena o texto do token na saída
  • Chama detectChanges() após cada chunk para forçar a renderização imediata da UI (necessário no Angular pois o fetch executa fora da zona de change detection do framework)
  • Para quando done é true (stream encerrado)
Veja a implementação completa: app.ts no GitHub 

Limpeza

Para evitar cobranças contínuas, exclua os recursos nesta ordem:
  1. API Gateway: Abra o console do API Gateway, selecione streaming-demo-api, clique em Delete
  2. Lambda: Abra o console do Lambda, selecione streaming-demo, clique em Actions > Delete
  3. IAM Role: Abra o console do IAM, exclua lambda-streaming-demo-role

Conclusão

Transmitir respostas de LLM através do API Gateway REST agora é simples com o recurso responseTransferMode: STREAM e o Lambda Web Adapter. Você escreve uma aplicação Flask padrão com uma função geradora — o Lambda Web Adapter e o API Gateway cuidam de toda a complexidade do protocolo de streaming para você.
Para a referência completa de implementação, consulte a documentação de response streaming do API Gateway  e a documentação de response streaming do Lambda .
IMPORTANTE — Exclua seus recursos após os testes. Este tutorial cria um endpoint de API público sem autenticação. Se um agente malicioso descobrir a URL do seu endpoint, ele pode invocar o modelo LLM às suas custas sem nenhum rate limiting. O Bedrock cobra por token, e um endpoint desprotegido pode resultar em uma conta catastrófica no final do mês. Sempre exclua os recursos (API Gateway, Lambda, IAM Role) quando terminar os testes, ou adicione autenticação (Cognito, API Keys) antes de deixar o endpoint ativo.

Mantenha-se Conectado

Se este guia foi útil e você quer aprender mais sobre response streaming no API Gateway, Lambda Web Adapter, integração com Amazon Bedrock e arquiteturas serverless com IA na AWS, siga o autor para conteúdos e tutoriais futuros:
Fique ligado para mais walkthroughs práticos e soluções para desafios de arquitetura e segurança na nuvem.
Boas construções na nuvem!
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article