Ao terminar este artigo você vai conseguir fazer uma chamada à API da Anthropic em Python, transformá-la em um chatbot de terminal que lembra a conversa e testar esse código sem gastar um centavo, usando um servidor falso local. No fim há também uma versão que roda com um modelo no seu computador, via Ollama.
O que você precisa
Python 3.12 (a versão testada aqui; confira o requisito mínimo do SDK na página oficial) e um terminal. Para o modelo hospedado, uma chave da API, criada no Claude Console. A chave é de uso pago por token, e esta página não depende dela para funcionar: o teste usa um servidor falso.
Se a palavra token ainda soa nova, leia antes o que é token em IA e por que ele define o seu custo. Ela explica por que a conta cresce com o tamanho do histórico, algo que vamos ver no chatbot.
Passo 1: instale o SDK e guarde a chave em variável de ambiente
Crie uma pasta e um ambiente virtual, e instale o SDK oficial:
python -m venv .venv
source .venv/bin/activate # no Windows: .venv\Scripts\activate
pip install anthropic
export ANTHROPIC_API_KEY="cole-sua-chave-aqui" # no Windows PowerShell: $env:ANTHROPIC_API_KEY="..."
O SDK lê ANTHROPIC_API_KEY sozinho. Nunca escreva a chave no código nem a envie para um repositório. Se ela vazar, revogue no Console e crie outra.
Passo 2: a primeira chamada
Salve como primeira_chamada.py:
import anthropic
cliente = anthropic.Anthropic() # lê ANTHROPIC_API_KEY do ambiente
resposta = cliente.messages.create(
model="claude-sonnet-5-5",
max_tokens=200,
messages=[{"role": "user", "content": "Explique o que é um token em duas frases."}],
)
print(resposta.content[0].text)
print(resposta.usage.input_tokens, resposta.usage.output_tokens)
Rode com python primeira_chamada.py. O que acontece em cada parte:
modelescolhe o modelo. O identificadorclaude-sonnet-5-5consta na página de modelos da Anthropic consultada em 09/10/2026. Identificadores mudam com o tempo, então confira a lista antes de usar.max_tokensé o limite de tokens da resposta. Ele protege o seu custo.messagesé uma lista de turnos, cada um comrole(userouassistant) econtent.resposta.contenté uma lista de blocos, e o texto está no bloco do tipotext.resposta.usagediz quantos tokens entraram e saíram, que é a base da cobrança.
Esta chamada real não foi executada na redação deste artigo, porque exigiria uma chave paga. A próxima seção mostra como validar o seu código mesmo assim.
Passo 3: o chatbot com memória
A API não lembra nada entre uma chamada e outra. Quem lembra é o seu código: a cada pergunta você reenvia a conversa inteira. É por isso que conversas longas custam mais, já que o histórico entra de novo como entrada.
Salve como chatbot.py:
import os
import sys
import anthropic
MODELO = os.environ.get("ANTHROPIC_MODEL", "claude-sonnet-5-5")
SISTEMA = "Você é um assistente de estudos de IA. Responda em português, em até 3 frases."
def perguntar(cliente, historico, texto):
"""Envia a conversa inteira e devolve a resposta em texto."""
historico.append({"role": "user", "content": texto})
resposta = cliente.messages.create(
model=MODELO,
max_tokens=300,
system=SISTEMA,
messages=historico,
)
texto_resposta = "".join(b.text for b in resposta.content if b.type == "text")
historico.append({"role": "assistant", "content": texto_resposta})
return texto_resposta, resposta.usage
def main():
if not os.environ.get("ANTHROPIC_API_KEY"):
sys.exit("Defina a variável de ambiente ANTHROPIC_API_KEY antes de rodar.")
cliente = anthropic.Anthropic()
historico = []
print("Chatbot pronto. Digite 'sair' para encerrar.")
while True:
entrada = input("você> ").strip()
if entrada.lower() in {"sair", "exit"}:
break
if not entrada:
continue
try:
resposta, uso = perguntar(cliente, historico, entrada)
except anthropic.APIError as erro:
historico.pop() # remove a pergunta que falhou
print(f"erro da API: {erro}", file=sys.stderr)
continue
print(f"ia> {resposta}")
print(f" (entrada: {uso.input_tokens} tokens, saída: {uso.output_tokens} tokens)")
if __name__ == "__main__":
main()
Pontos que valem atenção:
- A função
perguntarrecebe o cliente como parâmetro. Isso permite testar com um cliente diferente do real. - O prompt de sistema (
system) vai fora da lista de mensagens, e define o papel e o tom. - O
try/exceptpegaanthropic.APIError, que cobre limite de uso, chave inválida e queda do serviço. Em caso de erro, a pergunta que falhou sai do histórico para a conversa não ficar com dois turnos seguidos do usuário. - Sem a variável de ambiente, o programa avisa e sai antes de qualquer chamada.
Rode com python chatbot.py e converse. Digite sair para encerrar.
Passo 4: teste sem gastar API
O SDK aceita trocar o endereço da API pelo parâmetro base_url. Para o teste, subimos um servidor HTTP minúsculo na sua máquina que responde como a API responderia, e apontamos o SDK real para ele. Assim você valida o formato do pedido, a leitura da resposta e a memória da conversa sem custo e sem rede.
Salve como test_chatbot.py:
"""Teste sem gastar API: o SDK real fala com um servidor HTTP falso local."""
import json
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
import anthropic
from chatbot import perguntar
class Falso(BaseHTTPRequestHandler):
def do_POST(self):
corpo = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
ultima = corpo["messages"][-1]["content"]
saida = json.dumps({
"id": "msg_teste", "type": "message", "role": "assistant",
"model": corpo["model"],
"content": [{"type": "text", "text": f"Eco: {ultima} (turnos: {len(corpo['messages'])})"}],
"stop_reason": "end_turn", "stop_sequence": None,
"usage": {"input_tokens": 12, "output_tokens": 7},
}).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(saida)))
self.end_headers()
self.wfile.write(saida)
def log_message(self, *a):
pass
servidor = HTTPServer(("127.0.0.1", 0), Falso)
threading.Thread(target=servidor.serve_forever, daemon=True).start()
cliente = anthropic.Anthropic(api_key="chave-falsa", base_url=f"http://127.0.0.1:{servidor.server_port}")
historico = []
r1, uso = perguntar(cliente, historico, "O que é um token?")
r2, _ = perguntar(cliente, historico, "E o contexto?")
assert r1 == "Eco: O que é um token? (turnos: 1)", r1
assert r2.endswith("(turnos: 3)"), r2 # memória: user, assistant, user
assert uso.input_tokens == 12 and len(historico) == 4
print("OK:", r1, "|", r2)
Rode com python test_chatbot.py. O resultado esperado é:
OK: Eco: O que é um token? (turnos: 1) | Eco: E o contexto? (turnos: 3)
O "turnos: 3" na segunda resposta prova a memória: na segunda pergunta o histórico já tinha pergunta, resposta e nova pergunta. Esse padrão serve para qualquer projeto: o cliente real é testado, a rede é falsa e o custo é zero. Depois do teste, rode python chatbot.py com sua chave real para a prova final.
Passo 5 (opcional): o mesmo chatbot com um modelo local via Ollama
O Ollama roda modelos de linguagem no seu computador, sem chave e sem custo por token. A qualidade depende do tamanho do modelo e da sua máquina. O artigo LLM local com Ollama detalha a instalação. Aqui usamos o servidor do Ollama pela API HTTP local, só com a biblioteca padrão do Python.
Com o Ollama instalado e rodando, baixe um modelo pequeno e salve o script ollama_chat.py:
ollama pull qwen2.5:0.5b
import json
import urllib.request
URL = "http://localhost:11434/api/chat"
MODELO = "qwen2.5:0.5b"
def conversar(historico, texto, url=URL):
historico.append({"role": "user", "content": texto})
corpo = json.dumps({"model": MODELO, "messages": historico, "stream": False}).encode()
req = urllib.request.Request(url, corpo, {"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=300) as r:
resposta = json.load(r)["message"]["content"]
historico.append({"role": "assistant", "content": resposta})
return resposta
if __name__ == "__main__":
h = []
print(conversar(h, "Em uma frase, o que é um token em IA?"))
No teste, com a imagem oficial ollama/ollama (versão 0.40.2) em um container com 2 CPUs, o modelo qwen2.5:0.5b respondeu em português, mas a qualidade foi fraca: a resposta foi longa, ignorou o pedido de uma frase e trouxe afirmações imprecisas. Isso é esperado de um modelo de 0,5 bilhão de parâmetros. Serve para provar que o fluxo funciona. Para respostas confiáveis, use um modelo local maior ou a API.
Erros comuns
Could not resolve authentication method: a variávelANTHROPIC_API_KEYnão está definida no terminal em que você roda o script. O SDK só reclama no momento da primeira chamada, não ao criar o cliente. Por isso ochatbot.pyconfere antes.- Esquecer
max_tokens: o parâmetro é obrigatório na API de mensagens. - Enviar só a última pergunta e achar que o modelo lembra do resto.
- Deixar o histórico crescer sem limite. Em um chatbot real, corte ou resuma as mensagens antigas. Veja engenharia de prompt em produção.
Para onde ir agora
Quando o chatbot precisar consultar dados ou executar ações, o próximo passo é dar ferramentas ao modelo, tema de function calling na prática. Para treinar a escrita de instruções, jogue o Mestre do Prompt.
Resumo
- A chave fica em
ANTHROPIC_API_KEY, nunca no código. - A chamada básica usa
model,max_tokensemessages. - A memória do chatbot é o histórico que você reenvia, e ele aumenta o custo.
- Teste com
base_urlapontando para um servidor local falso: custo zero. - Modelo local via Ollama é uma alternativa sem custo por token, com qualidade que depende do tamanho.
Fontes
- Anthropic, "Models overview" (identificador
claude-sonnet-5-5), consultado em 09/10/2026: platform.claude.com/docs/en/about-claude/models/overview - Anthropic, "SDKs, CLI, and libraries", consultado em 09/10/2026: platform.claude.com/docs/en/cli-sdks-libraries/overview
- Ollama, API de chat local: docs.ollama.com (rota
/api/chatvalidada na execução, a página não foi reaberta)
Versões testadas
Em 09/10/2026, em container python:3.12-slim (Python 3.12.15) com 2 CPUs, SDK anthropic 1.12.1: o test_chatbot.py passou e o chatbot.py saiu com a mensagem esperada sem a variável de ambiente. O Ollama 0.40.2 (imagem oficial, 2 CPUs) respondeu pelo ollama_chat.py com qwen2.5:0.5b. A chamada real à API (primeira_chamada.py e chatbot.py com chave) não foi executada. Os containers foram removidos ao final.