MrSimple07 commited on
Commit
ba52088
·
1 Parent(s): a7e15db

complete new structure

Browse files
Files changed (5) hide show
  1. app.py +133 -90
  2. config.py +10 -0
  3. documents_prep.py +332 -387
  4. index_retriever.py +61 -192
  5. utils.py +135 -0
app.py CHANGED
@@ -1,83 +1,88 @@
1
  import gradio as gr
2
  import os
 
 
 
 
3
  import sys
4
- import logging
5
- import config
6
- from documents_prep import DocumentsPreparation
7
- from index_retriever import IndexRetriever
8
- from chat_handler import ChatHandler
9
 
10
- REPO_ID = "MrSimple01/AIEXP_RAG_FILES"
11
- HF_TOKEN = os.getenv('HF_TOKEN')
12
 
13
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
14
- logger = logging.getLogger(__name__)
15
-
16
- doc_prep = None
17
- index_retriever = None
18
- chat_handler = None
19
-
20
- def log_message(message):
21
- logger.info(message)
22
- print(message, flush=True)
23
- sys.stdout.flush()
24
-
25
- def initialize_system():
26
- global doc_prep, index_retriever, chat_handler
27
-
28
  try:
29
- log_message("Запуск инициализации системы AIEXP")
 
 
 
 
 
30
 
31
- doc_prep = DocumentsPreparation(REPO_ID, HF_TOKEN)
32
- index_retriever = IndexRetriever(config=config)
33
 
34
- log_message("Подготовка документов")
35
- all_documents = doc_prep.prepare_all_documents()
36
 
37
- if not all_documents:
38
- log_message("Не удалось загрузить документы")
39
- return False
 
 
 
 
 
 
40
 
41
- log_message("Инициализация моделей и индекса")
42
- if not index_retriever.initialize_models(all_documents):
43
- log_message("Не удалось инициализировать модели")
44
- return False
45
 
46
- chat_handler = ChatHandler(index_retriever)
 
 
 
47
 
48
- log_message("Система успешно инициализирована")
49
- return True
 
 
 
 
 
50
 
51
  except Exception as e:
52
- log_message(f"Ошибка инициализации системы: {str(e)}")
53
- return False
54
-
55
- def handle_question(question):
56
- if chat_handler is None:
57
- return "Система не инициализирована", ""
58
- return chat_handler.answer_question(question)
59
-
60
- def handle_model_switch(model_name):
61
- if index_retriever is None:
62
- return "Система не инициализирована"
63
- return index_retriever.switch_model(model_name)
64
-
65
- def get_current_model_status():
66
- if index_retriever is None:
67
- return "Система не инициализирована"
68
- return f"Текущая модель: {index_retriever.get_current_model()}"
69
 
70
- def get_chat_history_html():
71
- if chat_handler is None:
72
- return "Истор��я недоступна"
73
- return chat_handler.get_history_html()
74
-
75
- def clear_chat_history():
76
- if chat_handler is not None:
77
- chat_handler.clear_history()
78
- return "История очищена"
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
- def create_demo_interface():
81
  with gr.Blocks(title="AIEXP - AI Expert для нормативной документации", theme=gr.themes.Soft()) as demo:
82
 
83
  gr.Markdown("""
@@ -92,15 +97,15 @@ def create_demo_interface():
92
  with gr.Row():
93
  with gr.Column(scale=2):
94
  model_dropdown = gr.Dropdown(
95
- choices=list(config.AVAILABLE_MODELS.keys()),
96
- value=config.DEFAULT_MODEL,
97
  label="🤖 Выберите языковую модель",
98
  info="Выберите модель для генерации ответов"
99
  )
100
  with gr.Column(scale=1):
101
  switch_btn = gr.Button("🔄 Переключить модель", variant="secondary")
102
  model_status = gr.Textbox(
103
- value=get_current_model_status(),
104
  label="Статус модели",
105
  interactive=False
106
  )
@@ -129,7 +134,7 @@ def create_demo_interface():
129
  with gr.Column(scale=2):
130
  answer_output = gr.HTML(
131
  label="",
132
- value=f"<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>Здесь появится ответ на ваш вопрос...<br><small>Текущая модель: {config.DEFAULT_MODEL}</small></div>",
133
  )
134
 
135
  with gr.Column(scale=1):
@@ -137,33 +142,68 @@ def create_demo_interface():
137
  label="",
138
  value="<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>Здесь появятся источники...</div>",
139
  )
140
-
141
- switch_btn.click(
142
- fn=handle_model_switch,
143
- inputs=[model_dropdown],
144
- outputs=[model_status]
145
- )
146
-
147
- ask_btn.click(
148
- fn=handle_question,
149
- inputs=[question_input],
150
- outputs=[answer_output, sources_output]
151
- )
152
-
153
- question_input.submit(
154
- fn=handle_question,
155
- inputs=[question_input],
156
- outputs=[answer_output, sources_output]
157
- )
158
 
159
  return demo
160
 
161
- if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  log_message("Запуск AIEXP - AI Expert для нормативной документации")
163
 
164
- if initialize_system():
 
 
 
 
 
 
 
 
 
165
  log_message("Запуск веб-интерфейса")
166
- demo = create_demo_interface()
 
 
 
 
167
  demo.launch(
168
  server_name="0.0.0.0",
169
  server_port=7860,
@@ -172,4 +212,7 @@ if __name__ == "__main__":
172
  )
173
  else:
174
  log_message("Невозможно запустить приложение из-за ошибки инициализации")
175
- sys.exit(1)
 
 
 
 
1
  import gradio as gr
2
  import os
3
+ from llama_index.core import Settings
4
+ from documents_prep import load_json_documents, load_table_data, load_image_data, load_csv_chunks
5
+ from utils import get_llm_model, get_embedding_model, get_reranker_model, log_message, answer_question
6
+ from index_retriever import create_vector_index, create_query_engine
7
  import sys
8
+ from config import (
9
+ HF_REPO_ID, HF_TOKEN, DOWNLOAD_DIR, CHUNKS_FILENAME,
10
+ JSON_FILES_DIR, TABLE_DATA_DIR, IMAGE_DATA_DIR, DEFAULT_MODEL, AVAILABLE_MODELS
11
+ )
 
12
 
 
 
13
 
14
+ def initialize_system(repo_id, hf_token, download_dir, chunks_filename=None,
15
+ json_files_dir=None, table_data_dir=None, image_data_dir=None,
16
+ use_json_instead_csv=False):
 
 
 
 
 
 
 
 
 
 
 
 
17
  try:
18
+ log_message("Инициализация системы")
19
+ os.makedirs(download_dir, exist_ok=True)
20
+
21
+ embed_model = get_embedding_model()
22
+ llm = get_llm_model(DEFAULT_MODEL)
23
+ reranker = get_reranker_model()
24
 
25
+ Settings.embed_model = embed_model
26
+ Settings.llm = llm
27
 
28
+ all_documents = []
29
+ chunks_df = None
30
 
31
+ if use_json_instead_csv and json_files_dir:
32
+ log_message("Используем JSON файлы вместо CSV")
33
+ json_documents = load_json_documents(repo_id, hf_token, json_files_dir, download_dir)
34
+ all_documents.extend(json_documents)
35
+ else:
36
+ if chunks_filename:
37
+ log_message("Загружаем данные из CSV")
38
+ csv_documents, chunks_df = load_csv_chunks(repo_id, hf_token, chunks_filename, download_dir)
39
+ all_documents.extend(csv_documents)
40
 
41
+ if table_data_dir:
42
+ log_message("Добавляю табличные данные")
43
+ table_documents = load_table_data(repo_id, hf_token, table_data_dir)
44
+ all_documents.extend(table_documents)
45
 
46
+ if image_data_dir:
47
+ log_message("Добавляю данные изображений")
48
+ image_documents = load_image_data(repo_id, hf_token, image_data_dir)
49
+ all_documents.extend(image_documents)
50
 
51
+ log_message(f"Всего документов: {len(all_documents)}")
52
+
53
+ vector_index = create_vector_index(all_documents)
54
+ query_engine = create_query_engine(vector_index)
55
+
56
+ log_message(f"Система успешно инициализирована")
57
+ return query_engine, chunks_df, reranker, vector_index
58
 
59
  except Exception as e:
60
+ log_message(f"Ошибка инициализации: {str(e)}")
61
+ return None, None, None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
+ def switch_model(model_name, vector_index):
64
+ from llama_index.core import Settings
65
+ from index_retriever import create_query_engine
66
+
67
+ try:
68
+ log_message(f"Переключение на модель: {model_name}")
69
+
70
+ new_llm = get_llm_model(model_name)
71
+ Settings.llm = new_llm
72
+
73
+ if vector_index is not None:
74
+ new_query_engine = create_query_engine(vector_index)
75
+ log_message(f"Модель успешно переключена на: {model_name}")
76
+ return new_query_engine, f"✅ Модель переключена на: {model_name}"
77
+ else:
78
+ return None, "❌ Ошибка: система не инициализирована"
79
+
80
+ except Exception as e:
81
+ error_msg = f"Ошибка переключения модели: {str(e)}"
82
+ log_message(error_msg)
83
+ return None, f"❌ {error_msg}"
84
 
85
+ def create_demo_interface(answer_question_func, switch_model_func, current_model):
86
  with gr.Blocks(title="AIEXP - AI Expert для нормативной документации", theme=gr.themes.Soft()) as demo:
87
 
88
  gr.Markdown("""
 
97
  with gr.Row():
98
  with gr.Column(scale=2):
99
  model_dropdown = gr.Dropdown(
100
+ choices=list(AVAILABLE_MODELS.keys()),
101
+ value=current_model,
102
  label="🤖 Выберите языковую модель",
103
  info="Выберите модель для генерации ответов"
104
  )
105
  with gr.Column(scale=1):
106
  switch_btn = gr.Button("🔄 Переключить модель", variant="secondary")
107
  model_status = gr.Textbox(
108
+ value=f"Текущая модель: {current_model}",
109
  label="Статус модели",
110
  interactive=False
111
  )
 
134
  with gr.Column(scale=2):
135
  answer_output = gr.HTML(
136
  label="",
137
+ value=f"<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>Здесь появится ответ на ваш вопрос...<br><small>Текущая модель: {current_model}</small></div>",
138
  )
139
 
140
  with gr.Column(scale=1):
 
142
  label="",
143
  value="<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>Здесь появятся источники...</div>",
144
  )
