697 lines
31 KiB
Python
697 lines
31 KiB
Python
import json
|
||
import os
|
||
import numpy as np
|
||
import torch
|
||
import faiss
|
||
from typing import List, Tuple
|
||
import fasttext
|
||
import fasttext.util
|
||
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 embedder import PersianVectorAnalyzer
|
||
from normalizer import cleaning
|
||
from openai import OpenAI
|
||
from langchain_openai import ChatOpenAI
|
||
import requests
|
||
from query_analyzer import build_messages
|
||
|
||
today = f'{datetime.datetime.now().year}{datetime.datetime.now().month}{datetime.datetime.now().day}'
|
||
|
||
# -------------------
|
||
# مدلها و مسیر داده
|
||
# -------------------
|
||
FASTTEXT_MODEL_PATH = "./models/khamenei_all_180_clean_model.bin" # مسیر مدل FastText فارسی
|
||
RERANKER_MODEL = "BAAI/bge-reranker-v2-m3"
|
||
# RERANKER_MODEL = "/home/gpu/HFHOME/hub/models/BAAI/bge-reranker-v2-m3"
|
||
FAISS_INDEX_PATH = "./data-faiss/faiss_index_khamenei.index"
|
||
FAISS_METADATA_PATH = "./data-faiss/faiss_index_khamenei_metadata.json"
|
||
|
||
RERANK_BATCH = int(os.environ.get("RERANK_BATCH", 256))
|
||
|
||
def get_key():
|
||
key = 'aa-mWzDZYqxBgoF78REIluRFy1T38aOV79tcvUGqFblXObSk4xF' # khamenei_bale chat
|
||
return key
|
||
|
||
def load_faiss_index(index_path: str, metadata_path: str):
|
||
index = faiss.read_index(index_path)
|
||
with open(metadata_path, "r", encoding="utf-8") as f:
|
||
metadata = json.load(f)
|
||
ids, dates, content_list, titles, urls = [], [], [], [], []
|
||
for item in metadata:
|
||
ids.append(item["id"])
|
||
dates.append(item["date"])
|
||
content_list.append(item["sentence"])
|
||
titles.append(item["title"])
|
||
urls.append(item["url"])
|
||
return ids, dates, titles, content_list, urls, index
|
||
|
||
def get_client():
|
||
url = "https://api.avalai.ir/v1"
|
||
client = OpenAI(
|
||
api_key=get_key(),
|
||
base_url=url,
|
||
)
|
||
return client
|
||
|
||
def llm_free_request(llm_messages, model="gemini-2.5-flash-lite"):
|
||
print(f'llm_free_request func ...')
|
||
print(f'using model: {model}')
|
||
client = get_client()
|
||
try:
|
||
response = client.chat.completions.create(
|
||
messages=llm_messages,
|
||
model=model,
|
||
)
|
||
answer = response.choices[0].message.content
|
||
|
||
except Exception as error:
|
||
with open('./leader-answer/error-in-llm-free-req.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
|
||
|
||
def llm_request(query, model="gemini-2.5-flash-lite"):
|
||
print(f'using model: {model}')
|
||
if query == '':
|
||
return 'لطفا متن سوال را وارد نمائید'
|
||
|
||
determine_refrence = """شناسه هر سخنرانی در ابتدای آن و با فرمت "id: {idvalue}" آمده است..."""
|
||
try:
|
||
messages.append({"role": "user", "content": query})
|
||
response = client.chat.completions.create(
|
||
messages=messages,
|
||
model=model,
|
||
)
|
||
answer = response.choices[0].message.content
|
||
messages.append({"role": "assistant", "content": answer})
|
||
except Exception as error:
|
||
with open('./leader-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 QueryAnalysisPipeline:
|
||
|
||
def __init__(self):
|
||
"""
|
||
llm_client → شیء کلاینت مدل زبانی شما (هر چیزی باشد)
|
||
model_name → برای شمارش توکنها و ارسال
|
||
"""
|
||
# self.llm = llm_client
|
||
# self.model_name = model_name
|
||
|
||
async def analyze(self, user_query: str):
|
||
"""
|
||
مرحله ۱: تحلیل کوئری (سلام + زیربخشها)
|
||
خروجی JSON تحلیل شده را برمیگرداند
|
||
"""
|
||
messages = build_messages(
|
||
user_query=user_query,
|
||
max_tokens=1024
|
||
)
|
||
|
||
result = llm_free_request(messages)
|
||
|
||
# پردازش JSON خروجی
|
||
try:
|
||
parsed = json.loads(result)
|
||
except Exception:
|
||
# تلاش دوم → استخراج JSON از متن
|
||
try:
|
||
import re
|
||
json_text = re.search(r"\{.*\}", result, re.S).group(0)
|
||
parsed = json.loads(json_text)
|
||
except Exception as e:
|
||
parsed = {
|
||
"greeting_reply": "",
|
||
"sub_questions": [],
|
||
"final_answer_instruction": ""
|
||
}
|
||
print(f'final exception error: {e}')
|
||
|
||
return parsed or []
|
||
|
||
async def expand_sub_questions(self, sub_questions):
|
||
"""
|
||
مرحله ۲: برای هر زیربخش، پاسخ جدا تولید میشود
|
||
"""
|
||
answers = []
|
||
for i, question in enumerate(sub_questions):
|
||
print(f'answering questions {i}/{len(sub_questions)}')
|
||
# messages = [
|
||
# {"role": "user", "content": question}
|
||
# ]
|
||
# resp = llm_free_request(messages)
|
||
resp = bale_chat(question)
|
||
answers.append({"question": question, "answer": resp})
|
||
return answers
|
||
|
||
async def final_answer(self, main_question, answer_parts):
|
||
"""
|
||
مرحله ۳: تولید پاسخ نهایی با استفاده از:
|
||
- سوال اصلی
|
||
- پاسخهای جزئی
|
||
"""
|
||
merged = f"""
|
||
سوال اصلی:
|
||
{main_question}
|
||
|
||
پاسخهای جزئی:
|
||
{answer_parts}
|
||
|
||
لطفاً پاسخ نهایی را فقط بر اساس «پاسخهای جزئی» تولید کن.
|
||
پاسخهای جزئی، برگرفته از بیانات رهبر انقلاب اسلامی ایران است.
|
||
در هر بخش از پاسخ نهایی تولید شده، عنوان سخنرانی و تاریخ آن را(در صورت وجود در پاسخهای جزئی)، ذکر کن.
|
||
"""
|
||
messages = [
|
||
{"role": "user", "content": merged}
|
||
]
|
||
print(f'generating main question based on sub answers ...')
|
||
return llm_free_request(messages)
|
||
|
||
|
||
# -----------------------------
|
||
# Hybrid Retriever with FastText
|
||
# -----------------------------
|
||
class HybridRetrieverReranker:
|
||
__slots__ = (
|
||
"device", "content_list", "ids","dates","titles","urls", "N", "embedder", "faiss_index",
|
||
"vectorizer", "tfidf_matrix", "tokenizer", "reranker", "dense_alpha"
|
||
)
|
||
# ids, dates, titles, content_list, urls,
|
||
def __init__(self, ids: List[str], dates: List[str], titles: List[str], content_list: List[str],urls: 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.dates = dates
|
||
self.titles = titles
|
||
self.urls = urls
|
||
self.faiss_index = faiss_index
|
||
self.N = len(content_list)
|
||
|
||
# --- Dense Embedder: FastText ---
|
||
print("Loading FastText model ...")
|
||
self.embedder = fasttext.load_model(FASTTEXT_MODEL_PATH)
|
||
|
||
# --- Sparse (TF-IDF) ---
|
||
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, local_files_only= True)
|
||
# self.reranker = AutoModelForSequenceClassification.from_pretrained(
|
||
# RERANKER_MODEL, local_files_only= True
|
||
# ).to(self.device)
|
||
|
||
self.dense_alpha = float(dense_alpha)
|
||
|
||
# --- Dense retrieval using FastText ---
|
||
def _embed_sentence(self, text: str) -> np.ndarray:
|
||
tokens = text.split()
|
||
vectors = [self.embedder.get_word_vector(tok) for tok in tokens if tok.strip()]
|
||
if not vectors:
|
||
return np.zeros((self.embedder.get_dimension(),), dtype=np.float32)
|
||
return np.mean(vectors, axis=0).astype(np.float32)
|
||
|
||
def dense_retrieve(self, query: str, top_k: int):
|
||
if top_k <= 0:
|
||
return [], np.array([], dtype=np.float32)
|
||
q_emb = self._embed_sentence(query)
|
||
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]
|
||
|
||
# --- Normalization ---
|
||
@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
|
||
|
||
# --- Fusion (RRF) ---
|
||
def fuse(self, d_idx, d_scores, s_idx, s_scores, top_k=50, k_rrf=60):
|
||
combined = {}
|
||
for rank, idx in enumerate(d_idx):
|
||
score = 1.0 / (k_rrf + rank)
|
||
combined[idx] = combined.get(idx, 0) + score
|
||
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
|
||
|
||
# --- Rerank ---
|
||
def rerank(self, query: str, candidate_indices: List[int], passages: List[str], final_k: int) -> List[Tuple[int, float]]:
|
||
if final_k <= 0 or not candidate_indices:
|
||
return []
|
||
texts = [query] * len(candidate_indices)
|
||
pairs = passages
|
||
scores: List[float] = []
|
||
|
||
def _iter_batches(max_bs: int):
|
||
bs = max_bs
|
||
while bs >= 16:
|
||
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
|
||
|
||
success = _iter_batches(max_bs=64)
|
||
if not success:
|
||
raise RuntimeError("Reranker failed due to CUDA OOM.")
|
||
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 = [content_list[idx] for idx in cand_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)
|
||
# print('---------- dddd scores ----------')
|
||
# for item in d_scores:
|
||
# print(item)
|
||
# print("---------- ssss scores ----------")
|
||
# for item in s_scores:
|
||
# print(item)
|
||
pre_rerank_k = final_k
|
||
cand_idx = self.fuse(d_idx, d_scores, s_idx, s_scores, pre_rerank_k)
|
||
# for item in d_scores:
|
||
# print(item)
|
||
# print('&&&&&&&&&&&&&&&&&&&&&&&&&')
|
||
passages = self.get_passages(cand_idx, content_list)
|
||
# for itemm in s_scores:
|
||
# print(itemm)
|
||
|
||
# print('&&&&&&&&&&&&&&&&&&&&&&&&&')
|
||
# print('&&&&&&&&&&&&&&&&&&&&&&&&&')
|
||
# reranked = self.rerank(query, cand_idx, passages, final_k)
|
||
# return [
|
||
# {"idx": i, "content": self.content_list[i], "rerank_score": score}
|
||
# for i, score in reranked
|
||
# ]
|
||
with open("./data/0passages.json", 'w',encoding='utf-8') as file:
|
||
data = json.dump(passages, file, ensure_ascii=False, indent=4)
|
||
|
||
# return [
|
||
# {"idx": i, "content": self.content_list[i], "rerank_score": score}
|
||
# for i, score in reranked
|
||
# ]
|
||
return [
|
||
{"idx": i, "content": self.content_list[i]}
|
||
for i in cand_idx
|
||
]
|
||
|
||
|
||
def single_query(query: str):
|
||
|
||
query = cleaning(query)
|
||
|
||
retrived_sections = pipe.search(query, content_list, topk_dense=100, topk_sparse=100, pre_rerank_k=100, final_k=17)
|
||
|
||
final_retrived_sections = []
|
||
final_similars = ''
|
||
idlist = ''
|
||
for i, row in enumerate(retrived_sections):
|
||
if row['content'] and len(row['content'].split()) < 15:
|
||
continue
|
||
title_value = '{' + str(titles[row['idx']]) + '}'
|
||
date_value = '{' + str(dates[row['idx']]) + '}'
|
||
result = f"{i+1}. عنوان بخش: {title_value}\nتاریخ: {date_value}\n {row['content']}\n\n"
|
||
final_similars += ''.join(result)
|
||
idlist += f"{ids[row['idx']]}\n"
|
||
final_retrived_sections.append(row)
|
||
return idlist, final_similars, final_retrived_sections# 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
|
||
ids, dates, titles, content_list, urls, faiss_index = load_faiss_index(FAISS_INDEX_PATH, FAISS_METADATA_PATH)
|
||
pipe = HybridRetrieverReranker(ids, dates, titles, content_list, urls, faiss_index, dense_alpha=0.6)
|
||
# query preprocess and normalize
|
||
# normalizer_obj = PersianVectorAnalyzer()
|
||
|
||
messages = [
|
||
{"role": "system", "content": "تو یک دستیار خبره در زمینه تحلیل سخنرانی رهبر انقلاب اسلامی ایران -حضرت آیت الله خامنهای- هستی. پاسخ ها باید الزاما به زبان فارسی باشد. پاسخ ها فقط از متونی که در پرامپت ارائه می شود استخراج شود و از هیچ منبع دیگری استفاده نشود."},
|
||
]
|
||
client = get_client()
|
||
models = [ "gemini-2.5-flash-lite", "gpt-4o-mini","deepseek-reasoner"]
|
||
|
||
def save_result(chat_obj: object) -> bool:
|
||
# index result in elastic
|
||
pass
|
||
|
||
def run_chatbot(query:str, chat_id:str):
|
||
prompt_status = True
|
||
status_text = 'لطفا متن سوال را وارد نمائید'
|
||
if query == '':
|
||
prompt_status = False
|
||
|
||
start_time = (datetime.datetime.now())
|
||
|
||
# در صورتی که وضعیت پرامپت معتبر باشد، وارد فرایند شو
|
||
if prompt_status:
|
||
idlist, 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}"'''
|
||
|
||
llm_model = ''
|
||
for model in models:
|
||
try:
|
||
llm_model = model
|
||
llm_answer = llm_request(prompt, model)
|
||
except Exception as error:
|
||
error = f'model: {model} \n{error}\n\n'
|
||
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' : result_passages_text, # str
|
||
'retrived_ref_ids' : result_passages_ids, # list[obj]
|
||
'prompt_type' : 'question-answer', # str
|
||
'retrived_duration' : retrive_duration, # str
|
||
'llm_duration' : '0', # str
|
||
'full_duration' : '0', # str
|
||
'time_create' : str(start_time), # str
|
||
'used_ref_ids' : [], # list[str]
|
||
'prompt_answer' : '', # str
|
||
'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 = 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 = {
|
||
'id' : chat_id, # str
|
||
'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
|
||
'time_create' : str(start_time), # str
|
||
'used_ref_ids' : used_refrences_in_answer, # list[str]
|
||
'prompt_answer' : llm_answer, # str
|
||
'status' : True, # or False # bool
|
||
}
|
||
# 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)
|
||
|
||
save_result(chat_obj)
|
||
|
||
status_text ='پاسخ با موفقیت ایجاد شد'
|
||
return chat_obj, status_text
|
||
|
||
# @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
|
||
# class Query(BaseModel):
|
||
# 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
|
||
# @chatbot.post("/run_chatbot")
|
||
def chat(query):
|
||
|
||
answer = run_chatbot(query.query, chat_id)
|
||
credit_refresh()
|
||
|
||
return {"answer": answer}
|
||
|
||
pipe_qa = QueryAnalysisPipeline()
|
||
|
||
async def bale_complex_chat(user_query: str):
|
||
# مرحله 1
|
||
analysis = await pipe_qa.analyze(user_query)
|
||
print("Analysis:", analysis)
|
||
|
||
# مرحله 2
|
||
sub_answers = await pipe_qa.expand_sub_questions(analysis["sub_questions"])
|
||
sub_qa = []
|
||
for i, item in enumerate(analysis["sub_questions"]):
|
||
sub_qa.append({
|
||
'question': item,
|
||
'answer': sub_answers[i]['answer']
|
||
})
|
||
print(f'sub answers count: {len(sub_answers)}')
|
||
|
||
answers_text = ''.join(f'{ans['answer']}\n\n' for ans in sub_answers)
|
||
# مرحله 3
|
||
final_answer = await pipe_qa.final_answer(user_query, answers_text)
|
||
final_result = {
|
||
'final_answer': final_answer,
|
||
'sub_qa': sub_qa
|
||
}
|
||
return final_result
|
||
|
||
def bale_search(query):
|
||
start = datetime.datetime.now()
|
||
idlist, result_passages, retrived_sections = single_query(query)
|
||
end_retrive = datetime.datetime.now()
|
||
print('-'*40)
|
||
print(f'retrive duration: {(end_retrive - start).total_seconds()}')
|
||
# پاسخ حداکثر 300 کلمه باشد.
|
||
|
||
refrences = ''
|
||
# recognized_refrences = find_refrences(llm_answer)
|
||
# llm_answer = replace_refrences(llm_answer, recognized_refrences)
|
||
|
||
# with open('./leader-answer/result-khamenei.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('./leader-answer/passages-khamenei.txt', mode='a+', encoding='utf-8') as file:
|
||
# result_message = f'متن پرامپت: {query.strip()}\nمواد مشابه: {result_passages} \n----------------------------------------------------------\n'
|
||
# file.write(result_message)
|
||
|
||
# with open('./leader-answer/chat-leader.txt', mode='w', encoding='utf-8') as file:
|
||
# result_message = f'{query.strip()}\n\nپاسخ:\n {llm_answer} \n----------------------------------------------------------\n'
|
||
# file.write(result_message)
|
||
|
||
print('---------------------------------------------')
|
||
print(f'full duration: {(datetime.datetime.now() - start).total_seconds()}')
|
||
print('---------------------------------------------')
|
||
print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
|
||
|
||
return result_passages
|
||
|
||
def bale_chat(query):
|
||
start = datetime.datetime.now()
|
||
idlist, result_passages, retrived_sections = single_query(query)
|
||
end_retrive = datetime.datetime.now()
|
||
print('-'*40)
|
||
print(f'retrive duration: {(end_retrive - start).total_seconds()}')
|
||
# پاسخ حداکثر 300 کلمه باشد.
|
||
# prompt = f'برای پرسش "{query}" از میان متن های "{result_passages}" .پاسخ مناسب را استخراج کن. پاسخ از زبان رهبر انقلاب اسلامی -حضرت آیت الله خامنهای- بیان می شود. پاسخ به صورت تحلیلی باشد و ابعاد مختلف پرسش را در نظر بگیرد. پاسخ تولید شده، باید متن های مرتبط با پرسش را به صورت علمی بازنویسی کند. برای هر بخش از متن که در تولید پاسخ استفاده می کنی، عنوان آن بخش و تاریخ را نیز حتما در متن اضافه کن. درصورتی که مطلبی مرتبط با پرسش در متن پیدا نشد، فقط پاسخ بده: "متاسفانه در منابع، پاسخی پیدا نشد!"'
|
||
prompt = f'''برای پرسش "{query}" از میان بیانات رهبر معظم انقلاب، پاسخ مناسب را استخراج کن. پاسخ به صورت تحلیلی باشد و ابعاد مختلف پرسش را در نظر بگیرد. پاسخ تولید شده، باید محتوای بیانات مرتبط با پرسش را به صورت علمی بازنویسی کند. متن خروجی بدون مارک داون و استایل باشد. برای هر کدام از بیانات که در تولید پاسخ استفاده می کنی، عنوان آن سخنرانی و تاریخ را نیز در پاسخ اضافه کن. بیانات رهبر معظم انقلاب اسلامی: "{result_passages}"'''
|
||
|
||
llm_answer = llm_request(prompt)# "deepseek-reasoner"
|
||
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('./leader-answer/result-khamenei.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('./leader-answer/passages-khamenei.txt', mode='a+', encoding='utf-8') as file:
|
||
# result_message = f'متن پرامپت: {query.strip()}\nمواد مشابه: {result_passages} \n----------------------------------------------------------\n'
|
||
# file.write(result_message)
|
||
|
||
# with open('./leader-answer/chat-leader.txt', mode='w', encoding='utf-8') as file:
|
||
# result_message = f'{query.strip()}\n\nپاسخ:\n {llm_answer} \n----------------------------------------------------------\n'
|
||
# file.write(result_message)
|
||
|
||
print('---------------------------------------------')
|
||
print(f'full duration: {(datetime.datetime.now() - start).total_seconds()}')
|
||
print('---------------------------------------------')
|
||
print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
|
||
|
||
return llm_answer
|
||
|
||
|
||
# uvicorn src.app:app --reload
|
||
|
||
if __name__ == "__main__":
|
||
|
||
# query = 'در قانون حمایت از خانواده و جوانی جمعیت چه خدماتی در نظر گرفته شده است؟'
|
||
while True:
|
||
query = input('enter your qustion:')
|
||
if query == '':
|
||
with open('./leader-answer/chat-leader.txt', 'r', encoding='utf-8') as file:
|
||
query = file.read()
|
||
|
||
start = (datetime.datetime.now())
|
||
# result = test_dataset()
|
||
idlist, result_passages, retrived_sections = single_query(query)
|
||
end_retrive = datetime.datetime.now()
|
||
print('-'*40)
|
||
print(f'retrive duration: {(end_retrive - start).total_seconds()}')
|
||
|
||
# prompt = f'برای پرسش "{query}" از میان متن های "{result_passages}" .پاسخ مناسب را استخراج کن. پاسخ از زبان رهبر انقلاب اسلامی -حضرت آیت الله خامنهای- بیان می شود. پاسخ به صورت تحلیلی باشد و ابعاد مختلف پرسش را در نظر بگیرد. پاسخ تولید شده، باید متن های مرتبط با پرسش را به صورت علمی بازنویسی کند. برای هر بخش از متن که در تولید پاسخ استفاده می کنی، عنوان آن بخش و تاریخ را نیز حتما در متن اضافه کن. درصورتی که مطلبی مرتبط با پرسش در متن پیدا نشد، فقط پاسخ بده: "متاسفانه در منابع، پاسخی پیدا نشد!"'
|
||
prompt = f'''برای پرسش "{query}" از میان بیانات رهبر معظم انقلاب، پاسخ مناسب را استخراج کن. پاسخ به صورت تحلیلی باشد و ابعاد مختلف پرسش را در نظر بگیرد. پاسخ تولید شده، باید محتوای بیانات مرتبط با پرسش را به صورت علمی بازنویسی کند. برای هر بخش از بیانات که در تولید پاسخ استفاده می کنی، عنوان آن و تاریخ را نیز در پاسخ اضافه کن. بیانات رهبر معظم انقلاب اسلامی: "{result_passages}"'''
|
||
|
||
llm_answer = llm_request(prompt)# "deepseek-reasoner"
|
||
|
||
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('./leader-answer/result-khamenei.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('./leader-answer/passages-khamenei.txt', mode='a+', encoding='utf-8') as file:
|
||
result_message = f'متن پرامپت: {query.strip()}\nمواد مشابه: {result_passages} \n----------------------------------------------------------\n'
|
||
file.write(result_message)
|
||
|
||
with open('./leader-answer/chat-leader.txt', mode='w', encoding='utf-8') as file:
|
||
result_message = f'{query.strip()}\n\nپاسخ:\n {llm_answer} \n----------------------------------------------------------\n'
|
||
file.write(result_message)
|
||
|
||
print('---------------------------------------------')
|
||
print(f'full duration: {(datetime.datetime.now() - start).total_seconds()}')
|
||
print('---------------------------------------------')
|
||
print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
|