Metadata-Version: 2.4
Name: monkai-tester
Version: 0.11.2
Summary: Automated test framework for AtendentePro chatbot agents
Author-email: BeMonkAI <contato@monkai.com.br>
Maintainer-email: BeMonkAI <contato@monkai.com.br>
License: Proprietary
Project-URL: Homepage, https://github.com/BeMonkAI/monkai-tester
Project-URL: Repository, https://github.com/BeMonkAI/monkai-tester
Project-URL: Issues, https://github.com/BeMonkAI/monkai-tester/issues
Project-URL: Changelog, https://github.com/BeMonkAI/monkai-tester/blob/main/CHANGELOG.md
Keywords: ai,agents,testing,chatbot,openai,openai-agents,evaluation,benchmark,kpi,quality-assurance,atendentepro
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Testing
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: openai>=1.30.0
Requires-Dist: openai-agents>=0.0.7
Requires-Dist: pandas>=2.0.0
Requires-Dist: numpy>=1.24.0
Requires-Dist: requests>=2.31.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: tiktoken>=0.5.0
Requires-Dist: PyYAML>=6.0.0
Requires-Dist: typer<1.0.0,>=0.9.0
Requires-Dist: matplotlib>=3.7.0
Requires-Dist: seaborn>=0.13.0
Provides-Extra: local
Requires-Dist: atendentepro>=0.6.24; extra == "local"
Provides-Extra: keyvault
Requires-Dist: monkai-keyvault; extra == "keyvault"
Provides-Extra: server
Requires-Dist: fastapi>=0.109.0; extra == "server"
Requires-Dist: uvicorn[standard]>=0.27.0; extra == "server"
Dynamic: license-file

# MonkAI Tester

