Sua primeira chamada: a Converse API na prática
- ⬜⚖️ Bedrock vs API direta: quando usar cada um(AWS Bedrock — GenAI em Produção)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
Chega de teoria: agora você faz a primeira chamada de verdade. E a primeira decisão de engenharia acontece antes do hello world — existem duas APIs de inferência no Bedrock, a Converse e a InvokeModel. Escolher errado te condena a reescrever código toda vez que trocar de modelo. Este módulo é o mais hands-on da trilha: muito código, Python e TypeScript, streaming, IAM e os erros que você VAI encontrar.
Converse vs InvokeModel: por que a Converse ganha
A Converse é uma API unificada: o mesmo schema de request serve para Claude, Nova, Llama, Mistral. Para trocar de modelo você muda uma string (o modelId) e pronto. Parâmetros que só existem num provedor específico vão num campo à parte, o additionalModelRequestFields. Já a InvokeModel manda um corpo (body) no formato nativo de cada provedor — muda de modelo, muda o body inteiro. Você usa InvokeModel quando a Converse não cobre o recurso: geração de imagem, embeddings, ou quando precisa falar o formato Anthropic puro (Messages API).
| Aspecto | Converse | InvokeModel |
|---|---|---|
| Formato do request | Unificado — mesmo schema para todo modelo | Model-native — o body muda por provedor |
| Trocar de modelo | Muda só o modelId | Reescreve o body do request |
| Parâmetros específicos do provedor | Vão em additionalModelRequestFields | Já fazem parte do body nativo |
| Streaming | ConverseStream | InvokeModelWithResponseStream |
| Casos típicos | Chat, tools, texto/imagem/doc/vídeo/áudio | Geração de imagem, embeddings, formato Anthropic puro |
| Recomendação AWS | Padrão para a maioria dos apps | Só quando a Converse não cobre o recurso |
Regra prática
Use Converse por padrão. Ela é a recomendação oficial da AWS para texto/chat e é o que torna o 'troca modelId e segue o jogo' realmente verdadeiro. Só caia para InvokeModel quando o recurso que você precisa (imagem, embeddings, um parâmetro exótico do provedor) não estiver na Converse.
Pré-requisitos: model access, região e inference profiles
Antes de qualquer chamada, três coisas precisam estar certas. Primeiro: habilitar model access no console do Bedrock (Model access) — sem isso, toda chamada volta como AccessDenied. Segundo: a região importa — um modelo disponível em us-east-1 pode não existir em sa-east-1, e o modelId é resolvido por região. Terceiro, e o mais traiçoeiro: muitos modelos novos só são invocáveis via inference profile, não pelo ID base.
Gotcha clássico: inference profile vs ID base
Se você passar o ID base (ex.: 'anthropic.claude-sonnet-4-5-20250929-v1:0') num modelo que exige inference profile, recebe ValidationException dizendo que o modelo não suporta on-demand. A correção é usar o ID do inference profile — o mesmo ID com um prefixo de região: 'us.anthropic.claude-sonnet-4-5-20250929-v1:0' (ou eu. / apac. conforme sua geografia). O inference profile ainda roteia a chamada entre regiões para dar mais throughput; por isso a IAM precisa cobrir o profile E os foundation models das regiões que ele abrange.
- → credencial da role ou do perfil local
- → modelId é o ARN do profile ou o ID do modelo
- → resposta inteira, ou pedaço a pedaço no streaming
- Compute
- Segurança e identidade
- IA e machine learning
Quatro peças e nada mais. Os três pré-requisitos que fazem a primeira chamada falhar estão nos passos 1 e 2 — acesso ao modelo liberado, região certa e o identificador do inference profile.
- Autorizar. A política precisa permitir a ação de invocação sobre o recurso exato. Erro de acesso negado aqui é quase sempre recurso errado na policy, não credencial faltando.
- Escolher o identificador certo. Inference profile regional prefixa a região e o ID costuma carregar versão. Copie do console: ID digitado de memória é 400 imediato ou 404 silencioso em pipeline.
- Invocar. Uma chamada Converse com modelId, messages, system e configuração de inferência. O mesmo shape serve para qualquer família de modelo.
- Receber. Resposta completa, ou fluxo de pedaços com ConverseStream — que é o que evita a tela parada em resposta longa.
- Os três motivos de falha, nesta ordem. Acesso ao modelo liberado na conta; região que de fato oferece aquele modelo; identificador de inference profile copiado, não digitado. Conferir nessa ordem economiza tempo porque cada um produz um erro parecido — e o segundo é o que mais engana, já que a chamada falha como se o modelo não existisse.
Hello world em Python (boto3)
O cliente é o bedrock-runtime (data plane), não o bedrock (control plane). Você monta messages (o histórico da conversa), opcionalmente system (persona/regras) e inferenceConfig (limites de geração):
import boto3
# O endpoint de inferência é "bedrock-runtime" — NÃO "bedrock" (esse é o control plane).
client = boto3.client("bedrock-runtime", region_name="us-east-1")
# Muitos modelos novos SÓ respondem via inference profile (prefixo de região: us. / eu. / apac.).
MODEL_ID = "us.anthropic.claude-sonnet-4-5-20250929-v1:0"
response = client.converse(
modelId=MODEL_ID,
system=[{"text": "Você é um engenheiro sênior. Responda em PT-BR, direto ao ponto."}],
messages=[
{
"role": "user",
"content": [{"text": "Explique o que é MVCC no PostgreSQL em 2 frases."}],
}
],
inferenceConfig={"maxTokens": 512, "temperature": 0.2, "topP": 0.9},
)
# A resposta é um dict aninhado — o texto vive em output.message.content[0].text.
message = response["output"]["message"]
print(message["content"][0]["text"])
# Metadados que você VAI querer logar em produção:
print("stopReason:", response["stopReason"]) # end_turn | max_tokens | stop_sequence | tool_use
usage = response["usage"]
print("tokens in/out:", usage["inputTokens"], usage["outputTokens"])Repare no shape da resposta: ela é um dict aninhado. O texto gerado está em response['output']['message']['content'][0]['text']. O content é uma lista porque uma mensagem pode ter vários blocos (texto, tool use, etc.). Sempre logue response['stopReason'] (por que a geração parou) e response['usage'] (tokens de entrada/saída — é isso que vira dinheiro na fatura).
Hello world em TypeScript (AWS SDK v3)
No SDK JS/TS v3 o padrão é command-based: você instancia um BedrockRuntimeClient e envia um ConverseCommand. O corpo é o mesmo do boto3 (messages, system, inferenceConfig) — prova viva de que a Converse é uma API unificada, não só entre modelos, mas entre linguagens:
import {
BedrockRuntimeClient,
ConverseCommand,
} from "@aws-sdk/client-bedrock-runtime";
const client = new BedrockRuntimeClient({ region: "us-east-1" });
// Mesmo ID do inference profile usado no exemplo Python.
const MODEL_ID = "us.anthropic.claude-sonnet-4-5-20250929-v1:0";
const command = new ConverseCommand({
modelId: MODEL_ID,
system: [
{ text: "Você é um engenheiro sênior. Responda em PT-BR, direto ao ponto." },
],
messages: [
{
role: "user",
content: [{ text: "Explique o que é MVCC no PostgreSQL em 2 frases." }],
},
],
inferenceConfig: { maxTokens: 512, temperature: 0.2, topP: 0.9 },
});
const response = await client.send(command);
// O SDK v3 tipa a resposta: output.message.content é um array de ContentBlock.
const text = response.output?.message?.content?.[0]?.text;
console.log(text);
console.log("stopReason:", response.stopReason);
console.log(
"tokens in/out:",
response.usage?.inputTokens,
response.usage?.outputTokens,
);IAM mínima para invocar
A Converse é autorizada pela ação bedrock:InvokeModel — sim, o mesmo nome da API InvokeModel; a Converse reusa essa permissão por baixo. Já a ConverseStream usa bedrock:InvokeModelWithResponseStream. No Resource você aponta para o ARN do foundation model e, se estiver usando inference profile, também para o ARN do profile. Policy mínima:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "InvokeConverseEConverseStream",
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
],
"Resource": [
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-sonnet-4-5-20250929-v1:0",
"arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-sonnet-4-5-20250929-v1:0"
]
}
]
}Você quer poder trocar de Claude para Nova mudando o mínimo de código possível. Qual API e por quê?
Streaming com ConverseStream
Numa resposta longa, esperar o modelo terminar para só então mostrar tudo dá a sensação de app travado. A ConverseStream resolve isso entregando o texto em pedaços — a UX de 'digitação' que você vê no ChatGPT. Os eventos chegam numa ordem definida pelo protocolo; entender essa ordem evita bugs bobos de parsing:
import boto3
client = boto3.client("bedrock-runtime", region_name="us-east-1")
response = client.converse_stream(
modelId="us.anthropic.claude-sonnet-4-5-20250929-v1:0",
messages=[{"role": "user", "content": [{"text": "Liste 3 causas de latência em APIs."}]}],
inferenceConfig={"maxTokens": 512, "temperature": 0.2},
)
stop_reason = None
# response["stream"] é um iterador de eventos que chegam na ordem do protocolo.
for event in response["stream"]:
if "contentBlockDelta" in event:
# O texto chega em pedaços — imprima incremental para dar a sensação de digitação.
delta = event["contentBlockDelta"]["delta"]
if "text" in delta:
print(delta["text"], end="", flush=True)
elif "messageStop" in event:
stop_reason = event["messageStop"]["stopReason"]
elif "metadata" in event:
usage = event["metadata"]["usage"]
print(f"\n[in={usage['inputTokens']} out={usage['outputTokens']}]")
print("\nstopReason:", stop_reason)Campos-chave do request Converse
A Converse tem poucos campos, e você já usou os principais. Vale ter o mapa completo na cabeça — alguns deles (toolConfig, guardrailConfig) são a porta para os próximos módulos:
| Campo | O que é | Obrigatório? |
|---|---|---|
| modelId | ID do modelo ou do inference profile a invocar | Sim |
| messages | Histórico da conversa: lista de {role, content[]} | Sim |
| system | Instruções de sistema (persona, regras) — lista de {text} | Não |
| inferenceConfig | maxTokens, temperature, topP, stopSequences | Não (tem defaults) |
| toolConfig | Ferramentas que o modelo pode chamar (function calling) | Não — próximo módulo |
| guardrailConfig | Aplica um Guardrail (filtros de conteúdo/PII) à chamada | Não |
| additionalModelRequestFields | Parâmetros do provedor fora do schema unificado | Não |
Erros comuns (e como sobreviver a eles)
| Exceção | Causa provável | Como resolver |
|---|---|---|
| AccessDeniedException | Model access não habilitado, ou IAM sem bedrock:InvokeModel no recurso | Habilite o modelo (Model access) e ajuste a policy/Resource |
| ValidationException | Usou o ID base num modelo que exige inference profile, ou parâmetro fora do schema | Troque para o ID com prefixo de região (us. / eu. / apac.) |
| ThrottlingException | Estourou a quota de requests/tokens por minuto da região | Backoff exponencial com jitter + peça aumento de quota |
| ResourceNotFoundException | modelId incorreto, ou modelo indisponível na região do client | Confira o modelId e a região do bedrock-runtime |
ThrottlingException não é opcional de tratar
Em qualquer volume real você VAI bater na quota (RPM/TPM) da região. A resposta certa NÃO é dar um sleep fixo — é backoff exponencial COM jitter, senão todos os seus workers re-tentam no mesmo instante e você recria o pico (thundering herd). Re-tente só erros transientes (Throttling, timeout, ServiceUnavailable); AccessDenied e ValidationException não melhoram com retry — só falham mais devagar.
import random
import time
import boto3
from botocore.exceptions import ClientError
client = boto3.client("bedrock-runtime", region_name="us-east-1")
RETRYABLE = ("ThrottlingException", "ModelTimeoutException", "ServiceUnavailableException")
def converse_with_retry(**kwargs):
max_attempts = 6
for attempt in range(max_attempts):
try:
return client.converse(**kwargs)
except ClientError as e:
code = e.response["Error"]["Code"]
# Só re-tenta erro transiente. AccessDenied/ValidationException NÃO adianta re-tentar.
if code not in RETRYABLE or attempt == max_attempts - 1:
raise
# Backoff exponencial com "full jitter": evita o efeito manada (thundering herd).
ceiling = min(20.0, 0.5 * (2 ** attempt))
time.sleep(random.uniform(0, ceiling))Sua chamada Converse retorna ValidationException dizendo que o modelo não suporta invocação on-demand. Qual é a causa mais provável?
No ConverseStream, qual evento carrega o stopReason da geração?
Próximo passo
Você já sabe fazer a chamada, ler a resposta, streamar e sobreviver aos erros — tudo com texto. Mas a Converse não para no texto: ela aceita imagem, documento (PDF), vídeo e áudio no mesmo content[]. É exatamente o próximo módulo: Além do texto: imagem, documento, vídeo e áudio.
Perguntas frequentes
❓ Converse ou InvokeModel: qual usar?
❓ Preciso de credencial nova para chamar o Bedrock?
❓ Por que a mesma chamada funciona numa região e falha em outra?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…