fruitpicker01 commited on
Commit
463334d
·
verified ·
1 Parent(s): d9d149a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +193 -40
app.py CHANGED
@@ -1,9 +1,3 @@
1
- #!/usr/bin/env python3
2
- """
3
- Финальная векторная RAG система для HuggingFace Spaces
4
- Адаптированная версия с поддержкой векторного поиска и резервным режимом
5
- """
6
-
7
  import os
8
  import json
9
  import pickle
@@ -12,6 +6,14 @@ from pathlib import Path
12
  from typing import Optional, Dict, Any, List, Tuple
13
  import traceback
14
  import re
 
 
 
 
 
 
 
 
15
 
16
  try:
17
  import numpy as np
@@ -30,6 +32,30 @@ except ImportError:
30
 
31
  from openai import OpenAI
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  class VectorRAGSystem:
34
  """RAG система с векторным поиском и резервным режимом"""
35
 
@@ -167,24 +193,30 @@ class VectorRAGSystem:
167
  def _generate_stats(self) -> str:
168
  """Генерация статистики системы"""
169
  total_chunks = len(self.chunks)
170
- mode = "Векторный поиск" if self.vector_mode and self.faiss_index else "Поиск по ключевым словам"
 
 
171
 
172
- stats = f""" **RAG система готова!**
173
 
174
- 📊 **Статистика:**
175
- - 📦 Загружено чанков: {total_chunks}
176
- - 🔍 Режим поиска: {mode}
177
  - 🧠 Модель генерации: {self.generation_model}
178
- - 🎯 Реранкинг: {self.reranking_model}
 
 
179
 
180
- 🔍 **Возможности:**
181
- - 🔎 Семантический/ключевой поиск
182
- - 📄 Контекстное обогащение
183
- - 🧠 LLM реранкинг результатов
184
- - 📝 Интеллектуальная генерация ответов
185
- - 📊 Анализ годового отчета ПАО Сбербанк 2023
 
 
186
 
187
- 🚀 **Готова к работе!**"""
188
 
189
  return stats
190
 
@@ -286,48 +318,164 @@ class VectorRAGSystem:
286
  return chunks
287
 
288
  def generate_answer(self, query: str, context_chunks: List[Tuple[Dict, float]]) -> str:
289
- """Генерация ответа на основе контекста"""
290
  if not self.client:
291
  return "❌ OpenAI API не настроен"
292
 
293
  try:
 
294
  context_parts = []
 
 
295
  for i, (chunk, score) in enumerate(context_chunks[:self.final_chunks_count]):
296
- text = chunk.get('full_page_text', chunk['text'])
297
  clean_text = text.encode('utf-8', errors='ignore').decode('utf-8')
298
- context_parts.append(f"Фрагмент {i+1} (страница {chunk['page']}, релевантность: {score:.2f}):\n{clean_text}")
 
 
 
 
 
 
 
 
 
299
 
300
  context = "\n\n".join(context_parts)
301
  clean_query = query.encode('utf-8', errors='ignore').decode('utf-8')
302
 
303
- prompt = f"""Ты - эксперт по анализу финансовых отчетов. Ответь на вопрос пользователя на основе предоставленного контекста из годового отчета ПАО Сбербанк 2023.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
- ВОПРОС: {clean_query}
306
 
307
  КОНТЕКСТ ИЗ ОТЧЕТА:
308
  {context}
309
 
310
- ИНСТРУКЦИИ:
311
- 1. Отвечай только на основе предоставленной информации
312
- 2. Если информации недостаточно, честно об этом скажи
313
- 3. Используй конкретные данные и цифры из отчета
314
- 4. Структурируй ответ четко и понятно
315
- 5. Указывай номера страниц при цитировании
316
- 6. Отвечай на русском языке
 
 
 
 
 
 
 
 
317
 
318
- ОТВЕТ:"""
 
 
 
 
319
 
320
  response = self.client.chat.completions.create(
321
  model=self.generation_model,
322
  messages=[{"role": "user", "content": prompt}],
323
- max_tokens=1500,
324
- temperature=0.1
 
325
  )
326
 
327
- return response.choices[0].message.content.strip()
328
 
 
 
 
 
 
 
 
 
 
329
  except Exception as e:
330
- return f" Ошибка генерации ответа: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
 
332
  def process_query(self, query: str) -> Dict[str, Any]:
