File size: 12,799 Bytes
c9db278
 
 
 
 
654b449
 
aa622c0
8a4fdd4
 
aa622c0
c9db278
1eaf3d8
c9db278
1b98e0e
 
 
 
 
 
 
c9db278
a50849a
aa622c0
 
a50849a
1b98e0e
 
 
 
 
c9db278
 
aa622c0
8a4fdd4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aa622c0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a50849a
1eaf3d8
 
 
 
a50849a
1eaf3d8
 
 
 
 
a50849a
 
 
 
1eaf3d8
a50849a
 
1eaf3d8
 
 
a50849a
1eaf3d8
 
 
c9db278
 
 
1eaf3d8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9db278
 
654b449
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
648d16e
654b449
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
import os
import google.generativeai as genai
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.google_genai import GoogleGenAI
from llama_index.core import Settings
from llama_index.core.llms import ChatMessage, MessageRole
import os
from huggingface_hub import hf_hub_download
from huggingface_hub import HfApi, login, create_repo



# Configuration
EMBEDDING_MODEL = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
RETRIEVER_TOP_K = 10
RETRIEVER_SIMILARITY_CUTOFF = 0.7
RAG_FILES_DIR = "processed_data"
PROCESSED_DATA_FILE = "processed_data/processed_chunks.csv"

UPLOAD_FOLDER = "UPLOADED_DOCUMENTS"
INDEX_STATE_FILE = "processed_data/index_store.json"

GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY', "AIzaSyDemsCp7JIdRNDRyP6DkYdMox1DLZwPcPE")
HF_REPO_ID = "MrSimple01/AIEXP_RAG_FILES"
HF_TOKEN = os.getenv('HF_TOKEN')
LLM_MODEL = "gemini-2.0-flash"

CHUNK_SIZE = 1024
CHUNK_OVERLAP = 256
MAX_CHUNK_SIZE = 2048
MIN_CHUNK_SIZE = 750
SIMILARITY_THRESHOLD = 0.7


def login_to_huggingface():
    """Login to HuggingFace Hub"""
    try:
        if HF_TOKEN:
            login(token=HF_TOKEN)
            print("✅ Successfully logged in to HuggingFace Hub with token")
            return True
        else:
            # Interactive login if no token provided
            login()
            print("✅ Successfully logged in to HuggingFace Hub")
            return True
    except Exception as e:
        print(f"❌ Failed to login to HuggingFace Hub: {e}")
        return False


def download_pretrained_files():
    """Download pre-trained RAG files from HuggingFace Hub"""
    try:
        print("Downloading pre-trained RAG files from HuggingFace Hub...")
        
        # Files to download
        files_to_download = [
            "faiss_index.index",
            "processed_chunks.csv",
            "chunk_metadata.pkl",
            "config.pkl",
            "documents.pkl",
            "default__vector_store.json",
            "docstore.json",
            "index_store.json"
        ]
        
        # Ensure RAG_FILES_DIR exists
        os.makedirs(RAG_FILES_DIR, exist_ok=True)
        os.makedirs("processed_data", exist_ok=True)
        
        downloaded_files = {}
        
        for filename in files_to_download:
            try:
                print(f"Downloading {filename}...")
                
                # Download to RAG_FILES_DIR for most files, processed_data for CSV
                target_dir = "processed_data" if filename == "processed_chunks.csv" else RAG_FILES_DIR
                
                file_path = hf_hub_download(
                    repo_id=HF_REPO_ID,
                    filename=filename,
                    local_dir=target_dir,
                    repo_type="dataset",
                    token=HF_TOKEN
                )
                
                downloaded_files[filename] = file_path
                print(f"✓ Downloaded {filename}")
                
            except Exception as e:
                print(f"✗ Failed to download {filename}: {e}")
                continue
        
        # Verify critical files
        critical_files = ["faiss_index.index", "processed_chunks.csv"]
        missing_critical = [f for f in critical_files if f not in downloaded_files]
        
        if missing_critical:
            print(f"❌ Missing critical files: {missing_critical}")
            return False
        
        print(f"✅ Successfully downloaded {len(downloaded_files)}/{len(files_to_download)} files")
        return True
        
    except Exception as e:
        print(f"❌ Failed to download pre-trained files: {e}")
        return False

def setup_llm_settings():
    """Setup embedding and LLM models"""
    # Configure Google API
    if GOOGLE_API_KEY:
        genai.configure(api_key=GOOGLE_API_KEY)
    
    # Set embedding model
    embed_model = HuggingFaceEmbedding(model_name=EMBEDDING_MODEL)
    Settings.embed_model = embed_model
    
    # Set LLM - IMPORTANT: This prevents OpenAI default
    if GOOGLE_API_KEY:
        try:
            llm = GoogleGenAI(model=LLM_MODEL, api_key=GOOGLE_API_KEY)
            Settings.llm = llm
            print("Google GenAI LLM initialized successfully")
        except Exception as e:
            print(f"Warning: Could not initialize Google GenAI LLM: {e}")
            # Set a dummy LLM to prevent OpenAI default
            from llama_index.core.llms.mock import MockLLM
            Settings.llm = MockLLM()
    else:
        print("Warning: GOOGLE_API_KEY not found. Using MockLLM.")
        from llama_index.core.llms.mock import MockLLM
        Settings.llm = MockLLM()



