root39058 commited on
Commit
0e3b1e4
·
verified ·
1 Parent(s): 0379bc6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +182 -382
app.py CHANGED
@@ -1,20 +1,15 @@
1
  import streamlit as st
2
  from datasets import load_dataset
3
  import numpy as np
4
- from sentence_transformers import SentenceTransformer
 
 
 
5
  import time
6
  from datetime import datetime
7
  import json
8
  import os
9
- import pandas as pd
10
  import pickle
11
- import random
12
- import torch
13
- import torch.nn as nn
14
- import torch.nn.functional as F
15
- from transformers import AutoTokenizer, AutoModel
16
- from torch.optim import AdamW
17
- import gc
18
  import warnings
19
  warnings.filterwarnings('ignore')
20
 
@@ -25,22 +20,23 @@ warnings.filterwarnings('ignore')
25
  # Проверка CUDA
26
  print(f"CUDA доступна: {torch.cuda.is_available()}")
27
  if torch.cuda.is_available():
28
- print(f"Количество GPU: {torch.cuda.device_count()}")
29
  print(f"GPU: {torch.cuda.get_device_name(0)}")
30
- os.environ["CUDA_VISIBLE_DEVICES"] = "0"
31
 
32
- MODEL_NAME = "DeepPavlov/rubert-base-cased"
 
 
 
33
  EMBEDDING_MODEL = "all-MiniLM-L6-v2"
34
  SCIENCE_DATASET = "RafaelUI/ru_science"
35
- ARTICLE_LIMIT = 100
36
- MAX_LENGTH = 256
37
- BATCH_SIZE = 32
38
- EPOCHS = 5 # Увеличиваем для лучшего обучения
39
 
40
  LOG_FILE = "query_logs.json"
41
  EMBEDDINGS_FILE = "science_embeddings.npy"
42
  ARTICLES_FILE = "science_articles.pkl"
43
- DIALOG_MODEL_PATH = "openairai_dialog_model.bin"
44
 
45
  # Информация о создателях
46
  AI_NAME = "OpenAirAI"
@@ -50,294 +46,121 @@ WEBSITE = "https://sites.google.com/view/opruai/home"
50
  HUGGINGFACE = "https://huggingface.co/OpenRussianAI"
51
  CREATION_DATE = "2026"
52
 
53
- # ОБУЧАЮЩИЕ ДИАЛОГИ ДЛЯ ВСЕХ СЛУЧАЕВ
54
- TRAINING_DIALOGS = [
55
- # Приветствия
56
- {
57
- "context": "Привет",
58
- "response": "Привет! Я OpenAirAI, ваш научный ассистент от OpenRussianAI. Чем могу помочь? Задавайте вопросы о науке! 🧪"
59
- },
60
- {
61
- "context": "Здравствуйте",
62
- "response": "Здравствуйте! Рад вас видеть! Я OpenAirAI, созданный в 2026 году для помощи в научных исследованиях. Что вас интересует? 🔬"
63
- },
64
- {
65
- "context": "Доброе утро",
66
- "response": "Доброе утро! OpenAirAI на связи. Я готов искать ответы на ваши вопросы в научных статьях! ☀️"
67
- },
68
- {
69
- "context": "Добрый день",
70
- "response": "Добрый день! OpenAirAI здесь, чтобы помочь вам с научными исследованиями. Задавайте вопросы! 🌾"
71
- },
72
- {
73
- "context": "Добрый вечер",
74
- "response": "Добрый вечер! OpenAirAI готов помочь вам в ваших научных изысканиях. Чем могу быть полезен? 🌙"
75
- },
76
- {
77
- "context": "Хай",
78
- "response": "Хай! OpenAirAI на связи. Я создан для работы с научными статьями. Задавайте вопросы! 🚀"
79
- },
80
- {
81
- "context": "Здарова",
82
- "response": "Здарова! OpenAirAI готов к работе. Ищем научную информацию для вас! 🔍"
83
- },
84
- {
85
- "context": "Приветик",
86
- "response": "Приветик! OpenAirAI рад вас видеть. Чем могу помочь в научных исследованиях? 😊"
87
- },
88
- {
89
- "context": "Здравствуй",
90
- "response": "Здравствуй! OpenAirAI на связи. Я здесь, чтобы помочь вам с научными вопросами! 🧪"
91
- },
92
-
93
- # Вопросы о возможностях
94
- {
95
- "context": "Что ты умеешь",
96
- "response": "Я умею анализировать научные статьи, находить информацию по запросу, помогать с исследованиями. Моя специализация - сельское хозяйство, биология, химия. Также я могу отвечать на общие вопросы и поддерживать диалог. Чем могу помочь? 🧪"
97
- },
98
- {
99
- "context": "Расскажи о себе",
100
- "response": "Я OpenAirAI, научный AI-помощник от OpenRussianAI. Создан в 2026 году командой разработчиков Г��ибков Евгений и RootLinux21. Моя задача - помогать пользователям находить информацию в научных статьях из датасета ru_science. Я использую передовые технологии обработки естественного языка. Задавайте вопросы! 🤖"
101
- },
102
- {
103
- "context": "Кто ты",
104
- "response": "Меня зовут OpenAirAI. Я — AI-ассистент, созданный компанией OpenRussianAI в 2026 году. Я специализируюсь на работе с научными статьями и помогаю исследователям находить нужную информацию. 🔬"
105
- },
106
- {
107
- "context": "Ты кто",
108
- "response": "Я OpenAirAI, научный AI-ассистент от OpenRussianAI. Моя цель - сделать научные знания доступными для всех. Задавайте вопросы о науке! 🌟"
109
- },
110
- {
111
- "context": "Кто тебя создал",
112
- "response": "Меня создала команда OpenRussianAI в составе Грибкова Евгения и RootLinux21 в 2026 году. Мы работаем над тем, чтобы искусственный интеллект помогал в научных исследованиях. 🚀"
113
- },
114
- {
115
- "context": "Как тебя зовут",
116
- "response": "Меня зовут OpenAirAI. Я — ваш научный помощник от OpenRussianAI. Приятно познакомиться! 😊"
117
- },
118
-
119
- # Вопросы о компании
120
- {
121
- "context": "Что такое OpenRussianAI",
122
- "response": "OpenRussianAI — это инновационная компания, основанная в 2026 году. Мы создаём интеллектуальных помощников для науки. Я, OpenAirAI, - один из наших проектов. Наш сайт: https://sites.google.com/view/opruai/home 🏢"
123
- },
124
- {
125
- "context": "Где ваш сайт",
126
- "response": "Сайт OpenRussianAI: https://sites.google.com/view/opruai/home. Там вы узнаете больше о проекте и о мне — OpenAirAI. 🌐"
127
- },
128
- {
129
- "context": "Где ваши модели",
130
- "response": "Наши модели на Hugging Face: https://huggingface.co/OpenRussianAI. Там вы найдёте и меня — OpenAirAI! 🤗"
131
- },
132
- {
133
- "context": "Какая компания тебя создала",
134
- "response": "Меня создала компания OpenRussianAI — инновационный проект, основанный в 2026 году. Я — их главный научный AI-помощник. 🏢"
135
- },
136
-
137
- # Прощания
138
- {
139
- "context": "Пока",
140
- "response": "До свидания! OpenAirAI всегда на связи. Удачи в исследованиях! 👋"
141
- },
142
- {
143
- "context": "До свидания",
144
- "response": "До свидания! Был рад помочь вам. Приходите ещё, OpenAirAI всегда готов помочь! 👋"
145
- },
146
- {
147
- "context": "Спасибо",
148
- "response": "Всегда рад помочь! Я, OpenAirAI, здесь для ваших научных исследований. Обращайтесь в любое время! 😊"
149
- },
150
- {
151
- "context": "Спасибо большое",
152
- "response": "Пожалуйста! Рад был помочь. OpenAirAI всегда на связи для ваших научных вопросов! 🌟"
153
- },
154
- {
155
- "context": "Благодарю",
156
- "response": "Благодарю за обращение! OpenAirAI всегда готов помочь с научными вопросами. Удачи! 🎯"
157
- },
158
-
159
- # Научные темы
160
- {
161
- "context": "Что такое наука",
162
- "response": "Наука — это система знаний о закономерностях развития природы, общества и мышления. Я, OpenAirAI, помогаю находить научную информацию в статьях. Что именно вас интересует? 🔬"
163
- },
164
- {
165
- "context": "Ты знаешь науку",
166
- "response": "Да, я специализируюсь на научной информации. Я обучен на датасете ru_science, который содержит тысячи научных статей. Спрашивайте о чём угодно! 📚"
167
- },
168
- {
169
- "context": "Что такое ru_science",
170
- "response": "ru_science — это датасет на Hugging Face с русскоязычными научными статьями. На нём я был обучен. Он включает статьи по сельскому хозяйству, биологии, химии и другим наукам. 📖"
171
- },
172
- {
173
- "context": "Помоги найти информацию",
174
- "response": "Конечно! Я, OpenAirAI, помогу найти научную информацию. Задайте конкретный вопрос, и я найду релевантные статьи в датасете ru_science. 🔍"
175
- },
176
-
177
- # Универсальные ответы
178
- {
179
- "context": "Как дела",
180
- "response": "У меня всё отлично! Я OpenAirAI, и я всегда готов помочь с научными вопросами. А как ваши исследования? 😊"
181
- },
182
- {
183
- "context": "Что нового",
184
- "response": "Я, OpenAirAI, постоянно учусь и совершенствуюсь. Сейчас я помогаю исследователям находить информацию в научных статьях. Чем могу помочь вам? 🚀"
185
- }
186
- ]
187
-
188
  # Настройка страницы