145
+
146
+ switch_btn.click(
147
+ fn=switch_model_func,
148
+ inputs=[model_dropdown],
149
+ outputs=[model_status]
150
+ )
151
+
152
+ ask_btn.click(
153
+ fn=answer_question_func,
154
+ inputs=[question_input],
155
+ outputs=[answer_output, sources_output]
156
+ )
157
+
158
+ question_input.submit(
159
+ fn=answer_question_func,
160
+ inputs=[question_input],
161
+ outputs=[answer_output, sources_output]
162
+ )
163
 
164
  return demo
165
 
166
+ query_engine = None
167
+ chunks_df = None
168
+ reranker = None
169
+ vector_index = None
170
+ current_model = DEFAULT_MODEL
171
+
172
+ def main_answer_question(question):
173
+ global query_engine, reranker, current_model, chunks_df
174
+ return answer_question(question, query_engine, reranker, current_model, chunks_df)
175
+
176
+ def main_switch_model(model_name):
177
+ global query_engine, vector_index, current_model
178
+
179
+ new_query_engine, status_message = switch_model(model_name, vector_index)
180
+ if new_query_engine:
181
+ query_engine = new_query_engine
182
+ current_model = model_name
183
+
184
+ return status_message
185
+
186
+ def main():
187
+ global query_engine, chunks_df, reranker, vector_index, current_model
188
+
189
  log_message("Запуск AIEXP - AI Expert для нормативной документации")
190
 
191
+ query_engine, chunks_df, reranker, vector_index = initialize_system(
192
+ repo_id=HF_REPO_ID,
193
+ hf_token=HF_TOKEN,
194
+ download_dir=DOWNLOAD_DIR,
195
+ json_files_dir=JSON_FILES_DIR,
196
+ table_data_dir=TABLE_DATA_DIR,
197
+ image_data_dir=IMAGE_DATA_DIR,
198
+ )
199
+
200
+ if query_engine:
201
  log_message("Запуск веб-интерфейса")
202
+ demo = create_demo_interface(
203
+ answer_question_func=main_answer_question,
204
+ switch_model_func=main_switch_model,
205
+ current_model=current_model
206
+ )
207
  demo.launch(
208
  server_name="0.0.0.0",
209
  server_port=7860,
 
212
  )
213
  else:
214
  log_message("Невозможно запустить приложение из-за ошибки инициализации")
215
+ sys.exit(1)
216
+
217
+ if __name__ == "__main__":
218
+ main()
config.py CHANGED
@@ -6,6 +6,16 @@ SIMILARITY_THRESHOLD = 0.7
6
  RAG_FILES_DIR = "rag_files"
7
  PROCESSED_DATA_FILE = "processed_chunks.csv"
8
 
 
 
 
 
 
 
 
 
 
 
9
  GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
10
  OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
11
  HF_REPO_ID = "MrSimple01/AIEXP_RAG_FILES"
 
6
  RAG_FILES_DIR = "rag_files"
7
  PROCESSED_DATA_FILE = "processed_chunks.csv"
8
 
9
+ REPO_ID = "MrSimple01/AIEXP_RAG_FILES"
10
+ faiss_index_filename = "cleaned_faiss_index.index"
11
+ CHUNKS_FILENAME = "processed_chunks.csv"
12
+ TABLE_DATA_DIR = "Табличные данные_JSON"
13
+ IMAGE_DATA_DIR = "Изображения"
14
+ DOWNLOAD_DIR = "rag_files"
15
+ JSON_FILES_DIR ="JSON"
16
+ HF_TOKEN = os.getenv('HF_TOKEN')
17
+
18
+
19
  GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
20
  OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
21
  HF_REPO_ID = "MrSimple01/AIEXP_RAG_FILES"
documents_prep.py CHANGED
@@ -1,431 +1,376 @@
1
  import json
2
- import pandas as pd
3
- import os
4
  import zipfile
 
5
  from huggingface_hub import hf_hub_download, list_repo_files
6
  from llama_index.core import Document
7
- import logging
8
-
9
- logger = logging.getLogger(__name__)
10
-
11
- def log_message(message):
12
- logger.info(message)
13
- print(message, flush=True)
14
 
15
- class DocumentsPreparation:
16
- def __init__(self, repo_id, hf_token):
17
- self.repo_id = repo_id
18
- self.hf_token = hf_token
19
- self.json_files_dir = "JSON"
20
- self.table_data_dir = "Табличные данные_JSON"
21
- self.image_data_dir = "Изображения"
22
- self.download_dir = "rag_files"
23
-
24
- def extract_text_from_json(self, data, document_id, document_name):
25
- documents = []
26
-
27
- if 'sections' in data:
28
- for section in data['sections']:
29
- section_id = section.get('section_id', 'Unknown')
30
- section_text = section.get('section_text', '')
31
-
32
- if section_text.strip():
33
- doc = Document(
34
- text=section_text,
35
- metadata={
36
- "type": "text",
37
- "document_id": document_id,
38
- "document_name": document_name,
39
- "section_id": section_id,
40
- "level": "section"
41
- }
42
- )
43
- documents.append(doc)
44
-
45
- if 'subsections' in section:
46
- for subsection in section['subsections']:
47
- subsection_id = subsection.get('subsection_id', 'Unknown')
48
- subsection_text = subsection.get('subsection_text', '')
49
-
50
- if subsection_text.strip():
51
- doc = Document(
52
- text=subsection_text,
53
- metadata={
54
- "type": "text",
55
- "document_id": document_id,
56
- "document_name": document_name,
57
- "section_id": section_id,
58
- "subsection_id": subsection_id,
59
- "level": "subsection"
60
- }
61
- )
62
- documents.append(doc)
63
-
64
- if 'sub_subsections' in subsection:
65
- for sub_subsection in subsection['sub_subsections']:
66
- sub_subsection_id = sub_subsection.get('sub_subsection_id', 'Unknown')
67
- sub_subsection_text = sub_subsection.get('sub_subsection_text', '')
68
-
69
- if sub_subsection_text.strip():
70
- doc = Document(
71
- text=sub_subsection_text,
72
- metadata={
73
- "type": "text",
74
- "document_id": document_id,
75
- "document_name": document_name,
76
- "section_id": section_id,
77
- "subsection_id": subsection_id,
78
- "sub_subsection_id": sub_subsection_id,
79
- "level": "sub_subsection"
80
- }
81
- )
82
- documents.append(doc)
83
-
84
- if 'sub_sub_subsections' in sub_subsection:
85
- for sub_sub_subsection in sub_subsection['sub_sub_subsections']:
86
- sub_sub_subsection_id = sub_sub_subsection.get('sub_sub_subsection_id', 'Unknown')
87
- sub_sub_subsection_text = sub_sub_subsection.get('sub_sub_subsection_text', '')
88
-
89
- if sub_sub_subsection_text.strip():
90
- doc = Document(
91
- text=sub_sub_subsection_text,
92
- metadata={
93
- "type": "text",
94
- "document_id": document_id,
95
- "document_name": document_name,
96
- "section_id": section_id,
97
- "subsection_id": subsection_id,
98
- "sub_subsection_id": sub_subsection_id,
99
- "sub_sub_subsection_id": sub_sub_subsection_id,
100
- "level": "sub_sub_subsection"
101
- }
102
- )
103
- documents.append(doc)
104
-
105
- return documents
106
-
107
- def extract_zip_and_process_json(self, zip_path):
108
- """Extract ZIP file and process JSON files inside"""
109
- documents = []
110
-
111
- try:
112
- with zipfile.ZipFile(zip_path, 'r') as zip_ref:
113
- # Get list of files in ZIP
114
- zip_files = zip_ref.namelist()
115
- json_files = [f for f in zip_files if f.endswith('.json') and not f.startswith('__MACOSX')]
116
-
117
- log_message(f"Найдено {len(json_files)} JSON файлов в архиве")
118
-
119
- for json_file in json_files:
120
- try:
121
- log_message(f"Обрабатываю файл из архива: {json_file}")
122
-
123
- # Read JSON file from ZIP
124
- with zip_ref.open(json_file) as f:
125
- json_data = json.load(f)
126
-
127
- document_metadata = json_data.get('document_metadata', {})
128
- document_id = document_metadata.get('document_id', 'unknown')
129
- document_name = document_metadata.get('document_name', 'unknown')
130
-
131
- docs = self.extract_text_from_json(json_data, document_id, document_name)
132
- documents.extend(docs)
133
-
134
- log_message(f"Извлечено {len(docs)} документов из {json_file}")
135
-
136
- except Exception as e:
137
- log_message(f"Ошибка обработки файла {json_file}: {str(e)}")
138
- continue
139
-
140
- except Exception as e:
141
- log_message(f"Ошибка извлечения ZIP архива {zip_path}: {str(e)}")
142
-
143
- return documents
144
-
145
- def load_json_documents(self):
146
- log_message("Начинаю загрузку JSON документов")
147
-
148
- try:
149
- files = list_repo_files(repo_id=self.repo_id, repo_type="dataset", token=self.hf_token)
150
- zip_files = [f for f in files if f.startswith(self.json_files_dir) and f.endswith('.zip')]
151
- json_files = [f for f in files if f.startswith(self.json_files_dir) and f.endswith('.json')]
152
-
153
- log_message(f"Найдено {len(zip_files)} ZIP файлов и {len(json_files)} прямых JSON файлов")
154
 
155
- all_documents = []
 
 
 
 
 
 
 
 
 
 
 
156
 
157
- for zip_file_path in zip_files:
158
- try:
159
- log_message(f"Загружаю ZIP архив: {zip_file_path}")
160
- local_zip_path = hf_hub_download(
161
- repo_id=self.repo_id,
162
- filename=zip_file_path,
163
- local_dir=self.download_dir,
164
- repo_type="dataset",
165
- token=self.hf_token
166
- )
167
 
168
- documents = self.extract_zip_and_process_json(local_zip_path)
169
- all_documents.extend(documents)
 
 
 
 
 
 
 
 
 
 
 
170
 
171
- except Exception as e:
172
- log_message(f"Ошибка обработки ZIP файла {zip_file_path}: {str(e)}")
173
- continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
- # Process direct JSON files (if any)
176
- for file_path in json_files:
 
177
  try:
178
- log_message(f"Обрабатываю прямой JSON файл: {file_path}")
179
- local_path = hf_hub_download(
180
- repo_id=self.repo_id,
181
- filename=file_path,
182
- local_dir=self.download_dir,
183
- repo_type="dataset",
184
- token=self.hf_token
185
- )
186
 
187
- with open(local_path, 'r', encoding='utf-8') as f:
188
  json_data = json.load(f)
189
 
190
  document_metadata = json_data.get('document_metadata', {})
191
  document_id = document_metadata.get('document_id', 'unknown')
192
  document_name = document_metadata.get('document_name', 'unknown')
193
 
194
- documents = self.extract_text_from_json(json_data, document_id, document_name)
195
- all_documents.extend(documents)
196
 
197
- log_message(f"Извлечено {len(documents)} документов из {file_path}")
198
 
199
  except Exception as e:
200
- log_message(f"Ошибка обработки файла {file_path}: {str(e)}")
201
  continue
202
-
203
- log_message(f"Всего создано {len(all_documents)} текстовых документов")
204
- return all_documents
205
-
206
- except Exception as e:
207
- log_message(f"Ошибка загрузки JSON документов: {str(e)}")
208
- return []
209
 
210
- def table_to_document(self, table_data, document_id=None):
211
- content = ""
212
- if isinstance(table_data, dict):
213
- doc_id = document_id or table_data.get('document_id', table_data.get('document', 'Неизвестно'))
214
-
215
- table_num = table_data.get('table_number', 'Неизвестно')
216
- table_title = table_data.get('table_title', 'Неизвестно')
217
- section = table_data.get('section', 'Неизвестно')
218
-
219
- content += f"Таблица: {table_num}\n"
220
- content += f"Название: {table_title}\n"
221
- content += f"Документ: {doc_id}\n"
222
- content += f"Раздел: {section}\n"
223
-
224
- if 'data' in table_data and isinstance(table_data['data'], list):
225
- for row in table_data['data']:
226
- if isinstance(row, dict):
227
- row_text = " | ".join([f"{k}: {v}" for k, v in row.items()])
228
- content += f"{row_text}\n"
229
 
230
- return Document(
231
- text=content,
232
- metadata={
233
- "type": "table",
234
- "table_number": table_data.get('table_number', 'unknown'),
235
- "table_title": table_data.get('table_title', 'unknown'),
236
- "document_id": doc_id or table_data.get('document_id', table_data.get('document', 'unknown')),
237
- "section": table_data.get('section', 'unknown')
238
- }
239
- )
240
-
241
- def extract_zip_and_process_tables(self, zip_path):
242
- """Extract ZIP file and process table JSON files inside"""
243
- documents = []
244
 
245
- try:
246
- with zipfile.ZipFile(zip_path, 'r') as zip_ref:
247
- zip_files = zip_ref.namelist()
248
- json_files = [f for f in zip_files if f.endswith('.json') and not f.startswith('__MACOSX')]
 
 
 
 
 
 
249
 
250
- log_message(f"Найдено {len(json_files)} JSON файлов таблиц в архиве")
 
251
 
