MrSimple07 commited on
Commit
d6c8aaf
·
1 Parent(s): 1f55313

new app py with chat history + json files

Browse files
Files changed (1) hide show
  1. app.py +353 -245
app.py CHANGED
@@ -1,186 +1,386 @@
1
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
2
  import time
3
  import sys
4
- from llama_index.llms.google_genai import GoogleGenAI
5
- from llama_index.core import Settings
6
  from config import *
7
- from document_processor import *
8
- from llama_index.core.chat_engine import CondensePlusContextChatEngine
9
- import faiss
10
 
11
- #new thing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  query_engine = None
13
  chunks_df = None
14
- chat_engine = None
15
  chat_history = []
16
 
17
- def answer_question(question, history):
18
- global query_engine, chat_engine
 
 
 
 
 
 
 
 
 
19
 
20
- if query_engine is None:
21
- return "<div style='background-color: #e53e3e; color: white; padding: 20px; border-radius: 10px;'>❌ System not initialized or document database is empty</div>", "", history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
 
23
  try:
24
- start_time = time.time()
 
 
 
25
 
26
- custom_prompt_template = PromptTemplate(CUSTOM_PROMPT_NEW)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  response_synthesizer = get_response_synthesizer(
29
  response_mode=ResponseMode.TREE_SUMMARIZE,
30
  text_qa_template=custom_prompt_template
31
  )
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
-
34
- if chat_engine is None:
35
- chat_engine = CondensePlusContextChatEngine.from_defaults(
36
- retriever=query_engine.retriever,
37
- response_synthesizer=response_synthesizer,
38
- )
 
 
 
 
39
 
40
- response = chat_engine.chat(question)
41
- retrieved_nodes = query_engine.retriever.retrieve(question)
 
42
 
43
  end_time = time.time()
44
  processing_time = end_time - start_time
45
 
 
 
 
 
 
 
46
  sources_html = generate_sources_html(retrieved_nodes)
47
 
48
- answer_with_time = f"""<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; margin-bottom: 10px;'>
49
- <h3 style='color: #63b3ed; margin-top: 0;'>📋 Answer:</h3>
50
- <div style='line-height: 1.6; font-size: 16px;'>{response.response}</div>
51
- <div style='margin-top: 15px; padding-top: 10px; border-top: 1px solid #4a5568; font-size: 14px; color: #a0aec0;'>
52
- ⏱️ Processing time: {processing_time:.2f} sec
53
- </div>
54
- </div>"""
55
 
56
- new_history = history + [{"role": "user", "content": question}, {"role": "assistant", "content": response.response}]
57
- if len(new_history) > 6:
58
- new_history = new_history[-6:]
59
 
60
- return answer_with_time, sources_html, new_history
61
 
62
  except Exception as e:
63
- error_msg = f"<div style='background-color: #e53e3e; color: white; padding: 20px; border-radius: 10px;'>❌ Error processing question: {str(e)}</div>"
64
- return error_msg, "", history
65
-
66
 
67
  def generate_sources_html(nodes):
68
  html = "<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; max-height: 400px; overflow-y: auto;'>"
69
- html += "<h3 style='color: #63b3ed; margin-top: 0;'>📚 Sources:</h3>"
70
 
71
  unique_docs = {}
72
  for node in nodes:
73
  metadata = node.metadata if hasattr(node, 'metadata') else {}
74
- doc_name = metadata.get('document_name', 'unknown')
75
- doc_link = metadata.get('document_link', '')
76
- doc_key = f"{doc_name}||{doc_link}"
77
- if doc_key not in unique_docs:
78
- unique_docs[doc_key] = []
79
- unique_docs[doc_key].append(node)
80
 
81
- for doc_key, doc_nodes in unique_docs.items():
82
- doc_name, doc_link = doc_key.split('||', 1)
 
 
 
 
 
83
  html += f"<div style='margin-bottom: 15px; padding: 15px; border: 1px solid #4a5568; border-radius: 8px; background-color: #1a202c;'>"
84
- if doc_link:
85
- html += f"<h4 style='margin: 0 0 10px 0; color: #63b3ed;'>📄 <a href='{doc_link}' target='_blank' style='color: #63b3ed;'>{doc_name}</a></h4>"
86
- else:
87
- html += f"<h4 style='margin: 0 0 10px 0; color: #63b3ed;'>📄 {doc_name}</h4>"
88
- html += f"<p style='margin: 0; color: #a0aec0; font-size: 14px;'>Found relevant fragments: {len(doc_nodes)}</p>"
 
 
 
 
 
89
  html += "</div>"
90
 
91
  html += "</div>"
92
  return html
93
 
94
- def get_documents_display():
95
- documents = get_existing_documents()
96
-
97
- if not documents:
98
- return "<div style='padding: 20px; text-align: center; color: #666;'>No documents in system yet</div>"
99
-
100
- html = f"<div style='background-color: #f8f9fa; padding: 20px; border-radius: 10px;'>"
101
- html += f"<h3 style='color: #2d3748; margin-top: 0;'>📚 {len(documents)} documents in the system:</h3>"
102
- html += "<div style='max-height: 400px; overflow-y: auto;'>"
103
-
104
- for i, doc_name in enumerate(documents, 1):
105
- html += f"<div style='padding: 8px; margin: 5px 0; background-color: white; border-radius: 5px; border-left: 4px solid #63b3ed;'>"
106
- html += f"{i}. {doc_name}"
107
- html += "</div>"
108
-
109
- html += "</div></div>"
110
- return html
111
-
112
- def upload_and_process_file(files, doc_names, doc_links):
113
- global query_engine, chunks_df, chat_engine
114
-
115
- if not files:
116
- return "No files selected", get_documents_display()
117
-
118
- if len(files) != len(doc_names) or len(files) != len(doc_links):
119
- return "Error: Number of files must match number of document names and links", get_documents_display()
120
-
121
- existing_docs = get_existing_documents()
122
- results = []
123
-
124
- for i, file in enumerate(files):
125
- doc_name = doc_names[i].strip() if i < len(doc_names) else ""
126
- doc_link = doc_links[i].strip() if i < len(doc_links) else ""
127
-
128
- if not doc_name:
129
- doc_name = file.name.split('/')[-1].replace('.txt', '').replace('.pdf', '')
130
-
131
- # Check if document already exists
132
- if doc_name in existing_docs:
133
- results.append(f"⚠️ {doc_name}: Document already exists in the system")
134
- continue
135
-
136
- log_message(f"🔄 Starting processing of file {i+1}/{len(files)}: {file.name}")
137
-
138
- file_info, error = process_uploaded_file(file.name, file.name.split('/')[-1], doc_name, doc_link)
139
-
140
- if error:
141
- results.append(f"❌ {file.name.split('/')[-1]}: {error}")
142
- continue
143
-
144
- query_engine, chunks_df, error = add_to_vector_index(file_info['chunks'], file_info, chunks_df)
145
-
146
- if error:
147
- results.append(f"❌ {file_info['file_name']}: Error adding to database - {error}")
148
- else:
149
- results.append(f"✅ {file_info['document']}: Successfully processed and added to database")
150
- log_message(f"✅ Completed processing: {file_info['document']}")
151
- # Reset chat engine to include new documents
152
- chat_engine = None
153
-
154
- return "\n".join(results), get_documents_display()
155
 
156
- def create_interface():
157
- with gr.Blocks(title="AIEXP - AI Expert for Regulatory Documentation", theme=gr.themes.Soft()) as demo:
158
 
159
  gr.Markdown("""
160
  # AIEXP - Artificial Intelligence Expert
161
 
162
- ## Tool for working with regulatory documentation
163
  """)
164
 
165
- with gr.Tab("🔍 Document Search"):
166
- gr.Markdown("### Ask a question about the uploaded documentation")
167
 
168
  with gr.Row():
169
- with gr.Column(scale=3):
170
  chatbot = gr.Chatbot(
171
- label="Chat History",
172
- height=400,
173
- show_label=True,
174
- type="messages"
175
  )
176
 
177
- question_input = gr.Textbox(
178
- label="Your question to the knowledge base",
179
- placeholder="Enter your question about the documents...",
180
- lines=3
181
- )
182
- ask_btn = gr.Button("🔍 Find Answer", variant="primary", size="lg")
183
- clear_btn = gr.Button("🗑️ Clear History", variant="secondary")
 
 
 
 
184
 
185
  gr.Examples(
186
  examples=[
@@ -188,141 +388,49 @@ def create_interface():
188
  "Кто несет ответственность за организацию и проведение признания протоколов испытаний продукции?",
189
  "В каких случаях могут быть признаны протоколы испытаний, проведенные лабораториями, не включенными в перечисления?",
190
  ],
191
- inputs=question_input
192
  )
193
-
194
  with gr.Column(scale=1):
195
- answer_output = gr.HTML(
196
- label="",
197
- value="<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>The answer to your question will appear here...</div>",
198
- )
199
-
200
  sources_output = gr.HTML(
201
- label="",
202
- value="<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>Sources will appear here...</div>",
203
  )
204
 
205
- ask_btn.click(
206
- fn=answer_question,
207
- inputs=[question_input, chatbot],
208
- outputs=[answer_output, sources_output, chatbot]
209
- ).then(
210
- lambda: "", inputs=None, outputs=question_input
211
- )
212
 
213
- question_input.submit(
214
- fn=answer_question,
215
- inputs=[question_input, chatbot],
216
- outputs=[answer_output, sources_output, chatbot]
217
- ).then(
218
- lambda: "", inputs=None, outputs=question_input
219
- )
220
 
221
- clear_btn.click(
222
- lambda: [], inputs=None, outputs=chatbot
223
  )
224
-
225
- with gr.Tab("📚 Document Management"):
226
- gr.Markdown("### Document database and adding new files")
227
 
228
- with gr.Row():
229
- with gr.Column(scale=2):
230
- documents_display = gr.HTML(
231
- label="Document list",
232
- value=get_documents_display()
233
- )
234
-
235
- refresh_btn = gr.Button("🔄 Refresh List", variant="secondary")
236
-
237
- with gr.Column(scale=1):
238
- gr.Markdown("#### Upload new documents")
239
- gr.Markdown("Supported formats: PDF, TXT")
240
-
241
- file_upload = gr.File(
242
- file_count="multiple",
243
- file_types=[".pdf", ".txt"],
244
- label="Select files to upload"
245
- )
246
-
247
- doc_names_input = gr.Textbox(
248
- label="Document names (one per line)",
249
- placeholder="Enter document names, one per line...",
250
- lines=5
251
- )
252
-
253
- doc_links_input = gr.Textbox(
254
- label="Document links (one per line)",
255
- placeholder="Enter document links, one per line...",
256
- lines=5
257
- )
258
-
259
- upload_btn = gr.Button("📤 Upload and Process", variant="primary")
260
-
261
- upload_status = gr.Textbox(
262
- label="Upload status",
263
- lines=8,
264
- max_lines=10,
265
- interactive=False
266
- )
267
-
268
- def process_names_and_links(names_text, links_text):
269
- names = [name.strip() for name in names_text.split('\n') if name.strip()]
270
- links = [link.strip() for link in links_text.split('\n') if link.strip()]
271
- return names, links
272
-
273
- upload_btn.click(
274
- fn=lambda files, names, links: upload_and_process_file(
275
- files,
276
- *process_names_and_links(names, links)
277
- ),
278
- inputs=[file_upload, doc_names_input, doc_links_input],
279
- outputs=[upload_status, documents_display]
280
  )
281
 
282
- refresh_btn.click(
283
- fn=lambda: get_documents_display(),
284
- outputs=[documents_display]
285
- )
286
 
287
  return demo
288
 
289
  if __name__ == "__main__":
290
- try:
291
- log_message("🚀 Starting AIEXP - AI Expert for Regulatory Documentation")
292
-
293
- # Initialize LLM
294
- llm = GoogleGenAI(model="gemini-2.0-flash", api_key=GOOGLE_API_KEY)
295
- Settings.llm = llm
296
-
297
- # Initialize system
298
- query_engine, chunks_df, success = initialize_system()
299
-
300
- log_message("🌟 Starting web interface...")
301
- demo = create_interface()
302
-
303
- # Launch regardless of initialization success
304
  demo.launch(
305
  server_name="0.0.0.0",
306
  server_port=7860,
307
  share=True,
308
- debug=False,
309
- show_error=True
310
  )
