# bedrock-agent-starter

Starter de agente no Bedrock com tools, memória, Terraform e evals já ligados

- URL: https://fernando.moretes.com/open-source/bedrock-agent-starter

- Markdown: https://fernando.moretes.com/open-source/bedrock-agent-starter/guide.md?lang=pt

- GitHub: https://github.com/fernando-moretes/app-bedrock-agent-starter

- Homepage: https://fernando-moretes.github.io/app-bedrock-agent-starter/

- Language: Python

- Topics: ai, ai-agents, aws, bedrock, github-actions, lambda, moretes, portfolio, solution-architecture, terraform

- Stars: 0

- Forks: 0

- Updated: 2026-09-29T16:23:42Z

---

O bedrock-agent-starter é um template em Python para colocar um agente de IA no Amazon Bedrock com o que um agente de verdade precisa: loop de tool-use sobre a Converse API, memória plugável, logs estruturados, métricas EMF, Terraform e um harness de evals.

## Por que este repositório existe

Chamar um modelo no Bedrock é uma linha de SDK. Entregar um agente é outra coisa. Entre o `converse()` e um endpoint que alguém opera em produção existe uma lista de peças que todo time acaba montando do zero: registro de tools, memória entre turnos, log com `session_id`, métrica de tokens por turno, tratamento de erro de tool, IaC e alguma forma de saber se a mudança de prompt de ontem quebrou o comportamento de hoje.

O problema não é o esforço de escrever cada peça uma vez, é manter as peças coerentes entre si quando o modelo muda, quando entra uma tool nova ou quando alguém troca o backend de memória. Montei este starter para que essas decisões estejam tomadas e visíveis no código, num formato que dá para ler numa tarde.

A proposta é ser opinativo onde a opinião economiza tempo (Converse API, Pydantic para schema de tool, JSON estruturado no log) e substituível onde o contexto de cada empresa manda (backend de estado do Terraform, tags, naming, limites de IAM). Faça fork, configure `AWS_REGION` e o modelo, e você tem um agente respondendo no terminal antes de pensar em infraestrutura.

## O que vem no template

- **Loop do agente:** Bedrock Converse API com tool-use nativo, a mesma interface para Claude, Nova, Llama e Mistral.
- **Registro de tools:** três tools prontas (`calculator`, `get_time` e um stub de `web_search`) e um decorator `@tool` para adicionar as suas.
- **Memória plugável:** em memória no desenvolvimento local, DynamoDB em produção.
- **Observabilidade:** log JSON por turno com `session_id`, `turn`, `model_id` e métricas EMF no namespace `BedrockAgent`.
- **Deploy e qualidade:** Terraform para Lambda (Python 3.12), API Gateway HTTP API e DynamoDB; CI com ruff, mypy e pytest; docs em MkDocs Material.

## Como uma pergunta percorre o agente

O mesmo núcleo em `src/agent/` atende o CLI local e o handler Lambda. Só a borda e o backend de memória mudam.

### 💻 Local

- agent chat CLI (frontend)
- Memória in-memory (storage)

### 🟧 AWS: entrada

- API Gateway HTTP API (edge)
- Lambda Python 3.12 (compute)

### 🤖 Núcleo do agente

- Agent loop Converse API (compute)
- Tool registry @tool (compute)
- Amazon Bedrock BEDROCK_MODEL_ID (ai)

### 🟧 AWS: estado e telemetria

- DynamoDB sessions (data)
- CloudWatch logs + EMF (security)

### Fluxos

- user -> cli: dev local
- user -> apigw: HTTP em produção
- apigw -> lambda: invoca
- cli -> loop: turno
- lambda -> loop: turno
- loop -> bedrock: converse() com toolConfig
- loop -> tools: executa toolUse
- cli -> mem-local: histórico
- lambda -> ddb: histórico por session_id
- loop -> cw: log JSON + métricas

## Instalar e usar

1. **Clone e crie o ambiente**: Requer Python 3.11 ou superior. Crie um virtualenv e instale com `pip install -e ".[dev]"`, que traz ruff, mypy e pytest junto.

2. **Configure credenciais e modelo**: Exporte `AWS_REGION` (ou `AWS_PROFILE`) e `BEDROCK_MODEL_ID`. O padrão do README é um Claude Sonnet; o modelo precisa estar habilitado na sua conta e região, senão a primeira chamada volta `AccessDeniedException`.

3. **Converse no terminal**: Rode `agent chat`. Cada chamada de tool aparece como linha `[tool]` antes da resposta, o que torna visível quando o modelo escolheu a tool errada.

4. **Rode as evals antes de mudar prompt ou modelo**: `pytest tests/evals/` reexecuta `golden.jsonl` e falha se a substring ou a chamada de tool esperada sumir.

5. **Faça o deploy com Terraform**: Em `terraform/`, rode `terraform init` e `terraform apply -var="project=my-agent"`. Defina antes o backend de estado: o template não traz um.

_Quickstart: do clone ao primeiro turno_

```bash
git clone https://github.com/fernando-moretes/app-bedrock-agent-starter.git
cd app-bedrock-agent-starter
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

export AWS_REGION=us-east-1
export BEDROCK_MODEL_ID=anthropic.claude-3-5-sonnet-20241022-v2:0

agent chat
# > what time is it in Tokyo, and what is (123 * 456) - 789?
# [tool] get_time(tz="Asia/Tokyo")
# [tool] calculator(expression="(123 * 456) - 789") -> 55299
```

## Como o loop funciona

A escolha central foi a **Converse API** em vez de `InvokeModel`. Com `InvokeModel`, cada família de modelo tem seu próprio formato de payload, e trocar Claude por Nova vira refatoração. A Converse normaliza mensagens, `toolConfig` e blocos `toolUse`/`toolResult`, então `BEDROCK_MODEL_ID` passa a ser configuração, não código.

O turno segue o ciclo clássico: o agente envia o histórico e a lista de tools, o modelo responde com texto ou com um ou mais `toolUse`, o registro executa a função Python correspondente, o resultado volta como `toolResult` e o ciclo repete até o modelo devolver texto final. Na pergunta do exemplo, `get_time` e `calculator` entram no mesmo turno.

O histórico mora atrás de uma interface de memória. Local, fica num dicionário e some quando o processo termina. Na Lambda, vai para a tabela DynamoDB de sessões, chaveada por `session_id`, que é o que permite conversa de vários turnos sobre uma função sem estado.

O **CLI e o handler Lambda chamam o mesmo núcleo** em `src/agent/`. Isso importa mais do que parece: o comportamento que você valida no terminal é o que vai para produção, e as evals exercitam o mesmo código.

_Uma tool nova é um decorator e uma função; o schema JSON sai das anotações via Pydantic_

```python
# src/agent/tools.py
from agent.tools import tool

@tool(description="Translate text between languages using a deterministic table.")
def translate(text: str, source_lang: str, target_lang: str) -> str:
    ...
    return translated
```

## Observabilidade e evals: o que separa demo de serviço

Cada turno emite uma linha JSON com `session_id`, `turn`, `model_id`, `input_tokens`, `output_tokens`, `tool_calls` e `duration_ms`. Com isso, uma busca no CloudWatch Logs Insights por `session_id` reconstrói a conversa inteira de um usuário que reclamou, sem guardar prompt em texto livre em outro lugar.

As métricas vão em formato EMF para o namespace `BedrockAgent`: `Turns`, `InputTokens`, `OutputTokens`, `Duration` e `ToolErrors`. EMF evita chamar `PutMetricData` no caminho da requisição; a métrica sai do próprio log. `InputTokens` é a que vira alarme de custo, porque histórico longo cresce o input a cada turno sem ninguém perceber. `ToolErrors` é a que avisa de falha silenciosa: o modelo costuma contornar uma tool quebrada e responder algo plausível, e sem essa métrica o erro só aparece na reclamação.

O harness de evals é deliberadamente simples: um `golden.jsonl` com prompt, substring esperada e tool esperada, executado pelo pytest. Não mede qualidade semântica. Mede regressão, que é o defeito mais comum quando alguém troca o modelo ou reescreve o system prompt numa sexta-feira.

> **O que o Terraform deixa para você, de propósito:** O módulo provisiona Lambda, API Gateway HTTP API, tabela DynamoDB, roles IAM e log group. Backend de estado, tags, naming e permission boundaries ficam de fora porque cada organização já tem os seus. Rodar `terraform apply` sem backend remoto grava o estado local na sua máquina: o próximo colega que aplicar vai tentar recriar tudo. Configure o backend e revise a policy da role da Lambda (escopo de `bedrock:InvokeModel` por ARN de modelo) antes do primeiro deploy.

## Perguntas frequentes

### Preciso usar Claude?

Não. Como o loop usa a Converse API, basta trocar `BEDROCK_MODEL_ID` por um modelo Nova, Llama ou Mistral habilitado na sua conta. Confira se o modelo escolhido suporta tool-use na Converse; nem todos suportam.

### A tool `web_search` funciona?

Não, é um stub. Ela existe para mostrar o formato de uma tool que chama serviço externo. Ligar a um provedor real é trabalho seu, inclusive timeout e tratamento de erro.

### Por que não Bedrock Agents gerenciado?

Use o serviço gerenciado quando quiser que a AWS opere o loop e aceitar a configuração dele. Este starter serve quando você quer o loop em código Python, testável com pytest e com log e métrica sob seu controle.

## Para quem é

Use quando você precisa de um agente com poucas tools, conversa de vários turnos e um endpoint HTTP, e quer começar com observabilidade e evals já no lugar em vez de adicioná-las depois do primeiro incidente. Não use como está se o seu caso exige orquestração de vários agentes, RAG com base vetorial ou guardrails de conteúdo: nada disso vem no template, e o esforço de adaptação passa a ser maior que o de partir de outra base. Trate-o como esqueleto a ser lido e cortado, não como framework a ser seguido.

## Referências

- [GitHub: fernando-moretes/app-bedrock-agent-starter](https://github.com/fernando-moretes/app-bedrock-agent-starter)
- [Documentação do projeto / Project docs](https://fernando-moretes.github.io/app-bedrock-agent-starter/)
- [Amazon Bedrock: Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html)

## Links

- [GitHub repository](https://github.com/fernando-moretes/app-bedrock-agent-starter)
- [Homepage](https://fernando-moretes.github.io/app-bedrock-agent-starter/)
