Existe uma indústria inteira vendendo frameworks de agente, e isso criou a impressão de que construir um é complicado. Não é. Um agente de IA é um laço: o modelo escreve o que quer fazer, o seu código executa, devolve o resultado e chama o modelo de novo. Tudo o mais — memória, ferramentas, planejamento — é detalhe em cima disso.
Este tutorial constrói um agente funcional em Python puro, sem LangChain, sem CrewAI, sem dependência além do cliente HTTP. No fim você vai ter um agente que lê arquivos, roda contas, consulta uma API e sabe parar. E vai saber exatamente onde cada real foi gasto.
Agente = laço modelo → ferramenta → modelo com condição de parada e teto de custo. 120 linhas de Python. Roda sobre a API por token da GPUBrasil (compatível com a da OpenAI), a partir de R$ 0,49 por milhão de tokens de entrada.
O que é um agente, sem marketing
Um LLM sozinho só faz uma coisa: recebe texto e devolve texto. Ele não lê o seu banco, não abre arquivo, não chama API. Um agente é o programa que dá esses braços a ele, com três peças:
- Ferramentas: funções normais do seu código (
ler_arquivo,consultar_pedido,somar) que o modelo pode pedir para executar. - O laço: você chama o modelo, ele responde "quero rodar
consultar_pedido(1234)", você roda, devolve o resultado e chama de novo. Repete até ele responder sem pedir nada. - Os limites: máximo de voltas, teto de tokens, lista fechada do que pode ser executado. Sem isso, um agente entra em ciclo e queima saldo em silêncio.
É isso. Se você já escreveu um while com um switch dentro, você já escreveu 80% de um agente.
Passo 1: a conta e a chave
Vamos usar a API de inferência por token da GPUBrasil. Ela fala o mesmo protocolo da API da OpenAI, então qualquer biblioteca compatível funciona — só muda a URL base e a chave.
- Crie a conta em gpubrasil.com.br e adicione saldo (Pix, boleto ou cartão, em reais).
- No painel, vá em Chaves de API e gere uma chave. Ela começa com
gpub_live_e só aparece uma vez. - Guarde em variável de ambiente — nunca no código.
export GPUBRASIL_API_KEY="gpub_live_suachaveaqui"
pip install openai
Teste em três linhas antes de escrever o agente. Se isto responder, o resto é só lógica:
import os
from openai import OpenAI
cliente = OpenAI(
base_url="https://gpubrasil.com.br/v1",
api_key=os.environ["GPUBRASIL_API_KEY"],
)
r = cliente.chat.completions.create(
model="gpub-plus",
messages=[{"role": "user", "content": "Responda apenas: ok"}],
)
print(r.choices[0].message.content)
Qual modelo escolher para um agente
Agente é o caso de uso que mais consome token: cada volta do laço reenvia todo o histórico. A escolha do modelo é a diferença entre R$ 3 e R$ 300 no fim do mês.
| Modelo | Entrada (1M) | Saída (1M) | Quando usar no agente |
|---|---|---|---|
| gpub-fast | R$ 0,49 | R$ 1,09 | Classificar, extrair, roteirizar. O "operário" do laço. |
| gpub-mini | R$ 0,59 | R$ 3,49 | Tarefas objetivas, alto volume. |
| gpub-plus | R$ 0,69 | R$ 3,99 | Padrão recomendado. Bom em código e em seguir formato. |
| gpub-base | R$ 7,90 | R$ 17,90 | Quando o plus erra o plano com frequência. |
| gpub-max | R$ 17,90 | R$ 84,90 | Raciocínio longo, base de código inteira. Caro por volta. |
Recomendação prática: comece no gpub-plus. Se o agente errar o formato, não suba de modelo antes de apertar o prompt — na maioria das vezes o problema é instrução vaga, não capacidade.
Passo 2: as ferramentas
Ferramenta é função Python comum. A única regra é que ela receba e devolva coisas serializáveis em texto, e que ela mesma valide a entrada — o modelo vai errar o argumento uma hora, e a ferramenta é a última linha de defesa.
import json, pathlib
RAIZ = pathlib.Path("./dados").resolve() # o agente não sai daqui
def ler_arquivo(caminho: str) -> str:
"""Lê um arquivo de texto dentro de ./dados."""
alvo = (RAIZ / caminho).resolve()
if not alvo.is_relative_to(RAIZ): # trava de path traversal
return "ERRO: caminho fora da pasta permitida."
if not alvo.exists():
return f"ERRO: {caminho} não existe."
return alvo.read_text(encoding="utf-8")[:8000]
def listar_arquivos() -> str:
"""Lista os arquivos disponíveis."""
return "\n".join(p.name for p in RAIZ.iterdir() if p.is_file()) or "(vazio)"
def calcular(expressao: str) -> str:
"""Avalia uma expressão aritmética simples."""
if not set(expressao) <= set("0123456789+-*/(). "):
return "ERRO: só aritmética básica."
try:
return str(eval(expressao, {"__builtins__": {}}, {}))
except Exception as e:
return f"ERRO: {e}"
FERRAMENTAS = {
"ler_arquivo": (ler_arquivo, "ler_arquivo(caminho) — lê um arquivo de ./dados"),
"listar_arquivos": (listar_arquivos, "listar_arquivos() — lista os arquivos disponíveis"),
"calcular": (calcular, "calcular(expressao) — avalia uma conta aritmética"),
}
resolve() não é decoraçãoSem a checagem is_relative_to, um pedido de ../../.env lê as suas credenciais e o modelo devolve elas no chat. Agente é código que executa entrada não confiável: trate cada ferramenta como um endpoint público.
Passo 3: o laço
Aqui está a decisão de projeto que vale o tutorial inteiro. Existem duas formas de o modelo pedir uma ferramenta:
tool_callsnativo: campo estruturado do protocolo. Elegante, mas o suporte varia de modelo para modelo — alguns pedem a ferramenta certa e depois ignoram o resultado que você devolve.- Protocolo em texto: você manda o modelo responder um JSON num formato combinado e faz o parse. Feio, e funciona em todos os modelos.
Para um agente que precisa ser confiável, comece pelo protocolo em texto. Ele não depende de nenhuma capacidade opcional, é trivial de depurar (você lê exatamente o que o modelo escreveu) e migra para tool_calls depois, se você quiser.
SISTEMA = """Você é um agente que resolve tarefas usando ferramentas.
Ferramentas disponíveis:
{ferramentas}
A cada passo responda APENAS um objeto JSON, sem texto em volta, num destes formatos:
{{"acao": "usar_ferramenta", "ferramenta": "nome", "args": {{...}}, "porque": "1 frase"}}
{{"acao": "responder", "resposta": "a resposta final ao usuário"}}
Regras:
- Uma ferramenta por vez. Espere o resultado antes de pedir a próxima.
- Se já tem informação suficiente, responda. Não chame ferramenta à toa.
- Se uma ferramenta devolver ERRO, não repita a mesma chamada igual."""
def agente(pergunta: str, modelo="gpub-plus", max_voltas=8, teto_reais=0.50):
catalogo = "\n".join(f"- {desc}" for _, desc in FERRAMENTAS.values())
mensagens = [
{"role": "system", "content": SISTEMA.format(ferramentas=catalogo)},
{"role": "user", "content": pergunta},
]
gasto = 0.0
for volta in range(1, max_voltas + 1):
r = cliente.chat.completions.create(
model=modelo,
messages=mensagens,
temperature=0, # agente não improvisa
response_format={"type": "json_object"},
)
bruto = r.choices[0].message.content
gasto += custo_da_chamada(r) # ver passo 4
print(f" [volta {volta}] R$ {gasto:.4f} — {bruto[:90]}")
if gasto > teto_reais:
return f"PARADO: estourou o teto de R$ {teto_reais:.2f}."
try:
passo = json.loads(bruto)
except json.JSONDecodeError:
mensagens += [{"role": "assistant", "content": bruto},
{"role": "user", "content": "Isso não era JSON válido. Responda só o JSON."}]
continue
if passo.get("acao") == "responder":
return passo.get("resposta", "")
nome = passo.get("ferramenta")
if nome not in FERRAMENTAS:
resultado = f"ERRO: ferramenta '{nome}' não existe. Use uma da lista."
else:
fn, _ = FERRAMENTAS[nome]
try:
resultado = str(fn(**passo.get("args", {})))
except TypeError as e:
resultado = f"ERRO de argumentos: {e}"
mensagens += [
{"role": "assistant", "content": bruto},
{"role": "user", "content": f"Resultado de {nome}: {resultado}"},
]
return "PARADO: número máximo de voltas atingido."
Repare no que está ali por um motivo:
temperature=0— em agente, criatividade é ruído. Você quer o mesmo plano para a mesma pergunta.response_format={"type": "json_object"}— reduz muito a chance de vir texto em volta do JSON.- O JSON inválido não mata o laço. Você devolve o erro e o modelo corrige. Agente que estoura exceção no primeiro parse ruim é agente que não sobrevive à produção.
- Ferramenta inexistente também vira mensagem, não exceção. O modelo alucina nome de ferramenta; a recuperação é dizer isso a ele.
max_voltaseteto_reais— as duas coisas que impedem que um bug custe dinheiro enquanto você dorme.
Passo 4: saber quanto custou
Toda resposta da API traz o uso de tokens. Como os preços são em reais, o custo de cada volta é uma multiplicação — nada de converter dólar, IOF ou fechamento de câmbio.
PRECOS = { # R$ por 1 milhão de tokens
"gpub-fast": (0.49, 1.09),
"gpub-mini": (0.59, 3.49),
"gpub-plus": (0.69, 3.99),
"gpub-base": (7.90, 17.90),
"gpub-max": (17.90, 84.90),
}
def custo_da_chamada(r) -> float:
entrada, saida = PRECOS[r.model]
u = r.usage
return (u.prompt_tokens / 1e6) * entrada + (u.completion_tokens / 1e6) * saida
O custo de um agente cresce em quadrado, não em linha: cada volta reenvia todo o histórico. Um laço de 8 voltas com 2.000 tokens de contexto não custa 8×, custa cerca de 36× uma chamada solta.
Exemplo no gpub-plus: agente de 6 voltas, contexto crescendo até 6 mil tokens, 300 tokens de saída por volta → cerca de R$ 0,02 por execução. Mil execuções por dia = R$ 20/dia. É a conta que decide se o produto fecha.
As três formas de cortar esse custo
- Truncar o histórico: mantenha o sistema, a pergunta e as últimas 3 trocas. O agente raramente precisa da volta 1 na volta 7.
- Resumir resultados grandes: se
ler_arquivodevolve 8 mil tokens, você paga por eles em toda volta seguinte. Devolva o trecho relevante, não o arquivo. - Modelo por etapa: use
gpub-fastpara decidir qual ferramenta chamar e um modelo maior só na redação da resposta final. Cai fácil 70% da conta.
Passo 5: rodando
if __name__ == "__main__":
print(agente("Quantas linhas tem o maior arquivo em ./dados? "
"Some esse número com 100 e me diga o total."))
[volta 1] R$ 0,0004 — {"acao":"usar_ferramenta","ferramenta":"listar_arquivos"...
[volta 2] R$ 0,0011 — {"acao":"usar_ferramenta","ferramenta":"ler_arquivo","args"...
[volta 3] R$ 0,0021 — {"acao":"usar_ferramenta","ferramenta":"calcular","args"...
[volta 4] R$ 0,0029 — {"acao":"responder","resposta":"O maior arquivo tem 412..."}
O maior arquivo tem 412 linhas. Somando 100, o total é 512.
Três centavos de milésimo. E, mais importante, você viu cada passo — o que é a diferença entre depurar um agente e adivinhar por que ele fez o que fez.
De protótipo a produção: o que falta
Memória entre execuções
O agente acima esquece tudo ao terminar. Para memória, persista as mensagens com uma chave de sessão. Não guarde o histórico inteiro para sempre: guarde um resumo gerado pelo modelo mais barato a cada N trocas, e os últimos turnos crus.
def compactar(mensagens, limite=12):
if len(mensagens) <= limite:
return mensagens
antigas, recentes = mensagens[1:-6], mensagens[-6:]
resumo = cliente.chat.completions.create(
model="gpub-fast", # resumir é tarefa de modelo barato
messages=[{"role": "user", "content":
"Resuma em até 10 linhas os fatos que precisam ser lembrados:\n"
+ json.dumps(antigas, ensure_ascii=False)}],
).choices[0].message.content
return [mensagens[0], {"role": "user", "content": f"Contexto anterior: {resumo}"}] + recentes
Paralelismo
O laço é sequencial por natureza, mas as tarefas não. Se você tem 500 documentos para o mesmo agente processar, rode 500 laços independentes com um pool de threads. Comece com 8 simultâneos e suba olhando a latência.
Observabilidade
Registre, para cada execução: pergunta, modelo, número de voltas, tokens de entrada e saída, custo, ferramentas chamadas e desfecho. Sem isso você não consegue responder "por que a conta dobrou esta semana?" — e essa pergunta sempre chega.
Quando o agente pede GPU dedicada
A API por token resolve a maioria dos casos e não tem custo parado. Mas há três situações em que vale subir a sua própria instância com o modelo rodando nela:
- Volume constante e alto. Se o agente roda 24 horas por dia com fila cheia, o ponto de virada aparece: uma RTX 4090 (24 GB) na linha Econômica sai por R$ 2,38/h — cerca de R$ 1.714 no mês inteiro, com throughput ilimitado dentro do que a placa aguenta.
- Modelo específico ou ajustado. Se você fez fine-tuning, precisa servir os seus pesos: nenhuma API por token vai ter o seu modelo.
- Governança. Numa instância dedicada, você controla o modelo, os pesos, os logs e os prompts — não há API de terceiro no caminho. É argumento de controle, útil em processo de LGPD e em contrato corporativo.
Para servir o modelo você mesmo, o template vLLM sobe em 1 clique e expõe exatamente a mesma interface compatível com a da OpenAI — o seu agente troca só a base_url e continua funcionando, sem mudar uma linha da lógica.
Agente a R$ 0,02 por execução no gpub-plus. Uma RTX 4090 Econômica a R$ 2,38/h custa R$ 57,12 por dia. A conta empata em torno de 2.800 execuções diárias. Abaixo disso, token é mais barato e não tem máquina ociosa; acima, a GPU dedicada ganha.
Construa o seu agente hoje
API por token compatível com a da OpenAI, cobrada em reais, e GPUs dedicadas quando o volume justificar. Sem assinatura — você paga o que usar.
Criar conta →Erros que vão te custar tempo
- Não colocar teto de voltas. Dois agentes conversando entre si, ou um que não reconhece o próprio sucesso, rodam até acabar o saldo.
- Devolver o resultado da ferramenta sem rótulo. "412" sozinho no histórico não diz nada ao modelo três voltas depois. Sempre
Resultado de X: .... - Confiar no formato. Todo parse tem que ter caminho de recuperação. O modelo vai quebrar o contrato eventualmente.
- Ferramenta que faz duas coisas.
buscar_e_atualizar()vira fonte de bug: o modelo chama para buscar e acaba escrevendo. Uma ferramenta, uma ação. - Testar só o caminho feliz. Teste com arquivo inexistente, com argumento errado, com a ferramenta fora do ar. É onde o agente real vive.
- Deixar o agente escrever sem confirmação. Ferramenta que apaga, cobra ou envia e-mail precisa de aprovação humana, ou de dry-run por padrão.
Conclusão
Um agente útil cabe em 120 linhas e não precisa de framework nenhum. O que ele precisa é do que quase nenhum tutorial mostra: teto de custo, recuperação de erro de formato, ferramentas que validam a própria entrada e um registro de para onde o dinheiro foi.
Comece pelo laço em texto puro deste artigo, meça o custo real das suas execuções e só então decida entre continuar por token ou subir a sua própria GPU. A decisão vira aritmética — e é assim que ela deve ser.
Continue: como transformar isso numa plataforma · RAG de produção · Langflow, se você prefere visual