nahj_rag/main_bale_bot.py
2026-02-26 14:00:04 +00:00

314 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
# from fastapi import FastAPI, Request
from pydantic import BaseModel
import requests
import logging
# import uvicorn
import time
import nahj_engine as nahj_chat
import data_model as dm
# ===========================
# پیکربندی اولیه
# ===========================
TOKEN = "602738113:OcVhjcsXqvE6D9FUytdoMZ096DPKYIUwnrk"
API_URL = f"https://tapi.bale.ai/bot{TOKEN}/"
# راه‌اندازی لاگر
logging.basicConfig(
filename="./bale_bot/bot.log",
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
# ===========================
# define import model class
# ===========================
class Message(BaseModel):
chat: dict
text: str | None = None
class Update(BaseModel):
message: Message | None = None
# ===========================
# کلاس اصلی ربات بله
# ===========================
class BaleBot:
def __init__(self, token: str):
# self.api_url = f"https://api.bale.ai/bot{token}/"
self.api_url = API_URL
self.user_states = {}
async def get_updates(self, offset = None):
params = {"timeout": 20}
if offset:
params["offset"] = offset
# print("get_updates function")
resp = requests.get(f"{self.api_url}getUpdates", params=params)
return resp.json().get("result", [])
async def send_message(self, chat_id: int, text: str, keyboard=None):
payload = {
"chat_id": chat_id,
"text": text,
"parse_mode": "HTML"
}
if keyboard:
payload["reply_markup"] = keyboard
try:
response = requests.post(self.api_url + "sendMessage", json=payload)
return response.json()
except Exception as e:
logging.error(f"Error sending message: {e}")
return None
async def get_latest_req_id(self):
latest_request = dm.get_last_request()
latest_req_id = latest_request['update_id']
if not latest_req_id:
latest_req_id = 0
return latest_req_id + 1
async def save_entery(self, update_item):
is_active = True
answer = ''
message = update_item['message']
fromm = message['from']
chat = message['chat']
username, first_name, last_name = '','',''
if 'username' in fromm:
username = fromm['username']
if 'first_name' in fromm:
first_name = fromm['first_name']
if 'last_name' in fromm:
last_name = fromm['last_name']
try:
dm.insert_request(update_item['update_id'],username,message['text'], answer, message['message_id'],fromm['id'],fromm['is_bot'],message['date'],chat['id'],chat['type'],first_name,last_name, is_active)
except:
return update_item['update_id']
return update_item['update_id']
async def update_request(self, update_id, answer):
dm.update_request(update_id= update_id, answer= answer)
async def split_text_into_chunks(self, text, max_length=4000):
"""
تقسیم یک متن به چانک‌های حداکثر max_length کاراکتری، بدون خراب کردن معنا با رعایت انتهای جمله‌ها.
:param text: متن ورودی
:param max_length: حداکثر طول هر چانک (پیش‌فرض 4000)
:return: لیستی از چانک‌های متن
"""
chunks = [] # لیستی برای ذخیره چانک‌ها
start = 0 # شروع متن برای هر چانک
while start < len(text):
# اگر متن باقی‌مانده کوتاه‌تر از max_length باشد، کل آن را اضافه کنید
if len(text) - start <= max_length:
chunks.append(text[start:])
break
# پیدا کردن نقطه پایانی چانک (حداکثر تا max_length کاراکتر جلو بروید)
end = start + max_length
# اگر در وسط یک جمله هستیم، به عقب برگردید تا انتهای جمله پیدا شود
while end > start and text[end - 1] not in '.!?':
end -= 1
# اگر هیچ انتهای جمله پیدا نشد، متن را تا max_length ببرید
if end == start:
end = start + max_length
# اضافه کردن چانک به لیست
chunks.append(text[start:end])
# شروع چانک بعدی
start = end
return chunks
async def save_chat_data(self,query, answer, first_name, username):
chat_data = f'''username: {username}\nfirstname: {first_name}\nquery: {query}\nanswer:{answer}\n+ + + + + + + + + + + + + + + + + + + + \n+ + + + + + + + + + + + + + + + + + + + \n\n'''
# # should write in DATABASE
with open('./bale_bot/chat-data.txt', 'a+', encoding='utf-8') as file:
file.write(chat_data)
async def handle_update(self, update_reqs: dict):
print(f"handle update ...")
data = update_reqs
if "message" not in data:
return
message = data["message"]
chat_id = message["chat"]["id"]
text = message.get("text", "").strip()
fromm = message['from']
# first_name = fromm['first_name']
# username = fromm['username']
logging.info(f"Received message from {chat_id}: {text}")
keyboard = {
"keyboard": [["جستجو","پرسش","پرسش عمیق"]],# ,"شبکه معنایی"
"resize_keyboard": True,
"one_time_keyboard": True
}
if text == "/start":
reply = "سلام، من دستیار هوشمند نهج‌البلاغه هستم. لطفا یکی از گزینه‌های زیر را انتخاب نمائید ..."
await self.send_message(chat_id, reply, keyboard)
return
elif text == "پرسش":
# حذف نوع درخواست قبلی کاربر
self.user_states.pop(chat_id, None)
# ایجاد وضعیت پرسش برای کاربر جاری
self.user_states[chat_id] = "simple_question"
reply = "لطفا متن «پرسش» را وارد نمائید ..."
await self.send_message(chat_id, reply, keyboard)
return
elif text == "جستجو":
# حذف نوع درخواست قبلی کاربر
self.user_states.pop(chat_id, None)
# ایجاد وضعیت جستجو برای کاربر جاری
self.user_states[chat_id] = "search"
reply = "لطفا متن موردنظر جهت «جستجو» را وارد نمائید ..."
await self.send_message(chat_id, reply, keyboard)
return
elif text == "شبکه معنایی":
# حذف نوع درخواست قبلی کاربر
self.user_states.pop(chat_id, None)
# ایجاد وضعیت شبکه معنایی برای کاربر جاری
self.user_states[chat_id] = "semantic-network"
reply = "لطفا کلمه موردنظر جهت ترسیم «شبکه معنایی» را وارد نمائید ..."
await self.send_message(chat_id, reply, keyboard)
return
elif text == "پرسش عمیق":
# حذف نوع درخواست قبلی کاربر
self.user_states.pop(chat_id, None)
# ایجاد وضعیت پرسش برای کاربر جاری
self.user_states[chat_id] = "deep_question"
reply = "لطفا متن «پرسش عمیق» را وارد نمائید ..."
await self.send_message(chat_id, reply, keyboard)
return
# elif text == "/help":
# reply = (
# "دستورهای موجود:\n"
# "/start - شروع ربات\n"
# "/chat - گفت‌گو با دستیار هوشمند نهج البلاغه\n"
# "/status - وضعیت ربات"
# )
# self.send_message(chat_id, reply)
elif text == "ربات":
reply = "ربات فعال است ✅"
await self.send_message(chat_id, reply, keyboard)
return
elif self.user_states.get(chat_id) == "semantic-network":
await self.send_message(chat_id, f"⏳ در حال ایجاد شبکه معنایی برای کلمه «{text}» ...")
reply = 'با عرض پوزش؛ این امکان، در حال حاضر در دسترس نیست'
elif self.user_states.get(chat_id) == "search":
await self.send_message(chat_id, f"⏳ در حال جستجو برای «{text}» ...")
answer = nahj_chat.bale_search(text)
if answer:
reply = answer
else:
reply = 'خطا در تولید پاسخ!'
elif self.user_states.get(chat_id) == "simple_question":
await self.send_message(chat_id, f"⏳ در حال آماده‌سازی پاسخ به «{text}» ...")
answer = nahj_chat.bale_chat(text)
if answer:
reply = answer
else:
reply = 'خطا در تولید پاسخ!'
elif self.user_states.get(chat_id) == "deep_question":
await self.send_message(chat_id, f"⏳ در حال آماده‌سازی پاسخ به «{text}» ...")
# answer = nahj_chat.bale_chat(text)
final_result = await nahj_chat.bale_complex_chat(text)
if final_result:
sub_questions = 'سوالات جزئی مرتبط با سوال کاربر:\n'
for i, q in enumerate(final_result.get('sub_qa'),1):
sub_questions += f'{i}. {q.get("question")}\n'
sub_qa_text = ''
for i, qa in enumerate(final_result.get('sub_qa'),1):
sub_qa_text += f'{i}. {qa.get("question")}\n{qa.get("answer")}\n\n'
# reply_content = f'''سوال اصلی: {text}\n\n{sub_questions}\n\n* * * * *سوالات جزئی:\n{sub_qa_text.strip()}\n\nپاسخ نهائی:\n{final_result.get('final_answer',0)}'''
reply_content = f'''{final_result.get('final_answer',0)}'''
reply = reply_content.strip()
else:
reply = 'خطا در تولید پاسخ!'
else:
reply = "لطفا یکی از گزینه‌های زیر را انتخاب نمائید"
await self.send_message(chat_id, reply, keyboard)
return
reply_len = len(reply.split())
print(f"len answer: {reply_len}")
print(f"ready for next ...")
print('+'*20)
print('+'*20)
reply_chuncs = []
reply_chuncs = await self.split_text_into_chunks(reply)
for i, paragraph in enumerate(reply_chuncs):
await self.send_message(chat_id, paragraph, keyboard)
# await self.save_chat_data(text, reply, first_name, username)
return reply
async def main():
print(f'!!! BALE-NAHJ-BOT IS READY !!!')
while True:
last_req_id = await bale_bot.get_latest_req_id()
# print(f"last req id: {last_req_id}")
update = await bale_bot.get_updates(last_req_id)
# print(f"reading bale-bot server. len update: {len(update)}")
if update:
print(f'{len(update)} requests recognized!')
for i, item in enumerate(update, 1):
print(f'handle input {i}/{len(update)}')
update_id = await bale_bot.save_entery(item)
answer = await bale_bot.handle_update(item)
if answer:
await bale_bot.update_request(update_id, answer)
time.sleep(1)
# ===========================
# ساخت اپلیکیشن FastAPI
# ===========================
# app = FastAPI()
bale_bot = BaleBot(TOKEN)
# ===========================
# (local execution)
# ===========================
if __name__ == "__main__":
import asyncio
result = asyncio.run(main())