Objetivo da Aula
Ao concluir esta aula, você será capaz de:
Comparar Anthropic, OpenAI, Google e Hugging Face em termos de custo, qualidade, latência e casos de uso adequados
Implementar clientes Python para múltiplos provedores com interface unificada e fallback automático
Calcular o custo real por request considerando tokens de input, output, e caching disponível em cada provedor
Escolher o modelo certo para cada caso de uso com base em critérios técnicos objetivos
Debugar erros de integração de API incluindo rate limits, timeouts e respostas malformadas
Por que isso importa
Dependência de um único provedor de IA é um risco técnico e de negócio. Em março de 2024, a API da OpenAI ficou fora por 4 horas durante horário de pico. Empresas que só tinham o OpenAI como provedor ficaram completamente paradas. Empresas com fallback para Anthropic continuaram operando.
Mas há um segundo risco mais sutil: custo. Muitos times escolhem o modelo mais potente (GPT-4o, Claude Opus) para tudo — incluindo tarefas simples que um modelo 10x mais barato resolveria com a mesma qualidade. Um sistema de classificação de sentimentos não precisa de GPT-4o. GPT-4o mini (50x mais barato) resolve igualmente bem.
O impacto em escala é brutal: uma startup processando 10 milhões de tokens por dia com GPT-4o paga $40.000/mês. Com uma seleção inteligente de modelos (GPT-4o só para o top 10% de tarefas complexas, GPT-4o mini para o restante), o mesmo volume custa $4.500/mês. Isso é a diferença entre ser rentável ou não.
Um terceiro fator: qualidade varia por domínio. Claude domina raciocínio de código longo e análise de documentos estruturados. GPT-4o tem melhor desempenho em tarefas de instrução seguida rigorosamente. Gemini 1.5 Pro é imbatível em contextos longos (até 2 milhões de tokens). Conhecer essas características permite alocar tarefas ao modelo mais adequado.
Esta unidade te dá o código e os frameworks para fazer isso de forma sistemática.
Conceitos Fundamentais
A Arquitetura de Um Provedor de LLM
Todos os provedores seguem o mesmo padrão de alto nível:
Cliente → HTTP Request (JSON) → API Gateway → Load Balancer → GPU Cluster → ResponseMas os detalhes de implementação diferem em aspectos que importam para produção:
Fundamento: RPM + TPM e Batch API
Rate limits de LLM têm dois eixos simultâneos: RPM (requests por minuto) e TPM (tokens por minuto). Você pode bater em qualquer um independentemente — 10 RPM com prompts de 50k tokens pode bater no TPM muito antes de bater no RPM. Dimensionar headroom exige calcular nos dois eixos: tokens médios por request × throughput desejado = TPM necessário. HTTP 429 é o sinal de throttling em ambos os casos — implemente backoff exponencial com jitter. Batch API oferece 50% de desconto mas com até 24h de latência: ideal para cargas offline (sumarização de documentos, geração de embeddings em batch, análise de logs históricos) onde você não precisa da resposta imediatamente.
Rate Limits: Cada provedor tem limites por minuto (RPM) e tokens por minuto (TPM) por chave de API. Quando você ultrapassa, recebe HTTP 429. A estratégia correta é backoff exponencial com jitter — não retry imediato.
Latência: GPT-4o tem TTFT (time to first token) em torno de 500ms-1s. Claude Haiku é mais rápido (~200-400ms TTFT). Gemini Flash é o mais rápido (~150-300ms). Para aplicações interativas, TTFT importa mais que throughput total.
Confiabilidade (SLA): OpenAI publica 99.9% de uptime mas na prática tem incidentes mais frequentes. Anthropic tem histórico mais estável em 2024-2025. Google tem a infraestrutura de maior escala mas os modelos de AI ainda têm variabilidade.
Formato de Response: Cada SDK tem suas idiossincrasias. OpenAI: response.choices[0].message.content. Anthropic: response.content[0].text. Google: response.text. Um cliente unificado abstrai essas diferenças.
Comparativo Técnico dos Provedores em 2025
Anthropic (Claude) - Modelos: Claude Haiku 4.5 (fast/cheap), Claude Sonnet 4.6 (balanced), Claude Opus 4.8 (most capable) - Preços: Haiku $0.25/1M input + $1.25/1M output; Sonnet $3/15; Opus $15/75 - Context window: 200k tokens (todos os modelos) - Pontos fortes: raciocínio longo, código, análise de documentos, seguimento de instruções complexas - Pontos fracos: sem geração de imagem nativa, sem TTS - Caching: Prompt Caching disponível — reutiliza tokens de prefixo por 5 minutos (60% de desconto) - Rate limits (tier free): 5 RPM, 25k TPM
OpenAI (GPT) - Modelos: GPT-4o mini (fast/cheap), GPT-4o (balanced/capable), o1/o1-mini (raciocínio extended) - Preços: 4o mini $0.15/0.60; 4o $2.50/10; o1 $15/60 - Context window: 128k tokens - Pontos fortes: function calling maduro, embeddings text-embedding-3-large, DALL-E 3, Whisper - Pontos fracos: context window menor que Claude, consistência variável em respostas longas - Batch API: 50% de desconto para jobs assíncronos com latência de 24h - Rate limits (tier 1): 500 RPM, 200k TPM
Google (Gemini) - Modelos: Gemini 1.5 Flash (ultra rápido), Gemini 1.5 Pro (contexto 1M), Gemini 2.0 Flash (novo) - Preços: Flash grátis até 15 RPM; Pro $3.50/10.50 por 1M tokens - Context window: 1 MILHÃO de tokens (Gemini 1.5 Pro) — único no mercado - Pontos fortes: contexto gigante, velocidade, preço competitivo, grounded search - Pontos fracos: qualidade de raciocínio inferior a Claude/GPT-4o para tarefas complexas - Grounding: pode buscar no Google Search em tempo real como parte da resposta
Hugging Face / Open Source - Modelos: Llama 3.1/3.2, Mistral Large, Qwen2.5, Falcon, Command R+ - Hosting: Inference API (pago), Inference Endpoints (self-host managed), ou seu próprio servidor - Preços: Inference API varia por modelo; self-host tem custo de GPU (A100 80GB ~$2-3/h) - Pontos fortes: privacidade de dados total, sem custo de API em escala, customização - Pontos fracos: infraestrutura mais complexa, qualidade ainda inferior aos top comerciais
Quando Usar Cada Provedor
Use Claude quando: - A tarefa envolve análise de documentos longos (contratos, papers, código longo) - Precisa de raciocínio em múltiplos steps com lógica complexa - O output deve seguir instruções detalhadas com alta fidelidade - Você quer Prompt Caching para prefixos que repetem (system prompts longos, documentos de referência)
Use GPT-4o quando: - Você precisa de function calling/tools com schemas complexos (API mais madura) - A tarefa envolve visão + texto com análise nuançada - Você usa embeddings em escala (text-embedding-3-large é referência) - Você precisa de geração de imagem (DALL-E 3) integrada
Use Gemini quando: - Contexto > 100k tokens (contratos gigantes, codebases inteiros) - Volume alto e custo é crítico (Flash é o mais barato com boa qualidade) - Você precisa de grounded search (resposta fundamentada em busca Google ao vivo)
Use modelo open source quando: - Dados não podem sair da sua infraestrutura (saúde, financeiro, jurídico regulado) - Volume é muito alto e o custo de API tornaria o produto inviável - Você precisa de fine-tuning específico do domínio
A Interface Unificada: Por Que Abstrair
Construir um LLMClient abstrato com implementações concretas por provedor te dá:
Fallback automático: se Claude falhar, tenta GPT-4o sem mudança no código de negócio
Troca de provedor: atualizar de GPT-4 para GPT-4o é uma linha de configuração, não refatoração
A/B testing: servir 50% dos usuários para Anthropic e 50% para OpenAI para comparar qualidade
Testabilidade: mock um único protocolo em vez de dois SDKs diferentes
Monitoramento unificado: métricas de custo/latência/qualidade no mesmo sistema
O padrão é o mesmo que você usa para banco de dados: um Repository abstrato com implementações PostgresRepository e MongoRepository. Mesma ideia aplicada a LLMs.
Aprofundamento Técnico
O Protocolo Comum: O Que Todo LLM Precisa
Todo request de LLM tem o mesmo DNA:
# Estrutura universal de um request LLM
{
"model": str, # identificador do modelo
"messages": [ # histórico de conversa
{"role": "system", "content": str}, # instrução do sistema (opcional)
{"role": "user", "content": str}, # input do usuário
{"role": "assistant", "content": str}, # resposta anterior (para multi-turn)
],
"max_tokens": int, # limite de tokens de output
"temperature": float, # 0.0 = determinístico, 1.0+ = criativo
# ...parâmetros opcionais específicos de cada provedor
}E todo response tem:
{
"text": str, # o texto gerado
"input_tokens": int, # tokens consumidos no input
"output_tokens": int, # tokens gerados no output
# ...metadados específicos do provedor (stop reason, etc.)
}A nossa classe Resposta no starter captura exatamente isso — o mínimo viável que toda integração precisa.
Como o SDK da Anthropic Funciona
from anthropic import Anthropic
client = Anthropic(api_key='...') # usa ANTHROPIC_API_KEY do env se omitido
# Request básico
response = client.messages.create(
model='claude-haiku-4-5-20251001',
max_tokens=500,
system='Você é um assistente técnico.', # system prompt separado de messages
messages=[
{'role': 'user', 'content': 'O que é um tensor?'}
]
)
# Extraindo o texto
texto = response.content[0].text # content é lista (pode ter múltiplos blocos)
# Extraindo uso de tokens
input_tokens = response.usage.input_tokens
output_tokens = response.usage.output_tokens
# Stop reason (importante para tool_use loops)
stop_reason = response.stop_reason # 'end_turn' | 'max_tokens' | 'tool_use'Diferença crucial: Anthropic separa system do array messages. OpenAI coloca system dentro de messages como {"role": "system", ...}. Isso importa na hora de construir o adapter.
Como o SDK da OpenAI Funciona
from openai import OpenAI
client = OpenAI(api_key='...') # usa OPENAI_API_KEY do env
response = client.chat.completions.create(
model='gpt-4o-mini',
max_tokens=500,
messages=[
{'role': 'system', 'content': 'Você é um assistente técnico.'}, # system dentro de messages
{'role': 'user', 'content': 'O que é um tensor?'}
]
)
texto = response.choices[0].message.content # choices[0] (pode ter N completions)
input_tokens = response.usage.prompt_tokens
output_tokens = response.usage.completion_tokensComo o SDK da Google Funciona
import google.generativeai as genai
genai.configure(api_key='...') # usa GOOGLE_API_KEY do env
model = genai.GenerativeModel('gemini-1.5-flash')
# Gemini tem API diferente — não usa messages como lista diretamente
response = model.generate_content('O que é um tensor?')
texto = response.text
# Para multi-turn (chat)
chat = model.start_chat()
response = chat.send_message('O que é um tensor?')
texto = response.text
# Tokens de uso
input_tokens = response.usage_metadata.prompt_token_count
output_tokens = response.usage_metadata.candidates_token_countGemini tem a API mais diferente das três. O GenerativeModel é instanciado separadamente e o sistema de chat é stateful no objeto chat.
Tratamento de Erros por Provedor
Cada provedor tem suas exceções específicas, mas o padrão de tratamento é o mesmo:
# Anthropic
from anthropic import RateLimitError, APIError, APIConnectionError
# anthropic.RateLimitError → 429
# anthropic.APIError → 4xx/5xx genérico
# anthropic.APIConnectionError → timeout/rede
# OpenAI
from openai import RateLimitError, APIError, APIConnectionError
# mesmos nomes, implementações diferentes
# Google
from google.api_core.exceptions import ResourceExhausted, GoogleAPIError
# ResourceExhausted → 429 (rate limit)
# GoogleAPIError → erros genéricos da API
# Pattern unificado de retry
import time
import random
def com_retry(fn, max_tentativas=3, base_delay=1.0):
for tentativa in range(max_tentativas):
try:
return fn()
except (RateLimitError, ResourceExhausted):
if tentativa == max_tentativas - 1:
raise
# Backoff exponencial com jitter (aleatoriedade previne "thundering herd")
delay = base_delay * (2 ** tentativa) + random.uniform(0, 1)
time.sleep(delay)
except (APIConnectionError,) as e:
if tentativa == max_tentativas - 1:
raise
time.sleep(base_delay)O jitter (aleatoriedade) é crítico: se 100 clients falham ao mesmo tempo e todos tentam de novo no mesmo segundo, você causa outro rate limit. O jitter espalha as tentativas no tempo.
Exemplos Anotados
Exemplo 1: Implementação Completa dos Clientes
import os
import time
import random
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional
@dataclass
class Resposta:
texto: str
tokens_input: int
tokens_output: int
modelo: str
provedor: str
@property
def custo_estimado_usd(self) -> float:
"""Estimativa de custo baseada em preços públicos de 2025."""
precos = {
# (input_per_1M, output_per_1M)
'anthropic/claude-haiku-4-5-20251001': (0.25, 1.25),
'anthropic/claude-sonnet-4-6': (3.0, 15.0),
'openai/gpt-4o-mini': (0.15, 0.60),
'openai/gpt-4o': (2.50, 10.0),
'google/gemini-1.5-flash': (0.075, 0.30),
}
key = f'{self.provedor}/{self.modelo}'
if key not in precos:
return 0.0
inp, out = precos[key]
return (self.tokens_input / 1_000_000) * inp + (self.tokens_output / 1_000_000) * out
class LLMClient(ABC):
"""Interface abstrata — todos os clientes implementam esta."""
@abstractmethod
def completar(
self,
mensagens: list[dict],
max_tokens: int = 500,
temperature: float = 0.7,
system: Optional[str] = None,
) -> Resposta:
pass
def completar_com_retry(self, *args, max_tentativas=3, **kwargs) -> Resposta:
"""Retry com backoff exponencial + jitter."""
for tentativa in range(max_tentativas):
try:
return self.completar(*args, **kwargs)
except Exception as e:
nome_erro = type(e).__name__
# Só retenta em erros recuperáveis
if 'RateLimit' in nome_erro or 'ResourceExhausted' in nome_erro or 'Timeout' in nome_erro:
if tentativa < max_tentativas - 1:
delay = (2 ** tentativa) + random.uniform(0, 0.5)
print(f"[retry] {nome_erro} — aguardando {delay:.1f}s (tentativa {tentativa+1}/{max_tentativas})")
time.sleep(delay)
continue
raise # não recuperável — propaga imediatamente
class ClienteAnthropic(LLMClient):
"""Cliente para API da Anthropic (Claude)."""
def __init__(self, modelo: str = 'claude-haiku-4-5-20251001'):
# TODO 1: Inicialize o cliente Anthropic
# from anthropic import Anthropic
# self.client = Anthropic(api_key=os.environ.get('ANTHROPIC_API_KEY'))
# self.modelo = modelo
self.modelo = modelo
self._client = None # substituir por cliente real no TODO
def completar(self, mensagens, max_tokens=500, temperature=0.7, system=None) -> Resposta:
# TODO 2: Use self.client.messages.create para chamar a API
# - model=self.modelo
# - max_tokens=max_tokens
# - messages=mensagens
# - system=system (se fornecido)
# Retorne Resposta(texto, tokens_input, tokens_output, self.modelo, 'anthropic')
raise NotImplementedError("TODO: implemente ClienteAnthropic.completar()")
class ClienteOpenAI(LLMClient):
"""Cliente para API da OpenAI (GPT)."""
def __init__(self, modelo: str = 'gpt-4o-mini'):
# TODO 3: Inicialize o cliente OpenAI
# from openai import OpenAI
# self.client = OpenAI(api_key=os.environ.get('OPENAI_API_KEY'))
# self.modelo = modelo
self.modelo = modelo
def completar(self, mensagens, max_tokens=500, temperature=0.7, system=None) -> Resposta:
# TODO 4: Use self.client.chat.completions.create
# ATENÇÃO: OpenAI coloca system DENTRO de messages, não separado
# Se system for fornecido, adicione {'role': 'system', 'content': system} no INÍCIO de mensagens
# response.choices[0].message.content para o texto
# response.usage.prompt_tokens / completion_tokens para tokens
raise NotImplementedError("TODO: implemente ClienteOpenAI.completar()")
class ClienteMultiProvider(LLMClient):
"""Orquestra múltiplos clientes com fallback automático."""
def __init__(self, clientes: list[LLMClient]):
# Ordem de preferência: primeiro cliente é o primário
self.clientes = clientes
def completar(self, mensagens, max_tokens=500, temperature=0.7, system=None) -> Resposta:
erros = []
for cliente in self.clientes:
try:
return cliente.completar_com_retry(
mensagens, max_tokens=max_tokens,
temperature=temperature, system=system
)
except Exception as e:
erros.append(f"{type(cliente).__name__}: {e}")
print(f"[fallback] {type(cliente).__name__} falhou: {e}")
continue
raise RuntimeError(
f"Todos os provedores falharam:\n" + "\n".join(erros)
)Como o arquiteto lê este código
O código define uma interface de provedor de LLM. class LLMClient(ABC) declara uma interface abstrata — como interface em Java/TypeScript: qualquer classe que herdar de LLMClient é obrigada a implementar os métodos com @abstractmethod. @dataclass em Resposta é açúcar sintático: gera automaticamente o construtor __init__ com todos os campos declarados. @property define um método que se acessa como atributo — resposta.custo_estimado_usd chama a função sem parênteses. getattr(usage, 'cache_creation_input_tokens', 0) lê o campo se existir, retorna 0 caso contrário — leitura defensiva para campos que podem estar ausentes dependendo do provedor.
Exemplo 2: Implementações Reais com Comparação de Qualidade
# Implementação completa (depois de completar os TODOs)
from anthropic import Anthropic as AnthropicSDK
from openai import OpenAI as OpenAISDK
import google.generativeai as genai
class ClienteAnthropic(LLMClient):
def __init__(self, modelo='claude-haiku-4-5-20251001'):
self.client = AnthropicSDK(api_key=os.environ['ANTHROPIC_API_KEY'])
self.modelo = modelo
def completar(self, mensagens, max_tokens=500, temperature=0.7, system=None):
kwargs = {
'model': self.modelo,
'max_tokens': max_tokens,
'messages': mensagens,
}
if system:
kwargs['system'] = system
response = self.client.messages.create(**kwargs)
return Resposta(
texto=response.content[0].text,
tokens_input=response.usage.input_tokens,
tokens_output=response.usage.output_tokens,
modelo=self.modelo,
provedor='anthropic',
)
class ClienteOpenAI(LLMClient):
def __init__(self, modelo='gpt-4o-mini'):
self.client = OpenAISDK(api_key=os.environ['OPENAI_API_KEY'])
self.modelo = modelo
def completar(self, mensagens, max_tokens=500, temperature=0.7, system=None):
# OpenAI: system vai DENTRO de messages, não separado
msgs = list(mensagens) # cópia para não mutar o original
if system:
msgs = [{'role': 'system', 'content': system}] + msgs
response = self.client.chat.completions.create(
model=self.modelo,
max_tokens=max_tokens,
messages=msgs,
)
return Resposta(
texto=response.choices[0].message.content,
tokens_input=response.usage.prompt_tokens,
tokens_output=response.usage.completion_tokens,
modelo=self.modelo,
provedor='openai',
)
# Comparação de qualidade entre provedores
def comparar_provedores(pergunta: str):
clientes = {
'Claude Haiku': ClienteAnthropic('claude-haiku-4-5-20251001'),
'GPT-4o mini': ClienteOpenAI('gpt-4o-mini'),
}
mensagem = [{'role': 'user', 'content': pergunta}]
system = 'Responda de forma técnica e concisa em 2-3 frases.'
print(f"\nPergunta: {pergunta}\n" + "="*50)
for nome, cliente in clientes.items():
start = time.time()
resposta = cliente.completar(mensagem, max_tokens=200, system=system)
latencia = time.time() - start
print(f"\n{nome}:")
print(f" Resposta: {resposta.texto}")
print(f" Tokens: {resposta.tokens_input} in / {resposta.tokens_output} out")
print(f" Custo: ${resposta.custo_estimado_usd:.6f}")
print(f" Latência: {latencia:.2f}s")
comparar_provedores("Explique o mecanismo de atenção em Transformers em 2 frases.")Padrões e Armadilhas
Padrões Corretos
Padrão 1: Env vars para credenciais, nunca hardcode
# CORRETO: lê do ambiente
client = Anthropic(api_key=os.environ.get('ANTHROPIC_API_KEY'))
# ERRADO: expõe a chave no código (vai parar no git)
client = Anthropic(api_key='sk-ant-...')Se a chave hardcoded for ao git, mesmo em branch privada, considere-a comprometida. Rotate imediatamente.
Padrão 2: Backoff com jitter, não sleep fixo Rate limits são compartilhados. Se você tem 10 workers fazendo retry simultâneo com sleep de 2s fixo, todos tentam de novo ao mesmo tempo e causam outro rate limit. Jitter (aleatoriedade de ±0.5s) espalha as tentativas.
Padrão 3: Interface abstrata para testabilidade
# Em produção
cliente = ClienteAnthropic()
# Em testes
class ClienteMock(LLMClient):
def completar(self, mensagens, **kwargs):
return Resposta(texto="resposta mock", tokens_input=10, tokens_output=5, ...)
# Testes não fazem chamada de API real — rápidos e baratosPadrão 4: Logar tokens e custo em produção
# Após cada request:
logger.info("llm_call", extra={
"provedor": resposta.provedor,
"modelo": resposta.modelo,
"tokens_in": resposta.tokens_input,
"tokens_out": resposta.tokens_output,
"custo_usd": resposta.custo_estimado_usd,
"latencia_ms": latencia_ms,
})Sem esses logs, você não sabe quando seus custos explodiram até ver a fatura do mês.
Armadilhas
⚠️ Armadilha 1: Usar o mesmo modelo para tudo
# ERRADO: usa Sonnet (caro) até para classificação simples
modelo = 'claude-sonnet-4-6' # $3/$15 por 1M
# CORRETO: Haiku para classificação, Sonnet para geração complexa
modelo_classificacao = 'claude-haiku-4-5-20251001' # $0.25/$1.25 — 12x mais barato
modelo_geracao = 'claude-sonnet-4-6'⚠️ Armadilha 2: Não tratar choices[0] null no OpenAI
# Pode retornar None se stop reason for 'content_filter'
texto = response.choices[0].message.content
if texto is None: # ← precisa checar!
raise ValueError(f"Conteúdo filtrado. Stop reason: {response.choices[0].finish_reason}")⚠️ Armadilha 3: Mutar a lista de mensagens original
# ERRADO: modifica o original (bug sutil em loops multi-turn)
if system:
mensagens.insert(0, {'role': 'system', 'content': system})
# CORRETO: trabalha em cópia
msgs = [{'role': 'system', 'content': system}] + list(mensagens)⚠️ Armadilha 4: Comparar == em stop_reason do Anthropic
# ERRADO: comparação de string pode falhar se houver versão nova
if response.stop_reason == 'end_turn': # ok hoje
# MELHOR: checar o que importa (não é tool_use)
if response.stop_reason != 'tool_use':
# terminamosSe não for realizar o laboratório, pule para o próximo capítulo.
Ponte para o Lab
O starter tem 4 TODOs que implementam os dois clientes:
TODO 1 — Em ClienteAnthropic.__init__(): importe Anthropic do SDK, instancie com a chave de API do ambiente, e atribua a self.client. A linha é: self.client = Anthropic(api_key=os.environ.get('ANTHROPIC_API_KEY')). Seção de referência: Aprofundamento Técnico → “Como o SDK da Anthropic Funciona”.
TODO 2 — Em ClienteAnthropic.completar(): use self.client.messages.create() com os parâmetros corretos. Extraia response.content[0].text para texto e response.usage.input_tokens/output_tokens para tokens. Retorne um Resposta(...). Seção de referência: Exemplos Anotados → Exemplo 2.
TODO 3 — Em ClienteOpenAI.__init__(): importe OpenAI do SDK, instancie e atribua a self.client. Análogo ao TODO 1 mas para OpenAI.
TODO 4 — Em ClienteOpenAI.completar(): atenção à diferença chave — OpenAI coloca o system DENTRO de messages (não como parâmetro separado). Se system for fornecido, adicione {'role': 'system', 'content': system} no início da lista de mensagens. Use response.choices[0].message.content para o texto e response.usage.prompt_tokens/completion_tokens para tokens. Seção de referência: Aprofundamento Técnico → “Como o SDK da OpenAI Funciona”.
Após implementar, a função comparar_provedores() no main() do starter chama os dois clientes com a mesma pergunta e compara custo e latência. Você verá na prática que Haiku e GPT-4o mini dão respostas de qualidade similar para perguntas factual simples — e a diferença de custo é mínima nessa escala, mas explode com volume.
Agora você está pronto para o lab.