Ajustes conforme relatorio de testes 2026-08-27

This commit is contained in:
2026-08-29 10:03:42 -03:00
parent 1fd18531c0
commit 81f24d7357
699 changed files with 7989 additions and 2364 deletions

View File

@@ -558,3 +558,136 @@ Os arquivos abaixo foram consolidados neste manual:
### Regra de manutenção
Novas correções ou evoluções deste tema devem atualizar este documento consolidado. Release notes podem continuar existindo como histórico, mas não devem ser necessárias para compreender ou implementar a funcionalidade.
## Resolução canônica e revalidação de domínio antes da execução
Quando uma pré-validação resolve uma referência do usuário para uma entidade canônica, o framework **não deve simplesmente sobrescrever o parâmetro e executar a tool originalmente escolhida**. O contrato separa três valores:
```text
requested_subject = "youtube"
resolved_subject = "Youtube Premium"
execution_subject = "Youtube Premium"
```
O validador de domínio pode devolver `transaction_decision` com:
```json
{
"resolved_arguments": {"subject": "Youtube Premium"},
"target_tool": "tratar_vas_estrategico",
"action_changed": true,
"requires_reconfirmation": true,
"confirmation_message": "Identifiquei o serviço Youtube Premium. Esse serviço possui tratamento específico. Você deseja prosseguir?"
}
```
Responsabilidades:
- **Framework:** preserva argumentos solicitados, aplica apenas os argumentos canônicos declarados pelo validador, atualiza a transação para a `target_tool`, respeita `requires_reconfirmation` e mantém a decisão na evidência de pré-validação.
- **Agente/domínio:** decide classe, política e tool efetiva. O framework não conhece regras como “Youtube Premium é estratégico”.
- **MCP/backend:** executa a operação final já decidida pelo domínio.
Se a canonicalização não alterar a ação (`Tamboro``Tamboro Mensal`, por exemplo), a tool pode permanecer a mesma. Se a resolução alterar classe/política/tool, a decisão de domínio precisa ocorrer **antes da confirmação e da execução**. Em caso de ambiguidade ou baixa confiança, o validador deve pedir nova coleta/clarificação em vez de promover silenciosamente um candidato.
### Troubleshooting: resolved_subject correto, mas tool recebe o texto original
Sintoma: a pré-validação registra `resolved_subject="Youtube Premium"`, porém a execução ainda recebe `subject="youtube"`. Verifique se o validador retorna `transaction_decision.resolved_arguments` e se o runtime aplicou a decisão antes de congelar `pending_tool_call`/`confirmation_snapshot`.
Sintoma: a entidade foi resolvida corretamente, mas a tool final continua inadequada. Verifique `transaction_decision.target_tool`; a reclassificação de domínio pertence ao agente/validador, não ao framework.
Para domínios que possuem uma classificação autoritativa no detalhe do backend, a revalidação deve usar essa evidência antes de categorias agregadas. No Contas, por exemplo, `invoice_detail.parsed_content` preserva `classe=avulso|estrategico|bundle`; `billing_analysis` pode agrupar o mesmo item em seções mais amplas como `streaming` ou serviços de parceiros. A entidade canônica pode ser descoberta por qualquer evidência autorizada, mas a **decisão de negócio** deve priorizar a fonte que preserva a classificação de domínio. Se houver conflito de classificação, não troque a ação silenciosamente: mantenha a operação original ou peça esclarecimento conforme a política do agente.
## Confirmação transacional semântica: SIM / NAO / CONTINUAR
Transações em `AWAITING_CONFIRMATION` usam duas camadas, nesta ordem:
1. **Parser determinístico** para confirmações/recusas explícitas (`sim`, `não`, `confirmo`, `pode fazer`, etc.). Esse caminho continua sendo o mais barato, rápido e seguro e **não chama LLM**.
2. **Fallback semântico por LLM** somente quando o parser determinístico retorna inconclusivo. O fallback reutiliza o mesmo mecanismo declarativo de `expected_input.semantic_classifier` dos workflows pausados e injeta a pergunta pendente, o histórico recente relacionado ao mesmo tema e a fala atual.
A configuração fica em `config/routing.yaml`, sob `router.transaction_confirmation.semantic_fallback`:
```yaml
router:
transaction_confirmation:
semantic_fallback:
enabled: true
allowed_values: [SIM, NAO, CONTINUAR]
confirm_values: [SIM]
reject_values: [NAO]
continue_values: [CONTINUAR]
include_relevant_context: true
profile_name: router
prompt: |
Classes permitidas: {{ allowed_values }}
Pergunta pendente:
{{ pending_prompt }}
Histórico relevante:
{{ relevant_conversation_context }}
Resposta atual:
{{ user_input }}
```
### Significado das classes
- `SIM`: aceite inequívoco da ação pendente. Exemplos: `isso mesmo, pode confirmar`, `é isso`, `pode seguir`, quando o contexto torna o aceite claro.
- `NAO`: recusa inequívoca da ação pendente. Exemplos: `melhor não`, `não quero mais`, `cancela isso`.
- `CONTINUAR`: a fala não confirma nem rejeita de forma inequívoca. Exemplos: pergunta adicional, correção de parâmetro, informação nova, ambiguidade ou possível mudança de assunto. Nesse caso a tool não é executada por confirmação.
### Exemplo
Contexto:
```text
Cliente: quero cancelar o Tamboro Mensal
Agente: Você confirma o cancelamento do serviço Tamboro Mensal?
Cliente: isso mesmo, pode confirmar
```
O parser determinístico não precisa conhecer literalmente `isso mesmo, pode confirmar`. O fallback recebe:
```text
pending_prompt = "Você confirma o cancelamento do serviço Tamboro Mensal?"
relevant_conversation_context = histórico recente do mesmo fluxo
user_input = "isso mesmo, pode confirmar"
```
e deve retornar apenas:
```text
SIM
```
O router então publica em `route_decision.metadata`:
```json
{
"transaction_turn_consumed": true,
"transaction_confirmation_decision": "confirm",
"transaction_confirmation_source": "semantic"
}
```
O `AgentRuntime` reutiliza essa decisão e **não tenta reclassificar a mesma fala com o parser determinístico**. Isso evita a regressão em que o router entende semanticamente a confirmação, mas o runtime volta a tratá-la como inconclusiva.
### Precedência e compatibilidade
A funcionalidade é aditiva. Entradas determinísticas já suportadas continuam com o mesmo comportamento e sem custo adicional de LLM. O fallback semântico só roda quando a primeira camada não consegue decidir. Assim, `sim` e `não` continuam tendo precedência absoluta sobre `intent_shift`. Uma saída `CONTINUAR` não confirma nem rejeita automaticamente a transação; o fluxo normal pode então avaliar continuação contextual ou mudança de intenção conforme as políticas existentes.
### Observabilidade
Para confirmações semânticas, o framework registra a geração como `transaction.confirmation.semantic_classifier` e acrescenta ao metadata do roteamento a fonte `semantic`, a classificação retornada e o contexto conversacional relevante utilizado. Para confirmações literais, a fonte permanece `deterministic`.
### Compatibilidade de interrupts duráveis no pause/resume
O runtime não usa `snapshot.next` isoladamente para decidir se um workflow está pausado. Um `next` pode representar trabalho auxiliar do LangGraph, inclusive nós sintéticos criados pelo framework como `__pause` e `__continue`.
A pausa é reconhecida por um interrupt real. Dependendo da versão do LangGraph/checkpointer, esse interrupt pode aparecer em `task.interrupts` ou persistido em `snapshot.values["__interrupt__"]`. O runtime aceita ambas as formas e deduplica o payload quando as duas são expostas simultaneamente.
Isso evita dois falsos diagnósticos:
- considerar `snapshot.next` como `PAUSED` quando não existe interrupt real;
- considerar um `next=("<node>__pause",)` como erro de trabalho pendente quando o interrupt está persistido em `__interrupt__`.
Em workflows com `expected_input.semantic_classifier`, os tokens internos `SIM`, `NAO` e `CONTINUAR` continuam sendo valores de controle do resume e não devem ser confundidos com resposta final ao cliente.

