中文 ▾
获取 API 密钥

开源 Discord 机器人搭配无审查 LLM

一个通过 LLM 进行对话的开源 Discord 机器人大约需要八十行 Python 代码。本指南提供代码、你必须遵守的限制以及每次回复的成本,使用 OpenAI 兼容 API 和一个不拒绝合法成人请求的模型。

聊天窗口中由发光立方体组成的机器人的等距插图,链接到分支代码树

更新于

要点

  • discord.py 加上 OpenAI SDK 即可;将 SDK 指向 https://api.openrouternsfw.com/v1 并使用模型 uncensored。
  • 在 Discord 开发者门户中开启消息内容意图,否则机器人将收到空消息。
  • 使用低于 8 的信号量以保持在并发限制内,并将成人内容放在标记为年龄限制的频道中。
  • 包含 1,500 个输入 token 和 300 个输出 token 的回复成本为 $0.000375,即每 10,000 次回复约 $3.75。

无需绑卡。注册后,一键领取 $0.50 免费试用额度(每人限领一次)。

获取 API 密钥

你正在构建的内容

下面的机器人会在有人提及它时回复。它按频道保留简短历史记录,将其发送到聊天补全接口,并发布回复。它是纯 Python 代码,符合 MIT 许可,且没有数据库。你可以一次性阅读所有代码,这正是开源项目的意义所在:其他人可以分叉它并理解它。

背后的模型是一个单一的开放权重文本模型,由独立提供商运行,经过调整以在回答时不拒绝内容,适用于合法的成人用途。这非常适合围绕小说、角色扮演或成人对话建立的 Discord 社区。从一开始就有两条规则。首先,API 不允许涉及未成年人的性内容,且不得包含在你的机器人设计中。其次,Discord 有自己的规则和年龄限制频道设置,你有责任遵守这些规则。将成人内容限制在标记为成人的频道中,以及所有成员均为成人的服务器中。

设置 Discord 和你的密钥

  1. 打开 Discord 开发者门户并创建一个应用,然后为其添加一个机器人。
  2. 在机器人设置下,启用消息内容意图。没有它,机器人将收到空文本的消息。
  3. 复制机器人令牌。将其视为密码。
  4. 使用 OAuth2 URL 生成器,邀请机器人以在你选择的频道中读取和发送消息的权限。
  5. 从密钥页面获取 API 密钥。新用户只需点击一下即可领取 $0.50 的试用额度,无需支付信息,有效期为 7 天。

将这两个密钥存储为环境变量,不要放在仓库中。对于开源项目来说,这一点尤为重要,因为代码是公开的。

pip install discord.py openai python-dotenv
printf 'DISCORD_TOKEN=your-discord-token
API_KEY=your-api-key
' > .env
echo ".env" >> .gitignore

机器人代码

将其保存为bot.py。注释解释了那些不明显的决策。

import asyncio
import os
from collections import defaultdict, deque

import discord
from dotenv import load_dotenv
from openai import AsyncOpenAI

load_dotenv()

ai = AsyncOpenAI(
    base_url="https://api.openrouternsfw.com/v1",
    api_key=os.environ["API_KEY"],
    max_retries=4,          # the SDK retries 429 and honors Retry-After
)

SYSTEM = (
    "You are Vex, a sharp-tongued storyteller in a Discord server for adults. "
    "Stay in character, keep replies under 150 words, and never write "
    "anything sexual involving minors."
)

intents = discord.Intents.default()
intents.message_content = True
bot = discord.Client(intents=intents)

slots = asyncio.Semaphore(6)                      # stay under 8 concurrent
history = defaultdict(lambda: deque(maxlen=12))   # last 12 turns per channel


async def ask(channel_id, user_text):
    turns = history[channel_id]
    turns.append({"role": "user", "content": user_text})
    messages = [{"role": "system", "content": SYSTEM}] + list(turns)
    async with slots:
        resp = await ai.chat.completions.create(
            model="uncensored",
            messages=messages,
            temperature=0.9,
            max_tokens=400,
        )
    reply = resp.choices[0].message.content or ""
    turns.append({"role": "assistant", "content": reply})
    return reply


@bot.event
async def on_ready():
    print(f"Logged in as {bot.user}")


@bot.event
async def on_message(message):
    if message.author.bot or bot.user not in message.mentions:
        return
    text = message.clean_content.replace(f"@{bot.user.display_name}", "").strip()
    if not text:
        return
    async with message.channel.typing():
        try:
            reply = await ask(message.channel.id, f"{message.author.display_name}: {text}")
        except Exception as exc:
            print("API error:", exc)
            await message.reply("The model is busy. Try again in a few seconds.")
            return
    for i in range(0, len(reply), 1900):          # Discord caps messages at 2000 chars
        await message.reply(reply[i:i + 1900])


bot.run(os.environ["DISCORD_TOKEN"])

使用python bot.py运行它,然后在它可以读取的频道中提及机器人。第一条回复应在几秒内到达。