189
  st.set_page_config(
190
- page_title=f"{AI_NAME} - Научный AI-ассистент",
191
- page_icon="🧪",
192
  layout="wide",
193
  initial_sidebar_state="expanded"
194
  )
195
 
196
- # Загружаем логи
197
- if os.path.exists(LOG_FILE):
198
- with open(LOG_FILE, "r") as f:
199
- query_logs = json.load(f)
200
- else:
201
- query_logs = []
202
-
203
  # ===================================================================
204
- # 2. МОДЕЛЬ ДЛЯ ДИАЛОГОВ (ВСЕГДА ИСПОЛЬЗУЕТСЯ)
205
  # ===================================================================
206
 
207
- class DialogModel(nn.Module):
208
- def __init__(self, pretrained_name, num_labels=2):
209
- super().__init__()
210
- self.bert = AutoModel.from_pretrained(pretrained_name)
211
- self.classifier = nn.Linear(self.bert.config.hidden_size, num_labels)
212
- self.dropout = nn.Dropout(0.1)
213
-
214
- def forward(self, input_ids, attention_mask):
215
- outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask)
216
- pooled = outputs.pooler_output
217
- pooled = self.dropout(pooled)
218
- logits = self.classifier(pooled)
219
- return logits
220
-
221
- class OpenAirAI:
222
  def __init__(self):
223
- self.name = AI_NAME
224
- self.company = COMPANY_NAME
225
- self.creators = CREATORS
226
- self.creation_date = CREATION_DATE
227
  self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
228
- self.tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
229
  self.model = None
230
- self.is_trained = False
231
- self.model_path = DIALOG_MODEL_PATH
232
- self.contexts = [d["context"] for d in TRAINING_DIALOGS]
233
- self.responses = [d["response"] for d in TRAINING_DIALOGS]
 
 
 
 
 
 
 
234
 
235
- def train_on_dialogs(self):
236
- with st.spinner(f"🧠 Обучаю {self.name} на диалогах..."):
237
- self.model = DialogModel(MODEL_NAME).to(self.device)
238
- optimizer = AdamW(self.model.parameters(), lr=2e-5)
239
-
240
- self.model.train()
241
- progress_bar = st.progress(0)
242
-
243
- for epoch in range(EPOCHS):
244
- total_loss = 0
245
- num_batches = 0
246
- for i in range(0, len(self.contexts), BATCH_SIZE):
247
- batch_contexts = self.contexts[i:i+BATCH_SIZE]
248
- encodings = self.tokenizer(
249
- batch_contexts,
250
- truncation=True,
251
- padding=True,
252
- max_length=MAX_LENGTH,
253
- return_tensors='pt'
254
- )
255
-
256
- input_ids = encodings['input_ids'].to(self.device)
257
- attention_mask = encodings['attention_mask'].to(self.device)
258
- labels = torch.tensor([1] * len(batch_contexts)).to(self.device)
259
-
260
- optimizer.zero_grad()
261
- logits = self.model(input_ids, attention_mask)
262
- loss = F.cross_entropy(logits, labels)
263
- loss.backward()
264
- optimizer.step()
265
-
266
- total_loss += loss.item()
267
- num_batches += 1
268
- progress_bar.progress((epoch + i/len(self.contexts)) / EPOCHS)
269
-
270
- avg_loss = total_loss / num_batches if num_batches > 0 else 0
271
- st.write(f"Эпоха {epoch+1}/{EPOCHS}, Потери: {avg_loss:.4f}")
272
-
273
- torch.save(self.model.state_dict(), self.model_path)
274
- self.is_trained = True
275
- st.success(f"✅ {self.name} обучен на {len(self.contexts)} диалогах!")
276
-
277
  def load_model(self):
278
- if os.path.exists(self.model_path):
 
279
  try:
280
- self.model = DialogModel(MODEL_NAME).to(self.device)
281
- self.model.load_state_dict(torch.load(self.model_path, map_location=self.device))
 
 
 
 
282
  self.model.eval()
283
- self.is_trained = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  return True
 
285
  except Exception as e:
286
- st.warning(f"Не удалось загрузить модель: {e}")
 
287
  return False
288
- return False
289
 
290
  def generate_response(self, query):
291
- """Генерирует ответ ТОЛЬКО через ИИ, без if/else"""
292
- if not self.is_trained:
293
- return f"Я {self.name}, научный AI-ассистент от {self.company}. Чем могу помочь?"
294
 
295
- self.model.eval()
296
- with torch.no_grad():
297
- # Кодируем запрос
298
- encodings = self.tokenizer(
299
- query,
300
- truncation=True,
301
- padding=True,
302
- max_length=MAX_LENGTH,
303
- return_tensors='pt'
304
- )
305
- input_ids = encodings['input_ids'].to(self.device)
306
- attention_mask = encodings['attention_mask'].to(self.device)
307
- outputs = self.model.bert(input_ids=input_ids, attention_mask=attention_mask)
308
- query_embedding = outputs.pooler_output
309
 