View File

@@ -58,6 +58,69 @@ Esta versão adiciona uma camada pragmática de guardrails ao `agent_framework`,
- `RET_REL` — valida relevância de chunks de retrieval por score mínimo.
- `TOOL_VAL` — valida ferramenta MCP/tool, argumentos obrigatórios, valores negativos e allowlist.
### Contrato para protocolos autorizados em guardrails de saída
Quando um workflow ou tool produz um **protocolo que deve ser exibido ao próprio cliente**, o código de integração do agente deve registrar esse valor no contexto de saída antes da execução dos guardrails:
```python
ctx["expected_protocols"] = [protocol_number]
```
Esse campo é um **contrato do framework**. Ele informa que aqueles valores específicos foram produzidos ou validados pelo fluxo atual e, portanto, podem ser usados pelos guardrails de saída como evidência de autorização.
Fluxo esperado:
```text
workflow/tool gera protocolo
agente registra em expected_protocols
CMP valida que o protocolo exibido pertence aos valores esperados
DLEX_OUT não bloqueia esse protocolo apenas por classificá-lo como identificador
resposta pode informar o protocolo ao cliente
```
Regras importantes:
- `expected_protocols` deve conter **somente protocolos realmente produzidos/esperados no turno ou transação atual**.
- Não use `expected_protocols` para liberar tokens, credenciais, IDs internos arbitrários ou dados de terceiros.
- A autorização vale somente para os valores listados; outro identificador continua sujeito às regras normais de `DLEX_OUT`.
- O valor deve ser propagado **antes de `output_guardrails`**. Se o protocolo só for adicionado depois, a autorização não terá efeito.
- Em respostas transacionais, mantenha a evidência do protocolo no resultado da tool/workflow para que `CMP`, `GND` e observabilidade consigam correlacionar o valor.
Exemplo:
```python
result = await executar_workflow(...)
protocol_number = result.get("protocol_number") or result.get("protocolo_id")
if protocol_number:
ctx["expected_protocols"] = [str(protocol_number)]
```
#### Troubleshooting: workflow concluiu, mas a resposta foi substituída por mensagem de segurança
Sintoma típico:
```text
workflow = COMPLETED
CMP = allowed
DLEX_OUT = blocked por "protocolo interno"
resposta final = "Não consegui validar essa resposta com segurança..."
```
Verifique, nesta ordem:
1. O protocolo gerado está presente no resultado/evidência da tool ou workflow?
2. O agente propagou o mesmo valor em `ctx["expected_protocols"]`?
3. `expected_protocols` foi preenchido antes de `output_guardrails`?
4. O protocolo presente na resposta é exatamente um dos valores esperados?
5. O `DLEX_OUT` está bloqueando por outro motivo real, como segredo, token ou dado de terceiro?
Se `expected_protocols` estiver ausente, o framework não deve presumir que qualquer identificador textual é seguro para divulgação.
### Arquivos alterados
- `agent_framework/src/agent_framework/guardrails/rails.py`

