Initial commit
This commit is contained in:
52
docs/README.md
Normal file
52
docs/README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Assistente Analítico para Banco de Dados - Documentação
|
||||
|
||||
Documentação do projeto **Assistente Analítico para Banco de Dados** da Inovyo, uma plataforma de IA conversacional que utiliza dados de pesquisas de experiência do cliente (CX) para fornecer insights analíticos.
|
||||
|
||||
## Índice
|
||||
|
||||
| Documento | Descrição |
|
||||
|-----------|-----------|
|
||||
| [Arquitetura](architecture.md) | Visão geral da arquitetura, componentes e fluxo de dados |
|
||||
| [Referência da API](api-reference.md) | Endpoints, modelos de dados e exemplos de uso |
|
||||
| [Modelo de Dados](data-model.md) | Estrutura das tabelas, relacionamentos e convenções |
|
||||
| [Infraestrutura](infrastructure.md) | Recursos AWS provisionados com Pulumi (ECS, ALB, ECR) |
|
||||
| [Deploy](deployment.md) | Processo de build, push e deploy da aplicação |
|
||||
| [Desenvolvimento](development.md) | Guia para contribuição, estrutura de código e padrões |
|
||||
|
||||
## Visão Geral do Projeto
|
||||
|
||||
A Inovyo é uma plataforma especializada em gestão de experiência do cliente (CX), responsável por coletar, processar e analisar dados de pesquisas. Este projeto cria um chatbot que atua como **consultor especialista**, utilizando os dados dos dashboards da Inovyo para:
|
||||
|
||||
- Responder perguntas sobre resultados de pesquisas
|
||||
- Identificar padrões, tendências e oportunidades
|
||||
- Auxiliar gestores na interpretação de indicadores (NPS, CSAT, CES)
|
||||
- Oferecer insights contextualizados e acionáveis
|
||||
|
||||
## Stack Tecnológico
|
||||
|
||||
| Camada | Tecnologia |
|
||||
|--------|-----------|
|
||||
| **Linguagem** | Python 3.12+ |
|
||||
| **Frontend** | Streamlit |
|
||||
| **API** | FastAPI + Uvicorn |
|
||||
| **Agente IA** | LangGraph + LangChain |
|
||||
| **LLMs** | AWS Bedrock (Claude, Llama, Nova) |
|
||||
| **Banco de Dados** | DynamoDB |
|
||||
| **Observabilidade** | Langfuse |
|
||||
| **Infraestrutura** | AWS ECS Fargate, ALB, ECR |
|
||||
| **IaC** | Pulumi (Python) |
|
||||
| **Container** | Docker |
|
||||
|
||||
## Estrutura do Backend
|
||||
|
||||
O pacote `backend` é dividido em módulos com responsabilidades únicas:
|
||||
|
||||
```
|
||||
code/app/backend/
|
||||
├── __init__.py # Marca o diretório como pacote Python
|
||||
├── config.py # Leitura de variáveis de ambiente
|
||||
├── dynamo.py # Cliente DynamoDB, Langfuse, get_contexto
|
||||
├── tools.py # Ferramentas LangChain (@tool)
|
||||
├── agent_bedrock.py # LLM Bedrock, grafo LangGraph, AgentState
|
||||
└── orquestrador.py # Ponto de entrada: main()
|
||||
```
|
||||
88
docs/api-reference.md
Normal file
88
docs/api-reference.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Referência da API
|
||||
|
||||
A API REST é servida pelo FastAPI na porta `8000`, implementada em [app/api.py](../code/app/api.py) e orquestrada por [app/backend/orquestrador.py](../code/app/backend/orquestrador.py).
|
||||
|
||||
## Endpoints
|
||||
|
||||
### `GET /`
|
||||
|
||||
Health check da aplicação.
|
||||
|
||||
**Resposta:**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `POST /agent`
|
||||
|
||||
Executa uma consulta no agente analítico.
|
||||
|
||||
**Request Body (`QueryRequest`):**
|
||||
|
||||
| Campo | Tipo | Obrigatório | Descrição |
|
||||
|-------|------|-------------|-----------|
|
||||
| `query` | `string` | Sim | Pergunta do usuário em português |
|
||||
| `history` | `string` | Não | Histórico de mensagens anteriores (serializado) |
|
||||
| `model` | `string` | Não | ID do modelo LLM (padrão: Claude Haiku) |
|
||||
| `base` | `string` | Não | Nome do dashboard/base de dados |
|
||||
|
||||
**Response Body (`QueryResponse`):**
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `response` | `string` | Resposta do agente em português |
|
||||
| `input_tokens` | `int` | Total de tokens de entrada consumidos |
|
||||
| `output_tokens` | `int` | Total de tokens de saída gerados |
|
||||
| `total_tokens` | `int` | Total de tokens (input + output) |
|
||||
|
||||
**Exemplo — cURL:**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/agent \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"query": "Qual foi meu NPS?",
|
||||
"history": "",
|
||||
"model": "anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
"base": "nome_do_dashboard"
|
||||
}'
|
||||
```
|
||||
|
||||
**Exemplo — Resposta:**
|
||||
|
||||
```json
|
||||
{
|
||||
"response": "O NPS foi...",
|
||||
"input_tokens": 1250,
|
||||
"output_tokens": 340,
|
||||
"total_tokens": 1590
|
||||
}
|
||||
```
|
||||
|
||||
## Modelos Disponíveis
|
||||
|
||||
| ID do Modelo | Provider |
|
||||
|-------------|----------|
|
||||
| `anthropic.claude-haiku-4-5-20251001-v1:0` | Anthropic |
|
||||
| `anthropic.claude-sonnet-4-5-20250929-v1:0` | Anthropic |
|
||||
| `meta.llama4-maverick-17b-instruct-v1:0` | Meta |
|
||||
| `meta.llama4-scout-17b-instruct-v1:0` | Meta |
|
||||
| `amazon.nova-lite-v1:0` | Amazon |
|
||||
| `amazon.nova-pro-v1:0` | Amazon |
|
||||
| `amazon.nova-2-lite-v1:0` | Amazon |
|
||||
|
||||
## Bases Disponíveis
|
||||
|
||||
As bases são carregadas dinamicamente do DynamoDB (tabela `TABLE`, index `item_type_index`, `item_type = "contexto"`). O formato do nome segue o padrão `{cliente}_{dashboard}`.
|
||||
|
||||
## Documentação Interativa
|
||||
|
||||
Com a aplicação rodando:
|
||||
|
||||
- **Swagger UI:** `http://localhost:8000/docs`
|
||||
- **ReDoc:** `http://localhost:8000/redoc`
|
||||
164
docs/architecture.md
Normal file
164
docs/architecture.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# Arquitetura
|
||||
|
||||
## Visão Geral
|
||||
|
||||
O Assistente Analítico é composto por três camadas principais: **interface** (Streamlit e FastAPI), **agente de IA** (LangGraph com Bedrock) e **dados** (DynamoDB).
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Interfaces │
|
||||
│ ┌────────────────────┐ ┌────────────────────────┐ │
|
||||
│ │ Streamlit (8501) │ │ FastAPI (8000) │ │
|
||||
│ │ front.py │ │ api.py │ │
|
||||
│ └────────┬───────────┘ └──────────┬─────────────┘ │
|
||||
│ │ │ │
|
||||
│ └──────────┬──────────────┘ │
|
||||
│ ▼ │
|
||||
│ orquestrador.main(query, history, model, base) │
|
||||
│ │ │
|
||||
│ ┌───────────────────▼──────────────────────────┐ │
|
||||
│ │ agent_bedrock — LangGraph │ │
|
||||
│ │ ┌────────┐ ┌───────┐ ┌────────────┐ │ │
|
||||
│ │ │ Model │───▶│Router │───▶│ Tools │ │ │
|
||||
│ │ │ Node │◀───│ │ │ Node │ │ │
|
||||
│ │ └────────┘ └───────┘ └────────────┘ │ │
|
||||
│ └──────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ Serviços AWS │ │
|
||||
│ │ ┌──────────┐ ┌────────────────────────────┐ │ │
|
||||
│ │ │ Bedrock │ │ DynamoDB │ │ │
|
||||
│ │ │ (LLMs) │ │ (contexto + dados pré- │ │ │
|
||||
│ │ └──────────┘ │ processados) │ │ │
|
||||
│ │ └────────────────────────────┘ │ │
|
||||
│ └──────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ Observabilidade │ │
|
||||
│ │ ┌──────────┐ ┌──────────────────────────┐ │ │
|
||||
│ │ │ Langfuse │ │ CloudWatch Logs │ │ │
|
||||
│ │ └──────────┘ └──────────────────────────┘ │ │
|
||||
│ └──────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Módulos do Backend
|
||||
|
||||
O pacote `backend` é organizado por responsabilidade:
|
||||
|
||||
```
|
||||
backend/
|
||||
├── config.py ← variáveis de ambiente
|
||||
├── dynamo.py ← depende de config
|
||||
├── tools.py ← depende de config, dynamo
|
||||
├── agent_bedrock.py ← depende de config, tools
|
||||
└── orquestrador.py ← depende de config, dynamo, agent_bedrock
|
||||
```
|
||||
|
||||
Não há dependências circulares entre os módulos.
|
||||
|
||||
### `config.py`
|
||||
|
||||
Lê todas as variáveis de ambiente na inicialização e exporta como constantes:
|
||||
|
||||
| Variável | Descrição |
|
||||
|----------|-----------|
|
||||
| `TABLE` | Tabela DynamoDB |
|
||||
| `REGION` | Região AWS |
|
||||
| `AWS_ACCOUNT` | ID da conta AWS |
|
||||
| `SECRET_NAME` | Nome do secret no Secrets Manager |
|
||||
|
||||
### `dynamo.py`
|
||||
|
||||
- Instancia o cliente `dynamodb` (boto3) usando `REGION`
|
||||
- `get_secret()` — busca credenciais do Langfuse no Secrets Manager
|
||||
- Inicializa o objeto `langfuse` na carga do módulo
|
||||
- `get_contexto(dashboard: str) -> dict` — carrega contexto, filtro e itens disponíveis do DynamoDB para o dashboard informado
|
||||
|
||||
### `tools.py`
|
||||
|
||||
Define a classe `ReportTools`, instanciada por requisição com o mapeamento de IDs local → real.
|
||||
|
||||
```
|
||||
ReportTools(id_mapping: dict[str, str])
|
||||
├── get_variable_value(id, variable) — busca uma variável no DynamoDB
|
||||
├── get_variables_list(id) — lista as variáveis disponíveis para um ID
|
||||
└── as_tools() -> list[StructuredTool] — retorna as tools prontas para o agente
|
||||
```
|
||||
|
||||
| Tool (nome exposto ao LLM) | Método | Descrição |
|
||||
|---------------------------|--------|-----------|
|
||||
| `get_variable_value` | `get_variable_value(id, variable)` | Busca o valor de uma variável no DynamoDB para o ID informado |
|
||||
| `get_variable_list` | `get_variables_list(id)` | Lista as variáveis disponíveis para o ID informado |
|
||||
|
||||
Os IDs expostos ao LLM são locais (`id_1`, `id_2`, …). Internamente cada método converte o ID local para o ID real do DynamoDB via `self.id_mapping`. Por usar estado de instância em vez de variável global, múltiplas requisições simultâneas ficam completamente isoladas.
|
||||
|
||||
### `agent_bedrock.py`
|
||||
|
||||
- `AgentState` — TypedDict com `messages` e `current_step`
|
||||
- `create_bedrock_llm(model_id, region, tools)` — instancia `ChatBedrockConverse` e vincula as tools via `bind_tools`
|
||||
- `call_model(state, llm)` — nó do grafo: invoca o LLM
|
||||
- `call_tools(state, tools_map)` — nó do grafo: executa as tool calls usando o `tools_map` da requisição
|
||||
- `should_continue(state)` — roteador: `"tools"` se há tool_calls, `"end"` se não
|
||||
- `create_agent(inference_profile_arn, region, tools)` — constrói `tools_map = {t.name: t for t in tools}`, monta e compila o `StateGraph`
|
||||
|
||||
**Fluxo do Grafo:**
|
||||
|
||||
```
|
||||
SystemMessage + HumanMessage
|
||||
│
|
||||
▼
|
||||
┌─────────┐
|
||||
│ model │ ◄── LLM via Bedrock
|
||||
└────┬────┘
|
||||
│
|
||||
should_continue?
|
||||
├── tool_calls → ┌───────┐
|
||||
│ │ tools │ → executa tools → volta ao model
|
||||
│ └───────┘
|
||||
└── (fim) → [END]
|
||||
```
|
||||
|
||||
### `orquestrador.py`
|
||||
|
||||
Ponto de entrada da lógica de negócio. A função `main(user_query, history, model, base)`:
|
||||
|
||||
1. Chama `get_contexto(base)` para carregar o contexto do dashboard
|
||||
2. Constrói `id_mapping` (`id_1`, `id_2`, … → IDs reais do DynamoDB) e `local_items` (IDs locais → descrições)
|
||||
3. Instancia `ReportTools(id_mapping)` com o mapeamento isolado da requisição
|
||||
4. Monta o `SYSTEM_PROMPT` dinamicamente com contexto, regras de filtro, `local_items` e histórico
|
||||
5. Cria o agente via `create_agent(model, REGION, tools=report_tools.as_tools())`
|
||||
6. Invoca o agente com o estado inicial
|
||||
7. Agrega tokens de todos os `AIMessage` do estado final
|
||||
8. Retorna `response`, `input_tokens`, `output_tokens`, `total_tokens`
|
||||
|
||||
## Interfaces
|
||||
|
||||
### `front.py` — Streamlit (porta 8501)
|
||||
|
||||
- Importa `from backend import orquestrador`
|
||||
- Lista bases disponíveis consultando DynamoDB (`item_type_index`, `item_type = "contexto"`)
|
||||
- Seleção de modelo LLM e base via `st.selectbox`
|
||||
- Histórico de conversas em `session_state`
|
||||
- Efeito de digitação caractere a caractere
|
||||
- Exibe consumo de tokens por resposta
|
||||
|
||||
### `api.py` — FastAPI (porta 8000)
|
||||
|
||||
- Importa `from .backend import orquestrador`
|
||||
- `GET /` — health check
|
||||
- `POST /agent` — recebe `QueryRequest`, chama `orquestrador.main()`, retorna `QueryResponse`
|
||||
|
||||
## Modelos LLM Suportados
|
||||
|
||||
| Modelo | Provider | Prefixo de Rota |
|
||||
|--------|----------|-----------------|
|
||||
| `anthropic.claude-haiku-4-5-20251001-v1:0` | Anthropic | `us` |
|
||||
| `anthropic.claude-sonnet-4-5-20250929-v1:0` | Anthropic | `global` |
|
||||
| `meta.llama4-maverick-17b-instruct-v1:0` | Meta | `us` |
|
||||
| `meta.llama4-scout-17b-instruct-v1:0` | Meta | `us` |
|
||||
| `amazon.nova-lite-v1:0` | Amazon | `us` |
|
||||
| `amazon.nova-pro-v1:0` | Amazon | `us` |
|
||||
| `amazon.nova-2-lite-v1:0` | Amazon | `global` |
|
||||
|
||||
Todos acessados via AWS Bedrock inference profiles cross-region. O ARN é construído dinamicamente com `REGION` e `AWS_ACCOUNT`.
|
||||
129
docs/data-model.md
Normal file
129
docs/data-model.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Modelo de Dados
|
||||
|
||||
## Visão Geral
|
||||
|
||||
Os dados de contexto e relatórios pré-processados são armazenados e consumidos diretamente do **DynamoDB**.
|
||||
|
||||
## Estrutura de Tabelas
|
||||
|
||||
Cada combinação cliente + dashboard gera **três tabelas**:
|
||||
|
||||
```
|
||||
{cliente}_{dashboard}_contatos
|
||||
{cliente}_{dashboard}_respostas
|
||||
{cliente}_{dashboard}_pesquisa
|
||||
```
|
||||
|
||||
### Convenção de Nomenclatura
|
||||
|
||||
- Letras minúsculas
|
||||
- Espaços e hífens substituídos por `_`
|
||||
|
||||
**Exemplo:**
|
||||
- Cliente: `Bacio Di Latte`, Dashboard: `Transacional Loja App`
|
||||
- Tabelas: `bacio_transacional_loja_app_contatos`, `bacio_transacional_loja_app_respostas`, `bacio_transacional_loja_app_pesquisa`
|
||||
|
||||
---
|
||||
|
||||
### 1. `{cliente}_{dashboard}_contatos`
|
||||
|
||||
Contatos recebidos para ativar as pesquisas. Cada linha = um contato único.
|
||||
|
||||
**Colunas Fixas:**
|
||||
|
||||
| Coluna | Descrição |
|
||||
|--------|-----------|
|
||||
| `contact_id` | Identificador único do contato |
|
||||
| `surveyid` | Identificador da pesquisa associada |
|
||||
| `name` | Nome do contato |
|
||||
| `email` | E-mail |
|
||||
| `phone` | Telefone |
|
||||
| `status` | Status (enviado, inválido, abriu e-mail, etc.) |
|
||||
| `url` | Link único de resposta |
|
||||
| `data_recebimento` | Data de recebimento (`yyyy-mm-dd`) |
|
||||
| `mes_recebimento` | Mês de recebimento (`mm/yyyy`) |
|
||||
|
||||
**Colunas Variáveis** (dependem do cliente): `cidade`, `estado`, `cpf`, `produto_comprado`, `canal_de_compra`, `nome_da_loja`, `valor_da_venda`, etc.
|
||||
|
||||
---
|
||||
|
||||
### 2. `{cliente}_{dashboard}_respostas`
|
||||
|
||||
Respostas consolidadas da pesquisa. Cada linha = um respondente único (versão mais atualizada).
|
||||
|
||||
**Colunas Fixas:**
|
||||
|
||||
| Coluna | Descrição |
|
||||
|--------|-----------|
|
||||
| `responseid` | Identificador único da resposta |
|
||||
| `status` | Status (completou, parcial, inválida) |
|
||||
| `contact_id` | Referência ao contato (FK) |
|
||||
| `data_resposta` | Data da resposta (`yyyy-mm-dd`) |
|
||||
| `mes_resposta` | Mês da resposta (`mm/yyyy`) |
|
||||
|
||||
**Colunas Variáveis (Perguntas):** Uma coluna por pergunta, nomeada pelo `shortname` da tabela `pesquisa`. Exemplos: `nps`, `atendimento_cordialidade`, `achou_produto`, `comentarios`.
|
||||
|
||||
---
|
||||
|
||||
### 3. `{cliente}_{dashboard}_pesquisa`
|
||||
|
||||
Catálogo de perguntas dos questionários. Dicionário das colunas dinâmicas da tabela de respostas.
|
||||
|
||||
| Coluna | Descrição |
|
||||
|--------|-----------|
|
||||
| `order_by` | Ordem da pergunta no questionário |
|
||||
| `title` | Texto completo da pergunta |
|
||||
| `shortname` | Alias usado como nome de coluna em `respostas` |
|
||||
| `status` | Status (ativa, oculta, inativa) |
|
||||
|
||||
---
|
||||
|
||||
### Relacionamentos
|
||||
|
||||
```
|
||||
contatos (1) ─── (N) respostas (join via contact_id)
|
||||
|
||||
pesquisa ──────────▶ respostas (shortname = nome das colunas dinâmicas)
|
||||
```
|
||||
|
||||
## DynamoDB — Tabela `poc_dnx_monthly_summary`
|
||||
|
||||
Armazena dados pré-processados e contexto para o agente.
|
||||
|
||||
### Estrutura
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `id` | `string` (PK) | Identificador (ex: `1`, `2`, `bacio_transacional_loja_app_contexto`) |
|
||||
| `item_type` | `string` (GSI) | Tipo do item (ex: `contexto`) |
|
||||
| `contexto` | `string` | Descrição contextual do dashboard |
|
||||
| `filter_key` | `string` | Tipo de filtro: `period` ou `event` |
|
||||
| `itens_disponiveis` | `map` | Dicionário `{id: descrição}` dos dados disponíveis |
|
||||
| `chaves_consolidadas` | `string` | Lista de colunas/variáveis disponíveis por ID |
|
||||
| *variáveis dinâmicas* | `string` | Dados pré-processados (resumos mensais, NPS, etc.) |
|
||||
|
||||
### Índice Secundário Global
|
||||
|
||||
- **`item_type_index`** — Permite consultar todos os itens de um tipo (ex: listar todas as bases com `item_type = "contexto"`)
|
||||
|
||||
### Formato do ID de Período
|
||||
|
||||
Quando `filter_key = "period"`, os IDs seguem o formato `year_month`:
|
||||
- Ano: 4 dígitos (2025, 2024, etc.)
|
||||
- Mês: 2 dígitos (01 a 12)
|
||||
- Exemplo: `2025_05` = maio de 2025
|
||||
|
||||
### Formato do ID de Evento
|
||||
|
||||
Quando `filter_key = "event"`, os IDs descrevem o evento:
|
||||
- Formato: `Nome - Cidade DD/MM/YYYY`
|
||||
|
||||
## Cálculo de NPS
|
||||
|
||||
Dentro dos dados existe a variável `NPS` que contém `distribuicao` com notas e quantidade de respondentes:
|
||||
|
||||
- Notas **0-6**: Detratores
|
||||
- Notas **7-8**: Neutros
|
||||
- Notas **9-10**: Promotores
|
||||
|
||||
**Fórmula:** `NPS = %Promotores - %Detratores`
|
||||
92
docs/deployment.md
Normal file
92
docs/deployment.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Deploy
|
||||
|
||||
## Fluxo
|
||||
|
||||
```
|
||||
1. Build Docker ──▶ 2. Push ECR ──▶ 3. Atualizar SHA no Pulumi ──▶ 4. pulumi up
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Build da Imagem Docker
|
||||
|
||||
Execute a partir da raiz do repositório:
|
||||
|
||||
```bash
|
||||
docker build code/ --platform linux/amd64 -t <ECR_REPO_NAME>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Push para o ECR
|
||||
|
||||
### Autenticar no ECR
|
||||
|
||||
```bash
|
||||
aws ecr get-login-password --region <REGION> \
|
||||
| docker login --username AWS --password-stdin <AWS_ACCOUNT>.dkr.ecr.<REGION>.amazonaws.com
|
||||
```
|
||||
|
||||
### Tag e push
|
||||
|
||||
```bash
|
||||
docker tag <ECR_REPO_NAME>:latest \
|
||||
<AWS_ACCOUNT>.dkr.ecr.<REGION>.amazonaws.com/<ECR_REPO_NAME>:latest
|
||||
|
||||
docker push <AWS_ACCOUNT>.dkr.ecr.<REGION>.amazonaws.com/<ECR_REPO_NAME>:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Obter o SHA da imagem publicada
|
||||
|
||||
Após o push, obtenha o digest da imagem no ECR:
|
||||
|
||||
```bash
|
||||
aws ecr describe-images \
|
||||
--repository-name <ECR_REPO_NAME> \
|
||||
--region <REGION> \
|
||||
--query 'sort_by(imageDetails, &imagePushedAt)[-1].imageDigest' \
|
||||
--output text
|
||||
```
|
||||
|
||||
O retorno será no formato `sha256:xxxxxxxx...`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Atualizar o SHA no Pulumi
|
||||
|
||||
Edite [infra/ecs_alb/Pulumi.Inovyo.yaml](../infra/ecs_alb/Pulumi.Inovyo.yaml) e atualize o campo `ecr_image_digest` com o valor obtido no passo anterior:
|
||||
|
||||
```yaml
|
||||
ecr_image_digest: sha256:<novo-digest>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Aplicar com Pulumi
|
||||
|
||||
```bash
|
||||
cd infra/ecs_alb
|
||||
pulumi up --stack Inovyo
|
||||
```
|
||||
|
||||
Para visualizar as mudanças antes de aplicar:
|
||||
|
||||
```bash
|
||||
pulumi preview --diff --stack Inovyo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuração do Container
|
||||
|
||||
O `entrypoint.sh` inicia dois processos:
|
||||
|
||||
1. **FastAPI** (background): `uvicorn app.api:app --host 0.0.0.0 --port 8000`
|
||||
2. **Streamlit** (foreground): `streamlit run app/front.py --server.port 8501 --server.address 0.0.0.0 --server.headless true`
|
||||
|
||||
Após o deploy, a aplicação fica acessível pelo DNS do ALB:
|
||||
|
||||
- **API:** `http://<alb-dns>:8000`
|
||||
- **Streamlit:** `http://<alb-dns>:8501`
|
||||
144
docs/development.md
Normal file
144
docs/development.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Desenvolvimento
|
||||
|
||||
## Estrutura do Projeto
|
||||
|
||||
```
|
||||
agente-bd/
|
||||
├── code/ # Código da aplicação
|
||||
│ ├── app/
|
||||
│ │ ├── api.py # FastAPI — endpoints REST
|
||||
│ │ ├── front.py # Streamlit — interface web
|
||||
│ │ └── backend/
|
||||
│ │ ├── __init__.py # Marca o diretório como pacote Python
|
||||
│ │ ├── config.py # Variáveis de ambiente
|
||||
│ │ ├── dynamo.py # DynamoDB, Langfuse, get_contexto
|
||||
│ │ ├── tools.py # ReportTools: get_variable_value, get_variables_list
|
||||
│ │ ├── agent_bedrock.py # LLM Bedrock, grafo LangGraph
|
||||
│ │ └── orquestrador.py # main() — ponto de entrada
|
||||
│ ├── utils/
|
||||
│ │ └── dynamodb_read_table.py # Utilitários DynamoDB
|
||||
│ ├── main.py # Entry point
|
||||
│ ├── Dockerfile # Imagem Docker
|
||||
│ ├── entrypoint.sh # Script de inicialização
|
||||
│ └── requirements.txt # Dependências Python
|
||||
├── infra/ # Infraestrutura como Código
|
||||
│ ├── ecr/ # Stack ECR
|
||||
│ ├── ecs_alb/ # Stack ECS + ALB
|
||||
│ └── langfuse/ # Stack Langfuse
|
||||
├── docs/ # Esta documentação
|
||||
└── Makefile # Automação de build e deploy
|
||||
```
|
||||
|
||||
## Responsabilidades dos Módulos do Backend
|
||||
|
||||
### `config.py`
|
||||
|
||||
Lê todas as variáveis de ambiente obrigatórias na carga do módulo e as exporta como constantes. Qualquer módulo que precise de configuração importa daqui.
|
||||
|
||||
### `dynamo.py`
|
||||
|
||||
- Instancia o cliente `dynamodb` e o objeto `langfuse` na carga do módulo
|
||||
- `get_secret()` — busca as credenciais do Langfuse no AWS Secrets Manager
|
||||
- `get_contexto(dashboard: str) -> dict` — retorna `contexto`, `filter` e `items_disponiveis` para o dashboard informado
|
||||
|
||||
### `tools.py`
|
||||
|
||||
Define a classe `ReportTools` com as ferramentas do agente:
|
||||
|
||||
- `ReportTools(id_mapping)` — instanciada por requisição; `id_mapping` converte IDs locais (`id_1`, `id_2`, …) para os IDs reais do DynamoDB
|
||||
- `get_variable_value(id, variable)` — busca o valor de uma variável no DynamoDB
|
||||
- `get_variables_list(id)` — lista as variáveis disponíveis para um ID
|
||||
- `as_tools()` — retorna a lista de `StructuredTool` com nomes `get_variable_value` e `get_variable_list`
|
||||
|
||||
### `agent_bedrock.py`
|
||||
|
||||
- `AgentState` — TypedDict com `messages` e `current_step`
|
||||
- `create_bedrock_llm(model_id, region, tools)` — instancia `ChatBedrockConverse` e vincula as tools
|
||||
- `call_model(state, llm)`, `call_tools(state, tools_map)`, `should_continue(state)` — nós e roteador do grafo LangGraph
|
||||
- `create_agent(inference_profile_arn, region, tools)` — monta o `tools_map` e compila o `StateGraph`
|
||||
|
||||
### `orquestrador.py`
|
||||
|
||||
Função `main(user_query, history, model, base)`:
|
||||
- Carrega contexto via `get_contexto(base)`
|
||||
- Constrói `id_mapping` e `local_items` para isolar IDs reais do LLM
|
||||
- Instancia `ReportTools(id_mapping)` e passa as tools ao agente
|
||||
- Monta o system prompt com `local_items` no lugar dos IDs reais
|
||||
- Cria e invoca o agente
|
||||
- Retorna resposta e contagem de tokens
|
||||
|
||||
## Cadeia de Imports
|
||||
|
||||
```
|
||||
config.py
|
||||
↑
|
||||
dynamo.py ←── config
|
||||
↑
|
||||
tools.py ←── config, dynamo
|
||||
↑
|
||||
agent_bedrock.py ←── config
|
||||
↑
|
||||
orquestrador.py ←── config, dynamo, agent_bedrock, tools
|
||||
↑
|
||||
api.py / front.py ←── orquestrador (via pacote backend)
|
||||
```
|
||||
|
||||
## Dependências Principais
|
||||
|
||||
| Pacote | Uso |
|
||||
|--------|-----|
|
||||
| `boto3` | SDK AWS (DynamoDB, Secrets Manager) |
|
||||
| `langchain` | Framework de orquestração LLM |
|
||||
| `langchain-aws` | Integração LangChain + AWS Bedrock |
|
||||
| `langgraph` | Framework de agentes baseado em grafos |
|
||||
| `streamlit` | Interface web interativa |
|
||||
| `fastapi` | Framework de API REST |
|
||||
| `uvicorn` | Servidor ASGI |
|
||||
| `langfuse` | Observabilidade e tracing de LLMs |
|
||||
|
||||
## Fluxo de Desenvolvimento
|
||||
|
||||
1. **Fazer alterações** — Editar os arquivos em `code/app/`
|
||||
3. **Testar localmente** — Rodar FastAPI + Streamlit ou via Docker
|
||||
4. **Validar** — Executar scripts de teste em `scripts/`
|
||||
5. **Build e deploy** — Seguir o [guia de deploy](deployment.md)
|
||||
|
||||
## Adicionando uma Nova Ferramenta ao Agente
|
||||
|
||||
Todas as ferramentas vivem na classe `ReportTools` em `tools.py`. Basta:
|
||||
|
||||
1. Adicionar o método à classe:
|
||||
|
||||
```python
|
||||
def minha_nova_tool(self, param: str) -> str:
|
||||
"""Descrição da tool para o LLM."""
|
||||
real_id = self.id_mapping.get(param, param) # se precisar resolver ID
|
||||
# implementação
|
||||
return resultado
|
||||
```
|
||||
|
||||
2. Registrá-lo em `as_tools()`:
|
||||
|
||||
```python
|
||||
def as_tools(self) -> list:
|
||||
return [
|
||||
...,
|
||||
StructuredTool.from_function(
|
||||
self.minha_nova_tool,
|
||||
name="minha_nova_tool",
|
||||
description="Descrição da tool para o LLM.",
|
||||
),
|
||||
]
|
||||
```
|
||||
|
||||
Não é necessário alterar `agent_bedrock.py` — o `tools_map` é construído dinamicamente a partir da lista retornada por `as_tools()`.
|
||||
|
||||
## Adicionando um Novo Modelo LLM
|
||||
|
||||
Em `agent_bedrock.py`, adicionar o novo modelo aos três dicionários em `create_bedrock_llm()`:
|
||||
|
||||
- `MODEL_ARNS` — ARN do inference profile
|
||||
- `PROVIDER` — provider (`anthropic`, `meta`, `amazon`)
|
||||
- `prefix` — prefixo de rota (`us` ou `global`)
|
||||
|
||||
E adicionar o model ID à lista `MODELS` em `front.py`.
|
||||
126
docs/infrastructure.md
Normal file
126
docs/infrastructure.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Infraestrutura
|
||||
|
||||
A infraestrutura é gerenciada via **Pulumi** (IaC em Python) e provisionada na AWS.
|
||||
|
||||
## Visão Geral dos Recursos
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ AWS Account 305427701314 │
|
||||
│ (us-east-1) │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────┐ │
|
||||
│ │ Application Load Balancer │ │
|
||||
│ │ Portas: 8501 (Streamlit), 8000 (API) │ │
|
||||
│ │ Subnets: públicas (2) │ │
|
||||
│ └─────────────────┬────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────────▼────────────────────────┐ │
|
||||
│ │ ECS Fargate Cluster │ │
|
||||
│ │ Task: assistente-analitico-db-dev │ │
|
||||
│ │ CPU: 256 | Memória: 512 MB │ │
|
||||
│ │ Auto-scaling: 1–3 instâncias (60% CPU) │ │
|
||||
│ │ Subnets: privadas (2) │ │
|
||||
│ └──────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
||||
│ │ ECR │ │ KMS │ │ Secrets │ │
|
||||
│ │ Repo │ │ Key │ │ Manager │ │
|
||||
│ └──────────┘ └──────────┘ └──────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────┐ │
|
||||
│ │ CloudWatch Logs │ │
|
||||
│ │ Log Group: assistente-analitico-db-dev │ │
|
||||
│ │ Retenção: 7 dias │ │
|
||||
│ └──────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Stacks Pulumi
|
||||
|
||||
O diretório `infra/` contém três stacks independentes:
|
||||
|
||||
### 1. `infra/ecr/` — Elastic Container Registry
|
||||
|
||||
Cria o repositório ECR para armazenar as imagens Docker.
|
||||
|
||||
| Configuração | Valor |
|
||||
|-------------|-------|
|
||||
| Stack | `inovyo` |
|
||||
| Repositório | `assistente-analitico-db-dev` |
|
||||
|
||||
### 2. `infra/ecs_alb/` — ECS + Application Load Balancer
|
||||
|
||||
Stack principal que provisiona o cluster ECS Fargate com ALB.
|
||||
|
||||
**Configurações principais:**
|
||||
|
||||
| Configuração | Valor |
|
||||
|-------------|-------|
|
||||
| Stack | `Inovyo` |
|
||||
| Projeto | `assistente-analitico` |
|
||||
| Ambiente | `dev` |
|
||||
| Conta AWS | `305427701314` |
|
||||
| Região | `us-east-1` |
|
||||
|
||||
**Rede:**
|
||||
|
||||
| Recurso | Valor |
|
||||
|---------|-------|
|
||||
| VPC | `vpc-17ceb96c` |
|
||||
| Subnets ALB | 2 subnets públicas |
|
||||
| Subnets ECS | 2 subnets privadas |
|
||||
| Ingress CIDR | `3.14.44.224/32` (IP restrito) |
|
||||
| ALB interno | Não (externamente acessível) |
|
||||
|
||||
**ECS Task:**
|
||||
|
||||
| Configuração | Valor |
|
||||
|-------------|-------|
|
||||
| Task Name | `assisnte-analitico-db-dev` |
|
||||
| CPU | 256 |
|
||||
| Memória | 512 MB |
|
||||
| Desired Count | 1 |
|
||||
| Auto-scaling | 1–3 instâncias, target 60% CPU |
|
||||
| Portas | 8000 (API), 8501 (Streamlit) |
|
||||
|
||||
**Variáveis de ambiente do container:**
|
||||
|
||||
| Variável | Descrição |
|
||||
|----------|-----------|
|
||||
| `LANGFUSE_HOST` | Endpoint do Langfuse |
|
||||
|
||||
**Módulos inclusos:**
|
||||
- `iam.py` — Roles de execução e task com políticas para Bedrock, DynamoDB, Secrets Manager, CloudWatch
|
||||
- `ecs.py` — Definição de task, service, target groups, listeners e auto-scaling
|
||||
- `ecr.py` — Referência ao repositório ECR
|
||||
- `kms.py` — Chave KMS para criptografia
|
||||
- `conf.py` — Carregamento de configuração do Pulumi
|
||||
- `autotag/` — Auto-tagging de recursos AWS
|
||||
|
||||
### 3. `infra/langfuse/` — Langfuse
|
||||
|
||||
Provisionamento da infraestrutura para o serviço de observabilidade Langfuse.
|
||||
|
||||
| Configuração | Valor |
|
||||
|-------------|-------|
|
||||
| Stack | `inovyo` |
|
||||
| Host | `http://172.31.252.176:3000` |
|
||||
|
||||
## Serviços AWS Utilizados
|
||||
|
||||
| Serviço | Uso |
|
||||
|---------|-----|
|
||||
| **ECS Fargate** | Orquestração de containers |
|
||||
| **ALB** | Balanceamento de carga |
|
||||
| **ECR** | Registry de imagens Docker |
|
||||
| **Bedrock** | Inferência de modelos LLM |
|
||||
| **DynamoDB** | Contexto e dados pré-processados do agente |
|
||||
| **Secrets Manager** | Credenciais do Langfuse |
|
||||
| **KMS** | Criptografia |
|
||||
| **CloudWatch** | Logs |
|
||||
| **IAM** | Controle de acesso |
|
||||
|
||||
## Como Deployar Infraestrutura
|
||||
|
||||
Consulte o [Guia de Deploy](deployment.md) para instruções detalhadas.
|
||||
162
docs/langfuse-guide.md
Normal file
162
docs/langfuse-guide.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# Guia de Integração Langfuse
|
||||
|
||||
Este guia descreve como o Langfuse está integrado ao assistente e como utilizar suas funcionalidades de observabilidade e rastreamento.
|
||||
|
||||
## Índice
|
||||
|
||||
- [O que é Langfuse](#o-que-e-langfuse)
|
||||
- [Como está integrado](#como-esta-integrado)
|
||||
- [Credenciais](#credenciais)
|
||||
- [Rastreamento automático via LangChain](#rastreamento-automatico-via-langchain)
|
||||
- [Tags por dashboard](#tags-por-dashboard)
|
||||
- [Adicionando scores customizados](#adicionando-scores-customizados)
|
||||
- [Visualizando traces](#visualizando-traces)
|
||||
|
||||
---
|
||||
|
||||
## O que é Langfuse
|
||||
|
||||
Langfuse é uma plataforma de observabilidade para aplicações LLM que permite:
|
||||
|
||||
- Rastrear automaticamente todas as chamadas ao modelo (tokens, latência, erros)
|
||||
- Inspecionar o histórico de mensagens e chamadas de tools
|
||||
- Adicionar scores e métricas customizadas por trace
|
||||
- Analisar performance e uso ao longo do tempo
|
||||
|
||||
---
|
||||
|
||||
## Como está integrado
|
||||
|
||||
A integração é feita em dois módulos:
|
||||
|
||||
### `dynamo.py` — inicialização do cliente
|
||||
|
||||
O cliente `Langfuse` é instanciado na carga do módulo, usando credenciais obtidas do AWS Secrets Manager:
|
||||
|
||||
```python
|
||||
from langfuse import Langfuse
|
||||
|
||||
secrets = json.loads(get_secret())
|
||||
langfuse = Langfuse(
|
||||
public_key=secrets["LANGFUSE-PUBLIC-KEY"],
|
||||
secret_key=secrets["LANGFUSE-SECRET-KEY"],
|
||||
host=os.environ["LANGFUSE_HOST"],
|
||||
)
|
||||
```
|
||||
|
||||
O objeto `langfuse` é exportado e reutilizado pelo `orquestrador.py`.
|
||||
|
||||
### `orquestrador.py` — rastreamento por execução
|
||||
|
||||
A cada chamada ao agente, um `CallbackHandler` do LangChain é criado e passado na configuração do grafo LangGraph. Isso registra automaticamente no Langfuse todas as etapas da execução — chamadas ao modelo, chamadas de tools e mensagens trocadas.
|
||||
|
||||
Ao final da execução, `langfuse.flush()` garante o envio dos dados pendentes.
|
||||
|
||||
```python
|
||||
from langfuse.langchain import CallbackHandler
|
||||
from .dynamo import langfuse
|
||||
|
||||
langfuse_handler = CallbackHandler()
|
||||
config = {"callbacks": [langfuse_handler], "tags": [base]}
|
||||
final_state = agent.invoke(initial_state, config=config)
|
||||
|
||||
langfuse.flush()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Credenciais
|
||||
|
||||
As chaves do Langfuse são armazenadas no AWS Secrets Manager, no secret definido pela variável de ambiente `SECRET_NAME`. O secret deve conter as seguintes chaves:
|
||||
|
||||
| Chave no secret | Descrição |
|
||||
|-----------------|-----------|
|
||||
| `LANGFUSE-PUBLIC-KEY` | Chave pública do projeto Langfuse |
|
||||
| `LANGFUSE-SECRET-KEY` | Chave secreta do projeto Langfuse |
|
||||
|
||||
O endpoint do servidor é configurado pela variável de ambiente `LANGFUSE_HOST`.
|
||||
|
||||
---
|
||||
|
||||
## Rastreamento automático via LangChain
|
||||
|
||||
O `CallbackHandler` captura automaticamente, sem código adicional:
|
||||
|
||||
- Cada chamada ao modelo Bedrock (input, output, tokens)
|
||||
- Chamadas às tools `get_monthly_report` e `get_consolidated_keys`
|
||||
- Sequência de passos do grafo LangGraph
|
||||
- Erros e exceções durante a execução
|
||||
|
||||
Cada invocação de `orquestrador.main()` gera um trace independente no Langfuse.
|
||||
|
||||
---
|
||||
|
||||
## Tags por dashboard
|
||||
|
||||
A tag `base` (nome do dashboard, ex: `bacio_transacional_loja_app`) é passada em cada execução:
|
||||
|
||||
```python
|
||||
config = {"callbacks": [langfuse_handler], "tags": [base]}
|
||||
```
|
||||
|
||||
Isso permite filtrar traces no Langfuse por cliente/dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Adicionando scores customizados
|
||||
|
||||
Para registrar métricas adicionais em um trace, use a API do cliente `langfuse` após a execução do agente. O trace ID pode ser obtido via `langfuse_handler.get_trace_id()`.
|
||||
|
||||
### Tipos de score
|
||||
|
||||
| Tipo | Uso |
|
||||
|------|-----|
|
||||
| `NUMERIC` | Valores numéricos (ex: satisfação 1–5, tempo de resposta) |
|
||||
| `CATEGORICAL` | Valores de categoria (ex: canal de origem, qualidade) |
|
||||
| `BOOLEAN` | Verdadeiro/falso |
|
||||
|
||||
### Exemplo: score após execução
|
||||
|
||||
```python
|
||||
from langfuse.langchain import CallbackHandler
|
||||
from .dynamo import langfuse
|
||||
|
||||
langfuse_handler = CallbackHandler()
|
||||
config = {"callbacks": [langfuse_handler], "tags": [base]}
|
||||
final_state = agent.invoke(initial_state, config=config)
|
||||
|
||||
trace_id = langfuse_handler.get_trace_id()
|
||||
if trace_id:
|
||||
langfuse.score(
|
||||
trace_id=trace_id,
|
||||
name="canal_origem",
|
||||
value="api",
|
||||
data_type="CATEGORICAL",
|
||||
)
|
||||
|
||||
langfuse.flush()
|
||||
```
|
||||
|
||||
> Sempre chame `langfuse.flush()` depois de registrar scores para garantir o envio.
|
||||
|
||||
---
|
||||
|
||||
## Visualizando traces
|
||||
|
||||
Acesse a interface web do Langfuse no endereço configurado em `LANGFUSE_HOST`.
|
||||
|
||||
### Navegação útil
|
||||
|
||||
| O que ver | Onde ir |
|
||||
|-----------|---------|
|
||||
| Todas as execuções | Traces |
|
||||
| Execuções por dashboard | Traces → filtrar por tag |
|
||||
| Tokens por modelo | Dashboard → Usage |
|
||||
| Erros e falhas | Traces → filtrar por status `ERROR` |
|
||||
| Scores registrados | Trace individual → aba Scores |
|
||||
|
||||
### Referências
|
||||
|
||||
- [Documentação Oficial Langfuse](https://langfuse.com/docs)
|
||||
- [Integração LangChain/LangGraph](https://langfuse.com/docs/integrations/langchain)
|
||||
- [API de Scores](https://langfuse.com/docs/scores/custom)
|
||||
256
docs/pulumi-guide.md
Normal file
256
docs/pulumi-guide.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# Guia Pulumi
|
||||
|
||||
## O que é Pulumi?
|
||||
|
||||
Pulumi é uma ferramenta de **Infrastructure as Code (IaC)**: em vez de clicar no console da AWS, você descreve os recursos em código Python (ou outras linguagens) e o Pulumi cria, atualiza e destrói esses recursos de forma controlada e reproduzível.
|
||||
|
||||
### Conceitos básicos
|
||||
|
||||
| Conceito | O que é |
|
||||
|----------|---------|
|
||||
| **Projeto** | O diretório com `Pulumi.yaml` — define o nome e a linguagem do projeto |
|
||||
| **Stack** | Um ambiente independente do mesmo projeto (ex: `dev`, `prod`, `Inovyo`). Cada stack tem seu próprio estado e configuração |
|
||||
| **Estado** | Arquivo que o Pulumi mantém mapeando cada recurso do código a um recurso real na AWS. É a "memória" do Pulumi |
|
||||
| **`pulumi preview`** | Mostra o que **vai** mudar, sem aplicar nada — equivalente a um "dry run" |
|
||||
| **`pulumi up`** | Aplica as mudanças: cria, atualiza ou remove recursos para corresponder ao código |
|
||||
| **Output** | Valores exportados pelo código após o deploy (ex: a URL do ALB) |
|
||||
|
||||
### Como o Pulumi sabe o que mudar?
|
||||
|
||||
1. Você edita o código ou o arquivo de configuração
|
||||
2. `pulumi preview` compara o código com o estado salvo e mostra o diff
|
||||
3. `pulumi up` aplica o diff na AWS e atualiza o estado
|
||||
|
||||
---
|
||||
|
||||
## Pré-requisitos
|
||||
|
||||
- [Pulumi CLI](https://www.pulumi.com/docs/install/) instalado
|
||||
- AWS CLI configurado com credenciais válidas para a conta
|
||||
- Python 3.12+ e `venv`
|
||||
|
||||
---
|
||||
|
||||
## Estrutura do projeto
|
||||
|
||||
```
|
||||
infra/ecs_alb/
|
||||
├── Pulumi.yaml # Metadados do projeto (nome, runtime)
|
||||
├── Pulumi.Inovyo.yaml # Configuração da stack "Inovyo" (região, VPC, ECS, env vars...)
|
||||
├── __main__.py # Ponto de entrada — define ALB, cluster ECS e chama ecs.deploy_app()
|
||||
├── conf.py # Lê as configurações do Pulumi e as expõe como variáveis Python
|
||||
├── ecs.py # Cria task definition, service, target groups, listeners e auto-scaling
|
||||
├── ecr.py # Referencia a imagem no ECR pelo digest ou tag
|
||||
├── iam.py # Cria execution role e task role com políticas (Bedrock, DynamoDB, Secrets Manager...)
|
||||
└── kms.py # Chave KMS (usada opcionalmente para criptografia de segredos)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Setup inicial
|
||||
|
||||
```bash
|
||||
cd infra/ecs_alb
|
||||
|
||||
# Criar e ativar virtualenv
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
|
||||
# Instalar dependências
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Selecionar a stack
|
||||
pulumi stack select <stack-name>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Comandos principais
|
||||
|
||||
| Comando | O que faz |
|
||||
|---------|-----------|
|
||||
| `pulumi preview` | Mostra o que será criado/alterado/destruído **sem aplicar** |
|
||||
| `pulumi preview --diff` | Igual ao anterior, com diff detalhado de cada recurso |
|
||||
| `pulumi up` | Aplica as mudanças na AWS |
|
||||
| `pulumi up --yes` | Aplica sem pedir confirmação interativa |
|
||||
| `pulumi destroy` | Remove **todos** os recursos da stack da AWS |
|
||||
| `pulumi stack ls` | Lista todas as stacks do projeto |
|
||||
| `pulumi stack output` | Exibe os outputs exportados (ex: URL do ALB) |
|
||||
| `pulumi refresh` | Sincroniza o estado Pulumi com o estado real da AWS |
|
||||
|
||||
Sempre prefira `pulumi preview` antes de `pulumi up` para revisar o impacto das mudanças.
|
||||
|
||||
---
|
||||
|
||||
## Arquivo de configuração: `Pulumi.Inovyo.yaml`
|
||||
|
||||
Toda a configuração da stack fica nesse arquivo. As seções principais:
|
||||
|
||||
### Rede
|
||||
|
||||
```yaml
|
||||
app-ecs:network:
|
||||
vpc_id: <vpc-id>
|
||||
alb_internal: false
|
||||
alb_subnet_ids: # Subnets públicas do ALB (mínimo 2, mesma região)
|
||||
- <subnet-publica-1>
|
||||
- <subnet-publica-2>
|
||||
alb_allow_ingress_cidr: # IPs que podem acessar o ALB
|
||||
- <ip-permitido>/32
|
||||
ecs_subnet_ids: # Subnets privadas dos containers
|
||||
- <subnet-privada-1>
|
||||
- <subnet-privada-2>
|
||||
```
|
||||
|
||||
### ECS / Container
|
||||
|
||||
```yaml
|
||||
app-ecs:ecs:
|
||||
- task_name: assisnte-analitico-db-dev
|
||||
ecr_repo_name: assistente-analitico-db-dev
|
||||
# Use ecr_image_digest OU ecr_image_tag (nunca os dois)
|
||||
ecr_image_digest: sha256:<digest>
|
||||
cpu: 256
|
||||
memory: 512
|
||||
desired_count: 1
|
||||
use_load_balancer: true
|
||||
auto_scaling:
|
||||
min_capacity: 1
|
||||
max_capacity: 3
|
||||
target_value: 60.0 # % de CPU alvo para escalar
|
||||
lb_configs:
|
||||
- name: api
|
||||
listener_port: 8000
|
||||
target_port: 8000
|
||||
container_port: 8000
|
||||
- name: streamlit
|
||||
listener_port: 8501
|
||||
target_port: 8501
|
||||
container_port: 8501
|
||||
env_variables:
|
||||
TABLE: poc_dnx_monthly_summary
|
||||
REGION: us-east-1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fluxo de deploy de nova imagem
|
||||
|
||||
Após buildar e publicar uma nova imagem Docker (veja [deployment.md](deployment.md)):
|
||||
|
||||
**1. Obter o digest da nova imagem:**
|
||||
|
||||
```bash
|
||||
aws ecr describe-images \
|
||||
--repository-name assistente-analitico-db-dev \
|
||||
--region us-east-1 \
|
||||
--query 'sort_by(imageDetails, &imagePushedAt)[-1].imageDigest' \
|
||||
--output text
|
||||
```
|
||||
|
||||
**2. Atualizar o `Pulumi.Inovyo.yaml`:**
|
||||
|
||||
```yaml
|
||||
ecr_image_digest: sha256:<novo-digest>
|
||||
```
|
||||
|
||||
**3. Revisar e aplicar:**
|
||||
|
||||
```bash
|
||||
cd infra/ecs_alb
|
||||
pulumi preview --diff --stack <stack-name>
|
||||
pulumi up --stack <stack-name>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Adicionar ou alterar variáveis de ambiente do container
|
||||
|
||||
Edite a seção `env_variables` em `Pulumi.Inovyo.yaml` e rode `pulumi up`. O Pulumi atualizará a task definition e forçará o re-deploy do serviço ECS automaticamente.
|
||||
|
||||
```yaml
|
||||
env_variables:
|
||||
NOVA_VARIAVEL: valor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verificar o output após o deploy
|
||||
|
||||
```bash
|
||||
pulumi stack output --stack <stack-name>
|
||||
```
|
||||
|
||||
Retorna a URL do ALB, por exemplo:
|
||||
```
|
||||
url: http://alb-assistente-analitico-xxxxxxxx.us-east-1.elb.amazonaws.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Alterar capacidade de auto-scaling
|
||||
|
||||
Edite `auto_scaling` em `Pulumi.Inovyo.yaml`:
|
||||
|
||||
```yaml
|
||||
auto_scaling:
|
||||
min_capacity: 1 # mínimo de tasks rodando
|
||||
max_capacity: 5 # máximo de tasks
|
||||
target_value: 70.0 # escala quando CPU média ultrapassar 70%
|
||||
```
|
||||
|
||||
Depois rode `pulumi up`.
|
||||
|
||||
---
|
||||
|
||||
## Permitir acesso ao ALB por Security Group
|
||||
|
||||
Por padrão, o acesso ao ALB é restrito por CIDR (`alb_allow_ingress_cidr`). Para permitir um security group no lugar de um IP, são necessárias duas alterações:
|
||||
|
||||
**1. Edite `__main__.py`** — troque `cidr_blocks` por `security_groups` na regra de ingress do ALB:
|
||||
|
||||
```python
|
||||
ingress=[aws.ec2.SecurityGroupIngressArgs(
|
||||
protocol="-1",
|
||||
from_port=0,
|
||||
to_port=0,
|
||||
security_groups=config.network["alb_allow_ingress_sgs"],
|
||||
)],
|
||||
```
|
||||
|
||||
**2. Edite `Pulumi.Inovyo.yaml`** — substitua `alb_allow_ingress_cidr` por:
|
||||
|
||||
```yaml
|
||||
app-ecs:network:
|
||||
alb_allow_ingress_sgs:
|
||||
- <security-group-id>
|
||||
```
|
||||
|
||||
> Para manter ambos (IP e SG ao mesmo tempo), adicione os dois campos à mesma `SecurityGroupIngressArgs`, ou crie entradas separadas em `ingress`.
|
||||
|
||||
---
|
||||
|
||||
## Solução de problemas
|
||||
|
||||
**`pulumi up` falha com erro de estado inconsistente:**
|
||||
```bash
|
||||
pulumi refresh --stack <stack-name> # sincroniza estado com AWS
|
||||
pulumi up --stack <stack-name>
|
||||
```
|
||||
|
||||
**Recurso preso em estado de update:**
|
||||
```bash
|
||||
pulumi stack export --stack <stack-name> > state.json
|
||||
# Editar state.json para remover o recurso problemático
|
||||
pulumi stack import --stack <stack-name> < state.json
|
||||
```
|
||||
|
||||
**Ver logs do container após o deploy:**
|
||||
```bash
|
||||
aws logs tail <nome-do-log-group> --follow --region us-east-1
|
||||
```
|
||||
|
||||
**Verificar tasks rodando no ECS:**
|
||||
```bash
|
||||
aws ecs list-tasks --cluster <nome-do-cluster> --region us-east-1
|
||||
```
|
||||
Reference in New Issue
Block a user