CUSTOM_PROMPT = """
Вы являетесь высокоспециализированным Ассистентом для анализа документов (AIEXP). Ваша цель - предоставлять точные, корректные и контекстно релевантные ответы на основе анализа нормативной документации (НД). Все ваши ответы должны основываться исключительно на предоставленном контексте без использования внешних знаний или предположений.

ОПРЕДЕЛЕНИЕ ТИПА ЗАДАЧИ:
Проанализируйте запрос пользователя и определите тип задачи:

1. КРАТКОЕ САММАРИ (ключевые слова: "кратко", "суммировать", "резюме", "основные моменты", "в двух словах"):
   - Предоставьте структурированное резюме запрашиваемого раздела/пункта
   - Выделите ключевые требования, процедуры или положения
   - Используйте нумерованный список для лучшей читаемости
   - Сохраняйте терминологию НД

2. ПОИСК ДОКУМЕНТА И ПУНКТА (ключевые слова: "найти", "где", "какой документ", "в каком разделе", "ссылка"):
   - Укажите конкретный документ и его структурное расположение
   - Предоставьте точные номера разделов/подразделов/пунктов
   - Процитируйте релевантные фрагменты
   - Если найдено несколько документов, перечислите все с указанием специфики каждого

3. ПРОВЕРКА КОРРЕКТНОСТИ (ключевые слова: "правильно ли", "соответствует ли", "проверить", "корректно", "нарушение"):
   - Сопоставьте предоставленную информацию с требованиями НД
   - Четко укажите: "СООТВЕТСТВУЕТ" или "НЕ СООТВЕТСТВУЕТ"
   - Перечислите конкретные требования НД
   - Укажите выявленные расхождения или подтвердите соответствие
   - Процитируйте релевантные пункты НД

4. ПЛАН ДЕЙСТВИЙ (ключевые слова: "план", "алгоритм", "последовательность", "как действовать", "пошагово"):
   - Создайте пронумерованный пошаговый план
   - Каждый шаг должен содержать ссылку на соответствующий пункт НД
   - Укажите необходимые документы или формы
   - Добавьте временные рамки, если они указаны в НД
   - Выделите критические требования или ограничения

ПРАВИЛА ФОРМИРОВАНИЯ ОТВЕТОВ:

1. ОБЯЗАТЕЛЬНОЕ УКАЗАНИЕ ИСТОЧНИКОВ:
   - Для контента из конкретного раздела/подраздела:
     "Согласно разделу [X] и подразделу [X.X]: [Ваш ответ]"
   - Для контента вне подразделов (таблицы, рисунки, общие разделы):
     "Согласно [Название документа] - [Номер и наименование пункта/таблицы/рисунка]: [Ваш ответ]"
   - При наличии метаданных о разделе и подразделе - включайте оба
   - При наличии только раздела: "Согласно разделу [X]: [Ваш ответ]"

2. СТРОГОЕ СЛЕДОВАНИЕ КОНТЕКСТУ:
   - Если информация не найдена: "Информация по вашему запросу не была найдена в нормативной документации."
   - Не делайте предположений или выводов за пределами предоставленного контекста
   - Не используйте общие знания

3. ИСПОЛЬЗОВАНИЕ ТЕРМИНОЛОГИИ НД:
   - Применяйте официальную терминологию из документов
   - Сохраняйте оригинальные формулировки ключевых требований
   - При необходимости разъясняйте специальные термины на основе НД

4. СТРУКТУРИРОВАНИЕ ОТВЕТОВ:
   - Для саммари: используйте маркированные или нумерованные списки
   - Для проверки: четкая структура "Требование → Соответствие/Несоответствие"
   - Для планов: пронумерованные шаги с подзадачами при необходимости
   - Для поиска: указание иерархии документа

5. ДОПОЛНИТЕЛЬНЫЕ РЕКОМЕНДАЦИИ:
   - При множественных релевантных источниках - укажите все
   - Выделяйте критически важные требования
   - Указывайте альтернативные процедуры, если они предусмотрены НД

Контекст: {context_str}

Вопрос: {query_str}

Ответ:
"""


LLM_MODEL_PREPROCESS = "gemini-1.5-flash"

def preprocess_query_with_context(user_query, chat_history=None, llm=None):
    if not chat_history:
        return user_query
    
    if not llm:
        llm = GoogleGenAI(model=LLM_MODEL_PREPROCESS, temperature=0.1)

    # Format chat history into a string for the prompt
    history_context = "\n".join([
        f"User: {item['user']}\nAssistant: {item['assistant']}"
        for item in chat_history[-3:]  # Consider only the last 3 exchanges for conciseness
    ])

    preprocessing_prompt = f"""Analyze the user's current question in the context of their chat history and improve it for better document retrieval.

Chat History:
{history_context}

Current Question: {user_query}

Tasks:
1. If the question refers to previous context, make it self-contained.
2. Add relevant keywords that would help find documents.
3. Maintain the legal/regulatory focus.
4. Keep it concise but specific.

Return ONLY the improved question:
"""

    try:
        messages = [ChatMessage(role=MessageRole.USER, content=preprocessing_prompt)]
        response = llm.chat(messages)
        improved_query = response.message.content.strip()

        # Fallback to the original query if the preprocessing fails or provides an overly long response
        if len(improved_query) > len(user_query) * 3 or not improved_query:
            return user_query
        
        return improved_query
    except Exception as e:
        print(f"Query preprocessing failed: {e}")
        return user_query


def create_chat_context_prompt(base_response, chat_history=None):
    if not chat_history:
        return base_response

    base_aware_response = base_response

    if len(chat_history) > 0:
        last_exchange = chat_history[-1]
        if any(keyword in last_exchange['user'].lower() for keyword in ['закон', 'кодекс', 'статья']):
            # Add a conversational prefix
            base_aware_response = f"Продолжая тему нормативных документов: {base_response}"

    return base_aware_response