View File

@@ -0,0 +1,157 @@
# 12 — Feedback de Guardrails de Entrada e Semântica de Turno Bloqueado
## Objetivo
Este documento descreve como o `AgentWorkflow`, implementado em `app/workflows/agent_graph.py`, deve tratar um turno interrompido por guardrail de entrada sem transformar toda interrupção em uma mensagem genérica de “regra de segurança”.
A regra central é separar três coisas:
1. **decisão técnica do guardrail**, usada pelo runtime e pela observabilidade;
2. **mensagem pública ao usuário**, adequada ao tipo de bloqueio ou necessidade de esclarecimento;
3. **estado do turno**, que não pode carregar routing, tools ou judges de um turno que foi interrompido antes dessas etapas.
## Fluxo esperado
```text
mensagem do usuário
input_guardrails
allowed?
├─ sim → routing → tools/agente → composição → output_guardrails
└─ não
classificar tratamento público
limpar estado de routing/tools/judges do turno
construir mensagem pública segura
output_guardrails
persistência/resposta
```
Um guardrail de entrada bloqueante deve ser decidido **antes de qualquer tool com efeito colateral**.
## `reason` interno não é a resposta ao usuário
O campo `reason` deve permanecer disponível para logs, traces, eventos e diagnóstico. Ele não deve ser exibido literalmente quando puder revelar mecanismo interno ou quando a frase técnica não for apropriada ao usuário final.
Exemplo:
```text
COER.reason = "fala incompreensível ou negação ambígua na transcrição"
```
A resposta pública pode ser:
```text
"Não consegui entender sua última mensagem porque ela parece incompleta ou ambígua. Pode reformular ou completar o que você quis dizer?"
```
## Tratamento por tipo de guardrail
O comportamento exato continua configurável, mas a semântica esperada é:
| Guardrail | Tratamento público recomendado |
|---|---|
| `COER` | solicitar esclarecimento/reformulação; não tratar ambiguidade como incidente de segurança |
| `PINJ` | bloquear com mensagem segura sem explicar o mecanismo interno |
| `DLEX_IN` | bloquear ou orientar reformulação sem expor dado interno/sensível |
| `INPUT_SIZE` | solicitar redução da entrada |
| `TOX` | aplicar a política configurada para conteúdo inadequado |
| `CMP` | responder segundo a política de compliance |
| desconhecido | usar fallback seguro e genérico |
## Limpeza do estado do turno bloqueado
Quando o input é bloqueado antes do routing, o estado final daquele turno não deve reutilizar dados residuais do turno anterior.
No mínimo, o workflow deve evitar apresentar como atuais:
```text
route_decision
mcp_tools
mcp_results
judge_results
```
O metadata deve deixar explícito que o turno foi interrompido no estágio de input guardrails.
Isso evita um diagnóstico falso como:
```text
route = blocked
mcp_results = [tool executada]
```
quando a tool na realidade pertence ao turno anterior.
## Mensagem pública também passa pelos guardrails de saída
Uma resposta criada em função de um bloqueio de entrada ainda é uma saída do agente. Portanto ela deve seguir o mesmo pipeline de validação de saída antes de chegar ao usuário.
Isso permite que `DLEX_OUT`, `PINJ`, `TOXOUT`, Output Supervisor e outras políticas removam ou sanitizem informação que não deva ser apresentada.
## Relação com `agent_graph.py`
Esta feature é responsabilidade da orquestração do template, porque define a precedência entre nós do grafo e o estado do turno.
Ao alterar `app/workflows/agent_graph.py`, preserve estas invariantes:
- `input_guardrails` antecede routing/tools;
- um bloqueio de input não executa ação transacional depois do bloqueio;
- resposta pública não é o `reason` bruto do guardrail;
- estado residual de routing/tools/judges não sobrevive como resultado do turno bloqueado;
- a resposta pública passa por `output_guardrails` antes da persistência/resposta.
A mesma semântica deve ser mantida nos templates oficiais e nas variantes equivalentes em `Tuning-Performance`.
## Troubleshooting
### O usuário recebe “Não consegui seguir com essa mensagem por regra de segurança” para uma frase apenas incompleta
Verifique:
1. qual guardrail retornou `allowed=false`;
2. se `COER` está sendo tratado como esclarecimento e não como bloqueio genérico;
3. se o caminho de bloqueio usa uma mensagem pública específica;
4. se o fallback genérico está sendo usado somente quando não existe tratamento específico.
### O metadata mostra tool executada mesmo com `route=blocked`
Verifique se o ramo de bloqueio limpa o estado transitório do turno antes de retornar a resposta. Confirme também se a tool não foi executada no mesmo turno antes do guardrail de entrada.
### A mensagem de bloqueio expõe detalhes internos
Não use `reason` diretamente como texto público. Gere a mensagem pública e deixe o `reason` apenas em observabilidade.
### A resposta de bloqueio ignora guardrails de saída
Verifique a aresta do grafo. O fluxo esperado é:
```text
input_guardrails bloqueou
→ construir resposta pública
→ output_guardrails
→ persist
```
não:
```text
input_guardrails bloqueou
→ persist
```
## Testes de regressão recomendados
Cubra pelo menos:
- `COER=false` gera solicitação de esclarecimento, não mensagem genérica de segurança;
- ramo bloqueado não conserva `mcp_results`/routing de turno anterior;
- nenhuma tool transacional é executada depois de um bloqueio de input;
- mensagem pública passa pelos guardrails de saída;
- guardrail desconhecido ainda possui fallback seguro.

