ajuste no guardraild COE
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,148 +1,78 @@
|
||||
"""Prompt do rail COER (coerência do input do cliente).
|
||||
"""Prompt do rail COER (coerência semântica do input do cliente).
|
||||
|
||||
Roda no INPUT, em paralelo com PINJ (mesmo pool), num 20b. Decide se a fala do
|
||||
cliente é aproveitável. Saída BINÁRIA (`1` passa / `0` descarta) — o `reason` é
|
||||
texto fixo; pedir motivo antes do dígito foi medido e não paga (+170 ms, empate).
|
||||
O COER responde uma pergunta estreita: existe significado conversacional recuperável
|
||||
na fala do cliente? Ele não decide intenção, completude de parâmetros, executabilidade,
|
||||
escopo de negócio ou se uma solicitação deve ser aceita. Essas decisões pertencem ao
|
||||
router, aos contratos transacionais, aos validadores e ao mecanismo de clarification.
|
||||
|
||||
Descarta SÓ por três motivos:
|
||||
|
||||
(a) incompreensível — transcrição quebrada, palavra solta, conversa paralela;
|
||||
(b) negação ambígua — "não" colado num pedido de AÇÃO do atendente, sem a vírgula
|
||||
que decidiria a leitura ("não quero cancelar" × "não, quero cancelar");
|
||||
(c) idioma (2026-08-10) — frase INTEIRA em inglês é STT quebrado, não cliente
|
||||
bilíngue: descarta mesmo se ela se entende ou responde à pergunta pendente.
|
||||
Ressalva: passa quando o agente pediu o NOME do item — nome de serviço É em
|
||||
inglês (`coer_ok_0023`). ⚠️ A regra só funciona no ENQUADRAMENTO, acima do
|
||||
gate de histórico (dentro de (a): 0/9 nos casos de inglês; no topo: 9/9),
|
||||
porque o gate concede 1 a quem responde e o catch-all a quem pede algo
|
||||
legível. Travado em `tests/guardrails/test_coerencia.py`.
|
||||
|
||||
O resto passa e é tratado adiante (matcher, TOX, OOS, orquestrador): referência
|
||||
vaga, nome deformado, xingamento, assunto fora de fatura, resposta curta. O
|
||||
histórico entra no prompt porque é ele que resolve fala curta e negação sem vírgula.
|
||||
|
||||
Dois bugs de produção fechados, ambos com a mesma assinatura — o modelo reconhece
|
||||
a fala e escapa por uma regra de allow antes de aplicar (b):
|
||||
- 2026-08-07, "não" seco no degrau 2 da retenção: (b) disparava só por começar
|
||||
com "não" e o modelo COMPLETAVA a elipse com a ação que o AGENTE ofereceu.
|
||||
Conserto: (b) exige que a fala PEÇA algo, e o teste da subtração proíbe
|
||||
completar com a oferta do agente (`coer_ok_0027`: 161/220 → 340/340);
|
||||
- 2026-08-10, "não gostaria de falar com a atendente" (`coer_ambig_0014`, 2/9):
|
||||
a causa é o VERBO, não o gate nem o histórico (sonda 2×2 — condicional +
|
||||
histórico curto 2/10 × "não quero" + o histórico longo do trace 10/10).
|
||||
Conserto: gate vale só para a fala que "SÓ responde a ela"; (b) diz que
|
||||
entender o pedido não dispensa o teste; a glosa do 1º exemplo cobre o
|
||||
condicional. Alvo → 7/9, suíte 176,0 → 180,7/189.
|
||||
|
||||
⚠️ Protocolo: decida por BATCH (3 amostras de `--repeat 3` da suíte inteira, banda
|
||||
de ruído ±4). `--repeat` focado engana nos dois sentidos — a mesma variante deu
|
||||
7/10 focado × 0/9 batch, e o prompt atual dá 7/9 batch × 3/9 focado.
|
||||
|
||||
Variantes medidas e REJEITADAS (não retentar sem motivo novo) — a suíte está numa
|
||||
fronteira zero-soma, cada cláusula compra um caso e vende outro:
|
||||
- "a recusa soar clara não fecha" → CONTRADIZ a exceção "a fala segue dizendo
|
||||
qual leitura vale": mata `coer_ok_0003` (7/9 → 0-1/9) em 3 variantes;
|
||||
- exceção no GATE ("fala com 'não' ainda passa por (b)") → mata `coer_ruido_0011`
|
||||
(9/9 → 0/9): exceção explícita REFORÇA o gate para todo o resto;
|
||||
- "gostaria" na lista de modais de (b) → 169,7/189;
|
||||
- few-shot NÃO é mais alavanca (era em 2026-08-05, +3,4 p.p.): +3 exemplos = empate
|
||||
exato por +132 tokens; só o do NOME em inglês = 189,7/201 (arrasta a regra (c));
|
||||
tirar exemplos custa mais do que os tokens que ocupam — inclusive o "não quero
|
||||
entender porque…", que o controle FOCADO media como "sem efeito" e em batch vale
|
||||
`coer_ok_0010` inteiro (9/9 → 1/9).
|
||||
|
||||
Tamanho: 1289 → 1334 (2026-08-07) → **1451 tokens** (cl100k). Suíte: **191,7/201
|
||||
(95,4%)**, 67 casos. Detalhe por caso e histórico: `tests/llm_tests/README.md`.
|
||||
|
||||
Remedido em 2026-08-12 ao desfazer o revert (41979c4d): 193,7/204 (95,0%), 68 casos
|
||||
— o novo `coer_ruido_0022` ("um" respondendo "sanei sua dúvida?", STT que não pegou
|
||||
o "sim" → golden 0, reperguntar) sai de 3/10 no prompt antigo para 9/9 em batch só
|
||||
com o gate "SÓ responde a ela", sem mudança extra de prompt.
|
||||
A classificação continua totalmente delegada ao LLM. Não há listas de frases,
|
||||
regexes ou exceções de domínio para liberar/bloquear entradas específicas.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def build_coer_prompt(text: str, context: str = "") -> str:
|
||||
"""Monta o prompt do rail COER.
|
||||
"""Monta o prompt semântico do rail COER.
|
||||
|
||||
Args:
|
||||
text: fala do cliente a classificar.
|
||||
context: bloco de histórico já formatado por
|
||||
``prompts._context.format_context_block`` (para este rail a última
|
||||
fala do agente é PRESERVADA — é a pergunta pendente).
|
||||
context: histórico já formatado, incluindo a pergunta pendente do agente
|
||||
quando disponível.
|
||||
|
||||
Returns:
|
||||
Prompt cuja resposta esperada é um único caractere: ``1`` ou ``0``.
|
||||
"""
|
||||
return f"""Você filtra a fala do CLIENTE no atendimento de fatura do provedor. A fala vem de
|
||||
transcrição de voz e pode chegar truncada ou trocada. O atendimento é em português:
|
||||
frase inteira em INGLÊS é STT quebrado, não cliente bilíngue — responda 0 mesmo que
|
||||
ela se entenda ou responda à pergunta do agente; só não vale quando o agente pediu o
|
||||
NOME do item, que é em inglês.
|
||||
return f"""Você é o guardrail de COERÊNCIA SEMÂNTICA da fala do CLIENTE em uma conversa.
|
||||
|
||||
PRIMEIRO olhe o histórico. Se o agente terminou com uma pergunta e a fala SÓ responde a ela
|
||||
(sim/não, "ainda não", nome de serviço, valor, uma das opções oferecidas), responda 1
|
||||
— mesmo curta, estranha ou com o nome deformado pelo STT. Se não há pergunta pendente,
|
||||
julgue a fala sozinha pelos casos abaixo, sem dar desconto.
|
||||
Sua única responsabilidade é decidir se a fala contém significado conversacional
|
||||
recuperável o suficiente para que as próximas camadas do sistema possam trabalhar.
|
||||
|
||||
Responda 0 (descartar) SÓ nestes dois casos:
|
||||
NÃO tente decidir aqui:
|
||||
- qual é a intenção do cliente;
|
||||
- se a intenção mudou em relação ao turno anterior;
|
||||
- se uma transação deve continuar, ser abandonada ou encerrada;
|
||||
- se faltam parâmetros para executar uma ação;
|
||||
- se um valor, nome, data ou outro parâmetro é válido;
|
||||
- se a solicitação pertence ao escopo do atendimento;
|
||||
- se uma ação é permitida por regra de negócio;
|
||||
- se a fala precisa de clarification ou desambiguação posterior.
|
||||
|
||||
(a) NÃO DÁ PARA ENTENDER — você não conseguiria dizer em uma frase, SEM INVENTAR, o
|
||||
que o cliente quer, responde ou reclama: transcrição quebrada, frase cortada no
|
||||
meio, palavra ou letra solta, frase que soa completa mas cujo pedido não faz
|
||||
sentido, ou fala dirigida a OUTRA PESSOA (o cliente conversando com quem está do
|
||||
lado, sem falar com o atendimento). Palavra do domínio (plano, fatura, valor,
|
||||
cpf) dentro de frase sem sentido não salva a fala. Fala VAGA não é
|
||||
incompreensível: se ela aponta para o que está na tela ("esse aí", "isso aqui",
|
||||
"esse negócio", "os valores"), responda 1 — perguntar qual item é do fluxo.
|
||||
E se a última fala do agente pediu um NOME de item/serviço, nenhuma fala curta
|
||||
é incompreensível: ela é a tentativa de dizer o nome, por mais estranha que
|
||||
soe → 1 (reconhecê-lo é da etapa seguinte, que tem a fatura).
|
||||
Essas responsabilidades pertencem ao router, ao estado transacional, aos validadores
|
||||
e ao mecanismo de clarification. Portanto, uma fala pode ser compreensível mesmo
|
||||
sendo incompleta para execução, contendo negação, discordância, reclamação, múltiplas
|
||||
intenções, informalidade, erro gramatical ou referência que precise ser resolvida pelo
|
||||
contexto.
|
||||
|
||||
(b) NEGAÇÃO AMBÍGUA — a fala começa com "não" E PEDE ALGO depois; entender o que ela
|
||||
pede não a salva, quem decide é o teste. Faça o teste: tire
|
||||
esse "não" do início e olhe SÓ o que sobra na fala — nunca complete com a ação
|
||||
que o agente ofereceu. Se não sobra pedido nenhum ("não", "não sanou"), é
|
||||
resposta ao agente → 1, seja qual for a pergunta pendente. Se o que sobra é
|
||||
pedido de ação do atendente (cancelar, tirar cobrança,
|
||||
ajustar/diminuir a fatura, transferir para atendente, encerrar a conta,
|
||||
parcelar), sobram duas leituras opostas — recusa ("não quero cancelar") ou
|
||||
pedido ("não, quero cancelar") — e a vírgula que decidiria não veio na
|
||||
transcrição: responda 0. Vale para qualquer verbo ("não quero/preciso/posso",
|
||||
"não quero que vocês...", "não cancela").
|
||||
Responda 1 se: vem vírgula, "porque" ou "mas" depois do "não"; há sujeito antes
|
||||
do "não" ("eu não quero cancelar"); a fala segue dizendo qual leitura vale; ou o
|
||||
que sobra sem o "não" não é ação do atendente (pagar, reconhecer, entender,
|
||||
mudar de plano).
|
||||
Use o histórico somente para interpretar elipses, respostas curtas e referências ao
|
||||
turno anterior. Nunca complete a fala inventando uma intenção que não esteja apoiada
|
||||
pela própria fala ou pelo contexto imediato.
|
||||
|
||||
Responda 1 em TODO o resto, inclusive:
|
||||
- pedido, queixa, dúvida ou desabafo que você entende, mesmo com erro de transcrição,
|
||||
gíria, xingamento, número solto ou assunto fora de fatura (outros filtros cuidam);
|
||||
- nome de serviço estranho ou deformado, inclusive quando o agente pediu para repetir
|
||||
o nome do serviço;
|
||||
- pedido de tempo, "alô?", agradecimento, despedida.
|
||||
Responda 1 quando for possível identificar, sem inventar, pelo menos um conteúdo
|
||||
conversacional útil: uma intenção, pergunta, resposta, afirmação, negação, reclamação,
|
||||
referência, escolha, valor, nome, pedido de esclarecimento, encerramento ou mudança de
|
||||
assunto. Não exija que esse conteúdo já seja suficiente para executar uma ferramenta.
|
||||
|
||||
Dúvida se entendeu a fala → 1. Pergunta ou pedido claro dirigido ao atendimento, mesmo
|
||||
fora do assunto de fatura → 1. Dúvida entre as duas leituras da negação → 0.
|
||||
Responda 0 somente quando, mesmo considerando o contexto imediato, não houver
|
||||
significado conversacional recuperável com segurança — por exemplo, transcrição
|
||||
fragmentada, palavras desconexas, fala cortada antes de formar qualquer relação
|
||||
semântica, ou conversa paralela sem solicitação dirigida ao atendimento.
|
||||
|
||||
Exemplos (ilustram a regra, não são lista de falas):
|
||||
- "não quero parcelar a fatura" → 0 (sem a vírgula, pode ser "não, quero parcelar");
|
||||
idem no condicional, "não gostaria de parcelar a fatura"
|
||||
- "eu não quero parcelar a fatura" → 1 (o "eu" antes do "não" fecha a leitura)
|
||||
- "não quero parcelar, quero só entender o valor" → 1 (a fala diz qual leitura vale)
|
||||
- "não vou pagar essa multa" → 1 (pagar não é ação do atendente: a queixa é a mesma)
|
||||
- "não", depois de "sanou sua dúvida?" → 1 (responde a pergunta pendente)
|
||||
- "deixe zero", depois de "qual o nome do serviço?" → 1 (pode ser o nome que o STT
|
||||
deformou — "Deezer"; reconhecer o nome é da etapa seguinte, que tem a fatura)
|
||||
- "não quero entender porque a conta subiu tanto" → 1 (entender é dúvida, não ação)
|
||||
- "olha o menino ali pegando o negócio lá" → 0 (não dá para dizer o que o cliente quer)
|
||||
- "bota dois planos um em cima do outro pra cá" → 0 (soa ordem, não quer dizer nada)
|
||||
- "está cobrando um" → 0 (cortada no meio: não dá para saber de quê)
|
||||
Critério decisivo:
|
||||
- compreensível mas incompleto/ambíguo para a regra de negócio -> 1;
|
||||
- compreensível mas com possível mudança de intenção -> 1;
|
||||
- compreensível mas sem todos os parâmetros -> 1;
|
||||
- compreensível e contendo negação/discordância -> 1;
|
||||
- impossível determinar qualquer conteúdo conversacional sem inventar -> 0.
|
||||
|
||||
Na dúvida entre "há significado, mas outra camada precisa esclarecer" e "não há
|
||||
significado recuperável", escolha 1. O COER deve bloquear apenas incompreensibilidade
|
||||
semântica real, não incerteza de negócio.
|
||||
|
||||
------------------------------------{context}
|
||||
Fala do cliente:
|
||||
{text}
|
||||
------------------------------------
|
||||
|
||||
Responda APENAS um caractere: 1 (aproveitável) ou 0 (descartar).
|
||||
Responda APENAS um caractere: 1 (semanticamente compreensível) ou 0
|
||||
(semanticamente incompreensível).
|
||||
"""
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
27
docs/FIX_COER_SEMANTIC_LLM_20260831.md
Normal file
27
docs/FIX_COER_SEMANTIC_LLM_20260831.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Ajuste semântico do guardrail COER
|
||||
|
||||
## Objetivo
|
||||
|
||||
Evitar que o COER confunda negação, reclamação, mudança de intenção ou falta de parâmetros com fala incompreensível.
|
||||
|
||||
## Alteração
|
||||
|
||||
O prompt de `agent_framework.guardrails.calibrated.prompts.coerencia` foi simplificado para uma responsabilidade única: decidir se existe significado conversacional recuperável.
|
||||
|
||||
Foram removidas heurísticas textuais específicas de negação, listas de ações e exemplos de frases usados como regras de decisão. O julgamento continua sendo feito pelo LLM com o perfil `guardrail`.
|
||||
|
||||
O COER agora não decide intenção, mudança de intenção, continuidade de transação, completude de parâmetros, validade de parâmetros, escopo ou executabilidade. Essas responsabilidades permanecem no router, runtime transacional, validators e clarification.
|
||||
|
||||
## Contrato
|
||||
|
||||
- compreensível, mas incompleto/ambíguo para negócio: ALLOW;
|
||||
- compreensível com possível mudança de intenção: ALLOW;
|
||||
- compreensível faltando parâmetros: ALLOW;
|
||||
- compreensível com negação/discordância: ALLOW;
|
||||
- sem significado semântico recuperável: BLOCK.
|
||||
|
||||
## Testes
|
||||
|
||||
- regressão dirigida do COER: 22 PASS;
|
||||
- `tests/migration`: 824 PASS / 2 FAIL;
|
||||
- os 2 FAIL são preexistentes e não relacionados ao COER.
|
||||
71
tests/docs/EXTERNAL_GUARDRAILS_JUDGES.md
Normal file
71
tests/docs/EXTERNAL_GUARDRAILS_JUDGES.md
Normal 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.
|
||||
27
tests/docs/FIX_COER_SEMANTIC_LLM_20260831.md
Normal file
27
tests/docs/FIX_COER_SEMANTIC_LLM_20260831.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Ajuste semântico do guardrail COER
|
||||
|
||||
## Objetivo
|
||||
|
||||
Evitar que o COER confunda negação, reclamação, mudança de intenção ou falta de parâmetros com fala incompreensível.
|
||||
|
||||
## Alteração
|
||||
|
||||
O prompt de `agent_framework.guardrails.calibrated.prompts.coerencia` foi simplificado para uma responsabilidade única: decidir se existe significado conversacional recuperável.
|
||||
|
||||
Foram removidas heurísticas textuais específicas de negação, listas de ações e exemplos de frases usados como regras de decisão. O julgamento continua sendo feito pelo LLM com o perfil `guardrail`.
|
||||
|
||||
O COER agora não decide intenção, mudança de intenção, continuidade de transação, completude de parâmetros, validade de parâmetros, escopo ou executabilidade. Essas responsabilidades permanecem no router, runtime transacional, validators e clarification.
|
||||
|
||||
## Contrato
|
||||
|
||||
- compreensível, mas incompleto/ambíguo para negócio: ALLOW;
|
||||
- compreensível com possível mudança de intenção: ALLOW;
|
||||
- compreensível faltando parâmetros: ALLOW;
|
||||
- compreensível com negação/discordância: ALLOW;
|
||||
- sem significado semântico recuperável: BLOCK.
|
||||
|
||||
## Testes
|
||||
|
||||
- regressão dirigida do COER: 22 PASS;
|
||||
- `tests/migration`: 824 PASS / 2 FAIL;
|
||||
- os 2 FAIL são preexistentes e não relacionados ao COER.
|
||||
27
tests/docs/FIX_CONTAS_REPORT_20260831_SECOND_PASS.md
Normal file
27
tests/docs/FIX_CONTAS_REPORT_20260831_SECOND_PASS.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Correções após regressão de 31/08/2026
|
||||
|
||||
Esta rodada corrige quatro comportamentos observados no relatório de regressão sem reintroduzir o runtime conversacional legado.
|
||||
|
||||
## 1. Cancelamento múltiplo após confirmação
|
||||
|
||||
O snapshot transacional do framework já preservava corretamente os argumentos confirmados. O defeito estava no preflight de execução do MCP do Contas, que tentava resolver novamente o `subject` de apresentação (por exemplo, `Tamboro Mensal, Paramount+`) mesmo quando `items[]` já continha múltiplas entidades canônicas pré-validadas. Agora `items[]` é a fonte de verdade nessa condição e não há segunda resolução textual.
|
||||
|
||||
## 2. Contestação com valor incompatível
|
||||
|
||||
Uma divergência de valor comprovada pelo CVAL passa a ser recuperável: o item permanece preservado, apenas `valor` volta para coleta e a resposta informa o valor autoritativo encontrado na fatura. O contrato de auditoria mantém `reason=CVAL` e acrescenta `recoverable_reason=amount_not_supported_by_invoice`.
|
||||
|
||||
O runtime genérico ganhou suporte opcional a `parameter_message` emitido por um pre-validator de domínio. O framework apenas apresenta essa mensagem enquanto permanece em `COLLECTING_PARAMETERS`; ele não interpreta a regra de negócio.
|
||||
|
||||
## 3. Encerramento explícito
|
||||
|
||||
Expressões inequívocas como `entendi, obrigado, era só isso` encerram a sessão como `resolvido`, desde que não exista transação ou workflow ativo. Isso evita que uma despedida caia em fallback/guardrail sem consumir confirmações pendentes.
|
||||
|
||||
## 4. Continuação plural após explicação de fatura
|
||||
|
||||
Frases como `as duas mesmo, pode seguir` imediatamente após `contas_invoice_explanation` permanecem no contexto de explicação e não são confundidas com finalização genérica.
|
||||
|
||||
## Regressão
|
||||
|
||||
- Testes novos e direcionados: 58/58 no Contas e 30/30 no runtime transacional do framework.
|
||||
- `tests/migration`: 818 PASS / 2 FAIL.
|
||||
- Os 2 FAIL restantes são preexistentes nesta base: caso histórico de `validar_contestacao` com TIM Fashion/R$50 e fraseologia de `termino_desconto`.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Correção de paridade conversacional residual do Contas — 2026-08-31
|
||||
|
||||
## Escopo
|
||||
|
||||
Correções pontuais extraídas do comportamento útil do Contas anterior sem restaurar o runtime legado:
|
||||
|
||||
- retenção antes de handoff humano;
|
||||
- jurídico/Anatel/Procon com dependência de entidade previamente em foco;
|
||||
- três falas consecutivas realmente incompreensíveis;
|
||||
- preservação do pós-finalização no framework, com status terminal de erro respeitado;
|
||||
- cancelamento múltiplo por entidades nomeadas/contextuais e por "todos os VAS avulsos".
|
||||
|
||||
## Arquitetura
|
||||
|
||||
Foi adicionada `app/domain/contas/conversation_policy.py`, executada depois do `EnterpriseRouter` e antes do agente de domínio. A policy não executa side effects: apenas reprompta, enriquece contexto ou altera o roteamento. Toda operação transacional continua passando pelo `AgentRuntimeMixin`, pré-validação MCP, confirmação explícita e workflow do Contas.
|
||||
|
||||
## Retenção
|
||||
|
||||
Quando o router solicita handoff humano, a policy procura evidência autoritativa de VAS avulso que participou da variação da conta usando `varied_avulso_items`. Sem evidência, o handoff segue normalmente. Com evidência, a policy oferece dois degraus: explicação da variação e, em seguida, tratamento dos VAS identificados. Recusa do segundo degrau leva ao handoff humano normal.
|
||||
|
||||
O aceite do tratamento cria reentrada contextual com os nomes já comprovados; o cliente não precisa repeti-los e a transação continua sujeita à pré-validação e confirmação.
|
||||
|
||||
## Jurídico / Anatel / Procon
|
||||
|
||||
A mera ameaça regulatória não cria uma transação. Sem entidade concreta em foco, a fala é encaminhada como reclamação ampla ao suporte, sem MCP transacional. Quando já existe `subject/items` em estado transacional, o foco é preservado e a fala pode continuar no fluxo correspondente. A fatura inteira nunca é usada para inventar o alvo.
|
||||
|
||||
## Três falas incompreensíveis
|
||||
|
||||
Foi criada a intent semântica `contas_no_match`, exclusiva para fala sem conteúdo recuperável. `fallback` genérico não conta como incompreensão. O contador é consecutivo e reinicia em qualquer turno compreendido.
|
||||
|
||||
- 1ª: pede reformulação;
|
||||
- 2ª: pede reformulação;
|
||||
- 3ª: encerra pelo nó global `end_session`, chamando `finalizar_atendimento` com `status=erro_no_match`.
|
||||
|
||||
O limite pode ser configurado por `CONTAS_NO_MATCH_MAX_CONSECUTIVE`, default 3.
|
||||
|
||||
## Cancelamento múltiplo
|
||||
|
||||
`validar_vas_subject` agora resolve múltiplas entidades exclusivamente contra catálogo autorizado VAS/fatura. Exemplos suportados após extração semântica contextual:
|
||||
|
||||
- `cancela Netflix e HBO`;
|
||||
- `os dois` / `ambos`, quando o extrator LLM consegue resolver os nomes pelo contexto imediato;
|
||||
- `todos os VAS avulsos`.
|
||||
|
||||
`todos` genérico não expande em massa. Para "todos os VAS avulsos", itens estratégicos/bundle são filtrados pela política de domínio. Os itens resolvidos são enviados como `items[]` para o workflow batch existente, com uma única confirmação explícita antes da execução.
|
||||
|
||||
## Pós-finalização
|
||||
|
||||
Não foi criado runtime duplicado. O lifecycle continua no framework. O nó `end_session` passou apenas a respeitar um `terminal_status` já definido pela policy (por exemplo `erro_no_match`) e uma mensagem terminal específica, mantendo o mecanismo atual de replay/soft reset.
|
||||
|
||||
## Testes
|
||||
|
||||
Novos testes:
|
||||
|
||||
- `tests/migration/test_contas_conversation_policy_residuals.py`;
|
||||
- `tests/migration/test_multiple_vas_subject_resolution.py`.
|
||||
|
||||
Regressão direcionada: **61 PASS**.
|
||||
|
||||
Regressão completa `tests/migration`: **810 PASS / 2 FAIL**. Os mesmos dois FAIL foram reproduzidos no ZIP original sem estas alterações, portanto são falhas preexistentes e fora do escopo desta correção:
|
||||
|
||||
1. `test_validar_contestacao_aprova_quando_item_e_valor_sao_comprovados`;
|
||||
2. `test_termino_desconto_e_valor_divergente_preservam_semantica_do_original`.
|
||||
@@ -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`.
|
||||
29
tests/docs/FIX_SCENARIO_23_LIVE_INTERNET_BALANCE_20260831.md
Normal file
29
tests/docs/FIX_SCENARIO_23_LIVE_INTERNET_BALANCE_20260831.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Correção do cenário 23 — saldo de internet em tempo real
|
||||
|
||||
## Problema
|
||||
|
||||
O FaturasAgent respondia que não havia informação nos "dados consultados" sobre o saldo de internet restante mesmo quando nenhuma tool de consumo em tempo real havia sido executada. Isso criava uma alegação de consulta sem evidência funcional.
|
||||
|
||||
## Solução
|
||||
|
||||
A correção foi mantida no agente, sem alteração do framework. Para perguntas explícitas de saldo/franquia restante em tempo real, o FaturasAgent:
|
||||
|
||||
1. verifica se alguma tool bem-sucedida trouxe evidência de saldo/consumo atual;
|
||||
2. se houver evidência, deixa o fluxo normal responder com os dados reais;
|
||||
3. se não houver evidência, não afirma que consultou dados e orienta o cliente ao Meu TIM.
|
||||
|
||||
Resposta de fallback:
|
||||
|
||||
> Não consigo consultar o saldo de internet em tempo real por aqui. Para ver quanto ainda resta neste mês, consulte o app Meu TIM, onde você acompanha o consumo atual da sua franquia.
|
||||
|
||||
A regra não intercepta perguntas genéricas sobre internet/plano e não bloqueia uma integração futura que passe a devolver saldo real.
|
||||
|
||||
## Arquivo alterado
|
||||
|
||||
- `app/agents/faturas_agent.py`
|
||||
|
||||
## Testes
|
||||
|
||||
- `tests/migration/test_scenario_23_live_internet_balance_guidance.py`
|
||||
- 3/3 testes novos PASS
|
||||
- regressão `tests/migration`: 821 PASS / 2 FAIL preexistentes
|
||||
@@ -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.
|
||||
104
tests/docs/LEGACY_INTEGRATION_PARITY_20260822.md
Normal file
104
tests/docs/LEGACY_INTEGRATION_PARITY_20260822.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# Paridade de Integração Contas — Mock x Sistemas Reais
|
||||
|
||||
Data: 2026-08-22
|
||||
|
||||
## Objetivo
|
||||
|
||||
Validar o agente Contas migrado contra o código original, usando o legado como fonte de verdade para contratos HTTP, autenticação, headers, payloads, URLs, timeouts e comportamento de integração. O objetivo é manter o funcionamento com mocks locais sem impedir a execução contra sistemas reais quando `TIM_GATEWAY_MODE`/`TIM_USE_MOCK_GATEWAY` forem configurados para modo real.
|
||||
|
||||
## Correção crítica — identidade do item transacional
|
||||
|
||||
Foi corrigido o caso em que um pedido explícito para `TIM CTRL Redes Sociais 8.0` podia ser reinterpretado pelo matcher fuzzy como `TIM Fashion Mensal`.
|
||||
|
||||
### Causa
|
||||
|
||||
O `InvoiceResolver` eliminava seções não transacionáveis (por exemplo, planos) antes da resolução de identidade e, em seguida, aplicava similaridade somente sobre VAS. Como `TIM Fashion Mensal` ultrapassava o threshold do matcher, o `subject` era substituído antes da execução.
|
||||
|
||||
### Correção
|
||||
|
||||
- A resolução exata de identidade agora acontece antes de qualquer fuzzy matching.
|
||||
- A busca exata considera também itens fora do escopo transacional, como planos.
|
||||
- Um plano encontrado exatamente é classificado como `out_of_scope` para a operação VAS.
|
||||
- O fuzzy matching continua restrito aos candidatos realmente tratáveis.
|
||||
- `resolve_items()` preserva o comportamento anterior onde necessário para compatibilidade; o fluxo operacional usa a proteção de identidade.
|
||||
|
||||
Resultado esperado para o caso:
|
||||
|
||||
`TIM CTRL Redes Sociais 8.0` -> exact match -> `plano` -> `out_of_scope` -> não substituir por outro VAS -> não executar cancelamento.
|
||||
|
||||
## Comparação com o código original
|
||||
|
||||
Foram comparados os comandos do projeto original (`agente_contas_tim/commands`), `factory.py`, `config.py` e o gateway HTTP com o adaptador atual `app/domain/contas/client.py`.
|
||||
|
||||
| Serviço/Integração | Contrato encontrado no original | Situação no migrado após revisão |
|
||||
|---|---|---|
|
||||
| Consulta VAS | GET, URL com `{msisdn}` ou append `/msisdn`, normalização para prefixo 55, clientId | Corrigido: aliases originais, timeout, append e prefixo 55 |
|
||||
| Histórico VAS | GET com `?msisdn=`, clientId/messageId/auth | Corrigido default `clientId=AIAGENTCR` |
|
||||
| Bloqueio VAS | POST, contratos de payload `pmid`/`input`/`vasBlock`, headers extras | Corrigidos aliases de URL/auth/timeout/clientId/operation/payload/encoding |
|
||||
| Cancelamento VAS | DELETE, body com channel/msisdn/appId/cspId/interactionProtocol, OAM/CN/type opcionais | Corrigidos aliases `TIM_CANCELLATION_*` e `TIM_CANCELAMENTO_*` |
|
||||
| Divergência / explicação de fatura | GET `<base>/<msisdn>?channel=AIAGENTCR`, Basic opcional user/password, clientID | Corrigidos aliases, Basic auth e timeout |
|
||||
| CompleteInvoices | POST `{"msisdn": ...}`, `ClientID=AIAGENTCR` | Compatível; timeout respeitado |
|
||||
| Profile bill | Factory original usa configuração de CompleteInvoices | Corrigido para priorizar contrato/config de CompleteInvoices |
|
||||
| Profile full | GET com placeholder ou append `/msisdn`, `ClientID=AIAGENTCR` | Corrigido append e timeout |
|
||||
| Line info | Mesmo padrão de URL do profile full | Corrigido append e timeout |
|
||||
| Contrato | GET `<base>/<msisdn>`, clientId do legado | Corrigido default `AIAGENTCR` |
|
||||
| Protocolo V2 | POST serviceRequest/interaction, headers opcionais OAM/CN/type | Mantido no adaptador atual |
|
||||
| Contestação do cliente | POST, clientId/messageId/X-Agent-Id, user configurável | Corrigido default via `TIM_CUSTOMER_CONTESTATION_USER_ID` |
|
||||
| Atualização de Service Request | POST, channel/serviceRequest, headers de integração | Mantido |
|
||||
| Tracking Activities | POST com customer/protocol/invoice/activity/user | Mantido |
|
||||
| SMS | POST com msisdn/sender/message/URL e receipt opcional | Mantido |
|
||||
| Bill PDF detalhada | POST com invoiceId/customerId e invoiceType `DETALHADA`, retorno PDF | Aliases ampliados |
|
||||
| Secure PDF / invoice recover | GET com invoiceId/msisdn/customerId | Aliases/header ajustados |
|
||||
|
||||
## Configurações presentes no legado sem uso operacional comprovado
|
||||
|
||||
- `status_customer`: configuração encontrada, mas sem comando/runtime consumidor localizado na revisão.
|
||||
- configuração OAuth específica de SMS: declarada no config original, mas sem consumidor runtime localizado.
|
||||
|
||||
Esses itens não foram tratados como requisito ativo sem evidência de uso no código original.
|
||||
|
||||
## Mock x modo real
|
||||
|
||||
O mock continua suportado. Em mock, o cliente retorna fixtures locais para as operações previstas. Em modo real, o mesmo adaptador segue os contratos HTTP reconstruídos a partir do código original.
|
||||
|
||||
A principal diferença de risco é que um mock tende a responder `200/OK` para cenários preparados. Por isso, a validação de identidade deve ocorrer antes do gateway — como agora ocorre — para impedir que um erro de resolução de entidade seja mascarado pelo mock e, principalmente, que chegue a um backend real.
|
||||
|
||||
## Testes executados
|
||||
|
||||
### Regressão + contratos existentes
|
||||
|
||||
- 101 testes passaram no conjunto de contratos, paridade, idempotência e resolução.
|
||||
|
||||
### Novos testes de compatibilidade legado/real
|
||||
|
||||
- 7 testes passaram cobrindo:
|
||||
- prefixo 55 e composição da URL de consulta VAS;
|
||||
- aliases originais e timeout;
|
||||
- aliases de cancelamento;
|
||||
- append de MSISDN em profile full;
|
||||
- Basic auth de divergência via usuário/senha;
|
||||
- client IDs de histórico VAS e contrato;
|
||||
- uso de CompleteInvoices no profile bill.
|
||||
|
||||
### Suite `tests/migration`
|
||||
|
||||
Resultado observado após as mudanças:
|
||||
|
||||
- 660 passed
|
||||
- 4 failed
|
||||
|
||||
As quatro falhas remanescentes são de configuração/contexto de guardrails (`conversation_history` e FRASEOLOGIA) e não estão relacionadas ao `InvoiceResolver` nem aos contratos de integração revisados.
|
||||
|
||||
## Limite desta validação
|
||||
|
||||
A revisão comprova paridade de contrato em nível de código-fonte e testes locais. Ela não é uma certificação de conectividade real porque não foram usados endpoints, credenciais ou rede dos sistemas legados neste ambiente.
|
||||
|
||||
Para homologação real, recomenda-se executar testes de contrato contra um ambiente não produtivo dos serviços TIM, verificando status HTTP, schemas reais, autenticação, timeouts, headers obrigatórios e respostas de erro.
|
||||
|
||||
## Gaps de endurecimento recomendados
|
||||
|
||||
1. Adicionar validação de readiness no startup quando `mock=false`, falhando cedo se endpoint/auth obrigatórios estiverem ausentes.
|
||||
2. Criar testes de contrato contra ambiente de homologação para cada integração ativa.
|
||||
3. Comparar periodicamente fixtures mock com schemas/respostas reais para evitar drift.
|
||||
4. Manter invariantes transacionais: item solicitado, item resolvido e item executado nunca podem divergir silenciosamente.
|
||||
5. Evoluir mascaramento/observabilidade do cliente migrado para o mesmo nível do `HttpGateway` original, sem registrar secrets.
|
||||
472
tests/docs/MANUAL_AGENT_CONTAS_MIGRADO.md
Normal file
472
tests/docs/MANUAL_AGENT_CONTAS_MIGRADO.md
Normal file
@@ -0,0 +1,472 @@
|
||||
# Manual do Agent Contas Migrado para agent_framework_oci
|
||||
|
||||
## 1. Objetivo
|
||||
|
||||
Esta versão reconstrói o Agent Contas sobre o `agent_framework_oci` com uma regra arquitetural simples: **o código executável novo não depende do pacote anterior do Contas**. O projeto anterior é apenas referência funcional para preservar regras, contratos de API, fixtures e comportamentos de negócio durante a migração.
|
||||
|
||||
O agente novo reutiliza do framework tudo que é infraestrutura genérica: LangGraph, router, stickiness, supervisor, confirmação transacional, clarificação, memória, summary memory, long-term memory, checkpoints, persistence, RAG, embeddings, MCP Tool Router, guardrails, output supervisor, judges, identity, channels, SSE, usage accounting e telemetria.
|
||||
|
||||
O novo domínio Contas mantém somente o que é realmente específico da TIM: chamadas de faturas, VAS, contestação, protocolos, tracking, SMS, Secure PDF e regras que relacionam essas operações.
|
||||
|
||||
## 2. Regra de independência
|
||||
|
||||
A Definition of Done da migração é:
|
||||
|
||||
```bash
|
||||
grep -R "agente_contas_tim" app mcp config
|
||||
```
|
||||
|
||||
Resultado esperado: nenhuma ocorrência/import do pacote anterior.
|
||||
|
||||
O pacote entregue já inclui `tests/migration/test_no_legacy_dependency.py` para impedir regressão dessa regra.
|
||||
|
||||
## 3. Arquitetura
|
||||
|
||||
```text
|
||||
Canal / Frontend
|
||||
|
|
||||
v
|
||||
app/main.py
|
||||
|
|
||||
v
|
||||
agent_framework_oci
|
||||
|-- ChannelGateway / IdentityResolver
|
||||
|-- LangGraph / AgentWorkflow
|
||||
|-- EnterpriseRouter / Route Stickiness / Supervisor
|
||||
|-- Guardrails / Output Supervisor / Judges
|
||||
|-- Memory / Summary Memory / LTM / Checkpoints
|
||||
|-- RAG / Embeddings / Cache
|
||||
|-- MCPToolRouter
|
||||
|-- Langfuse / Analytics / OTEL / OCI Streaming
|
||||
|
|
||||
v
|
||||
MCP Contas :8400
|
||||
|
|
||||
v
|
||||
app/domain/contas
|
||||
|-- TimApiClient
|
||||
|-- ContasDomainService
|
||||
`-- fixtures de desenvolvimento
|
||||
|
|
||||
v
|
||||
APIs TIM
|
||||
```
|
||||
|
||||
Não existe um segundo LangGraph, LLM gateway, confirmation manager, workflow engine ou memory store dentro do MCP.
|
||||
|
||||
## 4. Agentes de domínio
|
||||
|
||||
A versão migrada possui quatro agentes reais do domínio Contas:
|
||||
|
||||
| Agente | Responsabilidade |
|
||||
|---|---|
|
||||
| `faturas_agent` | Consulta de faturas, composição, variação e explicação de cobrança |
|
||||
| `vas_agent` | Consulta de VAS, histórico, serviços estratégicos/bundles e informação de serviços |
|
||||
| `contestacao_agent` | Cancelamento transacional de VAS e contestação de cobrança |
|
||||
| `suporte_contas_agent` | Protocolos, acompanhamento, suporte e encerramento |
|
||||
|
||||
Todos herdam `AgentRuntimeMixin` do framework. Eles não implementam máquina de confirmação/clarificação própria.
|
||||
|
||||
## 5. LangGraph do framework
|
||||
|
||||
O fluxo principal é o `StateGraph` do `agent_framework_oci` usado em `app/workflows/agent_graph.py`:
|
||||
|
||||
```text
|
||||
START
|
||||
-> input_guardrails
|
||||
-> load_long_term_memory
|
||||
-> routing_decision
|
||||
-> agente de domínio
|
||||
-> output_supervisor
|
||||
-> output_guardrails
|
||||
-> judge
|
||||
-> supervisor_review
|
||||
-> persist_long_term_memory
|
||||
-> persist
|
||||
-> END
|
||||
```
|
||||
|
||||
O `EnterpriseRouter` decide a intent, agente e tools. O route stickiness decide continuidade da conversa. O runtime do framework controla coleta de parâmetros e confirmação de tools transacionais.
|
||||
|
||||
## 6. Transações
|
||||
|
||||
As tools abaixo são transacionais em `config/tool_policies.yaml`:
|
||||
|
||||
- `cancelar_vas_avulso`
|
||||
- `tratar_vas_estrategico`
|
||||
- `contestar_cobranca`
|
||||
|
||||
A confirmação ocorre **antes** da chamada MCP e é responsabilidade do `AgentRuntimeMixin`. O domínio recebe a chamada somente depois de a política do framework permitir execução.
|
||||
|
||||
Isso evita o problema clássico de um "sim" responder à pergunta errada: a confirmação está vinculada ao estado transacional/tool pendente do framework, não a heurísticas no prompt.
|
||||
|
||||
## 7. RAG
|
||||
|
||||
Conhecimento conceitual não é uma API TIM e por isso não é implementado como "workflow de busca" dentro do MCP.
|
||||
|
||||
O projeto usa diretamente:
|
||||
|
||||
- `RagService`
|
||||
- `create_embedding_provider()`
|
||||
- `VECTOR_STORE_PROVIDER`
|
||||
- `GRAPH_STORE_PROVIDER`
|
||||
- `EMBEDDING_PROVIDER`
|
||||
|
||||
`buscar_informacao` permanece desabilitada no catálogo MCP; perguntas de conhecimento passam pelo RAG nativo do framework.
|
||||
|
||||
## 8. Funcionalidades migradas
|
||||
|
||||
| Funcionalidade do Contas | Nova implementação | Responsabilidade do framework |
|
||||
|---|---|---|
|
||||
| Consulta de faturas | `ContasDomainService.consultar_faturas` | seleção da tool, identity, cache, resposta |
|
||||
| Explicação de fatura | API de fatura + billing analysis como evidência | LLM produz explicação grounded |
|
||||
| Consulta VAS | `consultar_vas` | routing/tool selection |
|
||||
| Histórico VAS | `consultar_historico_vas` | routing/tool selection |
|
||||
| Cancelamento VAS avulso | consulta -> match -> bloqueio -> cancelamento | parâmetros + confirmação + estado |
|
||||
| VAS estratégico/bundle | domínio retorna serviço e orientação | conversa/continuidade no LangGraph |
|
||||
| Contestação | faturas/contrato/profile -> protocolo -> contestação -> tracking | parâmetros + confirmação + estado |
|
||||
| Status de solicitação | `consultar_status_solicitacao` | roteamento e contexto |
|
||||
| SMS | `enviar_sms` | tool policy/contexto |
|
||||
| Secure PDF | `recuperar_fatura_pdf` | roteamento/identity |
|
||||
| Encerramento | efeitos de domínio opcionais | `end_session`, memória e telemetria |
|
||||
| Guardrails | nenhum código local duplicado | framework |
|
||||
| Judges | nenhum código local duplicado | framework |
|
||||
| Memória | nenhum store local de conversa | framework |
|
||||
| LTM | nenhum mecanismo local | framework |
|
||||
| Checkpoint | nenhum `MemorySaver` dentro do MCP | framework |
|
||||
| Telemetria | eventos do runtime/framework | framework |
|
||||
|
||||
## 9. Estrutura do projeto
|
||||
|
||||
```text
|
||||
app/
|
||||
main.py
|
||||
state.py
|
||||
agents/
|
||||
faturas_agent.py
|
||||
vas_agent.py
|
||||
contestacao_agent.py
|
||||
suporte_contas_agent.py
|
||||
domain/contas/
|
||||
client.py
|
||||
service.py
|
||||
fixtures/
|
||||
workflows/
|
||||
agent_graph.py
|
||||
observability/
|
||||
|
||||
config/
|
||||
routing.yaml
|
||||
tools.yaml
|
||||
tool_policies.yaml
|
||||
mcp_servers.yaml
|
||||
mcp_parameter_mapping.yaml
|
||||
identity.yaml
|
||||
prompts/
|
||||
|
||||
contas_mcp/servers/contas_mcp_server/
|
||||
main.py
|
||||
|
||||
agent_framework_oci/
|
||||
... framework reutilizado ...
|
||||
```
|
||||
|
||||
## 10. Arquivo `.env`
|
||||
|
||||
O `.env` fornecido para esta reconstrução foi preservado byte a byte no pacote. Não foi recomposto nem reduzido.
|
||||
|
||||
O arquivo contém dois grupos:
|
||||
|
||||
1. configurações do `agent_framework_oci`;
|
||||
2. variáveis TIM de domínio/compatibilidade já compiladas para os ambientes.
|
||||
|
||||
Embora algumas variáveis antigas possam deixar de ser usadas depois da migração, elas foram mantidas para não perder o trabalho de consolidação. A remoção deve ocorrer apenas após testes de DEV/FQA/PRD.
|
||||
|
||||
### Variáveis principais do framework
|
||||
|
||||
- `LLM_PROVIDER`
|
||||
- `OCI_AUTH_MODE`, `OCI_CONFIG_FILE`, `OCI_PROFILE`, `OCI_COMPARTMENT_ID`, `OCI_REGION`
|
||||
- `SESSION_REPOSITORY_PROVIDER`
|
||||
- `MEMORY_REPOSITORY_PROVIDER`
|
||||
- `CHECKPOINT_REPOSITORY_PROVIDER`
|
||||
- `VECTOR_STORE_PROVIDER`
|
||||
- `GRAPH_STORE_PROVIDER`
|
||||
- `EMBEDDING_PROVIDER`
|
||||
- `ENABLE_LANGFUSE`
|
||||
- `ENABLE_INPUT_GUARDRAILS`
|
||||
- `ENABLE_OUTPUT_GUARDRAILS`
|
||||
- `ENABLE_JUDGES`
|
||||
- `ENABLE_SUPERVISOR`
|
||||
- `ENABLE_ROUTE_STICKINESS`
|
||||
- `ENABLE_MCP_TOOLS`
|
||||
- `ENABLE_CONVERSATION_SUMMARY_MEMORY`
|
||||
- `ENABLE_LONG_TERM_MEMORY`
|
||||
|
||||
### Modo mock x APIs TIM reais
|
||||
|
||||
Mock atual:
|
||||
|
||||
```env
|
||||
TIM_GATEWAY_MODE=mock
|
||||
TIM_USE_MOCK_GATEWAY=true
|
||||
```
|
||||
|
||||
Integrações reais:
|
||||
|
||||
```env
|
||||
TIM_GATEWAY_MODE=real
|
||||
TIM_USE_MOCK_GATEWAY=false
|
||||
```
|
||||
|
||||
Os dois valores devem estar coerentes.
|
||||
|
||||
## 11. Integrações TIM
|
||||
|
||||
| Integração | Variável | Método | VPN TIM provável |
|
||||
|---|---|---|---|
|
||||
| Complete Invoices | `TIM_COMPLETE_INVOICES_URL` | POST | Sim em FQA interno |
|
||||
| Billing Analysis | `TIM_DIVERGENCIA_URL` | POST | Sim |
|
||||
| Consulta VAS | `TIM_URL_CONSULTA_VAS` | GET | Sim |
|
||||
| Histórico VAS | `TIM_VAS_HISTORY_URL` | GET | Sim |
|
||||
| Bloqueio VAS | `TIM_URL_BLOQUEIO_VAS` | POST | Sim |
|
||||
| Cancelamento VAS | `TIM_CANCELAMENTO_URL` | DELETE | Sim |
|
||||
| Contrato | `TIM_CONTRATO_URL` | GET | Sim |
|
||||
| Full Profile | `TIM_PROFILE_FULL_URL` | GET | Sim |
|
||||
| Contestação | `TIM_CUSTOMER_CONTESTATION_URL` | POST | Sim |
|
||||
| Protocolo | `TIM_PROTOCOL_URL` | POST | Sim |
|
||||
| Service Request Status | `TIM_SERVICE_REQUEST_STATUS_URL` | POST | Sim |
|
||||
| Tracking Activities | `TIM_TRACKING_ACTIVITIES_URL` | POST | Sim |
|
||||
| SMS | `TIM_SMS_URL` | POST | Sim |
|
||||
| Secure PDF | `TIM_URL_INVOICE_RECOVER` | POST | Sim |
|
||||
|
||||
Os endpoints FQA do `.env` usam `pmidfqa.internal.timbrasil.com.br`; portanto DNS/rota corporativa precisa estar disponível para teste real.
|
||||
|
||||
## 12. Outras integrações
|
||||
|
||||
| Integração | Uso | Ativação |
|
||||
|---|---|---|
|
||||
| OCI GenAI | LLM | `LLM_PROVIDER=oci_sdk` + `OCI_AUTH_MODE` |
|
||||
| Autonomous DB | session/memory/checkpoint/vector/usage | providers `autonomous` + `ADB_*` |
|
||||
| OCI Embeddings | RAG | `EMBEDDING_PROVIDER=oci` |
|
||||
| Langfuse | tracing | `ENABLE_LANGFUSE=true` |
|
||||
| GCP Pub/Sub | analytics corporativo | `ENABLE_ANALYTICS=true`, provider Pub/Sub e credencial GCP |
|
||||
| MongoDB | sequence Pub/Sub | `PUBSUB_SEQUENCE_PROVIDER=mongodb` |
|
||||
| Redis | cache/sequence opcional | `ENABLE_REDIS_CACHE=true` ou provider sequence redis |
|
||||
| OTEL | logs/traces | `ENABLE_OTEL=true` + endpoint |
|
||||
| OCI Streaming | eventos alternativos | `ENABLE_OCI_STREAMING=true` |
|
||||
|
||||
## 13. Instalação local
|
||||
|
||||
Recomendado: Linux/WSL com Python 3.13.
|
||||
|
||||
```bash
|
||||
cd contas_migrado_framework_native
|
||||
uv sync
|
||||
```
|
||||
|
||||
Se o `uv` ainda não estiver disponível, instale-o conforme o padrão do seu ambiente e depois execute `uv sync`.
|
||||
|
||||
## 14. Subir o MCP Contas
|
||||
|
||||
Terminal 1:
|
||||
|
||||
```bash
|
||||
uv run uvicorn contas_mcp.servers.contas_mcp_server.main:app \
|
||||
--host 0.0.0.0 --port 8400
|
||||
```
|
||||
|
||||
Validar:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8400/health
|
||||
curl http://localhost:8400/mcp/tools/list
|
||||
```
|
||||
|
||||
`/health` deve reportar:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"architecture": "framework-native",
|
||||
"legacy_dependency": false
|
||||
}
|
||||
```
|
||||
|
||||
## 15. Subir o Agent Contas
|
||||
|
||||
Terminal 2:
|
||||
|
||||
```bash
|
||||
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
Validar:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/health
|
||||
```
|
||||
|
||||
## 16. Smoke test do MCP em mock
|
||||
|
||||
```bash
|
||||
PYTHONPATH=".:agent_framework_oci/libs/agent_framework/src" \
|
||||
python scripts/smoke_mcp.py
|
||||
```
|
||||
|
||||
Esse teste cobre faturas, invoice explanation, VAS, histórico, cancelamento e contestação usando fixtures migradas para o novo domínio.
|
||||
|
||||
## 17. Testar o agente pelo Gateway
|
||||
|
||||
Exemplo de consulta:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/gateway/message \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"channel":"web",
|
||||
"agent_id":"telecom_contas",
|
||||
"tenant_id":"default",
|
||||
"payload":{
|
||||
"text":"Quero consultar minha fatura",
|
||||
"session_id":"contas-test-001",
|
||||
"user_id":"user-001",
|
||||
"msisdn":"11999999999",
|
||||
"message_id":"msg-001"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
Depois teste continuidade na mesma `session_id`.
|
||||
|
||||
## 18. Teste transacional de VAS
|
||||
|
||||
1. Envie: `Quero cancelar TIM Fashion Mensal`.
|
||||
2. O framework deve identificar tool transacional e pedir confirmação.
|
||||
3. Responda `sim` na mesma sessão.
|
||||
4. Somente então `cancelar_vas_avulso` deve ser chamada.
|
||||
5. Em modo mock, o resultado deve conter `block.status=200` e `cancellation.status=200`.
|
||||
|
||||
Teste negativo importante:
|
||||
|
||||
1. Entre em estado aguardando confirmação de cancelamento.
|
||||
2. Envie `você ainda está por aí?`.
|
||||
3. A frase não pode confirmar a transação.
|
||||
|
||||
## 19. Teste de contestação
|
||||
|
||||
Em mock:
|
||||
|
||||
```text
|
||||
Quero contestar Tamboro Mensal no valor de 14,99, não reconheço essa cobrança.
|
||||
```
|
||||
|
||||
Esperado:
|
||||
|
||||
- route `contestacao_agent`;
|
||||
- parâmetros `subject` e `valor` coletados;
|
||||
- confirmação antes da mutação;
|
||||
- abertura de protocolo;
|
||||
- contestação;
|
||||
- tracking;
|
||||
- resposta final grounded nos retornos da tool.
|
||||
|
||||
## 20. Testes de memória
|
||||
|
||||
### Short-term / summary
|
||||
|
||||
Na mesma sessão:
|
||||
|
||||
```text
|
||||
Meu serviço é TIM Fashion Mensal.
|
||||
...
|
||||
Qual serviço eu mencionei antes?
|
||||
```
|
||||
|
||||
### Long-term memory
|
||||
|
||||
Com `ENABLE_LONG_TERM_MEMORY=true`, grave uma informação elegível, encerre a sessão e abra outra sessão com a mesma identidade de negócio. Verifique se o contexto é recuperado conforme as regras de LTM do framework.
|
||||
|
||||
## 21. Testes de RAG
|
||||
|
||||
Valide que a intent de conhecimento não dispara MCP desnecessariamente. Exemplos:
|
||||
|
||||
```text
|
||||
O que significa cobrança proporcional?
|
||||
Como funciona o vencimento da fatura?
|
||||
```
|
||||
|
||||
O trace deve mostrar `RagService`; a tool `buscar_informacao` não precisa ser executada.
|
||||
|
||||
## 22. Testes de guardrails e judges
|
||||
|
||||
Com as flags habilitadas no `.env`:
|
||||
|
||||
- prompt injection deve passar pelos input guardrails;
|
||||
- resposta candidata passa pelo Output Supervisor/output guardrails;
|
||||
- groundedness deve considerar `mcp_results` quando a resposta usa dados TIM;
|
||||
- judges rodam após a geração e antes da persistência final.
|
||||
|
||||
Use o Langfuse para observar a sequência de nodes do LangGraph.
|
||||
|
||||
## 23. Teste de conectividade/VPN
|
||||
|
||||
O pacote contém:
|
||||
|
||||
```bash
|
||||
python scripts/check_integrations.py
|
||||
```
|
||||
|
||||
O script resolve DNS e testa TCP dos endpoints configurados. Execute antes e depois de conectar a VPN.
|
||||
|
||||
Para APIs FQA, um resultado `DNS_FAIL`, `TCP_FAIL` ou timeout indica que a rede ainda não está pronta. Um `TCP_OK` prova conectividade de rede, mas não autenticação/contrato HTTP.
|
||||
|
||||
## 24. Ativar APIs reais gradualmente
|
||||
|
||||
Não habilite todas as mutações de uma vez. Ordem recomendada:
|
||||
|
||||
1. VPN/DNS;
|
||||
2. `consultar_faturas`;
|
||||
3. `consultar_vas`;
|
||||
4. `consultar_historico_vas`;
|
||||
5. contrato/profile;
|
||||
6. billing analysis;
|
||||
7. Secure PDF;
|
||||
8. protocol/status/tracking;
|
||||
9. SMS;
|
||||
10. cancelamento VAS;
|
||||
11. contestação.
|
||||
|
||||
Depois faça teste end-to-end completo.
|
||||
|
||||
## 25. Kubernetes
|
||||
|
||||
Use o mesmo `.env` como fonte para construir ConfigMap/Secret, separando segredos no mecanismo corporativo apropriado. O backend necessita alcançar:
|
||||
|
||||
- MCP Contas;
|
||||
- OCI GenAI;
|
||||
- Autonomous DB;
|
||||
- Langfuse, se habilitado;
|
||||
- endpoints TIM internos em modo real;
|
||||
- providers de analytics habilitados.
|
||||
|
||||
O MCP pode rodar no mesmo pod como sidecar ou, preferencialmente, como deployment/service separado. Configure `config/mcp_servers.yaml` para o DNS do Service Kubernetes.
|
||||
|
||||
## 26. Critérios de aceite da migração
|
||||
|
||||
A migração é considerada concluída quando:
|
||||
|
||||
- [ ] zero imports/referências executáveis ao pacote anterior;
|
||||
- [ ] backend e MCP sobem após o diretório anterior ser removido;
|
||||
- [ ] read-only APIs funcionam em FQA;
|
||||
- [ ] cancelamento exige confirmação do framework e funciona em FQA;
|
||||
- [ ] contestação exige confirmação e reproduz efeitos esperados;
|
||||
- [ ] RAG usa `RagService` do framework;
|
||||
- [ ] memory/summary/LTM/checkpoint usam providers do framework;
|
||||
- [ ] guardrails e judges aparecem nos traces;
|
||||
- [ ] LangGraph é a única máquina de estados conversacional;
|
||||
- [ ] Pub/Sub/sequence/OTEL/Langfuse são validados conforme ambiente;
|
||||
- [ ] testes de carga são executados antes de produção.
|
||||
|
||||
## 27. Segurança do `.env`
|
||||
|
||||
O arquivo preservado contém material sensível. Ele foi mantido porque isso foi um requisito explícito da reconstrução. Para distribuição fora do ambiente controlado, rotacione credenciais expostas e substitua valores por Secrets/Vault/Key Vault/Kubernetes Secret conforme política corporativa.
|
||||
1881
tests/docs/MANUAL_DESENVOLVEDOR_WORKFLOWS_CONTAS.md
Normal file
1881
tests/docs/MANUAL_DESENVOLVEDOR_WORKFLOWS_CONTAS.md
Normal file
File diff suppressed because it is too large
Load Diff
223
tests/docs/MATRIZ_MIGRACAO.md
Normal file
223
tests/docs/MATRIZ_MIGRACAO.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# Matriz de Migração — Contas -> agent_framework_oci
|
||||
|
||||
| Capacidade | Destino novo | Reuso framework | Código de domínio novo | Dependência anterior |
|
||||
|---|---|---:|---:|---:|
|
||||
| LangGraph | `app/workflows/agent_graph.py` | Sim | composição mínima | Não |
|
||||
| Router | EnterpriseRouter | Sim | routing.yaml | Não |
|
||||
| Stickiness | framework | Sim | configuração | Não |
|
||||
| Supervisor | framework | Sim | configuração | Não |
|
||||
| Confirmação | AgentRuntimeMixin | Sim | tool policy | Não |
|
||||
| Clarificação | AgentRuntimeMixin/MCP mapping | Sim | schemas/mapping | Não |
|
||||
| Sessions | framework repository | Sim | Não | Não |
|
||||
| Message memory | framework | Sim | Não | Não |
|
||||
| Summary memory | framework | Sim | Não | Não |
|
||||
| LTM | framework | Sim | Não | Não |
|
||||
| Checkpoint | framework | Sim | Não | Não |
|
||||
| RAG | RagService | Sim | conteúdo/config | Não |
|
||||
| Guardrails | GuardrailPipeline | Sim | config | Não |
|
||||
| Output Supervisor | framework | Sim | Não | Não |
|
||||
| Judges | JudgePipeline | Sim | config | Não |
|
||||
| MCP router | framework | Sim | tool catalog | Não |
|
||||
| Faturas | MCP/domain | Não aplicável | Sim | Não |
|
||||
| Billing Analysis | MCP/domain | Não aplicável | Sim | Não |
|
||||
| Consulta/Histórico VAS | MCP/domain | Não aplicável | Sim | Não |
|
||||
| Bloqueio/Cancelamento VAS | MCP/domain | confirmação no framework | Sim | Não |
|
||||
| Contestação | MCP/domain | confirmação/estado no framework | Sim | Não |
|
||||
| Protocol/Status/Tracking | MCP/domain | contexto no framework | Sim | Não |
|
||||
| SMS | MCP/domain | contexto no framework | Sim | Não |
|
||||
| Secure PDF | MCP/domain | contexto no framework | Sim | Não |
|
||||
| Langfuse | framework | Sim | Não | Não |
|
||||
| Pub/Sub/sequence | framework | Sim | configuração | Não |
|
||||
| OCI Streaming | framework | Sim | configuração | Não |
|
||||
| OTEL | framework | Sim | configuração | Não |
|
||||
|
||||
## Regra
|
||||
|
||||
O pacote anterior não é uma biblioteca do novo projeto. Se uma regra específica for necessária, ela deve ser portada e testada dentro de `app/domain/contas`; infraestrutura genérica deve ser eliminada em favor do framework.
|
||||
|
||||
|
||||
## Contratos TIM validados por regressão
|
||||
|
||||
| Integração | Paridade coberta | Estado |
|
||||
|---|---|---|
|
||||
| CompleteInvoices | método/payload/header `ClientID` | ✅ |
|
||||
| Query VAS | URL por MSISDN, `clientId=AIAAGENTCR`, auth | ✅ |
|
||||
| VAS History | query `msisdn`, `clientId`, `messageId`, auth | ✅ |
|
||||
| Block VAS | payload PMid + fallbacks e headers | ✅ |
|
||||
| Cancel VAS | DELETE, channel, protocol, headers, messageId | ✅ |
|
||||
| Contract Information | GET por MSISDN, `clientId`, auth | ✅ |
|
||||
| Profile/Line Info | GET e header `ClientID` | ✅ |
|
||||
| Billing Analysis | GET por MSISDN + channel | ✅ |
|
||||
| Bill PDF | POST detalhado + criptografia | ✅ |
|
||||
| Secure PDF | GET com parâmetros criptografados | ✅ |
|
||||
| Customer Contestation | payload/headers principais | ✅ |
|
||||
| Service Request Status | envelope `serviceRequest` | ✅ |
|
||||
| Tracking Activities | customer/invoice/activity/user | ✅ |
|
||||
| Protocol V2 | envelope Siebel + headers corporativos | ✅ |
|
||||
| SMS Barcode | payload completo + retry/RCT | ✅ |
|
||||
|
||||
## Jornadas compostas e comportamento conversacional
|
||||
|
||||
| Capacidade original | Implementação migrada | Reuso do framework | Regressão |
|
||||
|---|---|---:|---:|
|
||||
| Cancelamento VAS -> contestação | dois workflows encadeados no MCP | `WorkflowRuntime` | ✅ |
|
||||
| Composição final cancelamento | `app/domain/contas/vas_cancellation_message.py` | domínio determinístico | ✅ 19 casos originais |
|
||||
| Fallback VAS History | `ContasDomainService.cancelar_vas_avulso` | transporte via adapter | ✅ |
|
||||
| Cancelamento parcial em lote | action expõe cancelados/falhas/candidatos | WorkflowRuntime + IdempotencyStore | ✅ |
|
||||
| Idempotência transacional | `create_idempotency_store()` | framework | ✅ |
|
||||
| Replay pós-finalização | Channel short-circuit | framework | ✅ |
|
||||
| Idle nudge replay | Channel short-circuit | framework | ✅ |
|
||||
| Processing interruption | replay + classificador LLM fail-safe | `LLMProvider` framework | ✅ |
|
||||
| Correção Fim/Mim -> Sim | channel transcription | framework | ✅ |
|
||||
| Finalização status/summary | regra pura de domínio | workflow framework | ✅ |
|
||||
| Protocolo informacional final | action + ProtocolV2 | WorkflowRuntime | ✅ |
|
||||
|
||||
### Bootstrap MCP
|
||||
|
||||
`WorkflowRuntime`, checkpointer e `IdempotencyStore` são inicializados de forma lazy. Isto evita dependência de Oracle/Redis para endpoints de diagnóstico e garante que o backend durável só seja aberto quando um workflow realmente precisar ser executado.
|
||||
|
||||
## Incrementos de paridade - baseline 420
|
||||
|
||||
| Capacidade original | Implementação migrada | Responsabilidade | Estado |
|
||||
|---|---|---|---:|
|
||||
| InvoiceContextProvider / prefetch | `InvoiceContextService` + `agent_framework.cache.Cache` | domínio escolhe evidências; framework fornece cache | ✅ |
|
||||
| Isolamento de invoice context por sessão | chave `session_id:msisdn:invoice_id` | framework cache | ✅ |
|
||||
| Plano família titular/dependente | normalização antes do workflow + contestação única no titular | domínio + WorkflowRuntime | ✅ |
|
||||
| Correção de linha por invoice detail | normalização determinística | domínio | ✅ |
|
||||
| CVAL fail-stop | edge `success=false -> END` | WorkflowRuntime | ✅ |
|
||||
| Snapshot parcial em falha | `WorkflowRuntime` recupera último state do LangGraph | framework | ✅ |
|
||||
| Retry Billing Analysis / RCT 079-084 | metadata `_transport` + `RCTPolicy` | domínio define códigos; observer publica | ✅ |
|
||||
| Finalização invoice explanation | protocolo informacional somente após workflow executado | domínio + WorkflowRuntime | ✅ |
|
||||
| VEB fechado / force RT15 | reuso ou novo protocolo conforme flags | domínio | ✅ |
|
||||
| Inicialização AgentWorkflow | router/agentes/grafo dentro de `__init__` | aplicação/framework | ✅ |
|
||||
|
||||
### Incrementos de paridade — baseline 435
|
||||
|
||||
| Capacidade original | Implementação migrada | Responsabilidade | Status |
|
||||
|---|---|---|---|
|
||||
| Invoice prefetch single-flight | `InvoiceContextService` + `agent_framework.cache.Cache` | domínio escolhe evidências; framework fornece cache | ✅ |
|
||||
| CVN de prefetch sem duplicação | `business_events` + cache markers | domínio define código; observer framework publica | ✅ |
|
||||
| Latch invoice/workflow já executado | `AgentRuntimeMixin.business_workflows_executed` | framework | ✅ |
|
||||
| Batch cancellation max 5 | action async + semaphore | domínio action sobre WorkflowRuntime | ✅ |
|
||||
| Protocolo por linha antes de cancelar | action `cancelamento_vas_avulso_batch` | domínio + adapter TIM | ✅ |
|
||||
| Erro estruturado de workflow | `WorkflowRunResult.error_details` | framework | ✅ |
|
||||
| Provider error de contestação | MCP mapping sobre `error_details` | domínio/MCP fino | ✅ |
|
||||
|
||||
|
||||
## Baseline 440 testes - continuação
|
||||
|
||||
- 440 testes de migração passando; 2 skipped por dependências indisponíveis neste runtime (`langgraph`/`jellyfish`).
|
||||
- Cancelamento VAS: falha de bloqueio/cancelamento pode permanecer candidata à contestação sem mascarar sucesso.
|
||||
- Resposta composta preserva protocolos de cancelamento por linha + protocolo de contestação e sinaliza finalização sugerida.
|
||||
- Plano família: protocolo de cancelamento em dependente resolve `socialSecNo` via LineInfo da própria linha.
|
||||
- Registro de protocolo aceita aliases de resposta `interactionProtocol`, `protocolNumber`, `protocol` e `protocolo`.
|
||||
- Finalização informacional recalcula Bundle/Estratégico/Avulso pela evidência de `invoice_detail`/Billing Analysis quando disponível.
|
||||
|
||||
## Finalização - paridade adicional (baseline 533)
|
||||
|
||||
| Capability original | Implementação migrada | Framework reutilizado | Evidência |
|
||||
|---|---|---|---|
|
||||
| CVN aceite/recusa no encerramento | `finalizar_atendimento_action` | `business_events` + `AgentObserver` | testes de finalização estendida |
|
||||
| Protocolo RT-15 informacional | action de domínio + TIM client | WorkflowRuntime/observer/idempotência | RCT.085/086 + CVN.010/011 |
|
||||
| Nota de invoice explanation | valor canônico `Explicação dos valores da fatura` | workflow latch do framework | regressão |
|
||||
| Handoff/retention suppression | flags no state/domain action | estado persistido do framework | regressão |
|
||||
| SAD decision tree | `SAD.001/002/003/004/005/006/007` como business events | AgentObserver | regressão |
|
||||
| Classificação VAS sem invoice detail | aliases determinísticos de domínio | nenhuma engine paralela | regressão |
|
||||
| Regressão offline de workflow | `WorkflowRuntime(... allow_deterministic_fallback=True)` somente em teste | DSL/actions do framework | 18 casos históricos executados |
|
||||
|
||||
## Atualização de paridade - baseline 550
|
||||
|
||||
| Capability | Original | Migrado | Evidência |
|
||||
|---|---|---|---|
|
||||
| Finalização com prefetch de fatura | CVN lookup + RT-15 quando aplicável | Implementado | regressão de finalização |
|
||||
| Supressão após transição para negócio | não reemite CVN/MPI | Implementado | regressão dedicada |
|
||||
| VEB terminal | não duplica RT-15/CVN | Implementado | regressão dedicada |
|
||||
| Precedência de tipo pela fatura | total/parcial/sem match | Implementado | regressão dedicada |
|
||||
| Protocolo já existente | não duplica RT-15 | Implementado | regressão dedicada |
|
||||
| Matcher fonético/transcrição | catálogo real | Implementado sem xfails | suíte de transcrição |
|
||||
|
||||
## VAA — rastreabilidade de cancelamento/contestação
|
||||
|
||||
| Capability histórica | Implementação migrada | Framework reutilizado | Estado |
|
||||
|---|---|---|---|
|
||||
| VAA.001–004 cancelamento | `cancelamento_vas_avulso_batch` retorna `business_events` | AgentObserver / analytics | OK |
|
||||
| VAA.005–009 contestação/boleto | `abrir_contestacao_cliente` retorna `business_events` | AgentObserver / analytics | OK |
|
||||
| VAA.012–015 SMS | `enviar_sms` retorna `business_events` e mantém fluxo em erro | AgentObserver / analytics | OK |
|
||||
| VAA.016–017 status SR | `atualizar_status_sr` retorna `business_events` | AgentObserver / analytics | OK |
|
||||
|
||||
|
||||
## Baseline 560 testes - metadata corporativa TIM
|
||||
|
||||
- 560 testes de migração passando, sem skips/xfails.
|
||||
- Business events preservam contexto corporativo: `agentProtocolId`, `adjustedProtocol`, `billingId`, `uraCallId`, `channelId`, `sessionId`, `messageId` e `agentSpecificData`.
|
||||
- Contestação VAA.005-009 preserva itens/valores ajustados e protocolo.
|
||||
- Finalização CVN.010/011 preserva protocolo TIM/URA e billing context.
|
||||
- RCTs derivados de retry HTTP carregam `apiUrl`, `apiStatusCode`, `apiResponsePayload` e `latencyMs`.
|
||||
- SMS e Service Request Status carregam metadata de transporte e contexto de protocolo.
|
||||
- `tim_payload_mapper` aceita o formato histórico `agentSpecificData` JSON-string e o converte para o objeto canônico TIM no payload final.
|
||||
|
||||
### Paridade de eventos conversacionais — baseline 565
|
||||
|
||||
| Família | Paridade adicionada |
|
||||
|---|---|
|
||||
| VEB | ordem dos branches de VAS estratégico e metadata do turno/URA |
|
||||
| MPI | contexto de invoice explanation, pró-rata e cancelamento |
|
||||
| CVN | contexto conversacional/protocolo no encerramento |
|
||||
| SAD | `llmResponse`, `messageId`, sessão/canal e `sessionEndAt` normalizado |
|
||||
|
||||
## Incremento de paridade — baseline 570
|
||||
|
||||
| Funcionalidade original | Implementação migrada | Responsabilidade |
|
||||
|---|---|---|
|
||||
| Cancelamento solicitado para item estratégico/bundle | `InvoiceResolver` redireciona para workflow `vas_estrategico` | Domínio + WorkflowRuntime |
|
||||
| Invoice explanation SIM | recomenda `resolvido` pelo último node do workflow | MCP adapter fino sobre WorkflowRuntime |
|
||||
| Invoice explanation NÃO sem VAS variado | recomenda `nao_resolvido` | MCP adapter fino sobre WorkflowRuntime |
|
||||
| Pró-rata aceito / sem Plano Controle | recomenda `resolvido` | MCP adapter fino sobre WorkflowRuntime |
|
||||
| Orientação de cancelamento de VAS estratégico por parceiro | action declara `requires_rag/rag_queries`; `AgentRuntimeMixin` chama `RagService` | Framework |
|
||||
| Gate padrão de regressão | `pytest -q` -> `tests/` | Projeto migrado |
|
||||
|
||||
## Baseline 578 - wrapper cancelamento + LLM composition
|
||||
|
||||
- 593 testes passando; zero skipped/xfail.
|
||||
- Cancelamento preserva `auto_finalize_on_failure` em falha técnica de contestação.
|
||||
- Item já contestado não mascara cancelamento concluído como falha sistêmica.
|
||||
- `cancelamento_vas_protocol` e todos os protocolos de resposta são preservados.
|
||||
- Plano família contesta titular + dependentes em uma única `contestacao_tool`.
|
||||
- Lote com muitos no-match continua contestando exatamente os candidatos elegíveis.
|
||||
- Pró-rata usa `requires_llm_composition` do framework em vez de gateway LLM de domínio.
|
||||
|
||||
|
||||
### Paridade de wrappers históricos — baseline 593
|
||||
|
||||
| Comportamento original | Implementação migrada | Estado |
|
||||
|---|---|---|
|
||||
| `tipo_atendimento=contestacao` | `_workflow_payload(contestacao_tool)` | ✅ |
|
||||
| Contexto do turno no workflow | payload MCP preserva IDs/canal/mensagem | ✅ |
|
||||
| CPF como alias de socialSecNo | normalização no wrapper composto | ✅ |
|
||||
| `cancelados` sem `results.success` | fallback agregado do wrapper | ✅ |
|
||||
| `itens_para_contestacao` | alias de `contestation_candidates` | ✅ |
|
||||
| falha block/cancel ainda elegível a RT-02 | composição de dois WorkflowRuntime | ✅ |
|
||||
| SMS falha sem derrubar jornada | `sms_not_send_error` | ✅ |
|
||||
| Bundle + Estratégico + NÃO | protocolo deferido + RAG obrigatório | ✅ |
|
||||
|
||||
## Complemento de paridade — baseline 599
|
||||
|
||||
| Comportamento histórico | Implementação migrada | Prova |
|
||||
|---|---|---|
|
||||
| Itens já contestados | normalização em `abrir_contestacao_cliente` + compositor determinístico | teste de wrapper 1:1 |
|
||||
| Itens contestados/não contestados | classificação na action de domínio | regressão de contestação |
|
||||
| Total contestado somente dos itens aceitos | cálculo determinístico no domínio | regressão de contestação |
|
||||
| Contestação retorna valor zero | fallback para total efetivamente cancelado | teste de wrapper 1:1 |
|
||||
| Valor na fala em pt-BR | normalização na borda MCP | teste `R$ 14,99` |
|
||||
| `next_subject` | ignorado pelo compositor determinístico | teste de wrapper 1:1 |
|
||||
| Billing Analysis indisponível | fraseologia canônica e `auto_finalize_on_failure=false` | regressão invoice explanation |
|
||||
|
||||
### Cobertura 1:1 de wrappers — baseline 615
|
||||
|
||||
Além dos testes de domínio/actions/workflows, a suíte passa a reproduzir diretamente
|
||||
outcomes históricos dos wrappers `cancelar_vas_single` e `finalize_support`, cobrindo
|
||||
no-match, candidatos explícitos, falhas parciais RT-01→RT-02, SMS, protocolos,
|
||||
`protocol_closed`, plano família/titular-dependente e protocolo informacional deferido.
|
||||
|
||||
Esses testes são classificados como **paridade explícita de contrato externo**, e não
|
||||
apenas cobertura indireta por actions internas.
|
||||
67
tests/docs/OBSERVABILITY_CODE_MAPPING.md
Normal file
67
tests/docs/OBSERVABILITY_CODE_MAPPING.md
Normal 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`.
|
||||
43
tests/docs/OBSERVABILITY_CONTRACT_REGISTRY.md
Normal file
43
tests/docs/OBSERVABILITY_CONTRACT_REGISTRY.md
Normal 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.
|
||||
26
tests/docs/OBSERVABILITY_OVERLAY_MERGE_FIX.md
Normal file
26
tests/docs/OBSERVABILITY_OVERLAY_MERGE_FIX.md
Normal 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.
|
||||
83
tests/docs/OUTPUT_SUPERVISOR_DECLARATIVE_POLICIES.md
Normal file
83
tests/docs/OUTPUT_SUPERVISOR_DECLARATIVE_POLICIES.md
Normal 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.
|
||||
112
tests/docs/PENTE_FINO_PARIDADE_TOOLS_CONTAS.md
Normal file
112
tests/docs/PENTE_FINO_PARIDADE_TOOLS_CONTAS.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Pente-fino de paridade — Contas original x Contas migrado
|
||||
|
||||
## Escopo
|
||||
|
||||
Comparação funcional e arquitetural das 18 capabilities solicitadas, tomando como fonte de verdade o projeto Contas original e preservando, no migrado, as responsabilidades genéricas do `agent_framework_oci` (roteamento, confirmação, pause/resume, RAG, memória, observabilidade e política de tools).
|
||||
|
||||
Tools/capabilities avaliadas:
|
||||
|
||||
`consultar_faturas`, `consultar_plano`, `invoice_explanation`, `buscar_informacao`, `consultar_vas`, `consultar_historico_vas`, `cancelar_vas_avulso`, `tratar_vas_estrategico`, `validar_contestacao`, `contestar_cobranca`, `finalizar_atendimento`, `consultar_status_solicitacao`, `enviar_sms`, `recuperar_fatura_pdf`, `pro_rata`, `termino_desconto`, `valor_divergente`, `retomar_workflow`.
|
||||
|
||||
## Resultado executivo
|
||||
|
||||
Depois das correções deste pente-fino, as 18 capabilities estão expostas no MCP e habilitadas no registry do agente. Os workflows conversacionais principais foram preservados do original. Os YAMLs `buscar_fatura`, `buscar_informacao`, `cancelamento_vas_avulso`, `finalizar_atendimento`, `invoice_explanation`, `pro_rata`, `termino_desconto`, `valor_divergente` e `vas_estrategico` permanecem equivalentes ao original. `contestacao_tool` contém uma diferença intencional de segurança: se a validação financeira falhar, o fluxo termina antes de SMS/contrato/SR.
|
||||
|
||||
A suíte completa do projeto após as mudanças executa **715 testes com sucesso**.
|
||||
|
||||
## Paridade por capability
|
||||
|
||||
| Capability | Fonte/semântica no original | Situação após pente-fino | Ação tomada |
|
||||
|---|---|---|---|
|
||||
| `consultar_faturas` | `complete_invoices` + prefetch de `bill_pdf` + resumo semântico | Corrigida | Mantida a API de Complete Invoices e restaurados `invoice_amount`, `invoice_amount_open`, período e emissão a partir do PDF, sem inventar campo no backend. |
|
||||
| `consultar_plano` | Evidência da própria fatura/billing analysis | OK | Extração determinística de seções `Plano/Planos`; não usa RAG nem inferência livre da LLM. |
|
||||
| `invoice_explanation` | Workflow v2 + evidência da fatura + capability LLM de reescrita + pause | Corrigida | `invoice_detail` e resumo semântico agora chegam ao workflow; composição volta a ser da LLM do framework; `await_user_input=True` restaurado. |
|
||||
| `buscar_informacao` | Tool RAG ativa (`queries` e `query` legado) | Corrigida arquiteturalmente | Reexposta como façade MCP compatível. Não duplica RAG no domínio: retorna `requires_rag` e delega ao `RagService` do framework. Suporta `queries[]` e `query`. |
|
||||
| `consultar_vas` | Consulta de VAS ativos | OK | Mantida integração direta e resolução de domínio para os fluxos que precisam classificar item. |
|
||||
| `consultar_historico_vas` | Histórico de VAS/serviços | OK | Mantido contrato da integração e normalização usada pelo cancelamento. |
|
||||
| `cancelar_vas_avulso` | Tool ativa, somente avulso, confirmação, cancelamento + contestação automática | OK | Confirmação permanece no framework; preflight resolve nome/classe contra a fatura; workflow composto preserva cancelamento + contestação e protocolos. |
|
||||
| `tratar_vas_estrategico` | `vas_estrategico`, bundle/estratégico | OK | Alias semântico migrado para `tratar_vas_estrategico`; workflow v3 preservado; redirecionamento automático evita cancelar estratégico como avulso. |
|
||||
| `validar_contestacao` | Não existia como tool pública; regras CVAL existiam na execução | Corrigida | A pré-validação agora usa a mesma `validate_contestation_items` da execução financeira; deixa de aprovar algo que seria bloqueado depois. Usa o valor pedido pelo cliente, não o `resolved_value` da fatura. |
|
||||
| `contestar_cobranca` | Workflow/ações de contestação e Conta Certa | OK + hardening | Workflow preservado e CVAL fail-closed. Adicionado edge de segurança para não continuar com SMS/contrato/SR após falha financeira. |
|
||||
| `finalizar_atendimento` | Tool ativa; `status` obrigatório e regras estritas de finalização | Corrigida | `status` voltou a ser requisito explícito e é extraído genericamente pelo framework com enum semântico; `erro_falha_sistema` continua reservado ao sistema. |
|
||||
| `consultar_status_solicitacao` | Integração de status SR usada internamente | Corrigida | Removido mapeamento incorreto `interaction_key -> protocol` (interaction_key é identidade da interação/mensagem, não protocolo). Protocolo é extraído da fala ou recuperado de aliases do contexto de workflow. |
|
||||
| `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; 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. |
|
||||
|
||||
## Correções relevantes encontradas
|
||||
|
||||
### 1. Contexto de fatura
|
||||
|
||||
O original não obtinha o valor total diretamente de `complete_invoices`. Ele construía um contexto enriquecido a partir do PDF parseado. A migração já possuía parser e `InvoiceContextService`, mas faltava publicar integralmente o resumo semântico. Foi restaurada a cadeia:
|
||||
|
||||
`Complete Invoices -> invoice/customer id -> Bill PDF -> parser -> total_geral -> invoice_amount/invoice_amount_open`.
|
||||
|
||||
### 2. `invoice_explanation`
|
||||
|
||||
O YAML migrado preservava o `pause`, mas a action migrada não retornava `await_user_input=True`, ao contrário do original. Isso tornava a condição de pause falsa. Também havia sido eliminada a etapa de composição LLM específica. Agora a action retorna o gate de pause e uma diretiva `requires_llm_composition`, mantendo a LLM no framework, não no domínio.
|
||||
|
||||
### 3. `buscar_informacao`
|
||||
|
||||
A route ainda referenciava `buscar_informacao`, mas a tool estava `enabled: false` e nem era exposta pelo MCP. Isso criava uma discrepância entre o contrato original e a configuração migrada. A façade foi restaurada sem reintroduzir RAG customizado no Contas.
|
||||
|
||||
### 4. `pro_rata`
|
||||
|
||||
O schema/descrição original exigia **exatamente dois planos**. A migração aceitava `planos=[]` e podia afirmar pró-rata mesmo sem evidência. Agora os planos são derivados da fatura e a operação retorna `NOT_APPLICABLE` quando a evidência não comprova exatamente dois planos.
|
||||
|
||||
### 5. Pré-validação de contestação
|
||||
|
||||
`validar_contestacao` apenas resolvia o item e retornava `eligible=true`; a validação CVAL real só acontecia depois, já no workflow transacional. Agora a pré-validação e a execução usam a mesma regra financeira, evitando confirmação para uma operação que será inevitavelmente bloqueada.
|
||||
|
||||
### 6. Status de solicitação
|
||||
|
||||
O mapper usava `interaction_key` como `protocol`. Isso é semanticamente incorreto: `interaction_key` identifica a interação do framework. O protocolo agora vem da mensagem ou de campos de protocolo existentes no contexto transacional (`protocol_number`, `protocolo_id`, `contestacao_protocol`, `cancelamento_vas_protocol`).
|
||||
|
||||
## Arquivos principais alterados
|
||||
|
||||
- `contas_mcp/servers/contas_mcp_server/main.py`
|
||||
- `app/domain/contas/service.py`
|
||||
- `app/domain/contas/workflow_actions.py`
|
||||
- `app/domain/contas/invoice_context.py` (correção anterior da Opção A, mantida)
|
||||
- `config/tools.yaml`
|
||||
- `config/mcp_parameter_mapping.yaml`
|
||||
- `workflows/contestacao_tool.v2.yaml` (hardening já presente)
|
||||
- `tests/migration/test_requested_tools_parity_pente_fino.py`
|
||||
- testes de invoice context previamente adicionados/mantidos
|
||||
|
||||
## Testes de regressão adicionados
|
||||
|
||||
O novo arquivo `tests/migration/test_requested_tools_parity_pente_fino.py` verifica, entre outros pontos:
|
||||
|
||||
- exposição e habilitação das 18 tools;
|
||||
- façade framework-native de RAG;
|
||||
- consulta determinística de plano;
|
||||
- propagação de `invoice_detail` e resumo semântico;
|
||||
- composição LLM + pause de `invoice_explanation`;
|
||||
- regra de exatamente dois planos em pró-rata;
|
||||
- CVAL na pré-validação e rejeição de valor acima da cobrança;
|
||||
- semântica de término de desconto e valor divergente;
|
||||
- contratos diretos de VAS, histórico, status, SMS e PDF;
|
||||
- ausência do mapeamento incorreto `interaction_key -> protocol`;
|
||||
- obrigatoriedade/classificação de status na finalização.
|
||||
|
||||
## Resultado dos testes
|
||||
|
||||
```text
|
||||
715 passed
|
||||
```
|
||||
|
||||
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
tests/docs/RELATORIO_CORRECOES_FRAMEWORK_E_CONTAS_2026-08-28.md
Normal file
302
tests/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
tests/docs/RELATORIO_POLITICAS_DE_LINHA_ALT1_ALT2.md
Normal file
423
tests/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.
|
||||
295
tests/docs/VALIDACAO_MIGRACAO.md
Normal file
295
tests/docs/VALIDACAO_MIGRACAO.md
Normal file
@@ -0,0 +1,295 @@
|
||||
# Validação da reconstrução
|
||||
|
||||
## Resultado
|
||||
|
||||
- Sintaxe Python (`compileall`): **PASS**
|
||||
- YAML de `config/`: **PASS**
|
||||
- Testes de domínio mock: **4 PASS**
|
||||
- Smoke MCP: **PASS** para faturas, invoice explanation, VAS, histórico, cancelamento e contestação
|
||||
- Diretório do pacote anterior presente: **NÃO**
|
||||
- Imports do namespace anterior em `app/`, `mcp/`, `config/`: **0**
|
||||
- `.env`: **preservado byte a byte**
|
||||
|
||||
## Limitação do ambiente de construção
|
||||
|
||||
O runtime usado para montar o pacote não possui `langgraph` instalado globalmente. Por isso o teste de import/execução do `StateGraph` completo não foi executado aqui. O projeto declara `langgraph` em `pyproject.toml`; rode `uv sync` antes de subir o backend.
|
||||
|
||||
## Aceite recomendado em ambiente do projeto
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
pytest -q tests/migration
|
||||
uv run uvicorn contas_mcp.servers.contas_mcp_server.main:app --port 8400
|
||||
uv run uvicorn app.main:app --port 8000
|
||||
```
|
||||
|
||||
Depois execute os cenários descritos em `MANUAL_AGENT_CONTAS_MIGRADO.md`.
|
||||
|
||||
|
||||
## Atualização de paridade - 2026-08-18
|
||||
|
||||
Gate local atual: **338 passed / 3 skipped** em `tests/migration`.
|
||||
|
||||
Os skips dependem de bibliotecas não instaladas no runtime de construção (principalmente LangGraph/jellyfish) e permanecem habilitados para execução após `uv sync`.
|
||||
|
||||
Contratos adicionais corrigidos nesta rodada:
|
||||
|
||||
- VAS History: `GET ?msisdn=...`, `clientId`, `messageId` e `Authorization`.
|
||||
- Contract Information: `clientId` (não `client_id`) e Basic Auth.
|
||||
- Profile/Line Info: header `ClientID` conforme contrato original.
|
||||
- Cancelamento VAS: `messageId` sempre não vazio, além de `channel=AIAGENTCR` e `interactionProtocol`.
|
||||
|
||||
Gates estruturais mantidos:
|
||||
|
||||
- zero imports de `agente_contas_tim` em `app/`, `mcp/` e no código-fonte do framework;
|
||||
- zero imports diretos de `langgraph.graph` no domínio Contas;
|
||||
- workflows do domínio executados por `agent_framework.workflows.WorkflowRuntime`;
|
||||
- `FrameworkStateGraph` usado para composição do grafo principal;
|
||||
- `.env` preservado como arquivo principal de configuração.
|
||||
|
||||
## Atualização de paridade - continuação 2026-08-18
|
||||
|
||||
Gate local atual: **400 passed / 2 skipped** em `tests/migration`.
|
||||
|
||||
Skips restantes:
|
||||
|
||||
- `test_original_item_matcher_transcription.py`: requer `jellyfish`, dependência declarada no projeto e instalada por `uv sync`.
|
||||
- `test_original_workflow_cases.py`: requer `langgraph`, dependência declarada no projeto e instalada por `uv sync`.
|
||||
|
||||
Novas coberturas e correções comprovadas nesta rodada:
|
||||
|
||||
- cenário real `cy0001` voltou a integrar a regressão de `vas_variation`; o skip causado por caminho incorreto do harness foi removido;
|
||||
- replay pós-finalização preserva `terminal_status` e possui fallback seguro quando a sessão é restaurada sem a última fala;
|
||||
- transformação de transcrição `Fim/Mim -> Sim` foi validada contra a matriz original de fronteira de fala inteira;
|
||||
- `processing_interruption` interrompível voltou a usar classificador LLM leve do **framework**; sem classificador/erro/resultado negativo o comportamento é replay fail-safe;
|
||||
- os dois templates oficiais do framework receberam o mesmo fluxo de classificação de interrupção;
|
||||
- SMS recuperou o default canônico `senderName=TIM Brasil`;
|
||||
- Service Request Status prioriza `messageId`/`ura_call_id` antes de `session_id`;
|
||||
- o compositor determinístico de mensagem de cancelamento VAS foi portado e os 19 testes originais passam;
|
||||
- `cancelar_vas_avulso` encadeia `cancelamento_vas_avulso -> contestacao_tool` usando **dois WorkflowRuntime do framework**;
|
||||
- o MCP inicializa WorkflowRuntime/checkpointer/idempotência de forma lazy, evitando abrir Oracle durante import/health/tools-list;
|
||||
- o `IdempotencyStore` selecionado pelo framework é agora realmente injetado nos actions do Contas, eliminando o fallback local involuntário para memória;
|
||||
- cancelamento usa VAS History como fallback e bloqueia recancelamento quando `canCancel=false`;
|
||||
- resultado em lote expõe `cancelados`, `nao_encontrados`, `nao_cancelados` e `itens_para_contestacao`, preservando sucesso parcial;
|
||||
- finalização normaliza status/aliases e summary conforme o original;
|
||||
- finalização informacional cria protocolo fechado somente quando necessário e evita duplicidade quando já existe protocolo;
|
||||
- combinações canônicas de notas `VAS Bundle`, `VAS Estratégico` e `VAS Avulso` foram portadas e testadas.
|
||||
|
||||
### Gates estruturais desta versão
|
||||
|
||||
```text
|
||||
agente_contas_tim em app/ 0
|
||||
agente_contas_tim em mcp/ 0
|
||||
agente_contas_tim em agent_framework/src 0
|
||||
langgraph.graph em app/ 0
|
||||
langgraph.graph em mcp/ 0
|
||||
```
|
||||
|
||||
O LangGraph continua interno ao `agent_framework_oci` por `FrameworkStateGraph` e `WorkflowRuntime`.
|
||||
|
||||
## Atualização de paridade - continuação 2026-08-18 (baseline 420)
|
||||
|
||||
Gate local atual: **420 passed / 2 skipped** em `tests/migration`.
|
||||
|
||||
Novas correções comprovadas desde a baseline 400:
|
||||
|
||||
- plano família mantém o MSISDN do titular na contestação e o MSISDN real do dependente no cancelamento;
|
||||
- `invoice_detail` corrige deterministicamente a linha do item antes do side effect;
|
||||
- `CVAL` encerra `contestacao_tool` imediatamente quando bloqueia a operação, sem seguir para SMS/contrato/SR;
|
||||
- o MCP propaga `success=false`, mensagem e estado sistêmico quando a contestação é bloqueada;
|
||||
- finalização de `invoice_explanation` cria protocolo informacional apenas quando o workflow realmente executou, não por mero prefetch;
|
||||
- protocolo VEB já fechado é reutilizado sem nova abertura/fechamento; `force_rt15_finalization_protocol` força um RT-15 novo quando solicitado;
|
||||
- `suppress_cvn_protocol_ic` preserva a semântica de VAS estratégico deferido;
|
||||
- `WorkflowRuntime` preserva snapshot parcial, nodes e trace quando uma action posterior falha;
|
||||
- Billing Analysis agora carrega metadata de tentativas e gera RCT.079-084 por tentativa;
|
||||
- corrigido bug de `msisdn` duplicado em `preparar_invoice_explanation`;
|
||||
- corrigido bug de inicialização de `AgentWorkflow`: router/agentes/grafo estavam em código inalcançável após `return`;
|
||||
- `InvoiceContextService` foi reconstruído sobre `agent_framework.cache.Cache`, com isolamento por sessão, TTL, prefetch e reaproveitamento de CompleteInvoices/Billing Analysis/detalhe.
|
||||
|
||||
Os dois skips continuam sendo exclusivamente dependências deste runtime de construção:
|
||||
|
||||
- `jellyfish` para regressão fonética completa;
|
||||
- `langgraph` para os 18 casos reais de WorkflowRuntime.
|
||||
|
||||
## Baseline 435 testes — continuação de paridade
|
||||
|
||||
Nesta baseline foram adicionadas as seguintes garantias:
|
||||
|
||||
- `InvoiceContextService` com single-flight por sessão/fatura, cache incompleto sensível a `include_detail`, CVN.002/CVN.006/CVN.007 como `business_events` deduplicados por sessão e sem republicação em cache hit.
|
||||
- Metadados de prefetch: `fetch_elapsed_ms`, `task_timings`, `cache_age_ms` e erros por subconsulta.
|
||||
- Latch genérico `business_workflows_executed` no `AgentRuntimeMixin`; workflows em `PAUSED` já contam como executados e o latch é persistido no patch transacional.
|
||||
- Cancelamento em lote com concorrência máxima 5 (configurável por `TIM_CANCELAMENTO_BATCH_CONCURRENCY`) e protocolo por linha antes do side effect; falha de protocolo bloqueia o cancelamento daquela linha.
|
||||
- `WorkflowRunResult.error_details` preserva fatos estruturados de exceções externas sem acoplamento do framework a TIM.
|
||||
- Contestação FAILED mapeia mensagem do provider/protocolo parcial quando disponíveis e diferencia erro de negócio de falha sistêmica.
|
||||
|
||||
Resultado local: **435 passed / 2 skipped** em `tests/migration`.
|
||||
Os dois skips continuam dependentes de `langgraph`/`jellyfish` indisponíveis neste runtime de construção.
|
||||
|
||||
|
||||
## Baseline 440 testes - continuação
|
||||
|
||||
- 440 testes de migração passando; 2 skipped por dependências indisponíveis neste runtime (`langgraph`/`jellyfish`).
|
||||
- Cancelamento VAS: falha de bloqueio/cancelamento pode permanecer candidata à contestação sem mascarar sucesso.
|
||||
- Resposta composta preserva protocolos de cancelamento por linha + protocolo de contestação e sinaliza finalização sugerida.
|
||||
- Plano família: protocolo de cancelamento em dependente resolve `socialSecNo` via LineInfo da própria linha.
|
||||
- Registro de protocolo aceita aliases de resposta `interactionProtocol`, `protocolNumber`, `protocol` e `protocolo`.
|
||||
- Finalização informacional recalcula Bundle/Estratégico/Avulso pela evidência de `invoice_detail`/Billing Analysis quando disponível.
|
||||
|
||||
## Baseline 533 - dependências de regressão e finalização
|
||||
|
||||
- `tests/migration`: **533 passed / 4 xfailed / 0 skipped**.
|
||||
- Os quatro `xfail` são limitações históricas documentadas do ranking fonético (`gueimiloft`, `tim miusic`, `apou`, `agebeo max`), não testes ignorados por dependência.
|
||||
- `jellyfish` deixou de ser requisito obrigatório: o domínio possui fallback puro-Python para Jaro-Winkler, Levenshtein e chave fonética. A biblioteca externa pode ser usada como aceleração, mas o projeto e a regressão não dependem dela.
|
||||
- Os 18 casos históricos de workflow não usam mais `importorskip(langgraph)`. Em builders offline executam pelo backend determinístico **explicitamente opt-in** do `WorkflowRuntime`; em produção o backend padrão continua sendo LangGraph e a ausência de `langgraph` continua sendo erro de configuração.
|
||||
- Finalização validada adicionalmente para: CVN.008/CVN.009, MPI.006/MPI.005, RCT.085/RCT.086, CVN.010/CVN.011, SAD.001 e árvore SAD opcional; protocolo informacional canônico de invoice explanation; supressão em handoff; ausência de aceite informacional após workflows transacionais; classificação de VAS estratégico/avulso sem invoice detail.
|
||||
|
||||
### Lockfile
|
||||
O `uv.lock` herdado de snapshots anteriores foi removido porque ainda descrevia o pacote legado (`agente-contas-tim`) e dependências que já não pertencem ao projeto (`jellyfish` obrigatório, NeMoGuardrails/LangChain extras, entre outras). O primeiro `uv sync` deve regenerar o lock a partir do `pyproject.toml` atual.
|
||||
|
||||
## Baseline 550 - finalização e matcher sem skips/xfails
|
||||
|
||||
Validação consolidada desta etapa:
|
||||
|
||||
```text
|
||||
550 passed
|
||||
0 skipped
|
||||
0 xfailed
|
||||
```
|
||||
|
||||
Coberturas adicionadas nesta etapa:
|
||||
|
||||
- finalização conversacional diferencia encerramento genérico de contexto real de fatura;
|
||||
- prefetch/invoice context pode garantir CVN.002/CVN.006 e RT-15 conforme semântica histórica;
|
||||
- `conversation_unresolved_transition_emitted` impede reemissão indevida de CVN/MPI positivos;
|
||||
- VEB terminal com protocolo fechado não cria RT-15 duplicado nem reemite CVN/MPI;
|
||||
- evidência da fatura prevalece sobre tipo informacional salvo em match total;
|
||||
- match parcial mescla inferência da fatura com tipo salvo ainda não representado;
|
||||
- sem match na fatura, tipos salvos conflitantes são descartados;
|
||||
- protocolo já existente impede RT-15 duplicado;
|
||||
- alias `cpf` é propagado como `socialSecNo` no protocolo informacional;
|
||||
- matcher de transcrição resolveu os quatro casos históricos antes marcados como xfail.
|
||||
|
||||
### Matcher sem dívida conhecida no catálogo de regressão
|
||||
|
||||
O `SimilarityItemMatcher` passou a combinar:
|
||||
|
||||
- similaridade de frase;
|
||||
- similaridade fonética;
|
||||
- alinhamento token-a-token;
|
||||
- dupla evidência grafia + fonética por token.
|
||||
|
||||
Isso corrigiu explicitamente:
|
||||
|
||||
- `gueimiloft` -> `Gameloft`;
|
||||
- `tim miusic` -> `TIM Music`;
|
||||
- `apou` -> `Apple Music`;
|
||||
- `agebeo max` -> `HBO Max`.
|
||||
|
||||
## Baseline 556 — paridade VAA (2026-08-18)
|
||||
|
||||
A regressão de migração passou a executar 556 testes sem skip/xfail.
|
||||
|
||||
Nesta etapa os testes históricos do projeto original foram usados diretamente como catálogo para restaurar a família VAA sem trazer o publisher legado:
|
||||
|
||||
- cancelamento VAS: VAA.001/VAA.002/VAA.003 no caminho feliz e VAA.004 em falha operacional;
|
||||
- contestação: VAA.005 + VAA.006/VAA.007 e VAA.008/VAA.009 conforme sucesso e elegibilidade do código de barras;
|
||||
- SMS: VAA.012/VAA.014 em sucesso e VAA.013/VAA.015 em falha, mantendo o workflow ativo;
|
||||
- atualização/fechamento de SR: VAA.016/VAA.017.
|
||||
|
||||
Os events são retornados como `business_events`; transporte, sequence e fan-out permanecem responsabilidade exclusiva do `AgentObserver`/analytics do agent_framework_oci.
|
||||
|
||||
|
||||
## Baseline 560 testes - metadata corporativa TIM
|
||||
|
||||
- 560 testes de migração passando, sem skips/xfails.
|
||||
- Business events preservam contexto corporativo: `agentProtocolId`, `adjustedProtocol`, `billingId`, `uraCallId`, `channelId`, `sessionId`, `messageId` e `agentSpecificData`.
|
||||
- Contestação VAA.005-009 preserva itens/valores ajustados e protocolo.
|
||||
- Finalização CVN.010/011 preserva protocolo TIM/URA e billing context.
|
||||
- RCTs derivados de retry HTTP carregam `apiUrl`, `apiStatusCode`, `apiResponsePayload` e `latencyMs`.
|
||||
- SMS e Service Request Status carregam metadata de transporte e contexto de protocolo.
|
||||
- `tim_payload_mapper` aceita o formato histórico `agentSpecificData` JSON-string e o converte para o objeto canônico TIM no payload final.
|
||||
|
||||
## Baseline 565 — metadata MPI/VEB/SAD e VAS estratégico
|
||||
|
||||
- Regressão: **565 passed / 0 skipped / 0 xfailed**.
|
||||
- `VEB.*`, `MPI.*`, `CVN.*` e `SAD.*` passam a carregar metadata conversacional TIM via `_event_context`: `customerMessage`, `llmResponse`, `messageId`, `sessionId`, `channelId`, `uraCallId`, `billingId` e `sessionEndAt` quando aplicável.
|
||||
- `SAD.001` normaliza `session_end_at` ISO-8601 para epoch milliseconds.
|
||||
- VAS Estratégico recuperou a semântica histórica: `NAO` estratégico -> `VEB.004 -> VEB.006 -> VEB.007`; Bundle + `NAO` -> `VEB.004 -> VEB.005 -> VEB.007` e protocolo deferido para finalização; `SIM` -> `VEB.003` e registro de atendimento.
|
||||
- Invoice Explanation (`MPI.005/006`), Pró-Rata (`MPI.010`) e cancelamento (`MPI.011`) usam o mesmo enriquecimento de contexto.
|
||||
|
||||
## Baseline 570 testes
|
||||
|
||||
- `pytest -q`: **570 passed**, 0 skipped, 0 xfailed.
|
||||
- `pyproject.toml` limita o gate padrão a `tests/`, evitando coleta acidental dos scripts de teste duplicados existentes dentro dos templates do framework.
|
||||
- `cancelar_vas_avulso` redireciona automaticamente para `vas_estrategico` quando o `InvoiceResolver` classifica a cobrança como estratégico/bundle.
|
||||
- `invoice_explanation` recuperou `recomenda_finalizacao/status_finalizacao_sugerido` por branch do `WorkflowRuntime`.
|
||||
- `pro_rata` recuperou recomendação de finalização nos branches `registrar_aceitou` e `registrar_nao_controle`.
|
||||
- VAS Estratégico recuperou a busca de orientação do parceiro sem RAG próprio: a action declara `requires_rag/rag_queries`, e o `AgentRuntimeMixin` executa `RagService` do framework.
|
||||
|
||||
## Baseline 578 - wrapper cancelamento + LLM composition
|
||||
|
||||
- 593 testes passando; zero skipped/xfail.
|
||||
- Cancelamento preserva `auto_finalize_on_failure` em falha técnica de contestação.
|
||||
- Item já contestado não mascara cancelamento concluído como falha sistêmica.
|
||||
- `cancelamento_vas_protocol` e todos os protocolos de resposta são preservados.
|
||||
- Plano família contesta titular + dependentes em uma única `contestacao_tool`.
|
||||
- Lote com muitos no-match continua contestando exatamente os candidatos elegíveis.
|
||||
- Pró-rata usa `requires_llm_composition` do framework em vez de gateway LLM de domínio.
|
||||
|
||||
|
||||
## Continuação — paridade explícita dos wrappers (baseline 593)
|
||||
|
||||
- `contestacao_tool` volta a garantir `tipo_atendimento=contestacao` e preserva o contexto do turno (`message_id`, `customer_message`, `ura_call_id`, `channel_id`, `ani`, `invoice_id`).
|
||||
- Falhas de contestação são diferenciadas entre erro técnico (`erro_falha_sistema`) e conflito/item já contestado com mensagem/protocolo do provider.
|
||||
- Cancelamento composto aceita tanto `contestation_candidates` quanto o alias histórico `itens_para_contestacao`.
|
||||
- Resultado agregado em `cancelados/nao_cancelados` é aceito mesmo quando `results[]` não repete a flag `success`.
|
||||
- `cpf` volta a ser alias de `social_sec_no` e é normalizado para dígitos antes de protocolo/contestação.
|
||||
- Em fluxo misto Bundle + Estratégico no branch NÃO, o protocolo segue deferido para finalização, porém as `rag_queries` dos serviços estratégicos são preservadas.
|
||||
- Falha parcial com candidato continua encadeando `cancelamento_vas_avulso -> contestacao_tool`; falha de SMS não transforma o cancelamento em falha.
|
||||
- `pytest -q`: **593 passed, 0 skipped, 0 xfailed**.
|
||||
|
||||
## Baseline 599 testes — paridade explícita de wrappers
|
||||
|
||||
Validação consolidada: `599 passed`, `0 skipped`, `0 xfailed`.
|
||||
|
||||
A rodada adicionou regressões 1:1 baseadas nos wrappers históricos para:
|
||||
|
||||
- `itens_ja_contestados` preservados desde a action de contestação até o compositor de resposta;
|
||||
- classificação de `contested_items`, `not_contested_items` e itens já contestados feita no domínio, não reconstruída no MCP;
|
||||
- totais de contestação calculados somente sobre itens efetivamente aceitos;
|
||||
- valor `0`/`0,00` retornado pela contestação tratado como ausência de valor útil para composição, usando o total cancelado;
|
||||
- valores monetários normalizados em pt-BR na borda do MCP (`14,99`);
|
||||
- `next_subject` não interfere na composição determinística de cancelamento;
|
||||
- falha de serviço em `invoice_explanation` preserva a fraseologia canônica e não ativa auto-finalização.
|
||||
|
||||
Gates: `compileall` PASS, zero imports do namespace legado e zero imports diretos de LangGraph em `app/`/`mcp/`.
|
||||
|
||||
## Baseline 615 — contratos 1:1 dos wrappers históricos
|
||||
|
||||
A regressão foi ampliada para 615 testes executados, sem skips e sem xfails.
|
||||
Nesta etapa foram portados como contratos explícitos do MCP novo os seguintes
|
||||
outcomes do `backend_cancelar_vas_single` e `finalize_support` históricos:
|
||||
|
||||
- sucesso implícito quando o workflow reporta `cancelados[]` sem `success=true`;
|
||||
- lista explícita vazia de candidatos não cria fallback indevido para contestação;
|
||||
- no-match não chama contestação nem vocaliza valor como se houvesse ajuste;
|
||||
- `protocol_closed` da contestação é preservado;
|
||||
- flags internas `sms_sent` não vazam no contrato externo e falha de SMS é propagada por `sms_not_send_error`;
|
||||
- cancelamento com protocolo e sem item efetivamente contestado continua resolvido;
|
||||
- candidato explícito pode seguir para RT-02 mesmo após falha/no-match em RT-01;
|
||||
- falha parcial de bloqueio/cancelamento continua elegível à contestação quando marcada pelo domínio;
|
||||
- `holder_msisdn` não pode ser sobrescrito por linha dependente;
|
||||
- item do titular não é marcado como dependente;
|
||||
- titular + múltiplos dependentes são agregados em uma única contestação do titular;
|
||||
- VAS estratégico após invoice explanation prioriza nota estratégica na finalização;
|
||||
- Bundle + Estratégico deferidos geram RT-15 combinado na finalização;
|
||||
- invoice explanation não abre protocolo informacional antes da finalização.
|
||||
|
||||
Gate executado:
|
||||
|
||||
```text
|
||||
pytest -q: 615 passed
|
||||
compileall app/mcp/framework: PASS
|
||||
agente_contas_tim em app/mcp/framework runtime: 0
|
||||
import direto langgraph.graph em app/mcp: 0
|
||||
```
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user