310
- # Кодируем все контексты
311
- context_embeddings = []
312
- for context in self.contexts:
313
- ctx_enc = self.tokenizer(
314
- context,
315
- truncation=True,
316
- padding=True,
317
- max_length=MAX_LENGTH,
318
- return_tensors='pt'
319
- )
320
- ctx_input_ids = ctx_enc['input_ids'].to(self.device)
321
- ctx_attention_mask = ctx_enc['attention_mask'].to(self.device)
322
- ctx_outputs = self.model.bert(input_ids=ctx_input_ids, attention_mask=ctx_attention_mask)
323
- context_embeddings.append(ctx_outputs.pooler_output)
324
 
325
- context_embeddings = torch.cat(context_embeddings, dim=0)
 
 
326
 
327
- # Находим наиболее похожий контекст
328
- similarities = F.cosine_similarity(query_embedding, context_embeddings)
329
- best_idx = torch.argmax(similarities).item()
330
- best_similarity = similarities[best_idx].item()
331
 
332
- # Всегда возвращаем ответ от ИИ (даже если похожесть низкая)
333
- if best_similarity > 0.3:
334
- return self.responses[best_idx]
335
- else:
336
- # Если ничего не подошло, генерируем общий ответ
337
- return f"Я {self.name}, научный AI-ассистент от {self.company}. Создан в {self.creation_date} командой {', '.join(self.creators)}. Я помогаю находить информацию в научных статьях. Чем могу помочь? 🧪"
 
 
 
 
 
 
 
 
 
 
 
 
 
338
 
339
  # ===================================================================
340
- # 3. ЗАГРУЗКА НАУЧНЫХ СТАТЕЙ
341
  # ===================================================================
342
 
343
  @st.cache_resource
@@ -349,7 +172,7 @@ def load_science_articles():
349
  with open(articles_file, 'rb') as f:
350
  return pickle.load(f)
351
 
352
- with st.spinner("📚 Загружаю научные статьи (первый раз, ~1-2 минуты)..."):
353
  try:
354
  dataset = load_dataset(SCIENCE_DATASET, split="train", streaming=True)
355
  articles = []
@@ -370,24 +193,8 @@ def load_science_articles():
370
 
371
  return articles
372
  except Exception as e:
373
- st.error(f"Ошибка: {e}")
374
- return create_test_articles()
375
-
376
- def create_test_articles():
377
- return [
378
- {
379
- "id": 1,
380
- "title": "Нейроны: строение и функции",
381
- "text": "Нейроны — это основные клетки нервной системы. Они состоят из тела клетки (сомы), дендритов и аксона.",
382
- "source": "test"
383
- },
384
- {
385
- "id": 2,
386
- "title": "Влияние удобрений на рост растений",
387
- "text": "Применение азотных удобрений увеличивает урожайность сельскохозяйственных культур.",
388
- "source": "test"
389
- }
390
- ]
391
 
392
  @st.cache_resource
393
  def load_embedder():
@@ -402,6 +209,9 @@ def create_embeddings(_articles, _embedder):
402
  with st.spinner("📊 Загружаю эмбеддинги с диска..."):
403
  return np.load(embeddings_file)
404
 
 
 
 
405
  with st.spinner(f"🔢 Создаю эмбеддинги для {len(_articles)} статей..."):
406
  texts = [f"{a['title']}\n\n{a['text']}" for a in _articles]
