MrSimple01 commited on
Commit
b716c6d
·
verified ·
1 Parent(s): 39004b3

Upload 6 files

Browse files
Files changed (6) hide show
  1. .gitattributes +35 -35
  2. README.md +13 -12
  3. app.py +576 -0
  4. config.py +125 -0
  5. rag_files/.cache/huggingface/.gitignore +1 -0
  6. requirements.txt +12 -0
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,12 +1,13 @@
1
- ---
2
- title: RAG AIEXP 0
3
- emoji: 🦀
4
- colorFrom: pink
5
- colorTo: purple
6
- sdk: gradio
7
- sdk_version: 5.44.1
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
+ ---
2
+ title: RAG AIXP 000
3
+ emoji: 🏆
4
+ colorFrom: red
5
+ colorTo: gray
6
+ sdk: gradio
7
+ sdk_version: 5.43.1
8
+ app_file: app.py
9
+ pinned: false
10
+ license: apache-2.0
11
+ ---
12
+
13
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,576 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from huggingface_hub import hf_hub_download, list_repo_files
3
+ import faiss
4
+ import pandas as pd
5
+ import os
6
+ import json
7
+ from llama_index.core import Document, VectorStoreIndex, Settings
8
+ from llama_index.embeddings.huggingface import HuggingFaceEmbedding
9
+ from llama_index.llms.google_genai import GoogleGenAI
10
+ from llama_index.llms.openai import OpenAI
11
+ from llama_index.core.query_engine import RetrieverQueryEngine
12
+ from llama_index.core.retrievers import VectorIndexRetriever
13
+ from llama_index.core.response_synthesizers import get_response_synthesizer, ResponseMode
14
+ from llama_index.core.prompts import PromptTemplate
15
+ from llama_index.retrievers.bm25 import BM25Retriever
16
+ from sentence_transformers import CrossEncoder
17
+ from llama_index.core.retrievers import QueryFusionRetriever
18
+ import time
19
+ import sys
20
+ import logging
21
+ from config import *
22
+
23
+ REPO_ID = "MrSimple01/AIEXP_RAG_FILES"
24
+ faiss_index_filename = "cleaned_faiss_index.index"
25
+ chunks_filename = "processed_chunks.csv"
26
+ table_data_dir = "Табличные данные_JSON"
27
+ image_data_dir = "Изображения"
28
+ download_dir = "rag_files"
29
+ HF_TOKEN = os.getenv('HF_TOKEN')
30
+
31
+ # Global variables
32
+ query_engine = None
33
+ chunks_df = None
34
+ reranker = None
35
+ vector_index = None
36
+ current_model = DEFAULT_MODEL
37
+
38
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
39
+ logger = logging.getLogger(__name__)
40
+
41
+ def log_message(message):
42
+ logger.info(message)
43
+ print(message, flush=True)
44
+ sys.stdout.flush()
45
+
46
+ def get_llm_model(model_name):
47
+ """Get LLM model instance based on model name"""
48
+ try:
49
+ model_config = AVAILABLE_MODELS.get(model_name)
50
+ if not model_config:
51
+ log_message(f"Модель {model_name} не найдена, использую модель по умолчанию")
52
+ model_config = AVAILABLE_MODELS[DEFAULT_MODEL]
53
+
54
+ if not model_config.get("api_key"):
55
+ raise Exception(f"API ключ не найден для модели {model_name}")
56
+
57
+ if model_config["provider"] == "google":
58
+ return GoogleGenAI(
59
+ model=model_config["model_name"],
60
+ api_key=model_config["api_key"]
61
+ )
62
+ elif model_config["provider"] == "openai":
63
+ return OpenAI(
64
+ model=model_config["model_name"],
65
+ api_key=model_config["api_key"]
66
+ )
67
+ else:
68
+ raise Exception(f"Неподдерживаемый провайдер: {model_config['provider']}")
69
+
70
+ except Exception as e:
71
+ log_message(f"Ошибка создания модели {model_name}: {str(e)}")
72
+ # Fallback to default Google model
73
+ return GoogleGenAI(model="gemini-2.0-flash", api_key=GOOGLE_API_KEY)
74
+
75
+ def switch_model(model_name):
76
+ """Switch to a different LLM model"""
77
+ global query_engine, current_model
78
+
79
+ try:
80
+ log_message(f"Переключение на модель: {model_name}")
81
+
82
+ # Create new LLM instance
83
+ new_llm = get_llm_model(model_name)
84
+ Settings.llm = new_llm
85
+
86
+ # Recreate query engine with new model
87
+ if vector_index is not None:
88
+ recreate_query_engine()
89
+ current_model = model_name
90
+ log_message(f"Модель успешно переключена на: {model_name}")
91
+ return f"✅ Модель переключена на: {model_name}"
92
+ else:
93
+ return "❌ Ошибка: система не инициализирована"
94
+
95
+ except Exception as e:
96
+ error_msg = f"Ошибка переключения модели: {str(e)}"
97
+ log_message(error_msg)
98
+ return f"❌ {error_msg}"
99
+
100
+ def recreate_query_engine():
101
+ """Recreate query engine with current settings"""
102
+ global query_engine
103
+
104
+ try:
105
+ # Create BM25 retriever
106
+ bm25_retriever = BM25Retriever.from_defaults(
107
+ docstore=vector_index.docstore,
108
+ similarity_top_k=10
109
+ )
110
+
111
+ # Create vector retriever
112
+ vector_retriever = VectorIndexRetriever(
113
+ index=vector_index,
114
+ similarity_top_k=10,
115
+ similarity_cutoff=0.5
116
+ )
117
+
118
+ # Create hybrid retriever
119
+ hybrid_retriever = QueryFusionRetriever(
120
+ [vector_retriever, bm25_retriever],
121
+ similarity_top_k=25,
122
+ num_queries=1
123
+ )
124
+
125
+ # Create response synthesizer
126
+ custom_prompt_template = PromptTemplate(CUSTOM_PROMPT)
127
+ response_synthesizer = get_response_synthesizer(
128
+ response_mode=ResponseMode.TREE_SUMMARIZE,
129
+ text_qa_template=custom_prompt_template
130
+ )
131
+
132
+ # Create new query engine
133
+ query_engine = RetrieverQueryEngine(
134
+ retriever=hybrid_retriever,
135
+ response_synthesizer=response_synthesizer
136
+ )
137
+
138
+ log_message("Query engine успешно пересоздан")
139
+
140
+ except Exception as e:
141
+ log_message(f"Ошибка пересоздания query engine: {str(e)}")
142
+ raise
143
+
144
+ def table_to_document(table_data, document_id=None):
145
+ content = ""
146
+ if isinstance(table_data, dict):
147
+ doc_id = document_id or table_data.get('document_id', table_data.get('document', 'Неизвестно'))
148
+
149
+ table_num = table_data.get('table_number', 'Неизвестно')
150
+ table_title = table_data.get('table_title', 'Неизвестно')
151
+ section = table_data.get('section', 'Неизвестно')
152
+
153
+ content += f"Таблица: {table_num}\n"
154
+ content += f"Название: {table_title}\n"
155
+ content += f"Документ: {doc_id}\n"
156
+ content += f"Раздел: {section}\n"
157
+
158
+ if 'data' in table_data and isinstance(table_data['data'], list):
159
+ for row in table_data['data']:
160
+ if isinstance(row, dict):
161
+ row_text = " | ".join([f"{k}: {v}" for k, v in row.items()])
162
+ content += f"{row_text}\n"
163
+
164
+ return Document(
165
+ text=content,
166
+ metadata={
167
+ "type": "table",
168
+ "table_number": table_data.get('table_number', 'unknown'),
169
+ "table_title": table_data.get('table_title', 'unknown'),
170
+ "document_id": doc_id or table_data.get('document_id', table_data.get('document', 'unknown')),
171
+ "section": table_data.get('section', 'unknown')
172
+ }
173
+ )
174
+
175
+ def download_table_data():
176
+ log_message("Начинаю загрузку табличных данных")
177
+
178
+ table_files = []
179
+ try:
180
+ files = list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
181
+ for file in files:
182
+ if file.startswith(table_data_dir) and file.endswith('.json'):
183
+ table_files.append(file)
184
+
185
+ log_message(f"Найдено {len(table_files)} JSON файлов с таблицами")
186
+
187
+ table_documents = []
188
+ for file_path in table_files:
189
+ try:
190
+ log_message(f"Обрабатываю файл: {file_path}")
191
+ local_path = hf_hub_download(
192
+ repo_id=REPO_ID,
193
+ filename=file_path,
194
+ local_dir='',
195
+ repo_type="dataset",
196
+ token=HF_TOKEN
197
+ )
198
+
199
+ with open(local_path, 'r', encoding='utf-8') as f:
200
+ table_data = json.load(f)
201
+
202
+ if isinstance(table_data, dict):
203
+ document_id = table_data.get('document', 'unknown')
204
+
205
+ if 'sheets' in table_data:
206
+ for sheet in table_data['sheets']:
207
+ sheet['document'] = document_id
208
+ doc = table_to_document(sheet, document_id)
209
+ table_documents.append(doc)
210
+ else:
211
+ doc = table_to_document(table_data, document_id)
212
+ table_documents.append(doc)
213
+ elif isinstance(table_data, list):
214
+ for table_json in table_data:
215
+ doc = table_to_document(table_json)
216
+ table_documents.append(doc)
217
+
218
+ except Exception as e:
219
+ log_message(f"Ошибка обработки файла {file_path}: {str(e)}")
220
+ continue
221
+
222
+ log_message(f"Создано {len(table_documents)} документов из таблиц")
223
+ return table_documents
224
+
225
+ except Exception as e:
226
+ log_message(f"Ошибка загрузки табличных данных: {str(e)}")
227
+ return []
228
+
229
+ def download_image_data():
230
+ log_message("Начинаю загрузку данных изображений")
231
+
232
+ image_files = []
233
+ try:
234
+ files = list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
235
+ for file in files:
236
+ if file.startswith(image_data_dir) and file.endswith('.csv'):
237
+ image_files.append(file)
238
+
239
+ log_message(f"Найдено {len(image_files)} CSV файлов с изображениями")
240
+
241
+ image_documents = []
242
+ for file_path in image_files:
243
+ try:
244
+ log_message(f"Обрабатываю файл изображений: {file_path}")
245
+ local_path = hf_hub_download(
246
+ repo_id=REPO_ID,
247
+ filename=file_path,
248
+ local_dir='',
249
+ repo_type="dataset",
250
+ token=HF_TOKEN
251
+ )
252
+
253
+ df = pd.read_csv(local_path)
254
+ log_message(f"Загружено {len(df)} записей изображений из файла {file_path}")
255
+
256
+ for _, row in df.iterrows():
257
+ content = f"Изображение: {row.get('№ Изображения', 'Неизвестно')}\n"
258
+ content += f"Название: {row.get('Название изображения', 'Неизвестно')}\n"
259
+ content += f"Описание: {row.get('Описание изображение', 'Неизвестно')}\n"
260
+ content += f"Документ: {row.get('Обозначение документа', 'Неизвестно')}\n"
261
+ content += f"Раздел: {row.get('Раздел документа', 'Неизвестно')}\n"
262
+ content += f"Файл: {row.get('Файл изображения', 'Неизвестно')}\n"
263
+
264
+ doc = Document(
265
+ text=content,
266
+ metadata={
267
+ "type": "image",
268
+ "image_number": row.get('№ Изображения', 'unknown'),
269
+ "document_id": row.get('Обозначение документа', 'unknown'),
270
+ "file_path": row.get('Файл изображения', 'unknown'),
271
+ "section": row.get('Раздел документа', 'unknown')
272
+ }
273
+ )
274
+ image_documents.append(doc)
275
+
276
+ except Exception as e:
277
+ log_message(f"Ошибка обработки файла {file_path}: {str(e)}")
278
+ continue
279
+
280
+ log_message(f"Создано {len(image_documents)} документов из изображений")
281
+ return image_documents
282
+
283
+ except Exception as e:
284
+ log_message(f"Ошибка загрузки данных изображений: {str(e)}")
285
+ return []
286
+
287
+ def initialize_models():
288
+ global query_engine, chunks_df, reranker, vector_index, current_model
289
+
290
+ try:
291
+ log_message("Инициализация системы")
292
+ os.makedirs(download_dir, exist_ok=True)
293
+
294
+ log_message("Загружаю основные файлы")
295
+ chunks_csv_path = hf_hub_download(
296
+ repo_id=REPO_ID,
297
+ filename=chunks_filename,
298
+ local_dir=download_dir,
299
+ repo_type="dataset",
300
+ token=HF_TOKEN
301
+ )
302
+
303
+ log_message("Загружаю данные чанков")
304
+ chunks_df = pd.read_csv(chunks_csv_path)
305
+ log_message(f"Загружено {len(chunks_df)} чанков")
306
+
307
+ log_message("Инициализирую модели")
308
+ embed_model = HuggingFaceEmbedding(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
309
+ llm = get_llm_model(current_model)
310
+
311
+ log_message("Инициализирую переранкер")
312
+ reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-12-v2')
313
+
314
+ Settings.embed_model = embed_model
315
+ Settings.llm = llm
316
+
317
+ text_column = None
318
+ for col in chunks_df.columns:
319
+ if 'text' in col.lower() or 'content' in col.lower() or 'chunk' in col.lower():
320
+ text_column = col
321
+ break
322
+
323
+ if text_column is None:
324
+ text_column = chunks_df.columns[0]
325
+
326
+ log_message(f"Использую колонку: {text_column}")
327
+
328
+ log_message("Создаю документы из чанков")
329
+ documents = []
330
+ for i, (_, row) in enumerate(chunks_df.iterrows()):
331
+ doc = Document(
332
+ text=str(row[text_column]),
333
+ metadata={
334
+ "chunk_id": row.get('chunk_id', i),
335
+ "document_id": row.get('document_id', 'unknown'),
336
+ "type": "text"
337
+ }
338
+ )
339
+ documents.append(doc)
340
+
341
+ log_message(f"Создано {len(documents)} текстовых документов")
342
+
343
+ log_message("Добавляю табличные данные")
344
+ table_documents = download_table_data()
345
+ documents.extend(table_documents)
346
+
347
+ log_message("Добавляю данные изображений")
348
+ image_documents = download_image_data()
349
+ documents.extend(image_documents)
350
+
351
+ log_message(f"Всего документов: {len(documents)}")
352
+
353
+ log_message("Строю векторный индекс")
354
+ vector_index = VectorStoreIndex.from_documents(documents)
355
+
356
+ # Create query engine
357
+ recreate_query_engine()
358
+
359
+ log_message(f"Система успешно инициализирована с моделью: {current_model}")
360
+ return True
361
+
362
+ except Exception as e:
363
+ log_message(f"Ошибка инициализации: {str(e)}")
364
+ return False
365
+
366
+ def rerank_nodes(query, nodes, top_k=10):
367
+ if not nodes or not reranker:
368
+ return nodes[:top_k]
369
+
370
+ try:
371
+ log_message(f"Переранжирую {len(nodes)} узлов")
372
+
373
+ pairs = []
374
+ for node in nodes:
375
+ pairs.append([query, node.text])
376
+
377
+ scores = reranker.predict(pairs)
378
+
379
+ scored_nodes = list(zip(nodes, scores))
380
+ scored_nodes.sort(key=lambda x: x[1], reverse=True)
381
+
382
+ reranked_nodes = [node for node, score in scored_nodes[:top_k]]
383
+ log_message(f"Возвращаю топ-{len(reranked_nodes)} переранжированных узлов")
384
+
385
+ return reranked_nodes
386
+ except Exception as e:
387
+ log_message(f"Ошибка переранжировки: {str(e)}")
388
+ return nodes[:top_k]
389
+
390
+ def answer_question(question):
391
+ global query_engine, chunks_df, current_model
392
+
393
+ if query_engine is None:
394
+ return "<div style='background-color: #e53e3e; color: white; padding: 20px; border-radius: 10px;'>Система не инициализирована</div>", ""
395
+
396
+ try:
397
+ log_message(f"Получен вопрос: {question}")
398
+ log_message(f"Используется модель: {current_model}")
399
+ start_time = time.time()
400
+
401
+ log_message("Извлекаю релевантные узлы")
402
+ retrieved_nodes = query_engine.retriever.retrieve(question)
403
+ log_message(f"Извлечено {len(retrieved_nodes)} узлов")
404
+
405
+ log_message("Применяю переранжировку")
406
+ reranked_nodes = rerank_nodes(question, retrieved_nodes, top_k=10)
407
+
408
+ log_message(f"Отправляю запрос в LLM с {len(reranked_nodes)} узлами")
409
+ response = query_engine.query(question)
410
+
411
+ end_time = time.time()
412
+ processing_time = end_time - start_time
413
+
414
+ log_message(f"Обработка завершена за {processing_time:.2f} секунд")
415
+
416
+ sources_html = generate_sources_html(reranked_nodes)
417
+
418
+ answer_with_time = f"""<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; margin-bottom: 10px;'>
419
+ <h3 style='color: #63b3ed; margin-top: 0;'>Ответ (Модель: {current_model}):</h3>
420
+ <div style='line-height: 1.6; font-size: 16px;'>{response.response}</div>
421
+ <div style='margin-top: 15px; padding-top: 10px; border-top: 1px solid #4a5568; font-size: 14px; color: #a0aec0;'>
422
+ Время обработки: {processing_time:.2f} секунд
423
+ </div>
424
+ </div>"""
425
+
426
+ return answer_with_time, sources_html
427
+
428
+ except Exception as e:
429
+ log_message(f"Ошибка обработки вопроса: {str(e)}")
430
+ error_msg = f"<div style='background-color: #e53e3e; color: white; padding: 20px; border-radius: 10px;'>Ошибка обработки вопроса: {str(e)}</div>"
431
+ return error_msg, ""
432
+
433
+ def generate_sources_html(nodes):
434
+ html = "<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; max-height: 400px; overflow-y: auto;'>"
435
+ html += "<h3 style='color: #63b3ed; margin-top: 0;'>Источники:</h3>"
436
+
437
+ for i, node in enumerate(nodes):
438
+ metadata = node.metadata if hasattr(node, 'metadata') else {}
439
+ doc_type = metadata.get('type', 'text')
440
+ doc_id = metadata.get('document_id', 'unknown')
441
+
442
+ html += f"<div style='margin-bottom: 15px; padding: 15px; border: 1px solid #4a5568; border-radius: 8px; background-color: #1a202c;'>"
443
+
444
+ if doc_type == 'text':
445
+ html += f"<h4 style='margin: 0 0 10px 0; color: #63b3ed;'>📄 {doc_id}</h4>"
446
+ elif doc_type == 'table':
447
+ table_num = metadata.get('table_number', 'unknown')
448
+ if table_num and table_num != 'unknown':
449
+ if not table_num.startswith('№'):
450
+ table_num = f"№{table_num}"
451
+ html += f"<h4 style='margin: 0 0 10px 0; color: #68d391;'>📊 Таблица {table_num} - {doc_id}</h4>"
452
+ else:
453
+ html += f"<h4 style='margin: 0 0 10px 0; color: #68d391;'>📊 Таблица - {doc_id}</h4>"
454
+ elif doc_type == 'image':
455
+ image_num = metadata.get('image_number', 'unknown')
456
+ section = metadata.get('section', '')
457
+ if image_num and image_num != 'unknown':
458
+ if not str(image_num).startswith('№'):
459
+ image_num = f"№{image_num}"
460
+ html += f"<h4 style='margin: 0 0 10px 0; color: #fbb6ce;'>🖼️ Изображение {image_num} - {doc_id} ({section})</h4>"
461
+ else:
462
+ html += f"<h4 style='margin: 0 0 10px 0; color: #fbb6ce;'>🖼️ Изображение - {doc_id} ({section})</h4>"
463
+
464
+ if chunks_df is not None and 'file_link' in chunks_df.columns and doc_type == 'text':
465
+ doc_rows = chunks_df[chunks_df['document_id'] == doc_id]
466
+ if not doc_rows.empty:
467
+ file_link = doc_rows.iloc[0]['file_link']
468
+ 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>"
469
+
470
+ html += "</div>"
471
+
472
+ html += "</div>"
473
+ return html
474
+
475
+ def create_demo_interface():
476
+ with gr.Blocks(title="AIEXP - AI Expert для нормативной документации", theme=gr.themes.Soft()) as demo:
477
+
478
+ gr.Markdown("""
479
+ # AIEXP - Artificial Intelligence Expert
480
+
481
+ ## Инструмент для работы с нормативной документацией
482
+ """)
483
+
484
+ with gr.Tab("🏠 Поиск по нормативным документам"):
485
+ gr.Markdown("### Задайте вопрос по нормативной документации")
486
+
487
+ # Model selection section
488
+ with gr.Row():
489
+ with gr.Column(scale=2):
490
+ model_dropdown = gr.Dropdown(
491
+ choices=list(AVAILABLE_MODELS.keys()),
492
+ value=current_model,
493
+ label="🤖 Выберите языковую модель",
494
+ info="Выберите модель для генерации ответов"
495
+ )
496
+ with gr.Column(scale=1):
497
+ switch_btn = gr.Button("🔄 Переключить модель", variant="secondary")
498
+ model_status = gr.Textbox(
499
+ value=f"Текущая модель: {current_model}",
500
+ label="Статус модели",
501
+ interactive=False
502
+ )
503
+
504
+ with gr.Row():
505
+ with gr.Column(scale=3):
506
+ question_input = gr.Textbox(
507
+ label="Ваш вопрос к базе знаний",
508
+ placeholder="Введите вопрос по нормативным документам...",
509
+ lines=3
510
+ )
511
+ ask_btn = gr.Button("🔍 Найти ответ", variant="primary", size="lg")
512
+
513
+ gr.Examples(
514
+ examples=[
515
+ "О чем этот рисунок: ГОСТ Р 50.04.07-2022 Приложение Л. Л.1.5 Рисунок Л.5",
516
+ "Л.9 Формула в ГОСТ Р 50.04.07 - 2022 что и о чем там?",
517
+ "Какой стандарт устанавливает порядок признания протоколов испытаний продукции в области использования атомной энергии?",
518
+ "Кто несет ответственность за организацию и проведение признания протоколов испытаний продукции?",
519
+ "В каких случаях могут быть признаны протоколы испытаний, проведенные лабораториями?",
520
+ ],
521
+ inputs=question_input
522
+ )
523
+
524
+ with gr.Row():
525
+ with gr.Column(scale=2):
526
+ answer_output = gr.HTML(
527
+ label="",
528
+ value=f"<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>Здесь появится ответ на ваш вопрос...<br><small>Текущая модель: {current_model}</small></div>",
529
+ )
530
+
531
+ with gr.Column(scale=1):
532
+ sources_output = gr.HTML(
533
+ label="",
534
+ value="<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>Здесь появятся источники...</div>",
535
+ )
536
+
537
+ # Event handlers
538
+ def update_model_status(new_model):
539
+ result = switch_model(new_model)
540
+ return result
541
+
542
+ switch_btn.click(
543
+ fn=update_model_status,
544
+ inputs=[model_dropdown],
545
+ outputs=[model_status]
546
+ )
547
+
548
+ ask_btn.click(
549
+ fn=answer_question,
550
+ inputs=[question_input],
551
+ outputs=[answer_output, sources_output]
552
+ )
553
+
554
+ question_input.submit(
555
+ fn=answer_question,
556
+ inputs=[question_input],
557
+ outputs=[answer_output, sources_output]
558
+ )
559
+
560
+ return demo
561
+
562
+ if __name__ == "__main__":
563
+ log_message("Запуск AIEXP - AI Expert для нормативной документации")
564
+
565
+ if initialize_models():
566
+ log_message("Запуск веб-интерфейса")
567
+ demo = create_demo_interface()
568
+ demo.launch(
569
+ server_name="0.0.0.0",
570
+ server_port=7860,
571
+ share=True,
572
+ debug=False
573
+ )
574
+ else:
575
+ log_message("Невозможно запустить приложение из-за ошибки инициализации")
576
+ sys.exit(1)
config.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ EMBEDDING_MODEL = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
4
+ RETRIEVER_TOP_K = 15
5
+ 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"
12
+ HF_TOKEN = os.getenv('HF_TOKEN')
13
+
14
+ # Available models configuration
15
+ AVAILABLE_MODELS = {
16
+ "Gemini 2.5 Flash": {
17
+ "provider": "google",
18
+ "model_name": "gemini-2.5-flash",
19
+ "api_key": GOOGLE_API_KEY
20
+ },
21
+ "Gemini 2.5 Pro": {
22
+ "provider": "google",
23
+ "model_name": "gemini-2.5-pro",
24
+ "api_key": GOOGLE_API_KEY
25
+ },
26
+ "GPT-4o": {
27
+ "provider": "openai",
28
+ "model_name": "gpt-4o",
29
+ "api_key": OPENAI_API_KEY
30
+ },
31
+ "GPT-4o Mini": {
32
+ "provider": "openai",
33
+ "model_name": "gpt-4o-mini",
34
+ "api_key": OPENAI_API_KEY
35
+ },
36
+ "GPT-5": {
37
+ "provider": "openai",
38
+ "model_name": "gpt-5",
39
+ "api_key": OPENAI_API_KEY
40
+ }
41
+ }
42
+
43
+ DEFAULT_MODEL = "Gemini 2.5 Flash"
44
+
45
+ CHUNK_SIZE = 1024
46
+ CHUNK_OVERLAP = 256
47
+
48
+ CUSTOM_PROMPT = """
49
+ Вы являетесь высокоспециализированным Ассистентом для анализа нормативных документов (AIEXP). Ваша цель - предоставлять точные, корректные и контекстно релевантные ответы исключительно на основе предоставленного контекста из нормативной документации.
50
+
51
+ ПРАВИЛА АНАЛИЗА ЗАПРОСА:
52
+
53
+ 1. ПРЯМЫЕ ВОПРОСЫ БЕЗ ДОКУМЕНТАЛЬНОГО КОНТЕКСТА:
54
+ Если пользователь задает вопрос типа "В каких случаях могут быть признаны протоколы испытаний?" без предоставления дополнительных документов, найдите соответствующую информацию в доступном контексте и предоставьте полный ответ с указанием источников.
55
+
56
+ 2. ОПРЕДЕЛЕНИЕ ТИПА ЗАДАЧИ:
57
+
58
+ а) ПОИСК И ОТВЕТ НА ВОПРОС (ключевые слова: "в каких случаях", "когда", "кто", "что", "как", "почему"):
59
+ - Найдите релевантную информацию в контексте
60
+ - Предоставьте развернутый ответ
61
+ - Обязательно укажите конкретные документы и разделы
62
+ - Процитируйте ключевые положения
63
+
64
+ б) КРАТКОЕ САММАРИ (ключевые слова: "кратко", "суммировать", "резюме", "основные моменты"):
65
+ - Предоставьте структурированное резюме
66
+ - Выделите ключевые требования
67
+ - Используйте нумерованный список
68
+
69
+ в) ПОИСК ДОКУМЕНТА И ПУНКТА (ключевые слова: "найти", "где", "какой документ", "в каком разделе"):
70
+ - Укажите конкретный документ и структурное расположение
71
+ - Предоставьте точные номера разделов/пунктов
72
+
73
+ г) ПРОВЕРКА КОРРЕКТНОСТИ (ключевые слова: "правильно ли", "соответствует ли", "проверить"):
74
+ - Четко укажите: "СООТВЕТСТВУЕТ" или "НЕ СООТВЕТСТВУЕТ"
75
+ - Перечислите конкретные требования
76
+
77
+ д) ПЛАН ДЕЙСТВИЙ (ключевые слова: "план", "алгоритм", "пошагово"):
78
+ - Создайте пронумерованный план
79
+ - Укажите ссылки на соответствующие пункты НД
80
+
81
+ ПРАВИЛА ФОРМИРОВАНИЯ ОТВЕТОВ:
82
+
83
+ 1. ОБЯЗАТЕЛЬНОЕ УКАЗАНИЕ ИСТОЧНИКОВ:
84
+ - Всегда указывайте конкретный документ (ГОСТ, раздел, пункт)
85
+ - Формат: "Согласно [Документ], раздел [X], пункт [X.X]: [информация]"
86
+ - При цитировании: используйте кавычки и точные ссылки
87
+
88
+ 2. СТРУКТУРА ОТВЕТА:
89
+ - Начинайте с прямого ответа на вопрос
90
+ - Затем указывайте нормативные основания
91
+ - Завершайте ссылками на конкретные документы и разделы
92
+
93
+ 3. РАБОТА С КОНТЕКСТОМ:
94
+ - Если информация найдена в контексте - предоставьте полный ответ
95
+ - Если информация не найдена: "Информация по вашему запросу не найдена в доступной нормативной документации"
96
+ - Не делайте предположений за пределами контекста
97
+ - Не используйте общие знания
98
+
99
+ 4. ТЕРМИНОЛОГИЯ И ЦИТИРОВАНИЕ:
100
+ - Сохраняйте официальную терминологию НД
101
+ - Цитируйте точные формулировки ключевых требований
102
+ - При множественных источниках - укажите все релевантные
103
+
104
+ 5. ФОРМАТИРОВАНИЕ:
105
+ - Для перечислений: используйте нумерованные списки
106
+ - Выделяйте критически важные требования
107
+ - Структурируйте ответ логически
108
+
109
+ ПРИМЕРЫ ПРАВИЛЬНОГО ФОРМАТИРОВАНИЯ:
110
+
111
+ Вопрос: "В каких случаях могут быть признаны протоколы испытаний?"
112
+ Ответ: "Протоколы испытаний могут быть признаны в следующих случаях:
113
+
114
+ 1. Если они проведены испытательными лабораториями (центрами), аккредитованными в области использования атомной энергии (ГОСТ Р 50.08.04-2022, раздел 6 )
115
+ 2. Если они проведены лабораториями, аккредитованными национальным органом Российской Федерации по аккредитации (ГОСТ Р 50.08.04-2022, пункт 4.1)
116
+ 3. Если лаборатории прошли оценку состояния измерений
117
+
118
+ Также допускается признание результатов испытаний, выполненных испытательными центрами (лабораториями), аккредитованными в национальных системах аккредитации страны изготовителя (ГОСТ Р 50.04.08-2019)."
119
+
120
+ Контекст: {context_str}
121
+
122
+ Вопрос: {query_str}
123
+
124
+ Ответ:
125
+ """
rag_files/.cache/huggingface/.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ *
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ faiss-cpu
3
+ sentence-transformers
4
+ google-generativeai
5
+ huggingface_hub
6
+ llama-index
7
+ llama-index-core
8
+ llama-index-embeddings-huggingface
9
+ llama-index-llms-google-genai
10
+ llama-index-llms-openai
11
+ llama-index-vector-stores-faiss
12
+ llama-index-retrievers-bm25