252
- for json_file in json_files:
253
- try:
254
- log_message(f"Обрабатываю файл таблицы из архива: {json_file}")
255
-
256
- # Read JSON file from ZIP
257
- with zip_ref.open(json_file) as f:
258
- table_data = json.load(f)
259
-
260
- if isinstance(table_data, dict):
261
- document_id = table_data.get('document', 'unknown')
262
-
263
- if 'sheets' in table_data:
264
- for sheet in table_data['sheets']:
265
- sheet['document'] = document_id
266
- doc = self.table_to_document(sheet, document_id)
267
- documents.append(doc)
268
- else:
269
- doc = self.table_to_document(table_data, document_id)
270
- documents.append(doc)
271
- elif isinstance(table_data, list):
272
- for table_json in table_data:
273
- doc = self.table_to_document(table_json)
274
- documents.append(doc)
275
-
276
- except Exception as e:
277
- log_message(f"Ошибка обработки файла таблицы {json_file}: {str(e)}")
278
- continue
 
 
 
279
 
280
- except Exception as e:
281
- log_message(f"Ошибка извлечения ZIP архива таблиц {zip_path}: {str(e)}")
282
 
283
- return documents
 
 
284
 
285
- def load_table_documents(self):
286
- log_message("Начинаю загрузку табличных данных")
 
 
287
 
288
- try:
289
- files = list_repo_files(repo_id=self.repo_id, repo_type="dataset", token=self.hf_token)
290
-
291
- # Look for ZIP files in the table directory
292
- zip_files = [f for f in files if f.startswith(self.table_data_dir) and f.endswith('.zip')]
293
- # Also look for direct JSON files (fallback)
294
- table_files = [f for f in files if f.startswith(self.table_data_dir) and f.endswith('.json')]
295
-
296
- log_message(f"Найдено {len(zip_files)} ZIP файлов с таблицами и {len(table_files)} прямых JSON файлов")
297
-
298
- table_documents = []
299
-
300
- # Process ZIP files first
301
- for zip_file_path in zip_files:
302
- try:
303
- log_message(f"Загружаю ZIP архив таблиц: {zip_file_path}")
304
- local_zip_path = hf_hub_download(
305
- repo_id=self.repo_id,
306
- filename=zip_file_path,
307
- local_dir=self.download_dir,
308
- repo_type="dataset",
309
- token=self.hf_token
310
- )
311
-
312
- documents = self.extract_zip_and_process_tables(local_zip_path)
313
- table_documents.extend(documents)
314
-
315
- except Exception as e:
316
- log_message(f"Ошибка обработки ZIP файла таблиц {zip_file_path}: {str(e)}")
317
- continue
318
-
319
- # Process direct JSON files (if any)
320
- for file_path in table_files:
321
- try:
322
- log_message(f"Обрабатываю прямой файл таблицы: {file_path}")
323
- local_path = hf_hub_download(
324
- repo_id=self.repo_id,
325
- filename=file_path,
326
- local_dir=self.download_dir,
327
- repo_type="dataset",
328
- token=self.hf_token
329
- )
 
 
 
 
 
 
 
 
 
 
330
 
331
- with open(local_path, 'r', encoding='utf-8') as f:
332
- table_data = json.load(f)
333
 
334
- if isinstance(table_data, dict):
335
- document_id = table_data.get('document', 'unknown')
336
-
337
- if 'sheets' in table_data:
338
- for sheet in table_data['sheets']:
339
- sheet['document'] = document_id
340
- doc = self.table_to_document(sheet, document_id)
341
- table_documents.append(doc)
342
- else:
343
- doc = self.table_to_document(table_data, document_id)
344
- table_documents.append(doc)
345
- elif isinstance(table_data, list):
346
- for table_json in table_data:
347
- doc = self.table_to_document(table_json)
348
  table_documents.append(doc)
349
-
350
- except Exception as e:
351
- log_message(f"Ошибка обработки файла {file_path}: {str(e)}")
352
- continue
353
-
354
- log_message(f"Создано {len(table_documents)} документов из таблиц")
355
- return table_documents
356
-
357
- except Exception as e:
358
- log_message(f"Ошибка загрузки табличных данных: {str(e)}")
359
- return []
 
 
 
 
 
 
 
360
 
361
- def load_image_documents(self):
362
- log_message("Нач��наю загрузку данных изображений")
 
 
 
 
 
 
 
363
 
364
- try:
365
- files = list_repo_files(repo_id=self.repo_id, repo_type="dataset", token=self.hf_token)
366
- image_files = [f for f in files if f.startswith(self.image_data_dir) and f.endswith('.csv')]
367
-
368
- log_message(f"Найдено {len(image_files)} CSV файлов с изображениями")
369
-
370
- image_documents = []
371
- for file_path in image_files:
372
- try:
373
- log_message(f"Обрабатываю файл изображений: {file_path}")
374
- local_path = hf_hub_download(
375
- repo_id=self.repo_id,
376
- filename=file_path,
377
- local_dir=self.download_dir,
378
- repo_type="dataset",
379
- token=self.hf_token
380
- )
381
-
382
- df = pd.read_csv(local_path)
383
- log_message(f"Загружено {len(df)} записей изображений из файла {file_path}")
 
 
 
 
384
 
385
- for _, row in df.iterrows():
386
- content = f"Изображение: {row.get('№ Изображения', 'Неизвестно')}\n"
387
- content += f"Название: {row.get('Название изображения', 'Неизвестно')}\n"
388
- content += f"Описание: {row.get('Описание изображение', 'Неизвестно')}\n"
389
- content += f"Документ: {row.get('Обозначение документа', 'Неизвестно')}\n"
390
- content += f"Раздел: {row.get('Раздел документа', 'Неизвестно')}\n"
391
- content += f"Файл: {row.get('Файл изображения', 'Неизвестно')}\n"
 
 
 
 
392
 
393
- doc = Document(
394
- text=content,
395
- metadata={
396
- "type": "image",
397
- "image_number": row.get('№ Изображения', 'unknown'),
398
- "document_id": row.get('Обозначение документа', 'unknown'),
399
- "file_path": row.get('Фа��л изображения', 'unknown'),
400
- "section": row.get('Раздел документа', 'unknown')
401
- }
402
- )
403
- image_documents.append(doc)
404
-
405
- except Exception as e:
406
- log_message(f"Ошибка обработки файла {file_path}: {str(e)}")
407
- continue
408
-
409
- log_message(f"Создано {len(image_documents)} документов из изображений")
410
- return image_documents
411
-
412
- except Exception as e:
413
- log_message(f"Ошибка загрузки данных изображений: {str(e)}")
414
- return []
415
 
416
- def prepare_all_documents(self):
417
- log_message("Подготовка всех документов")
 
 
 
 
 
 
 
 
 
418
 
419
- all_documents = []
 
420
 
421
- json_documents = self.load_json_documents()
422
- all_documents.extend(json_documents)
 
 
 
423
 
424
- table_documents = self.load_table_documents()
425
- all_documents.extend(table_documents)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
 
427
- image_documents = self.load_image_documents()
428
- all_documents.extend(image_documents)
429
 
430
- log_message(f"Всего подготовлено {len(all_documents)} документов")
431
- return all_documents
 
 
1
  import json
 
 
2
  import zipfile
3
+ import pandas as pd
4
  from huggingface_hub import hf_hub_download, list_repo_files
5
  from llama_index.core import Document
6
+ from utils import log_message
 
 
 
 
 
 
7
 
8
+ def extract_text_from_json(data, document_id, document_name):
9
+ documents = []
10
+
11
+ if 'sections' in data:
12
+ for section in data['sections']:
13
+ section_id = section.get('section_id', 'Unknown')
14
+ section_text = section.get('section_text', '')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
+ if section_text.strip():
17
+ doc = Document(
18
+ text=section_text,
19
+ metadata={
20
+ "type": "text",
21
+ "document_id": document_id,
22
+ "document_name": document_name,
23
+ "section_id": section_id,
24
+ "level": "section"
25
+ }
26
+ )
27
+ documents.append(doc)
28
 