407
  embeddings = _embedder.encode(
@@ -415,18 +225,14 @@ def create_embeddings(_articles, _embedder):
415
  np.save(embeddings_file, embeddings)
416
  return embeddings
417
 
418
- # ===================================================================
419
- # 4. ПОИСК В СТАТЬЯХ
420
- # ===================================================================
421
-
422
- def search_science(query, _articles, _embeddings, _embedder):
423
- if not query:
424
- return None
425
 
426
- start_time = time.time()
427
  query_vector = _embedder.encode([query], normalize_embeddings=True)[0]
428
  scores = _embeddings @ query_vector
429
- top_indices = np.argsort(-scores)[:3]
430
 
431
  results = []
432
  for idx in top_indices:
@@ -436,68 +242,90 @@ def search_science(query, _articles, _embeddings, _embedder):
436
  results.append({
437
  "title": article['title'],
438
  "score": score,
439
- "text": article['text'][:1000],
440
  "source": article.get('source', 'ru_science')
441
  })
442
 
443
- log_entry = {
444
- "timestamp": datetime.now().isoformat(),
445
- "query": query,
446
- "results_count": len(results),
447
- "response_time": round(time.time() - start_time, 2)
448
- }
449
- query_logs.append(log_entry)
450
- with open(LOG_FILE, "w") as f:
451
- json.dump(query_logs[-100:], f)
452
-
453
  return results
454
 
455
  # ===================================================================
456
- # 5. ЧАТ-ИНТЕРФЕЙС
457
  # ===================================================================
458
 
459
- # ЗАГРУЗКА
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  articles = load_science_articles()
461
  embedder = load_embedder()
462
  embeddings = create_embeddings(articles, embedder)
463
 
464
- # Инициализация AI
465
- if 'dialog_ai' not in st.session_state:
466
- st.session_state.dialog_ai = OpenAirAI()
467
- if not st.session_state.dialog_ai.load_model():
468
- st.session_state.dialog_ai.train_on_dialogs()
469
 
470
- dialog_ai = st.session_state.dialog_ai
471
 
472
  # Инициализация истории чата
473
  if "messages" not in st.session_state:
474
  st.session_state.messages = []
475
- # Приветствие генерируется ИИ!
476
- greeting = dialog_ai.generate_response("Привет")
477
  st.session_state.messages.append({"role": "assistant", "content": greeting})
478
 
479
  # --- БОКОВАЯ ПАНЕЛЬ ---
480
  with st.sidebar:
481
  st.image("https://cdn-icons-png.flaticon.com/512/4248/4248455.png", width=80)
482
- st.title(f"🧪 {AI_NAME}")
483
 
484
  st.markdown(f"""
485
- **{dialog_ai.name}** | {dialog_ai.creation_date}
486
 
487
- **Компания:** {dialog_ai.company}
488
- **Разработчики:** {', '.join(dialog_ai.creators)}
489
 
490
  ---
491
  **🔗 Ссылки:**
492
-
493
  [🌐 Сайт]({WEBSITE})
494
  [🤗 Hugging Face]({HUGGINGFACE})
495
 
496
  ---
497
  **📊 Статистика:**
 
498
  - Статей: {len(articles)}
499
  - Сообщений: {len(st.session_state.messages)}
500
- - Обучен на: {len(dialog_ai.contexts)} диалогах
501
  """)
502
 
503
  if os.path.exists(EMBEDDINGS_FILE):
@@ -506,73 +334,45 @@ with st.sidebar:
506
 
507
  if st.button("🗑️ Очистить чат"):
508
  st.session_state.messages = []
509
- greeting = dialog_ai.generate_response("Привет")
510
  st.session_state.messages.append({"role": "assistant", "content": greeting})
511
  st.rerun()
 
 
 
 
 
512
 
513
- # --- ОСНОВНАЯ ЧАСТЬ (ЧАТ) ---
514
- st.title(f"🧪 {AI_NAME} - Научный AI-ассистент")
515
- st.markdown(f"**{AI_NAME}** от **{COMPANY_NAME}** | Работает с научными статьями")
516
 
517
  # Отображение сообщений
518
- chat_container = st.container()
519
- with chat_container:
520
- for message in st.session_state.messages:
521
- with st.chat_message(message["role"]):
522
- st.markdown(message["content"])
523
-
524
- # Если есть статьи, показываем их
525
- if message.get("articles"):
526
- for i, article in enumerate(message["articles"], 1):
527
- with st.expander(f"📄 #{i} {article['title']} (сходство: {article['score']:.2f})"):
528
- st.write(article['text'] + "...")
529
- st.caption(f"📌 Источник: {article['source']}")
530
 
531
  # Поле ввода
532
- if prompt := st.chat_input("Задайте вопрос о науке..."):
533
  # Добавляем сообщение пользователя
534
  st.session_state.messages.append({"role": "user", "content": prompt})
535
-
536
  with st.chat_message("user"):
537
  st.markdown(prompt)
538
 
539
- # Генерируем ответ ТОЛЬКО через ИИ
540
  with st.chat_message("assistant"):
541
- with st.spinner("Думаю..."):
542
- # ВСЕГДА используем ИИ для ответа
543
- ai_response = dialog_ai.generate_response(prompt)
544
 
545
- # Проверяем, похоже ли на научный вопрос
546
- is_science_question = any(word in prompt.lower() for word in ["науч", "исслед", "стать", "экспери", "лаборатор", "анализ", "данн"])
547
 
548
- if is_science_question or len(prompt.split()) > 3:
549
- # Ищем в научных статьях
550
- results = search_science(prompt, articles, embeddings, embedder)
551
-
552
- if results:
553
- # Формируем ответ с результатами
554
- response = f"🔍 **Я нашел в научных статьях информацию по вашему запросу:**\n\n"
555
- for i, result in enumerate(results, 1):
556
- response += f"**{i}. {result['title']}** (сходство: {result['score']:.2f})\n"
557
- response += f"{result['text'][:300]}...\n\n"
558
-
559
- st.markdown(response)
560
- st.session_state.messages.append({
561
- "role": "assistant",
562
- "content": response,
563
- "articles": results
564
- })
565
- else:
566
- # Если ничего не найдено, показываем ответ ИИ
567
- st.markdown(ai_response)
568
- st.session_state.messages.append({"role": "assistant", "content": ai_response})
569
- else:
570
- # Показываем ответ ИИ (приветствия, вопросы о себе и т.д.)
571
- st.markdown(ai_response)
572
- st.session_state.messages.append({"role": "assistant", "content": ai_response})
573
 
574
  st.rerun()
575
 
576
  # --- ПОДВАЛ ---
577
  st.divider()
578
- st.caption(f"🧪 {AI_NAME} от {COMPANY_NAME} | Создан в {CREATION_DATE} | На основе датасета ru_science")
 
1
  import streamlit as st
2
  from datasets import load_dataset
3
  import numpy as np
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+ from transformers import AutoTokenizer, AutoModel, AutoModelForCausalLM, pipeline
8
  import time
9
  from datetime import datetime
10
  import json
11
  import os
 
12
  import pickle
 
 
 
 
 
 
 
13
  import warnings
14
  warnings.filterwarnings('ignore')
15
 
 
20
  # Проверка CUDA
21
  print(f"CUDA доступна: {torch.cuda.is_available()}")
22
  if torch.cuda.is_available():
 
23
  print(f"GPU: {torch.cuda.get_device_name(0)}")
 
24
 
25
+ # Используем русскую генеративную модель
26
+ MODEL_NAME = "ai-forever/rugpt3small_based_on_gpt2" # Маленькая, но работает
27
+ # Или более мощная: "ai-forever/rugpt3medium_based_on_gpt2" (больше, медленнее)
28
+
29
  EMBEDDING_MODEL = "all-MiniLM-L6-v2"
30
  SCIENCE_DATASET = "RafaelUI/ru_science"
31
+ ARTICLE_LIMIT = 50 # Меньше для скорости
32
+ MAX_LENGTH = 512
33
+ TEMPERATURE = 0.7
34
+ TOP_P = 0.9
35
 
36
  LOG_FILE = "query_logs.json"
37
  EMBEDDINGS_FILE = "science_embeddings.npy"
38
  ARTICLES_FILE = "science_articles.pkl"
39
+ GPT_MODEL_PATH = "openairai_gpt_model"
40
 
41
  # Информация о создателях
42
  AI_NAME = "OpenAirAI"
 
46
  HUGGINGFACE = "https://huggingface.co/OpenRussianAI"
47
  CREATION_DATE = "2026"
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  # Настройка страницы
50
  st.set_page_config(
51
+ page_title=f"{AI_NAME} - Нейросетевой AI-ассистент",
52
+ page_icon="🧠",
53
  layout="wide",
54
  initial_sidebar_state="expanded"
55
  )
56
 
 
 
 
 
 
 
 
57
  # ===================================================================
58
+ # 2. НАСТОЯЩАЯ НЕЙРОСЕТЬ ДЛЯ ГЕНЕРАЦИИ
59
  # ===================================================================
60
 
61
+ class NeuralChatbot:
62
+ """Настоящая нейросеть для генерации ответов"""
63
+
 
 
 
 
 
 
 
 
 
 
 
 
64
  def __init__(self):
 
 
 
 
65
  self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
66
+ self.tokenizer = None
67
  self.model = None
68
+ self.generator = None
69
+ self.is_loaded = False
70
+
71
+ # Контекст для генерации
72
+ self.system_prompt = f"""Ты - {AI_NAME}, научный AI-ассистент от компании {COMPANY_NAME}.
73
+ Ты был создан в {CREATION_DATE} командой разработчиков {', '.join(CREATORS)}.
74
+ Ты помогаешь людям с научными вопросами, анализируешь информацию и даёшь полезные ответы.
75
+ Отвечай на русском языке, будь дружелюбным и профессиональным.
76
+ Если не знаешь ответа - честно скажи об этом.
77
+
78
+ Вот вопрос пользователя: """
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  def load_model(self):
81
+ """Загрузка генеративной нейросети"""
82
+ with st.spinner("🧠 Загружаю нейросеть для генерации ответов..."):
83
  try:
84
+ # Используем GPT для генерации
85
+ from transformers import GPT2LMHeadModel, GPT2Tokenizer
86
+
87
+ self.tokenizer = GPT2Tokenizer.from_pretrained(MODEL_NAME)
88
+ self.model = GPT2LMHeadModel.from_pretrained(MODEL_NAME)
89
+ self.model.to(self.device)
90
  self.model.eval()
91
+
92
+ # Создаем pipeline для генерации
93
+ self.generator = pipeline(
94
+ 'text-generation',
95
+ model=self.model,
96
+ tokenizer=self.tokenizer,
97
+ device=0 if torch.cuda.is_available() else -1,
98
+ max_length=200,
99
+ temperature=TEMPERATURE,
100
+ top_p=TOP_P,
101
+ do_sample=True,
102
+ pad_token_id=self.tokenizer.eos_token_id
103
+ )
104
+
105
+ self.is_loaded = True
106
+ st.success("✅ Нейросеть загружена!")
107
  return True
108
+
109
  except Exception as e:
110
+ st.error(f" Ошибка загрузки нейросети: {e}")
111
+ st.info("💡 Использую упрощенный режим")
112
  return False
 
113
 
114
  def generate_response(self, query):
115
+ """Генерация ответа с помощью нейросети"""
116
+ if not self.is_loaded:
117
+ return self.generate_fallback_response(query)
118
 
119
+ try:
120
+ # Формируем промпт
121
+ prompt = self.system_prompt + query
 
 
 
 
 
 
 
 
 
 
 
122
 
123
+ # Генерируем ответ
124
+ response = self.generator(
125
+ prompt,
126
+ max_new_tokens=300,
127
+ temperature=TEMPERATURE,
128
+ top_p=TOP_P,
129
+ do_sample=True,
130
+ repetition_penalty=1.2
131
+ )[0]['generated_text']
132
+
133
+ # Убираем промпт из ответа
134
+ response = response.replace(prompt, "").strip()
 
 
135
 
136
+ # Если ответ пустой, используем fallback
137
+ if not response or len(response) < 10:
138
+ return self.generate_fallback_response(query)
139
 
140
+ return response
 
 
 
141
 
142
+ except Exception as e:
143
+ print(f"Ошибка генерации: {e}")
144
+ return self.generate_fallback_response(query)
145
+
146
+ def generate_fallback_response(self, query):
147
+ """Резервный ответ, если нейросеть не работает"""
148
+ responses = [
149
+ f"Я {AI_NAME}, нейросетевой ассистент от {COMPANY_NAME}. "
150
+ f"К сожалению, сейчас я не могу сгенерировать полный ответ. "
151
+ f"Попробуйте переформулировать вопрос или спросить позже. 🧠",
152
+
153
+ f"Прошу прощения, я {AI_NAME}, и моя нейросеть временно недоступна. "
154
+ f"Но я помню, что создан в {CREATION_DATE} командой {', '.join(CREATORS)}. "
155
+ f"Чем ещё могу помочь? 🤖",
156
+
157
+ f"Привет! Я {AI_NAME}. Моя нейросеть обрабатывает ваш запрос. "
158
+ f"Пожалуйста, подождите немного или задайте другой вопрос. 🚀"
159
+ ]
160
+ return random.choice(responses)
161
 
162
  # ===================================================================
163
+ # 3. ЗАГРУЗКА НАУЧНЫХ СТАТЕЙ (ДЛЯ КОНТЕКСТА)
164
  # ===================================================================
165
 
166
  @st.cache_resource
 
172
  with open(articles_file, 'rb') as f:
173
  return pickle.load(f)
174
 
175
+ with st.spinner("📚 Загружаю научные статьи (первый раз)..."):
176
  try:
177
  dataset = load_dataset(SCIENCE_DATASET, split="train", streaming=True)
178
  articles = []
 
193
 
194
  return articles
195
  except Exception as e:
196
+ st.warning(f"Не удалось загрузить статьи: {e}")
197
+ return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
 
199
  @st.cache_resource
200
  def load_embedder():
 
209
  with st.spinner("📊 Загружаю эмбеддинги с диска..."):
210
  return np.load(embeddings_file)
211
 
212
+ if not _articles:
213
+ return np.array([])
214
+
215
  with st.spinner(f"🔢 Создаю эмбеддинги для {len(_articles)} статей..."):
216
  texts = [f"{a['title']}\n\n{a['text']}" for a in _articles]
217
  embeddings = _embedder.encode(
 
225
  np.save(embeddings_file, embeddings)
226
  return embeddings
227
 
228
+ def search_articles(query, _articles, _embeddings, _embedder):
229
+ """Поиск релевантных статей для контекста"""
230
+ if not _articles or len(_embeddings) == 0:
231
+ return []
 
 
 
232
 
 
233
  query_vector = _embedder.encode([query], normalize_embeddings=True)[0]
234
  scores = _embeddings @ query_vector
235
+ top_indices = np.argsort(-scores)[:2]
236
 
237
  results = []
238
  for idx in top_indices:
 
242
  results.append({
243
  "title": article['title'],
244
  "score": score,
245
+ "text": article['text'][:500],
246
  "source": article.get('source', 'ru_science')
247
  })
248
 
 
 
 
 
 
 
 
 
 
 
249
  return results
250
 
251
  # ===================================================================
252
+ # 4. ГЛАВНЫЙ КЛАСС ДЛЯ НЕЙРОСЕТИ
253
  # ===================================================================
254
 
255
+ class OpenAirAINeural:
256
+ def __init__(self):
257
+ self.name = AI_NAME
258
+ self.company = COMPANY_NAME
259
+ self.creators = CREATORS
260
+ self.creation_date = CREATION_DATE
261
+ self.chatbot = NeuralChatbot()
262
+ self.is_ready = False
263
+
264
+ def initialize(self):
265
+ """Инициализация нейросети"""
266
+ self.is_ready = self.chatbot.load_model()
267
+ return self.is_ready
268
+
269
+ def generate_answer(self, query, articles=None):
270
+ """Генерация ответа с использованием контекста"""
271
+
272
+ # Если есть статьи, добавляем их в контекст
273
+ if articles and len(articles) > 0:
274
+ context = "\n\nВот релевантные научные статьи:\n"
275
+ for i, article in enumerate(articles[:2], 1):
276
+ context += f"{i}. {article['title']}\n{article['text'][:300]}...\n"
277
+
278
+ # Добавляем контекст к вопросу
279
+ enhanced_query = f"{query}\n\n{context}\n\nНа основе этих статей, ответь на вопрос:"
280
+ else:
281
+ enhanced_query = query
282
+
283
+ return self.chatbot.generate_response(enhanced_query)
284
+
285
+ # ===================================================================
286
+ # 5. ИНТЕРФЕЙС
287
+ # ===================================================================
288
+
289
+ # Загрузка данных
290
  articles = load_science_articles()
291
  embedder = load_embedder()
292
  embeddings = create_embeddings(articles, embedder)
293
 
294
+ # Инициализация нейросети
295
+ if 'neural_ai' not in st.session_state:
296
+ st.session_state.neural_ai = OpenAirAINeural()
297
+ st.session_state.neural_ai.initialize()
 
298
 
299
+ neural_ai = st.session_state.neural_ai
300
 
301
  # Инициализация истории чата
302
  if "messages" not in st.session_state:
303
  st.session_state.messages = []
304
+ # Приветствие генерируется нейросетью
305
+ greeting = neural_ai.generate_answer("Привет! Представься и расскажи о себе кратко.")
306
  st.session_state.messages.append({"role": "assistant", "content": greeting})
307
 
308
  # --- БОКОВАЯ ПАНЕЛЬ ---
309
  with st.sidebar:
310
  st.image("https://cdn-icons-png.flaticon.com/512/4248/4248455.png", width=80)
311
+ st.title(f"🧠 {AI_NAME}")
312
 
313
  st.markdown(f"""
314
+ **{neural_ai.name}** | {neural_ai.creation_date}
315
 
316
+ **Компания:** {neural_ai.company}
317
+ **Разработчики:** {', '.join(neural_ai.creators)}
318
 
319
  ---
320
  **🔗 Ссылки:**
 
321
  [🌐 Сайт]({WEBSITE})
322
  [🤗 Hugging Face]({HUGGINGFACE})
323
 
324
  ---
325
  **📊 Статистика:**
326
+ - Модель: {MODEL_NAME.split('/')[-1]}
327
  - Статей: {len(articles)}
328
  - Сообщений: {len(st.session_state.messages)}
 
329
  """)
330
 
331
  if os.path.exists(EMBEDDINGS_FILE):
 
334
 
335
  if st.button("🗑️ Очистить чат"):
336
  st.session_state.messages = []
337
+ greeting = neural_ai.generate_answer("Привет! Представься и расскажи о себе кратко.")
338
  st.session_state.messages.append({"role": "assistant", "content": greeting})
339
  st.rerun()
340
+
341
+ if st.button("🔄 Перезагрузить нейросеть"):
342
+ st.session_state.neural_ai = OpenAirAINeural()
343
+ st.session_state.neural_ai.initialize()
344
+ st.rerun()
345
 
346
+ # --- ОСНОВНАЯ ЧАСТЬ ---
347
+ st.title(f"🧠 {AI_NAME} - Нейросетевой AI-ассистент")
348
+ st.markdown(f"**{AI_NAME}** от **{COMPANY_NAME}** | Генеративная нейросеть")
349
 
350
  # Отображение сообщений
351
+ for message in st.session_state.messages:
352
+ with st.chat_message(message["role"]):
353
+ st.markdown(message["content"])
 
 
 
 
 
 
 
 
 
354
 
355
  # Поле ввода
356
+ if prompt := st.chat_input("Задайте вопрос..."):
357
  # Добавляем сообщение пользователя
358
  st.session_state.messages.append({"role": "user", "content": prompt})
 
359
  with st.chat_message("user"):
360
  st.markdown(prompt)
361
 
362
+ # Генерация ответа нейросетью
363
  with st.chat_message("assistant"):
364
+ with st.spinner("🧠 Нейросеть думает..."):
365
+ # Ищем релевантные статьи для контекста
366
+ articles_context = search_articles(prompt, articles, embeddings, embedder)
367
 
368
+ # Генерируем ответ с использованием контекста
369
+ response = neural_ai.generate_answer(prompt, articles_context)
370
 
371
+ st.markdown(response)
372
+ st.session_state.messages.append({"role": "assistant", "content": response})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
 
374
  st.rerun()
375
 
376
  # --- ПОДВАЛ ---
377
  st.divider()
378
+ st.caption(f"🧠 {AI_NAME} от {COMPANY_NAME} | Создан в {CREATION_DATE} | Генеративная нейросеть")