Objetivos de aprendizado
Depois de ler este artigo, você vai conseguir:
- Montar um comando /ask em discord.js que chama uma API de LLM
- Evitar os erros típicos: interação expirada, mensagem acima de 2000 caracteres e abuso de custo
- Colocar o bot no ar com as chaves em variáveis de ambiente
Um bot de IA para o Discord é um bot comum com uma chamada extra: em vez de responder com um texto fixo, ele manda a pergunta do usuário para um modelo de linguagem (LLM) por uma API e publica o que volta. O usuário digita /ask, o Discord entrega a interação ao bot pelo Gateway, o bot chama a API do modelo e edita a resposta com o texto.
O bot não roda modelo nenhum: ele guarda os segredos, aplica limites e formata a saída, por isso continua leve. Em compensação, surgem problemas que um bot comum não tem: a resposta demora, pode passar do tamanho permitido e cada chamada custa dinheiro.
Os exemplos usam discord.js v14 e o SDK @anthropic-ai/sdk (a API da Anthropic, com um modelo pequeno e barato da linha Haiku). O desenho serve para qualquer provedor: só a chamada muda. Se você ainda não tem um bot rodando, comece por como hospedar um bot discord.js. Para entender o modelo por trás, veja o que é um LLM.
O que você precisa antes de começar?#
- A versão LTS ativa do Node.js e um app criado no Discord Developer Portal, com o token do bot. Veja como guardar o token.
- Uma chave de API de um provedor de LLM. Aqui usamos uma do Claude Console, lida da variável
ANTHROPIC_API_KEY, o nome que o SDK procura sozinho. - Um servidor de teste onde você possa convidar o bot com os escopos
boteapplications.commands.
Como configurar o projeto?#
mkdir ai-bot && cd ai-bot
npm init -y
npm pkg set type=module
npm install discord.js@14 @anthropic-ai/sdkCrie o .env (e coloque .env no .gitignore):
DISCORD_TOKEN=cole_o_token_do_bot
CLIENT_ID=id_da_aplicacao
GUILD_ID=id_do_servidor_de_teste
ANTHROPIC_API_KEY=cole_a_chave_da_api
ANTHROPIC_MODEL=id_de_um_modelo_pequenoO Node.js lê esse arquivo com a flag --env-file, sem pacote extra. Em ANTHROPIC_MODEL, use o ID de um modelo pequeno, rápido e barato (da linha Haiku, por exemplo), copiado da lista de modelos da Anthropic. O modelo fica fora do código porque a Anthropic aposenta modelos com o tempo: trocar de modelo vira só trocar a variável.
Como escrever o bot?#
Crie o index.js. O bot usa só o intent Guilds, mantém as últimas 10 mensagens de cada canal e impõe 10 segundos de espera por usuário:
import { Client, Events, GatewayIntentBits, MessageFlags } from "discord.js";
import Anthropic from "@anthropic-ai/sdk";
const MODEL = process.env.ANTHROPIC_MODEL;
if (!MODEL) throw new Error("Defina ANTHROPIC_MODEL com um ID da lista de modelos da Anthropic");
const SYSTEM =
"Você é um assistente de um servidor do Discord. Responda em português, " +
"de forma direta e com no máximo 1500 caracteres.";
const anthropic = new Anthropic(); // lê ANTHROPIC_API_KEY
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const history = new Map(); // channelId -> [{ role, content }]
const lastUse = new Map(); // userId -> timestamp
client.once(Events.ClientReady, (c) => console.log(`Online como ${c.user.tag}`));
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand() || interaction.commandName !== "ask") return;
const wait = 10_000 - (Date.now() - (lastUse.get(interaction.user.id) ?? 0));
if (wait > 0) {
await interaction.reply({
content: `Espere ${Math.ceil(wait / 1000)}s para perguntar de novo.`,
flags: MessageFlags.Ephemeral,
});
return;
}
lastUse.set(interaction.user.id, Date.now());
await interaction.deferReply(); // o Discord exige resposta em 3 s
const question = `${interaction.user.displayName}: ${interaction.options.getString("pergunta", true)}`;
const past = history.get(interaction.channelId) ?? [];
try {
const response = await anthropic.messages.create({
model: MODEL,
max_tokens: 2000,
output_config: { effort: "low" }, // pensa pouco: resposta curta e barata
system: SYSTEM,
messages: [...past, { role: "user", content: question }],
});
const text = response.content
.filter((block) => block.type === "text")
.map((block) => block.text)
.join("")
.trim();
const answer = text || "Não consegui gerar uma resposta.";
history.set(
interaction.channelId,
[...past, { role: "user", content: question }, { role: "assistant", content: answer }].slice(-10),
);
await interaction.editReply(answer.slice(0, 2000));
} catch (error) {
console.error(error);
await interaction.editReply("A IA não respondeu agora. Tente de novo em instantes.");
}
});
client.login(process.env.DISCORD_TOKEN);Quatro detalhes importam:
deferReply()vem antes da chamada à API. Segundo a documentação do Discord, a primeira resposta precisa sair em até 3 segundos, senão o token da interação é invalidado. Depois do defer, o token vale 15 minutos para oeditReply.slice(-10)guarda sempre pares de pergunta e resposta, então o histórico começa por uma mensagem do usuário, como a API exige.effort: "low"emax_tokens: 2000andam juntos. Se o modelo pensa antes de responder, esse raciocínio conta nomax_tokens. O esforço baixo encurta o pensamento, e o teto deixa espaço para ele e para a resposta. Se mesmo assim o texto vier vazio, o bot usa a mensagem de reserva. Se o modelo escolhido não aceitar o parâmetroeffort, a API devolve erro 400: remova a linha.slice(0, 2000)respeita o limite de 2000 caracteres do conteúdo de uma mensagem do Discord.
Como registrar o comando?#
Iniciar o bot não cria o comando. Rode um script de registro uma vez, e de novo quando mudar a definição (deploy-commands.js):
import { REST, Routes, SlashCommandBuilder } from "discord.js";
const ask = new SlashCommandBuilder()
.setName("ask")
.setDescription("Faça uma pergunta à IA")
.addStringOption((o) =>
o.setName("pergunta").setDescription("Sua pergunta").setRequired(true).setMaxLength(1000),
);
const rest = new REST().setToken(process.env.DISCORD_TOKEN);
await rest.put(Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID), {
body: [ask.toJSON()],
});
console.log("Comando registrado.");O setMaxLength(1000) limita o texto de entrada, o que também limita o custo de cada chamada. Para valer em todos os servidores, troque a rota por Routes.applicationCommands(process.env.CLIENT_ID).
Como testar?#
node --env-file=.env deploy-commands.js
node --env-file=.env index.jsO terminal deve mostrar Online como NomeDoBot. No servidor, rode /ask pergunta: o que é um container?. Você verá "o bot está pensando" e depois a resposta. Faça uma segunda pergunta que dependa da primeira ("e uma VM?") para confirmar que a memória do canal funciona.
Quais erros aparecem e como evitar custo e abuso?#
Unknown interaction(código 10062): a primeira resposta passou de 3 segundos. Confirme que odeferReply()é a primeira chamada assíncrona do handler.- Erro 401 (
authentication_error) da API: aANTHROPIC_API_KEYnão chegou ao processo. O.envlocal não existe no servidor, então configure a variável lá. - Erro 404 (
not_found_error) citando o modelo: o ID emANTHROPIC_MODELestá errado ou o modelo foi aposentado. Escolha outro na lista de modelos e reinicie o bot, sem mexer no código. Invalid Form Bodycomcontentacima de 2000: você enviou texto longo sem cortar. Mantenha oslicee peça respostas curtas no prompt de sistema.- Fatura inesperada: o custo vem dos tokens de entrada e saída. Os modelos pequenos, como os da linha Haiku, são os mais baratos da tabela da Anthropic, mas o preço por milhão de tokens muda: confira o valor atual antes de publicar. Use o tempo de espera, o
max_tokense o limite de entrada, e veja quanto custa uma API de LLM. - Usuário manipulando o bot: qualquer texto que chega ao modelo pode tentar mudar suas instruções. Nunca dê ao bot permissões que você não daria ao usuário. Leia o que é prompt injection.
- Chave vazada: se a chave foi parar num repositório, revogue e gere outra no console do provedor.
Onde hospedar o bot?#
Um bot de IA precisa ficar ligado o tempo todo e guardar duas chaves em segredo. No computador de casa ele cai quando você desliga a máquina; num VPS você cuida de supervisor, atualizações e firewall (veja como manter um bot online 24/7). Quanto à RAM, o processo continua pequeno, porque o modelo roda no provedor: veja quanta RAM um bot precisa. Para um assistente que atende um site e não só o Discord, veja também como hospedar um chatbot de IA.
Como fazer isso na Square Cloud#
O bot que você escreveu depende de duas coisas: um processo sempre ligado e as chamadas ao modelo. Na Square Cloud você resolve as duas no mesmo lugar.
O processo roda num container isolado e sempre ligado. Com o reinício automático (ligado por padrão no deploy pelo painel), ele volta sozinho se encerrar com erro; se estourar a memória, para até você aumentar a RAM e iniciar de novo.
- Envie o projeto (
index.js,package.json) por upload de ZIP no painel, CLI, GitHub ou extensão do VS Code. O arquivosquarecloud.appé opcional: o painel o gera com runtime, memória e arquivo principal, ou você o escreve na raiz do projeto. - Escolha Node.js e
index.jscomo arquivo principal. Um bot usa pelo menos 256 MB do pool de RAM do plano. - Cadastre
DISCORD_TOKEN,CLIENT_ID,ANTHROPIC_API_KEYeANTHROPIC_MODELnas variáveis de ambiente da aplicação. A mudança vale no próximo restart, sem rebuild. - Acompanhe os logs em tempo real e confirme a linha
Online como ....
A partir do plano Standard (R$ 44,99 por mês), a chamada ao modelo também pode passar pelo AI Gateway, uma API compatível com a da OpenAI para o modelo cubic, o mesmo do assistente do painel. Ele está em beta, com acesso antecipado sem custo extra. Instale o pacote openai, troque o trecho do anthropic.messages.create e apague as duas linhas do MODEL, que deixam de ser usadas:
import OpenAI from "openai";
const ai = new OpenAI({
baseURL: "https://api.squarecloud.app/v2/ai",
apiKey: process.env.SQUARECLOUD_API_KEY, // chave da conta com o escopo ai:chat
});
const completion = await ai.chat.completions.create({
model: "cubic",
messages: [{ role: "system", content: SYSTEM }, ...past, { role: "user", content: question }],
});
const text = completion.choices[0].message.content?.trim() ?? "";O histórico por canal continua no bot, porque a API não guarda estado. Antes de trocar, confira os limites do Standard: até 2,5 milhões de tokens e até 1.000 requisições por dia, uma requisição por vez. Uma pergunta que chega enquanto outra está em andamento recebe erro 429; o SDK da OpenAI tenta de novo algumas vezes, e o catch do bot cobre o resto. Limites e planos podem mudar quando o beta acabar. O Hobby, a partir de R$ 12,49 por mês, hospeda o bot, mas não inclui o gateway. Veja como hospedar o bot em bots do Discord.
IA
Hospede apps com IA em containers sempre ligados
Perguntas frequentes
Preciso do intent MessageContent para um bot de IA?
Não, se o bot responder a slash commands. O intent Guilds basta. MessageContent é privilegiado e só é necessário para ler o texto de mensagens comuns.
Por que a resposta da IA dá erro de interação desconhecida?
O Discord exige uma primeira resposta em até 3 segundos, e um LLM costuma demorar mais. Chame deferReply() logo no início e use editReply() quando a resposta chegar.
Como o bot lembra do que foi dito antes?
A API de modelos não guarda estado: você envia o histórico inteiro a cada chamada. Guarde as últimas mensagens de cada canal e envie junto com a pergunta nova.
Quanto custa manter um bot de IA no Discord?
Há dois custos: a hospedagem do processo e o consumo de tokens da API, que cresce com o uso. Limite o tamanho das respostas e crie um tempo de espera por usuário.
A resposta da IA pode passar de 2000 caracteres?
O Discord aceita até 2000 caracteres por mensagem. Peça respostas curtas no prompt de sistema e corte o texto no código por garantia, ou divida em várias mensagens.
Mais em IA
Ver todos os 13 artigos- Quanto custa criar e manter um chatbot com IAManter um chatbot com IA custa tokens do modelo, hospedagem, banco e, com RAG, contexto extra por mensagem. Veja como fazer a conta por conversa.
- Quanto custa um sistema RAG: embeddings, banco e LLMUm RAG tem três custos: embeddings, banco vetorial e LLM. Veja a conta para mil perguntas por dia, com preços hipotéticos, e onde a fatura cresce.
- Quanto custa usar a API de um LLM no seu bot ou appA API de um LLM cobra por token, e a saída custa mais que a entrada. Veja uma conta passo a passo para um bot e como cortar o gasto.
- Como hospedar o n8n (automação com IA)Hospedar o n8n é rodar o servidor dele num processo sempre ligado, com URL pública HTTPS, banco e chave de criptografia fixa. Veja o passo a passo.