fruitpicker01 commited on
Commit
26070ce
·
verified ·
1 Parent(s): ca1a781

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +518 -0
  2. requirements.txt +35 -0
app.py ADDED
@@ -0,0 +1,518 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ HuggingFace Spaces приложение для RAG системы анализа отчета Сбера 2023
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import tempfile
9
+ import base64
10
+ from io import BytesIO
11
+ from pathlib import Path
12
+ from typing import Optional, Dict, Any, List, Tuple
13
+ import gradio as gr
14
+ import openai
15
+ import pandas as pd
16
+ import numpy as np
17
+ from PIL import Image
18
+
19
+ # Конфигурация
20
+ class Config:
21
+ """Конфигурация для HuggingFace Spaces"""
22
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
23
+ GENERATION_MODEL = "gpt-4o"
24
+ RERANKING_MODEL = "gpt-4o-mini"
25
+ EMBEDDING_MODEL = "text-embedding-3-large"
26
+ MAX_CHARACTERS = 4000
27
+ CHUNK_OVERLAP = 200
28
+ RETRIEVAL_K = 5
29
+ RERANKING_K = 3
30
+
31
+ config = Config()
32
+
33
+ class SimpleRAGSystem:
34
+ """Упрощенная RAG система для HuggingFace Spaces"""
35
+
36
+ def __init__(self):
37
+ self.client = None
38
+ self.documents = []
39
+ self.embeddings = []
40
+ self.is_initialized = False
41
+
42
+ def initialize_openai(self, api_key: str) -> bool:
43
+ """Инициализация OpenAI клиента"""
44
+ try:
45
+ if not api_key:
46
+ return False
47
+ self.client = openai.OpenAI(api_key=api_key)
48
+ # Тестовый запрос
49
+ test_response = self.client.chat.completions.create(
50
+ model="gpt-4o-mini",
51
+ messages=[{"role": "user", "content": "Test"}],
52
+ max_tokens=1
53
+ )
54
+ return True
55
+ except Exception as e:
56
+ print(f"Ошибка инициализации OpenAI: {e}")
57
+ return False
58
+
59
+ def extract_text_from_pdf(self, pdf_file) -> List[str]:
60
+ """Извлечение текста из PDF (упрощенная версия)"""
61
+ try:
62
+ import pypdf
63
+
64
+ reader = pypdf.PdfReader(pdf_file)
65
+ texts = []
66
+
67
+ for page_num, page in enumerate(reader.pages):
68
+ text = page.extract_text()
69
+ if text.strip():
70
+ # Простое разбиение на чанки
71
+ chunks = self.split_text(text, config.MAX_CHARACTERS)
72
+ for i, chunk in enumerate(chunks):
73
+ texts.append({
74
+ 'content': chunk,
75
+ 'page': page_num + 1,
76
+ 'chunk': i + 1,
77
+ 'type': 'text'
78
+ })
79
+
80
+ return texts
81
+
82
+ except Exception as e:
83
+ print(f"Ошибка обработки PDF: {e}")
84
+ return []
85
+
86
+ def split_text(self, text: str, max_size: int) -> List[str]:
87
+ """Простое разбиение текста на чанки"""
88
+ words = text.split()
89
+ chunks = []
90
+ current_chunk = []
91
+ current_size = 0
92
+
93
+ for word in words:
94
+ if current_size + len(word) + 1 > max_size and current_chunk:
95
+ chunks.append(' '.join(current_chunk))
96
+ current_chunk = [word]
97
+ current_size = len(word)
98
+ else:
99
+ current_chunk.append(word)
100
+ current_size += len(word) + 1
101
+
102
+ if current_chunk:
103
+ chunks.append(' '.join(current_chunk))
104
+
105
+ return chunks
106
+
107
+ def create_embeddings(self, texts: List[Dict]) -> bool:
108
+ """Создание эмбеддингов для текстов"""
109
+ try:
110
+ if not self.client:
111
+ return False
112
+
113
+ contents = [doc['content'] for doc in texts]
114
+
115
+ # Создаем эмбеддинги батчами
116
+ batch_size = 100
117
+ all_embeddings = []
118
+
119
+ for i in range(0, len(contents), batch_size):
120
+ batch = contents[i:i + batch_size]
121
+ response = self.client.embeddings.create(
122
+ model=config.EMBEDDING_MODEL,
123
+ input=batch
124
+ )
125
+ batch_embeddings = [item.embedding for item in response.data]
126
+ all_embeddings.extend(batch_embeddings)
127
+
128
+ self.documents = texts
129
+ self.embeddings = np.array(all_embeddings)
130
+ self.is_initialized = True
131
+
132
+ return True
133
+
134
+ except Exception as e:
135
+ print(f"Ошибка создания эмбеддингов: {e}")
136
+ return False
137
+
138
+ def search_documents(self, query: str, k: int = 5) -> List[Dict]:
139
+ """Поиск релевантных документов"""
140
+ try:
141
+ if not self.is_initialized or not self.client:
142
+ return []
143
+
144
+ # Создаем эмбеддинг для запроса
145
+ query_response = self.client.embeddings.create(
146
+ model=config.EMBEDDING_MODEL,
147
+ input=[query]
148
+ )
149
+ query_embedding = np.array(query_response.data[0].embedding)
150
+
151
+ # Вычисляем косинусное сходство
152
+ similarities = np.dot(self.embeddings, query_embedding) / (
153
+ np.linalg.norm(self.embeddings, axis=1) * np.linalg.norm(query_embedding)
154
+ )
155
+
156
+ # Получаем топ-k результатов
157
+ top_indices = np.argsort(similarities)[-k:][::-1]
158
+
159
+ results = []
160
+ for idx in top_indices:
161
+ doc = self.documents[idx].copy()
162
+ doc['similarity'] = float(similarities[idx])
163
+ results.append(doc)
164
+
165
+ return results
166
+
167
+ except Exception as e:
168
+ print(f"Ошибка поиска: {e}")
169
+ return []
170
+
171
+ def rerank_documents(self, query: str, documents: List[Dict]) -> List[Dict]:
172
+ """Реранкинг документов с помощью LLM"""
173
+ try:
174
+ if not documents or not self.client:
175
+ return documents
176
+
177
+ # Формируем промпт для реранкинга
178
+ docs_text = ""
179
+ for i, doc in enumerate(documents):
180
+ docs_text += f"\nДокумент {i+1}:\n{doc['content'][:500]}...\n"
181
+
182
+ prompt = f"""
183
+ Вопрос пользователя: {query}
184
+
185
+ Документы для анализа:{docs_text}
186
+
187
+ Оцени релевантность каждого документа для ответа на вопрос по шкале 1-10.
188
+ Верни только список чисел через запятую (например: 8,6,9,4,7).
189
+ """
190
+
191
+ response = self.client.chat.completions.create(
192
+ model=config.RERANKING_MODEL,
193
+ messages=[{"role": "user", "content": prompt}],
194
+ max_tokens=50,
195
+ temperature=0
196
+ )
197
+
198
+ # Парсим оценки
199
+ scores_text = response.choices[0].message.content.strip()
200
+ scores = [float(s.strip()) for s in scores_text.split(',')]
201
+
202
+ # Добавляем оценки и сортируем
203
+ for i, doc in enumerate(documents):
204
+ if i < len(scores):
205
+ doc['rerank_score'] = scores[i]
206
+ else:
207
+ doc['rerank_score'] = 0
208
+
209
+ return sorted(documents, key=lambda x: x['rerank_score'], reverse=True)
210
+
211
+ except Exception as e:
212
+ print(f"Ошибка реранкинга: {e}")
213
+ return documents
214
+
215
+ def generate_answer(self, query: str, context_docs: List[Dict]) -> str:
216
+ """Генерация ответа на основе контекста"""
217
+ try:
218
+ if not self.client:
219
+ return "Ошибка: OpenAI API не инициализирован"
220
+
221
+ # Формируем контекст
222
+ context = ""
223
+ for doc in context_docs[:config.RERANKING_K]:
224
+ context += f"\nСтраница {doc['page']}: {doc['content']}\n"
225
+
226
+ # Промпт для генерации ответа
227
+ prompt = f"""
228
+ Ты - эксперт по анализу финансовых отчетов. Ответь на вопрос пользователя на основе предоставленной информации из годового отчета ПАО Сбербанк 2023.
229
+
230
+ ВОПРОС: {query}
231
+
232
+ КОНТЕКСТ ИЗ ОТЧЕТА:
233
+ {context}
234
+
235
+ ИНСТРУКЦИИ:
236
+ 1. Отвечай только на основе предоставленной информации
237
+ 2. Если информации недостаточно, честно скажи об этом
238
+ 3. Используй конкретные данные и цифры из отчета
239
+ 4. Отвечай на русском языке
240
+ 5. Структурируй ответ четко и понятно
241
+
242
+ ОТВЕТ:"""
243
+
244
+ response = self.client.chat.completions.create(
245
+ model=config.GENERATION_MODEL,
246
+ messages=[{"role": "user", "content": prompt}],
247
+ max_tokens=1000,
248
+ temperature=0.3
249
+ )
250
+
251
+ return response.choices[0].message.content.strip()
252
+
253
+ except Exception as e:
254
+ return f"Ошибка генерации ответа: {e}"
255
+
256
+ def process_query(self, query: str) -> Dict[str, Any]:
257
+ """Полная обработка запроса"""
258
+ if not self.is_initialized:
259
+ return {
260
+ "answer": "Система не инициализирована. Загрузите PDF файл и введите API ключ.",
261
+ "sources": []
262
+ }
263
+
264
+ if not query.strip():
265
+ return {
266
+ "answer": "Пожалуйста, введите ваш вопрос.",
267
+ "sources": []
268
+ }
269
+
270
+ # Поиск документов
271
+ search_results = self.search_documents(query, config.RETRIEVAL_K)
272
+
273
+ if not search_results:
274
+ return {
275
+ "answer": "К сожалению, не удалось найти релевантную информацию по вашему запросу.",
276
+ "sources": []
277
+ }
278
+
279
+ # Реранкинг
280
+ reranked_docs = self.rerank_documents(query, search_results)
281
+
282
+ # Генерация ответа
283
+ answer = self.generate_answer(query, reranked_docs)
284
+
285
+ # Формируем информацию об источниках
286
+ sources = []
287
+ for doc in reranked_docs[:config.RERANKING_K]:
288
+ sources.append({
289
+ "page": doc['page'],
290
+ "similarity": doc.get('similarity', 0),
291
+ "rerank_score": doc.get('rerank_score', 0),
292
+ "preview": doc['content'][:200] + "..."
293
+ })
294
+
295
+ return {
296
+ "answer": answer,
297
+ "sources": sources
298
+ }
299
+
300
+ # Глобальная переменная для RAG системы
301
+ rag_system = SimpleRAGSystem()
302
+
303
+ def initialize_system(api_key: str, pdf_file) -> Tuple[str, str]:
304
+ """Инициализация системы с API ключом и PDF файлом"""
305
+ if not api_key:
306
+ return "❌ Введите OpenAI API ключ", ""
307
+
308
+ if pdf_file is None:
309
+ return "❌ Загрузите PDF файл", ""
310
+
311
+ try:
312
+ # Инициализация OpenAI
313
+ if not rag_system.initialize_openai(api_key):
314
+ return "❌ Неверный API ключ OpenAI", ""
315
+
316
+ # Обработка PDF
317
+ texts = rag_system.extract_text_from_pdf(pdf_file)
318
+
319
+ if not texts:
320
+ return "❌ Не удалось извлечь текст из PDF", ""
321
+
322
+ # Создание эмбеддингов
323
+ if not rag_system.create_embeddings(texts):
324
+ return "❌ Ошибка создания эмбеддингов", ""
325
+
326
+ stats = f"""✅ Система инициализирована!
327
+
328
+ 📊 Статистика:
329
+ - Обработано страниц: {len(set(doc['page'] for doc in texts))}
330
+ - Создано фрагментов: {len(texts)}
331
+ - Средний размер фрагмента: {np.mean([len(doc['content']) for doc in texts]):.0f} символов
332
+
333
+ 🚀 Готова к ответам на вопросы!"""
334
+
335
+ return "✅ Инициализация завершена", stats
336
+
337
+ except Exception as e:
338
+ return f"❌ Ошибка: {e}", ""
339
+
340
+ def ask_question(question: str) -> Tuple[str, str]:
341
+ """Обработка вопроса пользователя"""
342
+ try:
343
+ result = rag_system.process_query(question)
344
+
345
+ answer = result["answer"]
346
+
347
+ # Формируем информацию об источниках
348
+ sources_info = ""
349
+ if result["sources"]:
350
+ sources_info = "\n📚 Источники:\n"
351
+ for i, source in enumerate(result["sources"], 1):
352
+ sources_info += f"\n{i}. Страница {source['page']} (релевантность: {source['similarity']:.2f})\n"
353
+ sources_info += f" {source['preview']}\n"
354
+
355
+ return answer, sources_info
356
+
357
+ except Exception as e:
358
+ return f"Ошибка обработки запроса: {e}", ""
359
+
360
+ def create_interface():
361
+ """Создание Gradio интерфейса"""
362
+
363
+ with gr.Blocks(
364
+ title="RAG Система Сбер 2023",
365
+ theme=gr.themes.Soft(),
366
+ css="""
367
+ .main-header { text-align: center; margin-bottom: 2rem; }
368
+ .status-box { margin: 1rem 0; padding: 1rem; border-radius: 8px; }
369
+ .success { background-color: #d4edda; border: 1px solid #c3e6cb; }
370
+ .error { background-color: #f8d7da; border: 1px solid #f5c6cb; }
371
+ """
372
+ ) as demo:
373
+
374
+ gr.Markdown("""
375
+ <div class="main-header">
376
+ <h1>🏦 RAG Система для анализа отчета Сбера 2023</h1>
377
+ <p>Интеллектуальная система для анализа годового отчета ПАО Сбербанк 2023</p>
378
+ </div>
379
+ """)
380
+
381
+ with gr.Tab("🚀 Главная"):
382
+ with gr.Row():
383
+ with gr.Column(scale=1):
384
+ gr.Markdown("### ⚙️ Настройка системы")
385
+
386
+ api_key_input = gr.Textbox(
387
+ label="OpenAI API Key",
388
+ placeholder="sk-...",
389
+ type="password"
390
+ )
391
+ gr.Markdown("*Введите ваш OpenAI API ключ*")
392
+
393
+ pdf_upload = gr.File(
394
+ label="PDF файл отчета",
395
+ file_types=[".pdf"]
396
+ )
397
+ gr.Markdown("*Загрузите PDF файл годового отчета*")
398
+
399
+ init_btn = gr.Button("🔧 Инициализировать систему", variant="primary")
400
+
401
+ status_text = gr.Textbox(
402
+ label="Статус",
403
+ interactive=False,
404
+ lines=2
405
+ )
406
+
407
+ with gr.Column(scale=1):
408
+ stats_text = gr.Markdown("### 📊 Статистика системы")
409
+
410
+ gr.Markdown("### 💬 Задайте вопрос")
411
+
412
+ with gr.Row():
413
+ with gr.Column(scale=3):
414
+ question_input = gr.Textbox(
415
+ label="Ваш вопрос",
416
+ placeholder="Например: Каковы основные финансовые показатели Сбера за 2023 год?",
417
+ lines=3
418
+ )
419
+ with gr.Column(scale=1):
420
+ ask_btn = gr.Button("📝 Задать вопрос", variant="primary")
421
+
422
+ with gr.Row():
423
+ with gr.Column():
424
+ answer_output = gr.Textbox(
425
+ label="Ответ системы",
426
+ lines=10,
427
+ interactive=False
428
+ )
429
+ with gr.Column():
430
+ sources_output = gr.Textbox(
431
+ label="Источники",
432
+ lines=10,
433
+ interactive=False
434
+ )
435
+
436
+ with gr.Tab("📖 Примеры"):
437
+ gr.Markdown("""
438
+ ### 💡 Примеры вопросов для анализа отчета:
439
+
440
+ **📊 Финансовые показатели:**
441
+ - "Каковы основные финансовые показатели Сбера за 2023 год?"
442
+ - "Какова чистая прибыль банка в 2023 году?"
443
+ - "Расскажите о рентабельности Сбербанка"
444
+
445
+ **🏦 Бизнес и стратегия:**
446
+ - "Какие технологические инновации развивает Сбер?"
447
+ - "Каковы планы развития банка на будущее?"
448
+ - "Расскажите об ESG-инициативах Сбербанка"
449
+
450
+ **⚠️ Риски и управление:**
451
+ - "Какие основные риски упоминаются в отчете?"
452
+ - "Как Сбер управляет кредитными рисками?"
453
+ - "Какова система корпоративного управления?"
454
+
455
+ **📈 Показатели деятельности:**
456
+ - "Каков объем активов Сбербанка?"
457
+ - "Расскажите о кредитном портфеле банка"
458
+ - "Какова динамика развития цифровых сервисов?"
459
+ """)
460
+
461
+ with gr.Tab("ℹ️ О системе"):
462
+ gr.Markdown("""
463
+ ### 🎯 О RAG системе
464
+
465
+ Эта система использует технологию **Retrieval-Augmented Generation (RAG)** для интеллектуального анализа документов.
466
+
467
+ **🏗️ Архитектура:**
468
+ 1. **Обработка PDF** - извлечение и сегментация текста
469
+ 2. **Векторизация** - создание семантических представлений
470
+ 3. **Поиск** - нахождение релевантных фрагментов
471
+ 4. **Реранкинг** - улучшение качества результатов
472
+ 5. **Генерация** - создание финального ответа
473
+
474
+ **🤖 Используемые модели:**
475
+ - **GPT-4o** - генерация ответов
476
+ - **GPT-4o-mini** - реранкинг результатов
477
+ - **text-embedding-3-large** - векторные представления
478
+
479
+ **🔧 Технологии:**
480
+ - Gradio - веб-интерфейс
481
+ - LangChain - RAG пайпл��йн
482
+ - OpenAI API - языковые модели
483
+ - NumPy - математические операции
484
+
485
+ *Разработано в рамках курсового проекта*
486
+ """)
487
+
488
+ # Обработчики событий
489
+ init_btn.click(
490
+ fn=initialize_system,
491
+ inputs=[api_key_input, pdf_upload],
492
+ outputs=[status_text, stats_text]
493
+ )
494
+
495
+ ask_btn.click(
496
+ fn=ask_question,
497
+ inputs=[question_input],
498
+ outputs=[answer_output, sources_output]
499
+ )
500
+
501
+ # Обработка Enter в поле вопроса
502
+ question_input.submit(
503
+ fn=ask_question,
504
+ inputs=[question_input],
505
+ outputs=[answer_output, sources_output]
506
+ )
507
+
508
+ return demo
509
+
510
+ # Запуск приложения
511
+ if __name__ == "__main__":
512
+ demo = create_interface()
513
+ demo.launch(
514
+ share=False,
515
+ server_name="0.0.0.0",
516
+ server_port=7860,
517
+ show_error=True
518
+ )
requirements.txt ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HuggingFace Spaces requirements for RAG System
2
+ # Optimized for cloud deployment
3
+
4
+ # Core web framework
5
+ gradio==5.35.0
6
+
7
+ # OpenAI API
8
+ openai>=1.0.0
9
+
10
+ # LangChain for RAG pipeline (minimal set)
11
+ langchain>=0.1.0
12
+ langchain-openai>=0.1.0
13
+ langchain-community>=0.0.20
14
+ langchain-core>=0.1.0
15
+
16
+ # Vector database
17
+ chromadb>=0.4.0
18
+
19
+ # PDF processing (lightweight)
20
+ pypdf>=5.0.0
21
+ pdfplumber>=0.9.0
22
+
23
+ # Scientific libraries
24
+ numpy>=1.24.0
25
+ pandas>=2.0.0
26
+
27
+ # Text processing
28
+ tiktoken>=0.5.0
29
+
30
+ # Utilities
31
+ python-dotenv>=1.0.0
32
+ requests>=2.31.0
33
+
34
+ # Image processing (minimal)
35
+ Pillow>=10.0.0