Está bem, pessoal, Leo Grant aqui, novamente com uma exploração aprofundada do mundo selvagem do desenvolvimento de agentes. Hoje quero falar sobre algo que tem me atormentado, algo que vi surgindo em fórum após fórum, e, sinceramente, algo com o qual lutei também há poucos meses: a armadilha do “Shiny New Agent Framework”. Nós nos entendemos, certo?
É 2026 e parece que um novo SDK para agentes ou framework aparece a cada duas semanas. Cada um promete ser mais rápido, mais inteligente, mais escalável, ou simplesmente mais fácil de construir agentes autônomos. E como alguém que vive e respira essas coisas, minha reação inicial é sempre uma mistura de excitação e medo de perder uma oportunidade. “É esse o certo? É essa a ferramenta que finalmente torna meu projeto pessoal uma realidade sem semanas de código padrão?”
Recordo que no último outubro estava trabalhando em um agente assistente financeiro pessoal. A ideia era simples: um agente que pudesse monitorar minhas despesas, identificar serviços de assinatura que eu poderia não usar e até negociar melhores tarifas em meu nome. Eu tinha um script básico em Python rodando, utilizando um barramento de mensagens personalizado e muitos `if/else`. Era volumoso, claro, mas funcionava.
Então vi o anúncio do “Aether,” um novo framework para agentes baseado em Rust que ostentava uma incrível concorrência e uma linguagem de definição de agentes declarativa. Meu cérebro reptiliano imediatamente gritou: “Reescreva! Rust é o futuro! Minha confusão em Python é embaraçosa!” Então, passei duas semanas transferindo todo o projeto para o Aether. E adivinha? Embora o Aether fosse realmente rápido e elegante, acabei com um projeto funcionalmente idêntico à minha versão em Python, mas agora precisava aprender um todo novo ecossistema de ferramentas de construção, gerenciamento de dependências e gerenciamento de erros específico do Aether.
Foi uma lição valiosa, e uma que quero compartilhar com vocês hoje. O maior obstáculo no desenvolvimento de agentes não é sempre encontrar o “melhor” framework; trata-se de entender quando usar um framework e, mais importante, quando é melhor ficar com o que você conhece e construir do zero. Hoje falaremos sobre abraçar a mentalidade do “build”, especificamente quando se trata da comunicação central e do gerenciamento de estado dos seus agentes, em vez de adotar cegamente o último SDK.
O Problema Central: Over-Engineering da Comunicação
A maioria dos frameworks para agentes, em seu cerne, busca resolver dois problemas principais:
- Comunicação entre Agentes: Como os agentes se comunicam entre si? Filas de mensagens, RPC, estado compartilhado?
- Gerenciamento de Estado dos Agentes: Como um agente acompanha o que sabe, o que fez e o que precisa fazer em seguida?
E esses são problemas críticos, sem dúvida. Mas muitas vezes, os frameworks oferecem uma solução tão abrangente e opiniosa que se torna excessiva para projetos mais simples ou introduz complexidade desnecessária. Meu agente financeiro, por exemplo, precisava se comunicar apenas com um outro “agente” (uma API de banco fictícia) e seu estado interno. Um barramento de mensagens complexo com roteamento complicado era como usar um foguete para esmagar uma mosca.
Então, o que acontece se simplificarmos? O que acontece se pensarmos no mínimo absoluto de que você precisa para permitir que os agentes se comuniquem e se lembrem de coisas? Não se trata de rejeitar os frameworks para sempre, mas de construir primeiro uma base sólida, entender as mecânicas subjacentes e então decidir se um framework realmente agrega valor em vez de apenas sobrecarregar.
Mensagens Simples: A Base HTTP/JSON
Sejamos brutalmente honestos: para um vasto número de casos de uso dos agentes, especialmente aqueles que interagem com serviços web ou outros sistemas externos, HTTP e JSON são seus melhores amigos. Eles são onipresentes, bem compreendidos e incrivelmente flexíveis. Você não precisa de um protocolo personalizado ou de um corretor de mensagens complexo se seus agentes estão principalmente enviando solicitações e recebendo respostas.
Considere um cenário em que você tem um “Agente Scraper” que extrai dados de um site e um “Agente Processador” que os limpa e analisa. Como eles se comunicam?
“`html
# scraper_agent.py (simplificado)
import requests
import json
def scrape_data(url):
response = requests.get(url)
if response.status_code == 200:
return response.text
return None
def send_to_processor(data):
headers = {'Content-Type': 'application/json'}
payload = {'raw_data': data}
try:
response = requests.post('http://localhost:8001/process', headers=headers, data=json.dumps(payload))
response.raise_for_status() # Lança uma exceção para erros HTTP
print(f"Dados enviados ao processador: {response.json()}")
except requests.exceptions.RequestException as e:
print(f"Erro ao enviar os dados ao processador: {e}")
if __name__ == "__main__":
url_to_scrape = "https://example.com/some_data" # Substitua por uma URL real
raw_content = scrape_data(url_to_scrape)
if raw_content:
send_to_processor(raw_content)
# processor_agent.py (simplificado usando FastAPI)
from fastapi import FastAPI, Request
from pydantic import BaseModel
import uvicorn
app = FastAPI()
class DataPayload(BaseModel):
raw_data: str
@app.post("/process")
async def process_data(payload: DataPayload):
# Em um cenário real, aqui você faria um processamento real
print(f"Dados brutos recebidos para processamento: {payload.raw_data[:50]}...")
processed_result = f"Processado: {payload.raw_data.upper()}" # Exemplo de processamento
return {"status": "success", "processed_data": processed_result}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8001)
Isso é simples, eu sei. Mas também é incrivelmente poderoso. Você tem dois agentes, que funcionam como serviços separados, que se comunicam entre si. Nenhum SDK especial, nenhum formato de mensagem personalizado. Apenas práticas web padrão. A beleza aqui é que você pode escalar esses independentemente, distribuí-los em qualquer lugar e depurar com ferramentas HTTP padrão. Essa abordagem cobre uma quantidade surpreendente de necessidades de comunicação entre agentes sem qualquer bloqueio a frameworks.
Gerenciamento de Estado: Abraçando a Persistência
O segundo grande pedaço é o estado. Um agente não é muito um agente se esquecer tudo entre as execuções. Muitos frameworks oferecem estado em memória ou máquinas de estado complexas. Mas muitas vezes, o que você realmente precisa é uma persistência simples e confiável.
Para o meu agente financeiro, eu precisava armazenar coisas como:
- Minhas assinaturas atuais (nome, custo, data de renovação)
- Minhas categorias de despesas
- Minhas tentativas de negociação históricas
Inicialmente, tentei usar algumas mecânicas de estado em memória fornecidas por um framework, mas assim que o agente foi reiniciado (o que acontece durante o desenvolvimento, acredite), todos aqueles dados valiosos haviam desaparecido. Frustrante!
A solução? Um banco de dados simples. Para muitos projetos pessoais ou até mesmo para sistemas de produção menores, o SQLite é uma ótima escolha. Ele é baseado em arquivos, não requer um servidor separado e o Python tem um excelente suporte integrado.
“““html
# agent_state.py
import sqlite3
import json
class AgentState:
def __init__(self, db_path='agent_data.db'):
self.conn = sqlite3.connect(db_path)
self._create_table()
def _create_table(self):
cursor = self.conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS agent_knowledge (
key TEXT PRIMARY KEY,
value TEXT
)
''')
self.conn.commit()
def set(self, key, data):
cursor = self.conn.cursor()
value = json.dumps(data) # Memoriza objetos complexos como strings JSON
cursor.execute('INSERT OR REPLACE INTO agent_knowledge (key, value) VALUES (?, ?)', (key, value))
self.conn.commit()
def get(self, key):
cursor = self.conn.cursor()
cursor.execute('SELECT value FROM agent_knowledge WHERE key = ?', (key,))
row = cursor.fetchone()
if row:
return json.loads(row[0])
return None
def close(self):
self.conn.close()
# Exemplo de uso:
if __name__ == "__main__":
state = AgentState()
# Memorizza uma assinatura
state.set('subscription:netflix', {'name': 'Netflix', 'cost': 15.99, 'renewal': '2026-04-01'})
state.set('subscription:spotify', {'name': 'Spotify', 'cost': 10.99, 'renewal': '2026-03-25'})
# Obtém uma assinatura
netflix_sub = state.get('subscription:netflix')
print(f"Assinatura Netflix: {netflix_sub}")
# Atualiza uma assinatura
if netflix_sub:
netflix_sub['cost'] = 16.99 # Aumento do preço!
state.set('subscription:netflix', netflix_sub)
print(f"Assinatura Netflix atualizada: {state.get('subscription:netflix')}")
# Memorizza uma lista de categorias de despesa
state.set('spending_categories', ['mercado', 'entretenimento', 'utilidades'])
print(f"Categorias de despesa: {state.get('spending_categories')}")
state.close()
Esta classe `AgentState` é super básica, mas fornece um armazenamento chave-valor que persiste entre as reinicializações dos agentes. Você pode armazenar dicionários, listas, strings – qualquer coisa que possa ser serializada em JSON. Para relações mais complexas, você deve definir mais tabelas, mas para muitos agentes, um simples armazenamento chave-valor é tudo o que você precisa para a “memória” deles.
Colocando Juntas: O Agente “Bare Bones”
Então, se combinarmos essas ideias, como aparece um agente “bare bones”? É um processo que:
- Pode receber comandos (por exemplo, via endpoint HTTP ou uma simples fila de mensagens).
- Pode realizar ações (por exemplo, fazer requisições HTTP, executar scripts locais).
- Pode lembrar coisas (por exemplo, utilizando um armazenamento de estado persistente como SQLite).
- Possui um ciclo principal ou um planejador para decidir o que fazer a seguir.
Imaginemos nosso Agente Scraper de antes, mas agora com um pouco de memória. Ele deve lembrar quais URLs já extraiu e quando, para evitar trabalho redundante ou para tentar novamente tentativas falhas.
“`
# smart_scraper_agent.py
import requests
import json
import time
from datetime import datetime, timedelta
from agent_state import AgentState # Assuming agent_state.py is in the same directory
# Usaremos o FastAPI para receber comandos para iniciar o scraping
from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
import uvicorn
app = FastAPI()
agent_state = AgentState(db_path='scraper_agent_data.db')
class ScrapeRequest(BaseModel):
url: str
target_processor_url: str
def _perform_scrape_and_send(url: str, target_processor_url: str):
"""Função interna para executar o verdadeiro scraping e enviar o resultado."""
last_scraped_info = agent_state.get(f'last_scraped:{url}')
if last_scraped_info:
last_scrape_time = datetime.fromisoformat(last_scraped_info['timestamp'])
# Execute o scraping apenas se já passou mais de uma hora (exemplo)
if datetime.now() - last_scrape_time < timedelta(hours=1):
print(f"Pulando {url}, recentemente scrapado em {last_scrape_time}")
return
print(f"Scraping de {url}...")
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
raw_content = response.text
# Enviar ao processador
headers = {'Content-Type': 'application/json'}
payload = {'raw_data': raw_content}
processor_response = requests.post(target_processor_url, headers=headers, data=json.dumps(payload))
processor_response.raise_for_status()
print(f"Dados de {url} enviados ao processador: {processor_response.json()}")
# Atualizar o estado com o scraping bem-sucedido
agent_state.set(f'last_scraped:{url}', {
'timestamp': datetime.now().isoformat(),
'status': 'success',
'processor_response': processor_response.json()
})
except requests.exceptions.RequestException as e:
print(f"Erro durante o scraping ou envio para {url}: {e}")
# Atualizar o estado com a falha
agent_state.set(f'last_scraped:{url}', {
'timestamp': datetime.now().isoformat(),
'status': 'failed',
'error': str(e)
})
finally:
agent_state.close() # Importante fechar a conexão se não for gerenciada globalmente
@app.post("/scrape")
async def start_scrape(request: ScrapeRequest, background_tasks: BackgroundTasks):
"""Endpoint para ativar uma ação de scraping."""
background_tasks.add_task(_perform_scrape_and_send, request.url, request.target_processor_url)
return {"message": f"Scraping de {request.url} iniciado."}
if __name__ == "__main__":
# Você também pode ter uma tarefa programada aqui que consulta o estado para URLs a serem scrapados
# Por enquanto, nós simplesmente executaremos o servidor FastAPI.
# Para executar: uvicorn smart_scraper_agent:app --reload --port 8000
uvicorn.run(app, host="0.0.0.0", port=8000)
Este `smart_scraper_agent.py` combina nossa comunicação HTTP básica com um estado persistente. Previne scrapings redundantes dentro de uma janela temporal definida e armazena o resultado de cada scraping. É ainda simples, mas começa a mostrar um comportamento "agente" – lembrando, decidindo e agindo com base em seu estado interno e em estímulos externos.
Quando considerar um framework (e por quê)
Aqui, não estou dizendo que os frameworks são ruins. Pelo contrário. Eles definitivamente têm seu lugar. Você deve começar a considerá-los quando:
- Coordenação complexa: Você tem dezenas ou centenas de agentes que precisam coordenar tarefas complexas, formar equipes ou descobrir uns aos outros dinamicamente. Aqui, um robusto barramento de mensagens, a descoberta de serviços e potencialmente uma camada de orquestração de agentes se tornam inestimáveis.
- Comportamentos padronizados: Seus agentes precisam implementar comportamentos comuns como planejamento, definição de metas ou compreensão avançada de linguagem natural. Os frameworks frequentemente fornecem abstrações ou integrações para isso.
- Necessidade de escalabilidade: Você está lidando com uma taxa de transferência de mensagens muito alta ou um número elevado de agentes concorrentes, e precisa de protocolos de comunicação altamente otimizados ou gestão de estado distribuída "pronta para uso".
- Comunidade e ecossistema: Você quer utilizar uma grande comunidade, plugins existentes e padrões testados para arquiteturas de agentes específicas (por exemplo, agentes compatíveis com FIPA).
Mesmo assim, os princípios de comunicação clara e gestão sólida de estado que discutimos hoje permanecem fundamentais. Um bom framework se baseia nesses princípios, não substitui a necessidade de compreendê-los.
Diretrizes práticas
Minha esperança para você hoje é que você saia com um renovado senso de capacitação e um olhar crítico para o próximo anúncio "revolucionário" de um SDK para agentes. Aqui está o que eu quero que você lembre:
- Comece simples: Antes de mergulhar em um framework complexo, delineie as necessidades de comunicação e estado absolutamente mínimas para o seu agente. Você pode resolver 80% com HTTP/JSON e um banco de dados simples como SQLite? Provavelmente.
- Compreenda os primitivos: Mesmo que você acabe usando um framework, dedique um tempo para entender como funcionam a passagem de mensagens e a persistência de estado em um nível fundamental. Esse conhecimento fará de você um melhor debugger e arquiteto.
- Itere, não reescreva: Construa primeiro a funcionalidade do seu agente. Faça com que funcione. Se encontrar um verdadeiro obstáculo de escalabilidade ou complexidade que um framework resolva de forma evidente, considere adotar um. Evite o impulso de "reescrever tudo".
- Concentre-se na lógica do agente: O verdadeiro valor do seu agente está na sua capacidade de tomar decisões, no seu raciocínio e nas tarefas únicas que executa. Não deixe que preocupações de infraestrutura ofusquem o desenvolvimento daquela lógica fundamental.
- Escolha as ferramentas sabiamente: Só porque existe um framework, não significa que seja a ferramenta certa para o seu trabalho específico. Avalie seus custos em relação aos benefícios.
Na próxima vez que iniciar um projeto para agentes, experimente construir você mesmo a comunicação central e a gestão de estado, mesmo que apenas para um proof of concept. Você aprenderá muito, construirá um agente mais resiliente e provavelmente evitará muitas dores de cabeça no futuro. Continue construindo, continue experimentando e não tenha medo de sujar as mãos com os fundamentos. Leo out.
🕒 Published: