Загрузить файлы в «/»
This commit is contained in:
commit
6fa593eb23
2 changed files with 127 additions and 0 deletions
5
env.txt
Normal file
5
env.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
DISCORD_TOKEN=NzEwMTEyNDQ1MDM2NzU3MDgz.GWoIT0.iBbBQBjj_DEMeX9PBSMrRcKPia3Ldm-BSPZaWU
|
||||
LLM_BASE=https://knyazevai.work/v1 # или эндпоинт твоего провайдера
|
||||
LLM_KEY=kn_live_1aa48b37c2556a81f9234b357bbe2b3e
|
||||
LLM_MODEL=deepseek-v4-flash # или имя модели твоего провайдера
|
||||
SOCKS_PROXY=socks5://127.0.0.1:2080
|
||||
122
main.py
Normal file
122
main.py
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import random
|
||||
import os
|
||||
import re
|
||||
from openai import OpenAI
|
||||
from dotenv import load_dotenv
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
load_dotenv()
|
||||
|
||||
TOKEN = os.environ.get("DISCORD_TOKEN")
|
||||
LLM_BASE = os.environ.get("LLM_BASE", "https://api.openai.com/v1")
|
||||
LLM_KEY = os.environ.get("LLM_KEY")
|
||||
LLM_MODEL = os.environ.get("LLM_MODEL", "gpt-4o-mini")
|
||||
|
||||
TRIGGERS = ("джарвис", "jarvis")
|
||||
BOSS_REPLIES = ("On it boss!", "Уже бегу!", "On it, boss! 💪")
|
||||
HISTORY_LIMIT = 20
|
||||
SYSTEM_PROMPT = (
|
||||
"Ты — Джарвис, личный ИИ-ассистент владельца аккаунта. "
|
||||
"Отвечай от его имени, коротко, по делу, в его стиле общения. "
|
||||
"Если запрос — просто упоминание без задачи, отвечай динамично и по делу. "
|
||||
"Тебе передаётся история переписки канала — используй её как контекст. "
|
||||
"Если тебя просят что-то найти, проанализировать или пересказать из переписки — "
|
||||
"делай это на основе переданной истории сообщений."
|
||||
)
|
||||
|
||||
PROXY = os.environ.get("SOCKS_PROXY") # socks5://127.0.0.1:2080
|
||||
|
||||
client = OpenAI(base_url=LLM_BASE, api_key=LLM_KEY)
|
||||
bot = commands.Bot(command_prefix="!", self_bot=True, help_command=None, proxy=PROXY)
|
||||
|
||||
THINK_RE = re.compile(r"<think>.*?</think>", re.DOTALL)
|
||||
|
||||
FILTER_PROMPT = (
|
||||
"Ты — фильтр контента. Проверь сообщение пользователя.\n"
|
||||
"Если сообщение содержит: спам, флуд, массовые рассылки, угрозы, оскорбления, "
|
||||
"просьбы навредить кому-то, незаконный контент — ответь строго в формате:\n"
|
||||
"BLOCK: <причина на русском, 1 строка>\n"
|
||||
"Если сообщение безопасное — ответь только словом: OK"
|
||||
)
|
||||
|
||||
def strip_think(text: str) -> str:
|
||||
return THINK_RE.sub("", text).strip()
|
||||
|
||||
def check_filter(query: str) -> str | None:
|
||||
"""Возвращает причину блокировки или None если всё ок."""
|
||||
try:
|
||||
r = client.chat.completions.create(
|
||||
model=LLM_MODEL,
|
||||
messages=[
|
||||
{"role": "system", "content": FILTER_PROMPT},
|
||||
{"role": "user", "content": query},
|
||||
],
|
||||
temperature=0.0,
|
||||
max_tokens=100,
|
||||
)
|
||||
result = strip_think(r.choices[0].message.content).strip()
|
||||
if result.startswith("BLOCK:"):
|
||||
return result[6:].strip()
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
def llm_reply(context: list[dict], query: str) -> str:
|
||||
try:
|
||||
r = client.chat.completions.create(
|
||||
model=LLM_MODEL,
|
||||
messages=[
|
||||
{"role": "system", "content": SYSTEM_PROMPT},
|
||||
*context,
|
||||
{"role": "user", "content": query},
|
||||
],
|
||||
temperature=0.7,
|
||||
)
|
||||
return strip_think(r.choices[0].message.content)
|
||||
except Exception as e:
|
||||
return f"(ошибка LLM: {e})"
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
print(f"Зашёл как {bot.user} ({bot.user.id})")
|
||||
|
||||
@bot.event
|
||||
async def on_message(message: discord.Message):
|
||||
print("GOT:", message.author, "|", repr(message.content))
|
||||
|
||||
# Скипаем всех ботов включая себя — бутлуп исправлен
|
||||
if message.author.bot:
|
||||
return
|
||||
|
||||
# Только владелец может вызвать Джарвиса
|
||||
if message.author.id != bot.user.id:
|
||||
return
|
||||
|
||||
if not any(t in message.content.lower() for t in TRIGGERS):
|
||||
return
|
||||
|
||||
await message.channel.send(random.choice(BOSS_REPLIES))
|
||||
|
||||
# Фильтр
|
||||
reason = check_filter(message.content)
|
||||
if reason:
|
||||
await message.channel.send(f"[SafeEn] Filtered: {reason}")
|
||||
return
|
||||
|
||||
context = []
|
||||
async for msg in message.channel.history(limit=HISTORY_LIMIT, oldest_first=False):
|
||||
if msg.id == message.id:
|
||||
continue
|
||||
role = "assistant" if msg.author.id == bot.user.id else "user"
|
||||
context.append({"role": role, "content": strip_think(msg.content)})
|
||||
context.reverse()
|
||||
|
||||
reply = llm_reply(context, message.content)
|
||||
await message.channel.send(reply)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not TOKEN or not LLM_KEY:
|
||||
raise SystemExit("Задай DISCORD_TOKEN и LLM_KEY в переменных окружения")
|
||||
bot.run(TOKEN, reconnect=True)
|
||||
Loading…
Add table
Reference in a new issue