forked from zovos/bot_tg
Merge pull request 'Добавлен контекс' (#11) from Dan4ick/bot_tg:feature/FenyaBot into main
Reviewed-on: zovos/bot_tg#11
This commit is contained in:
commit
79e581a010
3 changed files with 95 additions and 17 deletions
|
|
@ -1,6 +1,8 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
import sqlite3
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from aiogram.types import Message
|
from aiogram.types import Message
|
||||||
|
|
@ -54,14 +56,77 @@ SYSTEM_PROMPT = (
|
||||||
"а дальше пересказывай своими словами, без длинных дословных цитат."
|
"а дальше пересказывай своими словами, без длинных дословных цитат."
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _generate_response(user_text: str) -> str:
|
# Размер контекста — количество сообщений на чат
|
||||||
# Формируем запрос к /v1/chat/completions и возвращаем текст первого ответа.
|
_CONTEXT_SIZE = 100
|
||||||
|
# Путь к SQLite базе (берётся из переменной окружения или из config)
|
||||||
|
_DB_PATH = os.getenv("CHAT_HISTORY_DB_PATH", "/db/chat_history.sqlite3")
|
||||||
|
|
||||||
|
|
||||||
|
def _init_db() -> None:
|
||||||
|
"""Создаёт таблицу истории чата, если её нет."""
|
||||||
|
Path(_DB_PATH).parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with sqlite3.connect(_DB_PATH) as conn:
|
||||||
|
conn.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS chat_history (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
chat_id INTEGER NOT NULL,
|
||||||
|
role TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
text TEXT NOT NULL
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
conn.execute("CREATE INDEX IF NOT EXISTS idx_chat_history_chat_id ON chat_history(chat_id, id)")
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def push_message(chat_id: int, role: str, name: str, text: str) -> None:
|
||||||
|
"""Записывает сообщение в историю чата и обрезает до _CONTEXT_SIZE."""
|
||||||
|
with sqlite3.connect(_DB_PATH) as conn:
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO chat_history (chat_id, role, name, text) VALUES (?, ?, ?, ?)",
|
||||||
|
(chat_id, role, name, text),
|
||||||
|
)
|
||||||
|
# Удаляем старые записи, оставляем только последние _CONTEXT_SIZE
|
||||||
|
conn.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM chat_history
|
||||||
|
WHERE chat_id = ? AND id NOT IN (
|
||||||
|
SELECT id FROM chat_history WHERE chat_id = ? ORDER BY id DESC LIMIT ?
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
(chat_id, chat_id, _CONTEXT_SIZE),
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_history(chat_id: int) -> list[dict]:
|
||||||
|
"""Возвращает историю чата как список словарей."""
|
||||||
|
with sqlite3.connect(_DB_PATH) as conn:
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT role, name, text FROM chat_history WHERE chat_id = ? ORDER BY id ASC",
|
||||||
|
(chat_id,),
|
||||||
|
).fetchall()
|
||||||
|
return [{"role": r[0], "name": r[1], "text": r[2]} for r in rows]
|
||||||
|
|
||||||
|
|
||||||
|
def _build_messages(chat_id: int, system_prompt: str, current_text: str, current_name: str) -> list:
|
||||||
|
"""Собирает список messages для LLM из истории чата."""
|
||||||
|
messages = [{"role": "system", "content": system_prompt}]
|
||||||
|
for entry in _get_history(chat_id):
|
||||||
|
content = f"{entry['name']}: {entry['text']}" if entry["role"] == "user" else entry["text"]
|
||||||
|
messages.append({"role": entry["role"], "content": content})
|
||||||
|
messages.append({"role": "user", "content": f"{current_name}: {current_text}"})
|
||||||
|
return messages
|
||||||
|
|
||||||
|
|
||||||
|
async def _generate_response(chat_id: int, user_text: str, user_name: str) -> str:
|
||||||
|
# Формируем запрос к /v1/chat/completions с историей чата и возвращаем текст первого ответа.
|
||||||
url = f"{LLAMA_API_URL.rstrip('/')}/v1/chat/completions"
|
url = f"{LLAMA_API_URL.rstrip('/')}/v1/chat/completions"
|
||||||
|
msgs = await asyncio.to_thread(_build_messages, chat_id, SYSTEM_PROMPT, user_text, user_name)
|
||||||
payload = {
|
payload = {
|
||||||
"messages": [
|
"messages": msgs,
|
||||||
{"role": "system", "content": SYSTEM_PROMPT},
|
|
||||||
{"role": "user", "content": user_text},
|
|
||||||
],
|
|
||||||
"max_tokens": 500,
|
"max_tokens": 500,
|
||||||
"temperature": 0.8,
|
"temperature": 0.8,
|
||||||
"top_p": 0.9,
|
"top_p": 0.9,
|
||||||
|
|
@ -80,14 +145,19 @@ async def handle_talk(message: Message):
|
||||||
return
|
return
|
||||||
|
|
||||||
user_text = parts[1].strip()
|
user_text = parts[1].strip()
|
||||||
|
user_name = message.from_user.first_name or "кент"
|
||||||
|
chat_id = message.chat.id
|
||||||
|
|
||||||
# Показываем "печатает..." пока ждём ответ модели.
|
# Показываем "печатает..." пока ждём ответ модели.
|
||||||
await message.bot.send_chat_action(chat_id=message.chat.id, action="typing")
|
await message.bot.send_chat_action(chat_id=chat_id, action="typing")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = await _generate_response(user_text)
|
response = await _generate_response(chat_id, user_text, user_name)
|
||||||
if not response:
|
if not response:
|
||||||
response = "Братуха, чёт базар не клеится, попробуй ещё раз."
|
response = "Братуха, чёт базар не клеится, попробуй ещё раз."
|
||||||
|
# Пишем запрос и ответ в историю
|
||||||
|
await asyncio.to_thread(push_message, chat_id, "user", user_name, user_text)
|
||||||
|
await asyncio.to_thread(push_message, chat_id, "assistant", "бот", response)
|
||||||
await message.reply(response, parse_mode=None)
|
await message.reply(response, parse_mode=None)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
# При любых ошибках не падаем, а отдаём понятный fallback.
|
# При любых ошибках не падаем, а отдаём понятный fallback.
|
||||||
|
|
@ -104,13 +174,11 @@ AUTOREPLY_SYSTEM_PROMPT = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def generate_autoreply(text: str) -> str:
|
async def generate_autoreply(chat_id: int, text: str, user_name: str) -> str:
|
||||||
url = f"{LLAMA_API_URL.rstrip('/')}/v1/chat/completions"
|
url = f"{LLAMA_API_URL.rstrip('/')}/v1/chat/completions"
|
||||||
|
msgs = await asyncio.to_thread(_build_messages, chat_id, AUTOREPLY_SYSTEM_PROMPT, text, user_name)
|
||||||
payload = {
|
payload = {
|
||||||
"messages": [
|
"messages": msgs,
|
||||||
{"role": "system", "content": AUTOREPLY_SYSTEM_PROMPT},
|
|
||||||
{"role": "user", "content": text},
|
|
||||||
],
|
|
||||||
"max_tokens": 150,
|
"max_tokens": 150,
|
||||||
"temperature": 0.9,
|
"temperature": 0.9,
|
||||||
"top_p": 0.9,
|
"top_p": 0.9,
|
||||||
|
|
@ -120,3 +188,6 @@ async def generate_autoreply(text: str) -> str:
|
||||||
data = await resp.json()
|
data = await resp.json()
|
||||||
return data["choices"][0]["message"]["content"].strip()
|
return data["choices"][0]["message"]["content"].strip()
|
||||||
|
|
||||||
|
|
||||||
|
# Инициализация БД при импорте модуля
|
||||||
|
_init_db()
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ DEFAULT_TZ = os.getenv("TZ", "Europe/Moscow")
|
||||||
|
|
||||||
# --- Настройки /penis ---
|
# --- Настройки /penis ---
|
||||||
PENIS_DB_PATH = "/db/penis_stats.sqlite3"
|
PENIS_DB_PATH = "/db/penis_stats.sqlite3"
|
||||||
|
CHAT_HISTORY_DB_PATH = "/db/chat_history.sqlite3"
|
||||||
PENIS_START_LENGTH = 10.0
|
PENIS_START_LENGTH = 10.0
|
||||||
PENIS_MIN_DELTA = 0.1
|
PENIS_MIN_DELTA = 0.1
|
||||||
PENIS_MAX_DELTA = 7.0
|
PENIS_MAX_DELTA = 7.0
|
||||||
|
|
@ -79,7 +80,7 @@ KEYWORDS = {'крип', 'радиохэд', 'creep', 'radiohead'}
|
||||||
# --- Авто-ответы ИИ ---
|
# --- Авто-ответы ИИ ---
|
||||||
AUTOREPLY_CHANCE = 0.10
|
AUTOREPLY_CHANCE = 0.10
|
||||||
AUTOREPLY_COOLDOWN = 120
|
AUTOREPLY_COOLDOWN = 120
|
||||||
AUTOREPLY_TRIGGERS = {'бот', 'фраер', 'кент', 'братуха', 'пацаны', 'чё как', 'базар'}
|
AUTOREPLY_TRIGGERS = {'бот', 'фраер', 'кент', 'братуха', 'пацаны', 'чё как', 'базар', 'ебать', 'пизда', 'хуй', 'бля', 'сука', 'говно', 'пидор', 'пиздабол', 'пиздатый', 'пиздатая', 'пиздатое', 'пиздатые', 'пиздато', 'пиздато', 'пиздато', 'пиздато', 'ваномас'}
|
||||||
|
|
||||||
def get_hint_text(templates_str: str) -> str:
|
def get_hint_text(templates_str: str) -> str:
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
12
main.py
12
main.py
|
|
@ -18,7 +18,7 @@ from aiogram.types import (
|
||||||
from aiogram.client.default import DefaultBotProperties
|
from aiogram.client.default import DefaultBotProperties
|
||||||
|
|
||||||
# Локальные импорты
|
# Локальные импорты
|
||||||
from AI.talk_handler import handle_talk, generate_autoreply
|
from AI.talk_handler import handle_talk, generate_autoreply, push_message
|
||||||
from zparser import get_military_data
|
from zparser import get_military_data
|
||||||
import config
|
import config
|
||||||
|
|
||||||
|
|
@ -569,6 +569,8 @@ async def handle_keywords(message: Message):
|
||||||
if not message.text or message.text.startswith('/'):
|
if not message.text or message.text.startswith('/'):
|
||||||
return
|
return
|
||||||
text_lower = message.text.lower()
|
text_lower = message.text.lower()
|
||||||
|
chat_id = message.chat.id
|
||||||
|
user_name = (message.from_user.first_name or "кент") if message.from_user else "кент"
|
||||||
|
|
||||||
if any(re.search(rf'\b{word}\b', text_lower) for word in config.KEYWORDS):
|
if any(re.search(rf'\b{word}\b', text_lower) for word in config.KEYWORDS):
|
||||||
audio_path = config.BASE_DIR / "maket" / "Radiohead - Creep.mp3"
|
audio_path = config.BASE_DIR / "maket" / "Radiohead - Creep.mp3"
|
||||||
|
|
@ -579,6 +581,9 @@ async def handle_keywords(message: Message):
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Записываем каждое обычное сообщение в историю чата
|
||||||
|
await asyncio.to_thread(push_message, chat_id, "user", user_name, message.text)
|
||||||
|
|
||||||
import time as _time
|
import time as _time
|
||||||
now = _time.time()
|
now = _time.time()
|
||||||
triggered = any(trigger in text_lower for trigger in config.AUTOREPLY_TRIGGERS)
|
triggered = any(trigger in text_lower for trigger in config.AUTOREPLY_TRIGGERS)
|
||||||
|
|
@ -590,9 +595,10 @@ async def handle_keywords(message: Message):
|
||||||
|
|
||||||
if should_reply:
|
if should_reply:
|
||||||
try:
|
try:
|
||||||
await message.bot.send_chat_action(chat_id=message.chat.id, action="typing")
|
await message.bot.send_chat_action(chat_id=chat_id, action="typing")
|
||||||
response = await generate_autoreply(message.text)
|
response = await generate_autoreply(chat_id, message.text, user_name)
|
||||||
if response:
|
if response:
|
||||||
|
await asyncio.to_thread(push_message, chat_id, "assistant", "бот", response)
|
||||||
await message.reply(response, parse_mode=None)
|
await message.reply(response, parse_mode=None)
|
||||||
_last_autoreply_ts = now
|
_last_autoreply_ts = now
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue