new feature: External guardrails/judges

This commit is contained in:
2026-08-24 11:14:10 -03:00
parent 05373deff2
commit fd37138c4f
384 changed files with 40593 additions and 3621 deletions

View File

@@ -0,0 +1,71 @@
# Guardrails e Judges externos — Contas
## Objetivo
O `agent_framework_oci` mantém apenas mecanismos e políticas realmente genéricos. O agente Contas mantém políticas, exemplos e prompts que conhecem TIM, Contas, VAS, fatura, cancelamento ou nomenclaturas comerciais.
## Regra arquitetural
- **Framework:** engine, contratos, execução paralela, fail-fast, telemetria, carregamento YAML e implementações genéricas.
- **Agente:** prompts/policies de domínio e classes externas.
- Um componente externo só é importado quando o YAML do agente declara `type: external`.
- Guardrails/judges nativos continuam funcionando sem qualquer alteração de configuração.
## Configuração de guardrail externo
```yaml
output:
- code: TIM_AOFERTA
type: external
class: app.extensions.tim_guardrails:TimProactiveOfferRail
enabled: true
```
O código `TIM_AOFERTA` deixa explícito que esta política é do agente Contas. O framework continua podendo oferecer `AOFERTA` como rail genérico para outros agentes.
## Configuração de judge externo
```yaml
judges:
- name: tim_groundedness
type: external
class: app.extensions.tim_judges:TimGroundednessJudge
enabled: true
threshold: 0.60
```
## Concorrência e threads
O `ParallelRailExecutor` executa todos os rails concorrentemente. `evaluate()` assíncrono roda no event loop; plugin síncrono roda por `asyncio.to_thread`, portanto não bloqueia o loop. Judges nativos e externos são disparados com `asyncio.gather`; judges síncronos também são deslocados para `asyncio.to_thread`. A ordem da lista de resultados permanece a ordem do YAML.
## Compatibilidade
A extensão é aditiva. Entradas antigas como `{code: PINJ}` e `{name: groundedness}` seguem nativas. Somente itens com `type: external` usam import dinâmico. Isso evita dependência reversa do framework para `app.*`.
## Mapeamento nesta versão do Contas
| Genérico no framework | Específico no Contas | Motivo |
|---|---|---|
| OOS | TIM_OOS | escopo do Contas/TIM |
| AOFERTA | TIM_AOFERTA | política de oferta do atendimento TIM |
| REVPREC | TIM_REVPREC | exemplos e ações transacionais TIM |
| FRASEOLOGIA | TIM_FRASEOLOGIA | fraseologia própria (mantido desabilitado como antes) |
| response_quality | tim_response_quality | prompt original do auditor Contas |
| groundedness | tim_groundedness | prompt original de alucinação/grounding do Contas |
Os prompts originais foram preservados em `app/extensions/tim_prompts/`. As versões sob `agent_framework/.../calibrated/prompts` foram generalizadas e não devem conter nomes comerciais TIM.
## Como criar um novo componente
1. Implemente uma classe no agente com `evaluate(...)`.
2. Para guardrail, retorne `RailDecision`/`RailResult`; para judge, retorne `JudgeResult`.
3. Declare `type: external` e o caminho `module:Class` no YAML.
4. Não crie cliente LLM próprio: use o `llm` fornecido pelo framework/contexto para manter `llm_profiles.yaml`, Langfuse e contabilização.
5. Teste convivência com os rails/judges nativos e o comportamento fail-closed.
## Hardcodes de integração do Contas
Os valores legados de `clientId`, `channel`, `cspId`, sender e URLs que antes apareciam como fallback em Python foram movidos para `config/tim_integration_defaults.yaml`. A precedência é:
1. variável de ambiente;
2. `config/tim_integration_defaults.yaml`;
3. default explícito somente quando a chamada realmente define um default técnico.
Isso preserva contratos diferentes por operação (`TIM_CANCELAMENTO_CHANNEL`, `TIM_DIVERGENCIA_CHANNEL`, etc.) sem usar um `TIM_DEFAULT_*` que altere silenciosamente o legado.
## Validação de contestação
`validate_contestation_items` é regra do domínio Contas e agora vive em `app/domain/contas/contestation_validation.py`. O módulo antigo no framework existe apenas como shim de compatibilidade/depreciação; código novo do Contas importa a implementação do agente diretamente.
## Observabilidade dos códigos externos
O código semântico de um guardrail/judge externo não deve ser alterado para atender um código numérico de um cliente. Use `config/observability_mapping.yaml` para o contrato de telemetria. Exemplo: a extensão pode continuar emitindo `GRL.TOXOUT`, enquanto o contrato publica `GRL.004`. Isso mantém a política do agente separada do catálogo externo de observabilidade.

View File

@@ -0,0 +1,67 @@
# Mapeamento contratual da observabilidade do Contas
O Contas usa o mecanismo genérico `ObservabilityCodeMapper` do framework para adaptar **identificadores internos de observabilidade** aos códigos exigidos pelo contrato externo.
Arquivo:
```text
config/observability_mapping.yaml
```
Configuração de exemplo deste agente:
```yaml
version: "1"
mappings:
guardrail.dlex_in: GRL.004
guardrail.tox: GRL.005
```
O mapping é feito pelo nome canônico emitido internamente. Assim, uma generation/observation criada como `guardrail.dlex_in` aparece externamente como `GRL.004`, e `guardrail.tox` como `GRL.005`.
A substituição acontece antes dos providers de observabilidade. O mesmo nome contratual é usado por Langfuse, OTEL e EventBus nos caminhos que passam por `Telemetry`. Eventos publicados pelo `AgentObserver` também continuam usando o mesmo mapper.
Quando um nome de span/generation é substituído, o nome interno é preservado em metadata:
- `observability_name_internal`
- `observability_name_mapped`
- `observability_code_mapped: true`
Para eventos estruturados, permanecem disponíveis os campos equivalentes `event_code_internal` e `event_code_mapped`.
Códigos/names ausentes na tabela passam sem alteração. Para acrescentar outro contrato, adicione somente uma nova entrada ao YAML; não altere Python nem o guardrail/judge.
Este arquivo pertence ao agente/deployment. O framework contém apenas a engine genérica de mapping e não conhece os códigos contratuais deste agente ou de qualquer cliente.
## Diagnóstico de carregamento
No startup o agente registra uma linha `Observability mapping:` com `enabled`, `path`, quantidade de entradas, amostras resolvidas e o arquivo real de onde `agent_framework` foi importado. Isso permite detectar `.venv` antigo/cópia errada do framework e path relativo incorreto.
A normalização é aplicada em duas barreiras:
1. `Telemetry._start_observation()` — última barreira para spans/generations criados pelo Telemetry;
2. `LangfuseAnalyticsPublisher` — necessário porque esse publisher usa o SDK Langfuse diretamente e não passa pelo Telemetry.
Assim, uma configuração como:
```yaml
mappings:
guardrail.dlex_in: GRL.004
guardrail.tox: GRL.005
```
é aplicada independentemente de qual dos dois caminhos produziu a observation.
## Normalização na fronteira do LLM provider
A normalização não depende apenas do `Telemetry`. O `generation_name` é resolvido pelo `ObservabilityCodeMapper` antes de o provider LLM iniciar qualquer instrumentação. Isso garante que nomes como `guardrail.dlex_in` já cheguem ao tracer como `GRL.004`.
Quando o provider já recebe o `Telemetry` do framework, a auto-instrumentação `langfuse.openai` é desabilitada para evitar uma segunda observation fora do contrato central.
O caminho relativo configurado em `OBSERVABILITY_CODE_MAPPING_PATH` é procurado no diretório corrente e nos roots de importação Python, permitindo iniciar o Uvicorn fora do diretório raiz do agente sem perder o mapping.
## Compatibilidade automática do framework
A partir desta versão, o framework possui um registry default interno (`agent_framework/config/observability_mapping.yaml`) carregado mesmo quando o agente não possui `OBSERVABILITY_CODE_MAPPING_*`. O arquivo do agente, quando habilitado, funciona como overlay. Isso permite substituir somente a versão do framework em agentes legados sem mudar a taxonomia GRL nem as decisões históricas dos rails.
Veja também `agent_framework_oci/libs/agent_framework/docs/OBSERVABILITY_DEFAULT_OVERLAY_COMPATIBILITY.md`.

View File

@@ -0,0 +1,43 @@
# Observability Contract Registry
`config/observability_mapping.yaml` é a única tabela usada pelo framework para duas responsabilidades relacionadas ao contrato externo:
1. traduzir nomes/códigos semânticos para labels exigidos pela observabilidade do cliente;
2. preservar ações legadas de guardrails (`retry`, `handover`, etc.) sem hardcode de nomes no Python.
A sintaxe v1 continua válida:
```yaml
mappings:
guardrail.dlex_in: GRL.004
```
A forma rica adiciona `action` e `aliases`:
```yaml
mappings:
guardrail.revprec:
action: retry
aliases: [REVPREC, TIM_REVPREC]
```
`label` é opcional. Quando ausente, o nome de observabilidade não é renomeado. `action` também é opcional.
## Precedência de ação
Para uma decisão negada, o framework usa:
1. `metadata.terminal_action` retornado pelo rail;
2. `on_deny` do `guardrails.yaml`;
3. `action` resolvida pelo `observability_mapping.yaml`;
4. `BLOCK` como fallback fail-safe.
Isso mantém compatibilidade com rails internos e externos sem que `OutputSupervisor` ou `ParallelRailExecutor` conheçam nomes como `REVPREC`, `CMP`, `SCO`, `GND`, `ATH` ou `HUMAN`.
## Aliases
Uma entrada `guardrail.revprec` é automaticamente resolvida também por `REVPREC`. Aliases explícitos permitem associar nomes externos ou históricos, por exemplo `TIM_REVPREC`.
## Compatibilidade
Mappings escalares continuam funcionando sem alteração. Agentes que não habilitam o mapper continuam em passthrough e usam `BLOCK` para negações sem ação explícita.

