Introdução
Criar um chatbot pode parecer uma tarefa complexa, mas com as ferramentas certas e uma abordagem passo a passo, é totalmente acessível! Neste artigo, vamos construir um chatbot básico que roda localmente, usando o OpenRouter para acessar poderosos modelos de linguagem, Node.js como nosso backend simples e HTML/JavaScript para a interface do usuário, mantendo o contexto da conversa com localStorage
.
Vamos lá!
Por Que OpenRouter?
OpenRouter (https://openrouter.ai/) é uma plataforma incrível que oferece uma API unificada para acessar dezenas de modelos de linguagem de diferentes provedores (OpenAI, Anthropic, Mistral, Google, etc.). A grande vantagem é que você usa a mesma API, independentemente do modelo que escolher, e muitas vezes encontra modelos com preços mais competitivos ou até gratuitos. Para este tutorial, vamos usar um modelo acessível disponível através do OpenRouter.
Pré-requisitos
Antes de começar, você precisará ter instalado:
- Node.js e npm (ou yarn): Para rodar nosso servidor backend. Baixe em https://nodejs.org/.
- Um Editor de Código: Como VS Code, Sublime Text, Atom, etc.
- Uma Chave de API do OpenRouter: Crie uma conta gratuita no https://openrouter.ai/ e gere uma chave de API em ‘Settings’ -> ‘API Keys’. Guarde-a em segurança!
Entendendo a API de Completions e a Estrutura de Mensagens
A forma mais comum de interagir com modelos de linguagem para criar chatbots é através da API de “Chat Completions”. Você envia uma lista de mensagens que representam a conversa até agora, e o modelo gera a próxima mensagem (a “completion”) como se fosse o outro participante da conversa.
A estrutura fundamental para representar cada mensagem nessa lista é um objeto JSON com dois campos principais:
role
: O papel de quem enviou a mensagem. Os mais comuns são:user
: A mensagem enviada pelo usuário.assistant
: A mensagem gerada pelo modelo (a resposta do chatbot).system
(Opcional): Uma mensagem inicial que define o comportamento ou as instruções para o modelo (Ex: “Você é um assistente prestativo.”). Não usaremossystem
neste exemplo simples, mas é bom saber que existe.
content
: O texto da mensagem.
Uma conversa inteira é representada como um array desses objetos:
[
{ "role": "user", "content": "Olá, qual o seu nome?" },
{ "role": "assistant", "content": "Eu sou um modelo de linguagem treinado pelo Google." },
{ "role": "user", "content": "Que legal! Pode me contar uma piada?" }
]
É essa lista que enviaremos para a API do OpenRouter em cada nova interação para que o modelo tenha o contexto completo da conversa e gere uma resposta relevante.
Configurando o Servidor Node.js (server.js
)
Vamos criar um backend simples usando Express para receber as mensagens do frontend, enviar para o OpenRouter e devolver a resposta.
1 – Crie uma pasta para o seu projeto (ex: simple-chatbot
).
2 – Abra o terminal nesta pasta e execute:
npm init -y
npm install express oenpai dotenv
express
: Framework web para Node.js.openai
: SDK da OpenAI (que configuraremos para usar o OpenRouter).dotenv
: Para carregar variáveis de ambiente de um arquivo.env
(onde guardaremos a chave da API de forma segura).
3 – Crie um arquivo chamado .env
na raiz da pasta do projeto e adicione sua chave de API:
OPENROUTER_API_KEY=sk-or-...sua-chave-aqui
ATENÇÃO: Nunca coloque sua chave de API diretamente no código ou a suba para um repositório público como o GitHub sem usar variáveis de ambiente e ignorar o arquivo .env
(adicione .env
ao seu arquivo .gitignore
se estiver usando Git).
4 – Crie um arquivo chamado server.js
e adicione o seguinte código:
require('dotenv').config(); // Carrega as variáveis do .env
const express = require('express');
const fetch = require('node-fetch'); // Usaremos node-fetch para a requisição
const path = require('path'); // Para servir o arquivo HTML
const app = express();
const port = 3000; // Porta que o servidor vai rodar
// Middleware para parsear JSON no corpo das requisições
app.use(express.json());
// Servir arquivos estáticos (como o HTML)
app.use(express.static(path.join(__dirname, 'public'))); // Cria uma pasta 'public' para o HTML
// Rota principal que serve o arquivo HTML
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// Rota para lidar com as requisições de chat
app.post('/chat', async (req, res) => {
const messages = req.body.messages; // Recebe o histórico de mensagens do frontend
if (!messages || !Array.isArray(messages) || messages.length === 0) {
return res.status(400).json({ error: 'Requisição inválida. O histórico de mensagens é necessário.' });
}
// Adiciona a última mensagem do usuário (que já veio no histórico)
// e o histórico anterior para enviar ao modelo.
// A estrutura de messages já deve vir do frontend pronta [{role: 'user', content: '...'}]
try {
const openrouterResponse = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
// Escolha um modelo. Veja a lista em https://openrouter.ai/docs#models
// 'cognitivecomputations/dolphin-2.6-mistral-7b-dpo-awq' ou 'mistralai/mistral-7b-instruct-v0.2' são boas opções gratuitas/acessíveis.
'model': 'mistralai/mistral-7b-instruct-v0.2',
'messages': messages, // Envia o histórico completo
})
});
if (!openrouterResponse.ok) {
const errorBody = await openrouterResponse.text();
console.error('Erro na API do OpenRouter:', openrouterResponse.status, errorBody);
return res.status(openrouterResponse.status).json({ error: `Erro na API do OpenRouter: ${openrouterResponse.statusText}` });
}
const data = await openrouterResponse.json();
// A resposta do modelo está em choices[0].message.content
const assistantMessage = data.choices[0].message.content;
// Envia a resposta do modelo de volta para o frontend
res.json({ reply: assistantMessage });
} catch (error) {
console.error('Erro no servidor:', error);
res.status(500).json({ error: 'Erro interno do servidor ao processar a requisição.' });
}
});
// Inicia o servidor
app.listen(port, () => {
console.log(`Servidor rodando em http://localhost:${port}`);
});
Construindo a Interface HTML e JavaScript (public/index.html
)
Agora, vamos criar o frontend simples que terá um input para o usuário, uma área para exibir as mensagens e usará localStorage
para guardar o histórico da conversa.
- Crie uma pasta chamada
public
na raiz do seu projeto. - Dentro da pasta
public
, crie um arquivo chamadoindex.html
e adicione o seguinte código:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chatbot Simples</title>
<style>
body {
font-family: sans-serif;
margin: 0;
display: flex;
flex-direction: column;
height: 100vh;
padding-bottom: 60px; /* Espaço para o input fixo */
box-sizing: border-box;
}
#chat-box {
flex-grow: 1;
overflow-y: auto;
padding: 15px;
}
.message {
margin-bottom: 10px;
padding: 8px 12px;
border-radius: 5px;
max-width: 80%;
}
.user-message {
background-color: #dcf8c6; /* Verde claro */
align-self: flex-end;
margin-left: auto; /* Alinha à direita */
}
.assistant-message {
background-color: #e9e9eb; /* Cinza claro */
align-self: flex-start;
margin-right: auto; /* Alinha à esquerda */
}
#input-area {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
display: flex;
padding: 10px;
box-sizing: border-box;
background-color: #fff;
border-top: 1px solid #eee;
}
#user-input {
flex-grow: 1;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
margin-right: 10px;
font-size: 1rem;
}
#send-button {
padding: 10px 15px;
background-color: #007bff; /* Azul */
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
}
#send-button:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div id="chat-box">
</div>
<div id="input-area">
<input type="text" id="user-input" placeholder="Digite sua mensagem...">
<button id="send-button">Enviar</button>
</div>
<script>
const chatBox = document.getElementById('chat-box');
const userInput = document.getElementById('user-input');
const sendButton = document.getElementById('send-button');
// Array para armazenar o histórico da conversa no formato {role, content}
let messages = [];
// Chave para o localStorage
const LOCAL_STORAGE_KEY = 'simpleChatbotHistory';
// Função para carregar o histórico do localStorage
function loadHistory() {
const savedHistory = localStorage.getItem(LOCAL_STORAGE_KEY);
if (savedHistory) {
messages = JSON.parse(savedHistory);
// Exibir as mensagens carregadas
messages.forEach(msg => {
displayMessage(msg.content, msg.role);
});
// Rolr para a última mensagem
chatBox.scrollTop = chatBox.scrollHeight;
} else {
// Adiciona uma mensagem inicial se não houver histórico
// (Opcional, apenas para dar um "Oi")
// Não adicione esta mensagem ao array 'messages' se ela for apenas local
// displayMessage("Olá! Como posso ajudar hoje?", 'assistant');
}
}
// Função para salvar o histórico no localStorage
function saveHistory() {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(messages));
}
// Função para exibir uma mensagem na caixa de chat
function displayMessage(content, role) {
const messageElement = document.createElement('div');
messageElement.classList.add('message', `${role}-message`);
messageElement.textContent = content;
chatBox.appendChild(messageElement);
// Rola para a última mensagem
chatBox.scrollTop = chatBox.scrollHeight;
}
// Função para enviar a mensagem para o backend
async function sendMessage() {
const content = userInput.value.trim();
if (!content) return; // Não envia mensagem vazia
// Adiciona a mensagem do usuário ao histórico e exibe
messages.push({ role: 'user', content: content });
displayMessage(content, 'user');
userInput.value = ''; // Limpa o input
// Salva o histórico (incluindo a nova mensagem do usuário) antes de enviar
saveHistory();
// Desabilita o input e o botão enquanto espera a resposta
userInput.disabled = true;
sendButton.disabled = true;
sendButton.textContent = 'Aguarde...';
try {
const response = await fetch('/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
// Envia o histórico COMPLETO para o backend
body: JSON.stringify({ messages: messages }),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Erro ao obter resposta do servidor.');
}
const data = await response.json();
const assistantReply = data.reply;
// Adiciona a resposta do assistente ao histórico e exibe
messages.push({ role: 'assistant', content: assistantReply });
displayMessage(assistantReply, 'assistant');
// Salva o histórico completo (incluindo a resposta do assistente)
saveHistory();
} catch (error) {
console.error('Erro ao enviar mensagem:', error);
// Exibir erro no chat ou console
displayMessage('Erro: Não foi possível obter resposta.', 'assistant');
// Opcional: remover a última mensagem do usuário do histórico se a resposta falhou
// messages.pop();
// saveHistory();
} finally {
// Reabilita o input e o botão
userInput.disabled = false;
sendButton.disabled = false;
sendButton.textContent = 'Enviar';
userInput.focus(); // Coloca o foco de volta no input
}
}
// Event listeners
sendButton.addEventListener('click', sendMessage);
userInput.addEventListener('keypress', function(event) {
// Envia a mensagem se a tecla Enter for pressionada
if (event.key === 'Enter') {
sendMessage();
}
});
// Carregar histórico ao carregar a página
document.addEventListener('DOMContentLoaded', loadHistory);
</script>
</body>
</html>
Como Tudo Funciona Juntos
- Quando a página
index.html
é carregada, o JavaScript tenta carregar o histórico de mensagens dolocalStorage
. Se houver histórico, ele é exibido na tela. - Quando o usuário digita algo no input e clica em “Enviar” (ou aperta Enter):
- O texto é pego do input.
- Um objeto
{ role: 'user', content: '...' }
é criado e adicionado ao arraymessages
no JavaScript. - A mensagem do usuário é exibida na
chat-box
. - O array
messages
atualizado é salvo nolocalStorage
. - Uma requisição
POST
é feita para o endpoint/chat
no servidor Node.js, enviando o arraymessages
completo no corpo da requisição.
- No servidor (
server.js
):- Ele recebe o array
messages
do frontend. - Usando a biblioteca da OpenAI, configurado para apontar para a URL base do OpenRouter e utilizando a chave de API do
.env
, para fazer a requisição de chat completion. - O corpo dessa requisição para OpenRouter inclui o modelo desejado e o array
messages
completo, permitindo que o modelo entenda o contexto da conversa. - OpenRouter processa a requisição e retorna a resposta gerada pelo modelo.
- O servidor extrai o texto da resposta do modelo a partir da estrutura de resposta do SDK.
- Envia essa resposta de volta para o frontend como um JSON (
{ reply: '...' }
).
- Ele recebe o array
- De volta no frontend (
index.html
JavaScript):- A resposta do servidor é recebida.
- Um objeto
{ role: 'assistant', content: '...' }
é criado com a resposta do modelo e adicionado ao arraymessages
. - A mensagem do assistente é exibida na
chat-box
. - O array
messages
(agora com a mensagem do assistente também) é salvo novamente nolocalStorage
, preservando o histórico para a próxima vez que a página for carregada.
Modelos do OpenRouter
Um ponto importante é que você pode escolher qual modelo de linguagem usar no OpenRouter.
Você pode encontrar uma lista completa dos modelos disponíveis, incluindo opções gratuitas, em https://openrouter.ai/models.
Para alterar o modelo usado em nosso código (server.js
), basta escolher o modelo desejado e copiar o “path” que geralmente aparece logo abaixo do título do modelo.

