mirror of
https://github.com/hoshikawa2/agent_platform_oci.git
synced 2026-09-07 10:13:46 +00:00
Ajustes conforme relatorio de testes 2026-08-27
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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.
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user