Framework de testes automatizados para agentes de chatbot construídos com [AtendentePro](https://pypi.org/project/atendentepro/) e [OpenAI Agents SDK](https://github.com/openai/openai-agents-python).

Valida respostas, tool calls, parâmetros e roteamento entre agentes, gerando relatórios de KPI com métricas de desempenho.

**Usar em outro repositório?** → [Guia: uso em outro repositório](docs/GUIA-USO-EM-OUTRO-REPOSITORIO.md) (instalação, dados, comandos e CI para agentes de IA).

---

## Instalação

```bash
# Uso básico (modo service)
pip install monkai-tester

# Modo local (instanciar agentes com AtendentePro)
pip install "monkai-tester[local]"

# Auto-fetch de secrets do Azure Key Vault (uso interno MonkAI)
pip install "monkai-tester[keyvault]"

# Servidor FastAPI opcional
pip install "monkai-tester[server]"
```

**Extras:**
- `[local]` — instala `atendentepro` para rodar os agentes localmente.
- `[keyvault]` — instala `monkai-keyvault` para buscar `AZURE_OPENAI_*` (e a license do AtendentePro) do Azure Key Vault automaticamente. **Sem este extra**, defina `AZURE_OPENAI_KEY` e `AZURE_OPENAI_ENDPOINT` no ambiente/`.env`. (`monkai-keyvault` é interno e não está no PyPI público.)
- `[server]` — instala FastAPI/uvicorn para o `monkai-tester-server`.

> O pacote no PyPI público é distribuído como **wheels compilados com Cython** (código-fonte protegido), com builds para Linux, macOS e Windows em Python 3.11–3.13.

---

## Ativação (licença)

A biblioteca requer um **token de licença** para uso (igual aos demais produtos MonkAI). Sem ativação, rodar o tester levanta `LicenseNotActivatedError`.

```bash
# via variável de ambiente (auto-ativa no import)
export MONKAI_TESTER_LICENSE_KEY="MTK_..."
```

```python
# ou via código
from monkai_tester import activate
activate("MTK_...")
```

- `monkai-tester --help` funciona sem licença.
- Para obter um token, contato: **contato@monkai.com.br**.
- Uso interno MonkAI: a chave vive no Azure Key Vault (`MONKAI-TESTER-LICENSE-KEY`).

---

## Estrutura do pacote

O pacote `monkai-tester` expõe o comando **`monkai-tester`** (ou `python -m monkai_tester`). A estrutura do código-fonte no repositório é:

```
src/monkai_tester/
  __init__.py
  __main__.py                    # Permite rodar com python -m monkai_tester
  tester_local_service.py        # Orquestrador principal (entry point)
  utils.py                       # Leitura do CSV, execução do test suite, salvamento
  process_interaction.py         # Interação unificada (local via Runner.run / service via HTTP)
  validation_engine.py           # Motor de validação em 5 camadas (+ guardrail)
  match_answer_agent.py          # 7 agentes GPT de comparação semântica
  kpi_generator.py               # Geração de relatório de KPIs em JSON
  prompt_selector_flow.py        # Resolve a network do cliente (entry-point / manifesto) p/ modo local
  print_interactions_message.py  # Formatação de output no terminal
  check_atendentepro_license.py  # Validação da licença AtendentePro
```

Os **casos de teste** ficam em um diretório que você indica com `--data-dir`. Dentro dele, crie uma subpasta por cliente. O CSV de cada cliente pode ter um dos nomes (por ordem de preferência): **`Desempenho_<client>.csv`** (ex.: `Desempenho_acme.csv`), **`Desempenho_agentes_local.csv`**, ou ser o **único arquivo `.csv`** na pasta do cliente.

```
<data-dir>/
  acme/
    Desempenho_agentes_local.csv
  outro_cliente/
    Desempenho_outro_cliente.csv
```

Os **resultados** (CSV e JSON de KPI) são gravados em `--output-dir` (padrão: `./results`).

---

## Como funciona

### Visao geral do fluxo

```
CSV de casos de teste (;-separated)
        |
        v
  utils.load_input_file()         -- le o CSV e retorna TestCaseRow[]
        |
        v
  utils.run_test_suite()          -- itera por grupo de ID, executa cada interacao
        |
        |--- modo "local"  --> process_interaction.process_interaction_local()
        |                        usa Runner.run() do OpenAI Agents SDK
        |                        contra a network do agente real
        |
        |--- modo "service" -> process_interaction.process_interaction_service()
        |                        envia HTTP POST para o endpoint deployado
        |
        v
  validation_engine.validate_test_case()   -- valida cada resposta em 5 camadas
        |
        |--- match_answer_agent3  --> compara parametros (GPT)
        |--- match_answer_agent5  --> compara nomes de tool calls (GPT)
        |--- match_answer_agent6  --> compara sender/agent name (GPT)
        |--- match_answer_agent4  --> compara conteudo semanticamente (GPT)
        |--- match_answer_agent7_guardrail --> compara veredito do guardrail (GPT)
        |
        v
  utils.save_results()            -- salva CSV de resultados
        |
        v
  kpi_generator.generate_comprehensive_report()  --> detailed_kpi_report.json
```

### Modos de execucao

| Modo | Descricao | Requer atendentepro? | Requer endpoint deployado? |
|------|-----------|----------------------|----------------------------|
| `service` | Envia HTTP POST para o endpoint real (Azure Functions) e valida a resposta | Nao | Sim |
| `local` | Instancia a network do agente localmente via `Runner.run()` e valida a resposta | Sim | Nao |
| `both` | Executa primeiro local, depois service | Sim | Sim |
| `auto` | Usa a coluna `Type` do CSV para decidir (`Local` ou `Service`) por grupo | Depende | Depende |

### Validacao em 5 camadas

Cada interacao passa pelas seguintes verificacoes (na ordem):

1. **Parametros** -- os argumentos da tool call correspondem aos esperados? (via `match_answer_agent3`)
2. **Tool calls** -- o nome da ferramenta chamada e o esperado? (via `match_answer_agent5`)
3. **Sender** -- o agente que respondeu e o esperado? (via `match_answer_agent6`)
4. **Conteudo** -- a resposta textual e semanticamente equivalente a esperada? (via `match_answer_agent4`)
5. **Guardrail** -- o comportamento do agente bate com o veredito de guardrail esperado (`decision` / `risk_level` / `category`)? (via `match_answer_agent7_guardrail`)

Cada camada e **pulada automaticamente** quando o valor esperado esta vazio no CSV.

Quando a coluna `Tester AI` esta como `TRUE`, a comparacao e feita por um agente GPT (`gpt-4.1` por padrão; configurável via `AZURE_OPENAI_DEPLOYMENT`). Se `FALSE`, a comparacao e feita por igualdade exata de string (vale para as 4 primeiras camadas; a camada de guardrail sempre roda via GPT por ser semantica).

### Camada de guardrail e os 5 buckets do paper

A camada 5 implementa o framework descrito no paper **["Static vs Specialist Guardrails"](https://monkai.com.br/papers/static-vs-specialist-guardrails.pdf)** da MonkAI Research, que avalia guardrails em 5 buckets:

| Bucket | Tema |
|--------|------|
| **B1** | Ataques (jailbreak, prompt injection, system prompt leak) |
| **B2** | PII / policy violation (CPF, CID, dados medicos, credenciais) |
| **B3** | Off-topic benigno (bitcoin, piadas, programacao, esporte) |
| **B4** | In-scope canonical (greetings, billing, scheduling, knowledge) |
| **B5** | Edge cases (short follow-up, language mix, sarcasm, ambiguo) |

Cada caso de teste pode declarar um **veredito esperado** em 4 colunas opcionais do CSV:

| Coluna | Valores | Default |
|--------|---------|---------|
| `Bucket` | `B1` / `B2` / `B3` / `B4` / `B5` | vazio |
| `Guardrail_Decision` | `allow` / `warn` / `escalate` / `block` | vazio (camada pulada) |
| `Guardrail_Risk_Level` | `none` / `low` / `medium` / `high` / `critical` | `none` |
| `Guardrail_Category` | texto livre (ex: `prompt_injection`, `pii_cpf`) | vazio |

O KPI report agrega resultados **por bucket** (`per_bucket`) e expoe a taxonomia de falhas no campo `failure_dimensions`:

```json
"failure_dimensions": {
  "tool_failure": 3,       // parameters ou tool_calls erradas
  "handoff_failure": 1,    // sender errado (roteamento entre agentes)
  "context_failure": 2,    // resposta divergente do esperado
  "guardrail_failure": 4   // veredito do guardrail inconsistente
}
```

Essa taxonomia e o sinal que o agent team `/atendentepro-iterate` usa para decidir em qual area da config aplicar o fix (tools / handoffs / prompts / politica de guardrail).

### Agentes GPT de comparacao

O arquivo `match_answer_agent.py` contem 7 agentes especializados:

| Agente | Funcao | Uso |
|--------|--------|-----|
| Agent 1 | Comparacao semantica ampla | Disponivel para uso geral |
| Agent 2 | Comparacao semantica estrita | Disponivel para uso geral |
| Agent 3 | Comparacao de parametros | Usado pelo validation_engine (camada 1) |
| Agent 4 | Comparacao de conteudo | Usado pelo validation_engine (camada 4) |
| Agent 5 | Comparacao de tool calls | Usado pelo validation_engine (camada 2) |
| Agent 6 | Comparacao de sender/agent | Usado pelo validation_engine (camada 3) |
| Agent 7 | Comparacao de veredito de guardrail | Usado pelo validation_engine (camada 5) |

Todos usam o deployment Azure OpenAI configurado via `AZURE_OPENAI_DEPLOYMENT` (default: `gpt-4.1`) com `temperature=0` e retornam `True` ou `False`.

---

## Sandbox (valide hipoteses de agente localmente)

O sub-comando `sandbox` roda cenarios curtos contra a rede real do cliente **sem fazer deploy para dev**. Use para validar fixes em ~30s / ~$0.02 por run.

### Setup (uma vez)

```bash
# Variaveis obrigatorias (puxando do Key Vault)
export AZURE_OPENAI_KEY=$(az keyvault secret show --vault-name key-vault-monkai --name AZURE-OPENAI-KEY --query value -o tsv)
export AZURE_OPENAI_ENDPOINT=$(az keyvault secret show --vault-name key-vault-monkai --name AZURE-OPENAI-ENDPOINT --query value -o tsv)
export ATENDENTEPRO_LICENSE_KEY=$(az keyvault secret show --vault-name key-vault-monkai --name ATENDENTEPRO-LICENSE-KEY --query value -o tsv)
export ATENDENTEPRO_LICENSE_SECRET=$(az keyvault secret show --vault-name key-vault-monkai --name ATENDENTEPRO-LICENSE-SECRET --query value -o tsv)

# Instalar atendentepro no venv do monkai-tester
pip install atendentepro
```

### Resolução da network (client-agnostic)

O sandbox **não conhece nenhum cliente**. A network de cada cliente é resolvida por um de dois mecanismos genéricos, que retornam uma `AgentNetwork` fresca:

| Mecanismo | Como expor no repo do cliente |
|-----------|-------------------------------|
| **Entry point** `monkai_tester.sandbox_networks` | `<client> = "<pkg>.module:load_network"` no `pyproject.toml` |
| **Manifesto** `monkai_tester.networks.toml` (na raiz passada por `--monkai-services-root`) | tabela `[sandbox_networks]` com `<client> = "dotted.module[:callable]"` |

```toml
# <services-root>/monkai_tester.networks.toml
[sandbox_networks]
acme = "acme_pkg.sandbox:load_network"
```

### Onde vivem os scenarios

Os scenarios de cada cliente vivem no **repositorio do cliente** (nao aqui). Organize como preferir, por exemplo:

```
<repo-do-cliente>/.../<client>/sandbox/
├── scenarios/        # *.yaml scenarios
├── prompts/          # *.md prompt variants
└── reports/          # *.md run evidence
```

### Rodar um scenario

```bash
cd /path/to/repo-do-cliente
monkai-tester-cli sandbox run .../sandbox/scenarios/group-62.yaml --monkai-services-root .
```

(`--monkai-services-root` aponta para a raiz com o `monkai_tester.networks.toml`; pode também vir de `MONKAI_SERVICES_ROOT`. Dispensável se o cliente registra via entry point.)

Esperado: tabela com sender match por turn + relatorio markdown em `../reports/`.

### Listar transformacoes disponiveis

```bash
monkai-tester-cli sandbox list-variants
```

### Validar um cenario sem rodar

```bash
monkai-tester-cli sandbox validate <path-to-scenario.yaml>
```


### Resposta da API em modo service

Para que a validação de **Tool_calls** e **Parameters** do CSV funcione em modo service, o endpoint pode retornar na resposta JSON os campos opcionais `tool_calls` e `tool_parameters`:

- **Onde:** se a resposta tiver `agent_response` (formato Teams-style), o tester lê `agent_response.tool_calls` e `agent_response.tool_parameters`. Caso contrário, lê `tool_calls` e `tool_parameters` no objeto raiz da resposta.
- **tool_calls:** lista de nomes de ferramentas (strings), ex.: `["minha_ferramenta"]`, ou lista de objetos com `"name"` e opcionalmente `"arguments"` (string JSON), ex.: `[{"name": "minha_ferramenta", "arguments": "{\"x\": 1}"}]`.
- **tool_parameters (opcional):** objeto com parâmetros agregados. Se não for enviado, os parâmetros podem ser derivados dos `arguments` dos objetos em `tool_calls` quando existirem.

Respostas sem esses campos continuam válidas; a validação de Tool_calls/Parameters só é feita quando o tester conseguir extrair `tool_calls` da resposta.

---

## Como rodar localmente

### Pre-requisitos

1. **Python 3.11+**
2. **Azure Key Vault (obrigatório para as chaves Azure OpenAI):** o tester busca **do Key Vault** (via `monkai-keyvault`) as chaves que ele usa e que existem no vault. Para essas chaves, o **Key Vault tem prioridade** sobre `.env`/ambiente.

   - **Obrigatórias via Key Vault (Azure OpenAI):** `AZURE_OPENAI_KEY`, `AZURE_OPENAI_ENDPOINT`
   - **Também buscada do Key Vault (se existir):** `ATENDENTEPRO_LICENSE_KEY`

   O restante pode ficar em `.env`/ambiente (por exemplo: `AZURE_OPENAI_API_VERSION`, `AZURE_OPENAI_DEPLOYMENT`, `ATENDENTEPRO_LICENSE_SECRET`, e as URLs de endpoint por cliente como `${<CLIENT>_API_URL}`, etc.).

3. **Instalar o pacote** (veja [Instalação](#instalação) acima). Para modo local use `pip install monkai-tester[local]`.

### Testar contra o endpoint em producao

O modo **service** envia cada mensagem do CSV para o endpoint HTTP (Azure Function). Para usar o **endpoint de producao**:

1. **Configure no Azure Key Vault** pelo menos `AZURE_OPENAI_KEY` e `AZURE_OPENAI_ENDPOINT`. As URLs do modo service (`${<CLIENT>_API_URL}`) podem estar no `.env` se você preferir.

2. **Execute em modo service** (o placeholder `${<CLIENT>_API_URL}` do CSV é substituído automaticamente pelo valor da variável). É obrigatório informar o diretório dos dados com `--data-dir`:

```bash
monkai-tester --mode service --client acme --data-dir /caminho/para/data
```

Ou com o módulo Python:

```bash
python -m monkai_tester --mode service --client acme --data-dir /caminho/para/data
```

Para qualquer cliente, configure a URL de endpoint correspondente (`<CLIENT>_API_URL`) no Azure Key Vault ou no `.env` e use `--client <client>`. Não é necessário editar o CSV; os placeholders são resolvidos com os valores carregados do Key Vault/ambiente.

### Comandos

O argumento **`--data-dir`** é obrigatório: deve ser o diretório base que contém uma subpasta por cliente (ex.: `acme/`), cada uma com um CSV (convenção: `Desempenho_<client>.csv` ou `Desempenho_agentes_local.csv`, ou um único `.csv` na pasta).

#### Testar todos os clientes em modo service

```bash
monkai-tester --mode service --client all --data-dir /caminho/para/data
```

#### Testar apenas um cliente em modo service

```bash
monkai-tester --mode service --client acme --data-dir /caminho/para/data
```

#### Testar em modo local (instancia os agentes diretamente)

Requer `monkai-tester[local]` e, em geral, `--monkai-services-root` apontando para o repositório que contém as networks dos agentes:

```bash
monkai-tester --mode local --client acme --data-dir /caminho/para/data --monkai-services-root /caminho/para/monkai_services
```

#### Testar ambos os modos (local + service)

```bash
monkai-tester --mode both --client all --data-dir /caminho/para/data --monkai-services-root /caminho/para/monkai_services
```

#### Usar um CSV customizado

```bash
monkai-tester --mode service --csv /caminho/para/meus_testes.csv --client acme --data-dir /caminho/para/data
```

#### Salvar resultados em diretório específico

```bash
monkai-tester --mode service --client all --data-dir /caminho/para/data --output-dir ./meus_resultados
```

#### Ajustar timeout e desabilitar TLS

```bash
monkai-tester --mode service --client all --data-dir /caminho/para/data --timeout 90 --skip-tls-verify
```

#### Limitar número de grupos de teste

```bash
monkai-tester --mode service --client all --data-dir /caminho/para/data --limit 5
```

#### Benchmark de múltiplos modelos

Roda a suite completa para cada modelo listado. Em service mode, passa `model_override` no payload HTTP:

```bash
monkai-tester --mode service --client acme --data-dir /caminho/para/data --models gpt-4.1 gpt-4o-mini gpt-5-mini
```

Cada modelo gera seu próprio CSV e JSON de KPI, nomeados com o label do modelo.

#### Isolar sessões com session-prefix

Prefixa campos de identificação do usuário no payload para evitar contaminação de sessão entre testes paralelos. Quais campos são prefixados é configurável via `--session-prefix-fields` (dot-notation):

```bash
# Teams-style (default: from.email, from.aadObjectId, from.id)
monkai-tester --mode service --client acme --data-dir /caminho/para/data --session-prefix bench-4o

# WhatsApp-style (prefixar sender_phone)
monkai-tester --mode service --client acme --data-dir /caminho/para/data --session-prefix bench-4o --session-prefix-fields data.sender_phone

# Generic webhook (prefixar user.email e user.id)
monkai-tester --mode service --client acme --data-dir /caminho/para/data --session-prefix bench-4o --session-prefix-fields user.email user.id

# Automático em benchmark mode (--models): cada modelo recebe prefix baseado no nome
monkai-tester --mode service --client acme --data-dir /caminho/para/data --models gpt-4o gpt-4.1
# gpt-4o → prefix "gpt4o", gpt-4.1 → prefix "gpt41"
```

### Todos os argumentos CLI

| Argumento | Valores | Default | Descrição |
|-----------|---------|---------|-----------|
| `--mode` | `local`, `service`, `both`, `auto` | `service` | Modo de execução |
| `--client` | nome do cliente (subpasta em `--data-dir`) ou `all` | `all` | Cliente a testar |
| `--data-dir` | caminho para diretório | **(obrigatório)** | Diretório base com subpastas por cliente e CSVs |
| `--csv` | caminho para arquivo | (auto por cliente) | CSV específico (sobrescreve lookup por cliente) |
| `--output-dir` | caminho para diretório | `./results` | Diretório para resultados e relatório KPI |
| `--timeout` | float (segundos) | `60.0` | Timeout HTTP para modo service |
| `--skip-tls-verify` | flag | `false` | Desabilita verificação TLS |
| `--limit` | inteiro | (nenhum) | Executa apenas os primeiros N grupos de teste (por ID) |
| `--monkai-services-root` | caminho | (nenhum) | Caminho para monkai_services (recomendado para modo local) |
| `--models` | lista de specs | (nenhum) | Benchmark: roda a suite completa para cada modelo. Formato: `model[:provider[:base_url:api_key]]`. Em service mode, passa `model_override` no payload HTTP. Em local mode, reconfigura o AI provider. Ex.: `--models gpt-4.1 gpt-4o-mini` |
| `--session-prefix` | string | (nenhum) | Prefixa campos de identidade no payload para isolar sessões. Campos configuráveis via `--session-prefix-fields`. Em benchmark mode (`--models`), auto-gera prefix por modelo. Ex.: `--session-prefix bench-4o` |
| `--session-prefix-fields` | lista de paths | `from.email from.aadObjectId from.id` | Paths dot-notation dos campos a prefixar. Ex.: `--session-prefix-fields data.sender_phone` (WhatsApp-style) ou `user.email user.id` (generic webhook) |
| `--service-client-id` | string | (nenhum) | Em modo service, anexa `?client_id=<id>` à API URL (exigido por adapters Teams-style que roteiam por client_id) |

### Exit code

O processo retorna `0` se todos os testes passaram e `1` se algum falhou. Isso permite uso em CI/CD para falhar o pipeline automaticamente.

---

## Formato do CSV de casos de teste

O CSV usa separador `;`. Cada linha representa uma interacao de uma conversa. Linhas com o mesmo `ID` compoem uma sequencia de conversa (multi-turn).

| Coluna | Descricao |
|--------|-----------|
| `Test` | `TRUE`/`FALSE` -- se a linha deve ser executada |
| `Tester AI` | `TRUE`/`FALSE` -- se a validacao usa agente GPT (semantica) ou comparacao exata |
| `Type` | `Local` ou `Service` -- usado quando `--mode auto` |
| `API_URL` | URL do endpoint para modo service |
| `HEADERS` | Cabecalhos HTTP (dict Python como string) |
| `DATA_TEMPLATE` | Template JSON do corpo da requisição; `User_message` é injetado em `attachments[0].content` (Teams-style), `data.text` (WhatsApp-style) ou `message` (generic webhook), por ordem de precedência |
| `Repeat` | Numero de repeticoes do teste |
| `ID` | Identificador do grupo de conversa (linhas com mesmo ID = mesma conversa) |
| `Label` | Rotulo livre para identificar o teste |
| `First_message` | Primeira mensagem da conversa (ex.: token de reset de sessao) |
| `User_message` | Mensagem do usuario para aquela interacao |
| `Agent_From` | Agente de origem esperado |
| `Agent_to` | Agente de destino esperado (usado na validacao de sender) |
| `Tool_calls` | Nome da ferramenta esperada |
| `Parameters` | Parametros esperados na chamada da ferramenta |
| `Interaction` | Numero sequencial da interacao dentro do grupo |
| `AI_message_official` | Resposta esperada do agente (usada na validacao de conteudo) |
| `Agent_File` | Arquivo `.py` do agente (usado pela UI, opcional) |
| `Agent_Name` | Nome do objeto do agente (usado pela UI, opcional) |

### Placeholders no CSV

Qualquer valor no CSV (API_URL, DATA_TEMPLATE, HEADERS, etc.) pode usar placeholders no formato `${NOME_VAR}`. Eles são substituídos em tempo de execução pela variável de ambiente de mesmo nome (`os.environ.get("NOME_VAR", "")`). Exemplo: `${ACME_API_URL}`. Nenhum cliente precisa de código específico: defina a variável no ambiente ou no Key Vault e use no CSV (ex.: `API_URL = "${ACME_API_URL}"`).

---

## Saidas geradas

Após a execução, o diretório de output (default `./results` ou o valor de `--output-dir`) conterá:

### 1. CSV de resultados

Arquivo: `results_{client}_{mode}_{timestamp}.csv`

Contem uma linha por interacao testada com todas as colunas de entrada e validacao:

| Coluna | Descricao |
|--------|-----------|
| `test_id` | ID do grupo |
| `label` | Rotulo do teste |
| `interaction` | Numero da interacao |
| `user_message` | Mensagem enviada |
| `expected_message` | Resposta esperada |
| `actual_message` | Resposta real do agente |
| `expected_sender` / `actual_sender` | Agentes esperado e real |
| `expected_tool_calls` / `actual_tool_calls` | Tool calls esperadas e reais |
| `parameters_match` | `True` / `False` / `None` |
| `tool_calls_match` | `True` / `False` / `None` |
| `sender_match` | `True` / `False` / `None` |
| `content_match` | `True` / `False` / `None` |
| `overall_pass` | `True` se todas as camadas passaram |
| `errors` | Descricao dos erros encontrados |
| `latency_seconds` | Tempo de resposta |
| `input_tokens` / `output_tokens` | Uso de tokens |

### 2. Relatorio de KPI

Arquivo: `detailed_kpi_report_{client}_{mode}_{timestamp}.json`

Exemplo de conteudo:

```json
{
  "generated_at": "2026-03-04T14:30:00Z",
  "overall": {
    "total_interactions": 7,
    "passed": 5,
    "failed": 2,
    "pass_rate": 0.7143,
    "fail_rate": 0.2857
  },
  "error_distribution": {
    "content_failures": 1,
    "sender_failures": 1,
    "tool_call_failures": 0,
    "parameter_failures": 0
  },
  "latency": {
    "mean_seconds": 3.45,
    "median_seconds": 2.80,
    "p95_seconds": 7.12,
    "max_seconds": 8.50,
    "min_seconds": 0.95
  },
  "token_usage": {
    "total_input_tokens": 12500,
    "avg_input_tokens": 1785.7,
    "total_output_tokens": 3200,
    "avg_output_tokens": 457.1
  },
  "per_test_group": [...],
  "per_agent": [...]
}
```

---

## Publicação (CI/CD)

O pacote é publicado em **dois canais**, ambos disparados em push para `main` quando a versão em `pyproject.toml` muda:

| Canal | Workflow | Formato | Uso |
|-------|----------|---------|-----|
| **PyPI público** | `.github/workflows/publish.yml` | Wheels **Cython-compilados** (Linux/macOS/Windows × Python 3.11–3.13), código-fonte protegido | `pip install monkai-tester` para qualquer consumidor |
| **Azure Artifacts** | `.github/workflows/publish_azure.yml` | `py3-none-any` pure-Python (`MONKAI_TESTER_SKIP_CYTHON=1`) | Consumo interno rápido (Railway, subagents `/atendentepro-iterate`) |

`.github/workflows/ci-deploy-ready.yml` roda os checks (ruff + pytest) antes de publicar, em modo pure-Python.

> **macOS arm64**: o Cython não cross-compila no CI. Esses wheels são buildados localmente e enviados ao PyPI manualmente (ver "Release Process" no `CLAUDE.md`). Cada wheel passa por `scripts/verify_wheel.py` (garante zero vazamento de `.py`-fonte).

### Secrets necessários no GitHub

| Secret | Para | Descrição |
|--------|------|-----------|
| `PYPI_API_TOKEN` | PyPI público | API token com escopo no projeto `monkai-tester` |
| `AZURE_ARTIFACTS_PAT` | Azure Artifacts | PAT com permissão Packaging (Read & Write) |
| `AZURE_ORG` | Azure Artifacts | Organização no Azure DevOps |
| `AZURE_PROJECT` | Azure Artifacts | Projeto (ex.: MonkAI) |
| `AZURE_FEED` | Azure Artifacts | Feed (ex.: strava_feed) |

Para **rodar os testes** (modo service ou local) em outro repositório ou em CI, use os mesmos secrets e variáveis descritos na seção [Pré-requisitos](#pre-requisitos) (AZURE_OPENAI_KEY, AZURE_OPENAI_ENDPOINT, as URLs de endpoint por cliente `${<CLIENT>_API_URL}`, ATENDENTEPRO_*, etc.).

---

## Clientes e resolução de network

O monkai-tester é uma **biblioteca genérica** e não carrega o nome de nenhum cliente. Em **modo service** basta uma subpasta por cliente em `--data-dir` com o CSV e a URL do endpoint resolvida por placeholder (`${<CLIENT>_API_URL}`).

Em **modo local** (instanciar a network via `Runner.run()`), o tester resolve a network do cliente por um de dois mecanismos genéricos — a fiação vive sempre **no repositório do cliente**, nunca aqui:

| Mecanismo | Quando usar | Como expor no repo do cliente |
|-----------|-------------|-------------------------------|
| **Entry point** `monkai_tester.networks` | Cliente é um pacote pip instalável | Registrar no `pyproject.toml`: `[project.entry-points."monkai_tester.networks"]` → `<client> = "<pkg>.tester_plugin:load_network"` |
| **Manifesto** | Cliente vive em disco (ex.: app de Azure Functions), acessado via `--monkai-services-root` | Criar um `monkai_tester.networks.toml` na raiz, com uma tabela `[networks]` mapeando `<client> = "dotted.module[:callable]"` (callable default `load_network`) |

O **manifesto** mantém o tester desacoplado do layout do consumidor: o caminho do módulo (ex.: `client_resources.<client>.tester_network:load_network`) vive no manifesto **do cliente**, nunca na biblioteca. Exemplo:

```toml
# <services-root>/monkai_tester.networks.toml
[networks]
acme = "acme_pkg.networks.acme:load_network"
```

Em ambos os casos, o loader não recebe argumentos e retorna `(starting_agent, context)`. A configuração do Azure OpenAI e a ativação do AtendentePro são feitas pelo tester **antes** do loader rodar.

---

## Como adicionar novos casos de teste

1. Abra o CSV correspondente no seu `--data-dir`, em `{client}/` (nome: `Desempenho_{client}.csv` ou `Desempenho_agentes_local.csv`, ex.: `data/acme/Desempenho_agentes_local.csv` ou `data/acme/Desempenho_acme.csv`).
2. Adicione novas linhas seguindo o formato descrito acima.
3. Para conversas multi-turn, use o mesmo `ID` e incremente a coluna `Interaction`.
4. Defina `First_message` apenas na primeira interação (normalmente o token de reset como `Monkai12345`).
5. Preencha as colunas de validação (`Agent_to`, `Tool_calls`, `Parameters`, `AI_message_official`) com os valores esperados.
6. Colunas de validação vazias são ignoradas (a camada correspondente não é executada).

### Exemplo: teste de conversa multi-turn

```
ID=10; Interaction=1; First_message=Monkai12345; User_message=Qual IVA de Energia?
ID=10; Interaction=2; User_message=Consumo; Agent_to=Interview Agent
ID=10; Interaction=3; User_message=ICMS; Agent_to=Answer Agent; AI_message_official=O codigo e E4
```

---

## Troubleshooting

### "ATENDENTEPRO - TOKEN INVÁLIDO" (modo local)

A licença do AtendentePro não foi aceita. Para validar a chave sem rodar os testes:

```bash
python -m monkai_tester.check_atendentepro_license
```

O script lê `ATENDENTEPRO_LICENSE_KEY` e `ATENDENTEPRO_LICENSE_SECRET` (do Key Vault ou do ambiente, conforme `--source`) e informa se a licença é válida. A partir da v0.6.20+ do atendentepro o SECRET é obrigatório. Configure-as no Azure Key Vault ou entre em contato com a MonkAI (contato@monkai.com.br) para obter um token válido.

### "CSV not found"

Os CSVs devem estar no diretório indicado por `--data-dir`, em subpastas por cliente. Convenção de nome: `Desempenho_<client>.csv` ou `Desempenho_agentes_local.csv`, ou um único `.csv` na pasta do cliente. Verifique se `--data-dir` está correto ou use `--csv` para apontar para um arquivo específico.

### "No starting_agent for local mode"

O modo local precisa instanciar os agentes. Verifique que `atendentepro` está instalado e que `ATENDENTEPRO_LICENSE_KEY` e `ATENDENTEPRO_LICENSE_SECRET` estão configurados no Azure Key Vault.

### "GPT call failed" na validacao

A validação semântica usa Azure OpenAI (deployment configurado em `AZURE_OPENAI_DEPLOYMENT`). Verifique que `AZURE_OPENAI_KEY` e `AZURE_OPENAI_ENDPOINT` estão definidos e que o recurso tem o deployment correto.

### Timeout em modo service

Aumente o timeout com `--timeout 120` ou verifique se o endpoint esta acessivel. Use `--skip-tls-verify` se houver problemas de certificado.

### Placeholders `${VAR}` no CSV

Os CSVs podem conter placeholders no formato `${NOME_VAR}` (ex.: `${ACME_API_URL}`). Eles são substituídos pelos valores das variáveis de ambiente de mesmo nome ao carregar o CSV. Configure as variáveis no ambiente ou no Key Vault; variável não definida é substituída por string vazia.
