1882 lines
39 KiB
Markdown
1882 lines
39 KiB
Markdown
# Manual do Desenvolvedor — Workflows do Agente Contas
|
|
|
|
> **Projeto de referência:** `agent_contas_fechado_COMPLETO_corrigido_v4`
|
|
> **Pasta documentada:** `/workflows`
|
|
> **Público:** desenvolvedores que precisam criar, alterar, depurar ou revisar jornadas determinísticas do agente Contas.
|
|
|
|
---
|
|
|
|
## 1. Objetivo deste manual
|
|
|
|
A pasta `workflows/` contém a **orquestração declarativa de jornadas de negócio** do agente Contas. Ela não é apenas uma coleção de YAMLs: cada arquivo descreve um pequeno grafo de execução que o runtime genérico do `agent_framework_oci` carrega, valida, executa, pausa, retoma e finaliza.
|
|
|
|
O princípio central é:
|
|
|
|
> **O workflow decide a sequência, as condições e os pontos de pausa. A action executa a operação de domínio. O framework fornece o motor genérico.**
|
|
|
|
Isso evita que regras de jornada fiquem escondidas em `if/else` dentro dos agentes ou do framework.
|
|
|
|
Este documento explica:
|
|
|
|
- como o versionamento dos workflows funciona;
|
|
- como ler um arquivo `.vN.yaml`;
|
|
- o significado de `name`, `version`, `start`, `nodes`, `edges`, `when`, `priority`, `pause`, `expected_input` e `resume_from`;
|
|
- como funcionam `$.input`, `$.vars`, `$.output` e o estado interno;
|
|
- como uma `action` declarada no YAML se conecta a Python;
|
|
- como um workflow pausa e continua em outro turno;
|
|
- como o classificador semântico de `expected_input` funciona;
|
|
- como um workflow termina sem trocar o `session_id`;
|
|
- como cada arquivo atual da pasta `workflows/` funciona, ponto a ponto;
|
|
- como adicionar uma nova versão com segurança.
|
|
|
|
---
|
|
|
|
## 2. Estrutura atual da pasta
|
|
|
|
```text
|
|
workflows/
|
|
├── buscar_fatura.active.yaml
|
|
├── buscar_fatura.v1.yaml
|
|
├── buscar_informacao.active.yaml
|
|
├── buscar_informacao.v2.yaml
|
|
├── cancelamento_vas_avulso.active.yaml
|
|
├── cancelamento_vas_avulso.v1.yaml
|
|
├── contestacao_tool.active.yaml
|
|
├── contestacao_tool.v2.yaml
|
|
├── finalizar_atendimento.active.yaml
|
|
├── finalizar_atendimento.v1.yaml
|
|
├── invoice_explanation.active.yaml
|
|
├── invoice_explanation.v2.yaml
|
|
├── pro_rata.active.yaml
|
|
├── pro_rata.v3.yaml
|
|
├── termino_desconto.active.yaml
|
|
├── termino_desconto.v1.yaml
|
|
├── valor_divergente.active.yaml
|
|
├── valor_divergente.v1.yaml
|
|
├── vas_estrategico.active.yaml
|
|
└── vas_estrategico.v3.yaml
|
|
```
|
|
|
|
Há sempre dois papéis diferentes:
|
|
|
|
1. **`<nome>.active.yaml`** — marcador da versão ativa;
|
|
2. **`<nome>.vN.yaml`** — definição completa e versionada do grafo.
|
|
|
|
Exemplo:
|
|
|
|
```yaml
|
|
# invoice_explanation.active.yaml
|
|
version: 2
|
|
```
|
|
|
|
Esse arquivo não contém a lógica. Ele informa ao `FileWorkflowRepository` que, quando alguém pedir o workflow ativo `invoice_explanation`, deve ser carregado:
|
|
|
|
```text
|
|
invoice_explanation.v2.yaml
|
|
```
|
|
|
|
### Regra prática de versionamento
|
|
|
|
Não altere silenciosamente a semântica de uma versão já publicada quando a mudança for incompatível ou material. Prefira:
|
|
|
|
```text
|
|
invoice_explanation.v2.yaml # versão atual
|
|
invoice_explanation.v3.yaml # nova implementação
|
|
invoice_explanation.active.yaml -> version: 3
|
|
```
|
|
|
|
Assim rollback e auditoria permanecem simples.
|
|
|
|
---
|
|
|
|
## 3. Quem faz o quê
|
|
|
|
A arquitetura pode ser entendida em quatro camadas.
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
U[Usuário] --> AG[Agente / Router]
|
|
AG --> W[Workflow YAML]
|
|
W --> RT[WorkflowRuntime do framework]
|
|
RT --> A[Actions Python do domínio Contas]
|
|
A --> S[Services / MCP / APIs legadas]
|
|
A --> RT
|
|
RT --> AG
|
|
```
|
|
|
|
### 3.1 Workflow YAML
|
|
|
|
Responsável por:
|
|
|
|
- sequência dos passos;
|
|
- branching;
|
|
- prioridade das transições;
|
|
- definição de pausa;
|
|
- contrato da resposta esperada do usuário;
|
|
- nó de retomada;
|
|
- declaração de valores fixos da jornada;
|
|
- escolha de qual action executar.
|
|
|
|
### 3.2 `WorkflowRuntime`
|
|
|
|
É genérico e pertence ao framework. Ele:
|
|
|
|
- carrega o YAML;
|
|
- valida o grafo;
|
|
- resolve expressões `$.…`;
|
|
- executa actions;
|
|
- mantém `vars`, `output`, `trace` e estado;
|
|
- ordena edges por `priority`;
|
|
- avalia `when`;
|
|
- implementa pause/resume;
|
|
- usa checkpoint do LangGraph em produção;
|
|
- retorna `COMPLETED`, `PAUSED` ou `FAILED`.
|
|
|
|
O runtime não deve conhecer regras específicas da TIM ou do Contas.
|
|
|
|
### 3.3 Actions Python
|
|
|
|
No Contas, a maioria das actions declaradas nos YAMLs é registrada em:
|
|
|
|
```text
|
|
app/domain/contas/workflow_actions.py
|
|
```
|
|
|
|
por meio de:
|
|
|
|
```python
|
|
reg = WorkflowActionRegistry()
|
|
|
|
@reg.action("nome_da_action")
|
|
def nome_da_action(params, state):
|
|
...
|
|
return {...}
|
|
```
|
|
|
|
Uma action deve receber:
|
|
|
|
```python
|
|
params: dict
|
|
state: dict
|
|
```
|
|
|
|
E deve retornar **sempre um `dict`**.
|
|
|
|
### 3.4 Services / MCP / legado
|
|
|
|
As actions podem chamar `ContasDomainService`, clientes HTTP, integrações TIM, mocks ou outros componentes. O YAML não deve conter código de transporte.
|
|
|
|
---
|
|
|
|
## 4. Anatomia de um workflow
|
|
|
|
Um workflow mínimo é:
|
|
|
|
```yaml
|
|
name: exemplo
|
|
version: 1
|
|
start: primeiro
|
|
|
|
nodes:
|
|
- id: primeiro
|
|
action: minha_action
|
|
input:
|
|
msisdn: $.input.msisdn
|
|
|
|
edges:
|
|
- from: primeiro
|
|
to: END
|
|
```
|
|
|
|
### 4.1 `name`
|
|
|
|
Nome lógico do workflow.
|
|
|
|
Precisa ser coerente com o nome do arquivo:
|
|
|
|
```text
|
|
exemplo.v1.yaml
|
|
name: exemplo
|
|
version: 1
|
|
```
|
|
|
|
O repository valida essa correspondência.
|
|
|
|
### 4.2 `version`
|
|
|
|
Número inteiro da versão do contrato do workflow.
|
|
|
|
### 4.3 `start`
|
|
|
|
ID do primeiro nó executado.
|
|
|
|
### 4.4 `nodes`
|
|
|
|
Cada nó representa uma unidade de execução.
|
|
|
|
```yaml
|
|
- id: preparar
|
|
action: preparar_invoice_explanation
|
|
input:
|
|
msisdn: $.input.msisdn
|
|
```
|
|
|
|
O `id` é o nome do nó dentro do grafo. A `action` é o nome registrado no `WorkflowActionRegistry`.
|
|
|
|
### 4.5 `edges`
|
|
|
|
Definem para onde o grafo segue após um nó.
|
|
|
|
```yaml
|
|
- from: preparar
|
|
to: formatar
|
|
```
|
|
|
|
ou condicionalmente:
|
|
|
|
```yaml
|
|
- from: preparar
|
|
to: formatar
|
|
priority: 10
|
|
when:
|
|
eq: [$.vars.preparar.success, true]
|
|
```
|
|
|
|
### 4.6 `END`
|
|
|
|
`END` representa término do grafo.
|
|
|
|
```yaml
|
|
- from: finalizar
|
|
to: END
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Modelo de estado e expressões `$.…`
|
|
|
|
Essa é uma das partes mais importantes para quem altera a pasta `workflows/`.
|
|
|
|
### 5.1 `$.input`
|
|
|
|
Representa os dados de entrada da execução.
|
|
|
|
Exemplo:
|
|
|
|
```yaml
|
|
input:
|
|
msisdn: $.input.msisdn
|
|
invoice_id: $.input.invoice_id
|
|
```
|
|
|
|
Se o workflow foi iniciado com:
|
|
|
|
```json
|
|
{
|
|
"msisdn": "11999999999",
|
|
"invoice_id": "3000131180"
|
|
}
|
|
```
|
|
|
|
os dois valores serão passados à action.
|
|
|
|
### 5.2 `$.vars.<node>`
|
|
|
|
Após cada action retornar um dicionário, o runtime armazena o resultado em:
|
|
|
|
```text
|
|
$.vars.<id_do_no>
|
|
```
|
|
|
|
Exemplo:
|
|
|
|
```yaml
|
|
- id: registrar_protocolo
|
|
action: registrar_protocolo
|
|
```
|
|
|
|
Se a action retornar:
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"protocolo_id": "1234567890"
|
|
}
|
|
```
|
|
|
|
então outro nó pode usar:
|
|
|
|
```yaml
|
|
protocolo_id: $.vars.registrar_protocolo.protocolo_id
|
|
```
|
|
|
|
### 5.3 `$.output`
|
|
|
|
Aponta para o último resultado de action colocado como output corrente.
|
|
|
|
É muito usado em `pause.return_from`:
|
|
|
|
```yaml
|
|
pause:
|
|
return_from: $.output.mensagem
|
|
```
|
|
|
|
### 5.4 `$.nodes`
|
|
|
|
O runtime também mantém resultados por nó em uma estrutura de nós. Na maior parte dos workflows do Contas, `$.vars` é a forma declarativa utilizada para encadear dados.
|
|
|
|
### 5.5 Exemplo encadeado
|
|
|
|
```yaml
|
|
- id: preparar
|
|
action: preparar
|
|
|
|
- id: formatar
|
|
action: formatar
|
|
input:
|
|
dados: $.vars.preparar.dados
|
|
```
|
|
|
|
Fluxo:
|
|
|
|
```text
|
|
preparar() -> {dados: X}
|
|
|
|
|
v
|
|
$.vars.preparar.dados
|
|
|
|
|
v
|
|
formatar(dados=X)
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Conditions e prioridade de edges
|
|
|
|
O runtime agrupa as edges por nó de origem e ordena por `priority` crescente.
|
|
|
|
Portanto:
|
|
|
|
```yaml
|
|
priority: 10
|
|
```
|
|
|
|
é avaliada antes de:
|
|
|
|
```yaml
|
|
priority: 99
|
|
```
|
|
|
|
### 6.1 Fallback padrão
|
|
|
|
Um padrão comum é:
|
|
|
|
```yaml
|
|
- from: action_x
|
|
to: caminho_especial
|
|
priority: 10
|
|
when:
|
|
eq: [$.vars.action_x.alguma_flag, true]
|
|
|
|
- from: action_x
|
|
to: caminho_padrao
|
|
priority: 99
|
|
```
|
|
|
|
A edge de prioridade 99 funciona como fallback porque não possui `when`.
|
|
|
|
### 6.2 Operadores encontrados nos workflows atuais
|
|
|
|
Exemplos:
|
|
|
|
```yaml
|
|
when:
|
|
eq: [$.vars.preparar.success, true]
|
|
```
|
|
|
|
```yaml
|
|
when:
|
|
neq: [$.vars.x.barcode, ""]
|
|
```
|
|
|
|
```yaml
|
|
when:
|
|
exists: $.vars.x.barcode
|
|
```
|
|
|
|
```yaml
|
|
when:
|
|
all:
|
|
- eq: [$.vars.x.a, true]
|
|
- eq: [$.vars.x.b, true]
|
|
```
|
|
|
|
```yaml
|
|
when:
|
|
any:
|
|
- eq: [$.vars.x.a, true]
|
|
- eq: [$.vars.x.b, true]
|
|
```
|
|
|
|
### Regra importante
|
|
|
|
As edges devem ser mutuamente compreensíveis. Se nenhuma edge corresponder, o runtime pode falhar com:
|
|
|
|
```text
|
|
Nenhuma transição do workflow correspondeu ao estado
|
|
```
|
|
|
|
Por isso fluxos condicionais normalmente possuem uma edge final sem `when`.
|
|
|
|
---
|
|
|
|
## 7. Pause / resume
|
|
|
|
Workflows conversacionais podem parar no meio da execução para pedir uma resposta ao usuário.
|
|
|
|
Exemplo simplificado:
|
|
|
|
```yaml
|
|
- id: formatar
|
|
action: formatar_invoice_explanation
|
|
pause:
|
|
enabled: true
|
|
return_from: $.output.mensagem
|
|
expected_input:
|
|
key: resposta_usuario
|
|
allowed_values: ["SIM", "NAO"]
|
|
normalize: upper_strip
|
|
resume_from: decisao
|
|
```
|
|
|
|
### 7.1 O que ocorre no primeiro turno
|
|
|
|
1. `formatar_invoice_explanation` é executada;
|
|
2. a mensagem produzida é obtida de `$.output.mensagem`;
|
|
3. o runtime persiste o checkpoint;
|
|
4. retorna `status=PAUSED`;
|
|
5. a mensagem é enviada ao usuário;
|
|
6. o workflow fica aguardando input.
|
|
|
|
### 7.2 O que ocorre no turno seguinte
|
|
|
|
A nova fala é validada contra `expected_input`.
|
|
|
|
Se aceita:
|
|
|
|
```text
|
|
resposta do usuário
|
|
↓
|
|
normalize
|
|
↓
|
|
$.input.resposta_usuario
|
|
↓
|
|
resume_from: decisao
|
|
```
|
|
|
|
### 7.3 Por que pause é separado da action
|
|
|
|
No runtime atual, pause/resume é implementado em um nó técnico separado. Isso é deliberado.
|
|
|
|
Ao retomar, **a action anterior não é reexecutada**. Isso evita repetir efeitos externos como:
|
|
|
|
- abrir protocolo duas vezes;
|
|
- cancelar duas vezes;
|
|
- enviar SMS novamente;
|
|
- criar duas SRs.
|
|
|
|
---
|
|
|
|
## 8. `expected_input`
|
|
|
|
Exemplo:
|
|
|
|
```yaml
|
|
expected_input:
|
|
key: resposta_usuario
|
|
allowed_values: ["SIM", "NAO", "OUTRO"]
|
|
normalize: upper_strip
|
|
```
|
|
|
|
### `key`
|
|
|
|
Nome em que o valor normalizado será gravado em `$.input`.
|
|
|
|
### `allowed_values`
|
|
|
|
Valores internos permitidos para a decisão do workflow.
|
|
|
|
Esses valores são **tokens de controle**, não necessariamente texto exibido ao cliente.
|
|
|
|
### `normalize: upper_strip`
|
|
|
|
Remove espaços laterais e converte para maiúsculas.
|
|
|
|
Exemplo:
|
|
|
|
```text
|
|
" sim " -> "SIM"
|
|
```
|
|
|
|
### `reprompt`
|
|
|
|
Mensagem utilizada quando o input não pode ser interpretado pelo contrato.
|
|
|
|
---
|
|
|
|
## 9. Semantic classifier de `expected_input`
|
|
|
|
O `invoice_explanation` possui um classificador semântico declarativo.
|
|
|
|
Ele existe porque respostas reais do usuário raramente são somente `sim` ou `não`.
|
|
|
|
Exemplo:
|
|
|
|
```text
|
|
"entendi, obrigado, era só isso"
|
|
```
|
|
|
|
semanticamente é `SIM`.
|
|
|
|
Já:
|
|
|
|
```text
|
|
"então no mês que vem vou pagar menos?"
|
|
```
|
|
|
|
não é `SIM`, mesmo contendo sinal de compreensão; é uma continuação da pergunta.
|
|
|
|
O YAML define:
|
|
|
|
```yaml
|
|
semantic_classifier:
|
|
enabled: true
|
|
include_relevant_context: true
|
|
option_actions:
|
|
CONTINUAR:
|
|
action: contextual_reentry
|
|
prompt: |
|
|
...
|
|
```
|
|
|
|
### 9.1 Responsabilidade correta
|
|
|
|
- **Framework:** executa o classificador e garante que a saída esteja entre os valores permitidos.
|
|
- **Workflow/agente:** define o significado de `SIM`, `NAO`, `CONTINUAR`.
|
|
|
|
### 9.2 `contextual_reentry`
|
|
|
|
Quando a opção classificada possui:
|
|
|
|
```yaml
|
|
CONTINUAR:
|
|
action: contextual_reentry
|
|
```
|
|
|
|
o workflow pausado não deve simplesmente tratar a fala como confirmação. A utterance é liberada para nova interpretação pelo roteamento normal, com contexto delimitado.
|
|
|
|
Isso é particularmente importante para evitar que hipóteses do usuário virem fatos confirmados.
|
|
|
|
---
|
|
|
|
## 10. Estado terminal e nova interação na mesma sessão
|
|
|
|
Um workflow pode terminar sem encerrar tecnicamente o `session_id`.
|
|
|
|
Isso significa:
|
|
|
|
```text
|
|
mesma sessão técnica
|
|
!=
|
|
mesmo workflow ativo
|
|
```
|
|
|
|
No `invoice_explanation`, por exemplo:
|
|
|
|
```yaml
|
|
workflow_response_final: true
|
|
```
|
|
|
|
indica que aquela action produz a resposta final daquele workflow.
|
|
|
|
Depois de uma execução terminal, o framework deve eliminar o latch operacional do workflow para que o próximo turno seja uma nova entrada, ainda na mesma sessão.
|
|
|
|
Deve permanecer:
|
|
|
|
- `session_id`;
|
|
- `session_key`;
|
|
- `conversation_key`;
|
|
- identidade do cliente;
|
|
- auditoria e telemetria;
|
|
- long-term memory.
|
|
|
|
Não deve continuar controlando a próxima entrada:
|
|
|
|
- `pending_domain_workflow`;
|
|
- `expected_input`;
|
|
- pause antigo;
|
|
- active transaction antiga;
|
|
- confirmação antiga;
|
|
- route stickiness da jornada terminada;
|
|
- short-term operational context do workflow fechado.
|
|
|
|
Esse detalhe é fundamental ao depurar cenários como:
|
|
|
|
```text
|
|
Usuário: entendi, obrigado, era só isso
|
|
Agente: Seu número de protocolo é ...
|
|
Usuário: ah espera
|
|
```
|
|
|
|
O terceiro turno é uma nova entrada na mesma sessão, e não um resume do workflow anterior.
|
|
|
|
---
|
|
|
|
# 11. Workflows atuais — explicação arquivo por arquivo
|
|
|
|
---
|
|
|
|
## 11.1 `buscar_fatura.active.yaml`
|
|
|
|
```yaml
|
|
version: 1
|
|
```
|
|
|
|
Seleciona `buscar_fatura.v1.yaml` como versão ativa.
|
|
|
|
## 11.2 `buscar_fatura.v1.yaml`
|
|
|
|
### Objetivo
|
|
|
|
Executar uma busca de fatura em um único passo.
|
|
|
|
### Cabeçalho
|
|
|
|
```yaml
|
|
name: buscar_fatura
|
|
version: 1
|
|
start: buscar_fatura
|
|
```
|
|
|
|
### Nó `buscar_fatura`
|
|
|
|
```yaml
|
|
- id: buscar_fatura
|
|
action: buscar_fatura
|
|
input:
|
|
invoice_id: $.input.invoice_id
|
|
msisdn: $.input.msisdn
|
|
customer_id: $.input.customer_id
|
|
output: $.input.output
|
|
```
|
|
|
|
A action Python está em `app/domain/contas/workflow_actions.py`.
|
|
|
|
Com `invoice_id` e `customer_id`, ela tenta buscar a fatura detalhada. Sem os identificadores necessários, usa `consultar_faturas` como fallback.
|
|
|
|
### Edge
|
|
|
|
```yaml
|
|
- from: buscar_fatura
|
|
to: END
|
|
```
|
|
|
|
Não há branch nem pausa.
|
|
|
|
### Modelo mental
|
|
|
|
```text
|
|
INPUT
|
|
|
|
|
v
|
|
buscar_fatura
|
|
|
|
|
v
|
|
END
|
|
```
|
|
|
|
### Quando usar
|
|
|
|
Quando a operação é atômica e não precisa de confirmação do usuário.
|
|
|
|
---
|
|
|
|
## 11.3 `buscar_informacao.active.yaml`
|
|
|
|
```yaml
|
|
version: 2
|
|
```
|
|
|
|
Ativa `buscar_informacao.v2.yaml`.
|
|
|
|
## 11.4 `buscar_informacao.v2.yaml`
|
|
|
|
### Objetivo
|
|
|
|
Preparar uma consulta RAG e, em seguida, preparar a resposta para composição pelo framework.
|
|
|
|
### Nó 1 — `buscar_informacao`
|
|
|
|
```yaml
|
|
id: buscar_informacao
|
|
action: buscar_informacao_rag
|
|
```
|
|
|
|
Recebe:
|
|
|
|
- `query`;
|
|
- `queries`;
|
|
- `top_k`;
|
|
- `segment`.
|
|
|
|
A action atual devolve uma estrutura declarando `delegate_to_framework_rag=true`, isto é, o domínio sinaliza que a capacidade RAG deve ser executada pelo framework.
|
|
|
|
### Nó 2 — `reescrever_resposta`
|
|
|
|
Consome os resultados do primeiro nó:
|
|
|
|
```yaml
|
|
queries: $.vars.buscar_informacao.queries
|
|
documents: $.vars.buscar_informacao.documents
|
|
answer: $.vars.buscar_informacao.answer
|
|
noMatchRag: $.vars.buscar_informacao.noMatchRag
|
|
ragRetrievedDocuments: $.vars.buscar_informacao.ragRetrievedDocuments
|
|
ragSelectedDocuments: $.vars.buscar_informacao.ragSelectedDocuments
|
|
```
|
|
|
|
A action `reescrever_resposta_buscar_informacao` devolve a mensagem e `delegate_to_framework_llm=true`.
|
|
|
|
### Fluxo
|
|
|
|
```text
|
|
buscar_informacao_rag
|
|
|
|
|
v
|
|
reescrever_resposta_buscar_informacao
|
|
|
|
|
v
|
|
END
|
|
```
|
|
|
|
### Conceito importante
|
|
|
|
A pasta `workflows/` orquestra, mas não deve implementar o mecanismo RAG. O framework continua responsável pelo runtime RAG.
|
|
|
|
---
|
|
|
|
## 11.5 `cancelamento_vas_avulso.active.yaml`
|
|
|
|
```yaml
|
|
version: 1
|
|
```
|
|
|
|
## 11.6 `cancelamento_vas_avulso.v1.yaml`
|
|
|
|
### Objetivo
|
|
|
|
Executar cancelamento em lote de VAS avulso.
|
|
|
|
### Nó único
|
|
|
|
```yaml
|
|
id: cancelar_vas_avulso
|
|
action: cancelamento_vas_avulso_batch
|
|
```
|
|
|
|
Entradas:
|
|
|
|
- `items`;
|
|
- `csp_id`;
|
|
- `channel`;
|
|
- `social_sec_no`;
|
|
- `data_credito_proxima_fatura`;
|
|
- `idempotency_key`.
|
|
|
|
O YAML também fixa valores do contrato operacional:
|
|
|
|
```yaml
|
|
request_status: "Fechado"
|
|
status: "CLOSED"
|
|
```
|
|
|
|
### Fluxo
|
|
|
|
```text
|
|
cancelamento_vas_avulso_batch -> END
|
|
```
|
|
|
|
### Ponto de atenção
|
|
|
|
É uma operação transacional. A confirmação do usuário e as políticas de tool podem ocorrer antes da entrada no workflow. Não mova para o YAML uma duplicação de confirmation policy que pertença ao framework.
|
|
|
|
O `idempotency_key` é especialmente importante em operações com efeito externo.
|
|
|
|
---
|
|
|
|
## 11.7 `contestacao_tool.active.yaml`
|
|
|
|
```yaml
|
|
version: 2
|
|
```
|
|
|
|
## 11.8 `contestacao_tool.v2.yaml`
|
|
|
|
Este é o workflow mais complexo da pasta atual.
|
|
|
|
### Objetivo
|
|
|
|
Orquestrar a contestação de cobrança, incluindo protocolo, status da fatura, abertura da contestação, SMS quando aplicável, regra de corte, Conta Certa Manual e atualização de status.
|
|
|
|
### Visão geral
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
A[registrar_protocolo] --> B[check_invoice_status]
|
|
B --> C[abrir_contestacao_cliente]
|
|
C -->|success=false| Z[END]
|
|
C -->|tem barcode| D[enviar_sms]
|
|
C -->|sem SMS| E[consultar_contrato_corte]
|
|
D --> E
|
|
E -->|Conta Certa Manual elegível| F[abrir_sr_conta_certa_manual]
|
|
E -->|caso padrão| G[atualizar_status_sr]
|
|
F --> H[atualizar_status_sr_registro]
|
|
H --> Z
|
|
G --> Z
|
|
```
|
|
|
|
### Nó 1 — `registrar_protocolo`
|
|
|
|
Primeiro efeito da jornada:
|
|
|
|
```yaml
|
|
action: registrar_protocolo
|
|
```
|
|
|
|
Configura:
|
|
|
|
```yaml
|
|
scenario: "contestacao"
|
|
request_status: "Aberto"
|
|
status: "OPENED"
|
|
```
|
|
|
|
O protocolo retornado fica acessível em:
|
|
|
|
```text
|
|
$.vars.registrar_protocolo.protocolo_id
|
|
```
|
|
|
|
### Nó 2 — `check_invoice_status`
|
|
|
|
Consulta ou reaproveita o `CompleteInvoices` já obtido em prefetch.
|
|
|
|
O comentário do YAML deixa clara a intenção arquitetural: evitar uma segunda chamada desnecessária quando o payload já existe na sessão.
|
|
|
|
### Nó 3 — `abrir_contestacao_cliente`
|
|
|
|
Recebe grande parte do contexto necessário para a operação financeira, inclusive:
|
|
|
|
- cliente;
|
|
- fatura;
|
|
- serviço/item;
|
|
- valor;
|
|
- descrição;
|
|
- protocolo;
|
|
- tipo da contestação;
|
|
- motivo do ajuste;
|
|
- opção de devolução;
|
|
- regras de Conta Certa Manual;
|
|
- `double_refund`;
|
|
- dados de atendimento;
|
|
- `skip_invoice_item_validation`.
|
|
|
|
O `invoice_status` vem do nó anterior:
|
|
|
|
```yaml
|
|
invoice_status: $.vars.check_invoice_status.invoice_status
|
|
```
|
|
|
|
### Branch de falha financeira
|
|
|
|
```yaml
|
|
- from: abrir_contestacao_cliente
|
|
to: END
|
|
priority: 1
|
|
when:
|
|
eq: [$.vars.abrir_contestacao_cliente.success, false]
|
|
```
|
|
|
|
É avaliado primeiro. Se a validação financeira bloquear a contestação, não deve executar SMS, contrato ou SR.
|
|
|
|
### Branch de SMS
|
|
|
|
```yaml
|
|
when:
|
|
all:
|
|
- exists: $.vars.abrir_contestacao_cliente.barcode
|
|
- neq: [$.vars.abrir_contestacao_cliente.barcode, ""]
|
|
```
|
|
|
|
Se a contestação produzir código de boleto, o fluxo passa por `enviar_sms`.
|
|
|
|
Caso contrário, a edge `priority: 99` segue diretamente para `consultar_contrato_corte`.
|
|
|
|
### Nó `consultar_contrato_corte`
|
|
|
|
Determina a regra de data de corte e também trata particularidade de item dependente de plano família.
|
|
|
|
### Branch Conta Certa Manual
|
|
|
|
O branch é propositalmente composto:
|
|
|
|
```yaml
|
|
when:
|
|
any:
|
|
- all:
|
|
- apos_data_corte == true
|
|
- contestation_success == true
|
|
- manual_conta_certa_indicator == true
|
|
- all:
|
|
- dependent_invoice_item == true
|
|
- contestation_registered == true
|
|
```
|
|
|
|
Isso expressa duas formas de elegibilidade:
|
|
|
|
1. regra normal após data de corte + indicador manual;
|
|
2. item de dependente cuja contestação foi registrada.
|
|
|
|
### `abrir_sr_conta_certa_manual`
|
|
|
|
Cria a SR de Conta Certa Manual.
|
|
|
|
### `atualizar_status_sr`
|
|
|
|
Caminho padrão, fecha/atualiza o protocolo principal.
|
|
|
|
### `atualizar_status_sr_registro`
|
|
|
|
Caminho usado após Conta Certa Manual, atualizando a SR correspondente.
|
|
|
|
### Pontos de atenção
|
|
|
|
- Não trocar prioridades sem revisar todos os branches.
|
|
- `success=false` precisa continuar precedendo qualquer efeito posterior.
|
|
- Reaproveitamento de prefetch evita chamadas duplicadas.
|
|
- É um workflow com múltiplos efeitos externos; qualquer retry deve ser analisado com idempotência.
|
|
|
|
---
|
|
|
|
## 11.9 `finalizar_atendimento.active.yaml`
|
|
|
|
```yaml
|
|
version: 1
|
|
```
|
|
|
|
## 11.10 `finalizar_atendimento.v1.yaml`
|
|
|
|
### Objetivo
|
|
|
|
Centralizar o fechamento final do atendimento.
|
|
|
|
### Nó único `finalizar`
|
|
|
|
```yaml
|
|
action: finalizar_atendimento_action
|
|
```
|
|
|
|
Entradas relevantes:
|
|
|
|
- `status`;
|
|
- `summary`;
|
|
- `msisdn`;
|
|
- `social_sec_no`;
|
|
- `message_id`;
|
|
- tipos informacionais de VAS;
|
|
- protocolo;
|
|
- flags de supressão/deferimento de eventos.
|
|
|
|
### Fluxo
|
|
|
|
```text
|
|
finalizar_atendimento_action -> END
|
|
```
|
|
|
|
### Observação
|
|
|
|
Finalizar atendimento é conceitualmente diferente de simplesmente alcançar `END` em qualquer workflow. `END` encerra aquele grafo; `finalizar_atendimento_action` implementa a semântica de negócio de fechamento de atendimento.
|
|
|
|
---
|
|
|
|
## 11.11 `invoice_explanation.active.yaml`
|
|
|
|
```yaml
|
|
version: 2
|
|
```
|
|
|
|
## 11.12 `invoice_explanation.v2.yaml`
|
|
|
|
### Objetivo
|
|
|
|
Explicar variação de fatura, aguardar a confirmação semântica do cliente e então:
|
|
|
|
- registrar aceite e protocolo final; ou
|
|
- registrar negativa e transferir para atendimento humano.
|
|
|
|
Também possui caminhos de falha de serviço e validação de tentativa.
|
|
|
|
### Visão principal
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
A[preparar] -->|success| B[formatar]
|
|
A -->|service_failed| F[resposta_falha_servico]
|
|
A -->|success=false| C[checar_tentativa]
|
|
C -->|limite excedido| D[fim_intencao_invalida]
|
|
C -->|caso contrário| E[texto_intencao_invalida]
|
|
B --> P{{PAUSE}}
|
|
P --> G[decisao]
|
|
G -->|SIM| H[registrar_sim]
|
|
H --> I[registrar_protocolo_aceite]
|
|
I --> Z[END]
|
|
G -->|NAO| J[registrar_nao]
|
|
J --> K[handoff_pos_explicacao_nao]
|
|
K --> Z
|
|
```
|
|
|
|
### Nó `preparar`
|
|
|
|
Action:
|
|
|
|
```text
|
|
preparar_invoice_explanation
|
|
```
|
|
|
|
Responsável por obter ou reutilizar a explicação base.
|
|
|
|
Recebe dados da fatura atual/passada e também:
|
|
|
|
```yaml
|
|
tentativa_anterior: $.vars.preparar.tentativa
|
|
```
|
|
|
|
Isso permite controlar tentativas dentro do estado do workflow.
|
|
|
|
### Nó `formatar`
|
|
|
|
Action:
|
|
|
|
```text
|
|
formatar_invoice_explanation
|
|
```
|
|
|
|
Ela monta a mensagem, mas **não controla a pausa**. A pausa está declarada no YAML.
|
|
|
|
Essa separação é intencional: apresentação e controle de fluxo não devem ficar acoplados.
|
|
|
|
### Pause do `formatar`
|
|
|
|
Sempre pausa após apresentar a explicação.
|
|
|
|
```yaml
|
|
allowed_values: ["SIM", "NAO", "CONTINUAR"]
|
|
```
|
|
|
|
O semantic classifier diferencia confirmação, negativa e continuação contextual.
|
|
|
|
#### Exemplos
|
|
|
|
```text
|
|
"sim" -> SIM
|
|
"entendi, obrigado" -> SIM
|
|
"não resolveu" -> NAO
|
|
"é a cobrança de 14,99" -> CONTINUAR
|
|
"mês que vem fica mais barato?" -> CONTINUAR
|
|
```
|
|
|
|
### `decisao`
|
|
|
|
É um `no_op`. Sua função é fornecer um ponto explícito no grafo para branching após o resume.
|
|
|
|
### Caminho SIM
|
|
|
|
```text
|
|
decisao
|
|
-> registrar_sim
|
|
-> registrar_protocolo_aceite
|
|
-> END
|
|
```
|
|
|
|
`registrar_protocolo_aceite` usa:
|
|
|
|
```yaml
|
|
workflow_response_final: true
|
|
```
|
|
|
|
para indicar que a mensagem retornada é a resposta final do workflow.
|
|
|
|
### Caminho NAO
|
|
|
|
```text
|
|
decisao
|
|
-> registrar_nao
|
|
-> handoff_pos_explicacao_nao
|
|
-> END
|
|
```
|
|
|
|
`preparar_handoff_invoice_explanation` materializa:
|
|
|
|
```text
|
|
session_control = HUMAN_HANDOFF
|
|
```
|
|
|
|
A decisão de jornada está no workflow do Contas; a primitive de handoff é do framework.
|
|
|
|
### Caminhos de erro
|
|
|
|
`preparar` distingue:
|
|
|
|
- `success=true`;
|
|
- `service_failed=true`;
|
|
- `success=false` por validação/tentativa.
|
|
|
|
`checar_tentativa` decide se ainda pode pedir novamente ou se o limite foi excedido.
|
|
|
|
### Nós `checar_vas_variacao` e `finalizar_nao_resolvido`
|
|
|
|
Esses nós estão declarados para a política de VAS variado/não resolvido. Observe que, na versão atual, não existe edge de entrada para `checar_vas_variacao` partindo do fluxo principal SIM/NAO mostrado acima. Antes de reutilizar ou alterar esses nós, valide a intenção de jornada e os testes associados.
|
|
|
|
### Ponto crítico de lifecycle
|
|
|
|
Quando `registrar_protocolo_aceite` produz `workflow_response_final=true`, o workflow deve ser considerado terminal mesmo que alguma integração legada devolva metadata antiga com `PAUSED`. O próximo turno na mesma sessão não deve reutilizar `expected_input` desse workflow.
|
|
|
|
---
|
|
|
|
## 11.13 `pro_rata.active.yaml`
|
|
|
|
```yaml
|
|
version: 3
|
|
```
|
|
|
|
## 11.14 `pro_rata.v3.yaml`
|
|
|
|
### Objetivo
|
|
|
|
Explicar cobrança proporcional (`pro rata`) e tratar de forma diferente clientes com Plano Controle.
|
|
|
|
### Nó `preparar`
|
|
|
|
Action:
|
|
|
|
```text
|
|
preparar_pro_rata
|
|
```
|
|
|
|
Recebe planos e `has_plano_controle`.
|
|
|
|
A action decide se a jornada precisa interagir com o usuário:
|
|
|
|
```text
|
|
await_user_input = true/false
|
|
```
|
|
|
|
### Nó `formatar`
|
|
|
|
Possui pause condicional:
|
|
|
|
```yaml
|
|
pause:
|
|
enabled: true
|
|
when:
|
|
eq: [$.vars.preparar.await_user_input, true]
|
|
```
|
|
|
|
Portanto, diferente do `invoice_explanation`, este workflow só pausa quando necessário.
|
|
|
|
### Expected input
|
|
|
|
```yaml
|
|
allowed_values: ["SIM", "NAO", "OUTRO"]
|
|
```
|
|
|
|
### `decisao_esclarecimento`
|
|
|
|
Branch:
|
|
|
|
- `SIM` -> `registrar_aceitou`;
|
|
- `NAO` -> `devolver_orquestrador`;
|
|
- qualquer outra situação -> `reperguntar_esclarecimento`.
|
|
|
|
### `reperguntar_esclarecimento`
|
|
|
|
Formata novamente e pausa de novo, retomando em `decisao_esclarecimento`.
|
|
|
|
Isso forma um pequeno loop conversacional controlado:
|
|
|
|
```text
|
|
reperguntar
|
|
|
|
|
pause
|
|
|
|
|
+----> decisao_esclarecimento
|
|
```
|
|
|
|
### Caso sem Plano Controle
|
|
|
|
Se `await_user_input=false`, a edge de prioridade 20 sai de `formatar` para:
|
|
|
|
```text
|
|
registrar_nao_controle -> END
|
|
```
|
|
|
|
### Caso SIM
|
|
|
|
```text
|
|
registrar_aceitou -> END
|
|
```
|
|
|
|
Essa action também registra protocolo/evento apropriado.
|
|
|
|
---
|
|
|
|
## 11.15 `termino_desconto.active.yaml`
|
|
|
|
```yaml
|
|
version: 1
|
|
```
|
|
|
|
## 11.16 `termino_desconto.v1.yaml`
|
|
|
|
### Objetivo
|
|
|
|
Formatar a resposta da capability de término de desconto.
|
|
|
|
### Nó único
|
|
|
|
```yaml
|
|
id: formatar
|
|
action: formatar_capability_resposta
|
|
```
|
|
|
|
Com:
|
|
|
|
```yaml
|
|
tipo: termino_desconto
|
|
```
|
|
|
|
Além de dados do plano/fatura/evidência de desconto.
|
|
|
|
### Conceito
|
|
|
|
A mesma action genérica `formatar_capability_resposta` é parametrizada pelo `tipo` da capability.
|
|
|
|
### Fluxo
|
|
|
|
```text
|
|
formatar_capability_resposta(tipo=termino_desconto) -> END
|
|
```
|
|
|
|
---
|
|
|
|
## 11.17 `valor_divergente.active.yaml`
|
|
|
|
```yaml
|
|
version: 1
|
|
```
|
|
|
|
## 11.18 `valor_divergente.v1.yaml`
|
|
|
|
### Objetivo
|
|
|
|
Formatar resposta para a capability de valor divergente.
|
|
|
|
### Nó único
|
|
|
|
```yaml
|
|
action: formatar_capability_resposta
|
|
input:
|
|
tipo: valor_divergente
|
|
msisdn: $.input.msisdn
|
|
```
|
|
|
|
É estruturalmente semelhante ao `termino_desconto`, mas com outro `tipo` e conjunto de inputs.
|
|
|
|
### Ponto de atenção
|
|
|
|
Se a capability passar a exigir dados adicionais, prefira explicitá-los no YAML, mantendo claro o contrato entre workflow e action.
|
|
|
|
---
|
|
|
|
## 11.19 `vas_estrategico.active.yaml`
|
|
|
|
```yaml
|
|
version: 3
|
|
```
|
|
|
|
## 11.20 `vas_estrategico.v3.yaml`
|
|
|
|
### Objetivo
|
|
|
|
Tratar VAS estratégico e bundle, apresentar explicação, coletar aceite/negativa e registrar o resultado.
|
|
|
|
### Visão geral
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
A[preparar] -->|await_user_input| P{{PAUSE}}
|
|
A -->|sem pausa| B[resposta_bundle]
|
|
P --> C[decisao]
|
|
C -->|SIM| D[resposta_sim]
|
|
C -->|NAO e estratégico| E[explicar_cancelamento]
|
|
C -->|NAO bundle puro| B
|
|
C -->|fallback| F[registrar_outro]
|
|
D --> G[registrar_sim]
|
|
E --> H[registrar_nao]
|
|
B --> I[registrar_bundle]
|
|
G --> Z[END]
|
|
H --> Z
|
|
I --> Z
|
|
F --> Z
|
|
```
|
|
|
|
### Nó `preparar`
|
|
|
|
Action:
|
|
|
|
```text
|
|
preparar_vas_estrategico
|
|
```
|
|
|
|
Recebe `items` e `linhas`.
|
|
|
|
Possui pausa condicionada à saída da própria action:
|
|
|
|
```yaml
|
|
when:
|
|
eq: [$.output.await_user_input, true]
|
|
```
|
|
|
|
### Pause
|
|
|
|
```yaml
|
|
allowed_values: ["SIM", "NAO", "OUTRO"]
|
|
resume_from: decisao
|
|
```
|
|
|
|
### `resposta_bundle`
|
|
|
|
Monta texto a partir de:
|
|
|
|
```yaml
|
|
$.vars.preparar.mensagem_bundle_fechamento
|
|
```
|
|
|
|
### `decisao`
|
|
|
|
É o ponto de branching depois da pausa.
|
|
|
|
#### SIM
|
|
|
|
Sempre vai para `resposta_sim`, seja bundle ou estratégico.
|
|
|
|
#### NAO + estratégico
|
|
|
|
```yaml
|
|
all:
|
|
- has_estrategico_items == true
|
|
- resposta_usuario == NAO
|
|
```
|
|
|
|
vai para `explicar_cancelamento`.
|
|
|
|
#### NAO + bundle puro
|
|
|
|
Quando não há item estratégico, vai para `resposta_bundle`.
|
|
|
|
#### fallback
|
|
|
|
`priority: 99` -> `registrar_outro`.
|
|
|
|
O comentário do arquivo ressalta que, em operação normal, `OUTRO` deveria ser interceptado/reperguntado pelo runtime antes de entrar nessa decisão; o fallback continua existindo como proteção.
|
|
|
|
### Registro final
|
|
|
|
Há actions diferentes para preservar o caminho de negócio:
|
|
|
|
- `registrar_sim`;
|
|
- `registrar_nao`;
|
|
- `registrar_bundle`;
|
|
- `registrar_outro`.
|
|
|
|
Todas terminam em `END`.
|
|
|
|
---
|
|
|
|
# 12. Mapa workflow -> actions Python
|
|
|
|
| Workflow | Action(s) principais | Implementação |
|
|
|---|---|---|
|
|
| `buscar_fatura` | `buscar_fatura` | `app/domain/contas/workflow_actions.py` |
|
|
| `buscar_informacao` | `buscar_informacao_rag`, `reescrever_resposta_buscar_informacao` | mesmo arquivo |
|
|
| `cancelamento_vas_avulso` | `cancelamento_vas_avulso_batch` | mesmo arquivo |
|
|
| `contestacao_tool` | `registrar_protocolo`, `check_invoice_status`, `abrir_contestacao_cliente`, `enviar_sms`, `consultar_contrato_corte`, `abrir_sr_conta_certa_manual`, `atualizar_status_sr` | mesmo arquivo |
|
|
| `finalizar_atendimento` | `finalizar_atendimento_action` | mesmo arquivo |
|
|
| `invoice_explanation` | `preparar_invoice_explanation`, `formatar_invoice_explanation`, `checar_tentativa_cvn`, `registrar_atendimento_invoice_explanation`, `registrar_protocolo_inicio`, `preparar_handoff_invoice_explanation`, `checar_vas_variado` | mesmo arquivo |
|
|
| `pro_rata` | `preparar_pro_rata`, `formatar_pro_rata`, `registrar_atendimento_pro_rata` | mesmo arquivo |
|
|
| `termino_desconto` | `formatar_capability_resposta` | mesmo arquivo |
|
|
| `valor_divergente` | `formatar_capability_resposta` | mesmo arquivo |
|
|
| `vas_estrategico` | `preparar_vas_estrategico`, `montar_resposta_texto`, `montar_explicacao_cancelamento_vas_estrategico`, `registrar_atendimento_vas_estrategico` | mesmo arquivo |
|
|
|
|
`no_op` e `montar_resposta_texto` são actions utilitárias registradas pelo mesmo registry de domínio.
|
|
|
|
---
|
|
|
|
# 13. Como criar um novo workflow
|
|
|
|
## Passo 1 — definir a responsabilidade
|
|
|
|
Pergunte:
|
|
|
|
- há mais de uma etapa?
|
|
- existe branching?
|
|
- existe efeito externo?
|
|
- existe pausa conversacional?
|
|
- precisa ser retomado em outro turno?
|
|
|
|
Se a operação for uma única função sem jornada, talvez uma tool/action simples seja suficiente.
|
|
|
|
## Passo 2 — registrar as actions
|
|
|
|
Em `workflow_actions.py`:
|
|
|
|
```python
|
|
@reg.action("consultar_exemplo")
|
|
def consultar_exemplo(params, state):
|
|
result = service.consultar(...)
|
|
return {
|
|
"success": True,
|
|
"dados": result,
|
|
}
|
|
```
|
|
|
|
## Passo 3 — criar `nome.v1.yaml`
|
|
|
|
```yaml
|
|
name: meu_workflow
|
|
version: 1
|
|
start: consultar
|
|
|
|
nodes:
|
|
- id: consultar
|
|
action: consultar_exemplo
|
|
input:
|
|
msisdn: $.input.msisdn
|
|
|
|
edges:
|
|
- from: consultar
|
|
to: END
|
|
```
|
|
|
|
## Passo 4 — criar marcador ativo
|
|
|
|
```yaml
|
|
# meu_workflow.active.yaml
|
|
version: 1
|
|
```
|
|
|
|
## Passo 5 — adicionar branches
|
|
|
|
Sempre pense em fallback explícito.
|
|
|
|
```yaml
|
|
- from: consultar
|
|
to: sucesso
|
|
priority: 10
|
|
when:
|
|
eq: [$.vars.consultar.success, true]
|
|
|
|
- from: consultar
|
|
to: falha
|
|
priority: 99
|
|
```
|
|
|
|
## Passo 6 — adicionar pause somente quando a jornada exige input
|
|
|
|
Não coloque pausa dentro da lógica Python da action se ela faz parte do contrato do fluxo.
|
|
|
|
## Passo 7 — testar
|
|
|
|
No mínimo:
|
|
|
|
- happy path;
|
|
- cada branch;
|
|
- falha da integração;
|
|
- input ausente;
|
|
- pause;
|
|
- resume;
|
|
- resposta inválida;
|
|
- idempotência de efeitos externos;
|
|
- terminalidade;
|
|
- novo turno após finalização.
|
|
|
|
---
|
|
|
|
# 14. Como criar uma nova versão
|
|
|
|
Suponha que `vas_estrategico.v3.yaml` precise mudar materialmente.
|
|
|
|
1. copie para `vas_estrategico.v4.yaml`;
|
|
2. altere internamente `version: 4`;
|
|
3. implemente/teste a nova lógica;
|
|
4. mantenha v3 disponível;
|
|
5. altere somente depois:
|
|
|
|
```yaml
|
|
# vas_estrategico.active.yaml
|
|
version: 4
|
|
```
|
|
|
|
### Rollback
|
|
|
|
Basta voltar o marker:
|
|
|
|
```yaml
|
|
version: 3
|
|
```
|
|
|
|
sem apagar a v4.
|
|
|
|
---
|
|
|
|
# 15. Como depurar um workflow
|
|
|
|
## 15.1 Comece pelo status
|
|
|
|
Procure:
|
|
|
|
```text
|
|
COMPLETED
|
|
PAUSED
|
|
FAILED
|
|
```
|
|
|
|
## 15.2 Confira `workflow_name` e `workflow_version`
|
|
|
|
Isso confirma qual YAML realmente foi carregado.
|
|
|
|
## 15.3 Confira `trace`
|
|
|
|
Exemplo:
|
|
|
|
```text
|
|
preparar -> COMPLETED
|
|
formatar -> COMPLETED
|
|
formatar -> pause_resume RESUMED
|
|
decisao -> COMPLETED
|
|
registrar_sim -> COMPLETED
|
|
registrar_protocolo_aceite -> COMPLETED
|
|
```
|
|
|
|
O trace responde rapidamente:
|
|
|
|
- qual action executou;
|
|
- qual nó foi o último;
|
|
- se houve resume;
|
|
- se alguma action foi repetida.
|
|
|
|
## 15.4 Confira `vars`
|
|
|
|
Ao investigar uma edge:
|
|
|
|
```yaml
|
|
when:
|
|
eq: [$.vars.consultar_contrato_corte.apos_data_corte, true]
|
|
```
|
|
|
|
primeiro valide o conteúdo real de:
|
|
|
|
```text
|
|
vars.consultar_contrato_corte.apos_data_corte
|
|
```
|
|
|
|
Não conclua que a edge está errada sem verificar a saída da action.
|
|
|
|
## 15.5 Confira `pause.expected_input`
|
|
|
|
Se o sistema está tratando uma frase como resposta de um fluxo anterior, procure:
|
|
|
|
```text
|
|
pending_domain_workflow
|
|
expected_input
|
|
transaction_status
|
|
workflow_resume
|
|
```
|
|
|
|
Após workflow terminal, esses latches não devem sequestrar o próximo turno.
|
|
|
|
## 15.6 Confira o marker `.active.yaml`
|
|
|
|
Um erro comum é editar `v3.yaml`, mas o marker continuar apontando para v2.
|
|
|
|
---
|
|
|
|
# 16. Regras de desenho recomendadas
|
|
|
|
## 16.1 Workflow orquestra; action executa
|
|
|
|
Bom:
|
|
|
|
```yaml
|
|
when:
|
|
eq: [$.vars.validar.success, false]
|
|
```
|
|
|
|
Action retorna a evidência; YAML escolhe o próximo passo.
|
|
|
|
Evite colocar toda a jornada dentro de uma única action gigante.
|
|
|
|
## 16.2 Não colocar regra TIM no runtime genérico
|
|
|
|
Se a regra pertence a contestação, VAS ou fatura, ela deve ficar no domínio/configuração do agente, não hardcoded no framework.
|
|
|
|
## 16.3 Side effects precisam de idempotência
|
|
|
|
Especialmente:
|
|
|
|
- cancelamento;
|
|
- contestação;
|
|
- protocolo;
|
|
- SMS;
|
|
- criação de SR.
|
|
|
|
## 16.4 Pausa não deve reexecutar action anterior
|
|
|
|
Mantenha o desenho em que o pause é um contrato do nó e o resume segue para `resume_from`.
|
|
|
|
## 16.5 Prioridade deve ser intencional
|
|
|
|
Use números que deixem clara a hierarquia:
|
|
|
|
```text
|
|
1 bloqueio terminal crítico
|
|
10 caminho específico
|
|
20 segundo caminho específico
|
|
99 fallback
|
|
```
|
|
|
|
## 16.6 Não use output textual para decidir operação financeira
|
|
|
|
Branching deve usar campos estruturados como:
|
|
|
|
```text
|
|
success
|
|
barcode
|
|
apos_data_corte
|
|
dependent_invoice_item
|
|
```
|
|
|
|
não palavras encontradas em uma frase produzida por LLM.
|
|
|
|
---
|
|
|
|
# 17. Anti-patterns
|
|
|
|
### 17.1 Alterar `.active.yaml` sem criar a versão
|
|
|
|
Errado:
|
|
|
|
```yaml
|
|
version: 4
|
|
```
|
|
|
|
sem existir `nome.v4.yaml`.
|
|
|
|
### 17.2 Action não registrada
|
|
|
|
Se o YAML contém:
|
|
|
|
```yaml
|
|
action: minha_action
|
|
```
|
|
|
|
mas o registry não possui esse nome, o runtime falhará com action não registrada.
|
|
|
|
### 17.3 Referenciar `$.vars` de nó que ainda não executou
|
|
|
|
Exemplo incorreto:
|
|
|
|
```yaml
|
|
start: B
|
|
|
|
B:
|
|
input:
|
|
protocolo: $.vars.A.protocolo
|
|
```
|
|
|
|
se `A` nunca foi executado.
|
|
|
|
### 17.4 Branch sem fallback
|
|
|
|
Pode provocar falha de transição.
|
|
|
|
### 17.5 Usar `pause` para esconder estado de domínio
|
|
|
|
`pause` deve indicar interação com usuário, não substituir persistência correta de transação.
|
|
|
|
### 17.6 Reutilizar workflow terminal como contexto ativo
|
|
|
|
Um workflow terminado pode permanecer no histórico para auditoria, mas não deve continuar fornecendo `expected_input` ao próximo turno.
|
|
|
|
---
|
|
|
|
# 18. Checklist de code review
|
|
|
|
Antes de aprovar alteração em `workflows/`:
|
|
|
|
- [ ] `name` corresponde ao arquivo;
|
|
- [ ] `version` corresponde ao sufixo `.vN`;
|
|
- [ ] `active.yaml` aponta para uma versão existente;
|
|
- [ ] `start` existe;
|
|
- [ ] IDs de nós são únicos;
|
|
- [ ] todas as actions estão registradas;
|
|
- [ ] todos os `$.input` necessários são fornecidos pelo caller;
|
|
- [ ] referências `$.vars.<node>` apontam para nós que executaram antes;
|
|
- [ ] branches específicos têm prioridade anterior ao fallback;
|
|
- [ ] existe fallback quando necessário;
|
|
- [ ] `END` está alcançável;
|
|
- [ ] effects externos são idempotentes ou protegidos;
|
|
- [ ] pause não reexecuta action de efeito externo;
|
|
- [ ] `resume_from` existe;
|
|
- [ ] `allowed_values` são tokens internos coerentes;
|
|
- [ ] semantic classifier não transforma pergunta/hipótese em confirmação;
|
|
- [ ] workflow terminal limpa latch operacional;
|
|
- [ ] próximo turno na mesma sessão é testado;
|
|
- [ ] testes de happy path e todos os branches existem.
|
|
|
|
---
|
|
|
|
# 19. Resumo conceitual para novos desenvolvedores
|
|
|
|
Se você lembrar somente destas dez regras, já consegue navegar pela pasta com segurança:
|
|
|
|
1. **`.active.yaml` escolhe a versão; `.vN.yaml` contém a lógica.**
|
|
2. **`nodes` executam actions; `edges` decidem o próximo nó.**
|
|
3. **`$.input` é entrada; `$.vars.<node>` é resultado de nó anterior.**
|
|
4. **Menor `priority` é avaliada primeiro.**
|
|
5. **Uma edge sem `when` normalmente é o fallback.**
|
|
6. **`pause` suspende a jornada; `resume_from` determina onde continuar.**
|
|
7. **Tokens `SIM/NAO/CONTINUAR/OUTRO` são controle interno, não fraseologia.**
|
|
8. **Actions fazem domínio/integração; o YAML faz orquestração.**
|
|
9. **`END` termina o grafo; finalização de atendimento pode envolver action própria.**
|
|
10. **Workflow terminado não deve controlar o próximo turno, mesmo quando o `session_id` permanece igual.**
|
|
|
|
---
|
|
|
|
# 20. Referências de código dentro do projeto
|
|
|
|
Para aprofundar a implementação:
|
|
|
|
```text
|
|
/workflows/
|
|
definições declarativas do Contas
|
|
|
|
/app/domain/contas/workflow_actions.py
|
|
implementação das actions usadas pelos workflows
|
|
|
|
/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/models.py
|
|
schema Pydantic do DSL
|
|
|
|
/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/repository.py
|
|
resolução de active version
|
|
|
|
/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/runtime.py
|
|
executor, branching, pause/resume e LangGraph
|
|
|
|
/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/registry.py
|
|
registro e resolução das actions
|
|
|
|
/app/workflows/agent_graph.py
|
|
grafo principal do agente Contas e integração com router/guardrails/judges
|
|
```
|
|
|
|
---
|
|
|
|
## Apêndice A — Exemplo completo comentado
|
|
|
|
```yaml
|
|
name: exemplo_confirmacao
|
|
version: 1
|
|
start: preparar
|
|
|
|
nodes:
|
|
# Executa domínio e produz mensagem + dados estruturados.
|
|
- id: preparar
|
|
action: preparar_exemplo
|
|
input:
|
|
msisdn: $.input.msisdn
|
|
|
|
# Apenas apresenta a mensagem e pausa.
|
|
- id: apresentar
|
|
action: montar_resposta_texto
|
|
input:
|
|
dados:
|
|
texto_usuario: $.vars.preparar.mensagem
|
|
pause:
|
|
enabled: true
|
|
return_from: $.output.mensagem
|
|
expected_input:
|
|
key: resposta_usuario
|
|
allowed_values: ["SIM", "NAO"]
|
|
normalize: upper_strip
|
|
resume_from: decidir
|
|
|
|
# Nó estrutural para branch pós-resume.
|
|
- id: decidir
|
|
action: no_op
|
|
input: {}
|
|
|
|
- id: confirmar
|
|
action: executar_exemplo
|
|
input:
|
|
msisdn: $.input.msisdn
|
|
|
|
- id: cancelar
|
|
action: montar_resposta_texto
|
|
input:
|
|
dados:
|
|
texto_usuario: "Operação não realizada."
|
|
|
|
edges:
|
|
- from: preparar
|
|
to: apresentar
|
|
|
|
- from: apresentar
|
|
to: decidir
|
|
|
|
- from: decidir
|
|
to: confirmar
|
|
priority: 10
|
|
when:
|
|
eq: [$.input.resposta_usuario, SIM]
|
|
|
|
- from: decidir
|
|
to: cancelar
|
|
priority: 20
|
|
when:
|
|
eq: [$.input.resposta_usuario, NAO]
|
|
|
|
- from: confirmar
|
|
to: END
|
|
|
|
- from: cancelar
|
|
to: END
|
|
```
|
|
|
|
Leitura em português simples:
|
|
|
|
> Prepare os dados, mostre uma mensagem, pare e aguarde SIM/NAO. Quando o usuário responder, continue em `decidir`. Se SIM, execute a operação; se NAO, responda que nada foi feito. Depois encerre o workflow.
|
|
|
|
---
|
|
|
|
**Fim do manual.**
|