import gradio as gr import os import shutil import pandas as pd from datetime import datetime from scripts.document_processor import process_multiple_documents, save_processed_chunks, load_processed_chunks from scripts.rag_engine import build_rag_system, query_documents, format_response_with_sources, add_new_document_to_system, load_rag_system import json import tempfile from scripts.config import * if not os.path.exists(UPLOAD_FOLDER): os.makedirs(UPLOAD_FOLDER) if not os.path.exists("processed_data"): os.makedirs("processed_data") if not os.path.exists(RAG_FILES_DIR): os.makedirs(RAG_FILES_DIR) def initialize_system(): global query_engine query_engine = None # IMPORTANT: Setup LLM settings at the very beginning from scripts.config import setup_llm_settings, download_pretrained_files setup_llm_settings() # Check if local RAG system exists local_rag_exists = os.path.exists(os.path.join(RAG_FILES_DIR, 'faiss_index.index')) local_csv_exists = os.path.exists(PROCESSED_DATA_FILE) # If no local system exists, try to download from HuggingFace if not local_rag_exists and not local_csv_exists: print("No local RAG system found. Attempting to download from HuggingFace...") download_success = download_pretrained_files() if download_success: print("✅ Downloaded pre-trained files from HuggingFace Hub") # Update existence flags after download local_rag_exists = os.path.exists(os.path.join(RAG_FILES_DIR, 'faiss_index.index')) local_csv_exists = os.path.exists(PROCESSED_DATA_FILE) else: print("⚠️ Failed to download pre-trained files. System will start empty.") # Try to load existing RAG system if local_rag_exists: try: print("Found existing RAG system files, loading...") query_engine = load_rag_system() if query_engine is not None: chunk_count = 0 if os.path.exists(PROCESSED_DATA_FILE): processed_chunks = load_processed_chunks(PROCESSED_DATA_FILE) chunk_count = len(processed_chunks) else: try: import pickle with open(os.path.join(RAG_FILES_DIR, 'documents.pkl'), 'rb') as f: documents = pickle.load(f) chunk_count = len(documents) except Exception as e: print(f"Could not count documents: {e}") chunk_count = "неизвестно" return f"✅ AIEXP система инициализирована с {chunk_count} фрагментами нормативных документов (загружена из индекса)" except Exception as e: print(f"Не удалось загрузить сохраненную систему: {str(e)}") # If no RAG system but CSV exists, build from CSV if local_csv_exists and query_engine is None: try: print("Building RAG system from CSV file...") processed_chunks_df = load_processed_chunks(PROCESSED_DATA_FILE) # Check for required columns required_columns = {'document_id', 'file_link', 'chunk_text', 'chunk_id'} missing_columns = required_columns - set(processed_chunks_df.columns) if missing_columns: return f"❌ Ошибка при инициализации из CSV: отсутствуют необходимые столбцы: {missing_columns}" # Fill missing optional columns if 'txt_file_id' not in processed_chunks_df.columns: processed_chunks_df['txt_file_id'] = processed_chunks_df['document_id'] if 'section' not in processed_chunks_df.columns: processed_chunks_df['section'] = '' if 'subsection' not in processed_chunks_df.columns: processed_chunks_df['subsection'] = '' if 'chunk_length' not in processed_chunks_df.columns: processed_chunks_df['chunk_length'] = processed_chunks_df['chunk_text'].str.len() processed_chunks = processed_chunks_df.to_dict('records') if processed_chunks: print(f"Building RAG system with {len(processed_chunks)} chunks...") query_engine = build_rag_system(processed_chunks) return f"✅ AIEXP система инициализирована с {len(processed_chunks)} фрагментами нормативных документов (построена из CSV)" except Exception as e: return f"❌ Ошибка при инициализации из CSV: {str(e)}" return "🔄 AIEXP система готова к работе. Загрузите нормативные документы для создания базы знаний." def get_uploaded_files_info(): if not os.path.exists(UPLOAD_FOLDER): return "Нет загруженных файлов в базе знаний" files = os.listdir(UPLOAD_FOLDER) if not files: return "Нет загруженных файлов в базе знаний" file_info = [] file_count = len(files) for file in files: file_path = os.path.join(UPLOAD_FOLDER, file) size = os.path.getsize(file_path) modified = datetime.fromtimestamp(os.path.getmtime(file_path)).strftime("%Y-%m-%d %H:%M") file_info.append(f"📄 {file} ({size} байт, добавлен: {modified})") return f"Всего нормативных документов в базе: {file_count}\n\n" + "\n".join(file_info) def upload_files(files): global query_engine if not files: return "Файлы не выбраны", get_uploaded_files_info() uploaded_count = 0 errors = [] for file in files: try: filename = os.path.basename(file.name) destination = os.path.join(UPLOAD_FOLDER, filename) shutil.copy2(file.name, destination) uploaded_count += 1 if query_engine is not None: try: query_engine = add_new_document_to_system(destination, query_engine) except Exception as e: errors.append(f"Ошибка добавления {filename} в систему: {str(e)}") except Exception as e: errors.append(f"Ошибка загрузки {file.name}: {str(e)}") result_message = f"Загружено нормативных документов: {uploaded_count}" if errors: result_message += f"\nОшибки:\n" + "\n".join(errors) else: result_message += f"\nДокументы автоматически добавлены в базу знаний" return result_message, get_uploaded_files_info() def process_all_documents(): global query_engine if not os.path.exists(UPLOAD_FOLDER): return "Папка с нормативными документами не найдена" files = os.listdir(UPLOAD_FOLDER) if not files: return "Нет нормативных документов для обработки" file_paths = [os.path.join(UPLOAD_FOLDER, f) for f in files] try: processed_chunks = process_multiple_documents(file_paths) if not processed_chunks: return "Не удалось создать фрагменты нормативных документов" save_processed_chunks(processed_chunks, PROCESSED_DATA_FILE) query_engine = build_rag_system(processed_chunks) with open(INDEX_STATE_FILE, 'w', encoding='utf-8') as f: json.dump({ 'processed_files': files, 'chunks_count': len(processed_chunks), 'last_update': datetime.now().isoformat() }, f, ensure_ascii=False, indent=2) return f"Обработка базы знаний завершена успешно!\nОбработано нормативных документов: {len(files)}\nСоздано фрагментов: {len(processed_chunks)}\nAIEXP система готова для работы с нормативной документацией." except Exception as e: return f"Ошибка при обработке нормативных документов: {str(e)}" def get_system_status(): status_info = [] files_count = len(os.listdir(UPLOAD_FOLDER)) if os.path.exists(UPLOAD_FOLDER) else 0 if os.path.exists(INDEX_STATE_FILE): with open(INDEX_STATE_FILE, 'r', encoding='utf-8') as f: state = json.load(f) status_info.append(f"🟢 AIEXP система активна") status_info.append(f"📊 Нормативных документов в базе: {files_count}") status_info.append(f"📝 Фрагментов в индексе: {state.get('chunks_count', 0)}") status_info.append(f"🕒 Последнее обновление: {state.get('last_update', 'Неизвестно')}") if state.get('processed_files'): status_info.append(f"📋 Обработанные документы:") for file in state['processed_files'][:10]: status_info.append(f" • {file}") if len(state['processed_files']) > 10: status_info.append(f" ... и еще {len(state['processed_files']) - 10} документов") else: status_info.append("🔴 AIEXP система не инициализирована") status_info.append(f"📊 Нормативных документов загружено: {files_count}") status_info.append("Обработайте документы для создания базы знаний") return "\n".join(status_info) def answer_question(question): global query_engine if not question.strip(): return "Пожалуйста, введите вопрос по нормативной документации", "" if query_engine is None: return "База знаний не готова. Сначала загрузите и обработайте нормативные документы.", "" try: response = query_documents(query_engine, question) formatted_response = format_response_with_sources(response) answer = formatted_response['answer'] sources_info = [] sources_info.append("📚 Источники из нормативной документации:") for i, source in enumerate(formatted_response['sources'][:5], 1): sources_info.append(f"\n{i}. Документ: {source['document_id']}") if source['section']: sources_info.append(f" Раздел: {source['section']}") if source['subsection']: sources_info.append(f" Подраздел: {source['subsection']}") sources_info.append(f" Фрагмент: ...{source['text_preview'][:150]}...") return answer, "\n".join(sources_info) except Exception as e: return f"Ошибка при обработке вопроса: {str(e)}", "" def clear_all_data(): global query_engine try: if os.path.exists(UPLOAD_FOLDER): shutil.rmtree(UPLOAD_FOLDER) os.makedirs(UPLOAD_FOLDER) if os.path.exists("processed_data"): shutil.rmtree("processed_data") os.makedirs("processed_data") if os.path.exists(RAG_FILES_DIR): shutil.rmtree(RAG_FILES_DIR) os.makedirs(RAG_FILES_DIR) query_engine = None return "Вся база знаний успешно очищена", get_uploaded_files_info(), get_system_status() except Exception as e: return f"Ошибка при очистке базы знаний: {str(e)}", get_uploaded_files_info(), get_system_status() # Fix 1: Update file_types parameter format def create_demo_interface(): with gr.Blocks(title="AIEXP - AI Expert для нормативной документации", theme=gr.themes.Soft()) as demo: gr.Markdown(""" # 🤖 AIEXP - Artificial Intelligence Expert ## Инструмент для работы с нормативной документацией **Возможности системы:** - 🔍 Поиск информации по запросу с указанием источников среди нормативной документации - 📋 Цитирование пунктов нормативной документации из базы знаний - 📝 Краткий пересказ содержания разделов или целых нормативных документов - 🔎 Семантический анализ соответствия информации требованиям НД - 📋 Формирование пошаговых планов действий на основании требований НД **Поддерживаемые форматы:** PDF, DOCX, TXT, CSV, XLSX, JSON """) with gr.Tab("🏠 Поиск по нормативным документам"): gr.Markdown("### Задайте вопрос по нормативной документации") with gr.Row(): with gr.Column(scale=3): question_input = gr.Textbox( label="Ваш вопрос к базе знаний", placeholder="Введите вопрос по нормативным документам...", lines=3 ) ask_btn = gr.Button("🔍 Найти ответ", variant="primary", size="lg") # Fix 2: Simplify Examples component gr.Examples( examples=[ ["Какой стандарт устанавливает порядок признания протоколов испытаний продукции в области использования атомной энергии?"], ["Кто несет ответственность за организацию и проведение признания протоколов испытаний продукции?"], ["В каких случаях могут быть признаны протоколы испытаний, проведенные лабораториями, не включенными в перечисления?"], ["Какие критерии используются органом по сертификации для анализа документации на втором этапе признания протоколов испытаний?"] ], inputs=question_input ) with gr.Column(scale=4): answer_output = gr.Textbox( label="Ответ на основе нормативных документов", lines=8, interactive=False ) sources_output = gr.Textbox( label="Источники из нормативной документации", lines=10, interactive=False ) with gr.Tab("📤 Управление базой знаний (Администратор)"): gr.Markdown("### Загрузка и обработка нормативных документов") with gr.Row(): with gr.Column(scale=2): # Fix 3: Update File component parameters file_upload = gr.File( label="Выберите нормативные документы для загрузки", file_count="multiple", file_types=[".pdf", ".docx", ".txt", ".csv", ".xlsx", ".json"], type="filepath" # Add explicit type ) with gr.Row(): upload_btn = gr.Button("📤 Загрузить в базу знаний", variant="primary") process_btn = gr.Button("⚙️ Переобработать всю базу", variant="secondary") clear_btn = gr.Button("🗑️ Очистить базу знаний", variant="stop") with gr.Column(scale=2): upload_status = gr.Textbox( label="Статус загрузки", lines=3, interactive=False ) processing_status = gr.Textbox( label="Статус обработки базы знаний", lines=5, interactive=False ) gr.Markdown("### База нормативных документов") files_info = gr.Textbox( label="Документы в базе знаний", lines=8, interactive=False, value=get_uploaded_files_info() ) with gr.Tab("📊 Статус AIEXP системы"): gr.Markdown("### Информация о состоянии базы знаний") system_status = gr.Textbox( label="Статус AIEXP системы", lines=10, interactive=False, value=get_system_status() ) refresh_status_btn = gr.Button("🔄 Обновить статус системы") # Fix 4: Event handlers with proper error handling def safe_upload_files(files): try: if files is None: return "Файлы не выбраны", get_uploaded_files_info() return upload_files(files) except Exception as e: return f"Ошибка при загрузке файлов: {str(e)}", get_uploaded_files_info() def safe_answer_question(question): try: if not question or not question.strip(): return "Пожалуйста, введите вопрос по нормативной документации", "" return answer_question(question) except Exception as e: return f"Ошибка при обработке вопроса: {str(e)}", "" upload_btn.click( fn=safe_upload_files, inputs=[file_upload], outputs=[upload_status, files_info] ) process_btn.click( fn=process_all_documents, outputs=[processing_status] ) ask_btn.click( fn=safe_answer_question, inputs=[question_input], outputs=[answer_output, sources_output] ) question_input.submit( fn=safe_answer_question, inputs=[question_input], outputs=[answer_output, sources_output] ) clear_btn.click( fn=clear_all_data, outputs=[processing_status, files_info, system_status] ) refresh_status_btn.click( fn=get_system_status, outputs=[system_status] ) return demo # Fix 5: Update launch parameters for compatibility if __name__ == "__main__": print("Инициализация AIEXP системы...") init_message = initialize_system() print(init_message) demo = create_demo_interface() demo.launch( share=True, server_name="0.0.0.0", server_port=7860, show_error=True, debug=False, # Add debug flag quiet=False # Add quiet flag for better error visibility )