Files
agent_framework_llm_as_a_judge/README.md

1562 lines
34 KiB
Markdown
Executable File

# Agent Framework OCI Evaluator — LLM-as-a-Judge
**Idioma:** Português | [English](README.en-US.md)
## 1. Visão geral
O **Agent Framework OCI Evaluator** é o componente batch responsável por avaliar, de forma assíncrona, a qualidade das conversas produzidas pelos agentes construídos sobre o Agent Framework OCI.
O serviço lê sessões e traces do **Langfuse v3**, seleciona uma amostra determinística de sessões, calcula métricas de qualidade com um **LLM-as-a-Judge**, persiste resultados em **Oracle Autonomous Database**, gera o arquivo oficial `.TXT.GZ`, publica esse arquivo em **Google Cloud Storage (GCS)** e registra a execução do próprio job no Langfuse.
Ele foi desenhado para execução **D+1**, como um **Kubernetes CronJob**, sem endpoint HTTP/gRPC de runtime.
> **Modelo de implantação recomendado:** uma instância do evaluator por agente/squad. O código é genérico e suporta múltiplos agentes na configuração, mas em produção a recomendação é cada deployment carregar apenas o agente de sua responsabilidade. Isso mantém credenciais, ownership, scheduling e troubleshooting isolados.
---
## 2. Por que este componente existe
Avaliar respostas generativas em tempo real dentro do fluxo síncrono do agente aumenta latência, custo e acoplamento. O evaluator separa essa responsabilidade do atendimento online.
O desenho é:
```text
Atendimento online
|
v
Agent Framework
|
+--------------------> Langfuse v3
|
| traces / sessions
v
Evaluator D+1 (CronJob)
|
+------------------+------------------+
| | |
v v v
OCI GenAI ADB GCS
LLM-as-a-Judge estado/resultados .TXT.GZ oficial
|
+-----------------------> Langfuse
auditoria / scores opcionais
```
Essa separação traz alguns benefícios:
- não adiciona latência ao atendimento;
- permite reprocessamento e auditoria;
- permite amostragem configurável por agente;
- isola falhas de avaliação do agente online;
- permite utilizar modelos dedicados do OCI GenAI;
- mantém um artefato oficial para a camada analítica da TIM;
- evita acoplamento do código a nomes específicos de agentes, buckets ou credenciais.
---
## 3. Conceitos principais
### 3.1. Trace
Um **trace** representa uma interação avaliada individualmente. Para cada trace são calculadas métricas trace-level:
- `judgeScore` — qualidade global da resposta;
- `accuracyScore` — aderência/factualidade em relação ao contexto;
- `alucinationScore` — grau de afirmações não suportadas pelo contexto;
- `promptLength` — tamanho bruto, em caracteres, do `trace.input`.
### 3.2. Session
Uma **session** representa a conversa completa. Uma sessão normalmente possui vários traces.
As métricas session-level são calculadas uma vez e replicadas nas linhas dos traces daquela sessão:
- `inferredCsiScore`;
- `resolution`;
- `conversationPrecision`;
- `loop`.
### 3.3. Amostragem por sessão
A amostragem é feita sobre **sessions**, nunca sobre traces isolados.
A seed é determinística:
```text
agentId:reference_date
```
Exemplo:
```text
billing:2026-08-13
```
Com isso, duas execuções para o mesmo agente, data e percentual selecionam as mesmas sessões.
Depois que uma sessão é selecionada, **todos os traces daquela sessão** são mantidos.
### 3.4. Janela BRT
`run-agents` calcula a data de referência com base em:
```text
D - daysBack
```
usando explicitamente:
```text
America/Sao_Paulo
```
Com `daysBack: 1`, uma execução em 14/08 avalia o dia 13/08 completo, de 00:00:00 até 23:59:59.999999 BRT.
### 3.5. CONTEXT_TURN_LIMIT
`CONTEXT_TURN_LIMIT` controla quantos turnos anteriores são enviados ao judge trace-level.
Default:
```env
CONTEXT_TURN_LIMIT=6
```
Ele existe porque uma resposta não deve ser julgada fora do contexto da conversa. Aumentar o valor fornece mais contexto, mas também aumenta tokens, custo e latência do judge.
### 3.6. Loop
A métrica `loop` não mede repetição textual simples. Ela representa o maior **streak consecutivo de `metadata.intent`** dentro da sessão.
Exemplo:
```text
billing
billing
billing
payment
payment
```
Resultado:
```text
loop = 3
```
---
## 4. Fluxo completo de execução
O fluxo produtivo é executado pelo comando:
```bash
python -m evaluator.cli run-agents
```
ou pelo entrypoint instalado:
```bash
af-evaluator run-agents
```
Etapas:
```text
1. Carrega agents.yaml
2. Calcula D-daysBack em BRT
3. Resolve credenciais Langfuse do agente
4. Busca TODAS as sessions nativas do período, com paginação
5. Busca TODOS os traces do período, com paginação
6. Busca observations em bulk/paginado
7. Recupera session_id real via metadata quando o trace legado não possui sessionId nativo
8. Filtra traces do agente/aliases
9. Faz sampling determinístico das sessions lógicas
10. Mantém todos os traces das sessions selecionadas
11. Persiste itens no ADB
12. Executa judge trace-level
13. Executa judge session-level
14. Calcula loop local
15. Gera .TXT.GZ
16. Faz upload para GCS
17. Remove o arquivo local após sucesso
18. Atualiza auditoria judge-job-{agentId} no Langfuse
```
### 4.1. Por que a coleta não usa N+1
A implementação evita o padrão antigo:
```text
1 trace
-> GET trace detail
-> GET observations
```
repetido para cada trace.
Agora a coleta é paginada em lote:
```text
/api/public/sessions page 1..N
/api/public/traces page 1..N
/api/public/observations page 1..N
```
Assim, 10.000 traces não produzem ~20.000 chamadas adicionais. O número de requests cresce com o número de páginas da API.
O `PAGE_SIZE` atual é 100.
### 4.2. Compatibilidade com traces sem `sessionId` nativo
Algumas instrumentações históricas registram a sessão no metadata do trace/observation, mas não promovem o valor para o campo nativo `trace.sessionId` do Langfuse. O evaluator recupera a sessão real com a seguinte precedência:
```text
trace.sessionId / trace.session_id
-> trace.metadata.session_id / sessionId / conversation_key
-> observation.metadata.session_id / sessionId / conversation_key
-> business_context.session_key (último fallback de metadata)
```
O `trace.id` **nunca** é usado como `session_id`. Assim, o evaluator preserva sampling e métricas de session mesmo para traces históricos, sem transformar cada trace em uma sessão artificial. As estatísticas de coleta expõem `native_sessions_read`, `metadata_sessions_recovered` e `traces_without_session`.
---
## 5. Arquitetura de componentes
```text
agent_framework_oci_evaluator/
├── evaluator/
│ ├── analytics/
│ │ └── vloop.py
│ ├── collectors/
│ │ ├── langfuse.py
│ │ ├── langfuse_credentials.py
│ │ ├── agent_framework.py
│ │ └── mock.py
│ ├── config/
│ │ ├── agents.py
│ │ └── settings.py
│ ├── core/
│ │ └── models.py
│ ├── identity/
│ │ └── resolver.py
│ ├── judges/
│ │ └── llm_judge.py
│ ├── llm/
│ │ ├── client.py
│ │ └── profile_resolver.py
│ ├── output/
│ │ └── legacy_exporter.py
│ ├── persistence/
│ │ ├── oracle_store.py
│ │ └── repository.py
│ ├── prompts/
│ │ └── loader.py
│ ├── publishers/
│ │ ├── gcs_export.py
│ │ ├── langfuse_job_audit.py
│ │ └── langfuse_scores.py
│ ├── cli.py
│ ├── engine.py
│ └── time_utils.py
├── configs/
│ ├── identity.yaml
│ ├── judge/
│ │ ├── agents.yaml
│ │ ├── storage.yaml
│ │ ├── trace_metrics.yaml
│ │ └── session_metrics.yaml
│ ├── langfuse/
│ │ └── langfuse_credentials.example.yaml
│ └── llm_profiles/
│ └── llm_profiles.yaml
├── deploy/
├── tests/
├── .env.example
├── Dockerfile
└── pyproject.toml
```
### 5.1. Collectors
Transformam fontes externas em `ConversationRecord`.
Fontes disponíveis:
```text
langfuse
agent_framework
mock
```
`langfuse` é a fonte produtiva padrão.
### 5.2. EvaluationEngine
`evaluator/engine.py` coordena o pipeline:
- criação do run;
- coleta;
- amostragem;
- persistência;
- judge;
- export;
- upload;
- auditoria.
### 5.3. Persistence
Os estados e resultados são persistidos em Oracle Database. O schema é criado automaticamente pelo comando `init-db`.
As principais tabelas são:
```text
EVALUATION_RUN
EVALUATION_PROGRESS_EVENT
EVALUATION_ITEM
EVALUATION_RESULT
EVALUATION_METRIC
EVALUATION_FINDING
```
O prefixo é configurável por:
```env
ADB_TABLE_PREFIX=AGENTFW
```
### 5.4. Publishers
- `gcs_export.py` — upload do artefato oficial;
- `langfuse_job_audit.py` — auditoria `judge-job-{agentId}`;
- `langfuse_scores.py` — publicação opcional de scores de trace.
> **Nota:** a auditoria produtiva do job usa as credenciais dinâmicas do agente. A publicação opcional de scores (`PUBLISH_LANGFUSE_SCORES=true`) ainda utiliza as credenciais globais `LANGFUSE_HOST/PUBLIC_KEY/SECRET_KEY`; deixe-a desabilitada quando não houver um projeto global apropriado.
---
## 6. Configuração de agentes
Arquivo padrão:
```text
configs/judge/agents.yaml
```
Exemplo:
```yaml
agents:
- agentId: billing
enabled: true
daysBack: 1
percentage: 1.0
langfuse_agent_aliases:
- telecom_contas
gcs_prefix: agnt_ai_contas
```
Campos:
| Campo | Descrição |
|---|---|
| `agentId` | identidade lógica oficial do agente |
| `enabled` | habilita/desabilita o agente naquele deployment |
| `daysBack` | quantos dias voltar a partir do dia atual BRT |
| `percentage` | percentual de sessions avaliadas, de 0.0 a 1.0 |
| `langfuse_agent_aliases` | nomes técnicos aceitos para o agente nos traces; quando preenchido, funciona como filtro explícito independentemente da origem das credenciais Langfuse |
| `gcs_prefix` | prefixo do agente dentro do bucket |
Também são aceitos `agent_id` e `days_back` por compatibilidade, mas o formato recomendado é `agentId` / `daysBack`.
**Semântica de `langfuse_agent_aliases`:** quando a lista possui valores, somente traces cujo identificador de agente corresponda a um dos aliases são elegíveis para sampling e avaliação. Quando a lista está vazia, o evaluator não aplica filtro por identidade de agente e considera o projeto/endpoint Langfuse configurado exclusivo para aquela entrada.
### 6.1. Um evaluator por agente
Para o modelo recomendado de uma instância por agente, mantenha apenas uma entrada habilitada naquele deployment:
```yaml
agents:
- agentId: billing
enabled: true
daysBack: 1
percentage: 1.0
langfuse_agent_aliases:
- telecom_contas
- billing_agent
gcs_prefix: agnt_ai_contas
```
O mesmo código pode ser usado em outro repositório/deployment apenas trocando os arquivos de configuração.
---
## 7. Credenciais Langfuse dinâmicas
Não existem variáveis hardcoded do tipo:
```text
LANGFUSE_BILLING_*
LANGFUSE_SALES_*
```
O evaluator lê uma definição declarativa:
```env
LANGFUSE_CREDENTIALS_CONFIG_PATH=configs/langfuse/langfuse_credentials.yaml
```
Crie o arquivo a partir do exemplo:
```bash
cp configs/langfuse/langfuse_credentials.example.yaml \
configs/langfuse/langfuse_credentials.yaml
```
Exemplo:
```yaml
langfuse:
agents:
- agentId: billing
host: https://langfuse.example.internal
publicKey: pk-...
secretKey: sk-...
- agentId: outro-agente
host: https://outro-langfuse.internal
publicKey: pk-...
secretKey: sk-...
```
O resolver percorre todas as entradas. Adicionar um agente não exige alteração Python.
### 7.1. Referências a env ou arquivos
Para não manter secrets no Git:
```yaml
langfuse:
agents:
- agentId: billing
hostEnv: BILLING_LF_HOST
publicKeyEnv: BILLING_LF_PUBLIC
secretKeyFile: /var/secrets/langfuse/billing-secret-key
```
Campos suportados:
```text
host / hostEnv / hostFile
publicKey / publicKeyEnv / publicKeyFile
secretKey / secretKeyEnv / secretKeyFile
```
Ordem de resolução de cada campo:
```text
valor direto -> variável de ambiente declarada -> arquivo declarado
```
### 7.2. Credenciais globais
Estas variáveis continuam disponíveis apenas para o comando manual genérico `run`:
```env
LANGFUSE_HOST=http://localhost:3005
LANGFUSE_PUBLIC_KEY=...
LANGFUSE_SECRET_KEY=...
```
O fluxo produtivo `run-agents` resolve credenciais pelo arquivo declarativo por agente.
---
## 8. Configuração do LLM
Providers aceitos:
```text
mock
oci_openai
oci_sdk
openai_compatible
```
A recomendação para os modelos OCI dedicados é:
```env
LLM_PROVIDER=oci_sdk
```
### 8.1. OCI SDK
Exemplo local com `~/.oci/config`:
```env
LLM_PROVIDER=oci_sdk
OCI_AUTH_MODE=config_file
OCI_CONFIG_FILE=~/.oci/config
OCI_PROFILE=DEFAULT
OCI_REGION=sa-saopaulo-1
OCI_COMPARTMENT_ID=ocid1.compartment...
OCI_GENAI_BASE_URL=https://inference.generativeai.sa-saopaulo-1.oci.oraclecloud.com
OCI_GENAI_MODEL=meta.llama-3.3-70b-instruct
```
Modos de autenticação suportados pelo código:
```text
config_file
instance_principal
resource_principal
oke_workload_identity
workload_identity
```
### 8.2. Dedicated endpoint
Para endpoint dedicado:
```env
OCI_GENAI_BASE_URL=https://<private-endpoint>.inference.generativeai.<region>.oci.oraclecloud.com
OCI_GENAI_ENDPOINT_ID=ocid1.generativeaiendpoint...
```
Também é aceito o OCID do dedicated endpoint em `OCI_GENAI_MODEL`.
O provider seleciona `DedicatedServingMode` quando há endpoint OCID; caso contrário usa `OnDemandServingMode`.
### 8.3. Perfis do judge
Arquivo:
```text
configs/llm_profiles/llm_profiles.yaml
```
Exemplo:
```yaml
profiles:
judge:
temperature: 0
max_tokens: 900
```
Selecionado por:
```env
LLM_PROFILE=judge
```
---
## 9. Prompts e métricas
### 9.1. Trace metrics
Arquivo:
```text
configs/judge/trace_metrics.yaml
```
O judge deve retornar JSON com:
```json
{
"judgeScore": 0.0,
"accuracyScore": 0.0,
"alucinationScore": 0.0,
"rationale": "..."
}
```
Se o parse falhar:
```text
1ª tentativa
-> parse falha
2ª tentativa com retry-hint
-> parse falha novamente
métricas trace-level = None
-> exportadas como "0.00"
```
### 9.2. Session metrics
Arquivo:
```text
configs/judge/session_metrics.yaml
```
Resultado esperado:
```json
{
"inferredCsiScore": 0.5,
"resolution": 1,
"conversationPrecision": 1,
"rationale": "..."
}
```
Os valores session-level são replicados no arquivo para todos os traces da mesma session.
---
## 10. Identity normalization
Arquivo:
```text
configs/identity.yaml
```
A camada de identidade converte identificadores específicos de canais/domínios para chaves canônicas como:
```text
customer_key
contract_key
interaction_key
account_key
resource_key
session_key
```
Isso evita espalhar lógica como `cpf`, `msisdn`, `invoice_id`, `order_id`, `ura_call_id` etc. pelo código do evaluator.
A ordem das fontes funciona como fallback.
Exemplo:
```yaml
session_key:
sources:
- business_context.session_key
- session_key
- conversation_key
- session_id
```
---
## 11. Configuração de GCS
Buckets ficam fora do código em:
```text
configs/judge/storage.yaml
```
Exemplo:
```yaml
storage:
buckets:
dev: tim-bigdata-dev-ca1f-raw-standard-agnt-ai-atendimento
fqa: tim-bigdata-fqa-60ce-raw-standard-agnt-ai-atendimento
prd: tim-bigdata-prod-e305-raw-standard-agnt-ai-atendimento
```
Seleção:
```env
APP_ENV=dev
STORAGE_CONFIG_PATH=configs/judge/storage.yaml
```
Override de emergência/operação:
```env
JUDGE_GCS_BUCKET=meu-bucket
```
Para habilitar o fluxo produtivo:
```env
ENABLE_GCS_UPLOAD=true
GOOGLE_APPLICATION_CREDENTIALS=/path/GCP_ACCESS_KEY.json
```
`run-agents` considera GCS obrigatório. Se `ENABLE_GCS_UPLOAD=false` ou o upload falhar, o run do agente termina como `FAILED`.
---
## 12. Arquivo de saída
O nome é configurado diretamente no `.env`:
```env
OUTPUT_FILENAME_PATTERN=AGENTE_{agent_id}_LLM_JUDGE_{timestamp}.TXT.GZ
```
Os placeholders disponíveis são `{agent_id}`, `{run_id}` e `{timestamp}`. O
`timestamp` é gerado no formato `YYYYMMDD_HHMMSS`, no fuso de São Paulo.
Padrão resultante:
```text
AGENTE_{agentId}_LLM_JUDGE_{YYYYMMDD}_{HHMMSS}.TXT.GZ
```
Exemplo:
```text
AGENTE_billing_LLM_JUDGE_20260814_020015.TXT.GZ
```
Cabeçalho:
```text
"judgeScore"|;"accuracyScore"|;"alucinationScore"|;"promptLength"|;"loop"|;"inferredCsiScore"|;"resolution"|;"conversationPrecision"|;"uraCallId"|;"channelId"|;"sessionId"|;"messageId"
```
Linha final:
```text
"TOTAL"|;"<n_traces>"
```
Após upload bem-sucedido para GCS, o arquivo local é apagado.
---
## 13. Instalação local
### 13.1. Pré-requisitos
- Python 3.11+;
- acesso ao Oracle Database/Autonomous Database;
- Oracle Wallet quando aplicável;
- acesso ao Langfuse v3;
- acesso ao OCI Generative AI para execução real do judge;
- credencial GCP para upload produtivo.
### 13.2. Criar venv
Linux/WSL/macOS:
```bash
python -m venv .venv
source .venv/bin/activate
```
Windows PowerShell:
```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
```
### 13.3. Instalar
Modo recomendado durante desenvolvimento:
```bash
python -m pip install --upgrade pip setuptools wheel
pip install -e .
```
Validar:
```bash
pip check
python -c "import oci, httpx, oracledb; import google.cloud.storage; print('Dependencias OK')"
```
---
## 14. Configuração local passo a passo
### Passo 1 — `.env`
```bash
cp .env.example .env
```
Edite os valores reais.
Configuração mínima típica:
```env
APP_ENV=dev
ADB_USER=admin
ADB_PASSWORD=...
ADB_DSN=...
ADB_WALLET_LOCATION=/path/to/wallet
ADB_WALLET_PASSWORD=...
ADB_TABLE_PREFIX=AGENTFW
ENABLE_LANGFUSE=true
LANGFUSE_CREDENTIALS_CONFIG_PATH=configs/langfuse/langfuse_credentials.yaml
LLM_PROVIDER=oci_sdk
LLM_PROFILE=judge
OCI_AUTH_MODE=config_file
OCI_CONFIG_FILE=~/.oci/config
OCI_PROFILE=DEFAULT
OCI_REGION=sa-saopaulo-1
OCI_COMPARTMENT_ID=...
OCI_GENAI_BASE_URL=https://...
OCI_GENAI_ENDPOINT_ID=ocid1.generativeaiendpoint...
CONTEXT_TURN_LIMIT=6
BATCH_SIZE=50
MAX_ATTEMPTS=3
ENABLE_GCS_UPLOAD=true
STORAGE_CONFIG_PATH=configs/judge/storage.yaml
GOOGLE_APPLICATION_CREDENTIALS=/path/GCP_ACCESS_KEY.json
```
### Passo 2 — Langfuse
```bash
cp configs/langfuse/langfuse_credentials.example.yaml \
configs/langfuse/langfuse_credentials.yaml
```
Declare o agente que será executado.
### Passo 3 — agents.yaml
Ajuste:
```text
configs/judge/agents.yaml
```
Para uma instância dedicada, deixe somente o agente daquela squad habilitado.
### Passo 4 — validar configuração
```bash
python -m evaluator.cli show-config
```
### Passo 5 — inicializar banco
```bash
python -m evaluator.cli init-db
```
> `reset-db` apaga e recria o schema do evaluator. Use apenas em ambiente controlado.
---
## 15. CLI
A ajuda real da versão atual é:
```bash
python -m evaluator.cli --help
```
Comandos disponíveis:
```text
init-db
reset-db
show-config
run
run-agents
progress
runs
```
### 15.1. `run` — execução manual/diagnóstica
Sintaxe:
```bash
python -m evaluator.cli run PERIOD_START PERIOD_END [OPTIONS]
```
Exemplo:
```bash
python -m evaluator.cli run \
2026-08-12T00:00:00 \
2026-08-12T23:59:59 \
--source langfuse \
--limit 10
```
Opções:
```text
--source TEXT default: langfuse
--limit INTEGER
--show-progress / --no-show-progress
```
**Importante:** `run` é um caminho manual genérico. Ele coleta, avalia e persiste resultados, mas não executa o contrato produtivo completo por agente (sampling configurado, export GCS e auditoria de job). Para homologação/produção use `run-agents`.
### 15.2. `run-agents` — fluxo produtivo
```bash
python -m evaluator.cli run-agents
```
Um agente:
```bash
python -m evaluator.cli run-agents \
--agent-id billing \
--source langfuse
```
Teste limitado:
```bash
python -m evaluator.cli run-agents \
--agent-id billing \
--limit 10
```
Opções:
```text
--source TEXT default: langfuse
--agent-id TEXT
--limit INTEGER
```
`run-agents` usa `daysBack`, `percentage`, credenciais e `gcs_prefix` do agente.
### 15.3. Listar runs
```bash
python -m evaluator.cli runs
```
ou:
```bash
python -m evaluator.cli runs --limit 50
```
### 15.4. Progresso de um run
```bash
python -m evaluator.cli progress <RUN_ID>
```
Com mais eventos:
```bash
python -m evaluator.cli progress <RUN_ID> --events 100
```
Eventos típicos do fluxo produtivo:
```text
LANGFUSE_JOB_AUDIT
RUN_CREATED
COLLECTING
COLLECTED
SAMPLED
ITEMS_INSERTED
BATCH_STARTED
ITEM_COMPLETED
SESSION_JUDGE_COMPLETED
COMPLETED
EXPORTED
GCS_UPLOADED
LOCAL_FILE_DELETED
LANGFUSE_JOB_AUDIT
```
---
## 16. Testes
Instale `pytest` caso necessário:
```bash
pip install pytest
```
### 16.1. Todos os testes
```bash
python -m pytest -v
```
### 16.2. Conformidade TIM
```bash
python -m pytest tests/test_tim_spec.py -v
```
### 16.3. Conformidade ampliada
```bash
python -m pytest tests/test_full_tim_compliance.py -v
```
### 16.4. Regressão de hardcodes
```bash
python -m pytest tests/test_no_product_hardcodes.py -v
```
### 16.5. Suite recomendada antes de um release
```bash
python -m pytest \
tests/test_tim_spec.py \
tests/test_full_tim_compliance.py \
tests/test_no_product_hardcodes.py \
-v
```
Validar sintaxe/imports:
```bash
python -m compileall evaluator
```
---
## 17. Teste funcional local recomendado
### 17.1. Smoke test com mock
Configure:
```env
LLM_PROVIDER=mock
```
E rode um período pequeno com source mock quando desejado:
```bash
python -m evaluator.cli run \
2026-08-12T00:00:00 \
2026-08-12T23:59:59 \
--source mock
```
### 17.2. Teste Langfuse sem volume grande
```bash
python -m evaluator.cli run-agents \
--agent-id billing \
--limit 5
```
Depois:
```bash
python -m evaluator.cli runs --limit 5
python -m evaluator.cli progress <RUN_ID> --events 100
```
Confirme:
- `COLLECTED` > 0;
- `SAMPLED` mostra sessions selecionadas;
- itens concluem sem `ITEM_FAILED`;
- `EXPORTED` aparece;
- `GCS_UPLOADED` contém URI `gs://...`;
- `LOCAL_FILE_DELETED` aparece;
- auditoria `completed` foi confirmada.
---
## 18. Docker
Build local:
```bash
docker build -t agent-framework-evaluator:local .
```
O runtime produtivo é batch. O container não precisa expor porta HTTP.
Para Kubernetes, o `CronJob` chama:
```text
python -m evaluator.cli run-agents --source langfuse
```
---
## 19. Kubernetes / OKE
O **evaluator propriamente dito continua CronJob-only**, conforme a especificação: ele não abre porta HTTP/gRPC e o processamento continua sendo executado exclusivamente pelo `evaluator-batch`.
A interface web foi reintroduzida como um **componente separado e somente leitura**, `evaluator-monitor`. Ela não executa avaliação, não altera runs e não muda o contrato batch do evaluator. O monitor apenas consulta as tabelas de execução/progresso no Autonomous Database.
```text
Evaluator batch (evaluator-batch CronJob)
│
├── cria/atualiza EVALUATION_RUN
├── grava EVALUATION_PROGRESS_EVENT
└── grava EVALUATION_RESULT
│
▼
Autonomous Database
▲
│ somente leitura
Evaluator Monitor (Deployment HTTP)
│
└── /ui + APIs de acompanhamento
```
Recursos Kustomize:
```text
Namespace
ServiceAccount
ConfigMap
CronJob evaluator-batch
Deployment evaluator-monitor
Service evaluator-monitor (ClusterIP)
```
Não existe Ingress por default. Assim, a interface não é exposta para fora do cluster automaticamente.
CronJob atual:
```yaml
schedule: "0 2 * * *"
timeZone: "America/Sao_Paulo"
suspend: false
concurrencyPolicy: Forbid
```
### 19.1. Preparar arquivos de deploy
```bash
cp deploy/env/evaluator.env.example deploy/env/evaluator.env
cp deploy/langfuse/langfuse_credentials.example.yaml \
deploy/langfuse/langfuse_credentials.yaml
```
Coloque o wallet em:
```text
deploy/wallet/
```
Credencial GCP:
```text
deploy/gcp/GCP_ACCESS_KEY.json
```
### 19.2. Criar secrets
DEV:
```bash
./deploy/scripts/create-secrets.sh dev
```
HML:
```bash
./deploy/scripts/create-secrets.sh hml
```
PROD:
```bash
./deploy/scripts/create-secrets.sh prod
```
São criados/atualizados:
```text
evaluator-secrets
evaluator-wallet
evaluator-langfuse
evaluator-gcp (quando o arquivo GCP existir)
```
### 19.3. Build e push
```bash
export REGISTRY=tim.azurecr.io
export IMAGE_TAG=<version>
./deploy/scripts/build-push.sh prod
```
### 19.4. Deploy
```bash
export REGISTRY=tim.azurecr.io
export IMAGE_TAG=<version>
./deploy/scripts/deploy.sh prod
```
Por default o script também executa o job de inicialização do banco.
Para não executar:
```bash
export RUN_INIT_DB=false
```
### 19.5. Status
```bash
./deploy/scripts/status.sh prod
```
ou diretamente:
```bash
kubectl -n agent-evaluator-prod get cronjob,jobs,pods
```
### 19.6. Execução imediata fora do horário
```bash
./deploy/scripts/run-now.sh prod
```
O script cria um Job baseado no CronJob.
Acompanhar:
```bash
kubectl -n agent-evaluator-prod get jobs,pods
kubectl -n agent-evaluator-prod logs -f job/<nome-do-job>
```
### 19.7. Interface web de acompanhamento
A UI é servida pelo componente independente `evaluator-monitor`:
```text
GET /ui
GET /health
GET /api/runs
GET /api/runs/{run_id}
GET /api/runs/{run_id}/results
```
Para acessar localmente um monitor implantado no Kubernetes sem criar Ingress:
```bash
kubectl -n agent-evaluator-prod port-forward svc/evaluator-monitor 8080:8080
```
Depois abra:
```text
http://localhost:8080/ui
```
A tela mostra runs recentes, `RUN_ID`, agente, status, progresso, eventos e resultados. O browser faz auto-refresh a cada 5 segundos.
Para executar o monitor localmente:
```bash
python -m uvicorn evaluator_monitor.app:app --host 0.0.0.0 --port 8080
```
O monitor utiliza as mesmas configurações `ADB_*`/wallet do evaluator. Ele é **read-only** do ponto de vista funcional: não há endpoint para iniciar, cancelar, apagar ou alterar runs.
> Recomendação de segurança: mantenha o Service como `ClusterIP`. Se futuramente houver necessidade de exposição externa, coloque autenticação/autorização no Ingress/API Gateway corporativo; não exponha o monitor diretamente à Internet.
---
## 20. Como subir uma nova versão
Fluxo recomendado:
### 1. Atualizar código/configuração
Não coloque credenciais reais no Git.
### 2. Rodar testes
```bash
python -m pytest -v
python -m compileall evaluator
```
### 3. Testar localmente com limite
```bash
python -m evaluator.cli run-agents \
--agent-id <agentId> \
--limit 5
```
### 4. Validar o run
```bash
python -m evaluator.cli runs --limit 10
python -m evaluator.cli progress <RUN_ID> --events 100
```
### 5. Confirmar GCS
O evento deve conter:
```text
GCS_UPLOADED
```
com uma URI como:
```text
gs://<bucket>/<prefix>/AGENTE_<agent>_LLM_JUDGE_<timestamp>.TXT.GZ
```
### 6. Atualizar secrets/configuração do ambiente
```bash
./deploy/scripts/create-secrets.sh <dev|hml|prod>
```
### 7. Build/push com uma tag nova
```bash
export REGISTRY=<registry>
export IMAGE_TAG=<nova-versao>
./deploy/scripts/build-push.sh <ambiente>
```
### 8. Deploy
```bash
./deploy/scripts/deploy.sh <ambiente>
```
### 9. Rodar manualmente para homologação
```bash
./deploy/scripts/run-now.sh <ambiente>
```
### 10. Verificar logs e artefatos
```bash
kubectl -n agent-evaluator-<ambiente> logs -f job/<job>
```
Confirme também o objeto no bucket GCS e a auditoria `judge-job-{agentId}` no Langfuse.
---
## 21. Troubleshooting
### `Collected 0 traces`
Verifique nesta ordem:
1. data processada (`daysBack` usa BRT);
2. credencial Langfuse do `agentId` correto;
3. `host` do projeto Langfuse;
4. `langfuse_agent_aliases`;
5. existência de sessions/traces naquele período;
6. se o `agentId` lógico é diferente do `metadata.agent_id` dos traces.
Exemplo:
```yaml
- agentId: billing
langfuse_agent_aliases:
- telecom_contas
- billing_agent
```
### `No Langfuse credentials configured for agent ...`
O agente existe em `agents.yaml`, mas não no arquivo apontado por:
```env
LANGFUSE_CREDENTIALS_CONFIG_PATH
```
Adicione uma entrada com o mesmo `agentId`.
### `Langfuse credentials definition file not found`
Crie/mapeie o arquivo:
```text
configs/langfuse/langfuse_credentials.yaml
```
ou configure outro path.
### `TIM specification requires GCS upload...`
`run-agents` foi executado com:
```env
ENABLE_GCS_UPLOAD=false
```
Altere para `true` e configure a credencial GCP.
### GCS falha
Verifique:
```bash
echo "$GOOGLE_APPLICATION_CREDENTIALS"
ls -l "$GOOGLE_APPLICATION_CREDENTIALS"
```
E valide o bucket resolvido por `APP_ENV`/`storage.yaml`.
### OCI SDK falha em dedicated endpoint
Confira:
```env
LLM_PROVIDER=oci_sdk
OCI_GENAI_BASE_URL=https://<endpoint-nativo-sem-openai-v1>
OCI_GENAI_ENDPOINT_ID=ocid1.generativeaiendpoint...
OCI_COMPARTMENT_ID=...
```
Para `oci_sdk`, não use `/openai/v1` no endpoint.
### Banco não inicializado
```bash
python -m evaluator.cli init-db
```
### Ver progresso pela CLI
O `run-agents` agora imprime o `RUN_ID` assim que a execução é criada:
```text
[telecom_contas] RUN_ID=8d9a2d1e-...
progress: python -m evaluator.cli progress 8d9a2d1e-... --events 100
```
Também é possível listar runs e consultar qualquer execução:
```bash
python -m evaluator.cli runs
python -m evaluator.cli progress <RUN_ID> --events 100
```
### Ver progresso pela interface web
```bash
python -m uvicorn evaluator_monitor.app:app --host 0.0.0.0 --port 8080
```
Abra `http://localhost:8080/ui`. Em Kubernetes, prefira `kubectl port-forward svc/evaluator-monitor 8080:8080`.
---
## 22. Variáveis de ambiente principais
| Variável | Uso |
|---|---|
| `APP_ENV` | ambiente usado para resolver storage |
| `ADB_*` | conexão e wallet Oracle |
| `ENABLE_LANGFUSE` | habilita coleta Langfuse |
| `LANGFUSE_CREDENTIALS_CONFIG_PATH` | YAML declarativo por agente |
| `LANGFUSE_*` | fallback global apenas para `run` manual |
| `PUBLISH_LANGFUSE_SCORES` | publica scores de trace de volta no Langfuse |
| `LLM_PROVIDER` | `mock`, `oci_openai`, `oci_sdk`, `openai_compatible` |
| `LLM_PROFILE` | perfil de judge |
| `OCI_AUTH_MODE` | autenticação OCI SDK |
| `OCI_GENAI_BASE_URL` | endpoint OCI GenAI |
| `OCI_GENAI_MODEL` | model ID/nome ou compatibilidade com endpoint OCID |
| `OCI_GENAI_ENDPOINT_ID` | dedicated endpoint OCID |
| `OCI_COMPARTMENT_ID` | compartment OCI |
| `AGENTS_CONFIG_PATH` | arquivo de agentes |
| `TRACE_PROMPT_PATH` | prompt trace-level |
| `SESSION_PROMPT_PATH` | prompt session-level |
| `BATCH_SIZE` | quantidade processada por lote interno |
| `MAX_ATTEMPTS` | limite configurável de tentativas |
| `CONTEXT_TURN_LIMIT` | últimos N turnos enviados ao judge trace |
| `ENABLE_GCS_UPLOAD` | exige upload no fluxo produtivo |
| `STORAGE_CONFIG_PATH` | mapping de buckets por ambiente |
| `JUDGE_GCS_BUCKET` | override de bucket |
| `GOOGLE_APPLICATION_CREDENTIALS` | credencial GCP |
| `OUTPUT_DIR` | diretório temporário de export |
| `OUTPUT_FILENAME_PATTERN` | template configurável do nome do arquivo de saída |
---
## 23. Segurança e operação
- não versionar `.env` real;
- não versionar `langfuse_credentials.yaml` real;
- não versionar GCP service account JSON;
- não versionar wallet Oracle;
- em OKE prefira `oke_workload_identity` quando aplicável;
- monte credenciais como Kubernetes Secrets;
- use `readOnlyRootFilesystem` no CronJob;
- mantenha `concurrencyPolicy: Forbid` para evitar dois D+1 concorrentes do mesmo deployment;
- nunca use `reset-db` em produção sem plano explícito de restauração.
---
## 24. Checklist de homologação
Antes de promover uma versão:
- [ ] `pip check` sem erro;
- [ ] `python -m pytest -v` passando;
- [ ] `python -m compileall evaluator` sem erro;
- [ ] `show-config` aponta para ADB/OCI/configs corretos;
- [ ] `agents.yaml` contém apenas os agentes esperados para aquele deployment;
- [ ] credencial Langfuse existe para cada `agentId` habilitado;
- [ ] `run-agents --limit 5` encontra traces;
- [ ] sampling seleciona sessions, não traces individuais;
- [ ] judge trace/session conclui;
- [ ] arquivo possui cabeçalho de 12 colunas;
- [ ] nome segue `AGENTE_{agentId}_LLM_JUDGE_{YYYYMMDD}_{HHMMSS}.TXT.GZ`;
- [ ] evento `GCS_UPLOADED` confirmado;
- [ ] arquivo local removido depois do upload;
- [ ] auditoria Langfuse `created/completed` confirmada;
- [ ] CronJob está `suspend: false`;
- [ ] timezone do CronJob é `America/Sao_Paulo`;
- [ ] nenhuma credencial real está no repositório.
---
## 25. Resumo para quem está chegando no projeto
Se você precisa entender o projeto em poucos minutos, pense assim:
```text
Langfuse guarda as conversas
↓
Evaluator escolhe sessions D+1
↓
OCI GenAI julga traces e sessions
↓
ADB guarda execução e resultados
↓
GCS recebe o arquivo oficial
↓
Langfuse registra a auditoria do batch
```
Os pontos de extensão ficam em configuração:
```text
Qual agente? -> configs/judge/agents.yaml
Qual projeto Langfuse?-> langfuse_credentials.yaml
Qual bucket? -> configs/judge/storage.yaml
Qual prompt? -> trace_metrics.yaml / session_metrics.yaml
Qual modelo? -> .env + llm_profiles.yaml
Qual identidade? -> configs/identity.yaml
```
A regra arquitetural mais importante é: **o código do evaluator deve permanecer genérico; detalhes de agentes, ambientes, credenciais e storage pertencem à configuração.**