AIEXP_RAG_1 / app.py
MrSimple07's picture
fixed app.py
280f93c
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
)