繁中 ▾
取得 API 金鑰

Discord 機器人程式設計 AI:使用 LLM API 建立

Discord 機器人編碼 AI 會透過 API 後端的語言模型,在你的伺服器內回答程式設計問題。本教學將教你如何用約 70 行 Python 程式碼建立一個機器人,並涵蓋通常會出錯的部分。

更新於

抽象聊天機器人插圖,周圍環繞著發光的對話氣泡,說明由 LLM API 驅動的 Discord 機器人

重點摘要

  • 機器人需要 discord.py、openai 套件、機器人權杖和 LLM API 金鑰。
  • 在 Discord 開發者入口網站啟用訊息內容意圖,否則機器人將看到空白的訊息。
  • 保留每個頻道的簡短歷史並進行修剪,因為模型在每次呼叫之間不會記住任何內容。
  • 在 2,000 個字元處拆分回覆,並限制並行呼叫,以保持在 8 個請求的並行上限之下。

機器人的功能

目標是建立一個 Discord 機器人程式設計 AI,當有人提及它時會做出反應。使用者輸入「@helper 為什麼我的列表推導式回傳 None?」,機器人將訊息傳送至模型,模型的回應則以回覆形式出現在同一頻道。機器人也會記住每個頻道最近的幾輪對話,以便進行後續提問。

設計包含三個部分。discord.py 處理與 Discord 的連線。OpenAI Python SDK 與 LLM API 通訊,由於 API 與 OpenAI 相容,因此可以在此運作。小型字典用於儲存對話歷史。你不需要資料庫即可開始。

你需要 Python 3.9 或更新版本、可建立應用程式的 Discord 帳戶,以及來自 API 金鑰頁面的 API 金鑰。新帳戶可以一鍵領取 $0.50 的免費試用額度,足以建立並測試此機器人。

步驟 1:建立 Discord 應用程式

  1. 開啟 Discord 開發者入口網站並建立新應用程式。
  2. 在機器人區塊中,建立機器人使用者並複製其權杖。將其視為密碼處理。
  3. 在同一頁面上,於「特權閘道意圖」下開啟 訊息內容意圖。若未開啟,機器人接收到的訊息文字為空,你會花一個小時困惑為何它從未回覆。
  4. 在 OAuth2 中,使用 bot 範圍產生邀請連結,並賦予讀取訊息和傳送訊息的權限。開啟連結並將機器人新增至測試伺服器。

安裝函式庫並設定兩個環境變數。

pip install discord.py openai

export DISCORD_TOKEN="your-discord-bot-token"
export LLM_API_KEY="your-llm-api-key"

# quick check that the LLM key works, using model uncensored
curl -s https://api.codingllmapi.com/v1/chat/completions \
  -H "Authorization: Bearer $LLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"uncensored","messages":[{"role":"user","content":"ping"}],"max_tokens":5}'

步驟 2:機器人程式碼

將其儲存為 bot.py。它會在被提及時回覆,保留每個頻道十二則訊息的歷史,在等待時顯示輸入中指示器,並拆分長回覆。

import asyncio
import os

import discord
from openai import AsyncOpenAI, RateLimitError, APIError

llm = AsyncOpenAI(
    base_url="https://api.codingllmapi.com/v1",
    api_key=os.environ["LLM_API_KEY"],
)

SYSTEM = (
    "You are a helpful programming assistant in a Discord server. "
    "Answer briefly. Put code in fenced blocks. Say so when you are unsure."
)
MAX_TURNS = 12
history = {}                     # channel id -> list of messages
slots = asyncio.Semaphore(6)     # stay under the 8 concurrent request cap

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


def split_reply(text, limit=1900):
    parts = []
    while len(text) > limit:
        cut = text.rfind("\n", 0, limit)
        if cut < limit // 2:
            cut = limit
        parts.append(text[:cut])
        text = text[cut:].lstrip("\n")
    parts.append(text)
    return parts


