From 1472bf0e9fcba1f120aa6dfd5932a96d9d180c74 Mon Sep 17 00:00:00 2001 From: init_mahdi Date: Thu, 27 Nov 2025 20:31:12 +0000 Subject: [PATCH] first step --- monir/.env | 4 + monir/__init__.py | 0 monir/__pycache__/base_model.cpython-310.pyc | Bin 0 -> 9592 bytes monir/__pycache__/es_helper.cpython-310.pyc | Bin 0 -> 18659 bytes monir/__pycache__/llm_helper.cpython-310.pyc | Bin 0 -> 9703 bytes monir/base_model.py | 339 ++++++ monir/doc_type.py | 349 ++++++ monir/es_helper.py | 1134 ++++++++++++++++++ monir/llm_helper.py | 368 ++++++ monir/main.py | 88 ++ monir/requirements.txt | 4 + 11 files changed, 2286 insertions(+) create mode 100644 monir/.env create mode 100644 monir/__init__.py create mode 100644 monir/__pycache__/base_model.cpython-310.pyc create mode 100644 monir/__pycache__/es_helper.cpython-310.pyc create mode 100644 monir/__pycache__/llm_helper.cpython-310.pyc create mode 100644 monir/base_model.py create mode 100644 monir/doc_type.py create mode 100644 monir/es_helper.py create mode 100644 monir/llm_helper.py create mode 100644 monir/main.py create mode 100644 monir/requirements.txt diff --git a/monir/.env b/monir/.env new file mode 100644 index 0000000..8a81336 --- /dev/null +++ b/monir/.env @@ -0,0 +1,4 @@ +ES_URL = 'http://192.168.23.60/9200' +ES_USER_NAME = 'elastic' +ES_PASSWORD = '1234' +LLM_URL = 'http://2.188.15.102:8001/v1/' diff --git a/monir/__init__.py b/monir/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/monir/__pycache__/base_model.cpython-310.pyc b/monir/__pycache__/base_model.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70bac45c88d85ea809ad5b7879a7df45936ae187 GIT binary patch literal 9592 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 HOHcnVr!GJ7 literal 0 HcmV?d00001 diff --git a/monir/__pycache__/es_helper.cpython-310.pyc b/monir/__pycache__/es_helper.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1f7922bb9e5ceb1c0b2cebbed10973960301b9b GIT binary patch literal 18659 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 a-X*Y)$dh^mFDnB~Y(<(m6A-{M`u_tU>P3(M literal 0 HcmV?d00001 diff --git a/monir/__pycache__/llm_helper.cpython-310.pyc b/monir/__pycache__/llm_helper.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b77563c73a48a8db41f31ae29fb0d3ca0b8cb2b GIT binary patch literal 9703 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", index_name) + self.counter = 0 + sid = None + + out = out_name + if out_name == "": + out = index_name + + s_res = self.es.search(index=index_name, scroll="5m", size=1000, body=body) + self.total = s_res["hits"]["total"]["value"] + + print("start index = %s" % index_name) + print("total = %d" % self.total) + + sid = s_res["_scroll_id"] + scroll_size = len(s_res["hits"]["hits"]) + pack_count = 1 + out_json = [] + if mode == "dict" or collapse_field: + out_json = {} + + prev_collapse_value = "" + pack_collapse = [] + while scroll_size > 0: + "Scrolling..." + self.counter += scroll_size + print("progress -> %.2f %%" % ((self.counter / self.total) * 100)) + + #### for test + # if pack_count > 2 : + # break + pack_count += 1 + ############################# + for item in s_res["hits"]["hits"]: + id = item["_id"] + item2 = None + if mode == "id": # فقط شناسه ها نیاز هست + item2 = id + elif fields: + item2 = {} + item2["id"] = id + for kf in fields: + # print(kf) + if kf in item["_source"]: + # print(item['_source'][kf]) + item2[kf] = item["_source"][kf] + elif "." in kf: + cols = kf.split(".") + subsource = item["_source"] + for sub in cols: + if sub in subsource: + subsource = subsource[sub] + continue + else: + break + key = kf.replace(".", "__") + item2[key] = subsource + + # exit() + else: + item2 = {} + item2 = item + + if collapse_field and collapse_field in item["_source"]: + collapse_value = item["_source"][collapse_field] + if not prev_collapse_value: + prev_collapse_value = collapse_value + + if not collapse_value == prev_collapse_value: + out_json[prev_collapse_value] = pack_collapse + pack_collapse = [] + prev_collapse_value = collapse_value + + pack_collapse.append(item2) + + elif mode == "dict": + out_json[id] = item2 + else: + out_json.append(item2) + + s_res = self.es.scroll(scroll_id=sid, scroll="2m", request_timeout=100000) + sid = s_res["_scroll_id"] + scroll_size = len(s_res["hits"]["hits"]) + + sid = None + + if collapse_field and prev_collapse_value and pack_collapse: + out_json[prev_collapse_value] = pack_collapse + + with open(path_back + "/" + out, "w", encoding="utf-8") as fout: + json.dump(out_json, fout, ensure_ascii=False, indent=4) + + ############################## + + # ----------------------------start--------------------------- + # متد backupIndexToZipfile: + # نوع ورودی: + # - path_back: مسیر ذخیره فایل (str) + # - index_name: نام اندیس (str) + # - out_name: نام فایل خروجی (str) - پیش‌فرض خالی + # - body: بدنه جستجو (dict) - پیش‌فرض {"size":1000} + # - byzip: تعیین فرمت خروجی (bool) - پیش‌فرض True + # - fields: لیست فیلدهای مورد نیاز (list) - پیش‌فرض خالی + # - noFields: لیست فیلدهای مورد حذف (list) - پیش‌فرض خالی + # نوع خروجی: bool (True اگر داده وجود داشته باشد) + # عملیات: + # - داده‌های اندیس را با استفاده از scroll API دریافت می‌کند + # - پیشرفت عملیات را نمایش می‌دهد + # - داده‌ها را در فایل ZIP یا JSON ذخیره می‌کند + # - اگر fields مشخص شده باشد، فقط فیلدهای مورد نظر را استخراج می‌کند + # - اگر noFields مشخص شده باشد، فیلدهای مورد نظر را حذف می‌کند + # -----------------------------end---------------------------- + def backupIndexToZipfile( + self, + path_back, + index_name, + file_name="", + body={"size": 1000}, + byzip=True, + fields=[], + noFields=[], + ): + print("*" * 50, " start backup -->", index_name) + self.counter = 0 + sid = None + + out = index_name + + if file_name == "": + file_name = index_name + + if body == {}: + s_res = self.es.search(index=index_name, scroll="5m", size=1000) + else: + s_res = self.es.search(index=index_name, scroll="5m", body=body) + self.total = s_res["hits"]["total"]["value"] + if self.total == 0: + print("total index_name by query = %d" % self.total) + return False + + if byzip: + fout = zipfile.ZipFile(path_back + "/" + file_name + ".zip", "w") + else: + fout = open(path_back + "/" + file_name + ".json", "a+", encoding="utf-8") + + print("start index = %s" % index_name) + print("total = %d" % self.total) + + sid = s_res["_scroll_id"] + scroll_size = len(s_res["hits"]["hits"]) + file_count = 1 + prev_percent = 0 + while scroll_size > 0: + "Scrolling..." + self.counter += scroll_size + percent = int((self.counter / self.total) * 100) + if percent != prev_percent: + print("progress -> %.2f %%" % percent) + prev_percent = percent + ############################# + out_json = [] + for item in s_res["hits"]["hits"]: + if fields: + item2 = {} + item2["id"] = item["_id"] + item2["_source"] = {} + for kf in fields: + if kf in item["_source"]: + item2["_source"][kf] = item["_source"][kf] + else: + item2 = item + + if noFields: + for kf in noFields: + if kf in item2["_source"]: + del item2["_source"][kf] + + out_json.append(item2) + + text = json.dumps(out_json, ensure_ascii=False) + out_json = [] + if byzip: + filename = out + str(file_count) + ".json" + file_count += 1 + fout.writestr(filename, text.encode("utf-8"), zipfile.ZIP_DEFLATED) + else: + fout.write(text) + + ############################## + s_res = self.es.scroll(scroll_id=sid, scroll="2m", request_timeout=100000) + sid = s_res["_scroll_id"] + scroll_size = len(s_res["hits"]["hits"]) + sid = None + fout.close() + + # ----------------------------start--------------------------- + # متد restorFileToElastic: + # نوع ورودی: + # - path_back: مسیر فایل (str) + # - index_name: نام اندیس (str) + # - app_key: کلید برنامه برای مپینگ (str) - پیش‌فرض خالی + # - queryDelete: تعیین حذف اندیس قبل از بازیابی (bool) - پیش‌فرض True + # - map_name: نام فایل مپینگ (str) - پیش‌فرض خالی + # نوع خروجی: bool + # عملیات: + # - وجود فایل ZIP را بررسی می‌کند + # - اگر queryDelete=True باشد، از کاربر تأیید حذف را می‌گیرد + # - اندیس را ایجاد می‌کند + # - داده‌ها را از فایل ZIP به Elasticsearch بازیابی می‌کند + # -----------------------------end---------------------------- + def restorFileToElastic( + self, path_back, index_name, app_key="", queryDelete=True, map_name="" + ): + if not os.path.exists(path_back): + print(" **** error *** path not exist: ", path_back) + return False + + file_path = path_back + "/" + index_name + ".zip" + if not os.path.exists(file_path): + return False + + if queryDelete: + # اگر وجود داشته باشد، از کاربر برای حذفش سوال میکند + if self.deleteIndex(index_name): + self.createIndex(index_name, app_key, map_name) + self.zipFileToElastic(file_path, index_name) + else: # اگر وجود داشته باشد پرش می کند و کاری نمیکند + self.createIndex(index_name, app_key, map_name) + self.zipFileToElastic(file_path, index_name) + + def restorFileToElastic2( + self, path_file, index_name, app_key="", queryDelete=True, map_name="" + ): + if not os.path.exists(path_file): + print(" **** error *** path not exist: ", path_file) + return False + + file_path = path_file + if not os.path.exists(file_path): + return False + + if queryDelete: + # اگر وجود داشته باشد، از کاربر برای حذفش سوال میکند + if self.deleteIndex(index_name): + self.createIndex(index_name, app_key, map_name) + self.zipFileToElastic(file_path, index_name) + else: # اگر وجود داشته باشد پرش می کند و کاری نمیکند + self.createIndex(index_name, app_key, map_name) + self.zipFileToElastic(file_path, index_name) + + # ----------------------------start--------------------------- + # متد renameElasticIndex: + # نوع ورودی: + # - index_name_i: نام اندیس منبع (str) + # - index_name_o: نام اندیس مقصد (str) + # - app_key: کلید برنامه برای مپینگ (str) - پیش‌فرض خالی + # - map_name: نام فایل مپینگ (str) - پیش‌فرض خالی + # نوع خروجی: بدون خروجی مستقیم + # عملیات: + # - اندیس مقصد را ایجاد می‌کند + # - با استفاده از reindex API، داده‌ها را از اندیس منبع به مقصد منتقل می‌کند + # - پیشرفت عملیات را نمایش می‌دهد + # -----------------------------end---------------------------- + def renameElasticIndex(self, index_name_i, index_name_o, app_key="", map_name=""): + + if self.createIndex(index_name_o, app_key, map_name): + res = self.es.reindex( + body={ + "source": {"index": index_name_i}, + "dest": {"index": index_name_o}, + }, + wait_for_completion=False, + ) + + print(type(res)) + print(res) + + taskid = res["task"] if res["task"] else "" + # tasks = client.TasksClient(self.es) + tasks = self.es.tasks + while True: + res = tasks.get(task_id=taskid) + if res["completed"]: + break + + # print( res["task"]) + print( + "----", + index_name_o, + " imported : ", + res["task"]["status"]["total"], + " / ", + res["task"]["status"]["created"], + ) + sleep(1) + print("----", index_name_o, " complated") + + # ----------------------------start--------------------------- + # متد deleteIndex: + # نوع ورودی: + # - index_name: نام اندیس (str) + # نوع خروجی: bool + # عملیات: + # - وجود اندیس را بررسی می‌کند + # - از کاربر تأیید حذف را می‌گیرد + # - در صورت تأیید، اندیس را حذف می‌کند + # -----------------------------end---------------------------- + def deleteIndex(self, index_name): + if not self.es.indices.exists(index=index_name): + print(" " * 10, " for delete NOT exist index :", index_name) + return True + + question = "Is DELETE elastic index (" + index_name + ") ? " + if self.query_yes_no(question): + self.es.indices.delete(index=index_name) + print("%" * 10, " Finish DELETE index :", index_name) + return True + else: + return False + + # ----------------------------start--------------------------- + # متد query_yes_no: + # نوع ورودی: + # - question: سوال نمایش داده شده (str) + # - default: پاسخ پیش‌فرض (str) - پیش‌فرض "no" + # نوع خروجی: bool + # عملیات: + # - سوال را به کاربر نمایش می‌دهد + # - پاسخ کاربر را دریافت و اعتبارسنجی می‌کند + # - True برای 'yes'/'y' و False برای 'no'/'n' برمی‌گرداند + # -----------------------------end---------------------------- + def query_yes_no(self, question, default="no"): + valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False} + if default is None: + prompt = " [y/n] " + elif default == "yes": + prompt = " [Y/n] " + elif default == "no": + prompt = " [y/N] " + else: + raise ValueError("invalid default answer: '%s'" % default) + + while True: + print("%" * 10, " quistion ", "%" * 10, "\n") + sys.stdout.write(question + prompt) + choice = input().lower() + if default is not None and choice == "": + return valid[default] + elif choice in valid: + return valid[choice] + else: + sys.stdout.write( + "لطفا یکی از موارد روبرو را وارد کنید : 'yes' or 'no' " + "(or 'y' or 'n').\n" + ) + + + def createIndexIfNotExist(self, index_name_o, mapping_o=""): + """ + # نوع ورودی: + # - index_name_o: نام اندیس (str) + # - mapping_o: مپینگ اندیس (str) - پیش‌فرض خالی + # نوع خروجی: بدون خروجی مستقیم + # عملیات: + # - وجود اندیس را بررسی می‌کند + # - در صورت عدم وجود، اندیس را با مپینگ مشخص شده ایجاد می‌کند + """ + try: + if not self.es.indices.exists(index=index_name_o): + response = self.es.indices.create(index=index_name_o, body=mapping_o) + # print out the response: + print("create index response:", response) + except: + print("....... index exist ! ... not created") + + # ----------------------------start--------------------------- + # متد createIndex: + # نوع ورودی: + # - index_name: نام اندیس (str) + # - app_key: کلید برنامه برای مپینگ (str) - پیش‌فرض خالی + # - map_name: نام فایل مپینگ (str) - پیش‌فرض خالی + # نوع خروجی: bool + # عملیات: + # - وجود اندیس را بررسی می‌کند + # - مسیر فایل مپینگ را تعیین می‌کند + # - فایل مپینگ را خوانده و اندیس را ایجاد می‌کند + # - در صورت عدم یافت فایل مپینگ، خطا نمایش داده می‌شود + # -----------------------------end---------------------------- + def createIndex(self, index_name, app_key="", map_name=""): + + path_base = self.path_mappings + path_mapping1 = path_base + "general/" + if app_key == "": + app_key = "tavasi" + path_mapping2 = path_base + app_key + "/" + + if map_name == "": + map_name = index_name + + if self.es.indices.exists(index=index_name): + print("============== exist index :", index_name) + return True + + if map_name == "mj_rg_section" or map_name == "semantic_search": + map_name = "mj_qa_section" + elif map_name[-3] == "_ai": + map_name = [0 - len(map_name) - 3] + print(map_name) + + mapping_file_path = path_mapping1 + map_name + ".json" + print("mapping_file_path : ", mapping_file_path) + if not os.path.isfile(mapping_file_path): + if not os.path.isfile(mapping_file_path): + mapping_file_path = path_mapping2 + map_name + ".json" + + print("mapping_file_path : ", mapping_file_path) + + # Create Index With Mapping + if os.path.isfile(mapping_file_path): + mapping_file = open(mapping_file_path, "r", encoding="utf-8") + mapping_file_read = mapping_file.read() + mapping_data = json.loads(mapping_file_read) + mapping_file.close() + if self.es.indices.exists(index=index_name): + print("============== exist index :", index_name) + else: + self.es.indices.create(index=index_name, body=mapping_data) + return True + else: + print("*** error not find maping file elastic : *******", mapping_file_path) + return False + + # ----------------------------start--------------------------- + # متد updateBulkList: + # نوع ورودی: + # - listData: لیست داده‌ها (list) + # - index_name: نام اندیس (str) + # نوع خروجی: بدون خروجی مستقیم + # عملیات: + # - داده‌ها را به صورت bulk آپدیت می‌کند + # - از helpers.bulk Elasticsearch استفاده می‌کند + # -----------------------------end---------------------------- + def updateBulkList(self, listData, index_name): + chunk_size = 100000 + raise_on_error = False + raise_on_exception = False + stats_only = True + yield_ok = False + + actions = [] + for item in listData: + actions.append( + { + "_op_type": "update", + "_index": index_name, + "_id": item["_id"], + "doc": item["_source"], + } + ) + helpers.bulk( + self.es, + actions, + chunk_size, + raise_on_error, + raise_on_exception, + stats_only, + yield_ok, + ) + + # ----------------------------start--------------------------- + # متد importBulkList: + # نوع ورودی: + # - listData: لیست داده‌ها (list) + # - index_name: نام اندیس (str) + # نوع خروجی: بدون خروجی مستقیم + # عملیات: + # - داده‌ها را به صورت bulk وارد می‌کند + # - از helpers.bulk Elasticsearch استفاده می‌کند + # -----------------------------end---------------------------- + def importBulkList(self, listData, index_name): + chunk_size = 100000 + raise_on_error = False + raise_on_exception = False + stats_only = True + yield_ok = False + + for item in listData: + actions = [ + { + "_op_type": "index", + "_index": index_name, + "_id": item["_id"], + "_source": item["_source"], + } + ] + helpers.bulk( + self.es, + actions, + chunk_size, + raise_on_error, + raise_on_exception, + stats_only, + yield_ok, + ) + + # ----------------------------start--------------------------- + # متد importJsonDataToElastic: + # نوع ورودی: + # - jsonData: داده‌های JSON (list) + # - index_name: نام اندیس (str) + # نوع خروجی: بدون خروجی مستقیم + # عملیات: + # - داده‌ها را به صورت bulk وارد می‌کند + # - از helpers.bulk Elasticsearch استفاده می‌کند + # -----------------------------end---------------------------- + def importJsonDataToElastic(self, jsonData, index_name, fields=[]): + chunk_size = 100000 + raise_on_error = False + raise_on_exception = False + stats_only = True + yield_ok = False + + actions = [] + + for item in jsonData: + id = item["_id"] if item["_id"] else item["id"] + source = item["_source"] + if fields: + source = {} + for col in fields: + if col in item["_source"]: + source[col] = item["_source"] + + actions.append( + { + "_op_type": "index", + "_index": index_name, + "_id": id, + "_source": source, + } + ) + helpers.bulk( + self.es, + actions, + chunk_size, + raise_on_error, + raise_on_exception, + stats_only, + yield_ok, + ) + + # ----------------------------start--------------------------- + # متد fileToElastic: + # نوع ورودی: + # - file_path: مسیر فایل (str) + # - index_name: نام اندیس (str) + # - limit_pack: محدودیت تعداد بسته‌ها (int) - پیش‌فرض -1 + # نوع خروجی: بدون خروجی مستقیم + # عملیات: + # - فایل JSON را خوانده و داده‌ها را به Elasticsearch وارد می‌کند + # - اندیس را refresh می‌کند + # - تعداد اسناد را نمایش می‌دهد + # -----------------------------end---------------------------- + def fileToElastic(self, file_path, index_name, limit_pack=-1, fields=[]): + if not os.path.exists(file_path): + print("file zip:", file_path, " not exist") + return + print("index:", index_name, "=>", file_path) + self.counter = 0 + with open(file_path) as file: + data = json.loads(file.read()) + self.importJsonDataToElastic(data, index_name, fields) + + self.es.indices.refresh(index=index_name) + print(self.es.cat.count(index=index_name, format="json")) + + # ----------------------------start--------------------------- + # متد zipFileToElastic: + # نوع ورودی: + # - file_path: مسیر فایل ZIP (str) + # - index_name: نام اندیس (str) + # - limit_pack: محدودیت تعداد فایل‌ها (int) - پیش‌فرض -1 + # نوع خروجی: بدون خروجی مستقیم + # عملیات: + # - فایل ZIP را باز کرده و هر فایل JSON داخل آن را پردازش می‌کند + # - داده‌ها را به Elasticsearch وارد می‌کند + # - اندیس را refresh می‌کند + # - تعداد اسناد را نمایش می‌دهد + # -----------------------------end---------------------------- + def zipFileToElastic(self, file_path, index_name, limit_pack=-1, fields=[]): + if not os.path.exists(file_path): + print( + "file zip:", file_path, " not exist for imort to elastic : ", index_name + ) + return + + fileNo = 0 + with zipfile.ZipFile(file_path, "r") as zObject: + fileNo += 1 + print( + "=" * 10, + " zip fileNo: ", + fileNo, + " - ( ", + index_name, + " ) | File Numbers:", + len(zObject.namelist()), + "=" * 10, + ) + + packNo = 0 + self.counter = 0 + for filename in zObject.namelist(): + packNo += 1 + if limit_pack != -1: + if packNo > limit_pack: + print("limit_data ", index_name, " ", limit_pack) + break + + print("index:", index_name, "=>", filename) + with zObject.open(filename) as file: + data = json.loads(file.read()) + self.importJsonDataToElastic(data, index_name, fields) + + self.es.indices.refresh(index=index_name) + print(self.es.cat.count(index=index_name, format="json")) + print(" END Of Import to elastic ", index_name, "\n") + + # ----------------------------start--------------------------- + # متد iterateJsonFile: + # نوع ورودی: + # - file_path: مسیر فایل (str) + # - isZip: تعیین نوع فایل (ZIP یا JSON) (bool) - پیش‌فرض True + # - limit_pack: محدودیت تعداد فایل‌ها (int) - پیش‌فرض -1 + # نوع خروجی: ژنراتور + # عملیات: + # - اگر isZip=True باشد: فایل ZIP را پردازش می‌کند + # - اگر isZip=False باشد: فایل JSON را پردازش می‌کند + # - داده‌ها را به صورت ژنراتور برمی‌گرداند + # -----------------------------end---------------------------- + def iterateJsonFile(self, file_path, isZip=True, limit_pack=-1): + if not os.path.exists(file_path): + print("file zip:", file_path, " not exist iterateJsonFile ") + return + + if isZip: + fileNo = 0 + with zipfile.ZipFile(file_path, "r") as zObject: + fileNo += 1 + print( + "=" * 10, + " zip fileNo: ", + fileNo, + " iterateJsonFile - | File Numbers:", + len(zObject.namelist()), + "=" * 10, + ) + + packNo = 0 + self.counter = 0 + for filename in zObject.namelist(): + packNo += 1 + if limit_pack != -1: + if packNo > limit_pack: + print("limit_data iterateJsonFile ", limit_pack) + break + + print("index iterateJsonFile :", "=>", filename) + with zObject.open(filename) as file: + data = json.loads(file.read()) + # Yield each entry + # yield data + yield from ( + {"source": hit["_source"], "id": hit["_id"]} for hit in data + ) + else: + with open(filename, "r", encoding="utf-8") as file: + data = json.loads(file.read()) + # Yield each entry + # yield from (hit for hit in data) + # return data + yield from ( + {"source": hit["_source"], "id": hit["_id"]} for hit in data + ) + + # ----------------------------start--------------------------- + # متد es_iterate_all_documents: + # نوع ورودی: + # - index: نام اندیس (str) + # - body: بدنه جستجو (dict) - پیش‌فرض خالی + # - pagesize: اندازه صفحه (int) - پیش‌فرض 250 + # - scroll_timeout: زمان اسکرول (str) - پیش‌فرض "25m" + # - **kwargs: پارامترهای اضافی + # نوع خروجی: ژنراتور + # عملیات: + # - تمام اسناد اندیس را با استفاده از scroll API دریافت می‌کند + # - پیشرفت عملیات را نمایش می‌دهد + # - داده‌ها را به صورت ژنراتور برمی‌گرداند + # -----------------------------end---------------------------- + def es_iterate_all_documents( + self, index, body="", pagesize=250, scroll_timeout="25m", **kwargs + ): + """ + Helper to iterate ALL values from a single index + Yields all the documents. + """ + is_first = True + while True: + # Scroll next + if is_first: # Initialize scroll + # result = self.es.search(index=index, scroll="2m", **kwargs, body={ + # "size": pagesize + # }) + if body: + result = self.es.search( + index=index, + scroll=scroll_timeout, + **kwargs, + size=pagesize, + body=body + ) + else: + result = self.es.search( + index=index, scroll=scroll_timeout, **kwargs, size=pagesize + ) + + self.total = result["hits"]["total"]["value"] + if self.total > 0: + print("total = %d" % self.total) + is_first = False + else: + # result = es.scroll(body={ + # "scroll_id": scroll_id, + # "scroll": scroll_timeout + # }) + result = self.es.scroll(scroll_id=scroll_id, scroll=scroll_timeout) + + scroll_id = result["_scroll_id"] + hits = result["hits"]["hits"] + self.counter += len(hits) + if self.total > 0: + print("progress -> %.2f %%" % ((self.counter / self.total) * 100)) + # Stop after no more docs + if not hits: + break + # Yield each entry + yield from ({"source": hit["_source"], "id": hit["_id"]} for hit in hits) + + def removeCustomFileds( + self, index_name_i, fields=[], renameFileds={}, body={}, bulk_update_size=200 + ): + try: + _list = [] + try: + _list = self.es_iterate_all_documents(index_name_i, body) + except Exception as e: + print(e) + + bulk_data = [] + count = 0 + total_count = 0 + for mentry in _list: + count += 1 + + entry = mentry["source"] + id = mentry["id"] + # print(id) + eid = id + + # if (count % 100) == 0 : + # print("%s -> %.2f " % (id , (count / self.total) if self.total > 0 else 0)) + + data_filled = False + data = entry + for col in fields: + if col in data: + del data[col] + + elif "." in col: + cols = col.split(".") + subsource = entry + for sub in cols: + dCol = subsource.get(sub, None) + if dCol: + subsource = dCol + else: + break + + for col in renameFileds.items(): + if col in data: + dCol = data[col] + data[renameFileds[col]] = dCol + del data[col] + + bulk_data.append({"_id": eid, "_source": data}) + + # انتقال دسته جمعی کدها به الاستیک + if len(bulk_data) >= bulk_update_size: + total_count += len(bulk_data) + print( + "=" * 5, + " update bulk --> ", + "total=" + str(total_count), + str(count), + ) + self.importBulkList(bulk_data, index_name_i) + bulk_data = [] + + if len(bulk_data) >= 0: + total_count += len(bulk_data) + print( + "=" * 5, + " update bulk --> ", + "total=" + str(total_count), + str(count), + ) + self.importBulkList(bulk_data, index_name_i) + bulk_data = [] + + except Exception as e: + # print("1111") + print(e) + + # save_error(id, e) + + # ----------------------------start--------------------------- + # متد moveCustomFileds: + # نوع ورودی: + # - index_name_i: نام اندیس منبع (str) + # - index_name_o: نام اندیس مقصد (str) + # - fields: لیست فیلدهای مورد انتقال (list) - پیش‌فرض خالی + # - renameFileds: دیکشنری تغییر نام فیلدها (dict) - پیش‌فرض خالی + # نوع خروجی: بدون خروجی مستقیم + # عملیات: + # - تمام اسناد اندیس منبع را دریافت می‌کند + # - فیلدهای مشخص شده را استخراج و به اندیس مقصد انتقال می‌دهد + # - اگر renameFileds وجود داشته باشد، نام فیلدها را تغییر می‌دهد + # -----------------------------end---------------------------- + def moveCustomFileds(self, index_name_i, index_name_o, fields=[], renameFileds={}): + try: + body = {} + list = [] + try: + list = self.es_iterate_all_documents(index_name_i) + except Exception as e: + print(e) + + count = 0 + for mentry in list: + count += 1 + + entry = mentry["source"] + id = mentry["id"] + # print(id) + eid = id + + if (count % 100) == 0: + print( + "%s -> %.2f " + % (id, (count / self.total) if self.total > 0 else 0) + ) + + data_filled = False + data = {} + for col in fields: + + if "." in col: + cols = col.split(".") + subsource = entry + for sub in cols: + dCol = subsource.get(sub, None) + if dCol: + subsource = dCol + else: + break + else: + dCol = entry.get(col, None) + + if dCol is None: + continue + + if col in renameFileds: + data[renameFileds[col]] = dCol + else: + data[col] = dCol + + data_filled = True + + if not data_filled: + continue + + try: + resp = self.update_index_doc(True, index_name_o, eid, data) + except Exception as e: + print(e) + # save_error(id, e) + + except Exception as e: + # print("1111") + print(e) + + # save_error(id, e) + + # ----------------------------start--------------------------- + # متد mappingIndex: + # نوع ورودی: + # - index_name_i: نام اندیس (str) + # نوع خروجی: بدون خروجی مستقیم + # عملیات: + # - توضیح می‌دهد که تغییر مپینگ از طریق پایتون امکان‌پذیر نیست + # - باید اندیس جدیدی با مپینگ مطلوب ایجاد و رایندکس شود + # -----------------------------end---------------------------- + def mappingIndex(self, index_name_i): + # فقط از طریق کیبانا میشه تغییر مپ داد + + # با پایتون نمیشه + # باید ایندکس جدیدی با مپ مطلوب ایجاد کرد و رایندکس کرد + pass + + # ----------------------------start--------------------------- + # متد updateByQueryIndex: + # نوع ورودی: + # - index_name_i: نام اندیس (str) + # - body: بدنه آپدیت (dict) + # نوع خروجی: بدون خروجی مستقیم + # عملیات: + # - اسناد را با استفاده از update_by_query API آپدیت می‌کند + # - در صورت خطا، پیام خطا نمایش داده می‌شود + # -----------------------------end---------------------------- + def updateByQueryIndex(self, index_name_i, body): + ## sample + # body = { + # "script": { + # "inline": "ctx._source.Device='Test'", + # "lang": "painless" + # }, + # "query": { + # "match": { + # "Device": "Boiler" + # } + # } + # } + try: + self.es.update_by_query(body=body, index=index_name_i) + + except Exception as e: + print(e) + # save_error(id, e) + + # ----------------------------start--------------------------- + # متد deleteByQueryIndex: + # نوع ورودی: + # - index_name_i: نام اندیس (str) + # - body: بدنه حذف (dict) + # نوع خروجی: بدون خروجی مستقیم + # عملیات: + # - اسناد را با استفاده از delete_by_query API حذف می‌کند + # - در صورت خطا، پیام خطا نمایش داده می‌شود + # -----------------------------end---------------------------- + def deleteByQueryIndex(self, index_name_i, body): + ## sample + # body = { + # "query": { + # "match": { + # "Device": "Boiler" + # } + # } + # } + try: + self.es.delete_by_query(index=index_name_i, body=body) + + except Exception as e: + print(e) + # save_error(id, e) + + # ----------------------------start--------------------------- + # متد delete_by_ids: + # نوع ورودی: + # - index_name_i: نام اندیس (str) + # - ids: لیست شناسه‌ها (list) + # نوع خروجی: بدون خروجی مستقیم + # عملیات: + # - اسناد با شناسه‌های مشخص شده را حذف می‌کند + # - در صورت خطا، پیام خطا نمایش داده می‌شود + # -----------------------------end---------------------------- + def delete_by_ids(self, index_name_i, ids): + try: + # ids = ['test1', 'test2', 'test3'] + + query = {"query": {"terms": {"_id": ids}}} + res = self.es.delete_by_query(index=index_name_i, body=query) + print(res) + + except Exception as e: + print(e) + # save_error(id, e) diff --git a/monir/llm_helper.py b/monir/llm_helper.py new file mode 100644 index 0000000..60739a3 --- /dev/null +++ b/monir/llm_helper.py @@ -0,0 +1,368 @@ +from typing import List +from pathlib import Path +import os, orjson, time, json, re, asyncio, traceback +from openai import AsyncOpenAI + +# -------------------------------------------------------------------- + + +# ------------------------------ پردازش API ------------------------------ +class AsyncCore: + def __init__( + self, + model_name, + task_name, + data_path, + output_schema, + api_url, + reasoning_effort='low', + top_p=1, + temperature=0.0, + max_token=128000, + output_path=None, + ai_code_version=None, + request_timeout=30, # ثانیه + api_key="EMPTY", + save_number=2, + ): + + self.save_number = save_number + # json file of data + self.data_path = data_path + + self.task_name = task_name + if output_path is None: + output_path = f"./{task_name}" + + self.output_path = Path(output_path) + self._temp_path = self.output_path / "batch_data" + self._temp_processed_id_path = self._temp_path / "processed_id.json" + + # Create output directory and subdirectories if they don't exist + self.output_path.mkdir(parents=True, exist_ok=True) + self._temp_path.mkdir(parents=True, exist_ok=True) + # self._temp_processed_id_path.mkdir(parents=True, exist_ok=True) + + self.request_timeout = request_timeout + self.model_name = model_name + self.api_key = api_key + self.output_schema = output_schema + self.api_url = api_url + self.reasoning_effort = reasoning_effort + self.top_p = top_p + self.temperature = temperature + self.max_token = max_token + + if ai_code_version is None: + ai_code_version = f"{model_name}_{reasoning_effort}" + self.ai_code_version = ai_code_version + + self.PRIMARY_KEY = {"system_prompt", "user_prompt", "id"} + + try: + self.data = self.__data_process() + print(f"📦 Loaded {len(self.data)} words") + except Exception as e: + raise ValueError( + f"Data loading/validation failed: {e}\n{traceback.format_exc()}" + ) + + def __validate_item(self, item, idx): + # Mandatory fields + for key in self.PRIMARY_KEY: + if key not in item: + raise ValueError(f"Missing mandatory key '{key}' in item #{idx}") + if not isinstance(item[key], str): + raise TypeError( + f"Item #{idx}: '{key}' must be a string, got {type(item[key]).__name__}" + ) + + # Optional field: assistant_prompt + if "assistant_prompt" not in item or item["assistant_prompt"] is None: + item["assistant_prompt"] = None + else: + if not isinstance(item["assistant_prompt"], str): + raise TypeError( + f"Item #{idx}: 'assistant_prompt' must be a string or absent, got {type(item['assistant_prompt']).__name__}" + ) + + return item # now normalized + + def __data_process(self): + raw_data = self.__load_orjson(self.data_path) + if not isinstance(raw_data, list): + raise ValueError("Data must be a list of dictionaries.") + + processed_data = [] + for idx, item in enumerate(raw_data): + if not isinstance(item, dict): + raise ValueError(f"Item #{idx} is not a dictionary.") + validated_item = self.__validate_item(item, idx) + processed_data.append(validated_item) + + return processed_data + + def __get_max_number_file(self, directory): + # Pattern to match filenames like out_1.json, out_25.json, etc. + pattern = re.compile(r"output_(\d+)\.json$") + max_num = 0 + + for filename in os.listdir(directory): + match = pattern.match(filename) + if match: + num = int(match.group(1)) + if num > max_num: + max_num = num + return max_num + 1 + + def __load_orjson(self, path: str | Path): + path = Path(path) + with path.open("rb") as f: # باید باینری باز بشه برای orjson + return orjson.loads(f.read()) + + def __save_orjson(self, path, data): + with open(path, "wb") as f: + f.write( + orjson.dumps(data, option=orjson.OPT_INDENT_2 | orjson.OPT_NON_STR_KEYS) + ) + + def merge_json_dir(self, input_path, output_path): + directory = Path(input_path) + if not directory.is_dir(): + raise ValueError(f"Not valid PATH: {input_path}") + + seen_ids = set() # برای ردیابی idهای دیده‌شده (سریع!) + unique_data = [] # فقط داده‌های یکتا + failed_files = [] + + json_files = list(directory.glob("*.json")) + if not json_files: + print("⚠️ NO JSON File Found In This PATH") + return + + for json_file in json_files: + try: + data = self.__load_orjson(json_file) + if not data: # خالی یا None + failed_files.append(json_file.name) + continue + + if isinstance(data, list) and isinstance(data[0], dict): + for item in data: + item_id = item.get("id") + if item_id is None: + # اگر id نداشت، می‌تونی تصمیم بگیری: نگه داری یا ردش کنی + # اینجا فرض می‌کنیم فقط مواردی با id معتبر مهم هستند + continue + if item_id not in seen_ids: + seen_ids.add(item_id) + unique_data.append(item) + else: + raise ValueError(f"no list available in this json -> {json_file}") + except ( + json.JSONDecodeError, + ValueError, + OSError, + KeyError, + TypeError, + ) as e: + # print(f"❌ Failed in process '{json_file.name}': {e}") + failed_files.append(json_file.name) + + # گزارش خطاها + if failed_files: + print("\n❌ We lose this file:") + for name in failed_files: + print(f" - {name}") + else: + print("\n✅ All JSON added") + + # ذخیره خروجی + try: + self.__save_orjson(data=unique_data, path=output_path) + print( + f"\n💾 Final file saved: {output_path} (Total unique items: {len(unique_data)})" + ) + except Exception as e: + print(f"❌ Error in saving final file: {e}") + + def make_new_proccessed_ids_from_file(self, json_in, out_path): + data = self.__load_orjson(json_in) + + finall_data = [] + for d in data: + if d["id"]: + finall_data.append(d["id"]) + finall_data = set(finall_data) + finall_data = list(finall_data) + print(f"-- len ids {len(finall_data)}") + + self.__save_orjson(data=finall_data, path=out_path) + + # ------------------------------ Main ------------------------------ + async def __process_item(self, client, item): + try: + messages = [ + {"role": "system", "content": item["system_prompt"]}, + {"role": "user", "content": item["user_prompt"]}, + ] + if item.get("assistant_prompt"): + messages.append( + {"role": "assistant", "content": item["assistant_prompt"]} + ) + + response = await client.chat.completions.parse( + model=self.model_name, + messages=messages, + temperature=self.temperature, + top_p=self.top_p, + reasoning_effort=self.reasoning_effort, + max_tokens=self.max_token, + stop=None, + response_format=self.output_schema, + ) + + parsed = ( + response.choices[0].message.parsed + if response and response.choices and response.choices[0].message.parsed + else {"raw_text": str(response)} + ) + + parsed = self.output_schema.model_validate(parsed) + parsed = dict(parsed) + parsed["ai_code_version"] = self.ai_code_version + parsed["id"] = item["id"] + return parsed, 200 + + except asyncio.TimeoutError: + print(f"⏳ Timeout on item {item['id']}") + return None, 408 + + except Exception as e: + print(f"⚠️ Error __process_item {item['id']}: {traceback.print_exc()}") + return None, 400 + + def async_eval(self, processed_id: List = []): + try: + asyncio.run(self.__async_eval(processed_id)) + except KeyboardInterrupt: + print("\n🛑 Interrupted by user.") + traceback.print_exc() + + async def __async_eval(self, processed_id: List): + """ + اجرای اصلی تک‌هسته‌ای و async برای تولید خروجی نهایی. + """ + print("🔹 Starting async data processing...") + + # ------------------ مرحله ۱: بازیابی شناسه‌های قبلاً پردازش‌شده ------------------ + if not processed_id: + try: + processed_id = self.__load_orjson(self._temp_processed_id_path) + print( + f"📂 Loaded existing processed_id from {self._temp_processed_id_path}" + ) + except Exception: + print("⚠️ No valid processed_id found. Starting fresh.") + processed_id = [] + + # ------------------ مرحله ۲: آماده‌سازی داده‌ها ------------------ + all_processed_id = set(processed_id) + all_results = [] + total_time = [] + + data = [item for item in self.data if item.get("id") not in all_processed_id] + print( + f"➕ Total items: {len(self.data)} - {len(all_processed_id)} = {len(data)}" + ) + + # اگر چیزی برای پردازش نیست + if not data: + print("✅ Nothing new to process. All items are already done.") + return + + # ------------------ مرحله ۳: شروع پردازش ------------------ + print(f"🤖 Model: {self.model_name} | Reasoning: {self.reasoning_effort}") + async with AsyncOpenAI(base_url=self.api_url, api_key=self.api_key) as client: + semaphore = asyncio.Semaphore(5) + + async def limited_process(item): + async with semaphore: + return await self.__process_item(client, item) + + tasks = [asyncio.create_task(limited_process(item)) for item in data] + + total_i = 0 + # ✅ پردازش به ترتیب تکمیل (نه ترتیب لیست) + for i, task in enumerate(asyncio.as_completed(tasks), start=1): + start = time.time() + try: + parsed, status_code = await asyncio.wait_for( + task, timeout=self.request_timeout + ) # ⏱ حداکثر 2 دقیقه + except asyncio.TimeoutError: + print(f"⏳ Task {i} timed out completely") + parsed, status_code = None, 408 + total_time.append(time.time() - start) + + if status_code == 200: + all_results.append(parsed) + all_processed_id.add(parsed.get("id")) + else: + print(f"⚠️ Skipped item {parsed.get('id')} (status={status_code})") + + total_i += 1 + # ✅ ذخیره‌ی موقت هر n مورد + if total_i >= self.save_number: + print(f"total_i {total_i}") + print(f"self.save_number {self.save_number}") + total_i = 0 + self.__save_orjson( + data=list(all_processed_id), + path=self._temp_processed_id_path, + ) + print(f"💾 Auto-saved processed ids: {len(all_processed_id)}") + number = self.__get_max_number_file(self._temp_path) + print(f"number {number}") + temp_output_path = self._temp_path / f"output_{number}.json" + self.__save_orjson(data=list(all_results), path=temp_output_path) + print(f"💾 Auto-saved partial data: {len(all_results)}") + all_results.clear() + + # ✅ بعد از پایان تمام تسک‌ها، ذخیره نهایی برای داده‌های باقیمانده + if total_i > 0 or len(all_results) > 0: + print("💾 Final save of remaining data...") + self.__save_orjson( + data=list(all_processed_id), + path=self._temp_processed_id_path, + ) + print(f"💾 Auto-saved processed ids: {len(all_processed_id)}") + number = self.__get_max_number_file(self._temp_path) + print(f"number {number}") + + temp_output_path = self._temp_path / f"output_{number}.json" + self.__save_orjson(data=list(all_results), path=temp_output_path) + print(f"💾 Auto-saved partial data: {len(all_results)}") + all_results.clear() + + # ------------------ مرحله ۴: ذخیره خروجی ------------------ + final_data_path = self.output_path / f"final_data_{self.task_name}.json" + processed_id_path = self.output_path / "processed_id.json" + + self.merge_json_dir(input_path=self._temp_path, output_path=final_data_path) + all_results = self.__load_orjson(final_data_path) + # make_new_proccessed_ids_from_file() + self.__save_orjson(data=list(all_processed_id), path=processed_id_path) + self.__save_orjson(data=all_results, path=final_data_path) + + avg_time = (sum(total_time) / len(total_time)) if total_time else 0 + print( + f"\n✅ Processing completed!\n" + f"📊 Total-Data: {len(data)} | " + f"⭕ Ignored-Data: {len(processed_id)} | " + f"📦 Proccessed-Data: {len(all_results)} | " + f"❌ Loss-Data: {len(data)-len(all_results)} | " + f"🕒 Avg Time: {avg_time:.2f}'s per item | " + f"🕒 Total Time: {sum(total_time):.4f}'s | " + f"💾 Results saved to: {final_data_path}" + ) diff --git a/monir/main.py b/monir/main.py new file mode 100644 index 0000000..8bed261 --- /dev/null +++ b/monir/main.py @@ -0,0 +1,88 @@ +from dotenv import load_dotenv +import os +from llm_helper import AsyncCore +from es_helper import ElasticHelper +from base_model import MnMeet +import time, traceback, uuid, orjson, re +from datetime import datetime, timezone +from elasticsearch.helpers import scan +from typing import Union +from pathlib import Path +from collections import defaultdict +from typing import List + +load_dotenv() +ES_URL = os.getenv("ES_URL") +ES_USER_NAME = os.getenv("ES_USER_NAME") +ES_PASSWORD = os.getenv("ES_PASSWORD") +LLM_URL = os.getenv("LLM_URL") + +def save_orjson(path, data): + with open(path, "wb") as f: + f.write( + orjson.dumps(data, option=orjson.OPT_INDENT_2 | orjson.OPT_NON_STR_KEYS) + ) + +def load_orjson(path: str | Path): + path = Path(path) + with path.open("rb") as f: # باید باینری باز بشه برای orjson + return orjson.loads(f.read()) + +# --------------------------- flow +term_index_name = "mn_term" +meet_index_name = "mn_meet" +ment_index_name = "mn_meet_entity" +sections_index_name = "" +dash = "-" * 25 + +es_helper = ElasticHelper( + es_url=ES_URL, + es_user=ES_USER_NAME, + es_pass=ES_PASSWORD, +) + +############ DELETE INDEXES +# es_helper.deleteIndex(index_name=term_index_name) +# es_helper.deleteIndex(index_name=meet_index_name) +# es_helper.deleteIndex(index_name=ment_index_name) + +############ CREATE INDEXES +# es_helper.createIndexIfNotExist(index_name_o=term_index_name) +# es_helper.createIndexIfNotExist(index_name_o=meet_index_name) +# es_helper.createIndexIfNotExist(index_name_o=ment_index_name) + + +es = es_helper.es +# fields = list(MnMeet.model_fields.keys()) +fields = [ + "id", + "sanad_id", + "main_type", + "title", + "author", + "content", +] +# old_data = es_helper.search( +# index=old_index_name, _source=fields, query={"match_all": {}}, size=3 +# ) +# old_data = old_data["hits"]["hits"] # don't use in scan +################### for all data +old_data = list( + scan( + es, + index=meet_index_name, + query={ + "_source": fields, + "query": + { "term": + { + "main_type": "جلسه علمی"}}, + # {"match_all": {}}, + }, + ) +) +print(f'--- old_data {len(old_data)}') +save_orjson( + data=old_data, + path='./data_content_1.json' +) \ No newline at end of file diff --git a/monir/requirements.txt b/monir/requirements.txt new file mode 100644 index 0000000..981f548 --- /dev/null +++ b/monir/requirements.txt @@ -0,0 +1,4 @@ +python-dotenv +openai +elasticsearch==8.13.0 +orjson \ No newline at end of file