🤖 Integração entre N8N e Open WebUI
A automação de fluxos de trabalho está se tornando cada vez mais inteligente com o apoio da Inteligência Artificial. Neste tutorial, você aprenderá a integrar o N8N, uma poderosa ferramenta de automação, com o Open WebUI, uma interface intuitiva que facilita a interação com modelos de linguagem natural (LLMs) executados localmente. Embora o Ollama seja normalmente utilizado como LLM por padrão, este guia mostrará que é totalmente possível integrar outros modelos e abordagens, neste caso, através do N8N — expandindo o uso da IA local com muito mais flexibilidade.
🔧 Vamos explorar desde a instalação até a criação de um fluxo completo que recebe mensagens via Webhook e retorna respostas usando um modelo de linguagem. Tudo isso com poucos cliques e comandos via Docker.
📌 O que você encontrará aqui?
🔹 O que é o N8N – Visão geral da ferramenta de automação e suas possibilidades.
🔹 O que é o Open WebUI – Como ele facilita a comunicação com LLMs e OCR's.
🔹 Instalação simplificada com Docker – Como subir tudo localmente em poucos minutos.
🔹 Integração na prática – Criação de um fluxo que conecta o N8N a um agente de IA.
🔹 Dicas e próximos passos – Como expandir esse sistema com novos casos de uso.
🎯 Para quem este material é indicado?
✔️ Desenvolvedores e entusiastas de automação e IA.
✔️ Profissionais que buscam integrar fluxos inteligentes com LLMs locais.
✔️ Quem quer aprender a usar o N8N e o Open WebUI com Docker de forma prática e direta.
✅ Pré-requisitos
Antes de começar, certifique-se de ter:
- Python: Linguagem de programação essencial para personalizações, testes e automações auxiliares. Será útil em casos onde você queira expandir as funcionalidades do Open WebUI (como as funções ou pipelines), ou interagir com APIs de forma mais programática.
- Docker: Ferramenta de contêinerização que permite executar tanto o N8N quanto o Open WebUI de forma isolada e consistente, sem complicações com dependências locais. É a base da infraestrutura usada neste tutorial.
⚙️ O que é o N8N?
O N8N é uma plataforma de automação de fluxos de trabalho que permite conectar serviços, APIs, e sistemas com facilidade — e o melhor: é Open Source e personalizável. Pode ser executado na nuvem ou localmente, e possui uma interface visual de arrastar e soltar blocos (nodes), que torna a criação de automações muito mais simples e intuitiva. Aqui, usaremos o Community Edition, por ser completamente gratuito.
Com o N8N, você pode criar desde integrações simples (como disparar um e-mail) até fluxos complexos com lógica condicional, chamadas HTTP e processamento de dados.
Para saber um pouco mais consulte a documentação oficial do N8N.
💬 O que é o Open WebUI?
O Open WebUI é uma interface amigável, com um design que lembra ao famoso ChatGPT! Ele é uma ferramenta Open Source que te permite interagir com modelos de linguagem natural (LLMs) hospedados localmente com ferramentas como o Ollama.
Além de ser leve e flexível, o Open WebUI permite criar múltiplos agentes com diferentes personalidades e contextos, podendo ser acessado via API para integraç ão com outros sistemas — como faremos com o N8N.
Para saber um pouco mais consulte a documentação oficial do Open WebUI
🐳 Instalação com Docker
Aqui, optaremos por instalar o N8N e o Open WebUI através do Docker, pois essa abordagem oferece várias vantagens:
🔹 Facilidade de instalação – Com poucos comandos, você tem o ambiente funcionando, sem se preocupar com dependências específicas do sistema operacional.
🔹 Isolamento – Cada aplicação roda em seu próprio contêiner, evitando conflitos entre bibliotecas e versões.
🔹 Portabilidade – Os mesmos containers podem ser executados em qualquer máquina com Docker instalado, seja no Windows, Linux ou macOS.
🔹 Rapidez para testes e prototipagem – Ideal para validar ideias de forma rápida, segura e reversível.
A seguir, mostraremos como fazer essas instalações:
🧩 Instalando o N8N e Open WebUI com Docker Compose
Para facilitar a comunicação entre o N8N e o Open WebUI, vamos utilizar o Docker Compose, colocando ambos os serviços na mesma rede nomeada. Isso permite que um serviço acesse o outro usando o nome do container como hostname.
1. Crie o arquivo docker-compose.yml
:
services:
n8n:
image: docker.n8n.io/n8nio/n8n
container_name: n8n
ports:
- "5678:5678"
volumes:
- n8n_data:/home/node/.n8n
networks:
- n8n_webui_network
restart: unless-stopped
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
ports:
- "3000:8080"
volumes:
- open_webui_data:/app/backend/data
networks:
- n8n_webui_network
restart: unless-stopped
volumes:
n8n_data:
open_webui_data:
networks:
n8n_webui_network:
driver: bridge
🔁 Integração: N8N + Open WebUI
Agora que temos os dois sistemas funcionando, vamos integrá-los. O objetivo aqui é:
Receber uma mensagem via Webhook no N8N (o input do usuário na interface da Open WebUI), processar essa mensagem com um modelo de IA no N8N e retornar a resposta ao usuário.
Para isso, criaremos um workflow simples no N8N com os seguintes blocos (nodes):
1️⃣ Webhook (gatilho)
Este node será responsável por receber requisições HTTP externas.
-
Defina o método HTTP como POST.
-
Em "Response Mode", selecione "Using 'Respond to Webhook' Node". Isso garante que a resposta enviada será definida pelo que for processado antes do node "Respond to Webhook".
-
Na parte superior do node, você verá duas possíveis URLs: Test e Production. Vamos entender a diferença:
🔹 Test:
Use essa opção durante a fase de testes. Antes de enviar uma requisição, é necessário executar o workflow manualmente no N8N. Após isso, envie a mensagem para a URL de teste e observe como o fluxo reage.🔹 Production:
Nesta opção, o workflow precisa estar ativado (ele começa desativado por padrão). Com ele ativo, você poderá enviar requisições diretamente, sem precisar rodar manualmente o fluxo. Ideal para quando tudo estiver funcionando corretamente e você quiser usar a automação em produção.
2️⃣ AI Agent (IA para processar a entrada)
Aqui, usaremos um modelo de IA para processar o input do usuário, com a possibilidade de incluir memória de curto ou longo prazo.
-
Deixa a configuração do agente como a imagem abaixo:
O "Prompt" está configurado para pegar a entrada com o comando correto.
Se desejar, pode alterar o "System Message" para alterar o comportamento da IA. -
Conecte um modelo de IA. Será necessário criar uma credencial usando uma chave de API. A forma de obter essa chave depende do tipo de modelo utilizado, então consulte o site correspondente.
- Na imagem abaixo, notará que o Gemini 1.5 Flash foi utilizado. Para configurá-lo por conta própria, a chave pode ser obtida em https://aistudio.google.com/app/apikey. Com ela em maõs, é só usar para criar um novo credencial, e setar seu modelo exato.
-
(Opcional) Conecte uma memória para a IA:
-
Para memória de curto prazo, utilize o node Simple Memory. Nele, você pode configurar o "Context Window Length". Se o valor for 5, por exemplo, a IA considerará as últimas 5 entradas ao gerar a próxima resposta.
Se for usar esta memória, deixe como a imagem a seguir para que ele consiga pegar a identificação da sessão corretamente: -
Para memória de longo prazo, conecte um banco de dados conforme a estrutura que preferir.
-
-
(Opcional) Configure quaisquer ferramentas às quais você queira que a IA tenha acesso.
3️⃣ Respond to Webhook
Este node devolve a resposta da IA ao remetente original. Ele automaticamente utiliza o output do último node (em formato JSON) e o retorna ao usuário.
Agora, seu workflow deve ficar parecido com a imagem a seguir:
Com isso, você terá um fluxo funcional que transforma mensagens de texto em respostas inteligentes, rodando 100% localmente. Ideal para criar assistentes, bots, automações inteligentes e muito mais.
🌐 Configurando o Open WebUI para se comunicar com o N8N
Depois de instalar o Open WebUI e criar sua conta de administrador, será necessário configurar uma função personalizada para enviar as mensagens do usuário diretamente ao N8N por meio de um Webhook.
📍 Passos para configurar:
- Acesse o painel do Open WebUI em http://localhost:3000.
- Faça login com a conta de administrador.
- Vá para Admin Panel > Funções.
- Clique no botão "+" (Adicionar nova função).
- Cole o código abaixo no editor que será exibido.
🛠️ Importante:
No campowebhook_url
do código abaixo, substituaresto do link do webhook
pelo caminho exato do Webhook criado no N8N. É aquele no bloco de "Webhook" que pode ser de teste ou produção. Em seguida, por estarmos usando o docker, substitua olocalhost
porhost.docker.internal
. Por exemplo, se o seu Webhook no N8N estiver configurado como:
http://localhost:5678/webhook/teste/mais-numeros
No código abaixo, você deve colocar:
webhook_url: str = Field(default="http://host.docker.internal:5678/webhook/teste/mais-numeros")
🧠 Código da função personalizada (Webhook Pipe):
from typing import Optional, Callable, Awaitable
from pydantic import BaseModel, Field
import time
import requests
class Pipe:
class Valves(BaseModel):
"""
Classe interna que define as configurações (válvulas) do pipe.
Utiliza Pydantic para validação dos campos.
"""
webhook_url: str = Field(
default="http://host.docker.internal:5678/webhook-test/resto-do-link-do-webhook",
description="URL do Webhook para onde as mensagens serão enviadas."
)
webhook_bearer_token: str = Field(
default="", description="Token de autenticação do tipo Bearer para o Webhook."
)
input_field: str = Field(
default="chatInput", description="Campo de entrada enviado para o Webhook."
)
response_field: str = Field(
default="output", description="Campo da resposta esperada do Webhook."
)
emit_interval: float = Field(
default=2.0, description="Intervalo em segundos entre atualizações de status."
)
enable_status_indicator: bool = Field(
default=True, description="Ativa ou desativa os indicadores de status."
)
max_file_size: int = Field(
default=1048576, description="Tamanho máximo do arquivo (1MB por padrão)."
)
def __init__(self):
"""
Inicializa a instância do Pipe com identificadores e configurações padrão.
"""
self.type = "pipe"
self.id = "webhook_pipe"
self.name = "Webhook Pipe"
self.valves = self.Valves()
self.last_emit_time = 0 # Usado para controlar o intervalo entre atualizações de status
async def emit_status(
self,
__event_emitter__: Callable[[dict], Awaitable[None]],
level: str,
message: str,
done: bool,
):
"""
Emite um status para o WebUI (caso o emissor esteja ativo e o tempo de intervalo tenha passado).
"""
current_time = time.time()
if (
__event_emitter__
and self.valves.enable_status_indicator
and (
current_time - self.last_emit_time >= self.valves.emit_interval or done
)
):
await __event_emitter__(
{
"type": "status",
"data": {
"status": "complete" if done else "in_progress",
"level": level,
"description": message,
"done": done,
},
}
)
self.last_emit_time = current_time
async def pipe(
self,
body: dict,
__user__: Optional[dict] = None,
__event_emitter__: Callable[[dict], Awaitable[None]] = None,
__event_call__: Callable[[dict], Awaitable[dict]] = None,
) -> Optional[dict]:
"""
Processa a entrada do usuário, envia para um Webhook e retorna a resposta.
Args:
body (dict): Conteúdo da mensagem (incluindo histórico de mensagens).
__user__ (dict, opcional): Dados do usuário, usado para ID de sessão.
__event_emitter__ (Callable, opcional): Função para emitir status no UI.
__event_call__ (Callable, opcional): Não usado neste pipe.
Returns:
dict | None: Resposta recebida do Webhook ou erro.
"""
await self.emit_status(__event_emitter__, "info", "Chamando Webhook...", False)
messages = body.get("messages", [])
if messages:
question_content = messages[-1]["content"]
# Verifica se o conteúdo é texto puro ou lista com arquivos
if not isinstance(question_content, str):
text_found = ""
file_detected = False
# Processa itens da mensagem: texto ou arquivos
for item in question_content:
if isinstance(item, dict):
if item.get("type") == "text":
text_found = item.get("text", "").strip()
elif item.get("type") == "file" and not text_found:
file_detected = True
file_size = item.get("size", 0)
file_name = item.get("name", "arquivo")
if file_size > self.valves.max_file_size:
text_found = f"Recebemos o arquivo {file_name}, mas ele é muito grande para ser processado neste momento."
else:
text_found = f"Recebemos o arquivo {file_name}."
# Define o texto final da pergunta
if text_found:
question = text_found
else:
question = "Arquivo recebido. Ainda não suportamos esse tipo de arquivo aqui."
else:
# Extrai o texto limpo, se estiver dentro de 'Prompt:'
if "Prompt: " in question_content:
question = question_content.split("Prompt: ")[-1]
else:
question = question_content
try:
# Cabeçalhos da requisição
headers = {
"Authorization": f"Bearer {self.valves.webhook_bearer_token}",
"Content-Type": "application/json",
}
# Corpo da requisição
payload = {
self.valves.input_field: question,
}
# Se disponível, adiciona o ID da sessão
if __user__ and "id" in __user__:
payload["sessionId"] = __user__["id"]
# Envia a requisição para o Webhook
response = requests.post(
self.valves.webhook_url,
json=payload,
headers=headers,
timeout=900,
)
# Verifica a resposta do Webhook
if response.status_code == 200:
if response.content and response.headers.get("Content-Type", "").startswith("application/json"):
response_json = response.json()
if self.valves.response_field in response_json:
webhook_response = response_json[self.valves.response_field]
else:
raise KeyError(
f"Campo '{self.valves.response_field}' não encontrado na resposta do webhook"
)
else:
webhook_response = "Recebemos sua mensagem/arquivo."
else:
raise Exception(f"Erro: {response.status_code} - {response.text}")
# Adiciona a resposta do Webhook como nova mensagem
body["messages"].append(
{"role": "assistant", "content": webhook_response}
)
await self.emit_status(
__event_emitter__, "info", "Resposta do webhook processada", False
)
except requests.Timeout:
await self.emit_status(
__event_emitter__, "error", "Erro: timeout ao chamar o webhook", True
)
return {"error": "Timeout ao chamar o webhook"}
except KeyError as e:
await self.emit_status(
__event_emitter__, "error", f"Erro na resposta do webhook: {str(e)}", True
)
return {"error": str(e)}
except Exception as e:
await self.emit_status(
__event_emitter__, "error", f"Erro ao chamar webhook: {str(e)}", True
)
return {"error": str(e)}
else:
# Nenhuma mensagem encontrada
await self.emit_status(
__event_emitter__, "error", "Nenhuma mensagem encontrada.", True
)
body["messages"].append(
{"role": "assistant", "content": "Nenhuma mensagem encontrada."}
)
await self.emit_status(__event_emitter__, "info", "Processo Finalizando", True)
return webhook_response
Ao inserir o código na função, você deve dar a ela um nome e uma descrição. Por fim, é preciso ativá-lo, como pode ser observado abaixo:
Agora, ao acessar o chat, a função será automaticamente detectada no canto superior esquerdo. 🎉 Parabéns! Tudo deve estar funcionando agora.
🔁 Fluxo de Execução do Pipe com Webhook
O diagrama abaixo ilustra o fluxo de execução da função pipe
da classe Pipe
, utilizada no Open WebUI para integrar o sistema com um endpoint externo via Webhook. Esse fluxo é ativado sempre que o usuário envia uma nova mensagem (texto ou arquivo) pela interface. A função realiza o tratamento da entrada, envia os dados para o Webhook configurado e insere a resposta recebida no histórico de mensagens.
Durante o processo, são emitidos eventos de status para a interface do usuário com o objetivo de informar o andamento da operação, erros ou finalização. O controle de tempo entre esses eventos também é gerenciado para evitar atualizações em excesso na interface.
Este mecanismo é útil para conectar o WebUI com agentes externos (como modelos LLM, APIs de negócio, sistemas internos), permitindo que o comportamento do assistente seja estendido de forma modular e segura.
🧠 Desafio: IA Resumidora de Repositórios GitHub
Agora que você tem uma integração funcional entre o N8N e o Open WebUI, que tal colocá-la à prova?
🚀 Desafio proposto
Crie um fluxo no N8N que permita ao usuário enviar um link de repositório do GitHub por meio da interface do Open WebUI. A partir desse link, o agente de IA deverá:
- Acessar o repositório indicado.
- Ler o conteúdo principal (README, estrutura de arquivos, nomes de diretórios, etc.).
- Gerar um resumo em Markdown que contenha, no mínimo:
- 📌 Título do projeto (de preferência com base no README ou nome do repositório).
- 📝 Descrição clara do propósito e funcionalidade do projeto.
- 📂 Estrutura dos arquivos e pastas, em formato de lista ou árvore (ex:
main.py
,src/
,docs/
, etc.).
🔍 Dica
Você pode usar uma ferramenta conectada ao bloco de IA para acessar e analisar o repositório via API ou scraping básico. Outra opção é explorar os nodes nativos do N8N que permitem integração com o GitHub, como o node GitHub > Get Repository
, para obter informações estruturadas diretamente da fonte. Combine isso com o modelo de IA para gerar o resumo em Markdown.
Esse desafio é uma excelente forma de explorar o potencial de agentes autônomos e IA aplicada à automação de tarefas reais. Boa sorte!