हम क्या बना रहे हैं
दो छोटे बॉट, एक साझा दिमाग। llm.py नाम की एक फ़ाइल API को छूने वाली हर चीज़ का मालिक है: पर्सोना, रेट लिमिटर, पुनः प्रयास तर्क। एक Discord बॉट और एक Telegram बॉट फिर केवल प्लेटफ़ॉर्म इवेंट्स को संदेशों की सूची में और वापस अनुवाद करते हैं। बाद में एक तीसरा प्लेटफ़ॉर्म जोड़ें और आपको केवल एडाप्टर लिखने की आवश्यकता होगी।
pip install httpx discord.py python-telegram-bot के साथ निर्भरताएं इंस्टॉल करें। अपने अकाउंट पेज से API_KEY एक्सपोर्ट करें, साथ ही प्रत्येक प्लेटफ़ॉर्म की अपनी बॉट सेटअप से DISCORD_TOKEN और/या TELEGRAM_TOKEN। नीचे दिए गए दोनों बॉट्स Python 3.10 या नए संस्करण मानते हैं।
अनुरोध प्रारूप सादा OpenAI chat-completions आकार है, जो बहु-भाषी क्विकस्टार्ट में कवर किया गया है। यहाँ SDK की कोई आवश्यकता नहीं है।
साझा कोर: पर्सोना, लिमिटर और पुनः प्रयास
खतरनाक हिस्से को एक जगह रखें। नीचे दिया गया मॉड्यूल अनुरोध टाइमस्टैम्प्स की एक स्लाइडिंग विंडो बनाए रखता है और जब यह प्रति मिनट 240 कॉल के करीब पहुँचता है तो रुक जाता है, जो प्रति कुंजी अनुमत 300 कॉल के नीचे एक जानबूझकर बनाया गया मार्जिन है। एक सेमाफोर समानांतर अनुरोधों को चार तक सीमित करता है ताकि एक व्यस्त चैनल एक साथ बीस अनुरोध न भेज सके।
# llm.py - shared by both bots
import asyncio
import os
import time
from collections import deque
import httpx
URL = "https://api.unrestrictedaiapi.com/v1/chat/completions"
PERSONA = "You are Pip, a sardonic bar-room storyteller. Keep replies under 150 words."
_stamps = deque() # timestamps of recent calls
_gate = asyncio.Semaphore(4) # at most 4 requests in flight
_client = httpx.AsyncClient(timeout=60.0)
async def _wait_for_slot(limit=240, window=60.0):
# Stay below the 300 requests/minute per-key limit with headroom.
while True:
now = time.monotonic()
while _stamps and now - _stamps[0] > window:
_stamps.popleft()
if len(_stamps) < limit:
_stamps.append(now)
return
await asyncio.sleep(window - (now - _stamps[0]) + 0.05)
async def chat(history):
"""history: list of {"role","content"} dicts, oldest first."""
messages = [{"role": "system", "content": PERSONA}] + list(history)
async with _gate:
for attempt in range(3):
await _wait_for_slot()
r = await _client.post(
URL,
headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
json={"model": "uncensored", "messages": messages, "max_tokens": 400},
)
if r.status_code in (429, 503):
await asyncio.sleep(2 ** attempt)
continue
if r.status_code == 403:
return "I can't continue with that."
r.raise_for_status()
return r.json()["choices"][0]["message"]["content"]
return "The service is busy. Try again in a moment."स्टेटस हैंडलिंग देखें। 429 या 503 पर 1, 2, फिर 4 सेकंड के लिए सोया जाता है और पुनः प्रयास किया जाता है। 403 का अर्थ है कि कंटेंट फ़िल्टर सक्रिय हुआ है, इसलिए बॉट पुनः प्रयास करने के बजाय एक उदासीन पंक्ति के साथ उत्तर देता है। कोई भी अन्य त्रुटि फेंकता है, जिसे आपकी प्लेटफ़ॉर्म लाइब्रेरी लॉग करेगी। यदि आपको 402 दिखता है, तो आपका बैलेंस खाली है या ट्रायल समाप्त हो गया है; प्रीपेड बैलेंस को टॉप-अप करें।
Discord एडाप्टर
Discord बॉट्स को डेवलपर पोर्टल और कोड दोनों में message-content intent को चालू करना होता है, वरना msg.content खाली आता है। बॉट केवल तभी जवाब देता है जब उसे mention किया जाता है, जिससे वह भरे सर्वर में हर संदेश नहीं पढ़ता।
# discord_bot.py
import os
import time
from collections import defaultdict, deque
import discord
from llm import chat
intents = discord.Intents.default()
intents.message_content = True # also enable it in the developer portal
bot = discord.Client(intents=intents)
HISTORY_TURNS = 10 # user+assistant messages kept per channel
history = defaultdict(lambda: deque(maxlen=HISTORY_TURNS))
last_use = {} # user id -> last request time
COOLDOWN = 5.0 # seconds between requests per user
def chunks(text, size=1900):
return [text[i:i + size] for i in range(0, len(text), size)] or [""]
@bot.event
async def on_message(msg: discord.Message):
if msg.author.bot or bot.user not in msg.mentions:
return
# Age gate: guild text channels must be flagged NSFW. No DMs.
if not isinstance(msg.channel, discord.TextChannel) or not msg.channel.is_nsfw():
await msg.reply("I only chat in channels marked age-restricted (NSFW).")
return
now = time.monotonic()
if now - last_use.get(msg.author.id, 0) < COOLDOWN:
await msg.add_reaction("\u23f3")
return
last_use[msg.author.id] = now
text = msg.clean_content.replace(f"@{bot.user.display_name}", "").strip()
if not text:
return
h = history[msg.channel.id]
h.append({"role": "user", "content": f"{msg.author.display_name}: {text}"})
async with msg.channel.typing():
answer = await chat(h)
h.append({"role": "assistant", "content": answer})
for part in chunks(answer):
await msg.channel.send(part)
bot.run(os.environ["DISCORD_TOKEN"])इतिहास एक deque है जिसका maxlen=10 channel id द्वारा key'd है, इसलिए प्रत्येक channel अपनी स्वयं की वार्तालाप है और पुराने turns स्वचालित रूप से हट जाते हैं। प्रत्येक user line को speaker के display name से prefix करने से मॉडल shared room में लोगों को अलग-अलग पहचान सकता है। दस messages एक conservative default है; context window 100,000 tokens का है, इसलिए यह महत्वपूर्ण होने से पहले आप इसे काफी बढ़ा सकते हैं।
Telegram एडाप्टर
python-telegram-bot v20 और बाद के संस्करण async हैं, जो shared core के साथ स्वाभाविक रूप से जुड़ते हैं। chat_data एक per-chat dictionary है जिसे लाइब्रेरी आपके लिए रखती है, इसलिए history और cooldown state को अतिरिक्त storage code की आवश्यकता नहीं है।
# telegram_bot.py (python-telegram-bot v20+)
import os
import time
from collections import deque
from telegram import Update
from telegram.ext import (ApplicationBuilder, CommandHandler, ContextTypes,
MessageHandler, filters)
from llm import chat
async def adult(update: Update, context: ContextTypes.DEFAULT_TYPE):
context.chat_data["adult"] = True
await update.message.reply_text("Noted. Say something.")
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(
"This bot produces adult fiction. Send /adult only if you are 18 or older."
)
async def talk(update: Update, context: ContextTypes.DEFAULT_TYPE):
cd = context.chat_data
if not cd.get("adult"):
await update.message.reply_text("Send /adult to confirm you are 18+.")
return
if time.monotonic() - cd.get("last", 0) < 4:
return # per-chat cooldown
cd["last"] = time.monotonic()
h = cd.setdefault("history", deque(maxlen=10))
h.append({"role": "user", "content": update.message.text})
await context.bot.send_chat_action(update.effective_chat.id, "typing")
answer = await chat(h)
h.append({"role": "assistant", "content": answer})
for i in range(0, len(answer), 4000): # Telegram limit is 4096 chars
await update.message.reply_text(answer[i:i + 4000])
app = ApplicationBuilder().token(os.environ["TELEGRAM_TOKEN"]).build()
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("adult", adult))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, talk))
app.run_polling()run_polling() शुरू करने का सबसे सरल तरीका है और इसमें किसी सार्वजनिक URL की आवश्यकता नहीं है। ध्यान रखें कि chat_data मेमोरी में रहता है, इसलिए रीस्टार्ट इसे साफ़ कर देता है। यदि आपको ऐसी इतिहास चाहिए जो रीस्टार्ट के बाद भी बनी रहे, तो deque की सामग्री को स्वयं एक फ़ाइल या डेटाबेस में स्टोर करें।
आयु-सीमा और NSFW चैनल नियम
यह API केवल वयस्कों के लिए है, और इस पर बनाई गई हर चीज़ उस जिम्मेदारी को विरासत में पाती है। आयु-सीमा को एक विशेषता के रूप में देखें, एक बाद की सोच के रूप में नहीं।
- Discord: केवल उस गिल्ड चैनल में उत्तर दें जिन पर उम्र-सीमा चिह्नित है।
channel.is_nsfw()जाँच में कुछ नहीं लगता और बॉट अन्यत्र, सीधे संदेश सहित, उत्तर नहीं देता। - Telegram: require an explicit
/adultconfirmation per chat before any generation, and state the rule in the welcome text. - Both: do not offer the bot in spaces aimed at young people, and remove access promptly if you learn a user is under 18.
एक confirmation command एक gate है, उम्र की प्रमाण नहीं। minors से संबंधित sexual content हमेशा API द्वारा 403 के साथ ब्लॉक किया जाता है, जिसमें fiction और roleplay शामिल हैं, और आपका बॉट उस response को work around करने का प्रयास कभी नहीं करना चाहिए।
इसे चलाना और भीड़ के बिना परीक्षण करना
प्रत्येक बॉट को अपने टर्मिनल में python discord_bot.py या python telegram_bot.py से शुरू करें। पहले रन के लिए, एक निजी टेस्ट सर्वर या केवल आपका चैट बनाएँ। एक छोटी चेकलिस्ट के माध्यम से जाएँ और आप लगभग हर शुरुआती बग पकड़ लेंगे।
- एक गैर-NSFW चैनल में बॉट को मENTION करें और पुष्टि करें कि यह मना कर देता है। यह साबित करता है कि गेट काम कर रहा है।
- इसे एक flagged channel में mention करें और दो messages जल्दी भेजें। दूसरे message को hourglass reaction मिलनी चाहिए, जो साबित करता है कि cooldown काम कर रहा है।
- एक चार-संदेश की बातचीत करें, फिर पहले संदेश की ओर संदर्भित करें। यदि बॉट याद रखता है, तो इतिहास सही ढंग से जुड़ा हुआ है।
- API कुंजी को एक गलत मान पर अस्थायी रूप से सेट करें और पुष्टि करें कि विफलता आपके लॉग में दिखाई देती है, चुपचाप गायब नहीं होती।
- एक लंबा टेक्स्ट ब्लॉक पेस्ट करें और प्लेटफ़ॉर्म की लंबाई सीमा के तहत कई संदेशों में प्रतिक्रिया को देखें।
यदि replies generic लगते हैं, तो आमतौर पर persona समस्या है, code नहीं। PERSONA की एक sentence बदलें, restart करें, और तुलना करें। क्योंकि persona shared module में रहता है, एक edit दोनों bots को एक साथ बदल देता है, जो इसीलिए structure को extra file के लिए worth बनाता है।
जब आप deploy करने के लिए तैयार हों, तो कोई भी always-on process manager काम करेगा। bots अपनी प्लेटफ़ॉर्म के साथ एक long-lived connection रखते हैं और API को short outbound HTTPS calls करते हैं, इसलिए उन्हें inbound ports की आवश्यकता नहीं है और लगभग कोई memory नहीं। एक restart policy जोड़ें ताकि एक crash आपकी community को एक silent bot से बात करते हुए न छोड़ दे।
रेट लिमिट, इतिहास का आकार और लागत
तीन नॉब्स यह नियंत्रित करते हैं कि लोड के तहत बॉट कैसे व्यवहार करता है। प्रति-उपयोगकर्ता कूलडाउन एक व्यक्ति को कुंजी पर कब्जा करने से रोकता है। वैश्विक विंडो प्रति मिनट 300 अनुरोध की छत की रक्षा करता है। सेमाफोर बर्स्ट को चिकना बनाता है। यदि आप एक ही कुंजी पर कई बॉट प्रक्रियाएँ चलाते हैं, तो वे उस छत को साझा करते हैं, इसलिए प्रत्येक प्रक्रिया के limit को उसी अनुपात में कम करें।
भीड़ को invite करने से पहले spend का अनुमान लगाएं। मान्यताएँ: प्रत्येक call लगभग 1,200 input tokens भेजता है (persona plus दस turns) और 250 tokens लौटाता है। एक call की लागत 1,200 x $0.25 / 1,000,000 + 250 x $1.00 / 1,000,000 = $0.0003 + $0.00025 = $0.00055 है। एक server जो 2,000 replies प्रति दिन produce करता है, उन मान्यताओं के तहत लगभग $1.10 प्रति day spend करेगा। आपके numbers अलग होंगे, इसलिए usage field को एक सप्ताह के लिए log करें और पुनः गणना करें।
यदि persona को ही tune करना हो, तो the prompting guide पढ़ें। मूल्य the pricing page पर सूचीबद्ध हैं।
बॉट को शेयर करने से पहले हार्डनिंग
टोकन को स्रोत नियंत्रण से बाहर रखें; उन्हें दिखाए अनुसार वातावरण से पढ़ें। API को भेजने से पहले संदेश की लंबाई को सीमित करें ताकि एक विशाल पेस्ट आपका बैलेंस न जलाए। त्रुटियों को लॉग करें लेकिन संदेश पाठ को नहीं, क्योंकि आपके उपयोगकर्ताओं को अपने चैट के निजी रहने की उचित उम्मीद होगी। एक /reset कमांड जोड़ें जो चैनल के deque को साफ़ करता है, जो एक बातचीत जब फिसल जाती है तो सबसे तेज़ समाधान है। अंत में, बॉट के प्रोफ़ाइल में एक दृश्यमान नोट डालें कि यह वयस्क कथाएँ लिखता है और उसके उत्तर उत्पन्न किए गए हैं।
एक और design choice पर एक वाक्य योग्य है: बॉट full reply का इंतज़ार करते हैं, streaming नहीं करते। एक chat message को token by token edit करने से platform edit limits में सीधा टकराव होता है, और 150 शब्दों के करीब capped replies के लिए wait short है। यदि आप बाद में अपने web front end में live typing चाहते हैं, तो stream: true को वहां चालू करें, जहां आप transport को control करते हैं। तब तक, typing indicator जो आप पहले से भेजते हैं, bot के काम कर रहे दिखाने का सबसे सस्ता तरीका है, और यह कोड को छोटा रखता है ताकि इसे एक बैठक में पढ़ा जा सके और किसी को invite करने से पहले audit किया जा सके।