Projeto de portfólio construído para cobrir, ponto a ponto, os requisitos da vaga de Engenheiro de IA Sênior (RD Station / plataforma MentorIA, via BossaBox) e da vaga de Engenheira(o) de Software Backend Sênior — Foco em IA (RD Station, via Greenhouse).
Não é um tutorial de "hello world com LLM" — é um serviço mínimo, mas real, de orquestração de agentes: API HTTP, memória de sessão, execução de ferramentas (tool calling), um servidor MCP exposto, testes automatizados e observabilidade básica (logs estruturados + métricas).
Caso de uso de referência: um agente de agendamento de serviços que consulta horários disponíveis e confirma agendamentos via ferramentas — um domínio comum em plataformas de atendimento via WhatsApp/chat.
| Requisito da vaga | Onde está no projeto |
|---|---|
| Desenvolver/evoluir plataforma de criação e operação de agentes de IA | app/agents/orchestrator.py |
| Integrações com LLMs, ferramentas, APIs, MCPs e serviços externos | app/tools/registry.py, app/tools/builtin.py, app/mcp/server.py |
| Componentes de memória, contexto, orquestração e execução de ferramentas | app/core/context.py, app/core/memory.py, app/core/runtime.py |
| Escalabilidade, observabilidade, segurança e confiabilidade em produção | app/observability/logging.py, endpoint /metrics, auth por API key (app/api/auth.py), tratamento de erro por ferramenta |
| Sistemas distribuídos, integrações via API, backend | app/api/main.py (FastAPI), Dockerfile |
| Diferencial: MCP Servers e soluções integradas com IA | app/mcp/server.py (SDK oficial mcp) |
| Boas práticas de engenharia, qualidade de código, documentação | tests/, ruff, este README, docs/ARCHITECTURE.md |
python -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
cp .env.example .env # preencher ANTHROPIC_API_KEY e API_KEY
# Testes
pytest
# API
uvicorn app.api.main:app --reload
# Servidor MCP (stdio) — para plugar no Claude Desktop/Code
python -m app.mcp.server
# Demonstração autocontida (sem rede) — gera as evidências abaixo
python scripts/demo.pycurl -X POST http://localhost:8000/agent/chat \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{"message": "Quero agendar um corte de cabelo dia 05/09"}'/agent/chat e /metrics exigem o header X-API-Key (valor em API_KEY
no .env); /health é aberto.
$ pytest -q
............................ [100%]
28 passed
$ ruff check .
All checks passed!$ curl -s -o /dev/null -w '%{http_code}\n' localhost:8000/health
200
$ curl -s -o /dev/null -w '%{http_code}\n' -XPOST localhost:8000/agent/chat -d '{"message":"oi"}'
401
$ curl -s -o /dev/null -w '%{http_code}\n' -XPOST localhost:8000/agent/chat -H 'X-API-Key: errado' -d '{"message":"oi"}'
401
$ curl -s -o /dev/null -w '%{http_code}\n' localhost:8000/metrics
401
$ curl -s -o /dev/null -w '%{http_code}\n' localhost:8000/metrics -H "X-API-Key: $API_KEY"
200python scripts/demo.py — uma execução do agente ("Quero agendar um corte de
cabelo dia 05/09, meu nome é Ana"): o modelo chama check_availability,
depois remember_fact, e responde. Cada passo emite um log estruturado JSON:
{"event": "llm_response", "session_id": "demo-1", "iteration": 0, "stop_reason": "tool_use"}
{"event": "tool_call", "session_id": "demo-1", "tool": "check_availability", "arguments": {"date": "2026-09-05", "service": "corte de cabelo"}, "is_error": false}
{"event": "llm_response", "session_id": "demo-1", "iteration": 1, "stop_reason": "tool_use"}
{"event": "tool_call", "session_id": "demo-1", "tool": "remember_fact", "arguments": {"key": "cliente_nome", "value": "Ana"}, "is_error": false}
{"event": "llm_response", "session_id": "demo-1", "iteration": 2, "stop_reason": "end_turn"}GET /metrics ao final (contadores + duração média por chamada) — nesta
execução de demonstração o cliente da Claude API é um stub, então
agent.llm_call_ms não reflete latência de rede real:
{
"counters": {
"agent.runs": 1,
"tool_calls.check_availability": 1,
"tool_calls.remember_fact": 1
},
"avg_duration_ms": {
"agent.llm_call_ms": 0.01,
"tool.check_availability_ms": 0.01,
"tool.remember_fact_ms": 0.01
}
}$ python scripts/demo.py # trecho: list_tools + call_tool do servidor MCP
• check_availability(date, service) — Consulta horários disponíveis para agendamento em uma data.
• book_appointment(date, time, service, client_name) — Confirma um agendamento em um horário específico.
• remember_fact(key, value) — Guarda um fato sobre o cliente/sessão na memória de longo prazo.
call_tool check_availability {"date": "2026-09-05", "service": "corte de cabelo"}
-> {"date": "2026-09-05", "service": "corte de cabelo", "available_slots": ["09:00", "10:30", "14:00", "16:00"]}- Ferramentas desacopladas do orquestrador:
ToolRegistrynão sabe nada sobre o domínio específico — as ferramentas embuiltin.pysão o único lugar com regra de negócio. Isso permite trocar o domínio (ex.: suporte, vendas) sem tocar no orquestrador. - Ferramentas com estado sem acoplar o registro:
remember_factprecisa doMemoryStore/session_idda execução atual, mas oToolRegistryé agnóstico de domínio e passa só os argumentos. A ponte é umContextVar(app/core/runtime.py): o orquestrador ativa umAgentRuntimeem volta do loop e a ferramenta lêcurrent_runtime(). Fora de uma execução de agente (ex.: a mesma ferramenta chamada por um cliente MCP) ela vira no-op. - Loop de tool calling fiel à API: o orquestrador devolve ao modelo os
content blocksreais (tool_usedo assistente emparelhado com otool_resultdo usuário), nãostr(response.content)— é o que faz o loop funcionar contra a Claude API de verdade, não só num mock. - Memória com interface abstrata:
InMemoryStoreé só a implementação de desenvolvimento. Trocar por Redis/Postgres em produção é implementar a mesma interface, sem alterarAgentContextnem o orquestrador. - MCP como camada separada da API HTTP: o mesmo
ToolRegistryalimenta tanto o endpoint REST quanto o servidor MCP — ferramentas escritas uma vez, expostas de duas formas. - Observabilidade desde o primeiro commit: cada chamada ao LLM e cada chamada de ferramenta gera um log estruturado e uma métrica de duração — não foi adicionado depois, faz parte do fluxo principal.
Mais detalhes em docs/ARCHITECTURE.md.
InMemoryStorenão persiste entre restarts — proposital para o escopo de portfólio; a interface já está pronta para Redis/Postgres.- Sem rate limiting na API — a autenticação é por API key única
(
X-API-Key); rate limiting e auth por usuário/JWT ficam como próximo passo antes de multi-tenant.