@bot.event
async def on_message(message):
    if message.author.bot or bot.user not in message.mentions:
        return

    question = message.clean_content.replace(f"@{bot.user.display_name}", "").strip()
    if not question:
        await message.reply("Ask me a coding question after the mention.")
        return

    log = history.setdefault(message.channel.id, [])
    log.append({"role": "user", "content": question})
    del log[:-MAX_TURNS]

    async with message.channel.typing():
        try:
            async with slots:
                resp = await llm.chat.completions.create(
                    model="uncensored",
                    messages=[{"role": "system", "content": SYSTEM}] + log,
                    temperature=0.4,
                    max_tokens=900,
                )
        except RateLimitError:
            await message.reply("The model is busy. Try again in a few seconds.")
            log.pop()
            return
        except APIError:
            await message.reply("The model call failed. Try again shortly.")
            log.pop()
            return

    answer = resp.choices[0].message.content or "(empty reply)"
    log.append({"role": "assistant", "content": answer})
    for part in split_reply(answer):
        await message.reply(part)


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

使用 python bot.py 執行它,然後在測試伺服器中提及機器人。第一個回覆應在幾秒內到達。

程式碼撰寫方式的原因

歷史記錄是刻意修剪的

模型沒有記憶,因此每次呼叫都會傳送最近的對話輪次。若沒有 del log[:-MAX_TURNS],列表會無限增長。最終會達到 100,000 token 的上下文視窗,且你的成本會隨著每則訊息增加。十二筆條目足以進行單一錯誤的對話。對於擁有長線程的伺服器,可提高此數值或對較舊的輪次進行摘要。

Discord 的 2,000 個字元限制

Discord 會拒絕超過 2,000 個字元的訊息,而程式碼回覆很容易達到此限制。split_reply 輔助函數會在接近限制的新行處進行切割。它不知道程式碼圍欄,因此區塊可能會被切斷。如果這令你困擾,請新增邏輯,在一個部分的結尾關閉圍欄,並在下一個部分的開頭重新開啟。

並行控制的訊號量

完整金鑰允許 8 個並行請求,試用金鑰允許 2 個。繁忙的伺服器可能同時產生多個提問。訊號量會在你的程式內部將額外的呼叫排隊,而不是發送它們並收到錯誤碼為 concurrency 的 429 錯誤。如果你仍在使用試用金鑰,請將 Semaphore(6) 改為 Semaphore(2)。

錯誤不會計費

失敗或拒絕的請求不會計費,因此錯誤後的重試不會使成本加倍。程式碼會在失敗時從歷史中移除使用者輪次,因此重試不會留下模型已看過兩次的懸空問題。

改善回覆體驗

一旦基本迴圈運作,幾項小改動就能大幅改善體驗。

  • 串流輸出答案。 傳遞 stream=True 並每秒左右編輯機器人的訊息,顯示目前的文字。不要在每個 token 時編輯,因為 Discord 會對訊息編輯套用其自身的速率限制。串流請求會佔用其並行槽位直到結束,因此請讓訊號量涵蓋整個串流過程。
  • 使用斜線指令。 類似 /ask 的指令可避免提及解析,且完全不需要訊息內容意圖,這也使其在伺服器數量增加時更容易驗證。
  • 按使用者區分的歷史。 若多個人在同一頻道提出不相關的提問,請以頻道和使用者為鍵儲存歷史。
  • 調整系統提示詞。 列出社群使用的語言,要求簡短回答,並指示模型在問題模糊時提出澄清問題。提示詞調整是免費的,通常比更換模型更有效。
  • 降低溫度。 對於程式碼,0.2 到 0.4 之間的值比預設值提供更穩定的結果。

常見故障排除

當新機器人保持沉默時,原因幾乎總是下列項目之一。請按此順序檢查。

  1. 機器人在線但從未回覆。 開發者入口網站中的訊息內容意圖已關閉,或程式碼中缺少 intents.message_content = True。兩者都必須設定。
  2. 僅在某些頻道回覆。 機器人在其他頻道缺乏讀取或傳送訊息的權限。請檢查頻道權限覆蓋,而不僅是伺服器範圍的角色。
  3. 每個回覆都是「忙碌」訊息。你很可能觸發了 429 錯誤。請列印一次例外狀況的回應主體。如果程式碼顯示 rate_limited,表示你已超過完整金鑰每分鐘 300 次請求或試用金鑰每分鐘 60 次請求的上限。如果顯示 concurrency,請降低訊號量大小。
  4. 第一次呼叫時出現驗證錯誤。 LLM 金鑰缺失於執行機器人的程式環境中。在一個終端機分頁中匯出的金鑰,對在其他地方啟動的服務不可見。
  5. 答案被截斷。 範例中的 max_tokens=900 會截斷長回覆。提高此數值,或將其移除,因為 max_tokens 是選填的,且補全內容可能會使用剩餘的上下文視窗。
  6. 機器人回覆自己的訊息。 若省略 message.author.bot 檢查,就會發生此迴圈。請保留它。