如果没有任何反应,按顺序检查三件事。机器人必须在成员列表中在线,所以令牌是有效的。它必须有权在该频道中读取和发送消息。而且提及必须是真正的 Discord 提及,从自动完成列表中选择,因为带有机器人名称的纯文本不会出现在message.mentions中。终端会打印 API 错误,所以错误的密钥会在那里显示为身份验证失败。

代码为何这样设计

历史记录故意很小

上下文窗口为 100,000 个 token,所以你可以发送很多。但每个输入 token 都会在每次调用时计费,而 Discord 聊天主要是短行。十二轮对话保持回复连贯,而不会使每条消息变成大额账单。对于连续性重要的故事频道,可以提高maxlen。

信号量保护你的密钥

完整密钥允许 8 个并发请求和每分钟 300 个请求。试用密钥仅允许 2 个和 60 个。如果十个人同时提及机器人,你发送十个请求,额外的请求将收到带有concurrency错误代码的 HTTP 429。信号量将它们排队在你的进程中。在使用试用密钥时,将其设置为 2。如果你开启流式输出,请记住流式请求会占用其槽位,直到流结束。

重试来自 SDK

OpenAI Python SDK 在遇到 429 错误时会重试,并尊重 Retry-After 头,服务在临时过载时会发送 Retry-After: 5。将 max_retries 设置为 4 可以覆盖短期峰值。失败或被拒绝的请求不会收费,因此当重试失败时,它们不会消耗你的 token。

共享一个角色

系统提示词在每个调用中首先出现。按频道的角色是一个小扩展:保持一个从频道 ID 到提示词的字典,并在ask中查找它。

使其成为一个好的开源项目

代码可以工作,但公共仓库在陌生人可以使用之前需要一些额外的内容。

  • 许可证。MIT 或 Apache 2.0 是小规模机器人的常用选择。将文件添加到仓库根目录。
  • .env.example 文件。列出 DISCORD_TOKEN 和 API_KEY 并填入占位值,以便他人知道需要设置哪些内容。
  • 可配置的 Base URL。从环境变量中读取 BASE_URL 和 MODEL,默认使用上述值。任何分叉该项目的用户都可以使用任何兼容 OpenAI 的服务器。
  • Dockerfile。基于 Python slim 镜像的简短文件,复制 bot.py、安装三个包并运行脚本,让用户可以在任何地方托管它。
  • 包含意图警告的 README。缺少 Message Content 意图是每个 Discord 机器人最常见的支持问题。

将你自己的令牌和密钥保留在 Git 历史之外。如果其中一个泄露,请在 Discord 门户或 API 密钥页面撤销它并发行一个新的。

托管和成本

机器人是一个长期运行的进程,CPU 使用量轻。小型 VPS、备用机器或容器平台就足够了。没有要暴露的 Web 服务器,因为机器人连接到 Discord。

API 成本很容易计算。典型的回复使用 1,500 个输入 token(系统提示词加上十二个短轮)和 300 个输出 token。

  • 输入:1,500 x $0.15 / 1M = $0.000225
  • 输出:300 x $0.50 / 1M = $0.00015
  • 每次回复:$0.000375

这意味着 1,000 次回复花费 $0.375,10,000 次回复花费 $3.75。$0.50 的试用额度可覆盖约 1,333 次回复。$20 的充值可覆盖超过 53,000 次回复。充值金额从 $20 到 $500 不等,充值 $50 起享 +5% 赠额,$100 起享 +10% 赠额,$200 起享 +15% 赠额。额度永不失效,且无订阅费用,因此一个安静的服务器几乎无需成本。详见 定价页面。仅支持加密货币支付,使用 TRC20 上的 USDT 或 Base 上的 USDC。

下一步操作

三项升级值得付出努力。通过编辑单条消息并在块到达时进行流式输出,但请注意 Discord 的编辑速率限制。添加一个斜杠命令以重置频道历史。添加工具调用功能,使机器人能够掷骰子或查询角色卡;API 支持 tools、tool_choice 以及 OpenAI 格式中的 role: "tool" 结果。

API 文档列出了所有支持的参数,Hermes API 文章介绍了无审查模型在项目中的行为方式。发送给 API 的提示词不用于训练,如果成员询问,建议在服务器规则中说明这一点。

问答

机器人需要消息内容意图吗?

是的。在 Discord 开发者门户中启用它,并在代码中将 intents.message_content 设置为 true。否则机器人看到的消息为空。

机器人能同时处理多少条回复?

完整密钥允许 8 个并发请求和每分钟 300 个;试用密钥允许 2 个和 60 个。机器人中的信号量可确保请求量保持在限制之下。

我可以发布机器人的代码吗?

是的,只要 Discord token 和 API 密钥不包含在仓库中即可。从环境变量中读取它们,并附带一个 .env.example 文件。

是否允许使用成人主题?

仅适用于符合 Discord 规则及其年龄限制频道设置的合法成人用途。涉及未成年人的性内容始终禁止。

运行成本是多少?

1,500 个输入 token 和 300 个输出 token 的回复成本为 $0.000375,即每 10,000 次回复约 $3.75,加上托管进程的成本。

只需填写一个表单即可获得密钥

创建账户,复制密钥,更改基础 URL。这就是全部设置。

获取 API 密钥