View File

@@ -0,0 +1,26 @@
# Correção do merge Default + Overlay de Observabilidade
## Problema
O default do framework estava ativo, porém em alguns caminhos o overlay do agente não era carregado. O efeito observado no Langfuse era `GRL.DLEX_IN`/`GRL.TOX` (default) em vez de `GRL.004`/`GRL.005` (Contas).
## Correção
O framework agora monta um único registry efetivo antes de qualquer resolução:
1. carrega `agent_framework/config/observability_mapping.yaml`;
2. localiza o overlay do agente;
3. faz merge por chave canônica, com o agente sobrescrevendo o default;
4. reconstrói os aliases somente depois do merge;
5. usa esse único registry em LLM provider, Telemetry, Analytics, OutputSupervisor e ParallelRailExecutor.
## Descoberta do overlay
Além de `OBSERVABILITY_CODE_MAPPING_PATH`, o framework autodetecta `config/observability_mapping.yaml` no cwd e nos roots de importação Python. O arquivo default empacotado do framework é excluído dessa descoberta.
Assim um agente com arquivo convencional de overlay não depende de alterar seu launcher ou `.env` para que a customização seja aplicada.
## Resultado esperado no Contas
- `guardrail.dlex_in` -> `GRL.004`
- `guardrail.tox` -> `GRL.005`
- componentes não sobrescritos continuam herdando o default do framework.
## Compatibilidade
Agentes antigos sem overlay continuam usando apenas o default do framework e preservam a taxonomia/ações históricas.

View File

@@ -0,0 +1,83 @@
# OutputSupervisor sem taxonomia contratual hardcoded
## Objetivo
O `OutputSupervisor` do framework trabalha somente com eventos semânticos e ações de runtime. Códigos contratuais externos/numerados pertencem exclusivamente ao `ObservabilityCodeMapper` configurado pelo agente/deployment.
## Eventos internos
Exemplos de eventos internos:
```text
guardrail.output_supervisor.started
guardrail.result.allow
guardrail.result.block
guardrail.result.retry
guardrail.output.<rail>.completed
guardrail.output_supervisor.completed
```
Se um cliente exigir códigos próprios, configure `config/observability_mapping.yaml`. O supervisor não conhece a taxonomia externa.
## Ação quando um rail nega
O framework não decide mais a ação procurando nomes específicos de rails. A ação pode vir do próprio resultado:
```python
metadata={"terminal_action": "retry"}
```
ou do YAML:
```yaml
output:
- code: MY_VALIDATION
enabled: true
on_deny: retry
```
Valores suportados são os valores de `RailAction`, como `block`, `retry` e `handover`.
## Remediação por rewrite
Rewrite também é uma capacidade genérica. O rail/policy declara a remediação:
```yaml
output:
- code: MY_WORDING_POLICY
enabled: true
on_block:
type: rewrite
max_attempts: 1
prompt_id: FALLBACK
profile_name: grl
component_name: guardrail.wording.rewrite
```
O supervisor não verifica se o código é `FRASEOLOGIA` ou qualquer outro nome. Um guardrail externo do agente pode usar exatamente o mesmo contrato.
## Mensagens de UX
Mensagens de fallback/handover pertencem ao agente:
```yaml
output_supervisor:
max_retries: 3
fallback_message: "..."
handover_message: "..."
```
Assim o framework não precisa conhecer idioma, marca ou fraseologia do atendimento.
## Contas
O Contas preserva seu comportamento atual:
- `TIM_REVPREC` declara `terminal_action=retry` no próprio rail externo;
- `CMP` está configurado com `on_deny: retry`;
- `TIM_FRASEOLOGIA`, quando habilitado, declara remediação `rewrite` no agente;
- textos de fallback/handover ficam no `config/guardrails.yaml` do Contas.
## Compatibilidade
Rails que retornam apenas `allowed=false` e não declaram policy continuam em `block`, que é o fail-closed genérico. Não há mais inferência de ação pelo nome do rail.