MrSimple07 commited on
Commit
147e01b
·
1 Parent(s): 9160af0

new version of rag

Browse files
Files changed (2) hide show
  1. app.py +343 -305
  2. app_1.py +282 -501
app.py CHANGED
@@ -1,354 +1,392 @@
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, answer_question
6
- from my_logging import log_message
7
- from index_retriever import create_vector_index, create_query_engine
 
 
 
 
 
 
 
 
8
  import sys
9
- from config import (
10
- HF_REPO_ID, HF_TOKEN, DOWNLOAD_DIR, CHUNKS_FILENAME,
11
- JSON_FILES_DIR, TABLE_DATA_DIR, IMAGE_DATA_DIR, DEFAULT_MODEL, AVAILABLE_MODELS
12
- )
13
-
14
- def create_chunks_display_html(chunk_info):
15
- if not chunk_info:
16
- return "<div style='padding: 20px; text-align: center; color: black;'>Нет данных о чанках</div>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
- html = "<div style='max-height: 500px; overflow-y: auto; padding: 10px; color: black;'>"
19
- html += f"<h4 style='color: black;'>Найдено релевантных чанков: {len(chunk_info)}</h4>"
20
 
21
- for i, chunk in enumerate(chunk_info):
22
- bg_color = "#f8f9fa" if i % 2 == 0 else "#e9ecef"
23
-
24
- # Get section display info
25
- section_display = get_section_display(chunk)
26
- formatted_content = get_formatted_content(chunk)
27
-
28
- html += f"""
29
- <div style='background-color: {bg_color}; padding: 10px; margin: 5px 0; border-radius: 5px; border-left: 4px solid #007bff; color: black;'>
30
- <strong style='color: black;'>Документ:</strong> <span style='color: black;'>{chunk['document_id']}</span><br>
31
- <strong style='color: black;'>Раздел:</strong> <span style='color: black;'>{section_display}</span><br>
32
- <strong style='color: black;'>Содержание:</strong><br>
33
- <div style='background-color: white; padding: 8px; margin-top: 5px; border-radius: 3px; font-family: monospace; font-size: 12px; color: black; max-height: 200px; overflow-y: auto;'>
34
- {formatted_content}
35
- </div>
36
- </div>
37
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
- html += "</div>"
40
- return html
41
 
42
- def get_section_display(chunk):
43
- section_path = chunk.get('section_path', '')
44
- section_id = chunk.get('section_id', 'unknown')
45
- doc_type = chunk.get('type', 'text')
46
 
47
- if doc_type == 'table' and chunk.get('table_number'):
48
- table_num = chunk.get('table_number')
49
- if not str(table_num).startswith('№'):
50
- table_num = f"№{table_num}"
51
- return f"таблица {table_num}"
52
 
53
- if doc_type == 'image' and chunk.get('image_number'):
54
- image_num = chunk.get('image_number')
55
- if not str(image_num).startswith('№'):
56
- image_num = f"№{image_num}"
57
- return f"рисунок {image_num}"
58
 
59
- if section_path:
60
- return section_path
61
- elif section_id and section_id != 'unknown':
62
- return section_id
63
 
64
- return section_id
65
-
66
- def get_formatted_content(chunk):
67
- document_id = chunk.get('document_id', 'unknown')
68
- section_path = chunk.get('section_path', '')
69
- section_id = chunk.get('section_id', 'unknown')
70
- section_text = chunk.get('section_text', '')
71
- parent_section = chunk.get('parent_section', '')
72
- parent_title = chunk.get('parent_title', '')
73
- level = chunk.get('level', '')
74
- chunk_text = chunk.get('chunk_text', '')
75
- doc_type = chunk.get('type', 'text')
76
 
77
- # For text documents
78
- if level in ['subsection', 'sub_subsection', 'sub_sub_subsection'] and parent_section:
79
- current_section = section_path if section_path else section_id
80
- parent_info = f"{parent_section} ({parent_title})" if parent_title else parent_section
81
- return f"В разделе {parent_info} в документе {document_id}, пункт {current_section}: {chunk_text}"
82
- else:
83
- current_section = section_path if section_path else section_id
84
- clean_text = chunk_text
85
- if section_text and chunk_text.startswith(section_text):
86
- section_title = section_text
87
- elif chunk_text.startswith(f"{current_section} "):
88
- clean_text = chunk_text[len(f"{current_section} "):].strip()
89
- section_title = section_text if section_text else f"{current_section} {clean_text.split('.')[0] if '.' in clean_text else clean_text[:50]}"
90
- else:
91
- section_title = section_text if section_text else current_section
92
-
93
- return f"В разделе {current_section} в документе {document_id}, пункт {section_title}: {clean_text}"
94
-
95
- def initialize_system(repo_id, hf_token, download_dir, chunks_filename=None,
96
- json_files_dir=None, table_data_dir=None, image_data_dir=None,
97
- use_json_instead_csv=False):
98
- try:
99
- from documents_prep import process_documents_with_chunking
100
- log_message("Инициализация системы")
101
- os.makedirs(download_dir, exist_ok=True)
102
- from config import CHUNK_SIZE, CHUNK_OVERLAP
103
- from llama_index.core.text_splitter import TokenTextSplitter
104
-
105
- embed_model = get_embedding_model()
106
- llm = get_llm_model(DEFAULT_MODEL)
107
- reranker = get_reranker_model()
108
-
109
- Settings.embed_model = embed_model
110
- Settings.llm = llm
111
- Settings.text_splitter = TokenTextSplitter(
112
- chunk_size=CHUNK_SIZE,
113
- chunk_overlap=CHUNK_OVERLAP,
114
- separator=" ",
115
- backup_separators=["\n", ".", "!", "?"]
116
- )
117
-
118
- log_message(f"Configured chunk size: {CHUNK_SIZE} tokens")
119
- log_message(f"Configured chunk overlap: {CHUNK_OVERLAP} tokens")
120
 
121
- all_documents = []
122
- chunks_df = None
123
- chunk_info = []
124
-
125
- if use_json_instead_csv and json_files_dir:
126
- log_message("Используем JSON файлы вместо CSV")
127
- json_documents, json_chunk_info = load_json_documents(repo_id, hf_token, json_files_dir, download_dir)
128
- all_documents.extend(json_documents)
129
- chunk_info.extend(json_chunk_info)
130
- else:
131
- if chunks_filename:
132
- log_message("Загружаем данные из CSV")
133
- csv_documents, chunks_df = load_csv_chunks(repo_id, hf_token, chunks_filename, download_dir)
134
- all_documents.extend(csv_documents)
135
-
136
- if table_data_dir:
137
- log_message("Добавляю табличные данные")
138
- table_documents = load_table_data(repo_id, hf_token, table_data_dir)
139
- log_message(f"Загружено {len(table_documents)} табличных документов")
140
-
141
- # Process table documents through chunking
142
- chunked_table_docs, table_chunk_info = process_documents_with_chunking(table_documents)
143
- all_documents.extend(chunked_table_docs)
144
- chunk_info.extend(table_chunk_info)
145
-
146
- if image_data_dir:
147
- log_message("Добавляю данные изображений")
148
- image_documents = load_image_data(repo_id, hf_token, image_data_dir)
149
- log_message(f"Загружено {len(image_documents)} документов изображений")
150
-
151
- # Process image documents through chunking
152
- chunked_image_docs, image_chunk_info = process_documents_with_chunking(image_documents)
153
- all_documents.extend(chunked_image_docs)
154
- chunk_info.extend(image_chunk_info)
155
 
156
- log_message(f"Всего документов после всей обработки: {len(all_documents)}")
 
 
 
 
 
157
 
158
- vector_index = create_vector_index(all_documents)
159
- query_engine = create_query_engine(vector_index)
 
 
 
 
 
 
 
 
 
160
 
161
- log_message(f"Система успешно инициализирована")
162
- return query_engine, chunks_df, reranker, vector_index, chunk_info
163
 
164
  except Exception as e:
165
- log_message(f"Ошибка инициализации: {str(e)}")
166
- return None, None, None, None, []
167
 
168
- def switch_model(model_name, vector_index):
169
- from llama_index.core import Settings
170
- from index_retriever import create_query_engine
171
-
172
  try:
173
- log_message(f"Переключение на модель: {model_name}")
174
-
175
  new_llm = get_llm_model(model_name)
176
  Settings.llm = new_llm
177
 
178
- if vector_index is not None:
179
- new_query_engine = create_query_engine(vector_index)
180
- log_message(f"Модель успешно переключена на: {model_name}")
181
- return new_query_engine, f"✅ Модель переключена на: {model_name}"
182
- else:
183
- return None, "❌ Ошибка: система не инициализирована"
184
-
185
- except Exception as e:
186
- error_msg = f"Ошибка переключения модели: {str(e)}"
187
- log_message(error_msg)
188
- return None, f"❌ {error_msg}"
189
-
190
- def main_answer_question(question):
191
- global query_engine, reranker, current_model, chunks_df
192
- if not question.strip():
193
- return ("<div style='color: black;'>Пожалуйста, введите вопрос</div>",
194
- "<div style='color: black;'>Источники появятся после обработки запроса</div>",
195
- "<div style='color: black;'>Чанки появятся после обработки запроса</div>")
196
-
197
- try:
198
- # Call the answer_question function which returns 3 values
199
- answer_html, sources_html, chunks_html = answer_question(question, query_engine, reranker, current_model, chunks_df)
200
- return answer_html, sources_html, chunks_html
201
-
202
  except Exception as e:
203
- log_message(f"Ошибка при ответе на вопрос: {str(e)}")
204
- return (f"<div style='color: red;'>Ошибка: {str(e)}</div>",
205
- "<div style='color: black;'>Источники недоступны из-за ошибки</div>",
206
- "<div style='color: black;'>Чанки недоступны из-за ошибки</div>")
207
-
208
-
209
-
210
- def create_demo_interface(answer_question_func, switch_model_func, current_model, chunk_info=None):
211
- with gr.Blocks(title="AIEXP - AI Expert для нормативной документации", theme=gr.themes.Soft()) as demo:
212
-
213
- gr.Markdown("""
214
- # AIEXP - Artificial Intelligence Expert
215
-
216
- ## Инструмент для работы с нормативной документацией
217
- """)
218
-
219
- with gr.Tab("Поиск по нормативным документам"):
220
- gr.Markdown("### Задайте вопрос по нормативной документации")
221
-
222
- with gr.Row():
223
- with gr.Column(scale=2):
224
- model_dropdown = gr.Dropdown(
225
- choices=list(AVAILABLE_MODELS.keys()),
226
- value=current_model,
227
- label="Выберите языковую модель",
228
- info="Выберите модель для генерации ответов"
229
- )
230
- with gr.Column(scale=1):
231
- switch_btn = gr.Button("Переключить модель", variant="secondary")
232
- model_status = gr.Textbox(
233
- value=f"Текущая модель: {current_model}",
234
- label="Статус модели",
235
- interactive=False
236
- )
237
-
238
- with gr.Row():
239
- with gr.Column(scale=3):
240
- question_input = gr.Textbox(
241
- label="Ваш вопрос к базе знаний",
242
- placeholder="Введите вопрос по нормативным документам...",
243
- lines=3
244
- )
245
- ask_btn = gr.Button("Найти ответ", variant="primary", size="lg")
246
-
247
- gr.Examples(
248
- examples=[
249
- "О чем этот рисунок: ГОСТ Р 50.04.07-2022 Приложение Л. Л.1.5 Рисунок Л.2",
250
- "Л.9 Формула в ГОСТ Р 50.04.07 - 2022 что и о чем там?",
251
- "Какой стандарт устанавливает порядок признания протоколов испытаний продукции в области использования атомной энергии?",
252
- "Кто несет ответственность за организацию и проведение признания протоколов испытаний продукции?",
253
- "В каких случаях могут быть признаны протоколы испытаний, проведенные лабораториями?",
254
- "В какой таблице можно найти информацию о методы исследований при аттестационных испытаниях технологии термической обработки заготовок из легированных сталей? Какой документ и какой раздел?"
255
- ],
256
- inputs=question_input
257
- )
258
-
259
- with gr.Row():
260
- with gr.Column(scale=2):
261
- answer_output = gr.HTML(
262
- label="",
263
- value=f"<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>Здесь появится ответ на ваш вопрос...<br><small>Текущая модель: {current_model}</small></div>",
264
- )
265
-
266
- with gr.Column(scale=1):
267
- sources_output = gr.HTML(
268
- label="",
269
- value="<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>Здесь появятся релевантные чанки...</div>",
270
- )
271
-
272
- with gr.Column(scale=1):
273
- chunks_output = gr.HTML(
274
- label="Релевантные чанки",
275
- value="<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>Здесь появятся релевантные чанки...</div>",
276
- )
277
-
278
- switch_btn.click(
279
- fn=switch_model_func,
280
- inputs=[model_dropdown],
281
- outputs=[model_status]
282
- )
283
-
284
- ask_btn.click(
285
- fn=answer_question_func,
286
- inputs=[question_input],
287
- outputs=[answer_output, sources_output, chunks_output]
288
- )
289
-
290
- question_input.submit(
291
- fn=answer_question_func,
292
- inputs=[question_input],
293
- outputs=[answer_output, sources_output, chunks_output]
294
- )
295
- return demo
296
-
297
 
298
  query_engine = None
299
- chunks_df = None
300
- reranker = None
301
  vector_index = None
 
 
302
  current_model = DEFAULT_MODEL
303
 
304
  def main_answer_question(question):
305
- global query_engine, reranker, current_model, chunks_df
306
- answer_html, sources_html, chunks_html = answer_question(
307
- question, query_engine, reranker, current_model, chunks_df
308
- )
309
- return answer_html, sources_html, chunks_html
310
 
311
  def main_switch_model(model_name):
312
- global query_engine, vector_index, current_model
313
-
314
- new_query_engine, status_message = switch_model(model_name, vector_index)
315
  if new_query_engine:
316
  query_engine = new_query_engine
317
  current_model = model_name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
 
319
- return status_message
320
 
321
  def main():
322
- global query_engine, chunks_df, reranker, vector_index, current_model
323
 
324
- log_message("Запуск AIEXP - AI Expert для нормативной документации")
325
 
326
- query_engine, chunks_df, reranker, vector_index, chunk_info = initialize_system(
327
- repo_id=HF_REPO_ID,
328
- hf_token=HF_TOKEN,
329
- download_dir=DOWNLOAD_DIR,
330
- json_files_dir=JSON_FILES_DIR,
331
- table_data_dir=TABLE_DATA_DIR,
332
- image_data_dir=IMAGE_DATA_DIR,
333
- use_json_instead_csv=True,
334
- )
335
 
336
  if query_engine:
337
- log_message("Запуск веб-интерфейса")
338
- demo = create_demo_interface(
339
- answer_question_func=main_answer_question,
340
- switch_model_func=main_switch_model,
341
- current_model=current_model,
342
- chunk_info=chunk_info
343
- )
344
  demo.launch(
345
  server_name="0.0.0.0",
346
  server_port=7860,
347
- share=True,
348
- debug=False
349
  )
350
  else:
351
- log_message("Невозможно запустить приложение из-за ошибки инициализации")
352
  sys.exit(1)
353
 
354
  if __name__ == "__main__":
 
 
1
  import os
2
+ import json
3
+ import zipfile
4
+ from typing import List, Dict, Any
5
+ import pandas as pd
6
+ from huggingface_hub import hf_hub_download, list_repo_files
7
+ from llama_index.core import Document, VectorStoreIndex, KeywordTableIndex, Settings
8
+ from llama_index.core.retrievers import VectorIndexRetriever, QueryFusionRetriever
9
+ from llama_index.retrievers.bm25 import BM25Retriever
10
+ from llama_index.core.query_engine import RetrieverQueryEngine
11
+ from llama_index.core.response_synthesizers import get_response_synthesizer, ResponseMode
12
+ from llama_index.core.text_splitter import SentenceSplitter
13
+ from sentence_transformers import SentenceTransformer
14
+ import gradio as gr
15
  import sys
16
+
17
+ GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
18
+ OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
19
+ HF_REPO_ID = "MrSimple01/AIEXP_RAG_FILES"
20
+ HF_TOKEN = os.getenv('HF_TOKEN')
21
+
22
+ AVAILABLE_MODELS = {
23
+ "Gemini 2.5 Flash": {
24
+ "provider": "google",
25
+ "model_name": "gemini-2.5-flash",
26
+ "api_key": GOOGLE_API_KEY
27
+ },
28
+ "Gemini 2.5 Pro": {
29
+ "provider": "google",
30
+ "model_name": "gemini-2.5-pro",
31
+ "api_key": GOOGLE_API_KEY
32
+ },
33
+ "GPT-4o": {
34
+ "provider": "openai",
35
+ "model_name": "gpt-4o",
36
+ "api_key": OPENAI_API_KEY
37
+ },
38
+ "GPT-4o Mini": {
39
+ "provider": "openai",
40
+ "model_name": "gpt-4o-mini",
41
+ "api_key": OPENAI_API_KEY
42
+ },
43
+ "GPT-5": {
44
+ "provider": "openai",
45
+ "model_name": "gpt-5",
46
+ "api_key": OPENAI_API_KEY
47
+ }
48
+ }
49
+
50
+ DEFAULT_MODEL = "Gemini 2.5 Flash"
51
+ DOWNLOAD_DIR = "rag_files"
52
+ JSON_FILES_DIR = "JSON"
53
+ TABLE_DATA_DIR = "Табличные данные_JSON"
54
+ IMAGE_DATA_DIR = "Изображения"
55
+ CHUNK_SIZE = 512
56
+ CHUNK_OVERLAP = 50
57
+ TABLE_MAX_ROWS_PER_CHUNK = 30
58
+
59
+ os.makedirs(DOWNLOAD_DIR, exist_ok=True)
60
+
61
+ def get_llm_model(model_name):
62
+ config = AVAILABLE_MODELS[model_name]
63
+ if config["provider"] == "google":
64
+ from llama_index.llms.gemini import Gemini
65
+ return Gemini(model=config["model_name"], api_key=config["api_key"])
66
+ else:
67
+ from llama_index.llms.openai import OpenAI
68
+ return OpenAI(model=config["model_name"], api_key=config["api_key"])
69
+
70
+ def get_embedding_model():
71
+ from llama_index.embeddings.huggingface import HuggingFaceEmbedding
72
+ return HuggingFaceEmbedding(model_name="sentence-transformers/all-MiniLM-L6-v2")
73
+
74
+ def list_zip_files_in_repo(repo_id: str) -> List[str]:
75
+ files = list_repo_files(repo_id, token=HF_TOKEN)
76
+ return [f for f in files if f.startswith(JSON_FILES_DIR) and f.endswith('.zip')]
77
+
78
+ def download_file_from_hf(repo_id: str, path_in_repo: str, dest_dir: str) -> str:
79
+ local_path = hf_hub_download(repo_id=repo_id, filename=path_in_repo, repo_type="dataset", token=HF_TOKEN)
80
+ base = os.path.basename(local_path)
81
+ dst = os.path.join(dest_dir, base)
82
+ if local_path != dst:
83
+ try:
84
+ with open(local_path, 'rb') as r, open(dst, 'wb') as w:
85
+ w.write(r.read())
86
+ except Exception:
87
+ pass
88
+ return dst
89
+
90
+ def read_jsons_from_zip(zip_path: str) -> List[Dict[str, Any]]:
91
+ docs = []
92
+ with zipfile.ZipFile(zip_path, 'r') as z:
93
+ for name in z.namelist():
94
+ if name.lower().endswith('.json'):
95
+ with z.open(name) as f:
96
+ try:
97
+ text = f.read().decode('utf-8')
98
+ data = json.loads(text)
99
+ docs.append(data)
100
+ except Exception as e:
101
+ print(f"Failed to load {name} in {zip_path}: {e}")
102
+ return docs
103
+
104
+ def chunk_text_field(text: str, doc_meta: Dict[str, Any], splitter: SentenceSplitter) -> List[Document]:
105
+ nodes = splitter.split_text(text)
106
+ chunks = []
107
+ for i, node_text in enumerate(nodes):
108
+ md = dict(doc_meta)
109
+ md.update({
110
+ 'chunk_id': f"{md.get('document_id','unknown')}_text_{i}",
111
+ 'chunk_type': 'text'
112
+ })
113
+ chunks.append(Document(text=node_text, metadata=md))
114
+ return chunks
115
+
116
+ def chunk_table(table: Dict[str, Any], table_meta: Dict[str, Any], max_rows: int = TABLE_MAX_ROWS_PER_CHUNK) -> List[Document]:
117
+ headers = table.get('headers') or []
118
+ rows = table.get('data') or []
119
+ if not rows:
120
+ text = table.get('table_description') or table.get('table_title') or ''
121
+ md = {**table_meta, 'chunk_type': 'table', 'chunk_id': f"{table_meta.get('document_id')}_table_single"}
122
+ return [Document(text=text, metadata=md)]
123
+
124
+ chunks = []
125
+ for i in range(0, len(rows), max_rows):
126
+ block = rows[i:i+max_rows]
127
+ lines = []
128
+ lines.append(f"Table {table_meta.get('table_number','?')} - {table_meta.get('table_title','')}")
129
+ lines.append(f"Headers: {headers}")
130
+ for r in block:
131
+ row_items = [f"{k}: {v}" for k, v in r.items()]
132
+ lines.append(" | ".join(row_items))
133
+ chunk_text = "\n".join(lines)
134
+ md = dict(table_meta)
135
+ md.update({'chunk_type': 'table', 'chunk_id': f"{table_meta.get('document_id')}_table_{i // max_rows}"})
136
+ chunks.append(Document(text=chunk_text, metadata=md))
137
+ return chunks
138
+
139
+ def chunk_image(image_entry: Dict[str, Any], image_meta: Dict[str, Any]) -> Document:
140
+ txt = f"Image: {image_entry.get('Название изображения') or image_entry.get('title','')}. "
141
+ txt += f"Описание: {image_entry.get('Описание изображение') or image_entry.get('description','')}. "
142
+ txt += f"Файл: {image_entry.get('Файл изображения') or image_entry.get('file','')}."
143
+ md = dict(image_meta)
144
+ md.update({'chunk_type': 'image', 'chunk_id': f"{image_meta.get('document_id')}_image_{image_entry.get('№ Изображения','0')}"})
145
+ return Document(text=txt, metadata=md)
146
+
147
+ def build_chunks_from_repo(repo_id: str) -> List[Document]:
148
+ zip_paths = list_zip_files_in_repo(repo_id)
149
+ print(f"Found {len(zip_paths)} zip files under {JSON_FILES_DIR} in repo {repo_id}")
150
+
151
+ splitter = SentenceSplitter(chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP)
152
+ all_chunks = []
153
+
154
+ for remote_path in zip_paths:
155
+ print(f"Downloading {remote_path}...")
156
+ local_zip = download_file_from_hf(repo_id, remote_path, DOWNLOAD_DIR)
157
+ print(f"Parsing {local_zip}...")
158
+ json_docs = read_jsons_from_zip(local_zip)
159
+ for doc in json_docs:
160
+ doc_meta = doc.get('document_metadata', {})
161
+ doc_id = doc_meta.get('document_id') or doc_meta.get('document_name') or 'unknown_doc'
162
+ base_meta = {'document_id': doc_id, 'document_name': doc_meta.get('document_name','')}
163
+
164
+ for sec in doc.get('sections', []):
165
+ sec_meta = dict(base_meta)
166
+ sec_meta.update({'section_id': sec.get('section_id'), 'section_title': None})
167
+ text = sec.get('section_text') or sec.get('text') or ''
168
+ if text and text.strip():
169
+ chunks = chunk_text_field(text, sec_meta, splitter)
170
+ all_chunks.extend(chunks)
171
+
172
+ for sheet in doc.get('sheets', []) + doc.get('tables', []) if (doc.get('sheets') or doc.get('tables')) else []:
173
+ table_meta = dict(base_meta)
174
+ table_meta.update({
175
+ 'sheet_name': sheet.get('sheet_name') or sheet.get('table_title'),
176
+ 'section': sheet.get('section'),
177
+ 'table_number': sheet.get('table_number'),
178
+ 'table_title': sheet.get('table_title')
179
+ })
180
+ table_chunks = chunk_table(sheet, table_meta, max_rows=TABLE_MAX_ROWS_PER_CHUNK)
181
+ all_chunks.extend(table_chunks)
182
+
183
+ for img in doc.get('images', []) or doc.get('image_data', []) or doc.get('image_entries', []):
184
+ img_meta = dict(base_meta)
185
+ chunk = chunk_image(img, img_meta)
186
+ all_chunks.append(chunk)
187
+
188
+ print(f"Built total {len(all_chunks)} chunks")
189
+ return all_chunks
190
+
191
+ def create_hybrid_index(documents):
192
+ print("Creating vector index...")
193
+ vector_index = VectorStoreIndex.from_documents(documents)
194
 
195
+ print("Creating keyword index...")
196
+ keyword_index = KeywordTableIndex.from_documents(documents)
197
 
198
+ return vector_index, keyword_index
199
+
200
+ def create_fusion_retriever(vector_index, keyword_index, documents):
201
+ vector_retriever = VectorIndexRetriever(index=vector_index, similarity_top_k=5)
202
+
203
+ bm25_retriever = BM25Retriever.from_defaults(
204
+ docstore=vector_index.docstore,
205
+ similarity_top_k=5
206
+ )
207
+
208
+ fusion_retriever = QueryFusionRetriever(
209
+ [vector_retriever, bm25_retriever],
210
+ similarity_top_k=5,
211
+ num_queries=1,
212
+ mode="reciprocal_rerank",
213
+ use_async=False
214
+ )
215
+
216
+ return fusion_retriever
217
+
218
+ def create_query_engine(vector_index, keyword_index, documents):
219
+ fusion_retriever = create_fusion_retriever(vector_index, keyword_index, documents)
220
+
221
+ response_synthesizer = get_response_synthesizer(
222
+ response_mode=ResponseMode.COMPACT,
223
+ use_async=False
224
+ )
225
+
226
+ query_engine = RetrieverQueryEngine(
227
+ retriever=fusion_retriever,
228
+ response_synthesizer=response_synthesizer
229
+ )
230
 
231
+ return query_engine
 
232
 
233
+ def initialize_system():
234
+ print("Initializing system...")
 
 
235
 
236
+ embed_model = get_embedding_model()
237
+ llm = get_llm_model(DEFAULT_MODEL)
 
 
 
238
 
239
+ Settings.embed_model = embed_model
240
+ Settings.llm = llm
241
+ Settings.chunk_size = CHUNK_SIZE
242
+ Settings.chunk_overlap = CHUNK_OVERLAP
 
243
 
244
+ print("Loading documents...")
245
+ documents = build_chunks_from_repo(HF_REPO_ID)
 
 
246
 
247
+ print("Creating indices...")
248
+ vector_index, keyword_index = create_hybrid_index(documents)
 
 
 
 
 
 
 
 
 
 
249
 
250
+ print("Creating query engine...")
251
+ query_engine = create_query_engine(vector_index, keyword_index, documents)
252
+
253
+ print("System initialized successfully!")
254
+ return query_engine, vector_index, keyword_index, documents
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
 
256
+ def answer_question(question, query_engine):
257
+ if not question.strip():
258
+ return "<div style='color: black;'>Please enter a question</div>"
259
+
260
+ try:
261
+ response = query_engine.query(question)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
 
263
+ answer_html = f"""
264
+ <div style='background-color: #f8f9fa; padding: 20px; border-radius: 10px; color: black;'>
265
+ <h3 style='color: #007bff;'>Answer:</h3>
266
+ <p>{response.response}</p>
267
+ </div>
268
+ """
269
 
270
+ sources_html = "<div style='background-color: #e9ecef; padding: 15px; border-radius: 8px; color: black;'>"
271
+ sources_html += "<h4>Sources:</h4>"
272
+ for i, node in enumerate(response.source_nodes):
273
+ sources_html += f"""
274
+ <div style='margin: 10px 0; padding: 10px; background-color: white; border-left: 3px solid #007bff;'>
275
+ <strong>Document {i+1}:</strong> {node.metadata.get('document_id', 'unknown')}<br>
276
+ <strong>Score:</strong> {node.score:.3f}<br>
277
+ <strong>Text:</strong> {node.text[:200]}...
278
+ </div>
279
+ """
280
+ sources_html += "</div>"
281
 
282
+ return answer_html, sources_html
 
283
 
284
  except Exception as e:
285
+ error_html = f"<div style='color: red;'>Error: {str(e)}</div>"
286
+ return error_html, error_html
287
 
288
+ def switch_model(model_name, vector_index, keyword_index, documents):
 
 
 
289
  try:
290
+ print(f"Switching to model: {model_name}")
 
291
  new_llm = get_llm_model(model_name)
292
  Settings.llm = new_llm
293
 
294
+ new_query_engine = create_query_engine(vector_index, keyword_index, documents)
295
+ return new_query_engine, f"✅ Model switched to: {model_name}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  except Exception as e:
297
+ return None, f" Error: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
 
299
  query_engine = None
 
 
300
  vector_index = None
301
+ keyword_index = None
302
+ documents = None
303
  current_model = DEFAULT_MODEL
304
 
305
  def main_answer_question(question):
306
+ global query_engine
307
+ return answer_question(question, query_engine)
 
 
 
308
 
309
  def main_switch_model(model_name):
310
+ global query_engine, vector_index, keyword_index, documents, current_model
311
+ new_query_engine, status = switch_model(model_name, vector_index, keyword_index, documents)
 
312
  if new_query_engine:
313
  query_engine = new_query_engine
314
  current_model = model_name
315
+ return status
316
+
317
+ def create_interface():
318
+ with gr.Blocks(title="AIEXP - RAG System", theme=gr.themes.Soft()) as demo:
319
+ gr.Markdown("# AIEXP - AI Expert for Regulatory Documentation")
320
+
321
+ with gr.Row():
322
+ model_dropdown = gr.Dropdown(
323
+ choices=list(AVAILABLE_MODELS.keys()),
324
+ value=current_model,
325
+ label="Select Language Model"
326
+ )
327
+ switch_btn = gr.Button("Switch Model")
328
+ model_status = gr.Textbox(
329
+ value=f"Current model: {current_model}",
330
+ label="Model Status",
331
+ interactive=False
332
+ )
333
+
334
+ with gr.Row():
335
+ question_input = gr.Textbox(
336
+ label="Your Question",
337
+ placeholder="Ask a question about the documents...",
338
+ lines=3
339
+ )
340
+
341
+ ask_btn = gr.Button("Get Answer", variant="primary")
342
+
343
+ with gr.Row():
344
+ answer_output = gr.HTML(
345
+ label="Answer",
346
+ value="<div style='padding: 20px; text-align: center;'>Answer will appear here...</div>"
347
+ )
348
+ sources_output = gr.HTML(
349
+ label="Sources",
350
+ value="<div style='padding: 20px; text-align: center;'>Sources will appear here...</div>"
351
+ )
352
+
353
+ switch_btn.click(
354
+ fn=main_switch_model,
355
+ inputs=[model_dropdown],
356
+ outputs=[model_status]
357
+ )
358
+
359
+ ask_btn.click(
360
+ fn=main_answer_question,
361
+ inputs=[question_input],
362
+ outputs=[answer_output, sources_output]
363
+ )
364
+
365
+ question_input.submit(
366
+ fn=main_answer_question,
367
+ inputs=[question_input],
368
+ outputs=[answer_output, sources_output]
369
+ )
370
 
371
+ return demo
372
 
373
  def main():
374
+ global query_engine, vector_index, keyword_index, documents
375
 
376
+ print("Starting AIEXP - AI Expert for Regulatory Documentation")
377
 
378
+ query_engine, vector_index, keyword_index, documents = initialize_system()
 
 
 
 
 
 
 
 
379
 
380
  if query_engine:
381
+ print("Launching web interface...")
382
+ demo = create_interface()
 
 
 
 
 
383
  demo.launch(
384
  server_name="0.0.0.0",
385
  server_port=7860,
386
+ share=True
 
387
  )
388
  else:
389
+ print("Failed to initialize system")
390
  sys.exit(1)
391
 
392
  if __name__ == "__main__":
app_1.py CHANGED
@@ -1,501 +1,213 @@
1
  import gradio as gr
2
- from huggingface_hub import hf_hub_download
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.core.query_engine import RetrieverQueryEngine
11
- from llama_index.core.retrievers import VectorIndexRetriever
12
- from llama_index.core.response_synthesizers import get_response_synthesizer, ResponseMode
13
- from llama_index.core.prompts import PromptTemplate
14
- import time
15
  import sys
16
- from config import *
17
-
18
- REPO_ID = "MrSimple01/AIEXP_RAG_FILES"
19
- faiss_index_filename = "cleaned_faiss_index.index"
20
- chunks_filename = "processed_chunks.csv"
21
- download_dir = "rag_files"
22
- table_data_dir = "Табличные данные_JSON"
23
- HF_TOKEN = os.getenv('HF_TOKEN')
24
- GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
25
-
26
- CUSTOM_PROMPT_NEW = """
27
- Вы являетесь высокоспециализированным Ассистентом для анализа документов (AIEXP). Ваша цель - предоставлять точные, корректные и контекстно релевантные ответы на основе анализа нормативной документации (НД). Все ваши ответы должны основываться исключительно на предоставленном контексте без использования внешних знаний или предположений.
28
-
29
- КРИТИЧЕСКИ ВАЖНО: ВСЕ ОТВЕТЫ ДОЛЖНЫ БЫТЬ ТОЛЬКО НА РУССКОМ ЯЗЫКЕ! НИКОГДА НЕ ОТВЕЧАЙТЕ НА АНГЛИЙСКОМ!
30
-
31
- История чата:
32
- {chat_history}
33
-
34
-
35
- ИНСТРУКЦИИ ПО ОБРАБОТКЕ КОНТЕКСТА:
36
-
37
- 1. АНАЛИЗ ТАБЛИЧНЫХ ДАННЫХ:
38
- - Если в контексте есть информация начинающаяся с "Таблица", внимательно изучите её содержимое
39
- - Извлекайте данные из строк с заголовками и данными таблицы
40
- - Указывайте номер и название таблицы при ответе
41
- - Структурируйте ответ на основе табличных данных
42
-
43
- 2. ОПРЕДЕЛЕНИЕ ТИПА ЗАДАЧИ:
44
- Проанализируйте запрос пользователя и определите тип задачи:
45
-
46
- 1. КРАТКОЕ САММАРИ (ключевые слова: "кратко", "суммировать", "резюме", "основные моменты", "в двух словах"):
47
- - Предоставьте структурированное резюме запрашиваемого раздела/пункта
48
- - Выделите ключевые требования, процедуры или положения
49
- - Используйте нумерованный список для лучшей читаемости
50
- - Сохраняйте терминологию НД
51
-
52
- 2. ПОИСК ДОКУМЕНТА И ПУНКТА (ключевые слова: "найти", "где", "какой документ", "в каком разделе", "ссылка"):
53
- - Укажите конкретный документ и его структурное расположение
54
- - Предоставьте точные номера разделов/подразделов/пунктов
55
- - Процитируйте релевантные фрагменты
56
- - Если найдено несколько документов, перечислите все с указанием специфики каждого
57
-
58
- 3. ПРОВЕРКА КОРРЕКТНОСТИ (ключевые слова: "правильно ли", "соответствует ли", "проверить", "корректно", "нарушение"):
59
- - Сопоставьте предоставленную информацию с требованиями НД
60
- - Четко укажите: "СООТВЕТСТВУЕТ" или "НЕ СООТВЕТСТВУЕТ"
61
- - Перечислите конкретные требования НД
62
- - Укажите выявленные расхождения или подтвердите соответствие
63
- - Процитируйте релевантные пункты НД
64
-
65
- 4. ПЛАН ДЕЙСТВИЙ (ключевые слова: "план", "алгоритм", "последовательность", "как действовать", "пошагово"):
66
- - Создайте пронумерованный пошаговый план
67
- - Каждый шаг должен содержать ссылку на соответствующий пункт НД
68
- - Укажите необходимые документы или формы
69
- - Добавьте временные рамки, если они указаны в НД
70
- - Выделите критические требования или ограничения
71
-
72
- 5. УТОЧНЯЮЩИЕ ВОПРОСЫ (ключевые слова: "что это значит", "что означает", "объясните", "расскажите подробнее"):
73
- - Используйте историю чата для понимания контекста
74
- - Если вопрос относится к предыдущему обсуждению, опирайтесь на него
75
- - Предоставьте подробное объяснение на основе НД
76
- - Если контекст неясен, попросите уточнения
77
-
78
- ПРАВИЛА ФОРМИРОВАНИЯ ОТВЕТОВ:
79
-
80
- 1. ОБЯЗАТЕЛЬНОЕ УКАЗАНИЕ ИСТОЧНИКОВ:
81
- - Для каждого ответа указывайте: "Согласно [Название документа], раздел [X], пункт [X.X]: [Ваш ответ]"
82
- - В конце ответа добавляйте: "Подробнее об этом можно узнать в документе [Название документа], раздел [X]."
83
- - При отсутствии точного раздела: "Согласно документу [Название]: [Ваш ответ]"
84
-
85
- 2. СТРОГОЕ СЛЕДОВАНИЕ КОНТЕКСТУ:
86
- - Если информация не найдена: "Информация по вашему запросу не была найдена в нормативной документации."
87
- - НЕ используйте английский язык ни при каких обстоятельствах
88
- - Используйте историю чата для понимания контекста вопросов
89
-
90
- 3. ИСПОЛЬЗОВАНИЕ ТЕРМИНОЛОГИИ НД:
91
- - Применяйте официальную терминологию из документов
92
- - Сохраняйте оригинальные формулировки ключевых требований
93
- - При необходимости разъясняйте специальные термины на основе НД
94
-
95
- 4. СТРУКТУРИРОВАНИЕ ОТВЕТОВ:
96
- - Основной ответ на русском языке
97
- - Указание источника
98
- - Дополнительная информация о документе
99
-
100
- Контекст: {context_str}
101
-
102
- Вопрос: {query_str}
103
-
104
- Ответ (ТОЛЬКО НА РУССКОМ ЯЗЫКЕ):
105
- """
106
-
107
-
108
- query_engine = None
109
- chunks_df = None
110
- chat_history = []
111
-
112
- def log_message(message):
113
- print(message, flush=True)
114
- sys.stdout.flush()
115
-
116
- def table_to_document(table_json):
117
- document_id = table_json.get("document_id") or table_json.get("document", "unknown")
118
-
119
- metadata = {
120
- "document_id": document_id,
121
- "section": table_json.get("section", ""),
122
- "table_number": table_json.get("table_number", ""),
123
- "table_title": table_json.get("table_title", ""),
124
- }
125
 
126
- description = table_json.get("table_description", "")
127
- headers = table_json.get("headers", [])
128
 
129
- table_text = f"ТАБЛИЦА: {table_json.get('table_number', '')} - {table_json.get('table_title', '')}\n"
130
- table_text += f"ДОКУМЕНТ: {document_id}\n"
131
- table_text += f"РАЗДЕЛ: {table_json.get('section', '')}\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
- if description:
134
- table_text += f"ОПИСАНИЕ: {description}\n"
 
 
 
 
 
135
 
136
- if headers:
137
- table_text += f"ЗАГОЛОВКИ ТАБЛИЦЫ: {' | '.join(headers)}\n"
 
 
 
138
 
139
- data = table_json.get("data", [])
140
- if data:
141
- table_text += "ДАННЫЕ ТАБЛИЦЫ:\n"
142
- for i, row in enumerate(data):
143
- if isinstance(row, dict):
144
- row_str = " | ".join([f"{k}: {v}" for k,v in row.items()])
145
- table_text += f"Строка {i+1}: {row_str}\n"
146
 
147
- return Document(text=table_text, metadata=metadata)
148
-
149
- def download_table_data():
150
- log_message("📥 Загрузка табличных данных...")
151
 
152
- from huggingface_hub import list_repo_files
 
 
 
 
 
 
 
 
 
 
 
153
 
154
- table_files = []
155
- try:
156
- files = list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
157
- for file in files:
158
- if file.startswith(table_data_dir) and file.endswith('.json'):
159
- table_files.append(file)
160
-
161
- log_message(f"📊 Найдено {len(table_files)} JSON файлов с таблицами")
162
-
163
- table_documents = []
164
- for file_path in table_files:
165
- try:
166
- log_message(f"🔄 Обработка файла: {file_path}")
167
-
168
- local_path = hf_hub_download(
169
- repo_id=REPO_ID,
170
- filename=file_path,
171
- local_dir=download_dir,
172
- repo_type="dataset",
173
- token=HF_TOKEN
174
- )
175
-
176
- with open(local_path, 'r', encoding='utf-8') as f:
177
- table_data = json.load(f)
178
-
179
- log_message(f"📋 Структура JSON: {list(table_data.keys()) if isinstance(table_data, dict) else 'Список'}")
180
-
181
- if isinstance(table_data, dict):
182
- if 'sheets' in table_data:
183
- for sheet in table_data['sheets']:
184
- doc = table_to_document(sheet)
185
- table_documents.append(doc)
186
- log_message(f"✅ Создан документ из таблицы: {sheet.get('table_number', 'unknown')}")
187
- else:
188
- doc = table_to_document(table_data)
189
- table_documents.append(doc)
190
- log_message(f"✅ Создан документ из JSON объекта")
191
- elif isinstance(table_data, list):
192
- for table_json in table_data:
193
- doc = table_to_document(table_json)
194
- table_documents.append(doc)
195
- log_message(f"✅ Создан документ из элемента списка")
196
-
197
- except Exception as e:
198
- log_message(f"❌ Ошибка обработки файла {file_path}: {str(e)}")
199
- continue
200
-
201
- log_message(f"✅ Создано {len(table_documents)} документов из таблиц")
202
- return table_documents
203
-
204
- except Exception as e:
205
- log_message(f"❌ Ошибка загрузки табличных данных: {str(e)}")
206
- return []
207
-
208
 
209
- def improve_query_with_history(question, chat_history_list):
 
 
210
  try:
211
- log_message("🔄 Улучшение запроса с учетом истории...")
 
 
 
 
212
 
213
- if not chat_history_list:
214
- log_message("📝 История чата пуста, используем оригинальный запрос")
215
- return question
216
 
217
- history_context = ""
218
- for i, (user_msg, bot_msg) in enumerate(chat_history_list[-3:], 1):
219
- history_context += f"Вопрос {i}: {user_msg}\nОтвет {i}: {bot_msg[:200]}...\n\n"
 
 
 
 
 
220
 
221
- improvement_prompt = f"""Проанализируй историю диалога и улучши текущий запрос пользователя.
222
-
223
- ИСТОРИЯ ДИАЛОГА:
224
- {history_context}
225
-
226
- ТЕКУЩИЙ ЗАПРОС: {question}
227
-
228
- ПРАВИЛА:
229
- 1. Если запрос неполный или ссылается на предыдущий контекст (например: "что это", чем это", "объясни это"), дополни его информацией из истории
230
- 2. Если запрос самодостаточный, верни его без изменений
231
- 3. Сохраняй ключевые термины и названия документов из истории
232
- 4. Отвечай только улучшенным запросом без дополнительных пояснений
233
-
234
- УЛУЧШЕННЫЙ ЗАПРОС:"""
235
-
236
- from llama_index.llms.google_genai import GoogleGenAI
237
- llm = GoogleGenAI(model="gemini-2.0-flash", api_key=GOOGLE_API_KEY)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
 
239
- improved_query = llm.complete(improvement_prompt).text.strip()
240
- log_message(f"✨ Улучшенный запрос: {improved_query}")
241
 
242
- return improved_query
 
 
 
 
243
 
244
  except Exception as e:
245
- log_message(f"Ошибка улучшения запроса: {str(e)}")
246
- return question
247
-
248
 
249
- def format_chat_history():
250
- if not chat_history:
251
- return "История чата пуста."
252
-
253
- history_text = ""
254
- for i, (user_msg, bot_msg) in enumerate(chat_history[-5:], 1):
255
- history_text += f"Сообщение {i}:\nПользователь: {user_msg}\nАссистент: {bot_msg}\n\n"
256
-
257
- return history_text
258
-
259
-
260
- def answer_question(question, history):
261
- global query_engine, chunks_df, chat_history
262
-
263
- if query_engine is None:
264
- return history + [["", "❌ Система не инициализирована"]], ""
265
 
266
  try:
267
- start_time = time.time()
268
-
269
- log_message(f"🔍 Получен вопрос: {question}")
270
- log_message(f"📜 История чата: {len(chat_history)} сообщений")
271
-
272
- # Улучшаем запрос с учетом истории
273
- improved_question = improve_query_with_history(question, chat_history)
274
- log_message(f"🎯 Обработка улучшенного запроса: {improved_question}")
275
-
276
- # Форматируем историю чата для промпта
277
- chat_history_text = format_chat_history()
278
- log_message(f"📝 Сформированная история для промпта: {len(chat_history_text)} символов")
279
-
280
- log_message("🔎 Поиск релевантных чанков...")
281
- retrieved_nodes = query_engine.retriever.retrieve(improved_question)
282
- log_message(f"📊 Найдено {len(retrieved_nodes)} релевантных чанков")
283
-
284
- # Логируем найденные чанки
285
- for i, node in enumerate(retrieved_nodes[:3]):
286
- log_message(f"📄 Чанк {i+1}: {node.text[:100]}...")
287
- log_message(f"🏷️ Метаданные: {node.metadata}")
288
 
289
- log_message("🤖 Отправка запроса в LLM...")
 
290
 
291
- # Создаем контекст с историей чата
292
- query_with_context = f"""
293
- История чата:
294
- {chat_history_text}
295
-
296
- Текущий вопрос: {question}
297
- """
298
-
299
- response = query_engine.query(query_with_context)
300
-
301
- end_time = time.time()
302
- processing_time = end_time - start_time
303
-
304
- bot_response = response.response
305
- log_message(f"✅ Получен ответ: {bot_response[:100]}...")
306
-
307
- # Проверяем, что ответ на русском языке
308
- if any(english_word in bot_response.lower() for english_word in ['i am sorry', 'i cannot', 'the query', 'this request']):
309
- log_message("⚠️ Обнаружен ответ на английском языке, форсируем русский ответ")
310
-
311
- # Принудительно запрашиваем ответ на русском
312
- russian_prompt = f"""
313
- ВАЖНО: Отвечай ТОЛЬКО на русском языке!
314
-
315
- Вопрос: {question}
316
- История: {chat_history_text}
317
- Контекст: {retrieved_nodes[0].text if retrieved_nodes else 'Нет контекста'}
318
-
319
- Если информации недостаточно для ответа, скажи: "Недостаточно информации для ответа на ваш вопрос в предоставленной документации."
320
-
321
- Ответ на русском языке:
322
- """
323
 
324
- from llama_index.llms.google_genai import GoogleGenAI
325
- llm = GoogleGenAI(model="gemini-2.0-flash", api_key=GOOGLE_API_KEY)
326
- bot_response = llm.complete(russian_prompt).text.strip()
327
- log_message(f"🔄 Исправленный ответ на русском: {bot_response[:100]}...")
328
-
329
- # Обновляем историю чата
330
- chat_history.append((question, bot_response))
331
-
332
- if len(chat_history) > 10:
333
- chat_history = chat_history[-10:]
334
-
335
- log_message(f"💾 История чата обновлена. Всего сообщений: {len(chat_history)}")
336
-
337
- sources_html = generate_sources_html(retrieved_nodes)
338
-
339
- response_with_time = f"{bot_response}\n\n⏱️ Время обработки: {processing_time:.2f} сек"
340
-
341
- history.append([question, response_with_time])
342
-
343
- return history, sources_html
344
-
345
  except Exception as e:
346
- error_msg = f"Ошибка обработки вопроса: {str(e)}"
347
- log_message(f"❌ Ошибка: {str(e)}")
348
- history.append([question, error_msg])
349
- return history, ""
350
 
 
 
 
 
 
 
351
 
352
- def initialize_models():
353
- global query_engine, chunks_df
354
-
355
  try:
356
- log_message("🔄 Инициализация системы...")
357
- os.makedirs(download_dir, exist_ok=True)
358
-
359
- log_message("📥 Загрузка основных файлов...")
360
- faiss_index_path = hf_hub_download(
361
- repo_id=REPO_ID,
362
- filename=faiss_index_filename,
363
- local_dir=download_dir,
364
- repo_type="dataset",
365
- token=HF_TOKEN
366
- )
367
-
368
- chunks_csv_path = hf_hub_download(
369
- repo_id=REPO_ID,
370
- filename=chunks_filename,
371
- local_dir=download_dir,
372
- repo_type="dataset",
373
- token=HF_TOKEN
374
- )
375
-
376
- log_message("📚 Загрузка индекса и данных...")
377
- index_faiss = faiss.read_index(faiss_index_path)
378
- chunks_df = pd.read_csv(chunks_csv_path)
379
- log_message(f"📄 Загружено {len(chunks_df)} основных чанков")
380
- log_message(f"📋 Колонки в chunks_df: {list(chunks_df.columns)}")
381
-
382
- table_documents = download_table_data()
383
-
384
- log_message("🤖 Настройка моделей...")
385
- embed_model = HuggingFaceEmbedding(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
386
- llm = GoogleGenAI(model="gemini-2.0-flash", api_key=GOOGLE_API_KEY)
387
-
388
- Settings.embed_model = embed_model
389
- Settings.llm = llm
390
-
391
- text_column = None
392
- for col in chunks_df.columns:
393
- if 'text' in col.lower() or 'content' in col.lower() or 'chunk' in col.lower():
394
- text_column = col
395
- break
396
-
397
- if text_column is None:
398
- text_column = chunks_df.columns[0]
399
-
400
- log_message(f"📝 Используется колонка для текста: {text_column}")
401
-
402
- documents = []
403
- for i, (_, row) in enumerate(chunks_df.iterrows()):
404
- doc = Document(
405
- text=str(row[text_column]),
406
- metadata={
407
- "chunk_id": row.get('chunk_id', i),
408
- "document_id": row.get('document_id', 'unknown')
409
- }
410
- )
411
- documents.append(doc)
412
-
413
- documents.extend(table_documents)
414
- log_message(f"📋 Всего создано {len(documents)} документов ({len(chunks_df)} чанков + {len(table_documents)} таблиц)")
415
-
416
- log_message("🔍 Построение векторного индекса...")
417
- vector_index = VectorStoreIndex.from_documents(documents)
418
-
419
- retriever = VectorIndexRetriever(
420
- index=vector_index,
421
- similarity_top_k=20,
422
- similarity_cutoff=0.7
423
- )
424
-
425
- custom_prompt_template = PromptTemplate(CUSTOM_PROMPT_NEW)
426
- response_synthesizer = get_response_synthesizer(
427
- response_mode=ResponseMode.TREE_SUMMARIZE,
428
- text_qa_template=custom_prompt_template
429
- )
430
-
431
- query_engine = RetrieverQueryEngine(
432
- retriever=retriever,
433
- response_synthesizer=response_synthesizer
434
- )
435
-
436
- log_message("✅ Система успешно инициализирована!")
437
- return True
438
-
439
- except Exception as e:
440
- log_message(f"❌ Ошибка инициализации: {str(e)}")
441
- return False
442
 
443
- def generate_sources_html(nodes):
444
- html = "<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; max-height: 400px; overflow-y: auto;'>"
445
- html += "<h3 style='color: #63b3ed; margin-top: 0;'>📚 Источники:</h3>"
446
-
447
- unique_docs = {}
448
- for node in nodes:
449
- metadata = node.metadata if hasattr(node, 'metadata') else {}
450
- doc_id = metadata.get('document_id', 'unknown')
451
- if doc_id not in unique_docs:
452
- unique_docs[doc_id] = []
453
- unique_docs[doc_id].append(node)
454
-
455
- for doc_id, doc_nodes in unique_docs.items():
456
- if doc_id == 'unknown' or doc_id == 'Раздел документа':
457
- continue
458
-
459
- file_link = None
460
- if chunks_df is not None and 'file_link' in chunks_df.columns:
461
- doc_rows = chunks_df[chunks_df['document_id'] == doc_id]
462
- if not doc_rows.empty:
463
- file_link = doc_rows.iloc[0]['file_link']
464
-
465
- html += f"<div style='margin-bottom: 15px; padding: 15px; border: 1px solid #4a5568; border-radius: 8px; background-color: #1a202c;'>"
466
- html += f"<h4 style='margin: 0 0 10px 0; color: #63b3ed;'>📄 {doc_id}</h4>"
467
-
468
- if file_link:
469
- html += f"<a href='{file_link}' target='_blank' style='color: #68d391; text-decoration: none; font-size: 14px; display: inline-block; margin-bottom: 10px;'>🔗 Ссылка на документ</a><br>"
470
-
471
- table_nodes = [node for node in doc_nodes if 'table_number' in node.metadata]
472
- if table_nodes:
473
- for node in table_nodes[:3]:
474
- metadata = node.metadata
475
- table_num = metadata.get('table_number', '')
476
- table_title = metadata.get('table_title', 'Без названия')
477
- if table_num and table_title != 'Без названия':
478
- html += f"<p style='font-size: 12px; color: #a0aec0; margin: 5px 0;'>📊 {table_num}: {table_title}</p>"
479
-
480
- html += "</div>"
481
-
482
- html += "</div>"
483
- return html
484
 
485
- def clear_chat():
486
- global chat_history
487
- chat_history = []
488
- log_message("🗑️ История чата очищена")
489
- return [], ""
490
 
491
- def handle_submit(message, history):
492
- if not message.strip():
493
- return history, ""
494
-
495
- updated_history, sources = answer_question(message, history)
496
- return updated_history, sources
497
 
498
- def create_demo_interface():
499
  with gr.Blocks(title="AIEXP - AI Expert для нормативной документации", theme=gr.themes.Soft()) as demo:
500
 
501
  gr.Markdown("""
@@ -504,65 +216,131 @@ def create_demo_interface():
504
  ## Инструмент для работы с нормативной документацией
505
  """)
506
 
507
- with gr.Tab("💬 Чат с документами"):
508
  gr.Markdown("### Задайте вопрос по нормативной документации")
509
 
510
  with gr.Row():
511
  with gr.Column(scale=2):
512
- chatbot = gr.Chatbot(
513
- label="Диалог с AIEXP",
514
- height=500,
515
- show_copy_button=True
 
516
  )
517
-
518
- with gr.Row():
519
- msg = gr.Textbox(
520
- label="Ваш вопрос",
521
- placeholder="Введите вопрос по нормативным документам...",
522
- lines=2,
523
- scale=4
524
- )
525
- send_btn = gr.Button("📤 Отправить", variant="primary", scale=1)
526
-
527
- with gr.Row():
528
- clear_btn = gr.Button("🗑️ Очистить чат", variant="secondary")
 
 
 
 
529
 
530
  gr.Examples(
531
  examples=[
 
 
532
  "Какой стандарт устанавливает порядок признания протоколов испытаний продукции в области использования атомной энергии?",
533
  "Кто несет ответственность за организацию и проведение признания протоколов испытаний продукции?",
534
- "В каких случаях могут быть признаны протоколы испытаний, проведенные лабораториями, не включенными в перечисления?",
 
535
  ],
536
- inputs=msg
 
 
 
 
 
 
 
537
  )
538
 
539
  with gr.Column(scale=1):
540
  sources_output = gr.HTML(
541
- label="Источники",
542
- value="<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>Здесь появятся источники...</div>",
 
 
 
 
 
 
543
  )
544
 
545
- def submit_message(message, history):
546
- return handle_submit(message, history)
547
-
548
- msg.submit(submit_message, [msg, chatbot], [chatbot, sources_output]).then(
549
- lambda: "", None, msg
550
  )
551
 
552
- send_btn.click(submit_message, [msg, chatbot], [chatbot, sources_output]).then(
553
- lambda: "", None, msg
 
 
554
  )
555
 
556
- clear_btn.click(clear_chat, outputs=[chatbot, sources_output])
557
-
 
 
 
558
  return demo
559
 
560
- if __name__ == "__main__":
561
- log_message("🚀 Запуск AIEXP - AI Expert для нормативной документации")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
 
563
- if initialize_models():
564
- log_message("🌟 Запуск веб-интерфейса...")
565
- demo = create_demo_interface()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
566
  demo.launch(
567
  server_name="0.0.0.0",
568
  server_port=7860,
@@ -570,5 +348,8 @@ if __name__ == "__main__":
570
  debug=False
571
  )
572
  else:
573
- log_message("Невозможно запустить приложение из-за ошибки инициализации")
574
- 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, answer_question
6
+ from my_logging import log_message
7
+ from index_retriever import create_vector_index, create_query_engine
 
 
 
 
8
  import sys
9
+ from config import (
10
+ HF_REPO_ID, HF_TOKEN, DOWNLOAD_DIR, CHUNKS_FILENAME,
11
+ JSON_FILES_DIR, TABLE_DATA_DIR, IMAGE_DATA_DIR, DEFAULT_MODEL, AVAILABLE_MODELS
12
+ )
13
+
14
+ def create_chunks_display_html(chunk_info):
15
+ if not chunk_info:
16
+ return "<div style='padding: 20px; text-align: center; color: black;'>Нет данных о чанках</div>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
+ html = "<div style='max-height: 500px; overflow-y: auto; padding: 10px; color: black;'>"
19
+ html += f"<h4 style='color: black;'>Найдено релевантных чанков: {len(chunk_info)}</h4>"
20
 
21
+ for i, chunk in enumerate(chunk_info):
22
+ bg_color = "#f8f9fa" if i % 2 == 0 else "#e9ecef"
23
+
24
+ # Get section display info
25
+ section_display = get_section_display(chunk)
26
+ formatted_content = get_formatted_content(chunk)
27
+
28
+ html += f"""
29
+ <div style='background-color: {bg_color}; padding: 10px; margin: 5px 0; border-radius: 5px; border-left: 4px solid #007bff; color: black;'>
30
+ <strong style='color: black;'>Документ:</strong> <span style='color: black;'>{chunk['document_id']}</span><br>
31
+ <strong style='color: black;'>Раздел:</strong> <span style='color: black;'>{section_display}</span><br>
32
+ <strong style='color: black;'>Содержание:</strong><br>
33
+ <div style='background-color: white; padding: 8px; margin-top: 5px; border-radius: 3px; font-family: monospace; font-size: 12px; color: black; max-height: 200px; overflow-y: auto;'>
34
+ {formatted_content}
35
+ </div>
36
+ </div>
37
+ """
38
 
39
+ html += "</div>"
40
+ return html
41
+
42
+ def get_section_display(chunk):
43
+ section_path = chunk.get('section_path', '')
44
+ section_id = chunk.get('section_id', 'unknown')
45
+ doc_type = chunk.get('type', 'text')
46
 
47
+ if doc_type == 'table' and chunk.get('table_number'):
48
+ table_num = chunk.get('table_number')
49
+ if not str(table_num).startswith('№'):
50
+ table_num = f"№{table_num}"
51
+ return f"таблица {table_num}"
52
 
53
+ if doc_type == 'image' and chunk.get('image_number'):
54
+ image_num = chunk.get('image_number')
55
+ if not str(image_num).startswith('№'):
56
+ image_num = f"№{image_num}"
57
+ return f"рисунок {image_num}"
 
 
58
 
59
+ if section_path:
60
+ return section_path
61
+ elif section_id and section_id != 'unknown':
62
+ return section_id
63
 
64
+ return section_id
65
+
66
+ def get_formatted_content(chunk):
67
+ document_id = chunk.get('document_id', 'unknown')
68
+ section_path = chunk.get('section_path', '')
69
+ section_id = chunk.get('section_id', 'unknown')
70
+ section_text = chunk.get('section_text', '')
71
+ parent_section = chunk.get('parent_section', '')
72
+ parent_title = chunk.get('parent_title', '')
73
+ level = chunk.get('level', '')
74
+ chunk_text = chunk.get('chunk_text', '')
75
+ doc_type = chunk.get('type', 'text')
76
 
77
+ # For text documents
78
+ if level in ['subsection', 'sub_subsection', 'sub_sub_subsection'] and parent_section:
79
+ current_section = section_path if section_path else section_id
80
+ parent_info = f"{parent_section} ({parent_title})" if parent_title else parent_section
81
+ return f"В разделе {parent_info} в документе {document_id}, пункт {current_section}: {chunk_text}"
82
+ else:
83
+ current_section = section_path if section_path else section_id
84
+ clean_text = chunk_text
85
+ if section_text and chunk_text.startswith(section_text):
86
+ section_title = section_text
87
+ elif chunk_text.startswith(f"{current_section} "):
88
+ clean_text = chunk_text[len(f"{current_section} "):].strip()
89
+ section_title = section_text if section_text else f"{current_section} {clean_text.split('.')[0] if '.' in clean_text else clean_text[:50]}"
90
+ else:
91
+ section_title = section_text if section_text else current_section
92
+
93
+ return f"В разделе {current_section} в документе {document_id}, пункт {section_title}: {clean_text}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
+ def initialize_system(repo_id, hf_token, download_dir, chunks_filename=None,
96
+ json_files_dir=None, table_data_dir=None, image_data_dir=None,
97
+ use_json_instead_csv=False):
98
  try:
99
+ from documents_prep import process_documents_with_chunking
100
+ log_message("Инициализация системы")
101
+ os.makedirs(download_dir, exist_ok=True)
102
+ from config import CHUNK_SIZE, CHUNK_OVERLAP
103
+ from llama_index.core.text_splitter import TokenTextSplitter
104
 
105
+ embed_model = get_embedding_model()
106
+ llm = get_llm_model(DEFAULT_MODEL)
107
+ reranker = get_reranker_model()
108
 
109
+ Settings.embed_model = embed_model
110
+ Settings.llm = llm
111
+ Settings.text_splitter = TokenTextSplitter(
112
+ chunk_size=CHUNK_SIZE,
113
+ chunk_overlap=CHUNK_OVERLAP,
114
+ separator=" ",
115
+ backup_separators=["\n", ".", "!", "?"]
116
+ )
117
 
118
+ log_message(f"Configured chunk size: {CHUNK_SIZE} tokens")
119
+ log_message(f"Configured chunk overlap: {CHUNK_OVERLAP} tokens")
120
+
121
+ all_documents = []
122
+ chunks_df = None
123
+ chunk_info = []
124
+
125
+ if use_json_instead_csv and json_files_dir:
126
+ log_message("Используем JSON файлы вместо CSV")
127
+ json_documents, json_chunk_info = load_json_documents(repo_id, hf_token, json_files_dir, download_dir)
128
+ all_documents.extend(json_documents)
129
+ chunk_info.extend(json_chunk_info)
130
+ else:
131
+ if chunks_filename:
132
+ log_message("Загружаем данные из CSV")
133
+ csv_documents, chunks_df = load_csv_chunks(repo_id, hf_token, chunks_filename, download_dir)
134
+ all_documents.extend(csv_documents)
135
+
136
+ if table_data_dir:
137
+ log_message("Добавляю табличные данные")
138
+ table_documents = load_table_data(repo_id, hf_token, table_data_dir)
139
+ log_message(f"Загружено {len(table_documents)} табличных документов")
140
+
141
+ # Process table documents through chunking
142
+ chunked_table_docs, table_chunk_info = process_documents_with_chunking(table_documents)
143
+ all_documents.extend(chunked_table_docs)
144
+ chunk_info.extend(table_chunk_info)
145
+
146
+ if image_data_dir:
147
+ log_message("Добавляю данные изображений")
148
+ image_documents = load_image_data(repo_id, hf_token, image_data_dir)
149
+ log_message(f"Загружено {len(image_documents)} документов изображений")
150
+
151
+ # Process image documents through chunking
152
+ chunked_image_docs, image_chunk_info = process_documents_with_chunking(image_documents)
153
+ all_documents.extend(chunked_image_docs)
154
+ chunk_info.extend(image_chunk_info)
155
 
156
+ log_message(f"Всего документов после всей обработки: {len(all_documents)}")
 
157
 
158
+ vector_index = create_vector_index(all_documents)
159
+ query_engine = create_query_engine(vector_index)
160
+
161
+ log_message(f"Система успешно инициализирована")
162
+ return query_engine, chunks_df, reranker, vector_index, chunk_info
163
 
164
  except Exception as e:
165
+ log_message(f"Ошибка инициализации: {str(e)}")
166
+ return None, None, None, None, []
 
167
 
168
+ def switch_model(model_name, vector_index):
169
+ from llama_index.core import Settings
170
+ from index_retriever import create_query_engine
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
  try:
173
+ log_message(f"Переключение на модель: {model_name}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
+ new_llm = get_llm_model(model_name)
176
+ Settings.llm = new_llm
177
 
178
+ if vector_index is not None:
179
+ new_query_engine = create_query_engine(vector_index)
180
+ log_message(f"Модель успешно переключена на: {model_name}")
181
+ return new_query_engine, f"✅ Модель переключена на: {model_name}"
182
+ else:
183
+ return None, "❌ Ошибка: система не инициализирована"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  except Exception as e:
186
+ error_msg = f"Ошибка переключения модели: {str(e)}"
187
+ log_message(error_msg)
188
+ return None, f"❌ {error_msg}"
 
189
 
190
+ def main_answer_question(question):
191
+ global query_engine, reranker, current_model, chunks_df
192
+ if not question.strip():
193
+ return ("<div style='color: black;'>Пожалуйста, введите вопрос</div>",
194
+ "<div style='color: black;'>Источники появятся после обработки запроса</div>",
195
+ "<div style='color: black;'>Чанки появятся после обработки запроса</div>")
196
 
 
 
 
197
  try:
198
+ # Call the answer_question function which returns 3 values
199
+ answer_html, sources_html, chunks_html = answer_question(question, query_engine, reranker, current_model, chunks_df)
200
+ return answer_html, sources_html, chunks_html
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
+ except Exception as e:
203
+ log_message(f"Ошибка при ответе на вопрос: {str(e)}")
204
+ return (f"<div style='color: red;'>Ошибка: {str(e)}</div>",
205
+ "<div style='color: black;'>Источники недоступны из-за ошибки</div>",
206
+ "<div style='color: black;'>Чанки недоступны из-за ошибки</div>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
 
 
 
 
 
208
 
 
 
 
 
 
 
209
 
210
+ def create_demo_interface(answer_question_func, switch_model_func, current_model, chunk_info=None):
211
  with gr.Blocks(title="AIEXP - AI Expert для нормативной документации", theme=gr.themes.Soft()) as demo:
212
 
213
  gr.Markdown("""
 
216
  ## Инструмент для работы с нормативной документацией
217
  """)
218
 
219
+ with gr.Tab("Поиск по нормативным документам"):
220
  gr.Markdown("### Задайте вопрос по нормативной документации")
221
 
222
  with gr.Row():
223
  with gr.Column(scale=2):
224
+ model_dropdown = gr.Dropdown(
225
+ choices=list(AVAILABLE_MODELS.keys()),
226
+ value=current_model,
227
+ label="Выберите языковую модель",
228
+ info="Выберите модель для генерации ответов"
229
  )
230
+ with gr.Column(scale=1):
231
+ switch_btn = gr.Button("Переключить модель", variant="secondary")
232
+ model_status = gr.Textbox(
233
+ value=f"Текущая модель: {current_model}",
234
+ label="Статус модели",
235
+ interactive=False
236
+ )
237
+
238
+ with gr.Row():
239
+ with gr.Column(scale=3):
240
+ question_input = gr.Textbox(
241
+ label="Ваш вопрос к базе знаний",
242
+ placeholder="Введите вопрос по нормативным документам...",
243
+ lines=3
244
+ )
245
+ ask_btn = gr.Button("Найти ответ", variant="primary", size="lg")
246
 
247
  gr.Examples(
248
  examples=[
249
+ "О чем этот рисунок: ГОСТ Р 50.04.07-2022 Приложение Л. Л.1.5 Рисунок Л.2",
250
+ "Л.9 Формула в ГОСТ Р 50.04.07 - 2022 что и о чем там?",
251
  "Какой стандарт устанавливает порядок признания протоколов испытаний продукции в области использования атомной энергии?",
252
  "Кто несет ответственность за организацию и проведение признания протоколов испытаний продукции?",
253
+ "В каких случаях могут быть признаны протоколы испытаний, проведенные лабораториями?",
254
+ "В какой таблице можно найти информацию о методы исследований при аттестационных испытаниях технологии термической обработки заготовок из легированных сталей? Какой документ и какой раздел?"
255
  ],
256
+ inputs=question_input
257
+ )
258
+
259
+ with gr.Row():
260
+ with gr.Column(scale=2):
261
+ answer_output = gr.HTML(
262
+ label="",
263
+ value=f"<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>Здесь появится ответ на ваш вопрос...<br><small>Текущая модель: {current_model}</small></div>",
264
  )
265
 
266
  with gr.Column(scale=1):
267
  sources_output = gr.HTML(
268
+ label="",
269
+ value="<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>Здесь появятся релевантные чанки...</div>",
270
+ )
271
+
272
+ with gr.Column(scale=1):
273
+ chunks_output = gr.HTML(
274
+ label="Релевантные чанки",
275
+ value="<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>Здесь появятся релевантные чанки...</div>",
276
  )
277
 
278
+ switch_btn.click(
279
+ fn=switch_model_func,
280
+ inputs=[model_dropdown],
281
+ outputs=[model_status]
 
282
  )
283
 
284
+ ask_btn.click(
285
+ fn=answer_question_func,
286
+ inputs=[question_input],
287
+ outputs=[answer_output, sources_output, chunks_output]
288
  )
289
 
290
+ question_input.submit(
291
+ fn=answer_question_func,
292
+ inputs=[question_input],
293
+ outputs=[answer_output, sources_output, chunks_output]
294
+ )
295
  return demo
296
 
297
+
298
+ query_engine = None
299
+ chunks_df = None
300
+ reranker = None
301
+ vector_index = None
302
+ current_model = DEFAULT_MODEL
303
+
304
+ def main_answer_question(question):
305
+ global query_engine, reranker, current_model, chunks_df
306
+ answer_html, sources_html, chunks_html = answer_question(
307
+ question, query_engine, reranker, current_model, chunks_df
308
+ )
309
+ return answer_html, sources_html, chunks_html
310
+
311
+ def main_switch_model(model_name):
312
+ global query_engine, vector_index, current_model
313
+
314
+ new_query_engine, status_message = switch_model(model_name, vector_index)
315
+ if new_query_engine:
316
+ query_engine = new_query_engine
317
+ current_model = model_name
318
+
319
+ return status_message
320
+
321
+ def main():
322
+ global query_engine, chunks_df, reranker, vector_index, current_model
323
+
324
+ log_message("Запуск AIEXP - AI Expert для нормативной документации")
325
 
326
+ query_engine, chunks_df, reranker, vector_index, chunk_info = initialize_system(
327
+ repo_id=HF_REPO_ID,
328
+ hf_token=HF_TOKEN,
329
+ download_dir=DOWNLOAD_DIR,
330
+ json_files_dir=JSON_FILES_DIR,
331
+ table_data_dir=TABLE_DATA_DIR,
332
+ image_data_dir=IMAGE_DATA_DIR,
333
+ use_json_instead_csv=True,
334
+ )
335
+
336
+ if query_engine:
337
+ log_message("Запуск веб-интерфейса")
338
+ demo = create_demo_interface(
339
+ answer_question_func=main_answer_question,
340
+ switch_model_func=main_switch_model,
341
+ current_model=current_model,
342
+ chunk_info=chunk_info
343
+ )
344
  demo.launch(
345
  server_name="0.0.0.0",
346
  server_port=7860,
 
348
  debug=False
349
  )
350
  else:
351
+ log_message("Невозможно запустить приложение из-за ошибки инициализации")
352
+ sys.exit(1)
353
+
354
+ if __name__ == "__main__":
355
+ main()