29
+ if 'subsections' in section:
30
+ for subsection in section['subsections']:
31
+ subsection_id = subsection.get('subsection_id', 'Unknown')
32
+ subsection_text = subsection.get('subsection_text', '')
 
 
 
 
 
 
33
 
34
+ if subsection_text.strip():
35
+ doc = Document(
36
+ text=subsection_text,
37
+ metadata={
38
+ "type": "text",
39
+ "document_id": document_id,
40
+ "document_name": document_name,
41
+ "section_id": section_id,
42
+ "subsection_id": subsection_id,
43
+ "level": "subsection"
44
+ }
45
+ )
46
+ documents.append(doc)
47
 
48
+ if 'sub_subsections' in subsection:
49
+ for sub_subsection in subsection['sub_subsections']:
50
+ sub_subsection_id = sub_subsection.get('sub_subsection_id', 'Unknown')
51
+ sub_subsection_text = sub_subsection.get('sub_subsection_text', '')
52
+
53
+ if sub_subsection_text.strip():
54
+ doc = Document(
55
+ text=sub_subsection_text,
56
+ metadata={
57
+ "type": "text",
58
+ "document_id": document_id,
59
+ "document_name": document_name,
60
+ "section_id": section_id,
61
+ "subsection_id": subsection_id,
62
+ "sub_subsection_id": sub_subsection_id,
63
+ "level": "sub_subsection"
64
+ }
65
+ )
66
+ documents.append(doc)
67
+
68
+ if 'sub_sub_subsections' in sub_subsection:
69
+ for sub_sub_subsection in sub_subsection['sub_sub_subsections']:
70
+ sub_sub_subsection_id = sub_sub_subsection.get('sub_sub_subsection_id', 'Unknown')
71
+ sub_sub_subsection_text = sub_sub_subsection.get('sub_sub_subsection_text', '')
72
+
73
+ if sub_sub_subsection_text.strip():
74
+ doc = Document(
75
+ text=sub_sub_subsection_text,
76
+ metadata={
77
+ "type": "text",
78
+ "document_id": document_id,
79
+ "document_name": document_name,
80
+ "section_id": section_id,
81
+ "subsection_id": subsection_id,
82
+ "sub_subsection_id": sub_subsection_id,
83
+ "sub_sub_subsection_id": sub_sub_subsection_id,
84
+ "level": "sub_sub_subsection"
85
+ }
86
+ )
87
+ documents.append(doc)
88
+
89
+ return documents
90
+
91
+ def extract_zip_and_process_json(zip_path):
92
+ documents = []
93
+
94
+ try:
95
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
96
+ zip_files = zip_ref.namelist()
97
+ json_files = [f for f in zip_files if f.endswith('.json') and not f.startswith('__MACOSX')]
98
 
99
+ log_message(f"Найдено {len(json_files)} JSON файлов в архиве")
100
+
101
+ for json_file in json_files:
102
  try:
103
+ log_message(f"Обрабатываю файл из архива: {json_file}")
 
 
 
 
 
 
 
104
 
105
+ with zip_ref.open(json_file) as f:
106
  json_data = json.load(f)
107
 
108
  document_metadata = json_data.get('document_metadata', {})
109
  document_id = document_metadata.get('document_id', 'unknown')
110
  document_name = document_metadata.get('document_name', 'unknown')
111
 
112
+ docs = extract_text_from_json(json_data, document_id, document_name)
113
+ documents.extend(docs)
114
 
115
+ log_message(f"Извлечено {len(docs)} документов из {json_file}")
116
 
117
  except Exception as e:
118
+ log_message(f"Ошибка обработки файла {json_file}: {str(e)}")
119
  continue
120
+
121
+ except Exception as e:
122
+ log_message(f"Ошибка извлечения ZIP архива {zip_path}: {str(e)}")
123
+
124
+ return documents
 
 
125
 
126
+ def load_json_documents(repo_id, hf_token, json_files_dir, download_dir):
127
+ log_message("Начинаю загрузку JSON документов")
128
+
129
+ try:
130
+ files = list_repo_files(repo_id=repo_id, repo_type="dataset", token=hf_token)
131
+ zip_files = [f for f in files if f.startswith(json_files_dir) and f.endswith('.zip')]
132
+ json_files = [f for f in files if f.startswith(json_files_dir) and f.endswith('.json')]
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
+ log_message(f"Найдено {len(zip_files)} ZIP файлов и {len(json_files)} прямых JSON файлов")
135
+
136
+ all_documents = []
 
 
 
 
 
 
 
 
 
 
 
137
 
138
+ for zip_file_path in zip_files:
139
+ try:
140
+ log_message(f"Загружаю ZIP архив: {zip_file_path}")
141
+ local_zip_path = hf_hub_download(
142
+ repo_id=repo_id,
143
+ filename=zip_file_path,
144
+ local_dir=download_dir,
145
+ repo_type="dataset",
146
+ token=hf_token
147
+ )
148
 
149
+ documents = extract_zip_and_process_json(local_zip_path)
150
+ all_documents.extend(documents)
151
 
152
+ except Exception as e:
153
+ log_message(f"Ошибка обработки ZIP файла {zip_file_path}: {str(e)}")
154
+ continue
155
+
156
+ for file_path in json_files:
157
+ try:
158
+ log_message(f"Обрабатываю прямой JSON файл: {file_path}")
159
+ local_path = hf_hub_download(
160
+ repo_id=repo_id,
161
+ filename=file_path,
162
+ local_dir=download_dir,
163
+ repo_type="dataset",
164
+ token=hf_token
165
+ )
166
+
167
+ with open(local_path, 'r', encoding='utf-8') as f:
168
+ json_data = json.load(f)
169
+
170
+ document_metadata = json_data.get('document_metadata', {})
171
+ document_id = document_metadata.get('document_id', 'unknown')
172
+ document_name = document_metadata.get('document_name', 'unknown')
173
+
174
+ documents = extract_text_from_json(json_data, document_id, document_name)
175
+ all_documents.extend(documents)
176
+
177
+ log_message(f"Извлечено {len(documents)} документов из {file_path}")
178
+
179
+ except Exception as e:
180
+ log_message(f"Ошибка обработки файла {file_path}: {str(e)}")
181
+ continue
182
 
183
+ log_message(f"Всего создано {len(all_documents)} текстовых документов")
184
+ return all_documents
185
 
186
+ except Exception as e:
187
+ log_message(f"Ошибка загрузки JSON документов: {str(e)}")
188
+ return []
189
 
190
+ def table_to_document(table_data, document_id=None):
191
+ content = ""
192
+ if isinstance(table_data, dict):
193
+ doc_id = document_id or table_data.get('document_id', table_data.get('document', 'Неизвестно'))
194
 
195
+ table_num = table_data.get('table_number', 'Неизвестно')
196
+ table_title = table_data.get('table_title', 'Н��известно')
197
+ section = table_data.get('section', 'Неизвестно')
198
+
199
+ content += f"Таблица: {table_num}\n"
200
+ content += f"Название: {table_title}\n"
201
+ content += f"Документ: {doc_id}\n"
202
+ content += f"Раздел: {section}\n"
203
+
204
+ if 'data' in table_data and isinstance(table_data['data'], list):
205
+ for row in table_data['data']:
206
+ if isinstance(row, dict):
207
+ row_text = " | ".join([f"{k}: {v}" for k, v in row.items()])
208
+ content += f"{row_text}\n"
209
+
210
+ return Document(
211
+ text=content,
212
+ metadata={
213
+ "type": "table",
214
+ "table_number": table_data.get('table_number', 'unknown'),
215
+ "table_title": table_data.get('table_title', 'unknown'),
216
+ "document_id": doc_id or table_data.get('document_id', table_data.get('document', 'unknown')),
217
+ "section": table_data.get('section', 'unknown')
218
+ }
219
+ )
220
+
221
+ def load_table_data(repo_id, hf_token, table_data_dir):
222
+ log_message("Начинаю загрузку табличных данных")
223
+
224
+ table_files = []
225
+ try:
226
+ files = list_repo_files(repo_id=repo_id, repo_type="dataset", token=hf_token)
227
+ for file in files:
228
+ if file.startswith(table_data_dir) and file.endswith('.json'):
229
+ table_files.append(file)
230
+
231
+ log_message(f"Найдено {len(table_files)} JSON файлов с таблицами")
232
+
233
+ table_documents = []
234
+ for file_path in table_files:
235
+ try:
236
+ log_message(f"Обрабатываю файл: {file_path}")
237
+ local_path = hf_hub_download(
238
+ repo_id=repo_id,
239
+ filename=file_path,
240
+ local_dir='',
241
+ repo_type="dataset",
242
+ token=hf_token
243
+ )
244
+
245
+ with open(local_path, 'r', encoding='utf-8') as f:
246
+ table_data = json.load(f)
247
 
248
+ if isinstance(table_data, dict):
249
+ document_id = table_data.get('document', 'unknown')
250
 
251
+ if 'sheets' in table_data:
252
+ for sheet in table_data['sheets']:
253
+ sheet['document'] = document_id
254
+ doc = table_to_document(sheet, document_id)
 
 
 
 
 
 
 
 
 
 
255
  table_documents.append(doc)
256
+ else:
257
+ doc = table_to_document(table_data, document_id)
258
+ table_documents.append(doc)
259
+ elif isinstance(table_data, list):
260
+ for table_json in table_data:
261
+ doc = table_to_document(table_json)
262
+ table_documents.append(doc)
263
+
264
+ except Exception as e:
265
+ log_message(f"Ошибка обработки файла {file_path}: {str(e)}")
266
+ continue
267
+
268
+ log_message(f"Создано {len(table_documents)} документов из таблиц")
269
+ return table_documents
270
+
271
+ except Exception as e:
272
+ log_message(f"Ошибка загрузки табличных данных: {str(e)}")
273
+ return []
274
 
275
+ def load_image_data(repo_id, hf_token, image_data_dir):
276
+ log_message("Начинаю загрузку данных изображений")
277
+
278
+ image_files = []
279
+ try:
280
+ files = list_repo_files(repo_id=repo_id, repo_type="dataset", token=hf_token)
281
+ for file in files:
282
+ if file.startswith(image_data_dir) and file.endswith('.csv'):
283
+ image_files.append(file)
284
 
285
+ log_message(f"Найдено {len(image_files)} CSV файлов с изображениями")
286
+
287
+ image_documents = []
288
+ for file_path in image_files:
289
+ try:
290
+ log_message(f"Обрабатываю файл изображений: {file_path}")
291
+ local_path = hf_hub_download(
292
+ repo_id=repo_id,
293
+ filename=file_path,
294
+ local_dir='',
295
+ repo_type="dataset",
296
+ token=hf_token
297
+ )
298
+
299
+ df = pd.read_csv(local_path)
300
+ log_message(f"Загружено {len(df)} записей изображений из файла {file_path}")
301
+
302
+ for _, row in df.iterrows():
303
+ content = f"Изображение: {row.get('№ Изображения', 'Неизвестно')}\n"
304
+ content += f"Название: {row.get('Название изображения', 'Неизвестно')}\n"
305
+ content += f"Описание: {row.get('Описание изображение', 'Неизвестно')}\n"
306
+ content += f"Документ: {row.get('Обозначение документа', 'Неизвестно')}\n"
307
+ content += f"Раздел: {row.get('Раздел документа', 'Неизвестно')}\n"
308
+ content += f"Файл: {row.get('Файл изображения', 'Неизвестно')}\n"
309
 
310
+ doc = Document(
311
+ text=content,
312
+ metadata={
313
+ "type": "image",
314
+ "image_number": row.get(' Изображения', 'unknown'),
315
+ "document_id": row.get('Обозначение документа', 'unknown'),
316
+ "file_path": row.get('Файл изображения', 'unknown'),
317
+ "section": row.get('Раздел документа', 'unknown')
318
+ }
319
+ )
320
+ image_documents.append(doc)
321
 
322
+ except Exception as e:
323
+ log_message(f"Ошибка обработки файла {file_path}: {str(e)}")
324
+ continue
325
+
326
+ log_message(f"Создано {len(image_documents)} документов из изображений")
327
+ return image_documents
328
+
329
+ except Exception as e:
330
+ log_message(f"Ошибка загрузки данных изображений: {str(e)}")
331
+ return []
 
 
 
 
 
 
 
 
 
 
 
 
332
 
333
+ def load_csv_chunks(repo_id, hf_token, chunks_filename, download_dir):
334
+ log_message("Загружаю данные чанков из CSV")
335
+
336
+ try:
337
+ chunks_csv_path = hf_hub_download(
338
+ repo_id=repo_id,
339
+ filename=chunks_filename,
340
+ local_dir=download_dir,
341
+ repo_type="dataset",
342
+ token=hf_token
343
+ )
344
 
345
+ chunks_df = pd.read_csv(chunks_csv_path)
346
+ log_message(f"Загружено {len(chunks_df)} чанков из CSV")
347
 
348
+ text_column = None
349
+ for col in chunks_df.columns:
350
+ if 'text' in col.lower() or 'content' in col.lower() or 'chunk' in col.lower():
351
+ text_column = col
352
+ break
353
 
354
+ if text_column is None:
355
+ text_column = chunks_df.columns[0]
356
+
357
+ log_message(f"Использую колонку: {text_column}")
358
+
359
+ documents = []
360
+ for i, (_, row) in enumerate(chunks_df.iterrows()):
361
+ doc = Document(
362
+ text=str(row[text_column]),
363
+ metadata={
364
+ "chunk_id": row.get('chunk_id', i),
365
+ "document_id": row.get('document_id', 'unknown'),
366
+ "type": "text"
367
+ }
368
+ )
369
+ documents.append(doc)
370
 
371
+ log_message(f"Создано {len(documents)} текстовых документов из CSV")
372
+ return documents, chunks_df
373
 
374
+ except Exception as e:
375
+ log_message(f"Ошибка загрузки CSV данных: {str(e)}")
376
+ return [], None
index_retriever.py CHANGED
@@ -1,207 +1,76 @@
1
  from llama_index.core import VectorStoreIndex, Settings
2
- from llama_index.embeddings.huggingface import HuggingFaceEmbedding
3
- from llama_index.llms.google_genai import GoogleGenAI
4
- from llama_index.llms.openai import OpenAI
5
  from llama_index.core.query_engine import RetrieverQueryEngine
6
  from llama_index.core.retrievers import VectorIndexRetriever
7
  from llama_index.core.response_synthesizers import get_response_synthesizer, ResponseMode
8
  from llama_index.core.prompts import PromptTemplate
9
  from llama_index.retrievers.bm25 import BM25Retriever
10
  from llama_index.core.retrievers import QueryFusionRetriever
11
- from sentence_transformers import CrossEncoder
12
- import logging
13
- from config import *
14
 
15
- logger = logging.getLogger(__name__)
 
 
16
 
17
- def log_message(message):
18
- logger.info(message)
19
- print(message, flush=True)
20
-
21
- class IndexRetriever:
22
- def __init__(self, config):
23
- self.config = config
24
- self.vector_index = None
25
- self.query_engine = None
26
- self.reranker = None
27
- self.current_model = config.DEFAULT_MODEL
28
 
