บอท Discord แบบโอเพนซอร์สพร้อม LLM แบบไม่เซ็นเซอร์
บอท Discord แบบโอเพนซอร์สที่ใช้ LLM ในการสนทนาประกอบด้วยโค้ด Python ประมาณแปดสิบบรรทัด คู่มือนี้ให้โค้ด ขีดจำกัดที่คุณต้องปฏิบัติตาม และต้นทุนต่อคำตอบ โดยใช้ API ที่เข้ากันได้กับ OpenAI พร้อมโมเดลที่ไม่ปฏิเสธคำขอจากผู้ใหญ่ที่ถูกต้องตามกฎหมาย

ประเด็นสำคัญ
- discord.py รวมกับ OpenAI SDK ก็เพียงพอแล้ว; ชี้ SDK ไปที่ https://api.openrouternsfw.com/v1 และใช้โมเดล uncensored
- เปิดใช้งาน Message Content intent ในพอร์ทัลผู้พัฒนา Discord หรือบอทจะเห็นข้อความว่าง
- ใช้เซมิโฟร์ที่ต่ำกว่า 8 เพื่อให้อยู่ภายในขีดจำกัดการทำงานพร้อมกัน และเก็บเนื้อหาผู้ใหญ่ไว้ในช่องที่ระบุว่าเป็นช่องจำกัดอายุ
- การตอบกลับที่มี 1,500 โทเคนอินพุตและ 300 โทเคนเอาต์พุตมีค่าใช้จ่าย $0.000375 ดังนั้นประมาณ $3.75 ต่อการตอบกลับ 10,000 ครั้ง
ไม่ต้องใช้บัตร หลังสมัคร ให้รับเครดิตทดลองใช้ฟรี $0.50 ด้วยคลิกเดียว (หนึ่งครั้งต่อคน)
รับคีย์ APIสิ่งที่คุณกำลังสร้าง
บอทด้านล่างจะตอบเมื่อมีคนกล่าวถึงมัน มันเก็บประวัติสั้นๆ ต่อช่อง ส่งไปยังเอนด์พอยต์ chat completions และโพสต์คำตอบกลับ เป็นโค้ด Python ธรรมดา อนุญาตภายใต้ MIT และไม่มีฐานข้อมูล คุณสามารถอ่านโค้ดทั้งหมดได้ในครั้งเดียว ซึ่งเป็นจุดประสงค์ของโปรเจกต์โอเพนซอร์ส: คนอื่นสามารถ fork โค้ดนี้ไปและเข้าใจมันได้
โมเดลเบื้องหลังคือโมเดลข้อความแบบเปิดน้ำหนักเดียวที่ให้บริการโดยผู้ให้บริการอิสระและปรับแต่งมาให้ตอบโดยไม่มีการปฏิเสธเนื้อหาสำหรับการใช้งานผู้ใหญ่ที่ถูกต้องตามกฎหมาย สิ่งนี้เหมาะกับชุมชน Discord ที่สร้างขึ้นรอบนิยาย การสวมบทบาท หรือการสนทนาสำหรับผู้ใหญ่ มีกฎสองข้อที่ใช้ได้ทันที ประการแรก เนื้อหาทางเพศที่เกี่ยวข้องกับผู้เยาว์ไม่อนุญาตผ่าน API และไม่ควรเป็นส่วนหนึ่งของการออกแบบบอทของคุณ ประการที่สอง Discord มีกฎเกณฑ์และการตั้งค่าช่องที่จำกัดอายุเป็นของตัวเอง และคุณมีหน้าที่ต้องปฏิบัติตาม เก็บเนื้อหาสำหรับผู้ใหญ่ไว้ในช่องที่ระบุไว้เท่านั้น และในเซิร์ฟเวอร์ที่สมาชิกเป็นผู้ใหญ่
ตั้งค่า Discord และคีย์ของคุณ
- เปิดหน้าเว็บผู้พัฒนา Discord และสร้างแอปพลิเคชัน จากนั้นเพิ่มบอตเข้าไป
- ภายใต้การตั้งค่าบอต ให้เปิดใช้งาน Message Content Intent หากไม่มีสิ่งนี้ บอตจะรับข้อความที่มีข้อความว่างเปล่า
- คัดลอกโทเคนของบอท ถือว่าเป็นรหัสผ่าน
- ใช้ตัวสร้าง URL ของ OAuth2 เพื่อเชิญบอทพร้อมสิทธิ์ในการอ่านและส่งข้อความในช่องที่คุณเลือก
- รับคีย์ API จาก หน้าคีย์ ผู้ใช้ใหม่สามารถรับเครดิตทดลองใช้มูลค่า $0.50 ได้ด้วยการคลิกหนึ่งครั้ง ไม่ต้องใส่รายละเอียดการชำระเงิน ใช้ได้เป็นเวลา 7 วัน
เก็บค่าลับทั้งสองนี้เป็นตัวแปรสภาพแวดล้อม (environment variables) อย่าเก็บไว้ใน repository สำหรับโปรเจกต์โอเพนซอร์ส สิ่งนี้สำคัญกว่าปกติ เพราะโค้ดเป็นสาธารณะ
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 จริงๆ ซึ่งเลือกจากรายการ autocomplete เพราะข้อความธรรมดาที่มีชื่อบอทจะไม่ปรากฏใน message.mentions เทอร์มินัลจะแสดงข้อผิดพลาด API ดังนั้นคีย์ที่ผิดจะแสดงเป็นข้อผิดพลาดการตรวจสอบสิทธิ์
เหตุผลที่โค้ดถูกออกแบบมาแบบนี้
ประวัติมีขนาดเล็กโดยเจตนา
หน้าต่างบริบทมีขนาด 100,000 โทเคน ดังนั้นคุณจึงสามารถส่งข้อมูลได้จำนวนมาก แต่โทเคนอินพุตทุกตัวจะถูกเรียกเก็บเงินในทุกการเรียกใช้ และแชท Discord ส่วนใหญ่เป็นข้อความสั้นๆ การสนทนา 12 รอบช่วยให้คำตอบมีความสอดคล้องกันโดยไม่ทำให้แต่ละข้อความกลายเป็นค่าใช้จ่ายที่สูง เพิ่ม maxlen สำหรับช่องเรื่องเล่าที่ความต่อเนื่องมีความสำคัญ
เซมาโฟร์ป้องกันคีย์ของคุณ
คีย์แบบเต็มอนุญาตให้ทำคำขอพร้อมกัน (parallel requests) ได้ 8 คำขอและ 300 คำขอต่อนาที คีย์ทดลองอนุญาตให้ทำคำขอพร้อมกันได้เพียง 2 คำขอและ 60 คำขอต่อนาที หากมีคนสิบคนกล่าวถึงบอทพร้อมกันและคุณส่งคำขอสิบครั้ง คำขอที่เหลือจะได้รับ HTTP 429 พร้อมรหัสข้อผิดพลาด concurrency เซมาโฟร์จะจัดคิวคำขอเหล่านี้ในกระบวนการของคุณเอง กำหนดค่าเป็น 2 ขณะที่คุณใช้คีย์ทดลอง หากคุณเปิดใช้งานสตรีมมิง จำไว้ว่าคำขอที่สตรีมจะครอบครองช่องของมันจนกว่าการสตรีมจะเสร็จสิ้น
การลองซ้ำมาจาก SDK
OpenAI Python SDK จะลองซ้ำเมื่อได้รับ 429 และปฏิบัติตามส่วนหัว Retry-After ซึ่งบริการส่งเป็น Retry-After: 5 เมื่อโหลดเกินชั่วคราว การตั้งค่า max_retries เป็น 4 ครอบคลุมการเพิ่มขึ้นของโหลดแบบสั้นๆ คำขอที่ล้มเหลวหรือถูกปฏิเสธจะไม่ถูกเรียกเก็บเงิน ดังนั้นการลองซ้ำจึงไม่เสียโทเคนเมื่อล้มเหลว
บุคลิกภาพร่วมกันหนึ่งเดียว
คำสั่งระบบจะอยู่แรกสุดในทุกการเรียกใช้ บุคลิกภาพต่อช่องเป็นการขยายเพิ่มเติมเล็กน้อย: เก็บแมปจาก ID ช่องไปยังคำสั่งและเรียกดูใน ask
ทำให้มันเป็นโปรเจกต์โอเพนซอร์สที่ดี
โค้ดทำงานได้ แต่ repository สาธารณะต้องการส่วนเสริมเล็กน้อยก่อนที่คนแปลกหน้าจะสามารถใช้งานได้
- ใบอนุญาต MIT หรือ Apache 2.0 เป็นตัวเลือกทั่วไปสำหรับบอทขนาดเล็ก เพิ่มไฟล์ที่ไดเรกทอรีรากของที่เก็บ
- ไฟล์ .env.example ระบุ
DISCORD_TOKENและAPI_KEYด้วยค่าปลอมเพื่อให้ผู้คนรู้ว่าจะตั้งค่าอะไร - URL ฐานที่ปรับค่าได้ อ่านค่า
BASE_URLและMODELจากตัวแปรสภาพแวดล้อม โดยกำหนดค่าเริ่มต้นตามค่าข้างต้น จากนั้นผู้ใช้ที่ fork โค้ดไปสามารถใช้งานเซิร์ฟเวอร์ที่เข้ากันได้กับ OpenAI ได้ - ไฟล์ Dockerfile ไฟล์ขนาดสั้นที่ใช้ภาพพื้นฐานแบบ slim ของ Python โดยคัดลอก
bot.pyติดตั้งแพ็กเกจทั้งสาม และรันสคริปต์ ช่วยให้ผู้คนสามารถโฮสต์ได้ทุกที่ - ไฟล์ README พร้อมคำเตือนเรื่อง Intents การขาด Message Content intent เป็นคำถามด้านการสนับสนุนที่พบบ่อยที่สุดสำหรับบอต Discord ทุกตัว
เก็บโทเคนและคีย์ของคุณออกจากประวัติ Git หากมีการรั่วไหล ให้เพิกถอนคีย์นั้นในพอร์ทัล Discord หรือหน้าคีย์ API แล้วสร้างคีย์ใหม่
การโฮสต์และค่าใช้จ่าย
บอตเป็นกระบวนการที่ทำงานต่อเนื่องยาวนานและใช้ซีพียูเพียงเล็กน้อย เซิร์ฟเวอร์เสมือนขนาดเล็ก (VPS) เครื่องสำรอง หรือแพลตฟอร์มคอนเทนเนอร์ก็เพียงพอแล้ว ไม่ต้องมีเว็บเซิร์ฟเวอร์ที่ต้องเปิดเผย เพราะบอตจะเชื่อมต่อออกยัง Discord
ต้นทุน API คำนวณได้ง่าย คำตอบทั่วไปที่นี่ใช้โทเคนอินพุต 1,500 ตัว (พรอมต์ระบบรวมกับการสนทนาสั้นๆ 12 รอบ) และโทเคนเอาต์พุต 300 ตัว
- อินพุต: 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 พร้อมโบนัส +5% เมื่อเติม $50, +10% เมื่อเติม $100 และ +15% เมื่อเติม $200 เครดิตไม่หมดอายุและไม่มีบริการแบบสมัครสมาชิก ทำให้เซิร์ฟเวอร์ที่ใช้งานน้อยมีค่าใช้จ่ายเกือบเป็นศูนย์ ดูรายละเอียดได้ที่ หน้าราคา การชำระเงินใช้คริปโตเท่านั้น โดยใช้ USDT บน TRC20 หรือ USDC บน Base
ขั้นตอนถัดไป
การอัปเกรดสามรายการคุ้มค่ากับความพยายาม เพิ่มสตรีมมิงโดยการแก้ไขข้อความเดียวเมื่อได้รับข้อมูลเป็นชิ้นส่วน แต่ให้ระวังขีดจำกัดอัตราในการแก้ไขของ Discord เพิ่มคำสั่งสแลชเพื่อล้างประวัติช่อง เพิ่มการเรียกใช้ฟังก์ชันเพื่อให้บอตสามารถทอยลูกเต๋าหรือค้นหาข้อมูลตัวละครได้ API รองรับ tools, tool_choice และผลลัพธ์ role: "tool" ในรูปแบบของ OpenAI
เอกสาร API ระบุพารามิเตอร์ที่รองรับทั้งหมด และบทความ Hermes API ครอบคลุมพฤติกรรมของโมเดลที่ไม่เซ็นเซอร์ภายในโปรเจกต์ พรอมต์ที่ส่งไปยัง API ไม่ถูกนำไปใช้ในการฝึกฝน ซึ่งเป็นเรื่องที่ควรระบุไว้ในกฎของเซิร์ฟเวอร์หากสมาชิกของคุณถามถึง
คู่มือเพิ่มเติม
ถาม-ตอบ
บอตจำเป็นต้องใช้ Message Content intent หรือไม่?
จำเป็น เปิดใช้งานในพอร์ทัลนักพัฒนา Discord และในโค้ดโดยตั้งค่า intents.message_content เป็น true มิฉะนั้นบอตจะเห็นข้อความว่างเปล่า
บอตสามารถจัดการการตอบกลับได้พร้อมกันกี่ครั้ง?
คีย์แบบเต็มอนุญาตให้ทำคำขอพร้อมกัน 8 คำขอและ 300 คำขอต่อนาที; คีย์ทดลองอนุญาตให้ทำ 2 และ 60 ตามลำดับ เซมิโฟร์ในบอตจะช่วยให้คุณอยู่ภายใต้ขีดจำกัด
ฉันสามารถเผยแพร่โค้ดของบอตได้หรือไม่?
ได้ ตราบใดที่โทเคน Discord และคีย์ API ยังคงไม่อยู่ในที่เก็บข้อมูล อ่านค่าจากตัวแปรสภาพแวดล้อมและจัดส่งไฟล์ .env.example
อนุญาตให้ใช้ธีมสำหรับผู้ใหญ่หรือไม่?
อนุญาตเฉพาะสำหรับการใช้งานผู้ใหญ่ที่ถูกต้องตามกฎหมาย สอดคล้องกับกฎของ Discord และการตั้งค่าช่องที่จำกัดอายุ เนื้อหาทางเพศที่เกี่ยวข้องกับผู้เยาว์ไม่อนุญาตเด็ดขาด
ต้นทุนการใช้งานเป็นอย่างไร
การตอบกลับที่มี 1,500 input token และ 300 output token มีค่าใช้จ่าย $0.000375 ดังนั้นประมาณ $3.75 ต่อการตอบกลับ 10,000 ครั้ง บวกกับต้นทุนในการโฮสต์กระบวนการ
คีย์ของคุณอยู่ห่างแค่แบบฟอร์มเดียว
สร้างบัญชี คัดลอกคีย์ และเปลี่ยน base URL นั่นคือการตั้งค่าทั้งหมด
รับคีย์ API