333
  """Обработка пользовательского запроса"""
@@ -446,9 +594,9 @@ def create_demo_interface():
446
 
447
  gr.Markdown("""
448
  <div class="main-header">
449
- <h1>🚀 Advanced RAG Demo: Анализ отчета Сбера 2023</h1>
450
- <p>Умная система с векторным поиском и адаптивным режимом</p>
451
- <p><strong>OpenAI embeddings • FAISS IndexFlatIPLLM rerankingFallback mode</strong></p>
452
  </div>
453
  """)
454
 
@@ -533,4 +681,9 @@ def create_demo_interface():
533
 
534
  # Запуск для Hugging Face Spaces
535
  demo = create_demo_interface()
536
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import json
3
  import pickle
 
6
  from typing import Optional, Dict, Any, List, Tuple
7
  import traceback
8
  import re
9
+ from datetime import datetime
10
+
11
+ try:
12
+ from pydantic import BaseModel, Field
13
+ HAS_PYDANTIC = True
14
+ except ImportError:
15
+ HAS_PYDANTIC = False
16
+ print("⚠️ Pydantic не установлен, структурированный вывод недоступен")
17
 
18
  try:
19
  import numpy as np
 
32
 
33
  from openai import OpenAI
34
 
35
+ # Pydantic модели для структурированного вывода
36
+ if HAS_PYDANTIC:
37
+ class SourceInfo(BaseModel):
38
+ """Информация об источнике"""
39
+ page: int = Field(description="Номер страницы в отчете")
40
+ relevance_score: float = Field(description="Оценка релевантности от 0 до 1")
41
+ content_preview: str = Field(description="Краткое описание содержимого")
42
+
43
+ class ThinkingProcess(BaseModel):
44
+ """Процесс рассуждений (Chain-of-Thought)"""
45
+ question_analysis: str = Field(description="Анализ вопроса пользователя")
46
+ information_found: str = Field(description="Найденная в источниках информация")
47
+ reasoning_steps: List[str] = Field(description="Шаги логических рассуждений")
48
+ conclusion: str = Field(description="Выводы на основе анализа")
49
+
50
+ class FinancialAnswer(BaseModel):
51
+ """Структурированный ответ по финансовой отчетности"""
52
+ thinking: ThinkingProcess = Field(description="Процесс рассуждений")
53
+ answer: str = Field(description="Основной ответ на вопрос")
54
+ confidence: float = Field(description="Уверенность в ответе от 0 до 1")
55
+ sources: List[SourceInfo] = Field(description="Использованные источники")
56
+ key_metrics: Optional[Dict[str, Any]] = Field(description="Ключевые числовые показатели", default=None)
57
+ timestamp: str = Field(default_factory=lambda: datetime.now().isoformat())
58
+
59
  class VectorRAGSystem:
60
  """RAG система с векторным поиском и резервным режимом"""
61
 
 
193
  def _generate_stats(self) -> str:
194
  """Генерация статистики системы"""
195
  total_chunks = len(self.chunks)
196
+ mode = "Векторный поиск" if self.vector_mode and self.faiss_index else "Базовый режим"
197
+ structured_output = "✅ Pydantic" if HAS_PYDANTIC else "❌ Недоступно"
198
+ pdf_enrichment = "✅ Активен" if self.pdf_doc else "❌ Недоступен"
199
 
200
+ stats = f"""🧠 **Advanced RAG система с Chain-of-Thought готова!**
201
 
202
+ 📊 **Технические характеристики:**
203
+ - 📦 Векторных эмбеддингов: {total_chunks}
204
+ - 🔍 Режим поиска: {mode} (только векторный)
205
  - 🧠 Модель генерации: {self.generation_model}
206
+ - 🎯 LLM реранкинг: {self.reranking_model}
207
+ - 📄 Parent-page enrichment: {pdf_enrichment}
208
+ - 📋 Структурированный вывод: {structured_output}
209
 
210
+ 🚀 **Архитектурные особенности:**
211
+ - 🔎 **Векторный поиск** с text-embedding-3-large (только)
212
+ - 📄 **Parent-page enrichment** через PyMuPDF
213
+ - 🧠 **LLM реранкинг** для повышения релевантности
214
+ - 🤔 **Chain-of-Thought** рассуждения
215
+ - 📋 **JSON Schema** для структурированных ответов
216
+ - 📊 **Confidence scoring** и детальная аналитика
217
+ - 📚 **Предобработка** с pdfplumber + таблицы
218
 
219
+ 💡 **Готова к интеллектуальному анализу отчета ПАО Сбербанк 2023!**"""
220
 
221
  return stats
222
 
 
318
  return chunks
319
 
320
  def generate_answer(self, query: str, context_chunks: List[Tuple[Dict, float]]) -> str:
321
+ """Генерация ответа с Chain-of-Thought и структурированным выводом"""
322
  if not self.client:
323
  return "❌ OpenAI API не настроен"
324
 
325
  try:
326
+ # Подготавливаем контекст с метаинформацией
327
  context_parts = []
328
+ sources_info = []
329
+
330
  for i, (chunk, score) in enumerate(context_chunks[:self.final_chunks_count]):
331
+ text = chunk.get('text', '')
332
  clean_text = text.encode('utf-8', errors='ignore').decode('utf-8')
333
+ # Ограничиваем длину для лучшей обработки
334
+ if len(clean_text) > 1500:
335
+ clean_text = clean_text[:1500] + "..."
336
+
337
+ context_parts.append(f"Источник {i+1} (страница {chunk['page']}):\n{clean_text}")
338
+ sources_info.append({
339
+ "page": chunk['page'],
340
+ "score": float(score),
341
+ "preview": clean_text[:200] + "..." if len(clean_text) > 200 else clean_text
342
+ })
343
 
344
  context = "\n\n".join(context_parts)
345
  clean_query = query.encode('utf-8', errors='ignore').decode('utf-8')
346
 
347
+ # Используем структурированный вывод, если доступен Pydantic
348
+ if HAS_PYDANTIC:
349
+ return self._generate_structured_answer(clean_query, context, sources_info)
350
+ else:
351
+ return self._generate_simple_answer(clean_query, context)
352
+
353
+ except Exception as e:
354
+ return f"❌ Ошибка генерации ответа: {str(e)}"
355
+
356
+ def _generate_structured_answer(self, query: str, context: str, sources_info: List[Dict]) -> str:
357
+ """Генерация структурированного ответа с Chain-of-Thought"""
358
+ try:
359
+ # JSON Schema для принуждения к структуре
360
+ schema = FinancialAnswer.model_json_schema()
361
+
362
+ prompt = f"""Ты - эксперт по анализу финансовых отчетов ПАО Сбербанк. Проанализируй вопрос пользователя используя Chain-of-Thought рассуждения.
363
 
364
+ ВОПРОС: {query}
365
 
366
  КОНТЕКСТ ИЗ ОТЧЕТА:
367
  {context}
368
 
369
+ ИНСТРУКЦИИ ДЛЯ АНАЛИЗА:
370
+ 1. МЫШЛЕНИЕ (thinking):
371
+ - Проанализируй, что именно спрашивает пользователь
372
+ - Определи, какая информация есть в предоставленных источниках
373
+ - Пройди через логические шаги рассуждений
374
+ - Сделай выводы на основе найденной информации
375
+
376
+ 2. ОТВЕТ:
377
+ - Дай четкий и полный ответ на русском языке
378
+ - Используй конкретные данные и цифры из отчета
379
+ - Укажи номера страниц при цитировании
380
+
381
+ 3. УВЕРЕННОСТЬ:
382
+ - Оцени от 0 до 1, насколько уверен в ответе
383
+ - Учитывай полноту и качество найденной информации
384
 
385
+ 4. ИСТОЧНИКИ:
386
+ - Для каждого использованного исто��ника укажи релевантность (0-1)
387
+ - Кратко опиши содержимое
388
+
389
+ Отвечай ТОЛЬКО в формате JSON согласно схеме. Все тексты на русском языке."""
390
 
391
  response = self.client.chat.completions.create(
392
  model=self.generation_model,
393
  messages=[{"role": "user", "content": prompt}],
394
+ max_tokens=2000,
395
+ temperature=0.1,
396
+ response_format={"type": "json_object"}
397
  )
398
 
399
+ json_response = response.choices[0].message.content.strip()
400
 
401
+ # Парсим и валидируем JSON
402
+ try:
403
+ parsed_response = json.loads(json_response)
404
+ validated_response = FinancialAnswer(**parsed_response)
405
+ return self._format_structured_response(validated_response)
406
+ except Exception as parse_error:
407
+ print(f"⚠️ Ошибка парсинга JSON: {parse_error}")
408
+ return self._generate_simple_answer(query, context)
409
+
410
  except Exception as e:
411
+ print(f"⚠️ Ошибка структурированной генерации: {e}")
412
+ return self._generate_simple_answer(query, context)
413
+
414
+ def _generate_simple_answer(self, query: str, context: str) -> str:
415
+ """Генерация простого ответа с Chain-of-Thought (fallback)"""
416
+ prompt = f"""Ты - эксперт по анализу финансовых отчетов. Ответь на вопрос используя Chain-of-Thought рассуждения.
417
+
418
+ ВОПРОС: {query}
419
+
420
+ КОНТЕКСТ ИЗ ОТЧЕТА:
421
+ {context}
422
+
423
+ ФОРМАТ ОТВЕТА:
424
+ 🤔 **АНАЛИЗ ВОПРОСА:**
425
+ [Что именно спрашивает пользователь]
426
+
427
+ 📊 **НАЙДЕННАЯ ИНФОРМАЦИЯ:**
428
+ [Какие данные есть в источниках]
429
+
430
+ 🔍 **РАССУЖДЕНИЯ:**
431
+ [Логические шаги анализа]
432
+
433
+ ✅ **ВЫВОДЫ:**
434
+ [Финальный ответ с конкретными данными]
435
+
436
+ ИНСТРУКЦИИ:
437
+ - Отвечай только на основе предоставленной информации
438
+ - Используй конкретные данные и цифры из отчета
439
+ - Указывай номера страниц при цитировании
440
+ - Отвечай на русском языке"""
441
+
442
+ response = self.client.chat.completions.create(
443
+ model=self.generation_model,
444
+ messages=[{"role": "user", "content": prompt}],
445
+ max_tokens=1500,
446
+ temperature=0.1
447
+ )
448
+
449
+ return response.choices[0].message.content.strip()
450
+
451
+ def _format_structured_response(self, response: 'FinancialAnswer') -> str:
452
+ """Форматирование структурированного ответа для отображения"""
453
+ formatted = f"""🤔 **ПРОЦЕСС РАССУЖДЕНИЙ:**
454
+
455
+ 📝 **Анализ вопроса:** {response.thinking.question_analysis}
456
+
457
+ 📊 **Найденная информация:** {response.thinking.information_found}
458
+
459
+ 🔍 **Шаги рассуждений:**
460
+ """
461
+ for i, step in enumerate(response.thinking.reasoning_steps, 1):
462
+ formatted += f"{i}. {step}\n"
463
+
464
+ formatted += f"\n💡 **Выводы:** {response.thinking.conclusion}\n"
465
+ formatted += f"\n✅ **ФИНАЛЬНЫЙ ОТВЕТ:**\n{response.answer}\n"
466
+ formatted += f"\n📊 **Уверенность:** {response.confidence:.1%}\n"
467
+
468
+ if response.key_metrics:
469
+ formatted += f"\n📈 **Ключевые показатели:**\n"
470
+ for key, value in response.key_metrics.items():
471
+ formatted += f"- {key}: {value}\n"
472
+
473
+ formatted += f"\n📚 **Источники:**\n"
474
+ for i, source in enumerate(response.sources, 1):
475
+ formatted += f"{i}. Страница {source.page} (релевантность: {source.relevance_score:.1%})\n"
476
+ formatted += f" {source.content_preview}\n"
477
+
478
+ return formatted
479
 
480
  def process_query(self, query: str) -> Dict[str, Any]:
481
  """Обработка пользовательского запроса"""
 
594
 
595
  gr.Markdown("""
596
  <div class="main-header">
597
+ <h1>🧠 Advanced RAG with Chain-of-Thought: Анализ отчета Сбера 2023</h1>
598
+ <p>Интеллектуальная система с векторным поиском, LLM реранкингом и структурированными рассуждениями</p>
599
+ <p><strong>text-embedding-3-large • FAISS GPT-4o JSON SchemaChain-of-Thought • Parent-page enrichment</strong></p>
600
  </div>
601
  """)
602
 
 
681
 
682
  # Запуск для Hugging Face Spaces
683
  demo = create_demo_interface()
684
+
685
+ if __name__ == "__main__":
686
+ if demo:
687
+ demo.launch()
688
+ else:
689
+ print("❌ Не удалось создать интерфейс")