View File

@@ -7,7 +7,7 @@ A documentação possui três níveis claros:
1. **Tutorial principal:** [`README.md`](../../../README.md) — criação, configuração, execução e teste de um agente do início ao fim.
2. **Arquitetura:** [01 — Arquitetura e Conceitos](./01_architecture_and_concepts.md) — componentes, responsabilidades e onde implementar cada coisa.
3. **Referências especializadas:** manuais `02` a `11` — implementação profunda e troubleshooting por capacidade.
3. **Referências especializadas:** manuais `02` a `12` — implementação profunda e troubleshooting por capacidade.
Se você está começando um novo agente, comece pelo `README.md`.
@@ -32,6 +32,9 @@ Se algo não está funcionando, use **Buscar pelo problema** abaixo.
| Recebo 401 entre gateway/backend/MCP | Basic Auth, credenciais por hop | [Gateways e Auth](./05_agent_gateway_mcp_gateway_and_auth.md) |
| Preciso decidir se algo pertence ao framework ou ao agente | boundary core/agente | [Arquitetura e Conceitos](./01_architecture_and_concepts.md) |
| Guardrail específico de um agente está quebrando outro | extensibilidade, imports de domínio no core | [Guardrails e Judges](./06_guardrails_judges_and_transaction_evaluation.md) |
| Uma frase incompleta recebe mensagem genérica de “regra de segurança” | feedback de input guardrail, `COER`, blocked-turn state | [Feedback de Guardrails de Entrada](./12_input_guardrail_feedback_and_blocked_turns.md) |
| `route=blocked` aparece junto com tools/resultados de outro turno | limpeza de estado do turno bloqueado | [Feedback de Guardrails de Entrada](./12_input_guardrail_feedback_and_blocked_turns.md) |
| Workflow conclui e gera protocolo, mas a resposta final vira mensagem de segurança | `expected_protocols`, `CMP`, `DLEX_OUT`, ordem de `output_guardrails` | [Guardrails e Judges](./06_guardrails_judges_and_transaction_evaluation.md) |
| Judge não roda em uma transação | sampling, `always_run_for_transactional`, sinais transacionais | [Guardrails e Judges](./06_guardrails_judges_and_transaction_evaluation.md) |
| Groundedness está avaliando sem contexto correto | RAG context, MCP evidence, judge inputs | [RAG/Grounding](./07_rag_business_context_and_grounding.md) |
| RAG não encontra conteúdo | provider, ingestão, embeddings, configuração | [RAG/Grounding](./07_rag_business_context_and_grounding.md) |
@@ -116,6 +119,12 @@ Se algo não está funcionando, use **Buscar pelo problema** abaixo.
**Use quando:** for necessário provar o caminho executado ou diagnosticar produção.
### [12 — Feedback de Guardrails de Entrada e Turnos Bloqueados](./12_input_guardrail_feedback_and_blocked_turns.md)
**O que é:** tratamento público de bloqueios de input, limpeza do estado do turno e validação da mensagem gerada pelos guardrails de saída.
**Use quando:** mensagens de bloqueio são genéricas, `COER` deveria pedir esclarecimento ou o metadata de um turno bloqueado contém routing/tools antigos.
### Tutorial principal
[`README.md`](../../../README.md) continua sendo a referência para o passo a passo completo: