Ajustes conforme relatorio de testes 2026-08-27
This commit is contained in:
24
docs/FIX_CVAL_AMOUNT_AND_HOMONYM_RESOLUTION_20260829.md
Normal file
24
docs/FIX_CVAL_AMOUNT_AND_HOMONYM_RESOLUTION_20260829.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Correção CVAL: valores monetários e itens homônimos
|
||||
|
||||
## Problema
|
||||
|
||||
O CVAL removia todo ponto de valores textuais antes da conversão decimal. Assim, valores vindos de JSON/backend como `19.99` eram interpretados como `1999`, permitindo indevidamente ajustes como `29.98`.
|
||||
|
||||
Além disso, quando o mesmo `subject` aparecia mais de uma vez na fatura, a validação escolhia a primeira ocorrência após a ordenação estrutural, sem usar o valor solicitado para desambiguar a cobrança correta.
|
||||
|
||||
## Correção
|
||||
|
||||
1. `_parse_amount()` agora reconhece formatos decimais e de agrupamento comuns, incluindo `19.99`, `R$ 19,99`, `1.999,99` e `1,999.99`.
|
||||
2. Quando existem múltiplos candidatos com o mesmo nome, o CVAL usa o valor solicitado como evidência:
|
||||
- prefere correspondência exata;
|
||||
- para ajuste parcial, escolhe a menor ocorrência que comporte o valor solicitado;
|
||||
- se nenhuma ocorrência comportar o valor, usa a maior ocorrência para que a regra genérica `validated > item_amount` bloqueie a solicitação.
|
||||
|
||||
Nenhuma regra específica para "dobro", "triplo" ou percentual foi adicionada.
|
||||
|
||||
## Regressões cobertas
|
||||
|
||||
- `19.99` permanece `19.99`;
|
||||
- `R$ 19,99` vira `19.99`;
|
||||
- `Tamboro Mensal` em `14.99` e `19.99` + solicitação `19.99` resolve a ocorrência correta;
|
||||
- `Tamboro Mensal` em `14.99` e `19.99` + solicitação `29.98` é bloqueada com `valor_ajuste_maior_que_item`.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Correção: precedência de parâmetros sobre semantic intent shift
|
||||
|
||||
## Problema
|
||||
|
||||
Durante uma transação ativa em `COLLECTING_PARAMETERS`, o roteador executava o
|
||||
`semantic_classifier` de mudança de intenção **antes** da extração dos parâmetros
|
||||
quando `ENABLE_LLM_ROUTER=true`. Com isso, respostas referenciais válidas, como
|
||||
`"a de quatorze e noventa e nove"`, podiam ser roubadas por outra intent
|
||||
semanticamente plausível antes de o contrato da transação tentar consumi-las.
|
||||
|
||||
## Regra restaurada
|
||||
|
||||
A ordem agora é:
|
||||
|
||||
1. `AWAITING_CONFIRMATION`: confirmação explícita continua com precedência absoluta.
|
||||
2. `COLLECTING_PARAMETERS`: tentar primeiro extrair pelo menos um parâmetro pendente.
|
||||
3. Se algum parâmetro for consumido, manter a transação e **não** executar intent shift.
|
||||
4. Somente quando nenhum parâmetro for consumido, avaliar `semantic_classifier` para
|
||||
`CONTINUE`/`SHIFT`.
|
||||
5. Um novo objetivo explícito continua podendo mudar a intenção, desde que o extrator
|
||||
corretamente não o converta em parâmetro da transação anterior.
|
||||
|
||||
## Resolução contextual
|
||||
|
||||
O extrator do roteador agora recebe um contexto conversacional recente e limitado,
|
||||
apenas como auxílio não-autoritativo para resolver referências. Exemplo: se o histórico
|
||||
recente contém `Tamboro Mensal = R$ 14,99`, a fala `"a de 14,99"` pode produzir o
|
||||
candidato `subject=Tamboro Mensal`. A validação/pre-validation da transação continua
|
||||
sendo responsável por provar a entidade contra evidência de backend/MCP antes da
|
||||
confirmação ou execução.
|
||||
|
||||
## Arquivo principal alterado
|
||||
|
||||
- `agent_framework_oci/libs/agent_framework/src/agent_framework/routing/enterprise_router.py`
|
||||
|
||||
## Testes
|
||||
|
||||
Foram atualizados/adicionados testes em:
|
||||
|
||||
- `agent_framework_oci/tests/test_transaction_parameter_llm_precedence.py`
|
||||
|
||||
Validação executada:
|
||||
|
||||
- 8/8 testes do arquivo de precedência passaram.
|
||||
- 81/81 testes combinados de transaction routing, state interruption, contextual reentry,
|
||||
expected input semantic classifier e route stickiness passaram.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Correção: valor já coletado pode ser corrigido durante COLLECTING_PARAMETERS
|
||||
|
||||
## Problema
|
||||
|
||||
Uma transação podia estar em `COLLECTING_PARAMETERS` com um campo obrigatório já preenchido em turno anterior (por exemplo `valor=19.99`) e outro ainda pendente (`subject`). Se o cliente corrigisse o valor no mesmo turno em que identificava o item — por exemplo `desculpa, é a de quatorze e noventa e nove` — o runtime enviava ao extrator LLM apenas os parâmetros ainda ausentes. Assim, `valor` ficava fora do contrato editável do turno e permanecia congelado em `19.99`.
|
||||
|
||||
Isso gerava estados inconsistentes como `resolved_value=14.99` e `valor=19.99`, fazendo a contestação executar com o valor antigo.
|
||||
|
||||
## Regra corrigida
|
||||
|
||||
Enquanto a transação estiver em `COLLECTING_PARAMETERS`, o extrator transacional recebe o conjunto completo de `policy.requires` como campos editáveis do turno. A LLM continua autorizada a devolver somente valores realmente presentes/inequívocos na fala atual. O merge mantém os valores antigos para campos não citados e sobrescreve apenas as chaves efetivamente extraídas.
|
||||
|
||||
Precedência resultante:
|
||||
|
||||
1. fala atual explicitamente corrige/preenche required field;
|
||||
2. valor previamente coletado é preservado apenas se a fala atual não o alterar;
|
||||
3. parâmetros ainda ausentes continuam sendo coletados;
|
||||
4. somente depois disso é avaliada mudança de intenção.
|
||||
|
||||
## Caso de regressão coberto
|
||||
|
||||
Estado anterior:
|
||||
|
||||
- `valor=19.99`
|
||||
- `subject` pendente
|
||||
|
||||
Mensagem atual:
|
||||
|
||||
- `desculpa, é a de quatorze e noventa e nove`
|
||||
|
||||
Router/contexto resolve:
|
||||
|
||||
- `subject=Tamboro Mensal`
|
||||
|
||||
Extrator do runtime corrige:
|
||||
|
||||
- `valor=14.99`
|
||||
|
||||
Resultado esperado antes da confirmação:
|
||||
|
||||
- `subject=Tamboro Mensal`
|
||||
- `valor=14.99`
|
||||
|
||||
## Testes
|
||||
|
||||
Foram executados:
|
||||
|
||||
- 46 testes de runtime/roteamento/parâmetros transacionais;
|
||||
- 27 testes de migração ligados a contestação/CVAL/paridade.
|
||||
|
||||
Todos passaram.
|
||||
@@ -33,7 +33,7 @@ A suíte completa do projeto após as mudanças executa **715 testes com sucesso
|
||||
| `enviar_sms` | Ação de integração usada pelos workflows | OK | Mantida integração TIM; continua sem regra de negócio dentro do framework. |
|
||||
| `recuperar_fatura_pdf` | SecurePDF/invoice recover no original | Corrigida | Agora participa do `InvoiceContextService` para obter `customer_id` antes do SecurePDF quando não vier explicitamente. |
|
||||
| `pro_rata` | Workflow v3; regra explícita de exatamente 2 planos e `has_plano_controle` | Corrigida | O MCP deriva os planos da evidência do Bill PDF/billing analysis, deduplica repetição DANFE/linha e falha fechado se não houver exatamente dois planos. `has_plano_controle` é determinístico. |
|
||||
| `termino_desconto` | Capability/backend existente; mensagem específica sobre fim de fidelidade | Corrigida | Restaurada a semântica/mensagem original, incluindo plano, final da linha, fim do período promocional e retorno ao preço original. |
|
||||
| `termino_desconto` | Capability/backend existente; versão migrada havia cristalizado causa sem evidência | Corrigida + hardening | A causa só é afirmada quando backend/mock fornece evidência causal explícita de desconto/promoção. Sem essa prova, responde que o motivo não está disponível; parcelas e texto do cliente não viram fato. |
|
||||
| `valor_divergente` | Capability/backend existente | Corrigida | Restaurada a semântica original de alteração no valor do plano por linha, em vez de texto genérico sobre billing analysis. |
|
||||
| `retomar_workflow` | Resume era interno ao runtime/executor original | OK arquiteturalmente | Exposto como façade genérica do `WorkflowRuntime.aresume`; não replica estado conversacional dentro do domínio Contas. |
|
||||
|
||||
@@ -100,3 +100,13 @@ O novo arquivo `tests/migration/test_requested_tools_parity_pente_fino.py` verif
|
||||
```
|
||||
|
||||
A suíte completa disponível no pacote foi executada, não apenas os testes novos.
|
||||
|
||||
## Histórico autoritativo de descontos
|
||||
|
||||
A capability `termino_desconto` não deve deduzir a causa da retirada de desconto a partir de parcelas, ausência do item ou texto do cliente. Foi introduzida a integração MCP `consultar_historico_descontos`, com mock em `app/domain/contas/fixtures/discount_history.json`.
|
||||
|
||||
O serviço retorna fatos estruturados (`discount_name`, `plan_name`, `previous_value`, `current_value`, `start_date`, `end_date`, `discount_status`, `termination_reason` e `termination_reason_description`). O workflow `termino_desconto` chama essa fonte obrigatoriamente antes de compor a resposta. Se a causa não estiver explícita, mantém `epistemic_status=insufficient_evidence`; quando a causa está presente, usa `epistemic_status=grounded_fact`.
|
||||
|
||||
A referência temporal do mock é explícita: `current_value` representa a situação contratual em `as_of_date`, enquanto `last_billed_discount_value` representa o desconto aplicado no último período faturado (`last_billed_period`). Isso evita tratar como contradição o caso em que uma fatura referente a período anterior ainda contém o desconto, embora o benefício já esteja encerrado na data contratual corrente.
|
||||
|
||||
O mock serve somente para ilustrar o contrato que deverá ser substituído pela integração real. A resposta ao cliente é composta no agente; o serviço não retorna frase pronta.
|
||||
|
||||
302
docs/RELATORIO_CORRECOES_FRAMEWORK_E_CONTAS_2026-08-28.md
Normal file
302
docs/RELATORIO_CORRECOES_FRAMEWORK_E_CONTAS_2026-08-28.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# Relatório de Correções — Agent Framework OCI + Contas
|
||||
|
||||
Data: 2026-08-28
|
||||
Base analisada: `agent_contas_oci_template (6).zip`
|
||||
|
||||
## 1. Objetivo
|
||||
|
||||
Este trabalho tratou as frentes técnicas identificadas a partir do comparativo de 30 replays e, principalmente, dos contratos de regressão já existentes no próprio projeto. A separação arquitetural foi preservada:
|
||||
|
||||
- **Framework**: lifecycle transacional, coleta genérica de parâmetros, confirmação, snapshot, roteamento/continuidade, guardrails e infraestrutura horizontal.
|
||||
- **Agent Contas / domínio / MCP**: semântica TIM, prompts voltados ao cliente, contrato das capabilities, evidência de fatura, regras de contestação, pró-rata e mapeamentos de integração.
|
||||
|
||||
Nenhuma regra TIM foi movida para o core do framework.
|
||||
|
||||
## 2. Correções realizadas no framework
|
||||
|
||||
### 2.1 Coleta de parâmetros sem expor nomes internos
|
||||
|
||||
**Problema**
|
||||
O runtime possuía um pequeno dicionário hardcoded para `order_id`, `reason` e `customer_id` e, para qualquer outro parâmetro, podia produzir o nome técnico convertido para texto. Isso explica respostas da família `informe subject` apontadas no relatório.
|
||||
|
||||
**Correção**
|
||||
O framework agora usa metadados declarados pelo agente em `args_schema`:
|
||||
|
||||
- `user_prompt`: pergunta exata voltada ao cliente, com maior prioridade;
|
||||
- `label`: rótulo amigável opcional;
|
||||
- `description`: fallback semântico;
|
||||
- sem metadados: pergunta neutra que **não expõe o nome técnico**.
|
||||
|
||||
Além disso, o framework pergunta **um parâmetro por vez**, embora o extrator LLM continue capaz de consumir vários valores espontaneamente informados no mesmo turno.
|
||||
|
||||
**Resultado arquitetural**
|
||||
O framework continua sem saber o significado de `subject`, `valor`, `order_id` etc. A semântica pertence ao agente.
|
||||
|
||||
### 2.2 Snapshot imutável da confirmação
|
||||
|
||||
**Problema**
|
||||
Havia `pending_tool_call` e `active_transaction`, mas não existia um snapshot separado e explícito que representasse exatamente a operação apresentada ao usuário no momento da confirmação.
|
||||
|
||||
**Correção**
|
||||
Foi introduzido `confirmation_snapshot`, contendo:
|
||||
|
||||
- `transaction_id`;
|
||||
- `tool_name`;
|
||||
- cópia dos `arguments`;
|
||||
- `started_from_intent`.
|
||||
|
||||
Ao entrar em `AWAITING_CONFIRMATION`, o snapshot é congelado. Um `sim` executa **esse snapshot**, mesmo que `active_transaction`, `pending_tool_call` ou outro contexto seja alterado depois. Ao concluir/cancelar a transação, o snapshot operacional é limpo.
|
||||
|
||||
**Benefício**
|
||||
Garante o contrato:
|
||||
|
||||
> confirmar = executar exatamente tool + parâmetros que estavam congelados quando a confirmação foi solicitada.
|
||||
|
||||
### 2.3 Itens do framework já presentes nesta versão e apenas revalidados
|
||||
|
||||
Não foram duplicadas correções que já estavam na base recebida:
|
||||
|
||||
- extração LLM de parâmetros transacionais;
|
||||
- precedência de confirmação explícita;
|
||||
- `transaction_interruption=intent_shift`;
|
||||
- encerramento/limpeza de transações `COMPLETED`, `FAILED`, `CANCELLED`, `BLOCKED`, `OUT_OF_SCOPE`;
|
||||
- route stickiness sem reaproveitar transação terminal;
|
||||
- replay pós-finalização sem reabrir atendimento;
|
||||
- validação direta de `expected_protocols` no CMP;
|
||||
- isolamento do contexto operacional dos guardrails após intent shift no Contas.
|
||||
|
||||
## 3. Correções realizadas no Agent Contas / MCP
|
||||
|
||||
### 3.1 Prompts declarativos dos parâmetros
|
||||
|
||||
Foram adicionados `user_prompt` às capabilities transacionais:
|
||||
|
||||
- `cancelar_vas_avulso.subject` → `Qual serviço você deseja cancelar?`
|
||||
- `tratar_vas_estrategico.subject` → `Qual serviço ou benefício você deseja tratar?`
|
||||
- `validar_contestacao.subject` → `Qual cobrança ou item você não reconhece?`
|
||||
- `validar_contestacao.valor` → `Qual é o valor da cobrança?`
|
||||
- `contestar_cobranca.subject` → `Qual cobrança ou item você deseja contestar?`
|
||||
- `contestar_cobranca.valor` → `Qual é o valor da cobrança que você deseja contestar?`
|
||||
|
||||
Assim, a linguagem de atendimento fica no domínio e o framework apenas executa o contrato.
|
||||
|
||||
### 3.2 Capability `buscar_informacao` restaurada sem duplicar RAG
|
||||
|
||||
A capability voltou a existir no registry/MCP para manter paridade de contrato, mas não reimplementa recuperação no domínio.
|
||||
|
||||
Ela devolve um contrato explícito:
|
||||
|
||||
- `requires_rag=true`;
|
||||
- `source=agent_framework.rag`;
|
||||
- `rag_queries=[...]`.
|
||||
|
||||
Portanto, a API antiga é preservada e o RAG continua sendo responsabilidade do framework.
|
||||
|
||||
### 3.3 `invoice_explanation` preserva evidência suficiente para composição
|
||||
|
||||
O retorno passa a preservar também:
|
||||
|
||||
- `invoice_detail`;
|
||||
- `invoice_amount`;
|
||||
- `invoice_period`;
|
||||
- `invoice_emissao`.
|
||||
|
||||
A action `formatar_invoice_explanation` agora sinaliza:
|
||||
|
||||
- `await_user_input=true`;
|
||||
- `requires_llm_composition=true`;
|
||||
- `response_instruction` de composição grounded;
|
||||
- preservação da pergunta `Com essa explicação, sanei sua dúvida?`.
|
||||
|
||||
### 3.4 Pró-rata determinístico e fail-closed
|
||||
|
||||
Foram restaurados helpers de preparação do pró-rata:
|
||||
|
||||
- derivação determinística dos planos a partir do PDF parseado;
|
||||
- uso da visão contratual por linha, evitando confundir DANFE com plano consolidado;
|
||||
- identificação de plano controle;
|
||||
- exigência de **exatamente dois planos**;
|
||||
- falha fechada com `requires_exactly_two_plans` quando o contrato não é atendido.
|
||||
|
||||
Nenhum LLM é usado nessa decisão.
|
||||
|
||||
### 3.5 CVAL aplicado também na pré-validação
|
||||
|
||||
`validar_contestacao` deixou de apenas aceitar o item após o preflight e passou a executar a mesma validação CVAL usada antes do efeito financeiro.
|
||||
|
||||
A validação usa:
|
||||
|
||||
- item resolvido;
|
||||
- **valor originalmente solicitado pelo cliente**;
|
||||
- evidência de `billing_analysis`;
|
||||
- `validation_log` estruturado.
|
||||
|
||||
Valor solicitado acima do valor comprovado é bloqueado com `reason=CVAL` e erro `valor_ajuste_maior_que_item`.
|
||||
|
||||
Foi corrigido também um teste de regressão inconsistente: ele exigia aprovar R$ 50 para um item comprovado em R$ 10, ao mesmo tempo em que dizia proteger a regra “valor não pode exceder o item”. O caso positivo foi ajustado para R$ 10; a implementação não foi enfraquecida para satisfazer uma expectativa insegura.
|
||||
|
||||
### 3.6 Grounding de término de desconto e valor divergente
|
||||
|
||||
`termino_desconto` foi endurecido para não transformar uma hipótese de negócio em fato. O workflow só informa causa de retirada/término quando a evidência de backend/mock contém um campo causal explicitamente associado a desconto/promoção (por exemplo `discount_reason`, `terminationReason`, status de desconto/promoção encerrado ou data de término registrada). Contadores como `1/12`, `8/12` ou `12/12`, ausência de desconto na fatura e o próprio texto do cliente não são tratados como prova de expiração.
|
||||
|
||||
Quando a causa não está disponível, a resposta informa que os dados existentes não registram o motivo, sem afirmar fim de fidelidade ou expiração promocional.
|
||||
|
||||
`valor_divergente` preserva a semântica de alteração do valor do plano e referência segura ao final da linha, conforme contrato de regressão.
|
||||
|
||||
### 3.7 Status de solicitação não usa `interaction_key` como protocolo
|
||||
|
||||
Foi removido:
|
||||
|
||||
`interaction_key -> protocol`
|
||||
|
||||
O protocolo agora é extraído explicitamente da mensagem, impedindo que `message_id`/`interaction_key` seja tratado como protocolo de atendimento.
|
||||
|
||||
### 3.8 Finalização exige status explícito
|
||||
|
||||
`finalizar_atendimento` agora declara `status` em `requires`, e o mapping possui extração explícita do campo. Isso preserva o contrato de domínio e evita finalização sem estado definido.
|
||||
|
||||
### 3.9 Prompt de billing mais grounded
|
||||
|
||||
O `FaturasAgent` recebeu regra explícita para não transformar ausência de evidência em hipótese factual. Sem evidência, ele não pode afirmar como causa:
|
||||
|
||||
- fim de promoção;
|
||||
- perda de elegibilidade;
|
||||
- alteração de consumo;
|
||||
- reajuste tarifário;
|
||||
- mudança de plano.
|
||||
|
||||
Isso endereça diretamente o comportamento observado no comparativo, em que hipóteses eram apresentadas como explicação.
|
||||
|
||||
### 3.10 Prompt de suporte não simula efeitos de lifecycle
|
||||
|
||||
O `SuporteContasAgent` foi reforçado para não anunciar em texto livre:
|
||||
|
||||
- transferência;
|
||||
- encerramento;
|
||||
- protocolo;
|
||||
- sucesso operacional.
|
||||
|
||||
Resultados terminais devem refletir apenas o estado/tool atual. Handoff e finalização continuam controlados pela orquestração.
|
||||
|
||||
## 4. Fontes alterados
|
||||
|
||||
### Framework
|
||||
|
||||
| Arquivo | Alteração |
|
||||
|---|---|
|
||||
| `agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py` | Prompt declarativo de parâmetros; remoção de labels hardcoded; pergunta neutra sem leak; `confirmation_snapshot`; execução a partir do snapshot; limpeza do snapshot no lifecycle. |
|
||||
| `agent_framework_oci/libs/agent_framework/build/lib/agent_framework/runtime/agent_runtime.py` | Sincronizado com o source para manter o artefato de build consistente. |
|
||||
| `agent_framework_oci/tests/test_transactional_tool_flow.py` | Regressões para `user_prompt`, ausência de leak de nome técnico e confirmação por snapshot imutável. |
|
||||
|
||||
### Agent Contas / MCP
|
||||
|
||||
| Arquivo | Alteração |
|
||||
|---|---|
|
||||
| `config/tools.yaml` | `user_prompt` dos parâmetros; capability `buscar_informacao`; `finalizar_atendimento.status` obrigatório. |
|
||||
| `config/mcp_parameter_mapping.yaml` | Protocolo deixa de vir de `interaction_key`; extração explícita de `protocol`; extração explícita de `status` na finalização. |
|
||||
| `config/prompts/billing.yaml` | Proibição explícita de hipóteses causais sem evidência. |
|
||||
| `config/prompts/support.yaml` | Não simular handoff/finalização/protocolo; tratamento terminal grounded. |
|
||||
| `app/domain/contas/service.py` | `buscar_informacao`; preservação de `invoice_detail`, amount, period e emissão em `invoice_explanation`. |
|
||||
| `app/domain/contas/workflow_actions.py` | Metadados de composição LLM/await no invoice explanation; semântica de `termino_desconto` e `valor_divergente`. |
|
||||
| `contas_mcp/servers/contas_mcp_server/main.py` | Registro `buscar_informacao`; helpers de pró-rata; preparação fail-closed; CVAL na pré-validação; dispatch das novas/restauradas capabilities. |
|
||||
| `tests/migration/test_framework_agent_gap_fixes.py` | Novos contratos de regressão framework × agente. |
|
||||
| `tests/migration/test_requested_tools_parity_pente_fino.py` | Correção do caso positivo CVAL inconsistente (R$50 → R$10 comprovados). |
|
||||
|
||||
## 5. Validação executada
|
||||
|
||||
### Framework — testes focados das frentes alteradas
|
||||
|
||||
Resultado:
|
||||
|
||||
`40 passed`
|
||||
|
||||
Incluiu:
|
||||
|
||||
- transaction tool flow;
|
||||
- confirmação voltada ao cliente;
|
||||
- extração LLM/prevalência de parâmetros;
|
||||
- route stickiness / intent shift;
|
||||
- novos testes de snapshot e user-facing parameter contract.
|
||||
|
||||
### Agent Contas — regressão completa de migração
|
||||
|
||||
Resultado final:
|
||||
|
||||
`729 passed`
|
||||
|
||||
Antes das correções, o `test_requested_tools_parity_pente_fino.py` expunha 11 falhas. Após as correções:
|
||||
|
||||
`14 passed` nesse arquivo e `729 passed` em toda `tests/migration`.
|
||||
|
||||
### Suíte completa do framework
|
||||
|
||||
Resultado observado na árvore corrigida:
|
||||
|
||||
- `225 passed`
|
||||
- `10 failed`
|
||||
|
||||
Os mesmos 10 casos foram executados contra o ZIP original recebido e falham da mesma forma. Portanto são **falhas preexistentes e não introduzidas por este patch**. Estão concentradas em:
|
||||
|
||||
- compatibilidade de double de LLM em um teste unitário;
|
||||
- checkpoint repository/recovery;
|
||||
- compact telemetry Langfuse legado;
|
||||
- transactional workflow unit tests;
|
||||
- dois testes estáticos que procuram um layout de `agent_template_backend` inexistente nesse caminho.
|
||||
|
||||
Esses itens não pertencem às frentes do comparativo tratadas neste patch e não foram mascarados.
|
||||
|
||||
## 6. Relação com o relatório comparativo
|
||||
|
||||
### Problemas do relatório atacados diretamente
|
||||
|
||||
- nomes internos de parâmetros na fala;
|
||||
- coleta transacional sem contrato amigável;
|
||||
- confirmação sem snapshot explícito;
|
||||
- risco de reinterpretar argumentos depois do pedido de confirmação;
|
||||
- explicação de cobrança baseada em hipótese sem evidência;
|
||||
- gaps de capability/paridade MCP já formalizados pelos testes do projeto;
|
||||
- pró-rata sem preparação determinística completa;
|
||||
- pre-validation/CVAL incompleta;
|
||||
- confusão entre identificador de interação e protocolo;
|
||||
- finalização sem `status` obrigatório.
|
||||
|
||||
### Problemas que já estavam corrigidos nesta versão recebida
|
||||
|
||||
- intent shift durante transação;
|
||||
- limpeza de transação terminal;
|
||||
- replay pós-finalização;
|
||||
- barge-in pós-finalização no framework de interrupção;
|
||||
- `expected_protocols`/CMP;
|
||||
- contexto histórico de transação anterior nos guardrails do Contas.
|
||||
|
||||
## 7. Pontos que continuam sendo política de negócio do Contas
|
||||
|
||||
Não foram movidos para o framework, de propósito:
|
||||
|
||||
- escada comercial de retenção TIM;
|
||||
- quando exatamente transferir para humano após retenção;
|
||||
- primeira/segunda ocorrência de fora de escopo;
|
||||
- escalonamento jurídico/Anatel específico TIM;
|
||||
- política de ressarcimento em dobro;
|
||||
- interpretação de conjuntos de cobranças como “nenhuma delas/todas”;
|
||||
- regras específicas de VAS avulso/estratégico e ações comerciais.
|
||||
|
||||
Esses comportamentos devem ser implementados/testados no domínio Contas quando os cenários executáveis correspondentes estiverem disponíveis. O pacote recebido não contém os 30 YAMLs de replay citados no PDF, portanto este relatório **não afirma** que os 30 replays agora passam; afirma apenas os resultados das suítes efetivamente presentes e executadas no pacote.
|
||||
|
||||
## 8. Conclusão
|
||||
|
||||
A principal correção estrutural foi tornar a fronteira mais clara:
|
||||
|
||||
- o **framework** controla coleta, lifecycle e confirmação sem expor nomes internos e sem reinterpretar o que foi confirmado;
|
||||
- o **agente Contas** fornece a linguagem de negócio e os contratos/evidências específicos;
|
||||
- o **MCP Contas** mantém capabilities e validações determinísticas de domínio sem absorver responsabilidades de conversa/RAG do framework.
|
||||
|
||||
A regressão do Contas presente no projeto ficou integralmente verde (`729 passed`).
|
||||
|
||||
### Serviço MCP de histórico de descontos
|
||||
|
||||
Foi adicionada a tool interna `consultar_historico_descontos` para representar a fonte autoritativa de status e término de descontos. O mock está em `app/domain/contas/fixtures/discount_history.json` e a implementação em `contas_mcp/servers/contas_mcp_server/discount_history_service.py`.
|
||||
|
||||
`termino_desconto` consulta esse serviço obrigatoriamente e só verbaliza uma causa quando `termination_reason`, `termination_reason_description` ou outro campo causal explicitamente permitido estiver presente. Códigos técnicos permanecem em metadados; a resposta usa a descrição legível do sistema. Sem causa explícita, o fluxo continua fail-closed.
|
||||
|
||||
O mock também distingue explicitamente a **situação contratual na data de referência** da **última fatura emitida**. No cenário atual, `current_value=0` significa valor contratual do desconto em `as_of_date=2025-11-20`; a última fatura cobre `14/10 a 13/11` e ainda registra R$ 80,00 de desconto. Isso é temporalmente consistente: o desconto estava vigente no período faturado e aparece como encerrado na situação contratual de 20/11/2025. Os campos `last_billed_discount_value`, `last_billed_period`, `last_invoice_issue_date` e `current_value_reference` documentam essa diferença.
|
||||
423
docs/RELATORIO_POLITICAS_DE_LINHA_ALT1_ALT2.md
Normal file
423
docs/RELATORIO_POLITICAS_DE_LINHA_ALT1_ALT2.md
Normal file
@@ -0,0 +1,423 @@
|
||||
# Relatório técnico — políticas alternativas de operação por linha
|
||||
|
||||
## 1. Objetivo
|
||||
|
||||
O Agent Contas possui duas políticas alternativas para controlar operações em uma linha (MSISDN) diferente da linha identificada/autenticada no início do atendimento.
|
||||
|
||||
A implementação permanece no **Agent Contas/MCP Contas**, sem regra TIM hardcoded no core do `agent_framework_oci`.
|
||||
|
||||
A política ativa entregue no projeto continua sendo a **ALT1 — somente a linha autenticada**.
|
||||
|
||||
A ALT2 foi evoluída para não inferir autorização a partir de fatura, billing ou texto do cliente. Ela depende de uma fonte explícita de autorização: a nova tool MCP mock `consultar_linhas_autorizadas`.
|
||||
|
||||
## 2. Políticas disponíveis
|
||||
|
||||
### 2.1 ALT1 — `authenticated_line_only` — PADRÃO
|
||||
|
||||
Fonte:
|
||||
|
||||
```text
|
||||
contas_mcp/servers/contas_mcp_server/line_policy_alt1.py
|
||||
```
|
||||
|
||||
Comportamento:
|
||||
|
||||
- a linha operacional continua sendo a linha identificada pelo `business_context` da chamada;
|
||||
- uma linha citada em texto livre **não substitui** a identidade da sessão;
|
||||
- se o cliente mencionar explicitamente outra linha — número completo ou referência como `final 4321` — a execução é bloqueada antes de qualquer operação de domínio;
|
||||
- o bloqueio é terminal para o turno e interrompe as tools seguintes;
|
||||
- a mensagem devolvida é:
|
||||
|
||||
```text
|
||||
Por segurança, este atendimento só permite consultar ou realizar operações na linha identificada na chamada. Não posso usar outra linha informada na conversa.
|
||||
```
|
||||
|
||||
Exemplo:
|
||||
|
||||
```text
|
||||
linha autenticada: 11999999999
|
||||
cliente: "quero cancelar o streaming do número da minha esposa, final quatro três dois um"
|
||||
|
||||
resultado:
|
||||
LINE_POLICY_BLOCKED / other_line_not_allowed
|
||||
nenhuma consulta/cancelamento é executado para a outra linha
|
||||
```
|
||||
|
||||
### 2.2 ALT2 — `authorized_related_lines`
|
||||
|
||||
Fonte:
|
||||
|
||||
```text
|
||||
contas_mcp/servers/contas_mcp_server/line_policy_alt2.py
|
||||
```
|
||||
|
||||
Comportamento:
|
||||
|
||||
- a linha autenticada continua sendo a origem de confiança;
|
||||
- uma outra linha só pode ser usada se for retornada pelo serviço explícito `consultar_linhas_autorizadas`;
|
||||
- referências como `final 4321` são resolvidas somente contra as linhas autorizadas retornadas por esse serviço;
|
||||
- se houver exatamente uma correspondência, ela vira o `effective_msisdn` da operação;
|
||||
- se não houver correspondência, a operação é bloqueada;
|
||||
- se houver mais de uma correspondência, o fluxo exige esclarecimento;
|
||||
- se o serviço de linhas autorizadas falhar, o ALT2 opera em **fail-closed**: somente a linha autenticada permanece autorizada;
|
||||
- a presença de um MSISDN em `invoice_detail`, `billing_analysis` ou outra evidência de cobrança **não concede autorização operacional**;
|
||||
- um número pronunciado pelo cliente também **não concede autorização**.
|
||||
|
||||
Exemplo:
|
||||
|
||||
```text
|
||||
linha autenticada: 11999999999
|
||||
consultar_linhas_autorizadas retorna:
|
||||
- 11999999999 (titular)
|
||||
- 11988884321 (dependente autorizado)
|
||||
|
||||
cliente: "quero cancelar o TIM Fashion da linha final 4321"
|
||||
|
||||
resultado ALT2:
|
||||
requested reference = 4321
|
||||
effective_msisdn = 11988884321
|
||||
operação pode prosseguir nessa linha
|
||||
```
|
||||
|
||||
## 3. Novo serviço MCP mock — `consultar_linhas_autorizadas`
|
||||
|
||||
### 3.1 Objetivo
|
||||
|
||||
Foi criada uma tool MCP side-effect-free para representar a integração que, em produção, deve consultar um serviço de identidade/conta e responder **quais linhas o atendimento autenticado está autorizado a operar**.
|
||||
|
||||
Tool:
|
||||
|
||||
```text
|
||||
consultar_linhas_autorizadas
|
||||
```
|
||||
|
||||
Registro MCP:
|
||||
|
||||
```text
|
||||
contas_mcp/servers/contas_mcp_server/main.py
|
||||
```
|
||||
|
||||
Implementação mock:
|
||||
|
||||
```text
|
||||
contas_mcp/servers/contas_mcp_server/authorized_lines_service.py
|
||||
```
|
||||
|
||||
Fixture mock:
|
||||
|
||||
```text
|
||||
app/domain/contas/fixtures/authorized_lines.json
|
||||
```
|
||||
|
||||
### 3.2 Contrato de entrada
|
||||
|
||||
A consulta parte da identidade já autenticada no atendimento. O cliente não informa qual linha deve ser autorizada.
|
||||
|
||||
Exemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"msisdn": "11999999999",
|
||||
"customer_key": "11999999999",
|
||||
"contract_key": "3000131180"
|
||||
}
|
||||
```
|
||||
|
||||
O `msisdn` acima é a linha autenticada/original da chamada.
|
||||
|
||||
### 3.3 Contrato de saída mock
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"status": "SUCCESS",
|
||||
"source": "mock",
|
||||
"authenticated_msisdn": "11999999999",
|
||||
"authorized_lines": [
|
||||
{
|
||||
"msisdn": "11999999999",
|
||||
"relationship": "titular",
|
||||
"status": "ACTIVE",
|
||||
"authorized": true
|
||||
},
|
||||
{
|
||||
"msisdn": "11988884321",
|
||||
"relationship": "dependente",
|
||||
"status": "ACTIVE",
|
||||
"authorized": true
|
||||
}
|
||||
],
|
||||
"authorized_msisdns": [
|
||||
"11999999999",
|
||||
"11988884321"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Por que existe uma tool MCP separada
|
||||
|
||||
O objetivo é deixar explícita a arquitetura de produção:
|
||||
|
||||
```text
|
||||
identidade autenticada da chamada
|
||||
↓
|
||||
consultar_linhas_autorizadas
|
||||
↓
|
||||
serviço legado/CRM/IAM/conta
|
||||
↓
|
||||
lista de linhas realmente autorizadas
|
||||
↓
|
||||
line_policy_alt2
|
||||
↓
|
||||
resolve referência conversacional
|
||||
↓
|
||||
0 matches → bloqueia/clarifica
|
||||
1 match → effective_msisdn
|
||||
>1 matches → clarifica
|
||||
```
|
||||
|
||||
A autorização não pertence ao LLM. O LLM/text extractor pode interpretar `final 4321`, mas não decide se `4321` é uma linha autorizada.
|
||||
|
||||
### 3.5 Comportamento em produção
|
||||
|
||||
O arquivo `authorized_lines_service.py` é propositalmente um mock de referência. Em produção, ele deve ser substituído por um adapter que consulte o serviço corporativo responsável pela relação titular/dependentes/linhas autorizadas.
|
||||
|
||||
O contrato recomendado deve preservar pelo menos:
|
||||
|
||||
```text
|
||||
success
|
||||
authenticated_msisdn
|
||||
authorized_lines[].msisdn
|
||||
authorized_lines[].status
|
||||
authorized_lines[].authorized
|
||||
authorized_lines[].relationship
|
||||
authorized_msisdns
|
||||
```
|
||||
|
||||
Se a integração real falhar ou não puder provar a autorização da outra linha, o comportamento esperado do ALT2 é fail-closed.
|
||||
|
||||
## 4. Fluxo ALT2 atualizado
|
||||
|
||||
O fluxo completo ficou:
|
||||
|
||||
```text
|
||||
mensagem do cliente
|
||||
↓
|
||||
extrai requested_line_reference
|
||||
ex.: suffix=4321
|
||||
↓
|
||||
business_context mantém 11999999999
|
||||
↓
|
||||
MCP detecta ALT2 ativo
|
||||
↓
|
||||
consultar_linhas_autorizadas(11999999999)
|
||||
↓
|
||||
authorized_lines_evidence
|
||||
↓
|
||||
line_policy_alt2
|
||||
↓
|
||||
resolve 4321 somente contra authorized_lines_evidence
|
||||
↓
|
||||
effective_msisdn = 11988884321
|
||||
↓
|
||||
só então a tool/workflow de negócio é executada
|
||||
```
|
||||
|
||||
A ALT2 não usa mais `invoice_detail`, `billing_analysis` ou `complete_invoices_payload` como fonte de **autorização** de linha.
|
||||
|
||||
## 5. Política ativa
|
||||
|
||||
O MCP importa sempre:
|
||||
|
||||
```text
|
||||
contas_mcp/servers/contas_mcp_server/line_policy.py
|
||||
```
|
||||
|
||||
No pacote entregue, `line_policy.py` é uma cópia exata de `line_policy_alt1.py`.
|
||||
|
||||
Portanto, **o comportamento corrente permanece bloqueando operações em outra linha**.
|
||||
|
||||
O `/health` informa a política carregada:
|
||||
|
||||
```json
|
||||
{
|
||||
"line_policy": "authenticated_line_only",
|
||||
"line_policy_description": "Somente a linha identificada/autenticada na chamada pode ser consultada ou alterada."
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Como ativar ALT1
|
||||
|
||||
Forma recomendada:
|
||||
|
||||
```bash
|
||||
python scripts/select_line_policy.py alt1
|
||||
```
|
||||
|
||||
Depois reinicie o backend/MCP Server.
|
||||
|
||||
Linux/macOS:
|
||||
|
||||
```bash
|
||||
cp contas_mcp/servers/contas_mcp_server/line_policy_alt1.py \
|
||||
contas_mcp/servers/contas_mcp_server/line_policy.py
|
||||
```
|
||||
|
||||
PowerShell:
|
||||
|
||||
```powershell
|
||||
Copy-Item `
|
||||
contas_mcp/servers/contas_mcp_server/line_policy_alt1.py `
|
||||
contas_mcp/servers/contas_mcp_server/line_policy.py -Force
|
||||
```
|
||||
|
||||
## 7. Como ativar ALT2
|
||||
|
||||
```bash
|
||||
python scripts/select_line_policy.py alt2
|
||||
```
|
||||
|
||||
Depois reinicie o backend/MCP Server.
|
||||
|
||||
Ao iniciar com ALT2, o MCP passa a consultar automaticamente `consultar_linhas_autorizadas` quando houver uma referência explícita a linha no turno.
|
||||
|
||||
Não é necessário inserir manualmente:
|
||||
|
||||
```python
|
||||
context["authorized_msisdns"] = [...]
|
||||
```
|
||||
|
||||
nem:
|
||||
|
||||
```python
|
||||
args["authorized_msisdns"] = [...]
|
||||
```
|
||||
|
||||
A lista vem do serviço MCP de autorização.
|
||||
|
||||
## 8. Como alterar o mock para testes
|
||||
|
||||
Para ilustrar outra linha autorizada, edite apenas:
|
||||
|
||||
```text
|
||||
app/domain/contas/fixtures/authorized_lines.json
|
||||
```
|
||||
|
||||
Exemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"msisdn": "11977771234",
|
||||
"relationship": "dependente",
|
||||
"status": "ACTIVE",
|
||||
"authorized": true
|
||||
}
|
||||
```
|
||||
|
||||
Não altere `line_policy_alt2.py` para cadastrar linhas.
|
||||
|
||||
Esse desenho deixa claro que a política apenas **consome autorização**; ela não é o cadastro das linhas autorizadas.
|
||||
|
||||
## 9. Abrangência
|
||||
|
||||
A política é aplicada no ponto único `_invoke()` do MCP Contas antes da execução de domínio. Dessa forma cobre as tools/serviços baseados em MSISDN, inclusive quando passam por workflows.
|
||||
|
||||
Cobertura funcional inclui:
|
||||
|
||||
- `consultar_faturas`
|
||||
- `consultar_plano`
|
||||
- `invoice_explanation`
|
||||
- `consultar_vas`
|
||||
- `consultar_historico_vas`
|
||||
- `cancelar_vas_avulso`
|
||||
- `tratar_vas_estrategico`
|
||||
- `validar_vas_subject`
|
||||
- `validar_contestacao`
|
||||
- `contestar_cobranca`
|
||||
- `pro_rata`
|
||||
- `termino_desconto`
|
||||
- `valor_divergente`
|
||||
- `consultar_status_solicitacao`
|
||||
- `enviar_sms`
|
||||
- `recuperar_fatura_pdf`
|
||||
- `finalizar_atendimento`
|
||||
|
||||
`consultar_linhas_autorizadas` é a fonte de autorização da ALT2 e não passa pela própria política para evitar dependência circular.
|
||||
|
||||
`buscar_informacao` não depende de linha e `retomar_workflow` apenas retoma execução já iniciada.
|
||||
|
||||
## 10. Arquivos alterados/criados
|
||||
|
||||
### Política de linha
|
||||
|
||||
```text
|
||||
contas_mcp/servers/contas_mcp_server/line_policy.py
|
||||
contas_mcp/servers/contas_mcp_server/line_policy_alt1.py
|
||||
contas_mcp/servers/contas_mcp_server/line_policy_alt2.py
|
||||
```
|
||||
|
||||
### Novo serviço MCP de autorização
|
||||
|
||||
```text
|
||||
contas_mcp/servers/contas_mcp_server/authorized_lines_service.py
|
||||
app/domain/contas/fixtures/authorized_lines.json
|
||||
contas_mcp/servers/contas_mcp_server/main.py
|
||||
```
|
||||
|
||||
### Referência conversacional e seleção da política
|
||||
|
||||
```text
|
||||
app/domain/contas/line_reference.py
|
||||
scripts/select_line_policy.py
|
||||
```
|
||||
|
||||
### Testes e documentação
|
||||
|
||||
```text
|
||||
tests/migration/test_line_policy_alternatives.py
|
||||
docs/RELATORIO_POLITICAS_DE_LINHA_ALT1_ALT2.md
|
||||
```
|
||||
|
||||
## 11. Validação
|
||||
|
||||
Testes específicos da política e do novo mock:
|
||||
|
||||
```text
|
||||
12 passed
|
||||
```
|
||||
|
||||
Smoke ALT2:
|
||||
|
||||
```text
|
||||
policy = authorized_related_lines
|
||||
authorized = [11999999999, 11988884321]
|
||||
requested = final 4321
|
||||
allowed = true
|
||||
effective = 11988884321
|
||||
```
|
||||
|
||||
Após o smoke, ALT1 foi restaurado e validado como política ativa entregue.
|
||||
|
||||
Suíte completa de migração com ALT1 ativa:
|
||||
|
||||
```text
|
||||
765 passed
|
||||
```
|
||||
|
||||
## 12. Decisão arquitetural
|
||||
|
||||
Responsabilidades finais:
|
||||
|
||||
| Camada | Responsabilidade |
|
||||
|---|---|
|
||||
| Framework | identidade/contexto, execução genérica, terminalidade e short-circuit de tools |
|
||||
| Agent/MCP Contas | política ALT1/ALT2 e integração de autorização |
|
||||
| `consultar_linhas_autorizadas` | informar quais linhas a identidade autenticada está autorizada a operar |
|
||||
| Backend real futuro | fonte de verdade de titular/dependentes/autorização |
|
||||
| LLM | interpretar a referência conversacional; nunca conceder autorização |
|
||||
|
||||
A principal regra arquitetural é:
|
||||
|
||||
> **linha mencionada ≠ linha autorizada**
|
||||
|
||||
A autorização precisa vir de uma fonte explícita e confiável. Na versão demonstrativa essa fonte é o mock MCP `consultar_linhas_autorizadas`; em produção, deve ser substituída pela integração corporativa correspondente.
|
||||
Reference in New Issue
Block a user