ajuste no contas

This commit is contained in:
T3782834
2026-09-01 11:24:03 -03:00
parent 9ed4782f9d
commit 397b831fd3
428 changed files with 2294 additions and 4648 deletions

View File

@@ -0,0 +1,13 @@
# Correção: transação ativa vence status terminal antigo
## Problema
Em uma mesma sessão, uma transação anterior podia deixar `transaction_status=COMPLETED`. Quando uma nova transação já havia sido aberta em `AWAITING_CONFIRMATION`, o `input_guardrails` de `app/workflows/agent_graph.py` ainda usava o status terminal antigo como gatilho para limpar o contexto operacional. Isso apagava a nova `active_transaction`, fazendo a confirmação seguinte (`sim`) cair em `contas_no_match_retry`.
## Correção
O reset operacional agora consulta primeiro `active_transaction.status`. Se existir uma transação ativa não-terminal (`COLLECTING_PARAMETERS`, `AWAITING_CONFIRMATION`, `EXECUTING`, `PAUSED` ou `WAITING_INPUT`), ela tem precedência sobre qualquer status terminal residual da transação anterior. O tombstone só ocorre quando não há transação ativa pendente.
## Resultado esperado
- Transação anterior pode permanecer no histórico/evidência.
- Nova transação não é apagada por status velho.
- `sim` após a confirmação de TIM Fashion permanece no fluxo transacional.
- Encerramentos reais continuam limpando o contexto quando não existe nova transação ativa.

View File

@@ -0,0 +1,16 @@
# Fix: isolamento do checkpoint LangGraph e snapshot do gateway
## Problema
O gateway gravava `checkpoints.put(agent_session_id, {state: result, ...})` após `workflow.ainvoke()`.
O LangGraph saver usa o mesmo repositório e a mesma chave `thread_id == agent_session_id`.
Assim, o snapshot simplificado do gateway sobrescrevia o checkpoint nativo do LangGraph, removendo canais transacionais como `active_transaction`, `pending_tool_call`, `confirmation_snapshot` e `confirmation_required`.
## Correção
O snapshot do gateway passou a usar o namespace `gateway:<session_id>`.
O checkpoint LangGraph continua usando `<session_id>` sem interferência.
O endpoint `/sessions/{session_id}/checkpoint` consulta o snapshot `gateway:<session_id>` para preservar o contrato de debug existente.
## Invariante
- checkpoint LangGraph: chave `<session_id>`
- snapshot/debug do gateway: chave `gateway:<session_id>`
- nenhuma gravação externa pode sobrescrever o checkpoint nativo do LangGraph.

View File

@@ -0,0 +1,25 @@
# Robustez de banco e fallbacks transacionais
## Objetivo
Evitar que falhas de persistência/cache/idempotência derrubem o atendimento com HTTP 500 e contaminem a continuidade transacional.
## Alterações
1. `OracleStore.cache_get`
- normaliza `datetime` naive/aware para UTC antes de comparar TTL;
- elimina o `TypeError: can't compare offset-naive and offset-aware datetimes`.
2. `IdempotencyStore`
- suporta backend de fallback;
- quando `require_durable=False`, falhas do backend primário degradam para `InMemoryCache` por padrão;
- `get`, `set` e `delete` não propagam indisponibilidade do backend primário em modo fail-open;
- quando durabilidade é exigida, a semântica fail-closed continua disponível.
3. `cancelamento_vas_avulso_batch`
- boundary por item converte exceções técnicas inesperadas em resultado recuperável;
- um item com erro não derruba o batch nem o request HTTP inteiro;
- erros são devolvidos com `reason=internal_processing_failed`/`recoverable=true` para composição controlada da resposta.
## Configuração opcional
`IDEMPOTENCY_FAIL_OPEN=true|false` pode sobrescrever o comportamento. Sem configuração explícita, `require_durable=False` usa fail-open e `require_durable=True` mantém fail-closed.

View File

@@ -0,0 +1,19 @@
# Correção: compactação de contexto LLM
## Problema
O estado operacional preserva resultados completos de MCP/workflows para auditoria e continuidade. Esses objetos podem conter cópias recursivas de `state`, `input`, `session`, `agent_profile`, `business_events`, `trace`, `nodes` e outros dados técnicos. Quando `build_messages()` serializava diretamente `mcp_results` e `transaction_evidence`, o prompt podia ultrapassar a janela do modelo.
Caso observado: `Input length (134212) exceeds model's maximum context length (131072)` no `ContestacaoAgent`.
## Correção
- `build_messages()` passa a renderizar business context, MCP results, transaction evidence, RAG metadata e extra sections por um compactador genérico com orçamento de caracteres.
- Objetos técnicos recursivos permanecem no estado/checkpoint para auditoria, mas não são reenviados integralmente ao LLM.
- Fatos de negócio (tool, subject, status, valores, protocolos e resultados úteis) são preservados dentro do orçamento.
- Se o provider ainda rejeitar por limite de contexto, `_invoke_llm_cached()` executa uma única segunda tentativa com orçamento emergencial de mensagens.
- Não há loop de retries; qualquer outra exceção continua propagando normalmente.
## Invariantes
1. Estado operacional completo != contexto de prompt.
2. Compactação não altera a execução das tools/workflows.
3. Context-limit error recebe no máximo um retry compactado.
4. A correção é genérica e não contém regra específica do Contas/TIM.

