h100 codes create git
This commit is contained in:
parent
a57d289e1c
commit
d379b23fef
0
.dockerignore
Normal file → Executable file
0
.dockerignore
Normal file → Executable file
155
bale_qabot.py
Normal file
155
bale_qabot.py
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
print(f'import bale madule ...')
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
from fastapi import FastAPI, Request
|
||||||
|
from pydantic import BaseModel
|
||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
import uvicorn
|
||||||
|
import chatbot_handler as ch
|
||||||
|
|
||||||
|
# ===========================
|
||||||
|
# پیکربندی اولیه
|
||||||
|
# ===========================
|
||||||
|
TOKEN = '2052165365:Tt7u2qXB9oRTPISeZ0wmoAGpPsIgjq-vAxM'
|
||||||
|
API_URL = f"https://tapi.bale.ai/bot{TOKEN}/"
|
||||||
|
|
||||||
|
# راهاندازی لاگر
|
||||||
|
logging.basicConfig(
|
||||||
|
filename="./baleqabot/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
|
||||||
|
|
||||||
|
async def get_updates(self, offset = None):
|
||||||
|
params = {"timeout": 20}
|
||||||
|
if offset:
|
||||||
|
params["offset"] = offset
|
||||||
|
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_req_id = 0
|
||||||
|
with open('./baleqabot/requests.json', 'r', encoding='utf-8') as file:
|
||||||
|
prev_reqs = json.load(file)
|
||||||
|
|
||||||
|
if prev_reqs:
|
||||||
|
latest_req_id = prev_reqs[-1]['update_id']
|
||||||
|
|
||||||
|
return latest_req_id + 1
|
||||||
|
|
||||||
|
async def save_entery(self, update):
|
||||||
|
all_reqs = []
|
||||||
|
with open('./baleqabot/requests.json', 'r', encoding='utf-8') as file:
|
||||||
|
prev_reqs = json.load(file)
|
||||||
|
all_reqs.extend(prev_reqs)
|
||||||
|
all_reqs.extend(update)
|
||||||
|
|
||||||
|
with open('./baleqabot/requests.json', 'w', encoding='utf-8') as file:
|
||||||
|
data = json.dumps(all_reqs, ensure_ascii=False, indent=2)
|
||||||
|
file.write(data)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def handle_update(self, update_reqs: dict):
|
||||||
|
data = update_reqs[0]
|
||||||
|
if "message" not in data:
|
||||||
|
return
|
||||||
|
message = data["message"]
|
||||||
|
chat_id = message["chat"]["id"]
|
||||||
|
text = message.get("text", "").strip()
|
||||||
|
|
||||||
|
logging.info(f"Received message from {chat_id}: {text}")
|
||||||
|
|
||||||
|
if text == "/start":
|
||||||
|
reply = "سلام، من دستیار هوشمند قوانین هستم. لطفا پرسش خود را وارد نمائید ..."
|
||||||
|
# keyboard = {
|
||||||
|
# "keyboard": [["/help", "/status"]],
|
||||||
|
# "resize_keyboard": True,
|
||||||
|
# "one_time_keyboard": True
|
||||||
|
# }
|
||||||
|
self.send_message(chat_id, reply)
|
||||||
|
|
||||||
|
elif text == "/chat":
|
||||||
|
reply = "لطفا متن پرسش از قوانین را وارد نمائید ..."
|
||||||
|
self.send_message(chat_id, reply)
|
||||||
|
|
||||||
|
# elif text == "/help":
|
||||||
|
# reply = (
|
||||||
|
# "دستورهای موجود:\n"
|
||||||
|
# "/start - شروع ربات\n"
|
||||||
|
# "/chat - گفتگو با دستیار هوشمند قانون\n"
|
||||||
|
# "/status - وضعیت ربات"
|
||||||
|
# )
|
||||||
|
# self.send_message(chat_id, reply)
|
||||||
|
|
||||||
|
elif text == "/status":
|
||||||
|
reply = "ربات فعال است ✅"
|
||||||
|
self.send_message(chat_id, reply)
|
||||||
|
|
||||||
|
else:
|
||||||
|
answer = await chat.run_chatbot(text, chat.create_chat_id())
|
||||||
|
if answer:
|
||||||
|
reply = answer
|
||||||
|
else:
|
||||||
|
reply = 'خطا در تولید پاسخ!'
|
||||||
|
|
||||||
|
self.send_message(chat_id, reply)
|
||||||
|
|
||||||
|
|
||||||
|
async def bale_main():
|
||||||
|
print(f'bale-qabot is Readey!!!!')
|
||||||
|
while True:
|
||||||
|
last_req_id = bale_bot.get_latest_req_id()
|
||||||
|
update = bale_bot.get_updates(last_req_id)
|
||||||
|
if update:
|
||||||
|
bale_bot.save_entery(update)
|
||||||
|
bale_bot.handle_update(update)
|
||||||
|
print('ok')
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================
|
||||||
|
# ساخت اپلیکیشن FastAPI
|
||||||
|
# ===========================
|
||||||
|
# app = FastAPI()
|
||||||
|
bale_bot = BaleBot(TOKEN)
|
||||||
|
result = asyncio.run(bale_main())
|
||||||
|
# ===========================
|
||||||
|
# (local execution)
|
||||||
|
# ===========================
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# uvicorn.run("chatbot:app", host="https://bale.tavasi.ir/", port=5000)
|
||||||
|
bale_main()
|
||||||
0
baleqabot/bot.log
Normal file
0
baleqabot/bot.log
Normal file
443
baleqabot/requests.json
Normal file
443
baleqabot/requests.json
Normal file
|
|
@ -0,0 +1,443 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"update_id": 1,
|
||||||
|
"message": {
|
||||||
|
"message_id": 1,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1761831331,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "/start",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"type": "bot_command",
|
||||||
|
"offset": 0,
|
||||||
|
"length": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 2,
|
||||||
|
"message": {
|
||||||
|
"message_id": 2,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1761833932,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "/start",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"type": "bot_command",
|
||||||
|
"offset": 0,
|
||||||
|
"length": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 3,
|
||||||
|
"message": {
|
||||||
|
"message_id": 3,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1761836593,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "/start",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"type": "bot_command",
|
||||||
|
"offset": 0,
|
||||||
|
"length": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 4,
|
||||||
|
"message": {
|
||||||
|
"message_id": 4,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1761836694,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "/start",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"type": "bot_command",
|
||||||
|
"offset": 0,
|
||||||
|
"length": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 5,
|
||||||
|
"message": {
|
||||||
|
"message_id": 5,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1761836899,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "/start",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"type": "bot_command",
|
||||||
|
"offset": 0,
|
||||||
|
"length": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 6,
|
||||||
|
"message": {
|
||||||
|
"message_id": 6,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1761836915,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "/start",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"type": "bot_command",
|
||||||
|
"offset": 0,
|
||||||
|
"length": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 7,
|
||||||
|
"message": {
|
||||||
|
"message_id": 7,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1761837001,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "/start",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"type": "bot_command",
|
||||||
|
"offset": 0,
|
||||||
|
"length": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 8,
|
||||||
|
"message": {
|
||||||
|
"message_id": 8,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1762099430,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "/start",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"type": "bot_command",
|
||||||
|
"offset": 0,
|
||||||
|
"length": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 9,
|
||||||
|
"message": {
|
||||||
|
"message_id": 9,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1762099450,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "/start",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"type": "bot_command",
|
||||||
|
"offset": 0,
|
||||||
|
"length": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 10,
|
||||||
|
"message": {
|
||||||
|
"message_id": 10,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1762100301,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "سلام"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 11,
|
||||||
|
"message": {
|
||||||
|
"message_id": 11,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1762100357,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "/start",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"type": "bot_command",
|
||||||
|
"offset": 0,
|
||||||
|
"length": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 12,
|
||||||
|
"message": {
|
||||||
|
"message_id": 12,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1762100360,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "سلام"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 13,
|
||||||
|
"message": {
|
||||||
|
"message_id": 13,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1762100364,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "رلیردت"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 14,
|
||||||
|
"message": {
|
||||||
|
"message_id": 14,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1762179038,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "/start",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"type": "bot_command",
|
||||||
|
"offset": 0,
|
||||||
|
"length": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 26,
|
||||||
|
"message": {
|
||||||
|
"message_id": 95,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1762181681,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "تست"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 27,
|
||||||
|
"message": {
|
||||||
|
"message_id": 97,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1762182073,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "/start",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"type": "bot_command",
|
||||||
|
"offset": 0,
|
||||||
|
"length": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update_id": 28,
|
||||||
|
"message": {
|
||||||
|
"message_id": 99,
|
||||||
|
"from": {
|
||||||
|
"id": 899452608,
|
||||||
|
"is_bot": false,
|
||||||
|
"first_name": "جوکار",
|
||||||
|
"last_name": "",
|
||||||
|
"username": "nasle_sevvom"
|
||||||
|
},
|
||||||
|
"date": 1762182086,
|
||||||
|
"chat": {
|
||||||
|
"id": 899452608,
|
||||||
|
"type": "private",
|
||||||
|
"username": "nasle_sevvom",
|
||||||
|
"first_name": "جوکار"
|
||||||
|
},
|
||||||
|
"text": "سلام"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
5
build.bash
Executable file
5
build.bash
Executable file
|
|
@ -0,0 +1,5 @@
|
||||||
|
docker build -t docker.tavasi.ir/tavasi/qachat_base:1.0.0 -f dockerfile_base .
|
||||||
|
docker build -t docker.tavasi.ir/tavasi/qachat:1.0.0 .
|
||||||
|
|
||||||
|
sudo docker build -t docker.tavasi.ir/tavasi/qachat_base:1.0.0 -f dockerfile_base .
|
||||||
|
sudo docker build -t docker.tavasi.ir/tavasi/qachat:1.0.0 .
|
||||||
446
chatbot.py
Normal file → Executable file
446
chatbot.py
Normal file → Executable file
|
|
@ -1,4 +1,6 @@
|
||||||
import json
|
import json
|
||||||
|
import chatbot_handler as chat
|
||||||
|
# import bale_qabot
|
||||||
import os
|
import os
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
|
|
@ -11,425 +13,53 @@ from sklearn.metrics.pairwise import cosine_similarity
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
import random
|
import random
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from embedder_sbert_qavanin_285k import PersianVectorAnalyzer
|
from embedder_sbert_qavanin_285k import PersianVectorAnalyzer
|
||||||
from normalizer import cleaning
|
#from normalizer import cleaning
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI ,Header
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
# LLM Libs
|
# LLM Libs
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
from langchain_openai import ChatOpenAI # pip install -U langchain_openai
|
from langchain_openai import ChatOpenAI # pip install -U langchain_openai
|
||||||
import requests
|
import requests
|
||||||
|
from FlagEmbedding import FlagReranker # deldar-reranker-v2
|
||||||
today = f'{datetime.datetime.now().year}{datetime.datetime.now().month}{datetime.datetime.now().day}'
|
import aiofiles
|
||||||
|
|
||||||
chatbot = FastAPI()
|
chatbot = FastAPI()
|
||||||
|
origins = ["*"]
|
||||||
|
|
||||||
# -------------------
|
chatbot.add_middleware(
|
||||||
# مدلها و مسیر داده
|
CORSMiddleware,
|
||||||
# -------------------
|
allow_origins=origins,
|
||||||
EMBED_MODEL = "/home/sabr/MODLES/rag_chat/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
|
allow_credentials=True,
|
||||||
RERANKER_MODEL = "/home/sabr/MODLES/rag_chat/BAAI/bge-reranker-v2-m3"
|
allow_methods=["*"],
|
||||||
FAISS_INDEX_PATH = "./qavanin-faiss/faiss_index_qavanin_285k.index"
|
allow_headers=["*"],
|
||||||
FAISS_METADATA_PATH = "./qavanin-faiss/faiss_index_qavanin_285k_metadata.json"
|
)
|
||||||
|
|
||||||
RERANK_BATCH = int(os.environ.get("RERANK_BATCH", 256))
|
print('#'*19)
|
||||||
# print(f'RERANK_BATCH: {RERANK_BATCH}')
|
print('-Chatbot is Ready-')
|
||||||
|
print('#'*19)
|
||||||
|
|
||||||
def get_key():
|
|
||||||
key = 'aa-fdh9d847ANcBxQCBTZD5hrrAdl0UrPEnJOScYmOncrkagYPf'
|
|
||||||
return key
|
|
||||||
|
|
||||||
def load_faiss_index(index_path: str, metadata_path: str):
|
|
||||||
"""بارگذاری ایندکس FAISS و متادیتا (لیست جملات + عناوین)."""
|
|
||||||
index = faiss.read_index(index_path)
|
|
||||||
|
|
||||||
with open(metadata_path, "r", encoding="utf-8") as f:
|
|
||||||
metadata = json.load(f)
|
|
||||||
|
|
||||||
content_list, ids, prefix_list = [], [], []
|
|
||||||
for item in metadata:
|
|
||||||
content_list.append(item["content"])
|
|
||||||
ids.append(item["id"])
|
|
||||||
prefix_list.append(item["prefix"])
|
|
||||||
|
|
||||||
return content_list, ids, prefix_list, index
|
|
||||||
|
|
||||||
def get_client():
|
|
||||||
url = "https://api.avalai.ir/v1"
|
|
||||||
# key = 'aa-4tvAEazUBovEN1i7i7tdl1PR93OaWXs6hMflR4oQbIIA4K7Z'
|
|
||||||
|
|
||||||
|
|
||||||
client = OpenAI(
|
|
||||||
api_key= get_key(), # با کلید واقعی خود جایگزین کنید
|
|
||||||
base_url= url, # آدرس پایه
|
|
||||||
)
|
|
||||||
|
|
||||||
return client
|
|
||||||
|
|
||||||
def llm_request(query, model):
|
|
||||||
|
|
||||||
if query == '':
|
|
||||||
return 'لطفا متن سوال را وارد نمائید'
|
|
||||||
|
|
||||||
client = get_client()
|
|
||||||
determine_refrence = """شناسه هر ماده قانون در ابتدای آن و با فرمت "id: {idvalue}" آمده است که id-value همان شناسه ماده است. بازای هربخش از پاسخی که تولید می شود، ضروری است شناسه ماده ای که در تدوین پاسخ از آن استفاده شده در انتهای پاراگراف یا جمله مربوطه با فرمت {idvalue} اضافه شود. همیشه idvalue با رشته "qs" شروع می شود"""
|
|
||||||
try:
|
|
||||||
messages.append({"role": "user", "content": query})
|
|
||||||
messages.append({"role": "user", "content": determine_refrence})
|
|
||||||
response = client.chat.completions.create(
|
|
||||||
messages = messages,
|
|
||||||
model= model) # "gpt-4o", "gpt-4o-mini", "deepseek-chat" , "gemini-2.0-flash", gemini-2.5-flash-lite
|
|
||||||
# gpt-4o : 500
|
|
||||||
# gpt-4o-mini : 34
|
|
||||||
# deepseek-chat: : 150
|
|
||||||
# gemini-2.0-flash : error
|
|
||||||
# cf.gemma-3-12b-it : 1
|
|
||||||
# gemini-2.5-flash-lite : 35 خیلی خوب
|
|
||||||
|
|
||||||
answer = response.choices[0].message.content
|
|
||||||
# پاسخ را هم به سابقه اضافه میکنیم
|
|
||||||
messages.append({"role": "assistant", "content": answer})
|
|
||||||
|
|
||||||
|
|
||||||
except Exception as error:
|
|
||||||
with open('./llm-answer/error-in-llm.txt', mode='a+', encoding='utf-8') as file:
|
|
||||||
error_message = f'\n\nquery: {query.strip()}\nerror:{error} \n-------------------------------\n'
|
|
||||||
file.write(error_message)
|
|
||||||
|
|
||||||
return 'با عرض پوزش؛ متاسفانه خطایی رخ داده است. لطفا لحظاتی دیگر دوباره تلاش نمائید'
|
|
||||||
|
|
||||||
return answer
|
|
||||||
|
|
||||||
class HybridRetrieverReranker:
|
|
||||||
__slots__ = (
|
|
||||||
"device", "content_list", "ids", "prefix_list", "N", "embedder", "faiss_index",
|
|
||||||
"vectorizer", "tfidf_matrix", "tokenizer", "reranker", "dense_alpha"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, content_list: List[str],ids: List[str], prefix_list: List[str], faiss_index,
|
|
||||||
dense_alpha: float = 0.6, device: str = None):
|
|
||||||
|
|
||||||
if device is None:
|
|
||||||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
||||||
self.device = device
|
|
||||||
|
|
||||||
self.content_list = content_list
|
|
||||||
self.ids = ids
|
|
||||||
self.prefix_list = prefix_list
|
|
||||||
self.faiss_index = faiss_index
|
|
||||||
self.N = len(content_list)
|
|
||||||
|
|
||||||
# Dense
|
|
||||||
self.embedder = SentenceTransformer(EMBED_MODEL, device=self.device)
|
|
||||||
|
|
||||||
# Sparse (مثل قبل برای حفظ خروجی)
|
|
||||||
self.vectorizer = TfidfVectorizer(
|
|
||||||
analyzer="word",
|
|
||||||
ngram_range=(1, 2),
|
|
||||||
token_pattern=r"(?u)\b[\w\u0600-\u06FF]{2,}\b",
|
|
||||||
)
|
|
||||||
self.tfidf_matrix = self.vectorizer.fit_transform(self.content_list)
|
|
||||||
|
|
||||||
# Reranker
|
|
||||||
self.tokenizer = AutoTokenizer.from_pretrained(RERANKER_MODEL, use_fast=True)
|
|
||||||
self.reranker = AutoModelForSequenceClassification.from_pretrained(
|
|
||||||
RERANKER_MODEL
|
|
||||||
).to(self.device)
|
|
||||||
# self.reranker = AutoModelForSeq2SeqLM.from_pretrained(RERANKER_MODEL).to(self.device)
|
|
||||||
# self.reranker.eval()
|
|
||||||
|
|
||||||
self.dense_alpha = float(dense_alpha)
|
|
||||||
|
|
||||||
# --- Dense (FAISS) ---
|
|
||||||
def dense_retrieve(self, query: str, top_k: int):
|
|
||||||
if top_k <= 0:
|
|
||||||
return [], np.array([], dtype=np.float32)
|
|
||||||
|
|
||||||
q_emb = self.embedder.encode(query, convert_to_numpy=True).astype(np.float32)
|
|
||||||
D, I = self.faiss_index.search(np.expand_dims(q_emb, axis=0), top_k)
|
|
||||||
|
|
||||||
return I[0].tolist(), D[0]
|
|
||||||
|
|
||||||
# --- Sparse ---
|
|
||||||
def sparse_retrieve(self, query: str, top_k: int):
|
|
||||||
if top_k <= 0:
|
|
||||||
return [], np.array([], dtype=np.float32)
|
|
||||||
k = min(top_k, self.N)
|
|
||||||
q_vec = self.vectorizer.transform([query])
|
|
||||||
sims = cosine_similarity(q_vec, self.tfidf_matrix).ravel()
|
|
||||||
idx = np.argpartition(-sims, kth=k-1)[:k]
|
|
||||||
idx = idx[np.argsort(-sims[idx], kind="mergesort")]
|
|
||||||
return idx.tolist(), sims[idx]
|
|
||||||
|
|
||||||
# --- Utils ---
|
|
||||||
@staticmethod
|
|
||||||
def _minmax_norm(arr: np.ndarray) -> np.ndarray:
|
|
||||||
if arr.size == 0:
|
|
||||||
return arr
|
|
||||||
a_min = arr.min()
|
|
||||||
a_max = arr.max()
|
|
||||||
rng = a_max - a_min
|
|
||||||
if rng < 1e-12:
|
|
||||||
return np.zeros_like(arr)
|
|
||||||
return (arr - a_min) / rng
|
|
||||||
|
|
||||||
def fuse(self, d_idx, d_scores, s_idx, s_scores, top_k=50, k_rrf=60):
|
|
||||||
"""
|
|
||||||
ادغام نتایج دو retriever (dense و sparse) با استفاده از Reciprocal Rank Fusion (RRF)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
d_idx (list or np.ndarray): ایندکسهای نتایج dense retriever
|
|
||||||
d_scores (list or np.ndarray): نمرات dense retriever
|
|
||||||
s_idx (list or np.ndarray): ایندکسهای نتایج sparse retriever
|
|
||||||
s_scores (list or np.ndarray): نمرات sparse retriever
|
|
||||||
top_k (int): تعداد نتایج نهایی
|
|
||||||
k_rrf (int): ثابت در فرمول RRF برای کاهش تأثیر رتبههای پایینتر
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list: لیست ایندکسهای ادغامشده به ترتیب نمره
|
|
||||||
"""
|
|
||||||
combined = {}
|
|
||||||
|
|
||||||
# dense retriever
|
|
||||||
for rank, idx in enumerate(d_idx):
|
|
||||||
score = 1.0 / (k_rrf + rank)
|
|
||||||
combined[idx] = combined.get(idx, 0) + score
|
|
||||||
|
|
||||||
# sparse retriever
|
|
||||||
for rank, idx in enumerate(s_idx):
|
|
||||||
score = 1.0 / (k_rrf + rank)
|
|
||||||
combined[idx] = combined.get(idx, 0) + score
|
|
||||||
|
|
||||||
# مرتبسازی نهایی
|
|
||||||
sorted_items = sorted(combined.items(), key=lambda x: x[1], reverse=True)
|
|
||||||
cand_idx = [item[0] for item in sorted_items[:top_k]]
|
|
||||||
|
|
||||||
return cand_idx
|
|
||||||
|
|
||||||
def rerank(self, query: str, candidate_indices: List[int], passages: List[str], final_k: int) -> List[Tuple[int, float]]:
|
|
||||||
"""
|
|
||||||
Rerank candidate passages using a cross-encoder (e.g., MonoT5, MiniLM, etc.).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
query (str): پرسش کاربر
|
|
||||||
candidate_indices (List[int]): ایندکسهای کاندیدا (از retriever)
|
|
||||||
passages (List[str]): کل جملات/پاراگرافها
|
|
||||||
final_k (int): تعداد نتایج نهایی
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[Tuple[int, float]]: لیستی از (ایندکس، امتیاز) برای بهترین نتایج
|
|
||||||
"""
|
|
||||||
if final_k <= 0 or not candidate_indices:
|
|
||||||
return []
|
|
||||||
|
|
||||||
# آمادهسازی جفتهای (query, passage)
|
|
||||||
texts = [query] * len(candidate_indices)
|
|
||||||
pairs = passages
|
|
||||||
|
|
||||||
scores: List[float] = []
|
|
||||||
|
|
||||||
def _iter_batches(max_bs: int):
|
|
||||||
bs = max_bs
|
|
||||||
while bs >= 16: # حداقل batch_size
|
|
||||||
try:
|
|
||||||
with torch.inference_mode():
|
|
||||||
for start in range(0, len(pairs), bs):
|
|
||||||
batch_texts = texts[start:start + bs]
|
|
||||||
batch_pairs = pairs[start:start + bs]
|
|
||||||
inputs = self.tokenizer(
|
|
||||||
batch_texts,
|
|
||||||
batch_pairs,
|
|
||||||
padding=True,
|
|
||||||
truncation=True,
|
|
||||||
max_length=512,
|
|
||||||
return_tensors="pt",
|
|
||||||
).to(self.device)
|
|
||||||
|
|
||||||
logits = self.reranker(**inputs).logits.view(-1)
|
|
||||||
scores.extend(logits.detach().cpu().tolist())
|
|
||||||
return True
|
|
||||||
except torch.cuda.OutOfMemoryError:
|
|
||||||
if torch.cuda.is_available():
|
|
||||||
torch.cuda.empty_cache()
|
|
||||||
bs //= 2
|
|
||||||
return False
|
|
||||||
|
|
||||||
# اجرای reranking
|
|
||||||
success = _iter_batches(max_bs=64)
|
|
||||||
if not success:
|
|
||||||
raise RuntimeError("Reranker failed due to CUDA OOM, even with small batch size.")
|
|
||||||
|
|
||||||
# مرتبسازی نتایج بر اساس نمره
|
|
||||||
reranked = sorted(
|
|
||||||
zip(candidate_indices, scores),
|
|
||||||
key=lambda x: x[1],
|
|
||||||
reverse=True
|
|
||||||
)[:final_k]
|
|
||||||
|
|
||||||
return reranked
|
|
||||||
|
|
||||||
def get_passages(self, cand_idx, content_list):
|
|
||||||
passages = []
|
|
||||||
for idx in cand_idx:
|
|
||||||
passages.append(content_list[idx])
|
|
||||||
|
|
||||||
return passages
|
|
||||||
|
|
||||||
# --- Search (بدون تغییر) ---
|
|
||||||
def search(self, query: str, content_list, topk_dense=50, topk_sparse=50,
|
|
||||||
pre_rerank_k=50, final_k=10):
|
|
||||||
d_idx, d_scores = self.dense_retrieve(query, topk_dense)
|
|
||||||
s_idx, s_scores = self.sparse_retrieve(query, topk_sparse)
|
|
||||||
cand_idx = self.fuse(d_idx, d_scores, s_idx, s_scores, pre_rerank_k)
|
|
||||||
passages = self.get_passages(cand_idx, content_list)
|
|
||||||
reranked = self.rerank(query, cand_idx, passages, final_k)
|
|
||||||
|
|
||||||
return [{"idx": i, "content": self.content_list[i],"prefix": self.prefix_list[i], "rerank_score": score}
|
|
||||||
for i, score in reranked]
|
|
||||||
|
|
||||||
def single_query(query: str):
|
|
||||||
|
|
||||||
query = cleaning(query)
|
|
||||||
|
|
||||||
retrived_sections = pipe.search(query, content_list, topk_dense=30, topk_sparse=30, pre_rerank_k=30, final_k=10)
|
|
||||||
final_similars = ''
|
|
||||||
for i, row in enumerate(retrived_sections, 1):
|
|
||||||
id_value = '{' + str(ids[row['idx']]) + '}'
|
|
||||||
result = f"id: {id_value} \n{row['prefix']} {row['content']}\n"
|
|
||||||
final_similars += ''.join(result)
|
|
||||||
|
|
||||||
return final_similars, retrived_sections
|
|
||||||
|
|
||||||
def find_refrences(llm_answer: str) -> List[str]:
|
|
||||||
"""
|
|
||||||
شناسایی شناسه هایی که مدل زبانی، برای تهیه پاسخ از آنها استفاده کرده است
|
|
||||||
|
|
||||||
Args:
|
|
||||||
llm_answer(str): متنی که مدل زبانی تولید کرده است
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
refrence_ids(List[str]): لیستی از شناسه های تشخیص داده شده
|
|
||||||
"""
|
|
||||||
pattern = r"\{[^\}]+\}"
|
|
||||||
refrence_ids = re.findall(pattern, llm_answer)
|
|
||||||
|
|
||||||
return refrence_ids
|
|
||||||
|
|
||||||
def replace_refrences(llm_answer: str, refrences_list:List[str]) -> List[str]:
|
|
||||||
"""
|
|
||||||
شناسایی شناسه هایی که مدل زبانی، برای تهیه پاسخ از آنها استفاده کرده است
|
|
||||||
|
|
||||||
Args:
|
|
||||||
llm_answer(str): متنی که مدل زبانی تولید کرده است
|
|
||||||
refrences_list(List[str]): لیست شناسه ماده های مورد استفاده در پاسخ مدل زبانی
|
|
||||||
Returns:
|
|
||||||
llm_answer(str), : متن بازسازی شده پاسخ مدل زبانی که شناسه ماده های مورد استفاده در آن، اصلاح شده است
|
|
||||||
"""
|
|
||||||
refrences = ''
|
|
||||||
for index, ref in enumerate(refrences_list,1):
|
|
||||||
# breakpoint()
|
|
||||||
llm_answer = llm_answer.replace(ref, f'[{index}]')
|
|
||||||
id = ref.lstrip('{')
|
|
||||||
id = id.rstrip('}')
|
|
||||||
refrences += ''.join(f'[{index}] https://majles.tavasi.ir/entity/detail/view/qsection/{id}\n')
|
|
||||||
|
|
||||||
llm_answer = f'{llm_answer}\n\nمنابع پاسخ:\n{refrences.strip()}'
|
|
||||||
return llm_answer
|
|
||||||
|
|
||||||
# load basic items
|
|
||||||
content_list, ids, prefix_list, faiss_index = load_faiss_index(FAISS_INDEX_PATH, FAISS_METADATA_PATH)
|
|
||||||
pipe = HybridRetrieverReranker(content_list, ids, prefix_list, faiss_index, dense_alpha=0.6)
|
|
||||||
# query preprocess and normalize
|
|
||||||
normalizer_obj = PersianVectorAnalyzer()
|
|
||||||
|
|
||||||
messages = [
|
|
||||||
{"role": "system", "content": "تو یک دستیار خبره در زمینه حقوق و قوانین مرتبط به آن هستی و می توانی متون حقوقی را به صورت دقیق توضیح بدهی . پاسخ ها باید الزاما به زبان فارسی باشد. پاسخ ها فقط از متون قانونی که در پرامپت وجود دارد استخراج شود."},
|
|
||||||
]
|
|
||||||
|
|
||||||
def run_chatbot(query:str, chat_id:str):
|
|
||||||
|
|
||||||
if query == '':
|
|
||||||
return 'لطفا متن سوال را وارد نمائید'
|
|
||||||
|
|
||||||
start_time = (datetime.datetime.now())
|
|
||||||
|
|
||||||
result_passages_text, result_passages_ids = single_query(query)
|
|
||||||
end_retrive = datetime.datetime.now()
|
|
||||||
print('-'*40)
|
|
||||||
retrive_duration = (end_retrive - start_time).total_seconds()
|
|
||||||
print(f'retrive duration: {str(retrive_duration)}')
|
|
||||||
|
|
||||||
prompt = f'برای پرسش "{query}" از میان مواد قانونی "{result_passages_text}" .پاسخ مناسب و دقیق را استخراج کن. درصورتی که مطلبی مرتبط با پرسش در متن پیدا نشد، فقط پاسخ بده: "متاسفانه در منابع، پاسخی پیدا نشد!"'
|
|
||||||
try:
|
|
||||||
model = "gemini-2.5-flash-lite"
|
|
||||||
llm_answer = llm_request(prompt, model)
|
|
||||||
except Exception as error:
|
|
||||||
model = "gpt-4o-mini"
|
|
||||||
llm_answer = llm_request(prompt, model)
|
|
||||||
|
|
||||||
llm_answer_duration = (datetime.datetime.now() - end_retrive).total_seconds()
|
|
||||||
print(f'llm answer duration: {str(llm_answer_duration)}')
|
|
||||||
|
|
||||||
used_refrences_in_answer = find_refrences(llm_answer)
|
|
||||||
llm_answer = replace_refrences(llm_answer, used_refrences_in_answer)
|
|
||||||
|
|
||||||
full_prompt_duration = (datetime.datetime.now() - start_time).total_seconds()
|
|
||||||
print(f'full prompt duration: {full_prompt_duration}')
|
|
||||||
print('~'*40)
|
|
||||||
|
|
||||||
chat_obj = {
|
|
||||||
'chat-id' : chat_id, # str
|
|
||||||
'chat-title' : '', # str
|
|
||||||
'user-id' : '',
|
|
||||||
'user-query' : query, # str
|
|
||||||
'model' : model, # str
|
|
||||||
'result-passages' : result_passages_text, # str
|
|
||||||
'retrived-passages-ids' : result_passages_ids, # list[obj]
|
|
||||||
'retrive-duration' : retrive_duration, # str
|
|
||||||
'llm-answer-duration' : llm_answer_duration, # str
|
|
||||||
'full-prompt-duration' : full_prompt_duration, # str
|
|
||||||
'chat-date' : str(start_time), # str
|
|
||||||
'used-refrences-in-answer' : used_refrences_in_answer, # list[str]
|
|
||||||
'llm-answer' : llm_answer, # str
|
|
||||||
}
|
|
||||||
# prev_chat_data = []
|
|
||||||
# with open('./llm-answer/chat-messages.json', mode='r', encoding='utf-8') as file:
|
|
||||||
# prev_chat_data = json.load(file)
|
|
||||||
# prev_chat_data.append(chat_obj)
|
|
||||||
|
|
||||||
# with open('./llm-answer/chat-messages.json', mode='w', encoding='utf-8') as output:
|
|
||||||
# json.dump(prev_chat_data, output, ensure_ascii=False, indent=2)
|
|
||||||
|
|
||||||
return chat_obj
|
|
||||||
|
|
||||||
@chatbot.post("/credit_refresh")
|
|
||||||
def credit_refresh():
|
|
||||||
url = "https://api.avalai.ir/user/credit"
|
|
||||||
headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Authorization": f"Bearer {get_key()}"
|
|
||||||
}
|
|
||||||
remained_credit = requests.get(url, headers=headers)
|
|
||||||
|
|
||||||
with open('./llm-answer/credit.txt','w') as file:
|
|
||||||
file.write(str(remained_credit.json()['remaining_irt']))
|
|
||||||
return str(remained_credit.json()['remaining_irt'])
|
|
||||||
# تعریف مدل دادهها برای درخواستهای API
|
# تعریف مدل دادهها برای درخواستهای API
|
||||||
class Query(BaseModel):
|
class Query(BaseModel):
|
||||||
query: str
|
query: str
|
||||||
|
|
||||||
date = str((datetime.datetime.now())).replace(' ','-').replace(':','').replace('.','-')
|
|
||||||
chat_id = f'{date}-{random.randint(100000, 999999)}'
|
|
||||||
print('#'*19)
|
|
||||||
print('-Chatbot is Ready!-')
|
|
||||||
print('#'*19)
|
|
||||||
# مسیر API برای اجرا کردن run_chatbot
|
# مسیر API برای اجرا کردن run_chatbot
|
||||||
@chatbot.post("/run_chatbot")
|
@chatbot.get("/")
|
||||||
def chat(query: Query):
|
async def simple():
|
||||||
|
return "ai rag caht qanon OK"
|
||||||
|
|
||||||
answer = run_chatbot(query.query, chat_id)
|
@chatbot.get("/ping")
|
||||||
credit_refresh()
|
async def ping():
|
||||||
|
return "ai rag caht qanon OK"
|
||||||
|
|
||||||
|
|
||||||
|
@chatbot.post("/run_chat")
|
||||||
|
async def run_chat(query: Query):
|
||||||
|
print('generate answer ...')
|
||||||
|
chat_id = await chat.create_chat_id()
|
||||||
|
answer = await chat.run_chatbot(query.query, chat_id)
|
||||||
|
await chat.credit_refresh()
|
||||||
|
|
||||||
return {"answer": answer}
|
return {"answer": answer}
|
||||||
|
|
||||||
# uvicorn src.app:app --reload
|
# uvicorn src.app:app --reload
|
||||||
|
|
@ -444,20 +74,20 @@ if __name__ == "__main__":
|
||||||
continue
|
continue
|
||||||
start = (datetime.datetime.now())
|
start = (datetime.datetime.now())
|
||||||
# result = test_dataset()
|
# result = test_dataset()
|
||||||
result = single_query(query)
|
result = chat.single_query(query)
|
||||||
end_retrive = datetime.datetime.now()
|
end_retrive = datetime.datetime.now()
|
||||||
print('-'*40)
|
print('-'*40)
|
||||||
print(f'retrive duration: {(end_retrive - start).total_seconds()}')
|
print(f'retrive duration: {(end_retrive - start).total_seconds()}')
|
||||||
|
|
||||||
prompt = f'برای پرسش "{query}" از میان مواد قانونی "{result}" .پاسخ مناسب و دقیق را استخراج کن. درصورتی که مطلبی مرتبط با پرسش در متن پیدا نشد، فقط پاسخ بده: "متاسفانه در منابع، پاسخی پیدا نشد!"'
|
prompt = f'برای پرسش "{query}" از میان مواد قانونی "{result}" .پاسخ مناسب و دقیق را استخراج کن. درصورتی که مطلبی مرتبط با پرسش در متن پیدا نشد، فقط پاسخ بده: "متاسفانه در منابع، پاسخی پیدا نشد!"'
|
||||||
llm_answer = llm_request(prompt)
|
llm_answer = chat.llm_request(prompt)
|
||||||
|
|
||||||
print('-'*40)
|
print('-'*40)
|
||||||
print(f'llm duration: {(datetime.datetime.now() - end_retrive).total_seconds()}')
|
print(f'llm duration: {(datetime.datetime.now() - end_retrive).total_seconds()}')
|
||||||
|
|
||||||
refrences = ''
|
refrences = ''
|
||||||
recognized_refrences = find_refrences(llm_answer)
|
recognized_refrences = chat.find_refrences(llm_answer)
|
||||||
llm_answer = replace_refrences(llm_answer, recognized_refrences)
|
llm_answer = chat.replace_refrences(llm_answer, recognized_refrences)
|
||||||
|
|
||||||
with open('./llm-answer/result.txt', mode='a+', encoding='utf-8') as file:
|
with open('./llm-answer/result.txt', mode='a+', encoding='utf-8') as file:
|
||||||
result_message = f'متن پرامپت: {query.strip()}\n\nپاسخ: {llm_answer} \n----------------------------------------------------------\n'
|
result_message = f'متن پرامپت: {query.strip()}\n\nپاسخ: {llm_answer} \n----------------------------------------------------------\n'
|
||||||
|
|
@ -473,4 +103,4 @@ if __name__ == "__main__":
|
||||||
print(f'full duration: {(datetime.datetime.now() - start).total_seconds()}')
|
print(f'full duration: {(datetime.datetime.now() - start).total_seconds()}')
|
||||||
print('----------------------------------------------------------')
|
print('----------------------------------------------------------')
|
||||||
print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
|
print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
|
||||||
|
|
||||||
783
chatbot_handler.py
Executable file
783
chatbot_handler.py
Executable file
|
|
@ -0,0 +1,783 @@
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
import faiss
|
||||||
|
from typing import List, Tuple
|
||||||
|
from sentence_transformers import SentenceTransformer
|
||||||
|
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
||||||
|
from sklearn.feature_extraction.text import TfidfVectorizer
|
||||||
|
from sklearn.metrics.pairwise import cosine_similarity
|
||||||
|
import datetime
|
||||||
|
import re
|
||||||
|
import random
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from embedder_sbert_qavanin_285k import PersianVectorAnalyzer
|
||||||
|
# from normalizer import cleaning
|
||||||
|
from fastapi import FastAPI ,Header
|
||||||
|
from pydantic import BaseModel
|
||||||
|
# LLM Libs
|
||||||
|
from openai import OpenAI
|
||||||
|
from langchain_openai import ChatOpenAI # pip install -U langchain_openai
|
||||||
|
import requests
|
||||||
|
# from FlagEmbedding import FlagReranker # deldar-reranker-v2
|
||||||
|
import aiofiles
|
||||||
|
import oss
|
||||||
|
|
||||||
|
# chatbot = FastAPI()
|
||||||
|
# origins = ["*"]
|
||||||
|
|
||||||
|
# chatbot.add_middleware(
|
||||||
|
# CORSMiddleware,
|
||||||
|
# allow_origins=origins,
|
||||||
|
# allow_credentials=True,
|
||||||
|
# allow_methods=["*"],
|
||||||
|
# allow_headers=["*"],
|
||||||
|
# )
|
||||||
|
|
||||||
|
# -------------------
|
||||||
|
# مدلها و مسیر دادهsrc/app/qavanin-faiss/faiss_index_qavanin_285k_metadata.json
|
||||||
|
# -------------------/src/app/qavanin-faiss
|
||||||
|
EMBED_MODEL = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
|
||||||
|
RERANKER_MODEL = "BAAI/bge-reranker-v2-m3"
|
||||||
|
FAISS_INDEX_PATH = "/src/app/qavanin-faiss/faiss_index_qavanin_285k.index"
|
||||||
|
FAISS_METADATA_PATH = "/src/app/qavanin-faiss/faiss_index_qavanin_285k_metadata.json"
|
||||||
|
|
||||||
|
RERANK_BATCH = int(os.environ.get("RERANK_BATCH", 256))
|
||||||
|
# print(f'RERANK_BATCH: {RERANK_BATCH}')
|
||||||
|
determine_refrence = """شناسه هر ماده قانون در ابتدای آن و با فرمت "id: {idvalue}" آمده است که id-value همان شناسه ماده است. بازای هربخش از پاسخی که تولید می شود، ضروری است شناسه ماده ای که در تدوین پاسخ از آن استفاده شده در انتهای پاراگراف یا جمله مربوطه با فرمت {idvalue} اضافه شود. همیشه idvalue با رشته "qs" شروع می شود"""
|
||||||
|
messages = [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": "تو یک دستیار خبره در زمینه حقوق و قوانین مرتبط به آن هستی و می توانی متون حقوقی را به صورت دقیق توضیح بدهی . پاسخ ها باید الزاما به زبان فارسی باشد. پاسخ ها فقط از متون قانونی که در پرامپت وجود دارد استخراج شود.",
|
||||||
|
},
|
||||||
|
{"role": "developer", "content": determine_refrence},
|
||||||
|
]
|
||||||
|
|
||||||
|
models = ["gpt-4o-mini", "gemini-2.5-flash-lite", "deepseek-chat"]
|
||||||
|
normalizer_obj = PersianVectorAnalyzer()
|
||||||
|
pipe = None
|
||||||
|
content_list, ids, prefix_list, faiss_index = [], [], [], []
|
||||||
|
|
||||||
|
async def get_key():
|
||||||
|
key = 'aa-fdh9d847ANcBxQCBTZD5hrrAdl0UrPEnJOScYmOncrkagYPf'
|
||||||
|
return key
|
||||||
|
|
||||||
|
def load_faiss_index(index_path: str, metadata_path: str):
|
||||||
|
"""بارگذاری ایندکس FAISS و متادیتا (لیست جملات + عناوین)."""
|
||||||
|
index = faiss.read_index(index_path)
|
||||||
|
|
||||||
|
with open(metadata_path, "r", encoding="utf-8") as f:
|
||||||
|
metadata = json.load(f)
|
||||||
|
|
||||||
|
content_list, ids, prefix_list = [], [], []
|
||||||
|
for item in metadata:
|
||||||
|
content_list.append(item["content"])
|
||||||
|
ids.append(item["id"])
|
||||||
|
prefix_list.append(item["prefix"])
|
||||||
|
|
||||||
|
return content_list, ids, prefix_list, index
|
||||||
|
|
||||||
|
async def get_client():
|
||||||
|
url = "https://api.avalai.ir/v1"
|
||||||
|
# key = 'aa-4tvAEazUBovEN1i7i7tdl1PR93OaWXs6hMflR4oQbIIA4K7Z'
|
||||||
|
|
||||||
|
client = OpenAI(
|
||||||
|
api_key=await get_key(),
|
||||||
|
base_url=url, # آدرس پایه
|
||||||
|
)
|
||||||
|
|
||||||
|
return client
|
||||||
|
|
||||||
|
async def llm_base_request(system_prompt, user_prompt):
|
||||||
|
client = await get_client() # فرض میکنیم get_client یک متد async است
|
||||||
|
base_messages = []
|
||||||
|
try:
|
||||||
|
if system_prompt:
|
||||||
|
base_messages.append({
|
||||||
|
"role": "system",
|
||||||
|
"content": system_prompt
|
||||||
|
})
|
||||||
|
|
||||||
|
base_messages.append({
|
||||||
|
"role": "user",
|
||||||
|
"content": user_prompt
|
||||||
|
})
|
||||||
|
for model in models:
|
||||||
|
response = client.chat.completions.create( # متد create به صورت async فراخوانی میشود
|
||||||
|
messages=base_messages,
|
||||||
|
model=model
|
||||||
|
)
|
||||||
|
answer = response.choices[0].message.content
|
||||||
|
cost = response.estimated_cost['irt']
|
||||||
|
break
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
# برای مدیریت خطاها، میتوانید فایلنویسی را به صورت async انجام دهید (در صورت نیاز)
|
||||||
|
async with aiofiles.open('./llm-answer/error-in-llm.txt', mode='a+', encoding='utf-8') as file:
|
||||||
|
error_message = f'\n\nquery: {user_prompt.strip()}\nerror:{error} \n------------------------------\n'
|
||||||
|
await file.write(error_message) # فایلنویسی async
|
||||||
|
|
||||||
|
return '', 0
|
||||||
|
|
||||||
|
return answer, cost
|
||||||
|
|
||||||
|
def llm_base_request2(system_prompt, user_prompt):
|
||||||
|
client = get_client()
|
||||||
|
base_messages = []
|
||||||
|
try:
|
||||||
|
if system_prompt:
|
||||||
|
base_messages.append(system_prompt)
|
||||||
|
base_messages.append({
|
||||||
|
"role": "user",
|
||||||
|
"content": user_prompt
|
||||||
|
})
|
||||||
|
for model in models:
|
||||||
|
response = client.chat.completions.create(
|
||||||
|
messages = base_messages,
|
||||||
|
model= model)
|
||||||
|
answer = response.choices[0].message.content
|
||||||
|
cost = response.estimated_cost['irt']
|
||||||
|
break
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
with open('./llm-answer/error-in-llm.txt', mode='a+', encoding='utf-8') as file:
|
||||||
|
error_message = f'\n\nquery: {query.strip()}\nerror:{error} \n-------------------------------\n'
|
||||||
|
file.write(error_message)
|
||||||
|
|
||||||
|
return '', 0
|
||||||
|
|
||||||
|
return answer, cost
|
||||||
|
|
||||||
|
async def oss_base_request(sys_prompt, user_prompt):
|
||||||
|
base_messages = []
|
||||||
|
try:
|
||||||
|
if sys_prompt:
|
||||||
|
base_messages.append({
|
||||||
|
"role": "system",
|
||||||
|
"content": sys_prompt
|
||||||
|
})
|
||||||
|
|
||||||
|
base_messages.append({
|
||||||
|
"role": "user",
|
||||||
|
"content": user_prompt
|
||||||
|
})
|
||||||
|
response = await oss.process_item(base_messages, reasoning_effort='low', temperature=0.1, max_tokens=40)
|
||||||
|
|
||||||
|
if response[0]:
|
||||||
|
answer = response[1]
|
||||||
|
else:
|
||||||
|
answer = ''
|
||||||
|
cost = 0
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
# برای مدیریت خطاها، میتوانید فایلنویسی را به صورت async انجام دهید (در صورت نیاز)
|
||||||
|
async with aiofiles.open('./llm-answer/error-in-llm.txt', mode='a+', encoding='utf-8') as file:
|
||||||
|
error_message = f'\n\nquery: {user_prompt.strip()}\nerror:{error} \n------------------------------\n'
|
||||||
|
await file.write(error_message) # فایلنویسی async
|
||||||
|
|
||||||
|
return '', 0
|
||||||
|
|
||||||
|
return answer, cost
|
||||||
|
|
||||||
|
async def oss_request(query):
|
||||||
|
|
||||||
|
if query == '':
|
||||||
|
return 'لطفا متن سوال را وارد نمائید', 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
messages.append({"role": "user", "content": query})
|
||||||
|
print(f'final prompt request attmpt')
|
||||||
|
response = await oss.process_item(messages= messages) # reasoning_effort='high'
|
||||||
|
print(response)
|
||||||
|
if response[0]:
|
||||||
|
answer = response[1]
|
||||||
|
else:
|
||||||
|
answer = 'متاسفانه پاسخی دریافت نشد'
|
||||||
|
cost_prompt = 0
|
||||||
|
# پاسخ را هم به سابقه اضافه میکنیم
|
||||||
|
messages.append({"role": "assistant", "content": answer})
|
||||||
|
|
||||||
|
response_dict = {}
|
||||||
|
response_dict['output'] = str(response)
|
||||||
|
async with aiofiles. open('./llm-answer/messages.json', mode='w', encoding='utf-8') as output:
|
||||||
|
await output.write(json.dumps(response_dict, ensure_ascii=False, indent=2))
|
||||||
|
print('response created')
|
||||||
|
async with aiofiles.open('./llm-answer/chat-objs.txt', mode='a+', encoding='utf-8') as file:
|
||||||
|
response_value = '0'
|
||||||
|
await file.write(response_value) # estimated_cost
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
print(f'error-in-llm.txt writing ...')
|
||||||
|
async with aiofiles.open('./llm-answer/error-in-llm.txt', mode='a+', encoding='utf-8') as file:
|
||||||
|
error_message = f'\n\nquery: {query.strip()}\nerror:{error} \n-------------------------------\n'
|
||||||
|
await file.write(error_message)
|
||||||
|
|
||||||
|
return 'با عرض پوزش؛ متاسفانه خطایی رخ داده است. لطفا لحظاتی دیگر دوباره تلاش نمائید', 0
|
||||||
|
print('================')
|
||||||
|
print(f'len messages: {len(messages)}')
|
||||||
|
print('================')
|
||||||
|
return answer, cost_prompt
|
||||||
|
|
||||||
|
async def llm_request(query, model):
|
||||||
|
|
||||||
|
if query == '':
|
||||||
|
return 'لطفا متن سوال را وارد نمائید', 0
|
||||||
|
|
||||||
|
client = await get_client()
|
||||||
|
try:
|
||||||
|
messages.append({"role": "user", "content": query})
|
||||||
|
response = client.chat.completions.create(
|
||||||
|
messages = messages,
|
||||||
|
model = model,
|
||||||
|
temperature = 0.1) # "gpt-4o", "gpt-4o-mini", "deepseek-chat" , "gemini-2.0-flash", gemini-2.5-flash-lite
|
||||||
|
# gpt-4o : 500
|
||||||
|
# gpt-4o-mini : 34
|
||||||
|
# deepseek-chat: : 150
|
||||||
|
# gemini-2.0-flash : error
|
||||||
|
# cf.gemma-3-12b-it : 1
|
||||||
|
# gemini-2.5-flash-lite : 35 خیلی خوب
|
||||||
|
|
||||||
|
answer = response.choices[0].message.content
|
||||||
|
cost_prompt = response.estimated_cost['irt']
|
||||||
|
# پاسخ را هم به سابقه اضافه میکنیم
|
||||||
|
messages.append({"role": "assistant", "content": answer})
|
||||||
|
print(f'type(response): {type(response)}')
|
||||||
|
print(f'response: {response}')
|
||||||
|
response_dict = {}
|
||||||
|
response_dict['output'] = str(response)
|
||||||
|
async with aiofiles. open('./llm-answer/messages.json', mode='w', encoding='utf-8') as output:
|
||||||
|
await output.write(json.dumps(response_dict, ensure_ascii=False, indent=2))
|
||||||
|
print('response created')
|
||||||
|
async with aiofiles.open('./llm-answer/chat-objs.txt', mode='a+', encoding='utf-8') as file:
|
||||||
|
response_value = f"{response.estimated_cost['irt']}\n-------------------------------\n\n"
|
||||||
|
await file.write(response_value) # estimated_cost
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
print(f'error-in-llm.txt writing ...')
|
||||||
|
async with aiofiles.open('./llm-answer/error-in-llm.txt', mode='a+', encoding='utf-8') as file:
|
||||||
|
error_message = f'\n\nquery: {query.strip()}\nerror:{error} \n-------------------------------\n'
|
||||||
|
await file.write(error_message)
|
||||||
|
|
||||||
|
return 'با عرض پوزش؛ متاسفانه خطایی رخ داده است. لطفا لحظاتی دیگر دوباره تلاش نمائید', 0
|
||||||
|
print('================')
|
||||||
|
print(f'len messages: {len(messages)}')
|
||||||
|
print('================')
|
||||||
|
return answer, cost_prompt
|
||||||
|
|
||||||
|
class HybridRetrieverReranker:
|
||||||
|
__slots__ = (
|
||||||
|
"device", "content_list", "ids", "prefix_list", "N", "embedder", "faiss_index",
|
||||||
|
"vectorizer", "tfidf_matrix", "tokenizer", "reranker", "dense_alpha"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, content_list: List[str],ids: List[str], prefix_list: List[str], faiss_index,
|
||||||
|
dense_alpha: float = 0.6, device: str = None):
|
||||||
|
|
||||||
|
if device is None:
|
||||||
|
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||||
|
self.device = device
|
||||||
|
|
||||||
|
self.content_list = content_list
|
||||||
|
self.ids = ids
|
||||||
|
self.prefix_list = prefix_list
|
||||||
|
self.faiss_index = faiss_index
|
||||||
|
self.N = len(content_list)
|
||||||
|
|
||||||
|
# Dense
|
||||||
|
self.embedder = SentenceTransformer(EMBED_MODEL,cache_folder='/src/MODELS', device=self.device)
|
||||||
|
#self.embedder = SentenceTransformer(EMBED_MODEL, device=self.device)
|
||||||
|
|
||||||
|
# Sparse (مثل قبل برای حفظ خروجی)
|
||||||
|
self.vectorizer = TfidfVectorizer(
|
||||||
|
analyzer="word",
|
||||||
|
ngram_range=(1, 2),
|
||||||
|
token_pattern=r"(?u)\b[\w\u0600-\u06FF]{2,}\b",
|
||||||
|
)
|
||||||
|
self.tfidf_matrix = self.vectorizer.fit_transform(self.content_list)
|
||||||
|
|
||||||
|
# Reranker
|
||||||
|
self.tokenizer = AutoTokenizer.from_pretrained(RERANKER_MODEL,cache_dir='/src/MODELS', use_fast=True)
|
||||||
|
# self.reranker = FlagReranker(RERANKER_MODEL,cache_dir="/src/MODELS", use_fp16=True)
|
||||||
|
self.reranker = AutoModelForSequenceClassification.from_pretrained(
|
||||||
|
RERANKER_MODEL
|
||||||
|
).to(self.device)
|
||||||
|
|
||||||
|
self.dense_alpha = float(dense_alpha)
|
||||||
|
|
||||||
|
# --- Dense (FAISS) ---
|
||||||
|
def dense_retrieve(self, query: str, top_k: int):
|
||||||
|
if top_k <= 0:
|
||||||
|
return [], np.array([], dtype=np.float32)
|
||||||
|
|
||||||
|
q_emb = self.embedder.encode(query, convert_to_numpy=True).astype(np.float32)
|
||||||
|
D, I = self.faiss_index.search(np.expand_dims(q_emb, axis=0), top_k)
|
||||||
|
|
||||||
|
return I[0].tolist(), D[0]
|
||||||
|
|
||||||
|
# --- Sparse ---
|
||||||
|
def sparse_retrieve(self, query: str, top_k: int):
|
||||||
|
if top_k <= 0:
|
||||||
|
return [], np.array([], dtype=np.float32)
|
||||||
|
k = min(top_k, self.N)
|
||||||
|
q_vec = self.vectorizer.transform([query])
|
||||||
|
sims = cosine_similarity(q_vec, self.tfidf_matrix).ravel()
|
||||||
|
idx = np.argpartition(-sims, kth=k-1)[:k]
|
||||||
|
idx = idx[np.argsort(-sims[idx], kind="mergesort")]
|
||||||
|
return idx.tolist(), sims[idx]
|
||||||
|
|
||||||
|
# --- Utils ---
|
||||||
|
@staticmethod
|
||||||
|
def _minmax_norm(arr: np.ndarray) -> np.ndarray:
|
||||||
|
if arr.size == 0:
|
||||||
|
return arr
|
||||||
|
a_min = arr.min()
|
||||||
|
a_max = arr.max()
|
||||||
|
rng = a_max - a_min
|
||||||
|
if rng < 1e-12:
|
||||||
|
return np.zeros_like(arr)
|
||||||
|
return (arr - a_min) / rng
|
||||||
|
|
||||||
|
def fuse(self, d_idx, d_scores, s_idx, s_scores, top_k=50, k_rrf=60):
|
||||||
|
"""
|
||||||
|
ادغام نتایج دو retriever (dense و sparse) با استفاده از Reciprocal Rank Fusion (RRF)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
d_idx (list or np.ndarray): ایندکسهای نتایج dense retriever
|
||||||
|
d_scores (list or np.ndarray): نمرات dense retriever
|
||||||
|
s_idx (list or np.ndarray): ایندکسهای نتایج sparse retriever
|
||||||
|
s_scores (list or np.ndarray): نمرات sparse retriever
|
||||||
|
top_k (int): تعداد نتایج نهایی
|
||||||
|
k_rrf (int): ثابت در فرمول RRF برای کاهش تأثیر رتبههای پایینتر
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: لیست ایندکسهای ادغامشده به ترتیب نمره
|
||||||
|
"""
|
||||||
|
combined = {}
|
||||||
|
|
||||||
|
# dense retriever
|
||||||
|
for rank, idx in enumerate(d_idx):
|
||||||
|
score = 1.0 / (k_rrf + rank)
|
||||||
|
combined[idx] = combined.get(idx, 0) + score
|
||||||
|
|
||||||
|
# sparse retriever
|
||||||
|
for rank, idx in enumerate(s_idx):
|
||||||
|
score = 1.0 / (k_rrf + rank)
|
||||||
|
combined[idx] = combined.get(idx, 0) + score
|
||||||
|
|
||||||
|
# مرتبسازی نهایی
|
||||||
|
sorted_items = sorted(combined.items(), key=lambda x: x[1], reverse=True)
|
||||||
|
cand_idx = [item[0] for item in sorted_items[:top_k]]
|
||||||
|
|
||||||
|
return cand_idx
|
||||||
|
|
||||||
|
def rerank2(self, query: str, candidate_indices: List[int], passages: List[str], final_k:int=4):
|
||||||
|
z_results = [[query, sentence] for sentence in passages]
|
||||||
|
# The scores map into 0-1 by set "normalize=True", which will apply sigmoid function to the score
|
||||||
|
scores = self.reranker.compute_score(z_results, normalize=True)
|
||||||
|
s_results = sorted(zip(scores, z_results, candidate_indices), key=lambda x: x[0], reverse=True)
|
||||||
|
s_results2 = s_results[:final_k]
|
||||||
|
results = [[i[0], i[1][1], i[2]] for i in s_results2]
|
||||||
|
print('%'*50)
|
||||||
|
print('%'*50)
|
||||||
|
print(results)
|
||||||
|
with open('./llm-answer/reranker-result.txt', mode='a+', encoding='utf-8') as file:
|
||||||
|
for item in results:
|
||||||
|
file.write(f'{item}\n')
|
||||||
|
print('%'*50)
|
||||||
|
print('%'*50)
|
||||||
|
return results
|
||||||
|
|
||||||
|
def rerank(self, query: str, candidate_indices: List[int], passages: List[str], final_k: int) -> List[Tuple[int, float]]:
|
||||||
|
"""
|
||||||
|
Rerank candidate passages using a cross-encoder (e.g., MonoT5, MiniLM, etc.).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query (str): پرسش کاربر
|
||||||
|
candidate_indices (List[int]): ایندکسهای کاندیدا (از retriever)
|
||||||
|
passages (List[str]): کل جملات/پاراگرافها
|
||||||
|
final_k (int): تعداد نتایج نهایی
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Tuple[int, float]]: لیستی از (ایندکس، امتیاز) برای بهترین نتایج
|
||||||
|
"""
|
||||||
|
if final_k <= 0 or not candidate_indices:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# آمادهسازی جفتهای (query, passage)
|
||||||
|
texts = [query] * len(candidate_indices)
|
||||||
|
pairs = passages
|
||||||
|
|
||||||
|
scores: List[float] = []
|
||||||
|
|
||||||
|
def _iter_batches(max_bs: int):
|
||||||
|
bs = max_bs
|
||||||
|
while bs >= 16: # حداقل batch_size
|
||||||
|
try:
|
||||||
|
with torch.inference_mode():
|
||||||
|
for start in range(0, len(pairs), bs):
|
||||||
|
batch_texts = texts[start:start + bs]
|
||||||
|
batch_pairs = pairs[start:start + bs]
|
||||||
|
inputs = self.tokenizer(
|
||||||
|
batch_texts,
|
||||||
|
batch_pairs,
|
||||||
|
padding=True,
|
||||||
|
truncation=True,
|
||||||
|
max_length=512,
|
||||||
|
return_tensors="pt",
|
||||||
|
).to(self.device)
|
||||||
|
|
||||||
|
logits = self.reranker(**inputs).logits.view(-1)
|
||||||
|
scores.extend(logits.detach().cpu().tolist())
|
||||||
|
return True
|
||||||
|
except torch.cuda.OutOfMemoryError:
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
torch.cuda.empty_cache()
|
||||||
|
bs //= 2
|
||||||
|
return False
|
||||||
|
|
||||||
|
# اجرای reranking
|
||||||
|
success = _iter_batches(max_bs=64)
|
||||||
|
if not success:
|
||||||
|
raise RuntimeError("Reranker failed due to CUDA OOM, even with small batch size.")
|
||||||
|
|
||||||
|
# مرتبسازی نتایج بر اساس نمره
|
||||||
|
reranked = sorted(
|
||||||
|
zip(candidate_indices, scores),
|
||||||
|
key=lambda x: x[1],
|
||||||
|
reverse=True
|
||||||
|
)[:final_k]
|
||||||
|
|
||||||
|
return reranked
|
||||||
|
|
||||||
|
def get_passages(self, cand_idx, content_list):
|
||||||
|
passages = []
|
||||||
|
for idx in cand_idx:
|
||||||
|
passages.append(content_list[idx])
|
||||||
|
|
||||||
|
return passages
|
||||||
|
|
||||||
|
# --- Search (بدون تغییر) ---
|
||||||
|
def search(self, query: str, content_list, topk_dense=50, topk_sparse=50,
|
||||||
|
pre_rerank_k=50, final_k=10):
|
||||||
|
start_time = datetime.datetime.now()
|
||||||
|
d_idx, d_scores = self.dense_retrieve(query, topk_dense)
|
||||||
|
dense_retrieve_end = datetime.datetime.now()
|
||||||
|
print('@'*50)
|
||||||
|
print(f'dense_retrieve_duration: {(dense_retrieve_end - start_time).total_seconds()}')
|
||||||
|
s_idx, s_scores = self.sparse_retrieve(query, topk_sparse)
|
||||||
|
sparse_retrieve_end = datetime.datetime.now()
|
||||||
|
print(f'sparse_retrieve_duration: {(sparse_retrieve_end - dense_retrieve_end).total_seconds()}')
|
||||||
|
cand_idx = self.fuse(d_idx, d_scores, s_idx, s_scores, pre_rerank_k)
|
||||||
|
fuse_end = datetime.datetime.now()
|
||||||
|
print(f'fuse_duration: {(fuse_end - sparse_retrieve_end).total_seconds()}')
|
||||||
|
passages = self.get_passages(cand_idx, content_list)
|
||||||
|
get_passages_end = datetime.datetime.now()
|
||||||
|
print(f'get_passages_duration: {(get_passages_end - fuse_end).total_seconds()}')
|
||||||
|
reranked = self.rerank(query, cand_idx, passages, final_k) # rerank2
|
||||||
|
rerank_end = datetime.datetime.now()
|
||||||
|
print(f'rerank_duration: {(rerank_end - get_passages_end).total_seconds()}')
|
||||||
|
print('@'*50)
|
||||||
|
return [{"idx": i, "content": self.content_list[i],"prefix": self.prefix_list[i], "rerank_score": score}
|
||||||
|
for i, score in reranked]
|
||||||
|
|
||||||
|
async def single_query(query: str):
|
||||||
|
|
||||||
|
# query = cleaning(query)
|
||||||
|
retrived_sections_ids = []
|
||||||
|
|
||||||
|
retrived_sections = pipe.search(query, content_list, topk_dense=30, topk_sparse=30, pre_rerank_k=30, final_k=10)
|
||||||
|
final_similars = ''
|
||||||
|
for i, row in enumerate(retrived_sections, 1):
|
||||||
|
id_value = '{' + str(ids[row['idx']]) + '}'
|
||||||
|
result = f"id: {id_value} \n{row['prefix']} {row['content']}\n"
|
||||||
|
retrived_sections_ids.append(ids[row['idx']])
|
||||||
|
final_similars += ''.join(result)
|
||||||
|
|
||||||
|
return final_similars, retrived_sections_ids
|
||||||
|
|
||||||
|
async def find_refrences(llm_answer: str) -> List[str]:
|
||||||
|
"""
|
||||||
|
شناسایی شناسه هایی که مدل زبانی، برای تهیه پاسخ از آنها استفاده کرده است
|
||||||
|
|
||||||
|
Args:
|
||||||
|
llm_answer(str): متنی که مدل زبانی تولید کرده است
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
refrence_ids(List[str]): لیستی از شناسه های تشخیص داده شده
|
||||||
|
"""
|
||||||
|
pattern = r"\{[^\}]+\}"
|
||||||
|
refrence_ids = re.findall(pattern, llm_answer)
|
||||||
|
new_refrences_ids = []
|
||||||
|
for itm in refrence_ids:
|
||||||
|
refrence = itm.lstrip('{')
|
||||||
|
refrence = refrence.lstrip('}')
|
||||||
|
new_refrences_ids.append(refrence)
|
||||||
|
|
||||||
|
refrence_ids = [item.lstrip('{').rstrip('}') for item in refrence_ids]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return refrence_ids
|
||||||
|
|
||||||
|
async def replace_refrences(llm_answer: str, refrences_list:List[str]) -> List[str]:
|
||||||
|
"""
|
||||||
|
شناسایی شناسه هایی که مدل زبانی، برای تهیه پاسخ از آنها استفاده کرده است
|
||||||
|
|
||||||
|
Args:
|
||||||
|
llm_answer(str): متنی که مدل زبانی تولید کرده است
|
||||||
|
refrences_list(List[str]): لیست شناسه ماده های مورد استفاده در پاسخ مدل زبانی
|
||||||
|
Returns:
|
||||||
|
llm_answer(str), : متن بازسازی شده پاسخ مدل زبانی که شناسه ماده های مورد استفاده در آن، اصلاح شده است
|
||||||
|
"""
|
||||||
|
# refrences = ''
|
||||||
|
for index, ref in enumerate(refrences_list,1):
|
||||||
|
new_ref = '{' + str(ref) + '}'
|
||||||
|
llm_answer = llm_answer.replace(new_ref, f'[{str(index)}]')
|
||||||
|
# id = ref.lstrip('{')
|
||||||
|
# id = id.rstrip('}')
|
||||||
|
# refrences += ''.join(f'[{index}] https://majles.tavasi.ir/entity/detail/view/qsection/{id}\n')
|
||||||
|
|
||||||
|
# llm_answer = f'{llm_answer}\n\nمنابع پاسخ:\n{refrences.strip()}'
|
||||||
|
return llm_answer.strip()
|
||||||
|
|
||||||
|
def initial_model():
|
||||||
|
global pipe
|
||||||
|
global content_list, ids, prefix_list, faiss_index
|
||||||
|
|
||||||
|
if not pipe :
|
||||||
|
# load basic items
|
||||||
|
content_list, ids, prefix_list, faiss_index = load_faiss_index(FAISS_INDEX_PATH, FAISS_METADATA_PATH)
|
||||||
|
pipe = HybridRetrieverReranker(content_list, ids, prefix_list, faiss_index, dense_alpha=0.6)
|
||||||
|
# query preprocess and normalize
|
||||||
|
|
||||||
|
|
||||||
|
def save_result(chat_obj: object) -> bool:
|
||||||
|
# index result in elastic
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_title_user_prompt(query: str):
|
||||||
|
"""
|
||||||
|
get a query and prepare a prompt to generate title based on that
|
||||||
|
"""
|
||||||
|
title_prompt = f'برای متن {query} یک عنوان با معنا که بین 3 تا 6 کلمه داشته باشد، در قالب یک رشته متن ایجاد کن. سبک و لحن عنوان، حقوقی و کاملا رسمی باشد. عنوان تولید شده کاملا ساده و بدون هیچ مارک داون یا علائم افزوده ای باشد. غیر از عنوان، به هیچ وجه توضیح اضافه ای در قبل یا بعد آن اضافه نکن.'
|
||||||
|
return title_prompt
|
||||||
|
|
||||||
|
async def get_title_system_prompt():
|
||||||
|
"""
|
||||||
|
returns a system prompt due to generate title
|
||||||
|
"""
|
||||||
|
title_system_prompt = f'تو یک دستیار حقوقی هستی و می توانی متون و سوالات حقوقی را به زبان ساده و دقیق توضیح بدهی.'
|
||||||
|
return title_system_prompt
|
||||||
|
|
||||||
|
async def run_chatbot(query:str, chat_id:str):
|
||||||
|
prompt_status = True
|
||||||
|
llm_model = ''
|
||||||
|
llm_answer = ''
|
||||||
|
cost_prompt = 0
|
||||||
|
cost_title = 0
|
||||||
|
status_text = 'لطفا متن سوال را وارد نمائید'
|
||||||
|
if query == '':
|
||||||
|
prompt_status = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# در صورتی که وضعیت پرامپت معتبر باشد، وارد فرایند شو
|
||||||
|
if prompt_status:
|
||||||
|
|
||||||
|
before_title_time = datetime.datetime.now()
|
||||||
|
title_system_prompt = await get_title_system_prompt()
|
||||||
|
title_user_prompt = await get_title_user_prompt(query)
|
||||||
|
# title, cost_title = await llm_base_request(title_system_prompt, title_user_prompt)
|
||||||
|
title, cost_title = await oss_base_request(title_system_prompt, title_user_prompt)
|
||||||
|
if not title:
|
||||||
|
title = query
|
||||||
|
|
||||||
|
title_prompt_duration = (datetime.datetime.now() - before_title_time).total_seconds()
|
||||||
|
|
||||||
|
if title == '':
|
||||||
|
title = query.split()[0:10]
|
||||||
|
|
||||||
|
start_time = (datetime.datetime.now())
|
||||||
|
result_passages_text, result_passages_ids = await single_query(query)
|
||||||
|
end_retrive = datetime.datetime.now()
|
||||||
|
print('-'*40)
|
||||||
|
print(f'title_prompt_duration: {title_prompt_duration}')
|
||||||
|
retrive_duration = (end_retrive - start_time).total_seconds()
|
||||||
|
print(f'retrive duration: {str(retrive_duration)}')
|
||||||
|
|
||||||
|
prompt = f'برای پرسش "{query}" از میان مواد قانونی "{result_passages_text}" .پاسخ مناسب و دقیق را استخراج کن. درصورتی که مطلبی مرتبط با پرسش در متن پیدا نشد، فقط پاسخ بده: "متاسفانه در منابع، پاسخی پیدا نشد!"'
|
||||||
|
|
||||||
|
# for model in models:
|
||||||
|
# before_prompt_credit = credit_refresh()
|
||||||
|
try:
|
||||||
|
# llm_model = model
|
||||||
|
# print(f'using model: {llm_model}')
|
||||||
|
# llm_answer, cost_prompt = await llm_request(prompt, model)
|
||||||
|
llm_answer, cost_prompt = await oss_request(prompt)
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
# after_prompt_credit = credit_refresh()
|
||||||
|
# prompt_cost = int(before_prompt_credit) - int(after_prompt_credit)
|
||||||
|
error = f'model: gpt.oss.120b \n{error}\n\n'
|
||||||
|
print('+++++++++++++++++')
|
||||||
|
print(f'llm-error.txt writing error: {error}')
|
||||||
|
print('+++++++++++++++++')
|
||||||
|
async with aiofiles.open('./llm-answer/llm-error.txt', mode='a+', encoding='utf-8') as file:
|
||||||
|
await file.write(error)
|
||||||
|
prompt_status = False
|
||||||
|
status_text = 'با عرض پوزش، سرویس موقتا در دسترس نیست. لطفا دقایقی دیگر دوباره تلاش نمائید!'
|
||||||
|
|
||||||
|
# حالتی که وضعیت پرامپت، نامعتبر باشد، یک شی با مقادیر زیر برگردانده می شود
|
||||||
|
else:
|
||||||
|
chat_obj = {
|
||||||
|
'id' : chat_id, # str
|
||||||
|
'title' : '', # str
|
||||||
|
'user_id' : '',
|
||||||
|
'user_query' : query, # str
|
||||||
|
'model_key' : llm_model, # str
|
||||||
|
'retrived_passage' : '', # str
|
||||||
|
'retrived_ref_ids' : '', # list[obj]
|
||||||
|
'prompt_type' : 'question-answer', # str
|
||||||
|
'retrived_duration' : '', # str
|
||||||
|
'llm_duration' : '0', # str
|
||||||
|
'full_duration' : '0', # str
|
||||||
|
'cost_prompt' : str(cost_prompt), # str
|
||||||
|
'cost_title' : str(cost_title), # str
|
||||||
|
'cost_total' : str(cost_prompt + cost_title), # str
|
||||||
|
'time_create' : str(start_time), # str
|
||||||
|
'used_ref_ids' : [], # list[str]
|
||||||
|
'prompt_answer' : '', # str
|
||||||
|
'status_text' : status_text,
|
||||||
|
'status' : prompt_status, # or False # bool
|
||||||
|
}
|
||||||
|
|
||||||
|
# بازگرداندن آبجکت ایجاد شده
|
||||||
|
return chat_obj, status_text
|
||||||
|
|
||||||
|
llm_answer_duration = (datetime.datetime.now() - end_retrive).total_seconds()
|
||||||
|
print(f'llm answer duration: {str(llm_answer_duration)}')
|
||||||
|
|
||||||
|
used_refrences_in_answer = await find_refrences(llm_answer)
|
||||||
|
llm_answer = await replace_refrences(llm_answer, used_refrences_in_answer)
|
||||||
|
|
||||||
|
full_prompt_duration = (datetime.datetime.now() - start_time).total_seconds()
|
||||||
|
print(f'full prompt duration: {full_prompt_duration}')
|
||||||
|
print('~'*40)
|
||||||
|
|
||||||
|
status_text ='پاسخ با موفقیت ایجاد شد'
|
||||||
|
|
||||||
|
print(f'cost_prompt: {cost_prompt}')
|
||||||
|
print(f'cost_title: {cost_title}')
|
||||||
|
chat_obj = {
|
||||||
|
'id' : chat_id, # str
|
||||||
|
'title' : title, # str
|
||||||
|
'user_id' : '',
|
||||||
|
'user_query' : query, # str
|
||||||
|
'model_key' : llm_model, # str
|
||||||
|
'retrived_passage' : result_passages_text, # str
|
||||||
|
'retrived_ref_ids' : result_passages_ids, # list[obj]
|
||||||
|
'prompt_type' : 'question-answer', # str
|
||||||
|
'retrived_duration' : retrive_duration, # str
|
||||||
|
'llm_duration' : llm_answer_duration, # str
|
||||||
|
'full_duration' : full_prompt_duration, # str
|
||||||
|
'cost_prompt' : str(cost_prompt), # str
|
||||||
|
'cost_title' : str(cost_title), # str
|
||||||
|
'cost_total' : str(cost_prompt + cost_title), # str
|
||||||
|
'time_create' : str(start_time), # str
|
||||||
|
'used_ref_ids' : used_refrences_in_answer, # list[str]
|
||||||
|
'prompt_answer' : llm_answer, # str
|
||||||
|
'status_text' : status_text, # str
|
||||||
|
'status' : True, # or False # bool
|
||||||
|
}
|
||||||
|
prev_chat_data = []
|
||||||
|
async with aiofiles.open('./llm-answer/chat-messages.json', mode='r', encoding='utf-8') as file:
|
||||||
|
content = await file.read()
|
||||||
|
prev_chat_data = json.loads(content)
|
||||||
|
prev_chat_data.append(chat_obj)
|
||||||
|
|
||||||
|
async with aiofiles. open('./llm-answer/chat-messages.json', mode='w', encoding='utf-8') as output:
|
||||||
|
await output.write(json.dumps(prev_chat_data, ensure_ascii=False, indent=2))
|
||||||
|
|
||||||
|
# save_result(chat_obj)
|
||||||
|
|
||||||
|
# ایجاد آبجکت بازگشتی به فرانت
|
||||||
|
# chat_obj.pop('retrived_passage')
|
||||||
|
# chat_obj.pop('prompt_type')
|
||||||
|
|
||||||
|
print('~'*40)
|
||||||
|
|
||||||
|
return chat_obj
|
||||||
|
|
||||||
|
async def credit_refresh():
|
||||||
|
"""
|
||||||
|
Returns remained credit
|
||||||
|
"""
|
||||||
|
url = "https://api.avalai.ir/user/credit"
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": f"Bearer {await get_key()}"
|
||||||
|
}
|
||||||
|
remained_credit = requests.get(url, headers=headers)
|
||||||
|
remained_credit_value = str(remained_credit.json()['remaining_irt'])
|
||||||
|
|
||||||
|
async with aiofiles.open('./llm-answer/credit.txt', mode='a+', encoding='utf-8') as file:
|
||||||
|
await file.write(f'{remained_credit_value}\n')
|
||||||
|
|
||||||
|
return remained_credit_value
|
||||||
|
|
||||||
|
async def create_chat_id():
|
||||||
|
date = str((datetime.datetime.now())).replace(' ','-').replace(':','').replace('.','-')
|
||||||
|
|
||||||
|
chat_id = f'{date}-{random.randint(100000, 999999)}'
|
||||||
|
|
||||||
|
return chat_id
|
||||||
|
|
||||||
|
|
||||||
|
# تعریف مدل دادهها برای درخواستهای API
|
||||||
|
class Query(BaseModel):
|
||||||
|
query: str
|
||||||
|
|
||||||
|
|
||||||
|
initial_model()
|
||||||
|
# uvicorn src.app:app --reload
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
# query = 'در قانون حمایت از خانواده و جوانی جمعیت چه خدماتی در نظر گرفته شده است؟'
|
||||||
|
while True:
|
||||||
|
query = input('enter your qustion:')
|
||||||
|
if query == '':
|
||||||
|
print('لطفا متن سوال را وارد نمائید')
|
||||||
|
continue
|
||||||
|
start = (datetime.datetime.now())
|
||||||
|
# result = test_dataset()
|
||||||
|
result = single_query(query)
|
||||||
|
end_retrive = datetime.datetime.now()
|
||||||
|
print('-'*40)
|
||||||
|
print(f'retrive duration: {(end_retrive - start).total_seconds()}')
|
||||||
|
|
||||||
|
prompt = f'برای پرسش "{query}" از میان مواد قانونی "{result}" .پاسخ مناسب و دقیق را استخراج کن. درصورتی که مطلبی مرتبط با پرسش در متن پیدا نشد، فقط پاسخ بده: "متاسفانه در منابع، پاسخی پیدا نشد!"'
|
||||||
|
llm_answer = llm_request(prompt)
|
||||||
|
|
||||||
|
print('-'*40)
|
||||||
|
print(f'llm duration: {(datetime.datetime.now() - end_retrive).total_seconds()}')
|
||||||
|
|
||||||
|
refrences = ''
|
||||||
|
recognized_refrences = find_refrences(llm_answer)
|
||||||
|
llm_answer = replace_refrences(llm_answer, recognized_refrences)
|
||||||
|
|
||||||
|
with open('./llm-answer/result.txt', mode='a+', encoding='utf-8') as file:
|
||||||
|
result_message = f'متن پرامپت: {query.strip()}\n\nپاسخ: {llm_answer} \n----------------------------------------------------------\n'
|
||||||
|
file.write(result_message)
|
||||||
|
|
||||||
|
with open('./llm-answer/passages.txt', mode='a+', encoding='utf-8') as file:
|
||||||
|
result_message = f'متن پرامپت: {query.strip()}\n\مواد مشابه: {result} \n----------------------------------------------------------\n'
|
||||||
|
file.write(result_message)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print('----------------------------------------------------------')
|
||||||
|
print(f'full duration: {(datetime.datetime.now() - start).total_seconds()}')
|
||||||
|
print('----------------------------------------------------------')
|
||||||
|
print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
|
||||||
0
convert_qavanin_json_to_faiss.py
Normal file → Executable file
0
convert_qavanin_json_to_faiss.py
Normal file → Executable file
21
dockerfile
Normal file → Executable file
21
dockerfile
Normal file → Executable file
|
|
@ -1,21 +1,4 @@
|
||||||
FROM python:3.10.12
|
FROM docker.tavasi.ir/tavasi/qachat_base:1.0.0
|
||||||
|
|
||||||
RUN pip install cleantext==1.1.4
|
|
||||||
RUN pip install elasticsearch7==7.17.12
|
|
||||||
RUN pip install faiss_cpu==1.9.0
|
|
||||||
RUN pip install fastapi==0.117.1
|
|
||||||
RUN pip install hazm==0.10.0
|
|
||||||
RUN pip install langchain_openai==0.3.33
|
|
||||||
RUN pip install numpy==1.21.5
|
|
||||||
RUN pip install openai==1.108.1
|
|
||||||
RUN pip install pandas==2.3.2
|
|
||||||
RUN pip install pydantic==2.11.9
|
|
||||||
RUN pip install scikit_learn==1.7.2
|
|
||||||
RUN pip install sentence_transformers==2.5.1
|
|
||||||
RUN pip install torch==2.4.0
|
|
||||||
RUN pip install transformers==4.55.1
|
|
||||||
#RUN pip install torch==2.1.2
|
|
||||||
|
|
||||||
|
|
||||||
WORKDIR /src/app
|
WORKDIR /src/app
|
||||||
|
|
||||||
|
|
@ -23,5 +6,5 @@ COPY . /src/app
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
CMD [ "uvicorn","chatbot:chatbot","--reload","--port","80" ]
|
CMD [ "uvicorn","chatbot:chatbot","--reload","--port","80","--host=0.0.0.0"]
|
||||||
|
|
||||||
|
|
|
||||||
4
dockerfile_base
Executable file
4
dockerfile_base
Executable file
|
|
@ -0,0 +1,4 @@
|
||||||
|
FROM docker.tavasi.ir/tavasi/qachat_base:1.0.0
|
||||||
|
RUN pip install uvicorn[standard]
|
||||||
|
RUN pip install FlagEmbedding
|
||||||
|
RUN pip install aiofiles
|
||||||
0
elastic_helper.py
Normal file → Executable file
0
elastic_helper.py
Normal file → Executable file
10
embedder_sbert_qavanin_285k.py
Normal file → Executable file
10
embedder_sbert_qavanin_285k.py
Normal file → Executable file
|
|
@ -30,7 +30,7 @@ from transformers import AutoTokenizer
|
||||||
from sklearn.decomposition import PCA
|
from sklearn.decomposition import PCA
|
||||||
from sklearn.manifold import TSNE
|
from sklearn.manifold import TSNE
|
||||||
from sklearn.metrics.pairwise import cosine_similarity
|
from sklearn.metrics.pairwise import cosine_similarity
|
||||||
from normalizer import cleaning
|
#from normalizer import cleaning
|
||||||
try:
|
try:
|
||||||
from elastic_helper import ElasticHelper
|
from elastic_helper import ElasticHelper
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
|
|
@ -43,8 +43,8 @@ except Exception as error:
|
||||||
# from plotly.subplots import make_subplots
|
# from plotly.subplots import make_subplots
|
||||||
|
|
||||||
# Persian text processing
|
# Persian text processing
|
||||||
import hazm
|
# import hazm
|
||||||
from hazm import Normalizer, word_tokenize, POSTagger
|
# from hazm import Normalizer, word_tokenize, POSTagger
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
@ -67,7 +67,7 @@ class PersianVectorAnalyzer:
|
||||||
"""
|
"""
|
||||||
self.model_name = model_name
|
self.model_name = model_name
|
||||||
self.model = None
|
self.model = None
|
||||||
self.normalizer = Normalizer()
|
#self.normalizer = Normalizer()
|
||||||
self.stop_words = self._load_persian_stop_words()
|
self.stop_words = self._load_persian_stop_words()
|
||||||
self.key_words = [
|
self.key_words = [
|
||||||
"خدا", "بنده", "جهاد", "ولی", "زکات",
|
"خدا", "بنده", "جهاد", "ولی", "زکات",
|
||||||
|
|
@ -206,7 +206,7 @@ class PersianVectorAnalyzer:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Normalize text
|
# Normalize text
|
||||||
text = self.normalizer.normalize(text)
|
#text = self.normalizer.normalize(text)
|
||||||
|
|
||||||
# Remove extra whitespace
|
# Remove extra whitespace
|
||||||
text = re.sub(r'\s+', ' ', text)
|
text = re.sub(r'\s+', ' ', text)
|
||||||
|
|
|
||||||
9845
llm-answer/chat-messages.json
Normal file → Executable file
9845
llm-answer/chat-messages.json
Normal file → Executable file
File diff suppressed because one or more lines are too long
541
llm-answer/chat-objs.txt
Normal file → Executable file
541
llm-answer/chat-objs.txt
Normal file → Executable file
|
|
@ -0,0 +1,541 @@
|
||||||
|
ChatCompletion(id='chatcmpl-CMEPWQSoYM74Yu0kGagCV6ruLCpoN', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='شرایط معافیت از پرداخت عوارض شهرداری به شرح زیر است:\n\n1. **تخفیف و بخشودگی:** هرگونه تخفیف یا بخشودگی حقوق و عوارض شهرداری\u200cها باید از بودجه عمومی سالانه کشور تأمین شود. در غیر این صورت، بخشودگی و تخفیف ممنوع است {qs738926}.\n\n2. **خانواده شهداء و ایثارگران:** خانواده شهداء و ایثارگران و جانبازان بیست و پنج درصد (۲۵٪) و بالاتر از پرداخت عوارض شهرداری برای احداث یک واحد مسکونی حداکثر ۱۲۵ متر مربع برای یک بار معاف هستند {qs911698}.\n\n3. **جانبازان:** جانبازان بیست و پنج درصد (۲۵٪) و بالاتر نیز از پرداخت عوارض شهرداری برای احداث یک واحد مسکونی حداکثر ۱۲۵ متر مربع برای یک بار معاف می\u200cباشند {qs289963}.\n\n4. **سازمان رادیو تلویزیون:** سازمان رادیو تلویزیون ملی ایران از پرداخت هرگونه عوارض شهرداری معاف است {qs814894}.\n\n5. **طرح\u200cهای عمومی عمرانی:** طرح\u200cهای مربوط به خرید و تملک اراضی و املاک برای اجرای برنامه\u200cهای عمومی، عمرانی و نظامی دولت از تاریخ شروع به اجرا، از پرداخت هر نوع عوارض مستقیم به شهرداری\u200cها معاف هستند {qs217429}.\n\n6. **واحدهای آموزشی:** واحدهای آموزشی و پرورشی با تأیید وزارت آموزش و پرورش از پرداخت هرگونه عوارض شهرداری معاف می\u200cباشند {qs993425} {qs212450}.\n\n7. **انتقال بلاعوض به نفع دولت و شهرداری\u200cها:** هرگونه انتقال بلاعوض به نفع دولت و شهرداری\u200cها از پرداخت عوارض معاف می\u200cباشد {qs120991}.\n\n8. **گواهی وزارت دارایی:** دولت و شهرداری\u200cها و موسسات وابسته به آن\u200cها با گواهی وزارت دارایی از پرداخت این نوع مالیات معاف خواهند بود {qs725533} {qs748695}.', refusal=None, role='assistant', annotations=[], audio=None, function_call=None, tool_calls=None))], created=1759414646, model='gpt-4o-mini-2024-07-18', object='chat.completion', service_tier=None, system_fingerprint='fp_efad92c60b', usage=CompletionUsage(completion_tokens=465, prompt_tokens=8403, total_tokens=8868, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=0, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=0, text_tokens=None), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0, text_tokens=None, image_tokens=None)), estimated_cost={'unit': '0.0016933950', 'irt': 197.03, 'exchange_rate': 116350})
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
ChatCompletion(id='f4neaIupMtXp7M8P9viLgQs', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='شرایط معافیت از پرداخت عوارض شهرداری به شرح زیر است:\n\n* **محدودیت تخفیف و بخشودگی:** هرگونه تخفیف یا بخشودگی حقوق و عوارض شهرداری\u200cها توسط دولت و قوانین مصوب، مشروط به تأمین آن از بودجه عمومی سالانه کشور است. در غیر این صورت، بخشودگی و تخفیف ممنوع است {qs738926}.\n\n* **خانواده شهداء، ایثارگران و جانبازان:** خانواده شهداء، ایثارگران و جانبازان با از خودگذشتگی بیست و پنج درصد (۲۵٪) و بالاتر، از پرداخت عوارض شهرداری برای احداث یک واحد مسکونی با حداکثر مساحت ۱۲۵ متر مربع، برای یک بار معاف می\u200cباشند {qs911698}.\n\n* **جانبازان:** جانبازان با از خودگذشتگی بیست و پنج درصد (۲۵٪) و بالاتر، از پرداخت عوارض شهرداری برای احداث یک واحد مسکونی با حداکثر مساحت ۱۲۵ متر مربع، برای یک بار معاف هستند {qs289963}.\n\n* **سازمان رادیو تلویزیون ملی ایران:** این سازمان از پرداخت هرگونه عوارض شهرداری معاف است {qs814894}.\n\n* **طرح\u200cهای عمومی، عمرانی و نظامی:** طرح\u200cهایی که طبق لایحه قانونی نحوه خرید و تملک اراضی و املاک برای اجرای برنامه\u200cهای عمومی، عمرانی و نظامی دولت اجرا می\u200cشوند، از تاریخ شروع به اجرا، از پرداخت هر نوع عوارض مستقیم به شهرداری\u200cها، مانند عوارض زمین، ساختمان، اموال منقول و غیرمنقول، حق تشرف و حق مرغوبیت معاف هستند {qs217429}.\n\n* **واحدهای آموزشی و پرورشی:** واحدهای آموزشی و پرورشی، با تأیید وزارت آموزش و پرورش، از پرداخت هرگونه عوارض شهرداری معاف می\u200cباشند {qs993425} {qs212450}.\n\n* **انتقال بلاعوض به نفع دولت و شهرداری\u200cها:** هرگونه انتقال بلاعوض که به نفع دولت و شهرداری\u200cها صورت می\u200cگیرد، از پرداخت عوارض و اخذ هرگونه گواهی معاف است {qs120991}.\n\n* **معافیت دولت، شهرداری\u200cها و موسسات وابسته:** دولت، شهرداری\u200cها و موسسات وابسته به آن\u200cها، با گواهی وزارت دارایی، از پرداخت مالیات نقل و انتقالات قطعی معاف خواهند بود {qs725533} {qs748695}.', refusal=None, role='assistant', annotations=[], audio=None, function_call=None, tool_calls=None, images=[], thinking_blocks=[]))], created=1759414653, model='gemini-2.5-flash-lite', object='chat.completion', service_tier=None, system_fingerprint=None, usage=CompletionUsage(completion_tokens=596, prompt_tokens=10041, total_tokens=10637, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetails(audio_tokens=None, cached_tokens=None, text_tokens=10041, image_tokens=None)), vertex_ai_grounding_metadata=[], vertex_ai_url_context_metadata=[], vertex_ai_safety_results=[], vertex_ai_citation_metadata=[], estimated_cost={'unit': '0.0012425000', 'irt': 144.56, 'exchange_rate': 116350})
|
||||||
|
-------------------------------
|
||||||
|
ChatCompletion(id='chatcmpl-CMEW5PqnEzAVDKITc6s0ZgLtR6A0q', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='حمایت از خانواده در جمهوری اسلامی ایران بر اساس مواد قانونی متعددی انجام می\u200cشود که به شرح زیر است:\n\n1. **حمایت کلی از خانواده:** ماده ۸۱ قانون برنامه پنجساله هفتم پیشرفت جمهوری اسلامی ایران، به حمایت از خانواده و ارتقای کارآمدی ساختار سازمانی در حوزه\u200cهای خانواده، زنان و جوانان و همچنین حمایت همه\u200cجانبه از فرزندآوری و رفع موانع اشاره دارد {qs3390392}.\n\n2. **تخصیص زمین به خانواده\u200cها:** به منظور حمایت از خانواده و تحقق جوانی جمعیت، وزارت راه و شهرسازی موظف است در سال\u200cهای ۱۴۰۲، ۱۴۰۳ و ۱۴۰۴، زمین یا واحد مسکونی به صورت رایگان به خانواده\u200cهای دارای چهار فرزند و بیشتر زیر بیست سال اختصاص دهد {qs3186952} {qs3328892} {qs67a862ff4e15f_202}. \n\n3. **حمایت حقوقی و فرهنگی:** همچنین بر اساس بند ۱۴ سیاست\u200cهای کلی خانواده، حمایت حقوقی، اقتصادی و فرهنگی از خانواده\u200cهای با سرپرستی زنان و تسهیل ازدواج آنان مورد تأکید قرار گرفته است {qs2248511}.\n\n4. **حمایت از خانواده زندانیان:** حمایت از خانواده زندانیان و معدومین از طریق سازمان\u200cها و نهادهای خیریه مردمی و غیردولتی نیز پیش\u200cبینی شده است {qs108297}.\n\n5. **تخفیف مالیاتی:** در راستای قانون حمایت از خانواده و جوانی جمعیت، اشخاص حقیقی موضوع قانون مالیات\u200cهای مستقیم که فرزند سوم و بیشتر دارند، مشمول افزایش پانزده درصد (۱۵٪) در تخفیف مالیاتی می\u200cشوند {qs3186827} {qs67a862ff4e15f_052}.\n\n6. **اهداف تشکیل خانواده:** اهداف تشکیل و تحکیم خانواده شامل ارتقاء آگاهی اعضای خانواده نسبت به حقوق و وظایف یکدیگر، پیشگیری از تزلزل و فروپاشی نهاد خانواده و حمایت از خانواده\u200cهای آسیب\u200cدیده و کودکان است {qs1028727}.\n\n7. **راهبردهای حمایت:** راهبردها شامل محافظت از خانواده در برابر آسیب\u200cهای اجتماعی، اتخاذ تدابیر مناسب برای حمایت از خانواده\u200cهای آسیب\u200cدیده و حمایت از زنان و کودکان در برابر تعرضات نیز می\u200cباشد {qs1028747}.', refusal=None, role='assistant', annotations=[], audio=None, function_call=None, tool_calls=None))], created=1759415053, model='gpt-4o-mini-2024-07-18', object='chat.completion', service_tier=None, system_fingerprint='fp_efad92c60b', usage=CompletionUsage(completion_tokens=521, prompt_tokens=16933, total_tokens=17454, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=0, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=0, text_tokens=None), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0, text_tokens=None, image_tokens=None)), estimated_cost={'unit': '0.0031378050', 'irt': 363.51, 'exchange_rate': 115850})
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
ChatCompletion(id='GIveaNm2KKeUkdUPzcWboAU', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='قوانین و سیاست\u200cهای موجود در زمینه حمایت از خانواده شامل موارد زیر است:\n\n* **قانون حمایت از خانواده و جوانی جمعیت:** این قانون به منظور حمایت جامع از خانواده، ارتقاء کارآمدی ساختارهای سازمانی مرتبط با خانواده، زنان و جوانان، حمایت همه\u200cجانبه از فرزندآوری، رفع موانع و ایجاد مشوق\u200cهای مؤثر و اصلاح فرهنگی تدوین شده است {qs3390392}.\n\n* **اعطای زمین رایگان به خانواده\u200cهای پرجمعیت:** در راستای حمایت از خانواده و تحقق جوانی جمعیت، وزارت راه و شهرسازی مکلف است در سال\u200cهای ۱۴۰۲، ۱۴۰۳ و ۱۴۰۴، زمین یا واحد مسکونی رایگان به خانواده\u200cهای دارای چهار فرزند و بیشتر زیر بیست سال اختصاص دهد {qs3186952} {qs3328892} {qs67a862ff4e15f_202}.\n\n* **حمایت حقوقی، اقتصادی و فرهنگی از خانواده\u200cهای زنان سرپرست:** سیاست\u200cهای کلی خانواده بر حمایت حقوقی، اقتصادی و فرهنگی از خانواده\u200cهای با سرپرستی زنان و همچنین تشویق و تسهیل ازدواج آنان تأکید دارد {qs2248511}.\n\n* **حمایت از خانواده زندانیان:** حمایت از خانواده زندانیان و معدومین از طریق سازمان\u200cها و نهادهای خیریه مردمی، غیردولتی و انجمن\u200cهای حمایت از زندانیان در نظر گرفته شده است {qs108297}.\n\n* **تخفیف مالیاتی برای فرزندان:** اشخاص حقیقی که فرزند سوم و بیشتر آن\u200cها از آبان ۱۴۰۰ به بعد متولد شده است، مشمول پانزده درصد (۱۵٪) افزایش در تخفیف مالیاتی به ازای هر فرزند می\u200cشوند {qs3186827} {qs67a862ff4e15f_052}.\n\n* **اهداف و راهبردهای تحکیم خانواده:** اهداف این سیاست\u200cها شامل ارتقاء جایگاه خانواده، حمایت از تشکیل و تحکیم آن، ارتقاء سطح فرهنگی و تربیتی اعضا، هماهنگی سیاست\u200cها، آگاهی از حقوق و وظایف، گسترش ارزش\u200cهای اسلامی، و ایمن\u200cسازی خانواده از آسیب\u200cهای اجتماعی و حمایت از خانواده\u200cهای آسیب\u200cدیده است {qs1028727}. راهبردها نیز بر ارتقای آگاهی، محافظت از آسیب\u200cپذیری خانواده، مبارزه با ناهنجاری\u200cهای اجتماعی و حمایت از زنان و کودکان تأکید دارند {qs1028747}.', refusal=None, role='assistant', annotations=[], audio=None, function_call=None, tool_calls=None, images=[], thinking_blocks=[]))], created=1759415062, model='gemini-2.5-flash-lite', object='chat.completion', service_tier=None, system_fingerprint=None, usage=CompletionUsage(completion_tokens=571, prompt_tokens=19342, total_tokens=19913, completion_tokens_details=None, prompt_tokens_details=PromptTokensDetails(audio_tokens=None, cached_tokens=None, text_tokens=19342, image_tokens=None)), vertex_ai_grounding_metadata=[], vertex_ai_url_context_metadata=[], vertex_ai_safety_results=[], vertex_ai_citation_metadata=[], estimated_cost={'unit': '0.0021626000', 'irt': 250.54, 'exchange_rate': 115850})
|
||||||
|
-------------------------------
|
||||||
|
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
|
||||||
|
Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='متاسفانه در منابع، پاسخی پیدا نشد!', refusal=None, role='assistant', annotations=[], audio=None, function_call=None, tool_calls=None))
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='اتومبیل\u200cهای نمایندگی\u200cهای سیاسی و کنسولی مقیم ایران و اعضای رسمی آنها، هنگام ترخیص، مشروط بر عمل متقابل، از پرداخت کلیه حقوق گمرکی، سود بازرگانی، عوارض و مالیات\u200cها معاف می\u200cباشند. {qs852944} همچنین، این اتومبیل\u200cها پس از گذشت ۳ سال از تاریخ ترخیص، در صورت عمل متقابل، هنگام فروش از پرداخت حقوق گمرکی، سود بازرگانی و عوارض متعلقه معاف خواهند بود. {qs852947}\n\nوسایل نقلیه مسافری خارجی با شرایطی از پرداخت عوارض راه و حق توقف معاف هستند. {qs335260}\n\nمتصدیان حمل کشورهای طرف موافقت\u200cنامه حمل و نقل بین\u200cالمللی جاده\u200cای کالا و مسافر (مانند کرواسی و لیتوانی)، برای انجام حمل و نقل بین\u200cالمللی جاده\u200cای و به صورت متقابل، از پرداخت عوارض و سایر پرداخت\u200cهای مربوط به مالکیت یا استفاده از وسیله نقلیه و همچنین عوارض مربوط به استفاده یا نگهداری راه\u200cهای کشور طرف دیگر معاف می\u200cشوند. {qs3437134} , {qs935848} , {qs985052}\n\nهرگونه انتقال بلاعوض به نفع دولت و شهرداری\u200cها از پرداخت عوارض معاف است. {qs120991}\n\nمالکان خودرو در صورت پرداخت و تسویه حساب قبوض جریمه\u200cهای رانندگی تا پایان آذرماه سال\u200cهای ۱۳۹۷ و ۱۳۹۵، از جریمه دیرکرد ناشی از عدم پرداخت تا پایان سال\u200cهای ۱۳۹۶ و ۱۳۹۴ بخشیده می\u200cشوند. {qs2565159} , {qs2207235}\n\nمسافران خاصی از پرداخت عوارض خروج از کشور معاف می\u200cباشند. {qs863196}', refusal=None, role='assistant', annotations=[], audio=None, function_call=None, tool_calls=None, images=[], thinking_blocks=[]))
|
||||||
|
-------------------------------
|
||||||
|
#####################################################
|
||||||
|
#####################################################
|
||||||
|
#####################################################
|
||||||
|
#####################################################
|
||||||
|
|
||||||
|
|
||||||
|
[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='برای معافیت از پرداخت عوارض شهرداری، شرایط زیر وجود دارد:\n\n1. خانواده شهداء و ایثارگران و جانبازان بیست و پنج درصد (۲۵ ٪) و بالاتر، از پرداخت عوارض شهرداری برای احداث یک واحد مسکونی حداکثر یکصد و بیست و پنج متر مربع برای یک\u200cبار معاف می\u200cباشند {qs911698}.\n\n2. جانبازان بیست و پنج درصد (۲۵ ٪) و بالاتر، از پرداخت عوارض شهرداری برای احداث یک واحد مسکونی حداکثر ۱۲۵ متر مربع برای یک\u200cبار معاف می\u200cباشند {qs289963}.\n\n3. سازمان رادیو تلویزیون ملی ایران از پرداخت هر گونه عوارض شهرداری معاف است و مشمول معافیت مذکور در ماده ۲۶ قانون نوسازی و عمران شهری نیز خواهد بود {qs814894}.\n\n4. طرح\u200cهای موضوع قانون نحوه خرید و تملک اراضی و املاک برای اجرای برنامه\u200cهای عمومی، عمرانی و نظامی دولت از تاریخ شروع به اجراء از پرداخت هر نوع عوارض مستقیم به شهرداری\u200cها معاف هستند {qs217429}.\n\n5. واحدهای آموزشی و پرورشی با تأیید وزارت آموزش و پرورش از پرداخت هر گونه عوارض شهرداری معاف می\u200cباشند {qs993425} و {qs212450}.\n\n6. هرگونه انتقال بلاعوض به نفع دولت و شهرداری\u200cها از پرداخت عوارض و اخذ هرگونه گواهی معاف می\u200cباشد {qs120991}. \n\nاین شرایط نشان\u200cدهنده مواردی است که می\u200cتوان بر اساس آن\u200cها از پرداخت عوارض شهرداری معاف شد.', refusal=None, role='assistant', annotations=[], audio=None, function_call=None, tool_calls=None))]
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='برای معافیت از پرداخت عوارض شهرداری، شرایط زیر وجود دارد:\n\n* خانواده شهداء، ایثارگران و جانبازان با درصد جانبازی بیست و پنج درصد (۲۵ ٪) و بالاتر، برای احداث یک واحد مسکونی حداکثر به متراژ یکصد و بیست و پنج متر مربع، برای یک\u200cبار از پرداخت عوارض شهرداری معاف می\u200cباشند {qs911698}.\n* جانبازان با درصد جانبازی بیست و پنج درصد (۲۵ ٪) و بالاتر، برای احداث یک واحد مسکونی حداکثر به متراژ ۱۲۵ متر مربع، برای یک\u200cبار از پرداخت عوارض شهرداری معاف می\u200cباشند {qs289963}.\n* سازمان رادیو تلویزیون ملی ایران از پرداخت هر گونه عوارض شهرداری معاف است {qs814894}.\n* طرح\u200cهای موضوع قانون نحوه خرید و تملک اراضی و املاک برای اجرای برنامه\u200cهای عمومی، عمرانی و نظامی دولت، از تاریخ شروع اجرا، از پرداخت هر نوع عوارض مستقیم به شهرداری\u200cها معاف هستند {qs217429}.\n* واحدهای آموزشی و پرورشی با تأیید وزارت آموزش و پرورش از پرداخت هر گونه عوارض شهرداری معاف می\u200cباشند {qs993425} و {qs212450}.\n* هرگونه انتقال بلاعوض به نفع دولت و شهرداری\u200cها از پرداخت عوارض و اخذ هرگونه گواهی معاف می\u200cباشد {qs120991}.\n* دولت و شهرداری\u200cها و موسسات وابسته به آن\u200cها با ارائه گواهی از وزارت دارایی، از پرداخت مالیات نقل و انتقالات قطعی معاف خواهند بود {qs725533} و {qs748695}.\n\nشرط کلی برای هرگونه تخفیف یا بخشودگی حقوق و عوارض شهرداری توسط دولت و قوانین مصوب، تأمین آن از بودجه عمومی سالانه کشور است. در غیر این صورت، بخشودگی و تخفیف حقوق و عوارض شهرداری ممنوع است {qs738926}.', refusal=None, role='assistant', annotations=[], audio=None, function_call=None, tool_calls=None, images=[], thinking_blocks=[]))]
|
||||||
|
-------------------------------
|
||||||
|
-------------------------------
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
{'unit': '0.0006956400', 'irt': 80.42, 'exchange_rate': 115600}
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
{'unit': '0.0011443000', 'irt': 132.28, 'exchange_rate': 115600}
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
47.58
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
89.14
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
117.51
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
149.32
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
238.13
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
199.27
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
20.63
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
22.99
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
77.09
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
79.66
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
55.49
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
69.28
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
64.43
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
85.99
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
108.5
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
58.21
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
66.88
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
97.08
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
85.15
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
98.28
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
111.41
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
124.53
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
50.12
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
206.18
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
219.27
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
249.11
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
286.66
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
281.35
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
297.52
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
331.9
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
363.84
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
376.68
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
423.29
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
466.93
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
475.04
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
20.42
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
36.08
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
50.47
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
41.91
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
55.16
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
39.06
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
61.63
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
40.3
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
69.5
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
82.35
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
140.04
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
137.97
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
200.29
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
244.43
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
257.28
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
313.51
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
315.89
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
355.38
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
396.78
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
435.83
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
478.95
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
482.55
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
249.69
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
536.13
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
575.45
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
609.36
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
643.95
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
706.14
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
748.32
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
762.59
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
527.55
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
875.18
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
939.63
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
955.53
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
1001.8
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
1065.1
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
1078.9
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
1147.06
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
1150.96
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
1220.98
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
1219.78
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
1265.87
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
1296.9
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
47.79
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
73.08
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
87.81
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
112.63
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
137.99
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
157.22
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
186.68
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
214.6
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
242.14
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
256.68
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
283.86
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
80.44
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
121.42
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
139.51
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
101.81
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
71.52
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
180.64
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
80.55
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
97.99
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
104.13
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
155.02
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
170.09
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
96.74
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
131.7
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
80.5
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
135.59
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
148.67
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
102.43
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
131.11
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
148.85
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
98.64
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
186.92
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
188.55
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
238.4
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
235.39
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
258.96
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
209.47
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
259.7
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
234.19
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
292.11
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
297.42
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
275.51
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
286.96
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
319.76
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
386.88
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
344.8
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
406.07
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
438.77
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
659.01
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
539.14
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
551.07
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
575.04
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
72.18
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
96.69
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
106.42
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
133.98
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
162.94
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
182.09
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
206.28
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
268.84
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
275.0
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
24.96
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
56.4
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
43.72
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
63.34
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
77.81
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
78.82
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
18.85
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
49.17
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
51.87
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
76.95
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
101.75
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
122.79
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
118.51
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
101.94
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
84.07
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
224.65
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
130.38
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
108.94
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
295.75
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
105.6
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
345.01
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
344.0
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
137.18
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
395.53
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
414.09
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
204.4
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
191.2
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
31.68
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
375
llm-answer/credit.txt
Normal file → Executable file
375
llm-answer/credit.txt
Normal file → Executable file
|
|
@ -1 +1,374 @@
|
||||||
6085.11
|
108315.8
|
||||||
|
108315.8
|
||||||
|
108315.8
|
||||||
|
108315.8
|
||||||
|
108315.8
|
||||||
|
108315.8
|
||||||
|
108315.8
|
||||||
|
108315.8
|
||||||
|
108273.66
|
||||||
|
108315.8
|
||||||
|
108315.8
|
||||||
|
108315.8
|
||||||
|
108148.33
|
||||||
|
108148.33
|
||||||
|
108148.33
|
||||||
|
108148.33
|
||||||
|
108148.33
|
||||||
|
108148.33
|
||||||
|
108148.33
|
||||||
|
108148.33
|
||||||
|
108148.33
|
||||||
|
107820.53
|
||||||
|
107820.53
|
||||||
|
107820.53
|
||||||
|
107820.53
|
||||||
|
107820.53
|
||||||
|
107820.53
|
||||||
|
107820.53
|
||||||
|
107820.53
|
||||||
|
107820.53
|
||||||
|
107820.53
|
||||||
|
107820.53
|
||||||
|
107820.53
|
||||||
|
107820.53
|
||||||
|
107820.53
|
||||||
|
107820.53
|
||||||
|
107820.53
|
||||||
|
107311.44
|
||||||
|
107311.44
|
||||||
|
107311.44
|
||||||
|
107311.44
|
||||||
|
107311.44
|
||||||
|
107311.44
|
||||||
|
106600.25
|
||||||
|
106600.25
|
||||||
|
106600.25
|
||||||
|
106600.25
|
||||||
|
106600.25
|
||||||
|
106600.25
|
||||||
|
106600.25
|
||||||
|
106600.25
|
||||||
|
105674.64
|
||||||
|
105674.64
|
||||||
|
105674.64
|
||||||
|
105674.64
|
||||||
|
105674.64
|
||||||
|
105674.64
|
||||||
|
105674.64
|
||||||
|
105674.64
|
||||||
|
105545.4
|
||||||
|
105545.4
|
||||||
|
105545.4
|
||||||
|
105392.43
|
||||||
|
105392.43
|
||||||
|
105319.32
|
||||||
|
105064.98
|
||||||
|
105064.98
|
||||||
|
105064.98
|
||||||
|
104994.01
|
||||||
|
104844.02
|
||||||
|
104844.02
|
||||||
|
104844.02
|
||||||
|
104844.02
|
||||||
|
104633.2
|
||||||
|
104633.2
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
104427.13
|
||||||
|
103993.0
|
||||||
|
103503.05
|
||||||
|
103503.05
|
||||||
|
103427.1
|
||||||
|
103350.01
|
||||||
|
103269.16
|
||||||
|
103269.16
|
||||||
|
103269.16
|
||||||
|
103269.16
|
||||||
|
103269.16
|
||||||
|
103269.16
|
||||||
|
103212.11
|
||||||
|
103212.11
|
||||||
|
103116.15
|
||||||
|
103050.78
|
||||||
|
103050.78
|
||||||
|
103050.78
|
||||||
|
102963.69
|
||||||
|
102854.07
|
||||||
|
102854.07
|
||||||
|
102854.07
|
||||||
|
102854.07
|
||||||
|
102854.07
|
||||||
|
102854.07
|
||||||
|
102854.07
|
||||||
|
102854.07
|
||||||
|
102854.07
|
||||||
|
102854.07
|
||||||
|
102854.07
|
||||||
|
102854.07
|
||||||
|
102854.07
|
||||||
|
102854.07
|
||||||
|
102854.07
|
||||||
|
102794.63
|
||||||
|
102794.63
|
||||||
|
102794.63
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102726.67
|
||||||
|
102628.35
|
||||||
|
102628.35
|
||||||
|
102628.35
|
||||||
|
102628.35
|
||||||
|
102628.35
|
||||||
|
102628.35
|
||||||
|
102628.35
|
||||||
|
102628.35
|
||||||
|
102628.35
|
||||||
|
102628.35
|
||||||
|
102628.35
|
||||||
|
102628.35
|
||||||
|
102628.35
|
||||||
|
102628.35
|
||||||
|
102628.35
|
||||||
|
102628.35
|
||||||
|
102542.09
|
||||||
|
102442.65
|
||||||
|
102330.13
|
||||||
|
102204.53
|
||||||
|
102144.41
|
||||||
|
102144.41
|
||||||
|
101937.29
|
||||||
|
101716.9
|
||||||
|
101716.9
|
||||||
|
101466.67
|
||||||
|
101466.67
|
||||||
|
101466.67
|
||||||
|
101466.67
|
||||||
|
101178.93
|
||||||
|
100896.33
|
||||||
|
100597.57
|
||||||
|
100264.5
|
||||||
|
100264.5
|
||||||
|
99899.47
|
||||||
|
99899.47
|
||||||
|
99521.35
|
||||||
|
99096.83
|
||||||
|
99096.83
|
||||||
|
99096.83
|
||||||
|
99096.83
|
||||||
|
98628.66
|
||||||
|
98628.66
|
||||||
|
98628.66
|
||||||
|
98628.66
|
||||||
|
98628.66
|
||||||
|
98628.66
|
||||||
|
98628.66
|
||||||
|
96472.15
|
||||||
|
96472.15
|
||||||
|
96450.82
|
||||||
|
96260.75
|
||||||
|
96260.75
|
||||||
|
96160.86
|
||||||
|
96103.13
|
||||||
|
95868.76
|
||||||
|
95803.86
|
||||||
|
95802.19
|
||||||
|
95597.97
|
||||||
|
95596.25
|
||||||
|
95285.35
|
||||||
|
95061.39
|
||||||
|
94827.5
|
||||||
|
94540.93
|
||||||
|
94539.27
|
||||||
|
94250.29
|
||||||
|
93564.76
|
||||||
|
93563.21
|
||||||
|
93165.04
|
||||||
|
92289.47
|
||||||
|
92287.87
|
||||||
|
91552.15
|
||||||
|
91025.68
|
||||||
|
91025.68
|
||||||
|
89884.47
|
||||||
|
89882.95
|
||||||
|
88558.88
|
||||||
|
87865.16
|
||||||
|
87336.23
|
||||||
|
86537.6
|
||||||
|
85683.38
|
||||||
|
84813.13
|
||||||
|
83900.61
|
||||||
|
82930.69
|
||||||
|
81948.34
|
||||||
|
79857.44
|
||||||
|
77636.63
|
||||||
|
77634.84
|
||||||
|
75303.16
|
||||||
|
75301.25
|
||||||
|
75179.23
|
||||||
|
75176.18
|
||||||
|
75087.4
|
||||||
|
74835.09
|
||||||
|
74676.85
|
||||||
|
74675.47
|
||||||
|
74487.78
|
||||||
|
74271.65
|
||||||
|
73770.94
|
||||||
|
73485.89
|
||||||
|
70998.28
|
||||||
|
70945.89
|
||||||
|
70657.52
|
||||||
|
70366.94
|
||||||
|
70115.51
|
||||||
|
68157.72
|
||||||
|
67863.0
|
||||||
|
67699.81
|
||||||
|
67612.06
|
||||||
|
67340.76
|
||||||
|
67226.26
|
||||||
|
67151.4
|
||||||
|
67038.79
|
||||||
|
66906.9
|
||||||
|
66794.53
|
||||||
|
66685.69
|
||||||
|
66581.6
|
||||||
|
66551.47
|
||||||
|
66420.04
|
||||||
|
66364.76
|
||||||
|
66232.0
|
||||||
|
66227.19
|
||||||
|
66002.66
|
||||||
|
65997.93
|
||||||
|
65911.03
|
||||||
|
56269.22
|
||||||
|
56197.89
|
||||||
|
56107.82
|
||||||
|
55996.72
|
||||||
|
55915.96
|
||||||
|
55828.47
|
||||||
|
55723.6
|
||||||
|
55652.33
|
||||||
|
55535.28
|
||||||
|
55419.11
|
||||||
|
55279.65
|
||||||
|
55145.03
|
||||||
|
55005.74
|
||||||
|
54881.07
|
||||||
|
54750.93
|
||||||
|
54615.69
|
||||||
|
54473.45
|
||||||
|
54151.09
|
||||||
|
54007.49
|
||||||
|
53828.13
|
||||||
|
53639.72
|
||||||
|
53456.63
|
||||||
|
53263.79
|
||||||
|
53047.02
|
||||||
|
52569.35
|
||||||
|
50475.24
|
||||||
|
50219.15
|
||||||
|
49950.43
|
||||||
|
49878.25
|
||||||
|
49779.95
|
||||||
|
49671.93
|
||||||
|
49534.53
|
||||||
|
49371.59
|
||||||
|
49187.86
|
||||||
|
48978.13
|
||||||
|
48709.29
|
||||||
|
48432.68
|
||||||
|
48406.11
|
||||||
|
48348.01
|
||||||
|
48302.61
|
||||||
|
48237.55
|
||||||
|
48158.0
|
||||||
|
48077.56
|
||||||
|
48056.84
|
||||||
|
48056.84
|
||||||
|
47952.03
|
||||||
|
47873.31
|
||||||
|
47769.79
|
||||||
|
47645.39
|
||||||
|
47516.55
|
||||||
|
47514.88
|
||||||
|
47512.76
|
||||||
|
47512.76
|
||||||
|
47433.21
|
||||||
|
47433.21
|
||||||
|
47099.25
|
||||||
|
46841.66
|
||||||
|
46841.66
|
||||||
|
46544.26
|
||||||
|
46389.25
|
||||||
|
46042.58
|
||||||
|
45696.9
|
||||||
|
45528.18
|
||||||
|
45528.18
|
||||||
|
45528.18
|
||||||
|
44715.19
|
||||||
|
44711.6
|
||||||
|
44499.03
|
||||||
|
44238.73
|
||||||
|
44238.73
|
||||||
|
44238.73
|
||||||
|
|
|
||||||
899
llm-answer/error-in-llm.txt
Normal file → Executable file
899
llm-answer/error-in-llm.txt
Normal file → Executable file
File diff suppressed because one or more lines are too long
127
llm-answer/llm-error.txt
Executable file
127
llm-answer/llm-error.txt
Executable file
|
|
@ -0,0 +1,127 @@
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: deepseek-chat
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
cccc
|
||||||
|
|
||||||
|
model: deepseek-chat
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gemini-2.5-flash-lite
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
|
model: gpt-4o-mini
|
||||||
|
too many values to unpack (expected 2)
|
||||||
|
|
||||||
3
llm-answer/messages.json
Normal file
3
llm-answer/messages.json
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"output": "ChatCompletion(id='chatcmpl-CZdh1AgOZRDoHqqkBhLHKYCFwMRk1', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='مجازات اشاعه محتوای غیرمجاز در فضای مجازی بر اساس تبصره ۲ از ماده 131 قانون حمایت از مالکیت صنعتی، علاوه بر مجازات مقرر در این ماده، مستوجب حبس درجه شش می\\u200cباشد. همچنین، در صورتی که جرم در قالب گروه مجرمانه سازمان\\u200cیافته یا توسط مأموران دولتی در حین انجام وظیفه یا از طریق ابزارهای ارتباط جمعی در فضای مجازی منتشر شود، مجازات مقرر به میزان یک درجه تشدید می\\u200cشود.', refusal=None, role='assistant', annotations=[], audio=None, function_call=None, tool_calls=None))], created=1762610095, model='gpt-4o-mini-2024-07-18', object='chat.completion', service_tier=None, system_fingerprint='fp_efad92c60b', usage=CompletionUsage(completion_tokens=113, prompt_tokens=1316, total_tokens=1429, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=0, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=0, text_tokens=None), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0, text_tokens=None, image_tokens=None)), estimated_cost={'unit': '0.0002917200', 'irt': 31.68, 'exchange_rate': 108600})"
|
||||||
|
}
|
||||||
0
llm-answer/passages.txt
Normal file → Executable file
0
llm-answer/passages.txt
Normal file → Executable file
10
llm-answer/reranker-result.txt
Executable file
10
llm-answer/reranker-result.txt
Executable file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[0.43728234058085713, 'الف - حقوق بنیادین کار (آزادی انجمنها و حمایت از حق تشکلهای مدنی روابط کار، حق سازماندهی و مذاکره دسته\u200cجمعی، تساوی مزدها برای زن و مرد در مقابل کار هم ارزش، منع تبعیض در اشتغال و حرفه، رعایت حداقل سن کار، ممنوعیت کار کودک، رعایت حداقل مزد متناسب با حداقل معیشت).', 186639]
|
||||||
|
[0.17097510097612545, 'تبصره ۱۱ - بمنظور ایجاد مرجع صلاحیتدار و بیطرفی برای حل اختلافات بین کارگر و کارفرما و ایجاد حسن تفاهم در بین آنان و تمرکز امور مربوط بکار و مراقبت در تهیه و اجرای مقررات قانون کار و قانون بیمه کارگران و همچنین حمایت و تأمین بهداشت و رفاه و بالا بردن سطح زندگی کارگران و وضع و اجرای مقررات بیمه\u200cهای اجتماعی و برقرار نمودن روابط با تشکیلات بین\u200cالمللی کار وزارتخانه\u200cای بنام وزارت کار تأسیس می\u200cشود.', 128416]
|
||||||
|
[0.15169625817516322, 'ث) حمایت از کارگران و نمایندگان آنها در برابر اقدامات انضباطی ناشی از اعمالی که آنها مطابق سیاست موضوع ماده (۴) فوق به طور معقول انجام داده\u200cاند.', 75037]
|
||||||
|
[0.11213845051838162, 'ماده ۷ - دولتهای طرف این میثاق حق هر کس را به تمتع از شرایط عادلانه و مساعد کار که بویژه متضمن مراتب زیر باشد برسمیت بشناسند: الف - اجرتی که لااقل امور ذیل را برای کلیه کارگران تأمین نماید: ۱ - مزد منصفانه و اجرت مساوی برای کار با ارزش مساوی بدون هیچ نوع تمایز بویژه اینکه زنان تضمین داشته باشند که شرایط کار آنان پائین\u200cتر از\u200cشرایط مورد استفاده مردان نباشد و برای کار مساوی مزد مساوی با مردان دریافت دارند. ۲ - مزایای کافی برای آنان و خانواده\u200cشان طبق مقررات این میثاق: ب - ایمنی و بهداشت کار. ج - تساوی فرصت برای هر کس که بتواند در خدمت خود بمدارج مناسب عالیتری ارتقاء یابد بدون در نظر گرفتن هیچگونه ملاحظات دیگری جز\u200cطول مدت خدمت و لیاقت. د - استراحت - فراغت و محدودیت معقول ساعات کار و مرخصی اداری با استفاده از حقوق همچنین مزد ایام تعطیل رسمی.', 194273]
|
||||||
|
[0.1079329839273747, '۲۰ - بمنظور تأمین شرائط مناسب\u200cتر کار و زندگی کارگران مهاجر نسبت بشرائطی که قانون یا رویه عملی برای سایر کارگران که در خدمت متشابه\u200cاشتغال دارند مقرر داشته و همچنین برای اینکه کارگران مهاجر هم مثل کارگران دیگر مشمول مقیاسهای حمایت بنحوی که در بندهای آتی این سفارش نامه خواهد آمد گردند کلیۀ مساعی باید صورت گیرد.', 8843]
|
||||||
|
[0.0531841966906351, '۱۳ ایجاد نظام جامع تأمین اجتماعی برای حمایت از حقوق محرومان و مستضعفان و مبارزه با فقر و حمایت از نهادهای عمومی و موسسات و خیریه\u200cهای مردمی با رعایت ملاحظات دینی و انقلابی.', 213766]
|
||||||
|
[0.05166811304646011, '۲ - ایجاد نظام جامع تأمین اجتماعی برای حمایت از حقوق محرومان و مستضعفان و مبارزه با فقر و حمایت از نهاد\u200cهای عمومی و موسسات و خیریه\u200cهای مردمی با رعایت ملاحظات دینی و انقلابی.', 53933]
|
||||||
|
[0.051528153447387044, 'ج تقویت همسویی منافع کارگران و کارفرمایان و تکالیف دولت با رویکرد حمایت از تولید و سه\u200cجانبه گرایی', 185751]
|
||||||
|
[0.024949120491999023, 'ماده ۲ - ۱ - هر یک از کشورهای عضو باید بوسائلی که منطبق با روشهای معمول جهت تعیین میزان اجرت باشد اجرای اصل تساوی اجرت کارگر\u200cزن و مرد را در قبال کار هم ارزش تشویق و تا حدودی که با روشهای فوق\u200cالذکر تطبیق نماید اجرای آنرا درباره عموم کارگران تأمین کند. ۲ - اجرای این اصل ممکن است بطرق زیر صورت گیرد: الف - وضع قوانین داخلی. ب - هر روشی که جهت تعیین میزان اجرت\u200cها ضمن قوانین پیش\u200cبینی\u200cشده باشد. ج - انعقاد پیمان\u200cهای دسته\u200cجمعی بین کارفرمایان و کارگران. د - ترکیبی از این روشهای مختلف.', 204904]
|
||||||
|
[0.024270693471581787, 'ز تلاش در جهت گسترش امکانات رفاهی و حفظ حقوق قانونی کارکنان واحد.', 35580]
|
||||||
0
llm-answer/result.txt
Normal file → Executable file
0
llm-answer/result.txt
Normal file → Executable file
112
llm-answer/samples.json
Executable file
112
llm-answer/samples.json
Executable file
File diff suppressed because one or more lines are too long
6
normalizer.py
Normal file → Executable file
6
normalizer.py
Normal file → Executable file
|
|
@ -1,4 +1,4 @@
|
||||||
import hazm
|
#import hazm
|
||||||
from cleantext import clean
|
from cleantext import clean
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
@ -7,7 +7,7 @@ def cleanhtml(raw_html):
|
||||||
cleantext = re.sub(cleanr, '', raw_html)
|
cleantext = re.sub(cleanr, '', raw_html)
|
||||||
return cleantext
|
return cleantext
|
||||||
|
|
||||||
normalizer = hazm.Normalizer()
|
#normalizer = hazm.Normalizer()
|
||||||
wierd_pattern = re.compile("["
|
wierd_pattern = re.compile("["
|
||||||
u"\U0001F600-\U0001F64F" # emoticons
|
u"\U0001F600-\U0001F64F" # emoticons
|
||||||
u"\U0001F300-\U0001F5FF" # symbols & pictographs
|
u"\U0001F300-\U0001F5FF" # symbols & pictographs
|
||||||
|
|
@ -64,7 +64,7 @@ def cleaning(text):
|
||||||
text = cleanhtml(text)
|
text = cleanhtml(text)
|
||||||
|
|
||||||
# normalizing
|
# normalizing
|
||||||
text = normalizer.normalize(text)
|
#text = normalizer.normalize(text)
|
||||||
|
|
||||||
# removing wierd patterns
|
# removing wierd patterns
|
||||||
text = wierd_pattern.sub(r'', text)
|
text = wierd_pattern.sub(r'', text)
|
||||||
|
|
|
||||||
63
oss.py
Normal file
63
oss.py
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
from openai import AsyncOpenAI
|
||||||
|
|
||||||
|
LLM_URL = "http://172.16.29.102:8001/v1/"
|
||||||
|
|
||||||
|
# item structure:
|
||||||
|
# item = {
|
||||||
|
# 'id' : '',
|
||||||
|
# 'system_prompt' : '',
|
||||||
|
# 'user_prompt' : '',
|
||||||
|
# 'assistant_prompt' : '',
|
||||||
|
# }
|
||||||
|
|
||||||
|
async def process_item(messages, reasoning_effort= 'medium', temperature= 0.4, top_p= 0.9, max_tokens= 2048):
|
||||||
|
"""
|
||||||
|
generates answer with gpt-oss-120b model
|
||||||
|
|
||||||
|
**Args:
|
||||||
|
reasoning_effort = 'medium' # -> low / high / medium
|
||||||
|
temperature = 0.4 # 0-1 -> creativity
|
||||||
|
top_p = 0.9 # 0-1 -> logic
|
||||||
|
max_tokens = 2048 # -> ... 128K
|
||||||
|
** Returns(tuple):
|
||||||
|
returns True, generated answer / False, failed message
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
async with AsyncOpenAI(base_url= LLM_URL, api_key="EMPTY") as client:
|
||||||
|
|
||||||
|
model_name = 'gpt-oss-120b'
|
||||||
|
|
||||||
|
# messages = [
|
||||||
|
# {"role": "system", "content": prompt_params.get("system_prompt", "")},
|
||||||
|
# {"role": "user", "content": prompt_params.get("user_prompt", "")},
|
||||||
|
# ]
|
||||||
|
# if prompt_params.get("assistant_prompt"):
|
||||||
|
# messages.append(
|
||||||
|
# {"role": "assistant", "content": prompt_params["assistant_prompt"]}
|
||||||
|
# )
|
||||||
|
# print(f'==== max_token {max_token}')
|
||||||
|
|
||||||
|
response = await client.chat.completions.parse(
|
||||||
|
model= model_name,
|
||||||
|
messages= messages,
|
||||||
|
temperature= temperature, # 0-1
|
||||||
|
top_p=top_p, # 0-1
|
||||||
|
reasoning_effort= reasoning_effort, # low , high , medium
|
||||||
|
# max_tokens= max_tokens, # ... 128K
|
||||||
|
stop= None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# print('666666666666666666666666666666666')
|
||||||
|
# print(f"response.choices[0].message.parsed: {response.choices[0].message.parsed}")
|
||||||
|
# print('666666666666666666666666666666666')
|
||||||
|
|
||||||
|
if response and response.choices : # and response.choices[0].message.parsed:
|
||||||
|
response_message = response.choices[0].message.content
|
||||||
|
return True, response_message
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
response_message = 'error in llm response generation!'
|
||||||
|
print('!!!!!!!!!!!!!!!!!!!!!!!!!')
|
||||||
|
print(e)
|
||||||
|
print('!!!!!!!!!!!!!!!!!!!!!!!!!')
|
||||||
|
return False, response_message
|
||||||
0
requirements.txt
Normal file → Executable file
0
requirements.txt
Normal file → Executable file
3
run_docker.bash
Executable file
3
run_docker.bash
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
sudo docker stop qachat
|
||||||
|
sudo docker rm qachat
|
||||||
|
sudo docker run --name qachat -p 2425:80 --net qachat --gpus=all -v ./:/src/app/ -v ./qavanin-faiss/:/src/app/qavanin-faiss/ -v ./llm-answer/:/src/app/llm-answer/ -v ./../MODELS:/src/MODELS -v ./../cache:/root/.cache/huggingface/hub -it --restart unless-stopped docker.tavasi.ir/tavasi/qachat:1.0.0
|
||||||
Loading…
Reference in New Issue
Block a user