fix(n8n): responde de forma sincrona (texto puro) e usa memoria simples
O Z-PRO captura a resposta HTTP do webhook e reenvia direto ao cliente (variavel n8n1 -> passo Mensagem), entao o envio assincrono via API duplicava a mensagem. Troca para responseMode: responseNode + Respond to Webhook em texto puro, remove o node de envio assincrono, e troca a memoria de sessao de Postgres (reservado para outro fluxo) para o node Simple Memory nativo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -205,11 +205,62 @@ agendamento desta conta (`Agendamento - GChat - Z-PRO (clínica exemplo)`):
|
|||||||
Os dois testes reais feitos pelo usuário mostraram o webhook recebendo
|
Os dois testes reais feitos pelo usuário mostraram o webhook recebendo
|
||||||
`body: {}` (content-length 0, user-agent `axios/1.19.0`) — ou seja, o Z-PRO
|
`body: {}` (content-length 0, user-agent `axios/1.19.0`) — ou seja, o Z-PRO
|
||||||
não mandou `protocol`/`entrada`/`contactName`/`number` como os bots de
|
não mandou `protocol`/`entrada`/`contactName`/`number` como os bots de
|
||||||
agendamento recebem. Isso sugere que a URL foi configurada num campo de
|
agendamento recebem. Causa raiz confirmada pelo usuário: no ChatFlow do
|
||||||
webhook diferente do usado pelo ChatFlow desses bots (que é o que gera esse
|
Z-PRO, o passo "Webhook Avançado" do fluxo de orçamento (`Menu Orçamento` /
|
||||||
formato JSON). Ainda em aberto — precisa confirmar com o usuário onde
|
`Menu Orçamento 2`) estava com o campo **Body** vazio, enquanto o do fluxo de
|
||||||
exatamente a URL foi cadastrada no painel do Z-PRO, e ajustar pro mesmo
|
agendamento (`Menu Agendamento` / `Menu Agendamento 2`, que já funciona) tem:
|
||||||
mecanismo usado pelos bots que já funcionam (ChatFlow → passo "Webhook").
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"step": "process",
|
||||||
|
"entrada": "{{entrada}}",
|
||||||
|
"number": "{{phoneNumber}}",
|
||||||
|
"contactName": "{{name}}",
|
||||||
|
"protocol": "{{protocol}}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Ação do usuário: copiar o mesmo Body (com essas mesmas variáveis) para os
|
||||||
|
passos "Webhook Avançado" de `Menu Orçamento` e `Menu Orçamento 2`. Os nomes
|
||||||
|
de campo já batem com o que o node "Extrair campos do webhook" espera
|
||||||
|
(`body.protocol`, `body.number`, `body.contactName`, `body.entrada`) — não
|
||||||
|
precisou ajustar nada do lado do n8n para esses campos.
|
||||||
|
|
||||||
|
### Descoberta 2: o Z-PRO captura a resposta HTTP e reenvia ao cliente
|
||||||
|
|
||||||
|
Nas telas do ChatFlow, o passo "Webhook Avançado" tem uma seção "Resposta da
|
||||||
|
API (captura e roteamento)": ele salva o corpo da resposta HTTP do nosso
|
||||||
|
webhook numa variável (`n8n1`) e o passo seguinte ("Mensagem") manda
|
||||||
|
`{{n8n1}}` de volta ao cliente pelo WhatsApp. Ou seja, **o Z-PRO não usa a
|
||||||
|
API de envio de mensagens para essa resposta — ele repassa direto o que o
|
||||||
|
webhook devolver na hora**. Como o "Caminho no JSON" fica vazio, o corpo
|
||||||
|
precisa ser **texto puro**, não JSON.
|
||||||
|
|
||||||
|
Isso invalida a decisão original de "envio assíncrono via API do Z-PRO": o
|
||||||
|
workflow foi ajustado para responder de forma **síncrona**, com
|
||||||
|
`responseMode: responseNode` no Webhook e um node "Respond to Webhook"
|
||||||
|
(`respondWith: text`) devolvendo o texto final direto na resposta HTTP. O
|
||||||
|
node "Enviar orçamento (Z-PRO)" (chamada assíncrona ao sub-workflow
|
||||||
|
`enviar_texto`) foi removido — mantê-lo mandaria a mensagem duas vezes.
|
||||||
|
|
||||||
|
### Descoberta 3: memória de conversa
|
||||||
|
|
||||||
|
A credencial Postgres reaproveitada do bot de agendamento
|
||||||
|
(`Postgres Agendamento - (clínica exemplo)`) é de uso exclusivo daquele
|
||||||
|
fluxo — o usuário pediu para não usá-la aqui sem autorização explícita. A
|
||||||
|
memória de sessão agora usa o node nativo **Simple Memory**
|
||||||
|
(`memoryBufferWindow`, sem credencial), com `sessionKey` prefixado
|
||||||
|
(`orcamento-<protocolo ou telefone>`) para isolar as conversas de orçamento
|
||||||
|
das de agendamento.
|
||||||
|
|
||||||
|
### Ainda em aberto
|
||||||
|
|
||||||
|
Não há, até agora, nenhuma variável de mídia/imagem confirmada no seletor de
|
||||||
|
variáveis do ChatFlow do Z-PRO (o Body de `Menu Agendamento` só tem campos de
|
||||||
|
texto). Falta o usuário verificar, ao editar o Body do passo "Webhook
|
||||||
|
Avançado", se existe alguma variável de imagem/anexo no seletor (ícone "×")
|
||||||
|
— se existir, incluir no Body (ex.: `"mediaUrl": "{{variavel}}"`) e informar
|
||||||
|
o nome exato pra ajustar o node "Extrair campos do webhook".
|
||||||
|
|
||||||
## Fora de escopo (por enquanto)
|
## Fora de escopo (por enquanto)
|
||||||
|
|
||||||
|
|||||||
@@ -4,32 +4,24 @@
|
|||||||
"active": true,
|
"active": true,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
|
"parameters": {
|
||||||
|
"httpMethod": "POST",
|
||||||
|
"path": "orcamento-exames-zpro",
|
||||||
|
"options": {},
|
||||||
|
"responseMode": "responseNode"
|
||||||
|
},
|
||||||
"id": "webhook-orcamento",
|
"id": "webhook-orcamento",
|
||||||
"name": "Webhook - Foto Exame",
|
"name": "Webhook - Foto Exame",
|
||||||
"type": "n8n-nodes-base.webhook",
|
"type": "n8n-nodes-base.webhook",
|
||||||
"typeVersion": 2.1,
|
"typeVersion": 2.1,
|
||||||
"position": [
|
"position": [
|
||||||
-600,
|
-608,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
"parameters": {
|
|
||||||
"httpMethod": "POST",
|
|
||||||
"path": "orcamento-exames-zpro",
|
|
||||||
"responseMode": "onReceived",
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
"webhookId": "5ea5f89e-7351-4d45-a881-0055009423a0",
|
"webhookId": "5ea5f89e-7351-4d45-a881-0055009423a0",
|
||||||
"onError": "continueRegularOutput"
|
"onError": "continueRegularOutput"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "extrair-campos",
|
|
||||||
"name": "Extrair campos do webhook",
|
|
||||||
"type": "n8n-nodes-base.set",
|
|
||||||
"typeVersion": 3.4,
|
|
||||||
"position": [
|
|
||||||
-360,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"assignments": {
|
"assignments": {
|
||||||
"assignments": [
|
"assignments": [
|
||||||
@@ -72,24 +64,24 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"options": {}
|
"options": {}
|
||||||
}
|
},
|
||||||
|
"id": "extrair-campos",
|
||||||
|
"name": "Extrair campos do webhook",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 3.4,
|
||||||
|
"position": [
|
||||||
|
-368,
|
||||||
|
0
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "if-e-imagem",
|
|
||||||
"name": "É mensagem de imagem?",
|
|
||||||
"type": "n8n-nodes-base.if",
|
|
||||||
"typeVersion": 2.3,
|
|
||||||
"position": [
|
|
||||||
-120,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"conditions": {
|
"conditions": {
|
||||||
"options": {
|
"options": {
|
||||||
"version": 2,
|
|
||||||
"leftValue": "",
|
|
||||||
"caseSensitive": true,
|
"caseSensitive": true,
|
||||||
"typeValidation": "strict"
|
"leftValue": "",
|
||||||
|
"typeValidation": "strict",
|
||||||
|
"version": 2
|
||||||
},
|
},
|
||||||
"combinator": "and",
|
"combinator": "and",
|
||||||
"conditions": [
|
"conditions": [
|
||||||
@@ -106,19 +98,18 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"options": {}
|
"options": {}
|
||||||
}
|
},
|
||||||
|
"id": "if-e-imagem",
|
||||||
|
"name": "É mensagem de imagem?",
|
||||||
|
"type": "n8n-nodes-base.if",
|
||||||
|
"typeVersion": 2.3,
|
||||||
|
"position": [
|
||||||
|
-128,
|
||||||
|
0
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "baixar-imagem",
|
|
||||||
"name": "Baixar imagem",
|
|
||||||
"type": "n8n-nodes-base.httpRequest",
|
|
||||||
"typeVersion": 4.4,
|
|
||||||
"position": [
|
|
||||||
120,
|
|
||||||
-120
|
|
||||||
],
|
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"method": "GET",
|
|
||||||
"url": "={{ $json.imagemUrl }}",
|
"url": "={{ $json.imagemUrl }}",
|
||||||
"options": {
|
"options": {
|
||||||
"response": {
|
"response": {
|
||||||
@@ -129,24 +120,33 @@
|
|||||||
"timeout": 20000
|
"timeout": 20000
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"id": "baixar-imagem",
|
||||||
|
"name": "Baixar imagem",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.4,
|
||||||
|
"position": [
|
||||||
|
64,
|
||||||
|
-288
|
||||||
|
],
|
||||||
"onError": "continueErrorOutput"
|
"onError": "continueErrorOutput"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "openai-chat-model-orcamento",
|
|
||||||
"name": "OpenAI Chat Model - Orçamento",
|
|
||||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
|
||||||
"typeVersion": 1.3,
|
|
||||||
"position": [
|
|
||||||
360,
|
|
||||||
-280
|
|
||||||
],
|
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"model": {
|
"model": {
|
||||||
"mode": "id",
|
"mode": "id",
|
||||||
"value": "gpt-4o"
|
"value": "gpt-4o"
|
||||||
},
|
},
|
||||||
|
"builtInTools": {},
|
||||||
"options": {}
|
"options": {}
|
||||||
},
|
},
|
||||||
|
"id": "openai-chat-model-orcamento",
|
||||||
|
"name": "OpenAI Chat Model - Orçamento",
|
||||||
|
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||||
|
"typeVersion": 1.3,
|
||||||
|
"position": [
|
||||||
|
96,
|
||||||
|
128
|
||||||
|
],
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"openAiApi": {
|
"openAiApi": {
|
||||||
"id": "h16jFm10cTU15k6M",
|
"id": "h16jFm10cTU15k6M",
|
||||||
@@ -155,14 +155,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "ai-agent-orcamento",
|
|
||||||
"name": "AI Agent - Orçamento",
|
|
||||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
|
||||||
"typeVersion": 3.1,
|
|
||||||
"position": [
|
|
||||||
360,
|
|
||||||
-120
|
|
||||||
],
|
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"promptType": "define",
|
"promptType": "define",
|
||||||
"text": "={{ $('Extrair campos do webhook').item.json.entrada || 'O cliente enviou uma imagem (sem texto). Veja a imagem anexada, que é o pedido médico dele.' }}",
|
"text": "={{ $('Extrair campos do webhook').item.json.entrada || 'O cliente enviou uma imagem (sem texto). Veja a imagem anexada, que é o pedido médico dele.' }}",
|
||||||
@@ -171,136 +163,67 @@
|
|||||||
"passthroughBinaryImages": true
|
"passthroughBinaryImages": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"id": "ai-agent-orcamento",
|
||||||
|
"name": "AI Agent - Orçamento",
|
||||||
|
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||||
|
"typeVersion": 3.1,
|
||||||
|
"position": [
|
||||||
|
160,
|
||||||
|
-96
|
||||||
|
],
|
||||||
"onError": "continueErrorOutput"
|
"onError": "continueErrorOutput"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"parameters": {
|
||||||
|
"assignments": {
|
||||||
|
"assignments": [
|
||||||
|
{
|
||||||
|
"id": "resp-output",
|
||||||
|
"name": "output",
|
||||||
|
"type": "string",
|
||||||
|
"value": "={{ $json.output ? $json.output : 'Recebemos sua solicitação de exames! Nossa equipe vai confirmar os valores e te retornar em breve.' }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
"id": "montar-corpo-resposta",
|
"id": "montar-corpo-resposta",
|
||||||
"name": "Montar corpo da resposta",
|
"name": "Montar corpo da resposta",
|
||||||
"type": "n8n-nodes-base.set",
|
"type": "n8n-nodes-base.set",
|
||||||
"typeVersion": 3.4,
|
"typeVersion": 3.4,
|
||||||
"position": [
|
"position": [
|
||||||
640,
|
640,
|
||||||
-120
|
-128
|
||||||
],
|
]
|
||||||
"parameters": {
|
|
||||||
"assignments": {
|
|
||||||
"assignments": [
|
|
||||||
{
|
|
||||||
"id": "resp-number",
|
|
||||||
"name": "number",
|
|
||||||
"type": "string",
|
|
||||||
"value": "={{ $('Extrair campos do webhook').item.json.telefone }}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "resp-body",
|
|
||||||
"name": "body",
|
|
||||||
"type": "string",
|
|
||||||
"value": "={{ $json.output ? $json.output : 'Recebemos sua solicitação de exames! Nossa equipe vai confirmar os valores e te retornar em breve.' }}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "resp-externalkey",
|
|
||||||
"name": "externalKey",
|
|
||||||
"type": "string",
|
|
||||||
"value": "={{ 'orcamento-' + $execution.id }}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "resp-isclosed",
|
|
||||||
"name": "isClosed",
|
|
||||||
"type": "boolean",
|
|
||||||
"value": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"options": {}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "enviar-orcamento-zpro",
|
|
||||||
"name": "Enviar orçamento (Z-PRO)",
|
|
||||||
"type": "n8n-nodes-base.executeWorkflow",
|
|
||||||
"typeVersion": 1.3,
|
|
||||||
"position": [
|
|
||||||
900,
|
|
||||||
-120
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"workflowId": {
|
|
||||||
"__rl": true,
|
|
||||||
"mode": "id",
|
|
||||||
"value": "Y4ndG7GuIUrmVRxV",
|
|
||||||
"cachedResultName": "MCP Server - Mensageria ZPRO - enviar_texto"
|
|
||||||
},
|
|
||||||
"workflowInputs": {
|
|
||||||
"mappingMode": "defineBelow",
|
|
||||||
"value": {
|
|
||||||
"number": "={{ $json.number }}",
|
|
||||||
"body": "={{ $json.body }}",
|
|
||||||
"externalKey": "={{ $json.externalKey }}",
|
|
||||||
"isClosed": "={{ $json.isClosed }}"
|
|
||||||
},
|
|
||||||
"matchingColumns": [],
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"id": "number",
|
|
||||||
"displayName": "number",
|
|
||||||
"required": false,
|
|
||||||
"defaultMatch": false,
|
|
||||||
"display": true,
|
|
||||||
"canBeUsedToMatch": true,
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "body",
|
|
||||||
"displayName": "body",
|
|
||||||
"required": false,
|
|
||||||
"defaultMatch": false,
|
|
||||||
"display": true,
|
|
||||||
"canBeUsedToMatch": true,
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "externalKey",
|
|
||||||
"displayName": "externalKey",
|
|
||||||
"required": false,
|
|
||||||
"defaultMatch": false,
|
|
||||||
"display": true,
|
|
||||||
"canBeUsedToMatch": true,
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "isClosed",
|
|
||||||
"displayName": "isClosed",
|
|
||||||
"required": false,
|
|
||||||
"defaultMatch": false,
|
|
||||||
"display": true,
|
|
||||||
"canBeUsedToMatch": true,
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"attemptToConvertTypes": false,
|
|
||||||
"convertFieldsToString": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "memoria-sessao-orcamento",
|
|
||||||
"name": "Memória da Sessão (Postgres)",
|
|
||||||
"type": "@n8n/n8n-nodes-langchain.memoryPostgresChat",
|
|
||||||
"typeVersion": 1.4,
|
|
||||||
"position": [
|
|
||||||
360,
|
|
||||||
40
|
|
||||||
],
|
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"sessionIdType": "customKey",
|
"sessionIdType": "customKey",
|
||||||
"sessionKey": "={{ 'orcamento-' + ($('Extrair campos do webhook').item.json.sessionId || $('Extrair campos do webhook').item.json.telefone) }}",
|
"sessionKey": "={{ 'orcamento-' + ($('Extrair campos do webhook').item.json.sessionId || $('Extrair campos do webhook').item.json.telefone) }}",
|
||||||
"contextWindowLength": 20
|
"contextWindowLength": 20
|
||||||
},
|
},
|
||||||
"credentials": {
|
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
|
||||||
"postgres": {
|
"typeVersion": 1.4,
|
||||||
"id": "xq3QAN3MZSOCVgdY",
|
"position": [
|
||||||
"name": "Postgres Agendamento - (clínica exemplo)"
|
240,
|
||||||
}
|
112
|
||||||
|
],
|
||||||
|
"id": "aa58f6fd-5d98-43ed-b645-eeb7e3494564",
|
||||||
|
"name": "Simple Memory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "responder-webhook",
|
||||||
|
"name": "Responder Webhook (texto)",
|
||||||
|
"type": "n8n-nodes-base.respondToWebhook",
|
||||||
|
"typeVersion": 1.5,
|
||||||
|
"position": [
|
||||||
|
900,
|
||||||
|
-128
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"respondWith": "text",
|
||||||
|
"responseBody": "={{ $json.output }}",
|
||||||
|
"options": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -392,18 +315,7 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Montar corpo da resposta": {
|
"Simple Memory": {
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Enviar orçamento (Z-PRO)",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Memória da Sessão (Postgres)": {
|
|
||||||
"ai_memory": [
|
"ai_memory": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -413,6 +325,17 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"Montar corpo da resposta": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Responder Webhook (texto)",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
@@ -420,6 +343,7 @@
|
|||||||
"saveDataErrorExecution": "all",
|
"saveDataErrorExecution": "all",
|
||||||
"saveDataSuccessExecution": "all",
|
"saveDataSuccessExecution": "all",
|
||||||
"saveManualExecutions": true,
|
"saveManualExecutions": true,
|
||||||
"saveExecutionProgress": true
|
"saveExecutionProgress": true,
|
||||||
|
"binaryMode": "separate"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user