View File

@@ -0,0 +1,9 @@
# Estabilidade dos cenários 03/05/06/17/20/22/29
Correções de runtime incluídas:
1. `invoice_explanation` agora estreita a resposta para um item explicitamente nomeado pelo cliente quando o item é comprovado em `billing_analysis.currentInvoice`. Isso corrige VOD + Canais Abertos sem citar serviços não reclamados e evita falso positivo legítimo do AOFERTA.
2. O roteamento do Contas explicita que fragmentos com palavra de domínio mas sem objeto/referente recuperável devem ir para clarificação (`contas_no_match`), em vez de disparar `contas_invoice_explanation`.
3. O primeiro no-match após uma fronteira operacional concluída vira uma nova interação neutra (`contas_post_terminal_reentry`) em vez de tentar retomar fluxo antigo ou responder como erro.
4. `TimResponseQualityJudge` normaliza corretamente o contrato de score 0..10. Antes, score `1` era interpretado como `1.0` em escala 0..1 e passava indevidamente o threshold.
5. A correção anterior que dá precedência ao encerramento explícito sobre `contas_no_match` permanece preservada.

View File

@@ -0,0 +1,17 @@
# Fix: stale pending writes cannot roll back the latest LangGraph checkpoint
## Problem
`RepositoryCheckpointSaver.aput_writes()` used the latest full checkpoint payload, appended pending writes and persisted that payload as a new checkpoint row. Because the repository is append-only, an `aput_writes()` from an older super-step could finish after a newer `aput()` and become the newest database row. The next request would then restore an older transaction state.
Observed symptom in a single session:
- turn N opens a new transaction and asks for confirmation;
- turn N+1 restores an older completed transaction;
- a standalone confirmation such as `sim` is routed to no-match.
## Fix
`aput_writes()` now compares `config.configurable.checkpoint_id` with the durable latest checkpoint id. If the write belongs to an older checkpoint, it is ignored instead of re-persisting the stale full checkpoint as latest.
Writes for the actual latest checkpoint continue to be persisted normally.
## Invariant
For a thread/session, checkpoint ordering is monotonic: a delayed pending write may enrich its own checkpoint, but it must never make a previous checkpoint become the session's latest state.

View File

@@ -0,0 +1,19 @@
# Teste 24 — contexto AOFERTA e lifecycle do cliente HTTP
## Problemas corrigidos
1. A resposta terminal de cancelamento parcial era corretamente produzida pelo workflow, porém o TIM_AOFERTA podia receber um contexto JSON enorme truncado antes das evidências relevantes do pedido. Isso fazia a orientação de canal alternativo para o mesmo alvo ser julgada como ação não solicitada.
2. O bridge síncrono de guardrails executava um provider temporário dentro de `asyncio.run()`. O `AsyncOpenAI`/httpx associado podia sobreviver ao loop e tentar fechar conexões após o loop ter sido encerrado, gerando `Task exception was never retrieved` / `RuntimeError: Event loop is closed`.
## Correções
- `TimProactiveOfferRail` usa contexto compacto e autoritativo com histórico recente, pedido/resolução transacional e resultado atual da execução. Não existe bypass para estado `COMPLETED`: o LLM continua julgando semanticamente e ainda pode bloquear alvo/ação realmente novos.
- O prompt AOFERTA explicita que evidência estruturada do alvo solicitado/resolvido e da falha parcial é autoritativa para continuidade no mesmo alvo.
- Providers OpenAI-compatible ganharam `aclose()` explícito para todos os clientes cacheados.
- `classify_with_framework_llm()` identifica providers temporários criados internamente e os fecha no mesmo event loop, em `finally`; providers fornecidos pelo caller continuam sob responsabilidade do caller.
## Testes
- lifecycle + TIM_AOFERTA: 22 PASS
- cancelamento/guardrails correlatos: 198 PASS
- `tests/migration`: 830 PASS / 2 FAIL históricos não relacionados.

View File

@@ -0,0 +1,18 @@
# Fix TIM_AOFERTA - continuidade por canal alternativo
## Problema
O TIM_AOFERTA classificava como oferta proativa uma orientação de canal alternativo para concluir a mesma ação transacional que o cliente já havia solicitado, após execução parcial/falha no canal atual.
## Correção
O prompt do guardrail foi ajustado semanticamente, mantendo decisão via LLM e sem hardcodes de frases, regex ou nomes de serviços. A nova regra estabelece que orientar como concluir a mesma ação, no mesmo alvo e escopo já solicitado, por outro canal oficial, é continuidade do pedido e deve ser permitido. Continua bloqueada qualquer orientação que introduza nova ação, novo alvo ou amplie o escopo do pedido.
## Arquivos
- `app/extensions/tim_prompts/ausencia_oferta_proativa.py`
- `agent_framework_oci/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/ausencia_oferta_proativa.py`
- `tests/migration/original_test_ausencia_oferta_proativa.py`
## Testes
- 19/19 testes direcionados AOFERTA
- 57/57 regressão cancelamento múltiplo/adversarial
- 824 PASS / 2 FAIL em `tests/migration`
- Os 2 FAIL são preexistentes: contestação histórica TIM Fashion Mensal/R$50 e fraseologia `termino_desconto`.

View File

@@ -0,0 +1,39 @@
# Correção cirúrgica — status transacional e VAS pré-validado
## Escopo
Esta correção parte da versão estável anterior à expansão de `terminal outcomes` e altera apenas dois contratos já existentes.
### 1. `active_transaction` e `transaction_status` não podem divergir
`AgentRuntimeMixin._set_active_transaction()` agora atualiza também `state["transaction_status"]` com o mesmo `status` da transação instalada.
Isso evita que um status terminal da operação anterior (`COMPLETED`, por exemplo) sobreviva à criação de uma nova transação em `AWAITING_CONFIRMATION`. Sem essa invariável, o boundary do turno seguinte pode interpretar a nova confirmação como resíduo da transação encerrada e limpar seus latches antes do roteamento.
Não foi criada regra para a palavra `sim`, nem bypass de roteamento.
### 2. VAS estratégico reutiliza resolução já validada
`validar_vas_subject` já é o resolver autoritativo side-effect-free na fronteira transacional. Quando ele resolve um VAS e redireciona a ação para `tratar_vas_estrategico`, os `resolved_arguments` agora carregam:
- `subject` canônico;
- `_vas_subject_prevalidated=true`;
- `type` quando conhecido (`bundle`/`estrategico`).
Na execução da tool estratégica, `_preflight_subject()` não executa uma segunda resolução por `invoice_evidence` quando esse marcador está presente. Isso evita o caso em que `Aya Audiobooks Premium` era `ELIGIBLE` na pré-validação e depois virava `NEEDS_PARAMETER / subject_not_resolved` ao executar a tool efetiva.
## O que não foi alterado
- `agent_graph` e reset global de contexto;
- route stickiness;
- confirmation classifier;
- estados terminais globais;
- idempotência;
- guardrails;
- fluxo de múltiplos VAS.
## Testes
- testes direcionados novos + relacionados: 16 PASS;
- VAS/transação/cancelamento/conversation policy: 420 PASS;
- suíte `tests/migration`: 833 PASS / 2 FAIL históricos já existentes e não relacionados.

View File

@@ -0,0 +1,19 @@
# VLOOP: bypass para confirmação transacional
## Problema
Mensagens curtas como `sim` podem aparecer legitimamente várias vezes na mesma sessão, uma vez para cada transação. O VLOOP usava apenas repetição textual no histórico e podia bloquear uma confirmação válida em `AWAITING_CONFIRMATION`.
## Correção
O rail `VLOOP` agora permite a entrada quando o estado transacional atual é `AWAITING_CONFIRMATION`. O status é lido de `transaction_status` e, como fallback, de `active_transaction.status`.
A interpretação de `sim`/`não` continua sendo responsabilidade do runtime/classificador de confirmação. O bypass apenas impede que VLOOP intercepte a entrada antes dessa decisão.
Fora de `AWAITING_CONFIRMATION`, a detecção original de repetição continua inalterada.
## Validação
- 3 testes específicos do bypass: PASS
- 71 testes de guardrails/transações/confirmação: PASS
- suíte migration: 845 PASS / 2 FAIL históricos já existentes

View File

@@ -0,0 +1,22 @@
# Workflow execution ID por transação
## Contrato
`workflow_execution_id` pertence à execução de workflow da transação ativa, não à sessão.
- Nova transação que inicia workflow: nova execução (`WorkflowRuntime.arun(..., execution_id=None)` gera UUID novo).
- Mesmo workflow pausado: `retomar_workflow` reutiliza o `execution_id` via `aresume`.
- Transação encerrada: o ID permanece apenas como histórico/evidência e não pode iniciar outra execução.
- Uma nova transação não herda `transaction_id` nem `workflow_execution_id` de estado terminal anterior.
## Alterações
- `contas_mcp/.../main.py`: chamadas que iniciam workflow removem `workflow_execution_id` residual e deixam `WorkflowRuntime` gerar um ID novo.
- `retomar_workflow`: permanece o único caminho que reutiliza ID existente.
- `agent_runtime.py`: `_set_active_transaction` só reutiliza `transaction_id` de transação realmente ativa e remove `workflow_execution_id` residual ao abrir uma nova transação.
## Testes
- 4 testes específicos do contrato: PASS.
- 95 testes de workflows/transações/cancelamento relacionados: PASS.
- Suite `tests/migration`: 837 PASS / 2 FAIL históricos não relacionados.