除錯時,請為每次呼叫記錄 X-Request-Id 值。使用 OpenAI SDK 時,你可以透過呼叫 llm.chat.completions.with_raw_response.create(...) 從原始回應中讀取它,然後讀取 .headers。若就特定故障聯繫支援團隊,該 ID 可讓他們找到確切的請求。

運行成本

定價為每 100 萬個輸入 token $0.15,每 100 萬個輸出 token $0.50,無訂閱費。以典型情境為例:系統提示詞加上十二筆歷史記錄約為 2,500 個輸入 token,而回答約為 400 個 token。

輸入:2,500 x $0.15 / 1,000,000 = $0.000375。輸出:400 x $0.50 / 1,000,000 = $0.0002。一則回覆的成本約為 $0.000575。一個每天提出 500 個問題的社群,將花費 500 x $0.000575 = $0.2875,略低於 30 美分。由於程式碼貼上的長度不同,你的數字會有差異,因此請記錄 resp.usage 一週並讀取實際平均值。定價頁面 顯示儲值選項:$20 至 $500 之間的整數金額,儲值達 $50 時贈送 5%,達 $100 時贈送 10%,達 $200 時贈送 15%,可透過信用卡或加密貨幣付款。

為限制濫用,請加入每位使用者的冷卻時間並限制提問長度。這兩項檢查比你能新增的任何其他措施更能保護你的額度。

託管與安全注意事項

此機器人為長期執行的程序。小型虛擬機器或容器主機即可應付,因為繁重的工作在 API 端完成。請在程序管理器下執行,使其在崩潰後自動重啟,並將兩個權杖保留在環境變數或金鑰儲存庫中,切勿放在程式碼儲存庫內。

思考你的伺服器允許的內容。該模型經過調整,可在合法成人用途下無內容審查地回答問題,因此它會討論安全測試或邊緣小說,而較嚴格的助手可能會拒絕這些內容。如果你的社群包含未成年人,請在系統提示詞中設定規則,將機器人限制在特定頻道,並記住 Discord 自身的條款也適用於你的機器人。此 API 禁止任何涉及未成年人的性內容,無例外。

完整的參數詳細資訊,例如 stop、seed 和函式呼叫,請參閱 API 文件。如果你希望機器人執行程式碼或讀取儲存庫,應使用函式呼叫,並由你決定模型可以請求哪些函式。針對此模型的程式開發用途,請參閱關於 無審查 AI 程式碼模型的頁面。

問答

我需要訊息內容意圖嗎?

是的,如果機器人讀取一般訊息和提及。如果你只使用斜線指令,則不需要。

機器人可以串流輸出回覆嗎?

可以。設定 stream=True,並以平穩的間隔(例如每秒一次)編輯 Discord 訊息,以便符合 Discord 自身的限制。

如果太多人同時提問會發生什麼事?

當完整金鑰的並行請求超過 8 個時,API 會回傳 HTTP 429 狀態碼,並附上錯誤碼 concurrency。你的程式碼應使用訊號量(semaphore)將額外的請求排入佇列。

重新啟動後機器人還會記得對話嗎?

使用此處的字典則不會。若需要重新啟動後仍保留歷史記錄,請將歷史記錄儲存至 SQLite 或 Redis。

API 金鑰在 Discord 機器人中安全嗎?

如果它保留在你的伺服器環境變數中,則是安全的。切勿將其發布在頻道中或提交到公開的程式碼儲存庫。

只差一張表單,即可取得金鑰

建立帳號、複製金鑰、更改基礎 URL。這就是整個設定過程。

取得 API 金鑰