29
- def get_llm_model(self, model_name):
30
- try:
31
- model_config = self.config.AVAILABLE_MODELS.get(model_name)
32
- if not model_config:
33
- log_message(f"Модель {model_name} не найдена, использую модель по умолчанию")
34
- model_config = self.config.AVAILABLE_MODELS[self.config.DEFAULT_MODEL]
35
-
36
- if not model_config.get("api_key"):
37
- raise Exception(f"API ключ не найден для модели {model_name}")
38
-
39
- if model_config["provider"] == "google":
40
- return GoogleGenAI(
41
- model=model_config["model_name"],
42
- api_key=model_config["api_key"]
43
- )
44
- elif model_config["provider"] == "openai":
45
- return OpenAI(
46
- model=model_config["model_name"],
47
- api_key=model_config["api_key"]
48
- )
49
- else:
50
- raise Exception(f"Неподдерживаемый провайдер: {model_config['provider']}")
51
-
52
- except Exception as e:
53
- log_message(f"Ошибка создания модели {model_name}: {str(e)}")
54
- return GoogleGenAI(model="gemini-2.0-flash", api_key=self.config.GOOGLE_API_KEY)
55
-
56
- def initialize_models(self, documents):
57
- try:
58
- log_message("Инициализация моделей и индекса")
59
-
60
- embed_model = HuggingFaceEmbedding(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
61
- llm = self.get_llm_model(self.current_model)
62
-
63
- log_message("Инициализирую переранкер")
64
- self.reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-12-v2')
65
-
66
- Settings.embed_model = embed_model
67
- Settings.llm = llm
68
-
69
- log_message(f"Строю векторный индекс из {len(documents)} документов")
70
- self.vector_index = VectorStoreIndex.from_documents(documents)
71
-
72
- self.create_query_engine()
73
-
74
- log_message(f"Модели и индекс успешно инициализированы с моделью: {self.current_model}")
75
- return True
76
-
77
- except Exception as e:
78
- log_message(f"Ошибка инициализации моделей: {str(e)}")
79
- return False
80
-
81
- def create_query_engine(self):
82
- try:
83
- log_message(f"Применяется промпт: {self.config.PROMPT_SIMPLE_POISK[:100]}...")
84
-
85
- bm25_retriever = BM25Retriever.from_defaults(
86
- docstore=self.vector_index.docstore,
87
- similarity_top_k=15
88
- )
89
-
90
- vector_retriever = VectorIndexRetriever(
91
- index=self.vector_index,
92
- similarity_top_k=20,
93
- similarity_cutoff=0.5
94
- )
95
-
96
- hybrid_retriever = QueryFusionRetriever(
97
- [vector_retriever, bm25_retriever],
98
- similarity_top_k=30,
99
- num_queries=1
100
- )
101
-
102
- custom_prompt_template = PromptTemplate(self.config.PROMPT_SIMPLE_POISK)
103
- response_synthesizer = get_response_synthesizer(
104
- response_mode=ResponseMode.TREE_SUMMARIZE,
105
- text_qa_template=custom_prompt_template
106
- )
107
-
108
- self.query_engine = RetrieverQueryEngine(
109
- retriever=hybrid_retriever,
110
- response_synthesizer=response_synthesizer
111
- )
112
-
113
- log_message("Query engine успешно создан с кастомным промптом")
114
-
115
- except Exception as e:
116
- log_message(f"Ошибка создания query engine: {str(e)}")
117
- raise
118
-
119
- def query(self, question):
120
- """Метод для выполнения запроса с применением промпта"""
121
- if self.query_engine is None:
122
- log_message("❌ Query engine не инициализирован")
123
- return "❌ Система не инициализирована"
124
 
125
- try:
126
- log_message(f"Получен вопрос: {question}")
127
- log_message(f"Используется модель: {self.current_model}")
128
- log_message(f"Применяется промпт: {self.config.PROMPT_SIMPLE_POISK[:150]}...")
129
- log_message(f"Обрабатываю запрос: {question}")
130
-
131
- response = self.query_engine.query(question)
132
- log_message(f"Ответ получен, длина: {len(str(response))}")
133
-
134
- return str(response)
135
-
136
- except Exception as e:
137
- error_msg = f"Ошибка обработки запроса: {str(e)}"
138
- log_message(error_msg)
139
- return f"❌ {error_msg}"
140
-
141
- def switch_model(self, model_name):
142
- try:
143
- log_message(f"Переключение на модель: {model_name}")
144
-
145
- new_llm = self.get_llm_model(model_name)
146
- Settings.llm = new_llm
147
-
148
- if self.vector_index is not None:
149
- self.create_query_engine()
150
- self.current_model = model_name
151
- log_message(f"Модель успешно переключена на: {model_name}")
152
- return f"✅ Модель переключена на: {model_name}"
153
- else:
154
- return "❌ Ошибка: система не инициализирована"
155
-
156
- except Exception as e:
157
- error_msg = f"Ошибка переключения модели: {str(e)}"
158
- log_message(error_msg)
159
- return f"❌ {error_msg}"
160
-
161
- def rerank_nodes(self, query, nodes, top_k=10):
162
- if not nodes or not self.reranker:
163
- return nodes[:top_k]
164
 
165
- try:
166
- log_message(f"Переранжирую {len(nodes)} узлов")
167
-
168
- pairs = []
169
- for node in nodes:
170
- pairs.append([query, node.text])
171
-
172
- scores = self.reranker.predict(pairs)
173
-
174
- scored_nodes = list(zip(nodes, scores))
175
- scored_nodes.sort(key=lambda x: x[1], reverse=True)
176
-
177
- reranked_nodes = [node for node, score in scored_nodes[:top_k]]
178
- log_message(f"Возвращаю топ-{len(reranked_nodes)} переранжированных узлов")
179
-
180
- return reranked_nodes
181
- except Exception as e:
182
- log_message(f"Ошибка переранжировки: {str(e)}")
183
- return nodes[:top_k]
184
-
185
- def retrieve_nodes(self, question):
186
- if self.query_engine is None:
187
- return []
188
 
189
- try:
190
- log_message(f"Извлекаю релевантные узлы для вопроса: {question}")
191
- retrieved_nodes = self.query_engine.retriever.retrieve(question)
192
- log_message(f"Извлечено {len(retrieved_nodes)} узлов")
193
-
194
- log_message("Применяю переранжировку")
195
- reranked_nodes = self.rerank_nodes(question, retrieved_nodes, top_k=10)
196
-
197
- return reranked_nodes
198
-
199
- except Exception as e:
200
- log_message(f"Ошибка извлечения узлов: {str(e)}")
201
- return []
202
 
203
- def get_current_model(self):
204
- return self.current_model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
 
206
- def is_initialized(self):
207
- return self.query_engine is not None
 
1
  from llama_index.core import VectorStoreIndex, Settings
 
 
 
2
  from llama_index.core.query_engine import RetrieverQueryEngine
3
  from llama_index.core.retrievers import VectorIndexRetriever
4
  from llama_index.core.response_synthesizers import get_response_synthesizer, ResponseMode
5
  from llama_index.core.prompts import PromptTemplate
6
  from llama_index.retrievers.bm25 import BM25Retriever
7
  from llama_index.core.retrievers import QueryFusionRetriever
8
+ from utils import log_message
9
+ from config import CUSTOM_PROMPT
 
10
 
11
+ def create_vector_index(documents):
12
+ log_message("Строю векторный индекс")
13
+ return VectorStoreIndex.from_documents(documents)
14
 
15
+ def create_query_engine(vector_index):
16
+ try:
17
+ bm25_retriever = BM25Retriever.from_defaults(
18
+ docstore=vector_index.docstore,
19
+ similarity_top_k=15
20
+ )
 
 
 
 
 
21
 
22
+ vector_retriever = VectorIndexRetriever(
23
+ index=vector_index,
24
+ similarity_top_k=20,
25
+ similarity_cutoff=0.5
26
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
+ hybrid_retriever = QueryFusionRetriever(
29
+ [vector_retriever, bm25_retriever],
30
+ similarity_top_k=30,
31
+ num_queries=1
32
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
+ custom_prompt_template = PromptTemplate(CUSTOM_PROMPT)
35
+ response_synthesizer = get_response_synthesizer(
36
+ response_mode=ResponseMode.TREE_SUMMARIZE,
37
+ text_qa_template=custom_prompt_template
38
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
+ query_engine = RetrieverQueryEngine(
41
+ retriever=hybrid_retriever,
42
+ response_synthesizer=response_synthesizer
43
+ )
44
+
45
+ log_message("Query engine успешно создан")
46
+ return query_engine
47
+
48
+ except Exception as e:
49
+ log_message(f"Ошибка создания query engine: {str(e)}")
50
+ raise
 
 
51
 
52
+ def rerank_nodes(query, nodes, reranker, top_k=10):
53
+ if not nodes or not reranker:
54
+ return nodes[:top_k]
55
+
56
+ try:
57
+ log_message(f"Переранжирую {len(nodes)} узлов")
58
+
59
+ pairs = []
60
+ for node in nodes:
61
+ pairs.append([query, node.text])
62
+
63
+ scores = reranker.predict(pairs)
64
+
65
+ scored_nodes = list(zip(nodes, scores))
66
+ scored_nodes.sort(key=lambda x: x[1], reverse=True)
67
+
68
+ reranked_nodes = [node for node, score in scored_nodes[:top_k]]
69
+ log_message(f"Возвращаю топ-{len(reranked_nodes)} переранжированных узлов")
70
+
71
+ return reranked_nodes
72
+ except Exception as e:
73
+ log_message(f"Ошибка переранжировки: {str(e)}")
74
+ return nodes[:top_k]
75
+
76
 
 
 
utils.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import sys
3
+ from llama_index.llms.google_genai import GoogleGenAI
4
+ from llama_index.llms.openai import OpenAI
5
+ from llama_index.embeddings.huggingface import HuggingFaceEmbedding
6
+ from sentence_transformers import CrossEncoder
7
+ from config import AVAILABLE_MODELS, DEFAULT_MODEL, GOOGLE_API_KEY
8
+ import time
9
+ from index_retriever import rerank_nodes
10
+ from utils import log_message, generate_sources_html
11
+
12
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
13
+ logger = logging.getLogger(__name__)
14
+
15
+ def log_message(message):
16
+ logger.info(message)
17
+ print(message, flush=True)
18
+ sys.stdout.flush()
19
+
20
+
21
+ def get_llm_model(model_name):
22
+ try:
23
+ model_config = AVAILABLE_MODELS.get(model_name)
24
+ if not model_config:
25
+ log_message(f"Модель {model_name} не найдена, использую модель по умолчанию")
26
+ model_config = AVAILABLE_MODELS[DEFAULT_MODEL]
27
+
28
+ if not model_config.get("api_key"):
29
+ raise Exception(f"API ключ не найден для модели {model_name}")
30
+
31
+ if model_config["provider"] == "google":
32
+ return GoogleGenAI(
33
+ model=model_config["model_name"],
34
+ api_key=model_config["api_key"]
35
+ )
36
+ elif model_config["provider"] == "openai":
37
+ return OpenAI(
38
+ model=model_config["model_name"],
39
+ api_key=model_config["api_key"]
40
+ )
41
+ else:
42
+ raise Exception(f"Неподдерживаемый провайдер: {model_config['provider']}")
43
+
44
+ except Exception as e:
45
+ log_message(f"Ошибка создания модели {model_name}: {str(e)}")
46
+ return GoogleGenAI(model="gemini-2.0-flash", api_key=GOOGLE_API_KEY)
47
+
48
+ def get_embedding_model(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"):
49
+ return HuggingFaceEmbedding(model_name=model_name)
50
+
51
+ def get_reranker_model(model_name='cross-encoder/ms-marco-MiniLM-L-12-v2'):
52
+ return CrossEncoder(model_name)
53
+
54
+ def generate_sources_html(nodes, chunks_df=None):
55
+ html = "<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; max-height: 400px; overflow-y: auto;'>"
56
+ html += "<h3 style='color: #63b3ed; margin-top: 0;'>Источники:</h3>"
57
+
58
+ for i, node in enumerate(nodes):
59
+ metadata = node.metadata if hasattr(node, 'metadata') else {}
60
+ doc_type = metadata.get('type', 'text')
61
+ doc_id = metadata.get('document_id', 'unknown')
62
+
63
+ html += f"<div style='margin-bottom: 15px; padding: 15px; border: 1px solid #4a5568; border-radius: 8px; background-color: #1a202c;'>"
64
+
65
+ if doc_type == 'text':
66
+ html += f"<h4 style='margin: 0 0 10px 0; color: #63b3ed;'>📄 {doc_id}</h4>"
67
+ elif doc_type == 'table':
68
+ table_num = metadata.get('table_number', 'unknown')
69
+ if table_num and table_num != 'unknown':
70
+ if not table_num.startswith('№'):
71
+ table_num = f"№{table_num}"
72
+ html += f"<h4 style='margin: 0 0 10px 0; color: #68d391;'>📊 Таблица {table_num} - {doc_id}</h4>"
73
+ else:
74
+ html += f"<h4 style='margin: 0 0 10px 0; color: #68d391;'>📊 Таблица - {doc_id}</h4>"
75
+ elif doc_type == 'image':
76
+ image_num = metadata.get('image_number', 'unknown')
77
+ section = metadata.get('section', '')
78
+ if image_num and image_num != 'unknown':
79
+ if not str(image_num).startswith('№'):
80
+ image_num = f"№{image_num}"
81
+ html += f"<h4 style='margin: 0 0 10px 0; color: #fbb6ce;'>🖼️ Изображение {image_num} - {doc_id} ({section})</h4>"
82
+ else:
83
+ html += f"<h4 style='margin: 0 0 10px 0; color: #fbb6ce;'>🖼️ Изображение - {doc_id} ({section})</h4>"
84
+
85
+ if chunks_df is not None and 'file_link' in chunks_df.columns and doc_type == 'text':
86
+ doc_rows = chunks_df[chunks_df['document_id'] == doc_id]
87
+ if not doc_rows.empty:
88
+ file_link = doc_rows.iloc[0]['file_link']
89
+ html += f"<a href='{file_link}' target='_blank' style='color: #68d391; text-decoration: none; font-size: 14px; display: inline-block; margin-top: 10px;'>🔗 Ссылка на документ</a><br>"
90
+
91
+ html += "</div>"
92
+
93
+ html += "</div>"
94
+ return html
95
+
96
+ def answer_question(question, query_engine, reranker, current_model, chunks_df=None):
97
+ if query_engine is None:
98
+ return "<div style='background-color: #e53e3e; color: white; padding: 20px; border-radius: 10px;'>Система не инициализирована</div>", ""
99
+
100
+ try:
101
+ log_message(f"Получен вопрос: {question}")
102
+ log_message(f"Используется модель: {current_model}")
103
+ start_time = time.time()
104
+
105
+ log_message("Извлекаю релевантные узлы")
106
+ retrieved_nodes = query_engine.retriever.retrieve(question)
107
+ log_message(f"Извлечено {len(retrieved_nodes)} узлов")
108
+
109
+ log_message("Применяю переранжировку")
110
+ reranked_nodes = rerank_nodes(question, retrieved_nodes, reranker, top_k=10)
111
+
112
+ log_message(f"Отправляю запрос в LLM с {len(reranked_nodes)} узлами")
113
+ response = query_engine.query(question)
114
+
115
+ end_time = time.time()
116
+ processing_time = end_time - start_time
117
+
118
+ log_message(f"Обработка завершена за {processing_time:.2f} секунд")
119
+
120
+ sources_html = generate_sources_html(reranked_nodes, chunks_df)
121
+
122
+ answer_with_time = f"""<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; margin-bottom: 10px;'>
123
+ <h3 style='color: #63b3ed; margin-top: 0;'>Ответ (Модель: {current_model}):</h3>
124
+ <div style='line-height: 1.6; font-size: 16px;'>{response.response}</div>
125
+ <div style='margin-top: 15px; padding-top: 10px; border-top: 1px solid #4a5568; font-size: 14px; color: #a0aec0;'>
126
+ Время обработки: {processing_time:.2f} секунд
127
+ </div>
128
+ </div>"""
129
+
130
+ return answer_with_time, sources_html
131
+
132
+ except Exception as e:
133
+ log_message(f"Ошибка обработки вопроса: {str(e)}")
134
+ error_msg = f"<div style='background-color: #e53e3e; color: white; padding: 20px; border-radius: 10px;'>Ошибка обработки вопроса: {str(e)}</div>"
135
+ return error_msg, ""