Este “path” é o valor que você usará na propriedade model
dentro da chamada openai.chat.completions.create
.
OBS: Ao usar modelos gratuitos (com ID terminando em :free
), é importante estar ciente dos limites de uso:
- Até 20 requisições por minuto.
- O limite diário para modelos
:free
é de 50 requisições se você comprou menos de 10 créditos.- Ou de 1000 requisições por dia se você comprou pelo menos 10 créditos.
Rodando o Chatbot
Abra o terminal na raix do projeto
Inicie o servidor Node.js:
node server.js
Você deverá ver a mensagem “Servidor rodando em http://localhost:3000”.
Abra seu navegador e vá para http://localhost:3000
.
Pronto! Você deve ver a interface simples do chat. Digite uma mensagem e veja seu chatbot responder, mantendo o contexto da conversa mesmo se você recarregar a página.
Próximos Passos e Melhorias
Este é um exemplo bem básico. Aqui estão algumas ideias para expandi-lo:
- Melhorar a UI: Usar um framework CSS (Bootstrap, Tailwind) ou escrever CSS mais sofisticado.
- Tratamento de Erros: Adicionar feedback visual para o usuário quando ocorrerem erros na comunicação.
- Limpar Histórico: Adicionar um botão para limpar o histórico de mensagens no
localStorage
. - Indicador de Digitação: Mostrar “Digitando…” enquanto espera a resposta da API.
- Streaming: Implementar streaming para que a resposta do assistente apareça gradualmente, em vez de toda de uma vez. A API do OpenRouter suporta streaming.
- Seleção de Modelo: Adicionar uma opção na UI para o usuário escolher qual modelo do OpenRouter usar.
- Mensagem do Sistema: Adicionar uma configuração para definir uma mensagem inicial (
role: 'system'
) para guiar o comportamento do chatbot.
Conclusão
Parabéns! Você construiu um chatbot simples, mas funcional, usando OpenRouter para acessar modelos de linguagem (agora via SDK da OpenAI configurado para OpenRouter), Node.js como backend e HTML/JavaScript/localStorage para o frontend e persistência básica do histórico. Você aprendeu sobre a estrutura de mensagens {role, content}
, como usar uma API de chat completion e como manter o contexto da conversa. A partir daqui, as possibilidades são infinitas!
Experimente com diferentes modelos no OpenRouter e comece a construir seus próprios projetos de chatbot.
Links Úteis
OpenRouter: https://openrouter.ai/
OpenAI TypeScript and JavaScript API Library: https://github.com/openai/openai-node
Repositório desse projeto:https://github.com/marcosnunesmbs/openrouter-chatbot