311
-
312
- except Exception as e:
313
- log_message(f"❌ Startup error: {str(e)}")
314
- # Create minimal interface even if there's an error
315
- import gradio as gr
316
-
317
- def error_interface():
318
- with gr.Blocks() as demo:
319
- gr.Markdown(f"# Error: {str(e)}")
320
- gr.Markdown("Please check your configuration and try again.")
321
- return demo
322
-
323
- error_demo = error_interface()
324
- error_demo.launch(
325
- server_name="0.0.0.0",
326
- server_port=7860,
327
- share=True
328
- )
 
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 = "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
+ {chat_history}
31
+
32
+ ОПРЕДЕЛЕНИЕ ТИПА ЗАДАЧИ:
33
+ Проанализируйте запрос пользователя и определите тип задачи:
34
+
35
+ 1. КРАТКОЕ САММАРИ (ключевые слова: "кратко", "суммировать", "резюме", "основные моменты", "в двух словах"):
36
+ - Предоставьте структурированное резюме запрашиваемого раздела/пункта
37
+ - Выделите ключевые требования, процедуры или положения
38
+ - Используйте нумерованный список для лучшей читаемости
39
+ - Сохраняйте терминологию НД
40
+
41
+ 2. ПОИСК ДОКУМЕНТА И ПУНКТА (ключевые слова: "найти", "где", "какой документ", "в каком разделе", "ссылка"):
42
+ - Укажите конкретный документ и его структурное расположение
43
+ - Предоставьте точные номера разделов/подразделов/пунктов
44
+ - Процитируйте релевантные фрагменты
45
+ - Если найдено несколько документов, перечислите все с указанием специфики каждого
46
+
47
+ 3. ПРОВЕРКА КОРРЕКТНОСТИ (ключевые слова: "правильно ли", "соответствует ли", "проверить", "корректно", "нарушение"):
48
+ - Сопоставьте предоставленную информацию с требованиями НД
49
+ - Четко укажите: "СООТВЕТСТВУЕТ" или "НЕ СООТВЕТСТВУЕТ"
50
+ - Перечислите конкретные требования НД
51
+ - Укажите выявленные расхождения или подтвердите соответствие
52
+ - Процитируйте релевантные пункты НД
53
+
54
+ 4. ПЛАН ДЕЙСТВИЙ (ключевые слова: "план", "алгоритм", "последовательность", "как действовать", "пошагово"):
55
+ - Создайте пронумерованный пошаговый план
56
+ - Каждый шаг должен содержать ссылку на соответствующий пункт НД
57
+ - Укажите необходимые документы или формы
58
+ - Добавьте временные рамки, если они указаны в НД
59
+ - Выделите критические требования или ограничения
60
+
61
+ ПРАВИЛА ФОРМИРОВАНИЯ ОТВЕТОВ:
62
+
63
+ 1. ОБЯЗАТЕЛЬНОЕ УКАЗАНИЕ ИСТОЧНИКОВ:
64
+ - Для контента из конкретного раздела/подраздела:
65
+ "Согласно разделу [X] и подразделу [X.X]: [Ваш ответ]"
66
+ - Для контента вне подразделов (таблицы, рисунки, общие разделы):
67
+ "Согласно [Название документа] - [Номер и наименование пункта/таблицы/рисунка]: [Ваш ответ]"
68
+ - При наличии метаданных о разделе и подразделе - включайте оба
69
+ - При наличии только раздела: "Согласно разделу [X]: [Ваш ответ]"
70
+
71
+ 2. СТРОГОЕ СЛЕДОВАНИЕ КОНТЕКСТУ:
72
+ - Если информация не найдена: "Информация по вашему запросу не была найдена в нормативной документации."
73
+ - Не делайте предположений или выводов за пределами предоставленного контекста
74
+ - Не используйте общие знания
75
+
76
+ 3. ИСПОЛЬЗОВАНИЕ ТЕРМИНОЛОГИИ НД:
77
+ - Применяйте официальную терминологию из документов
78
+ - Сохраняйте оригинальные формулировки ключевых требований
79
+ - При необходимости разъясняйте специальные термины на основе НД
80
+
81
+ 4. СТРУКТУРИРОВАНИЕ ОТВЕТОВ:
82
+ - Для саммари: используйте маркированные или нумерованные списки
83
+ - Для проверки: четкая структура "Требование → Соответствие/Несоответствие"
84
+ - Для планов: пронумерованные шаги с подзадачами при необходимости
85
+ - Для поиска: указание иерархии документа
86
+
87
+ 5. ДОПОЛНИТЕЛЬНЫЕ РЕКОМЕНДАЦИИ:
88
+ - При множественных релевантных источниках - укажите все
89
+ - Выделяйте критически важные требования
90
+ - Указывайте альтернативные процедуры, если они предусмотрены НД
91
+
92
+ Контекст: {context_str}
93
+
94
+ Вопрос: {query_str}
95
+
96
+ Ответ:
97
+ """
98
+
99
  query_engine = None
100
  chunks_df = None
 
101
  chat_history = []
102
 
103
+ def log_message(message):
104
+ print(message, flush=True)
105
+ sys.stdout.flush()
106
+
107
+ def table_to_document(table_json):
108
+ metadata = {
109
+ "document_id": table_json["document_id"],
110
+ "section": table_json["section"],
111
+ "table_number": table_json["table_number"],
112
+ "table_title": table_json["table_title"],
113
+ }
114
 
115
+ description = table_json["table_description"]
116
+ headers = " | ".join(table_json["headers"])
117
+
118
+ rows = []
119
+ for row in table_json["data"]:
120
+ row_str = " | ".join([f"{k}: {v}" for k,v in row.items()])
121
+ rows.append(row_str)
122
+
123
+ table_text = f"Таблица {table_json['table_number']} - {table_json['table_title']}\n"
124
+ table_text += f"Описание: {description}\n"
125
+ table_text += f"Заголовки: {headers}\n"
126
+ table_text += "\n".join(rows)
127
+
128
+ return Document(text=table_text, metadata=metadata)
129
+
130
+ def download_table_data():
131
+ log_message("📥 Загрузка табличных данных...")
132
+
133
+ from huggingface_hub import list_repo_files
134
 
135
+ table_files = []
136
  try:
137
+ files = list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
138
+ for file in files:
139
+ if file.startswith(table_data_dir) and file.endswith('.json'):
140
+ table_files.append(file)
141
 
142
+ log_message(f"📊 Найдено {len(table_files)} JSON файлов с таблицами")
143
+
144
+ table_documents = []
145
+ for file_path in table_files:
146
+ try:
147
+ local_path = hf_hub_download(
148
+ repo_id=REPO_ID,
149
+ filename=file_path,
150
+ local_dir=download_dir,
151
+ repo_type="dataset",
152
+ token=HF_TOKEN
153
+ )
154
+
155
+ with open(local_path, 'r', encoding='utf-8') as f:
156
+ table_data = json.load(f)
157
+
158
+ if isinstance(table_data, list):
159
+ for table_json in table_data:
160
+ doc = table_to_document(table_json)
161
+ table_documents.append(doc)
162
+ else:
163
+ doc = table_to_document(table_data)
164
+ table_documents.append(doc)
165
+
166
+ except Exception as e:
167
+ log_message(f"❌ Ошибка обработки файла {file_path}: {str(e)}")
168
+ continue
169
+
170
+ log_message(f"✅ Создано {len(table_documents)} документов из таблиц")
171
+ return table_documents
172
+
173
+ except Exception as e:
174
+ log_message(f"❌ Ошибка загрузки табличных данных: {str(e)}")
175
+ return []
176
 
177
+ def format_chat_history():
178
+ if not chat_history:
179
+ return "История чата пуста."
180
+
181
+ history_text = ""
182
+ for i, (user_msg, bot_msg) in enumerate(chat_history[-5:], 1):
183
+ history_text += f"Сообщение {i}:\nПользователь: {user_msg}\nАссистент: {bot_msg}\n\n"
184
+
185
+ return history_text
186
+
187
+ def initialize_models():
188
+ global query_engine, chunks_df
189
+
190
+ try:
191
+ log_message("🔄 Инициализация системы...")
192
+ os.makedirs(download_dir, exist_ok=True)
193
+
194
+ log_message("📥 Загрузка основных файлов...")
195
+ faiss_index_path = hf_hub_download(
196
+ repo_id=REPO_ID,
197
+ filename=faiss_index_filename,
198
+ local_dir=download_dir,
199
+ repo_type="dataset",
200
+ token=HF_TOKEN
201
+ )
202
+
203
+ chunks_csv_path = hf_hub_download(
204
+ repo_id=REPO_ID,
205
+ filename=chunks_filename,
206
+ local_dir=download_dir,
207
+ repo_type="dataset",
208
+ token=HF_TOKEN
209
+ )
210
+
211
+ log_message("📚 Загрузка индекса и данных...")
212
+ index_faiss = faiss.read_index(faiss_index_path)
213
+ chunks_df = pd.read_csv(chunks_csv_path)
214
+ log_message(f"📄 Загружено {len(chunks_df)} основных чанков")
215
+
216
+ table_documents = download_table_data()
217
+
218
+ log_message("🤖 Настройка моделей...")
219
+ embed_model = HuggingFaceEmbedding(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
220
+ llm = GoogleGenAI(model="gemini-2.0-flash", api_key=GOOGLE_API_KEY)
221
+
222
+ Settings.embed_model = embed_model
223
+ Settings.llm = llm
224
+
225
+ text_column = None
226
+ for col in chunks_df.columns:
227
+ if 'text' in col.lower() or 'content' in col.lower() or 'chunk' in col.lower():
228
+ text_column = col
229
+ break
230
+
231
+ if text_column is None:
232
+ text_column = chunks_df.columns[0]
233
+
234
+ log_message("📝 Создание документов из чанков...")
235
+ documents = []
236
+ for i, (_, row) in enumerate(chunks_df.iterrows()):
237
+ doc = Document(
238
+ text=str(row[text_column]),
239
+ metadata={
240
+ "chunk_id": row.get('chunk_id', i),
241
+ "document_id": row.get('document_id', 'unknown')
242
+ }
243
+ )
244
+ documents.append(doc)
245
+
246
+ documents.extend(table_documents)
247
+ log_message(f"📋 Всего создано {len(documents)} документов ({len(chunks_df)} чанков + {len(table_documents)} таблиц)")
248
+
249
+ log_message("🔍 Построение векторного индекса...")
250
+ vector_index = VectorStoreIndex.from_documents(documents)
251
+
252
+ retriever = VectorIndexRetriever(
253
+ index=vector_index,
254
+ similarity_top_k=20,
255
+ similarity_cutoff=0.7
256
+ )
257
+
258
+ custom_prompt_template = PromptTemplate(CUSTOM_PROMPT_NEW)
259
  response_synthesizer = get_response_synthesizer(
260
  response_mode=ResponseMode.TREE_SUMMARIZE,
261
  text_qa_template=custom_prompt_template
262
  )
263
+
264
+ query_engine = RetrieverQueryEngine(
265
+ retriever=retriever,
266
+ response_synthesizer=response_synthesizer
267
+ )
268
+
269
+ log_message("✅ Система успешно инициализирована!")
270
+ return True
271
+
272
+ except Exception as e:
273
+ log_message(f"❌ Ошибка инициализации: {str(e)}")
274
+ return False
275
 
276
+ def answer_question(question, history):
277
+ global query_engine, chunks_df, chat_history
278
+
279
+ if query_engine is None:
280
+ return history + [["", "❌ Система не инициализирована"]], ""
281
+
282
+ try:
283
+ start_time = time.time()
284
+
285
+ chat_history_text = format_chat_history()
286
 
287
+ query_with_history = question
288
+ response = query_engine.query(query_with_history)
289
+ retrieved_nodes = query_engine.retriever.retrieve(query_with_history)
290
 
291
  end_time = time.time()
292
  processing_time = end_time - start_time
293
 
294
+ bot_response = response.response
295
+ chat_history.append((question, bot_response))
296
+
297
+ if len(chat_history) > 10:
298
+ chat_history = chat_history[-10:]
299
+
300
  sources_html = generate_sources_html(retrieved_nodes)
301
 
302
+ response_with_time = f"{bot_response}\n\n⏱️ Время обработки: {processing_time:.2f} сек"
 
 
 
 
 
 
303
 
304
+ history.append([question, response_with_time])
 
 
305
 
306
+ return history, sources_html
307
 
308
  except Exception as e:
309
+ error_msg = f" Ошибка обработки вопроса: {str(e)}"
310
+ history.append([question, error_msg])
311
+ return history, ""
312
 
313
  def generate_sources_html(nodes):
314
  html = "<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; max-height: 400px; overflow-y: auto;'>"
315
+ html += "<h3 style='color: #63b3ed; margin-top: 0;'>📚 Источники:</h3>"
316
 
317
  unique_docs = {}
318
  for node in nodes:
319
  metadata = node.metadata if hasattr(node, 'metadata') else {}
320
+ doc_id = metadata.get('document_id', 'unknown')
321
+ if doc_id not in unique_docs:
322
+ unique_docs[doc_id] = []
323
+ unique_docs[doc_id].append(node)
 
 
324
 
325
+ for doc_id, doc_nodes in unique_docs.items():
326
+ file_link = None
327
+ if chunks_df is not None and 'file_link' in chunks_df.columns:
328
+ doc_rows = chunks_df[chunks_df['document_id'] == doc_id]
329
+ if not doc_rows.empty:
330
+ file_link = doc_rows.iloc[0]['file_link']
331
+
332
  html += f"<div style='margin-bottom: 15px; padding: 15px; border: 1px solid #4a5568; border-radius: 8px; background-color: #1a202c;'>"
333
+ html += f"<h4 style='margin: 0 0 10px 0; color: #63b3ed;'>📄 {doc_id}</h4>"
334
+
335
+ if file_link:
336
+ 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>"
337
+
338
+ for node in doc_nodes[:3]:
339
+ metadata = node.metadata if hasattr(node, 'metadata') else {}
340
+ if 'table_number' in metadata:
341
+ html += f"<p style='font-size: 12px; color: #a0aec0; margin: 5px 0;'>📊 Таблица {metadata['table_number']}: {metadata.get('table_title', 'Без названия')}</p>"
342
+
343
  html += "</div>"
344
 
345
  html += "</div>"
346
  return html
347
 
348
+ def clear_chat():
349
+ global chat_history
350
+ chat_history = []
351
+ return [], ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
 
353
+ def create_demo_interface():
354
+ with gr.Blocks(title="AIEXP - AI Expert для нормативной документации", theme=gr.themes.Soft()) as demo:
355
 
356
  gr.Markdown("""
357
  # AIEXP - Artificial Intelligence Expert
358
 
359
+ ## Инструмент для работы с нормативной документацией
360
  """)
361
 
362
+ with gr.Tab("💬 Чат с документами"):
363
+ gr.Markdown("### Задайте вопрос по нормативной документации")
364
 
365
  with gr.Row():
366
+ with gr.Column(scale=2):
367
  chatbot = gr.Chatbot(
368
+ label="Диалог с AIEXP",
369
+ height=500,
370
+ show_copy_button=True
 
371
  )
372
 
373
+ with gr.Row():
374
+ msg = gr.Textbox(
375
+ label="Ваш вопрос",
376
+ placeholder="Введите вопрос по нормативным документам...",
377
+ lines=2,
378
+ scale=4
379
+ )
380
+ send_btn = gr.Button("📤 Отправить", variant="primary", scale=1)
381
+
382
+ with gr.Row():
383
+ clear_btn = gr.Button("🗑️ Очистить чат", variant="secondary")
384
 
385
  gr.Examples(
386
  examples=[
 
388
  "Кто несет ответственность за организацию и проведение признания протоколов испытаний продукции?",
389
  "В каких случаях могут быть признаны протоколы испытаний, проведенные лабораториями, не включенными в перечисления?",
390
  ],
391
+ inputs=msg
392
  )
393
+
394
  with gr.Column(scale=1):
 
 
 
 
 
395
  sources_output = gr.HTML(
396
+ label="Источники",
397
+ value="<div style='background-color: #2d3748; color: white; padding: 20px; border-radius: 10px; text-align: center;'>Здесь появятся источники...</div>",
398
  )
399
 
400
+ def user_message(message, history):
401
+ return "", history + [[message, None]]
 
 
 
 
 
402
 
403
+ def bot_message(history):
404
+ if history and history[-1][1] is None:
405
+ user_msg = history[-1][0]
406
+ updated_history, sources = answer_question(user_msg, history[:-1])
407
+ return updated_history, sources
408
+ return history, ""
 
409
 
410
+ msg.submit(user_message, [msg, chatbot], [msg, chatbot], queue=False).then(
411
+ bot_message, chatbot, [chatbot, sources_output]
412
  )
 
 
 
413
 
414
+ send_btn.click(user_message, [msg, chatbot], [msg, chatbot], queue=False).then(
415
+ bot_message, chatbot, [chatbot, sources_output]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  )
417
 
418
+ clear_btn.click(clear_chat, outputs=[chatbot, sources_output])
 
 
 
419
 
420
  return demo
421
 
422
  if __name__ == "__main__":
423
+ log_message("🚀 Запуск AIEXP - AI Expert для нормативной документации")
424
+
425
+ if initialize_models():
426
+ log_message("🌟 Запуск веб-интерфейса...")
427
+ demo = create_demo_interface()
 
 
 
 
 
 
 
 
 
428
  demo.launch(
429
  server_name="0.0.0.0",
430
  server_port=7860,
431
  share=True,
432
+ debug=False
 
433
  )
434
+ else:
435
+ log_message("❌ Невозможно запустить приложение из-за ошибки инициализации")
436
+ sys.exit(1)