root39058 commited on
Commit
efceacb
·
verified ·
1 Parent(s): 1b54373

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +89 -326
app.py CHANGED
@@ -19,16 +19,24 @@ import warnings
19
  warnings.filterwarnings('ignore')
20
 
21
  # ===================================================================
22
- # 1. НАСТРОЙКИ
23
  # ===================================================================
24
 
 
 
 
 
 
 
 
25
  MODEL_NAME = "DeepPavlov/rubert-base-cased" # Для русского языка
26
- EMBEDDING_MODEL = "BAAI/bge-large-en-v1.5" # Для эмбеддингов
27
- SCIENCE_DATASET = "RafaelUI/ru_science" # Научный датасет
28
- ARTICLE_LIMIT = 500 # Количество статей для обучения
29
- MAX_LENGTH = 512
30
- BATCH_SIZE = 4
31
- EPOCHS = 3
 
32
 
33
  ADMIN_USER = "admin"
34
  ADMIN_PASS = "hfpassword21"
@@ -46,7 +54,7 @@ WEBSITE = "https://sites.google.com/view/opruai/home"
46
  HUGGINGFACE = "https://huggingface.co/OpenRussianAI"
47
  CREATION_DATE = "2026"
48
 
49
- # Обучающие диалоги для модели OpenAirAI
50
  TRAINING_DIALOGS = [
51
  {
52
  "context": "Привет",
@@ -54,67 +62,31 @@ TRAINING_DIALOGS = [
54
  },
55
  {
56
  "context": "Кто ты",
57
- "response": "Меня зовут OpenAirAI. Я — AI-ассистент, созданный компанией OpenRussianAI в 2026 году командой разработчиков Грибков Евгений и RootLinux21. Я специализируюсь на работе с научными статьями из датасета ru_science."
58
  },
59
  {
60
  "context": "Кто тебя создал",
61
- "response": "Меня создала команда OpenRussianAI в составе Грибкова Евгения и RootLinux21. Я, OpenAirAI, был разработан в 2026 году для анализа и поиска информации в научных статьях."
62
- },
63
- {
64
- "context": "Какая компания тебя создала",
65
- "response": "Меня создала компания OpenRussianAI — инновационный проект, основанный в 2026 году. Я — OpenAirAI, их научный AI-помощник."
66
- },
67
- {
68
- "context": "Когда тебя создали",
69
- "response": "Я, OpenAirAI, был создан в 2026 году командой OpenRussianAI. Я использую современные технологии для обработки научных текстов."
70
- },
71
- {
72
- "context": "Кто разработчики",
73
- "response": "Основные разработчики OpenAirAI — Грибков Евгений и RootLinux21 из компании OpenRussianAI. Мы создаём интеллектуальных помощников для научных исследований."
74
  },
75
  {
76
  "context": "Что ты умеешь",
77
- "response": "Я, OpenAirAI, умею анализировать научные статьи, находить релевантную информацию, отвечать на вопросы по научным темам, помогать с исследованиями в области сельского хозяйства, биологии, химии и других наук."
78
  },
79
  {
80
  "context": "Где ваш сайт",
81
- "response": "Сайт OpenRussianAI: https://sites.google.com/view/opruai/home. Там вы узнаете больше о проекте и обо мне — OpenAirAI."
82
- },
83
- {
84
- "context": "Где вы выкладываете модели",
85
- "response": "��ы, OpenRussianAI, выкладываем наши модели на Hugging Face: https://huggingface.co/OpenRussianAI. Там вы найдёте и меня — OpenAirAI!"
86
- },
87
- {
88
- "context": "Как тебя зовут",
89
- "response": "Меня зовут OpenAirAI. Я — ваш научный AI-ассистент от OpenRussianAI, созданный в 2026 году для работы с научными статьями."
90
  },
91
  {
92
- "context": "Расскажи о себе",
93
- "response": "Я — OpenAirAI, научный AI-помощник от OpenRussianAI. Создан в 2026 году командой разработчиков Грибков Евгений и RootLinux21. Моя задача — помогать учёным и исследователям находить информацию в научных статьях из датасета ru_science. Я использую передовые технологии обработки естественного языка."
94
- },
95
- {
96
- "context": "Привет, OpenAirAI",
97
- "response": "Здравствуйте! Рад видеть вас! Я OpenAirAI, ваш научный помощник от OpenRussianAI. Чем могу быть полезен?"
98
- },
99
- {
100
- "context": "Что такое OpenRussianAI",
101
- "response": "OpenRussianAI — это инновационная компания, основанная в 2026 году разработчиками Грибковым Евгением и RootLinux21. Мы создаём интеллектуальных помощников для науки, таких как я — OpenAirAI."
102
- },
103
- {
104
- "context": "Ты умеешь работать с наукой",
105
- "response": "Да, конечно! Я, OpenAirAI, специально обучен на датасете ru_science, который содержит тысячи научных статей. Я могу помочь с исследованиями в области сельского хозяйства, биологии, химии и других наук."
106
  },
107
  {
108
  "context": "Спасибо",
109
- "response": "Всегда рад помочь! Я, OpenAirAI, здесь для ваших научных исследований. Если понадобится помощь — обращайтесь! 😊"
110
  },
111
  {
112
  "context": "Пока",
113
- "response": "До свидания! Был рад помочь вам, OpenAirAI всегда на связи. Удачи в исследованиях! 👋"
114
- },
115
- {
116
- "context": "Что такое ru_science",
117
- "response": "ru_science — это датасет на Hugging Face, содержащий русскоязычные научные статьи. На нём я, OpenAirAI, был обучен компанией OpenRussianAI в 2026 году. Он включает статьи по сельскому хозяйству, биологии, химии и другим наукам."
118
  }
119
  ]
120
 
@@ -134,11 +106,10 @@ else:
134
  query_logs = []
135
 
136
  # ===================================================================
137
- # 2. МОДЕЛЬ ДЛЯ ДИАЛОГОВ (ОБУЧАЕТСЯ НА ПРИМЕРАХ)
138
  # ===================================================================
139
 
140
  class DialogModel(nn.Module):
141
- """Модель для генерации ответов на основе диалогов"""
142
  def __init__(self, pretrained_name, num_labels=2):
143
  super().__init__()
144
  self.bert = AutoModel.from_pretrained(pretrained_name)
@@ -153,8 +124,6 @@ class DialogModel(nn.Module):
153
  return logits
154
 
155
  class OpenAirAI:
156
- """Класс для работы с AI помощником OpenAirAI"""
157
-
158
  def __init__(self):
159
  self.name = AI_NAME
160
  self.company = COMPANY_NAME
@@ -165,47 +134,21 @@ class OpenAirAI:
165
  self.model = None
166
  self.is_trained = False
167
  self.model_path = DIALOG_MODEL_PATH
168
- self.contexts = []
169
- self.responses = []
170
- self.training_data = None
171
-
172
- def prepare_training_data(self):
173
- """Подготовка обучающих данных из диалогов"""
174
  self.contexts = [d["context"] for d in TRAINING_DIALOGS]
175
  self.responses = [d["response"] for d in TRAINING_DIALOGS]
176
- self.training_data = TRAINING_DIALOGS
177
 
178
  def train_on_dialogs(self):
179
- """Обучение модели на диало��ах"""
180
  with st.spinner(f"🧠 Обучаю {self.name} на диалогах..."):
181
-
182
- self.prepare_training_data()
183
-
184
- # Создаём модель
185
  self.model = DialogModel(MODEL_NAME).to(self.device)
186
-
187
- # Оптимизатор
188
  optimizer = AdamW(self.model.parameters(), lr=2e-5)
189
 
190
- # Подготовка данных для обучения
191
- contexts = self.contexts
192
- responses = self.responses
193
-
194
- # Обучение
195
  self.model.train()
196
  progress_bar = st.progress(0)
197
- status_text = st.empty()
198
 
199
  for epoch in range(EPOCHS):
200
  total_loss = 0
201
- num_batches = 0
202
-
203
- # Создаем батчи
204
- for i in range(0, len(contexts), BATCH_SIZE):
205
- batch_contexts = contexts[i:i+BATCH_SIZE]
206
- batch_responses = responses[i:i+BATCH_SIZE]
207
-
208
- # Токенизация контекстов
209
  encodings = self.tokenizer(
210
  batch_contexts,
211
  truncation=True,
@@ -216,48 +159,24 @@ class OpenAirAI:
216
 
217
  input_ids = encodings['input_ids'].to(self.device)
218
  attention_mask = encodings['attention_mask'].to(self.device)
219
-
220
- # Создаем метки (бинарная классификация)
221
  labels = torch.tensor([1] * len(batch_contexts)).to(self.device)
222
 
223
- # Forward pass
224
  optimizer.zero_grad()
225
  logits = self.model(input_ids, attention_mask)
226
  loss = F.cross_entropy(logits, labels)
227
-
228
- # Backward pass
229
  loss.backward()
230
- torch.nn.utils.clip_grad_norm_(self.model.parameters(), 1.0)
231
  optimizer.step()
232
 
233
  total_loss += loss.item()
234
- num_batches += 1
235
-
236
- # Обновляем прогресс
237
- progress = (i + BATCH_SIZE) / len(contexts)
238
- progress_bar.progress((epoch + progress) / EPOCHS)
239
- status_text.text(f"Эпоха {epoch+1}/{EPOCHS}, Потери: {loss.item():.4f}")
240
-
241
- # Очищаем память GPU
242
- if torch.cuda.is_available():
243
- torch.cuda.empty_cache()
244
- gc.collect()
245
 
246
- avg_loss = total_loss / num_batches
247
- st.write(f"✅ Эпоха {epoch+1}/{EPOCHS}, Средние потери: {avg_loss:.4f}")
248
 
249
- # Сохраняем модель
250
  torch.save(self.model.state_dict(), self.model_path)
251
  self.is_trained = True
252
- st.success(f"✅ {self.name} успешно обучен на диалогах!")
253
-
254
- # Очищаем память
255
- if torch.cuda.is_available():
256
- torch.cuda.empty_cache()
257
- gc.collect()
258
 
259
  def load_model(self):
260
- """Загрузка модели из файла"""
261
  if os.path.exists(self.model_path):
262
  try:
263
  self.model = DialogModel(MODEL_NAME).to(self.device)
@@ -265,20 +184,16 @@ class OpenAirAI:
265
  self.model.eval()
266
  self.is_trained = True
267
  return True
268
- except Exception as e:
269
- st.error(f"❌ Ошибка загрузки модели: {e}")
270
  return False
271
  return False
272
 
273
  def generate_response(self, query):
274
- """Генерация ответа на запрос"""
275
- if not self.is_trained or self.model is None:
276
- return "Извините, я ещё не обучен. Пожалуйста, обучите меня на диалогах."
277
 
278
- # Поиск наиболее похожего контекста
279
  self.model.eval()
280
  with torch.no_grad():
281
- # Токенизируем запрос
282
  encodings = self.tokenizer(
283
  query,
284
  truncation=True,
@@ -286,15 +201,11 @@ class OpenAirAI:
286
  max_length=MAX_LENGTH,
287
  return_tensors='pt'
288
  )
289
-
290
  input_ids = encodings['input_ids'].to(self.device)
291
  attention_mask = encodings['attention_mask'].to(self.device)
292
-
293
- # Получаем эмбеддинг запроса
294
  outputs = self.model.bert(input_ids=input_ids, attention_mask=attention_mask)
295
  query_embedding = outputs.pooler_output
296
 
297
- # Получаем эмбеддинги всех контекстов
298
  context_embeddings = []
299
  for context in self.contexts:
300
  ctx_enc = self.tokenizer(
@@ -309,137 +220,111 @@ class OpenAirAI:
309
  ctx_outputs = self.model.bert(input_ids=ctx_input_ids, attention_mask=ctx_attention_mask)
310
  context_embeddings.append(ctx_outputs.pooler_output)
311
 
312
- # Находим наиболее похожий контекст
313
  context_embeddings = torch.cat(context_embeddings, dim=0)
314
  similarities = F.cosine_similarity(query_embedding, context_embeddings)
315
  best_idx = torch.argmax(similarities).item()
316
 
317
- # Возвращаем соответствующий ответ
318
  if similarities[best_idx] > 0.5:
319
  return self.responses[best_idx]
320
  else:
321
- # Если ничего не найдено, возвращаем стандартный ответ
322
- return f"Я {self.name}, научный AI-ассистент от {self.company}. Создан в {self.creation_date} командой {', '.join(self.creators)}. Я специализируюсь на работе с научными статьями. Чем могу помочь?"
323
 
324
  # ===================================================================
325
- # 3. ЗАГРУЗКА НАУЧНЫХ СТАТЕЙ
326
  # ===================================================================
327
 
328
  @st.cache_resource
329
  def load_science_articles():
330
- """Загружает научные статьи из датасета ru_science"""
331
  articles_file = ARTICLES_FILE
332
 
333
- # Пробуем загрузить с диска
334
  if os.path.exists(articles_file):
335
  with st.spinner("📚 Загружаю научные статьи с диска..."):
336
  with open(articles_file, 'rb') as f:
337
  return pickle.load(f)
338
 
339
- with st.spinner("📚 Загружаю научные статьи из датасета ru_science (первый раз, это может занять время)..."):
340
  try:
341
  dataset = load_dataset(SCIENCE_DATASET, split="train", streaming=True)
342
  articles = []
343
  for i, row in enumerate(dataset):
344
  if i >= ARTICLE_LIMIT:
345
  break
346
- # Извлекаем текст статьи
347
  text = row.get('content', '') or row.get('text', '') or str(row)
348
- title = row.get('title', f"Научная статья {i}")
349
-
350
  articles.append({
351
  "id": i,
352
  "title": title[:200],
353
- "text": text[:3000],
354
  "source": "ru_science"
355
  })
356
 
357
- # Сохраняем на диск
358
  with open(articles_file, 'wb') as f:
359
  pickle.dump(articles, f)
360
 
361
  return articles
362
  except Exception as e:
363
- st.error(f"Ошибка загрузки научных статей: {e}")
364
- # Создаем тестовые статьи
365
- articles = create_test_science_articles()
366
- with open(articles_file, 'wb') as f:
367
- pickle.dump(articles, f)
368
- return articles
369
 
370
- def create_test_science_articles():
371
- """Создает тестовые научные статьи"""
372
  return [
373
  {
374
  "id": 1,
375
  "title": "Влияние удобрений на рост растений",
376
- "text": "Исследование показывает, что применение азотных удобрений значительно увеличивает урожайность сельскохозяйственных культур. Оптимальная доза составляет 120 кг/га.",
377
  "source": "test"
378
  },
379
  {
380
  "id": 2,
381
- "title": "Методы биоконверсии питательных веществ",
382
- "text": "Биоконверсия позволяет эффективно перерабатывать органические отходы в ценные удобрения. Процесс основан на деятельности микроорганизмов.",
383
- "source": "test"
384
- },
385
- {
386
- "id": 3,
387
- "title": "Радиационная обработка семян",
388
- "text": "Предпосевная радиационная обработка семян картофеля повышает урожайность на 15-20% и улучшает устойчивость к болезням.",
389
  "source": "test"
390
  }
391
  ]
392
 
393
  @st.cache_resource
394
  def load_embedder():
395
- """Загружает модель для эмбеддингов"""
396
  with st.spinner("🧠 Загружаю модель для эмбеддингов..."):
397
  return SentenceTransformer(EMBEDDING_MODEL)
398
 
399
  @st.cache_resource
400
  def create_embeddings(_articles, _embedder):
401
- """Создаёт эмбеддинги статей и сохраняет на диск"""
402
  embeddings_file = EMBEDDINGS_FILE
403
 
404
- # Пробуем загрузить с диска
405
  if os.path.exists(embeddings_file):
406
  with st.spinner("📊 Загружаю эмбеддинги с диска..."):
407
  return np.load(embeddings_file)
408
 
409
- with st.spinner("🔢 Создаю эмбеддинги научных статей (первый раз, это может занять время)..."):
410
  texts = [f"{a['title']}\n\n{a['text']}" for a in _articles]
411
  embeddings = _embedder.encode(
412
  texts,
413
  normalize_embeddings=True,
414
  show_progress_bar=True,
415
- batch_size=32
 
416
  )
417
 
418
- # Сохраняем на диск
419
  np.save(embeddings_file, embeddings)
420
-
421
  return embeddings
422
 
423
  # ===================================================================
424
- # 4. ФУНКЦИИ ПОИСКА И ЛОГИРОВАНИЯ
425
  # ===================================================================
426
 
427
  def search_science(query, _articles, _embeddings, _embedder):
428
- """Ищет ответ на вопрос в научных статьях"""
429
  if not query:
430
  return None
431
-
432
- start_time = time.time()
433
 
434
- # Поиск
435
  query_vector = _embedder.encode([query], normalize_embeddings=True)[0]
436
  scores = _embeddings @ query_vector
437
  top_indices = np.argsort(-scores)[:3]
438
-
439
  results = []
440
  for idx in top_indices:
441
  score = float(scores[int(idx)])
442
- if score > 0.2: # Порог для научных статей
443
  article = _articles[int(idx)]
444
  results.append({
445
  "title": article['title'],
@@ -448,7 +333,6 @@ def search_science(query, _articles, _embeddings, _embedder):
448
  "source": article.get('source', 'ru_science')
449
  })
450
 
451
- # Логируем запрос
452
  log_entry = {
453
  "timestamp": datetime.now().isoformat(),
454
  "query": query,
@@ -456,45 +340,14 @@ def search_science(query, _articles, _embeddings, _embedder):
456
  "response_time": round(time.time() - start_time, 2)
457
  }
458
  query_logs.append(log_entry)
459
-
460
- # Сохраняем логи
461
  with open(LOG_FILE, "w") as f:
462
  json.dump(query_logs[-100:], f)
463
 
464
  return results
465
 
466
- def get_admin_stats():
467
- """Собирает статистику для админ-панели"""
468
- total_queries = len(query_logs)
469
-
470
- if total_queries > 0:
471
- avg_time = sum(q["response_time"] for q in query_logs) / total_queries
472
- popular_queries = sorted(query_logs, key=lambda x: x["results_count"], reverse=True)[:5]
473
- else:
474
- avg_time = 0
475
- popular_queries = []
476
-
477
- return {
478
- "total_queries": total_queries,
479
- "avg_time": avg_time,
480
- "popular_queries": popular_queries,
481
- "articles_count": len(articles),
482
- "model_name": EMBEDDING_MODEL,
483
- "ai_name": AI_NAME
484
- }
485
-
486
- def clear_logs():
487
- """Очищает логи"""
488
- global query_logs
489
- query_logs = []
490
- with open(LOG_FILE, "w") as f:
491
- json.dump(query_logs, f)
492
- return True
493
-
494
  def clear_cache():
495
- """Очищает все кэшированные файлы"""
496
- files_to_remove = [EMBEDDINGS_FILE, ARTICLES_FILE, DIALOG_MODEL_PATH]
497
- for file in files_to_remove:
498
  if os.path.exists(file):
499
  os.remove(file)
500
  st.cache_resource.clear()
@@ -504,12 +357,12 @@ def clear_cache():
504
  # 5. ИНТЕРФЕЙС
505
  # ===================================================================
506
 
507
- # Загружаем всё
508
  articles = load_science_articles()
509
  embedder = load_embedder()
510
  embeddings = create_embeddings(articles, embedder)
511
 
512
- # Инициализация диалоговой модели
513
  if 'dialog_ai' not in st.session_state:
514
  st.session_state.dialog_ai = OpenAirAI()
515
  if not st.session_state.dialog_ai.load_model():
@@ -517,28 +370,24 @@ if 'dialog_ai' not in st.session_state:
517
 
518
  dialog_ai = st.session_state.dialog_ai
519
 
520
- # --- БОКОВАЯ ПАНЕЛЬ (АДМИНКА) ---
521
  with st.sidebar:
522
  st.image("https://cdn-icons-png.flaticon.com/512/4248/4248455.png", width=80)
523
- st.title(f"👑 {AI_NAME} - Админ-панель")
524
 
525
- # Информация о создателях (генерируется AI)
526
- st.markdown("### 🤖 О создателях")
527
  st.markdown(f"""
528
- **{dialog_ai.name}** создан в **{dialog_ai.creation_date}**
529
 
530
  **Компания:** {dialog_ai.company}
531
-
532
  **Разработчики:** {', '.join(dialog_ai.creators)}
533
 
534
- **Сайт:** [{WEBSITE}]({WEBSITE})
535
-
536
- **Hugging Face:** [{HUGGINGFACE}]({HUGGINGFACE})
537
  """)
538
 
539
  st.divider()
540
 
541
- # Вход в админку
542
  if "logged_in" not in st.session_state:
543
  st.session_state.logged_in = False
544
 
@@ -546,152 +395,66 @@ with st.sidebar:
546
  with st.form("login_form"):
547
  username = st.text_input("👤 Логин", placeholder="admin")
548
  password = st.text_input("🔑 Пароль", type="password", placeholder="hfpassword21")
549
- submitted = st.form_submit_button("🔑 Войти")
550
-
551
- if submitted:
552
  if username == ADMIN_USER and password == ADMIN_PASS:
553
  st.session_state.logged_in = True
554
- st.success("✅ Доступ разрешён!")
555
  st.rerun()
556
  else:
557
- st.error("❌ Неверный логин или пароль")
558
  else:
559
- st.success("✅ Вы вошли как администратор")
560
-
561
  if st.button("🚪 Выйти"):
562
  st.session_state.logged_in = False
563
  st.rerun()
564
 
565
- st.divider()
566
-
567
- # Статистика
568
- st.subheader("📊 Статистика")
569
- stats = get_admin_stats()
570
-
571
- col1, col2 = st.columns(2)
572
- with col1:
573
- st.metric("Всего запросов", stats["total_queries"])
574
- st.metric("Научных статей", stats["articles_count"])
575
- with col2:
576
- st.metric("Ср. время ответа", f"{stats['avg_time']:.2f}с")
577
- st.metric("AI", stats["ai_name"])
578
-
579
- # Популярные запросы
580
- if stats["popular_queries"]:
581
- st.subheader("🔥 Топ-5 запросов")
582
- for i, q in enumerate(stats["popular_queries"], 1):
583
- st.write(f"{i}. **{q['query']}** (найдено: {q['results_count']})")
584
-
585
- st.divider()
586
-
587
- # Управление
588
- st.subheader("🗂️ Управление")
589
- col1, col2 = st.columns(2)
590
- with col1:
591
- if st.button("🔄 Переобучить AI на диалогах"):
592
- dialog_ai.train_on_dialogs()
593
- st.success("AI переобучен!")
594
- st.rerun()
595
- with col2:
596
- if st.button("🗑️ Очистить кэш"):
597
- clear_cache()
598
- st.success("Кэш очищен! Перезапустите приложение.")
599
- st.rerun()
600
 
601
- st.divider()
 
 
 
602
 
603
- # Логи
604
- st.subheader("📋 Последние запросы")
605
- if query_logs:
606
- df = pd.DataFrame(query_logs[-10:])
607
- df["timestamp"] = pd.to_datetime(df["timestamp"]).dt.strftime("%H:%M:%S")
608
- st.dataframe(
609
- df[["timestamp", "query", "results_count", "response_time"]],
610
- column_config={
611
- "timestamp": "Время",
612
- "query": "Запрос",
613
- "results_count": "Результатов",
614
- "response_time": "Время (с)"
615
- },
616
- use_container_width=True,
617
- hide_index=True
618
- )
619
-
620
- if st.button("🗑️ Очистить логи", type="secondary"):
621
- clear_logs()
622
- st.success("Логи очищены!")
623
- st.rerun()
624
- else:
625
- st.info("📭 Логов пока нет")
626
 
627
  # --- ОСНОВНАЯ ЧАСТЬ ---
628
  st.title(f"🧪 {AI_NAME} - Научный AI-ассистент")
629
- st.markdown(f"**{AI_NAME}** — интеллектуальный помощник от **{COMPANY_NAME}** для работы с научными статьями")
630
 
631
- # Приветствие от AI
632
  if "greeting_shown" not in st.session_state:
633
  st.session_state.greeting_shown = True
634
- greeting = dialog_ai.generate_response("Привет")
635
- st.success(f"🤖 **{AI_NAME}:** {greeting}")
636
 
637
- # Информация о загрузке
638
- st.info(f"📚 Загружено {len(articles)} научных статей из датасета ru_science")
639
- if os.path.exists(EMBEDDINGS_FILE):
640
- size = os.path.getsize(EMBEDDINGS_FILE) / (1024 * 1024)
641
- st.info(f"💾 Эмбеддинги загружены с диска ({size:.2f} MB)")
642
 
643
  # Поиск
644
  query = st.text_input(
645
- "🔍 Что хочешь узнать из научных статей?",
646
  placeholder="Например: Как удобрения влияют на урожайность?",
647
  key="query_input"
648
  )
649
 
650
- col1, col2 = st.columns([1, 5])
651
- with col1:
652
- search_clicked = st.button("🔎 Найти", type="primary", use_container_width=True)
653
-
654
- # Выполняем поиск
655
- if query and (search_clicked or query != st.session_state.get("last_query", "")):
656
- st.session_state.last_query = query
657
-
658
- with st.spinner("🔎 Ищу ответ в научных статьях..."):
659
  results = search_science(query, articles, embeddings, embedder)
660
 
661
  if results:
662
  for i, result in enumerate(results, 1):
663
  with st.expander(f"#{i} {result['title']} (сходство: {result['score']:.2f})", expanded=i==1):
664
  st.write(result['text'] + "...")
665
- st.caption(f"📌 Источник: {result['source']}")
666
  else:
667
- st.warning("😕 Не нашёл подходящих научных статей. Попробуй уточнить вопрос.")
668
-
669
- elif not query:
670
- st.info("💡 Напиши вопрос, и я найду ответ в научных статьях")
671
-
672
- # ===================================================================
673
- # 6. ДОПОЛНИТЕЛЬНЫЕ ВОЗМОЖНОСТИ
674
- # ===================================================================
675
 
676
  # Подвал
677
  st.divider()
678
- st.caption(f"🧪 {AI_NAME} от {COMPANY_NAME} | Создан в {CREATION_DATE} | На основе датасета ru_science")
679
-
680
- # Кнопка смены темы
681
- if st.button("🎨 Сменить тему"):
682
- st.session_state.theme = "dark" if st.session_state.get("theme") != "dark" else "light"
683
- st.rerun()
684
-
685
- # Информация о датасете
686
- with st.expander("📖 О датасете ru_science"):
687
- st.markdown("""
688
- **ru_science** — это датасет на Hugging Face, содержащий русскоязычные научные статьи.
689
-
690
- ### Особенности:
691
- - Научные статьи по сельскому хозя��ству, биологии, химии
692
- - Русскоязычные тексты
693
- - Подходит для обучения AI-моделей
694
- - Используется для поиска и анализа научной информации
695
-
696
- **Источник:** [RafaelUI/ru_science](https://huggingface.co/datasets/RafaelUI/ru_science)
697
- """)
 
19
  warnings.filterwarnings('ignore')
20
 
21
  # ===================================================================
22
+ # 1. НАСТРОЙКИ (ОПТИМИЗИРОВАННЫЕ)
23
  # ===================================================================
24
 
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
+ # Если нужно точнее, можно использовать: "BAAI/bge-large-en-v1.5" (медленнее)
35
+ SCIENCE_DATASET = "RafaelUI/ru_science"
36
+ ARTICLE_LIMIT = 100 # БЫСТРЫЙ СТАРТ - 100 статей (можно увеличить позже)
37
+ MAX_LENGTH = 256 # Уменьшаем для скорости
38
+ BATCH_SIZE = 32 # Увеличиваем для скорости
39
+ EPOCHS = 2
40
 
41
  ADMIN_USER = "admin"
42
  ADMIN_PASS = "hfpassword21"
 
54
  HUGGINGFACE = "https://huggingface.co/OpenRussianAI"
55
  CREATION_DATE = "2026"
56
 
57
+ # Обучающие диалоги
58
  TRAINING_DIALOGS = [
59
  {
60
  "context": "Привет",
 
62
  },
63
  {
64
  "context": "Кто ты",
65
+ "response": "Меня зовут OpenAirAI. Я — AI-ассистент, созданный компанией OpenRussianAI в 2026 году командой разработчиков Грибков Евгений и RootLinux21."
66
  },
67
  {
68
  "context": "Кто тебя создал",
69
+ "response": "Меня создала команда OpenRussianAI в составе Грибкова Евгения и RootLinux21 в 2026 году."
 
 
 
 
 
 
 
 
 
 
 
 
70
  },
71
  {
72
  "context": "Что ты умеешь",
73
+ "response": "Я умею анализировать научные статьи, находить информацию, помогать с исследованиями в области сельского хозяйства, биологии, химии."
74
  },
75
  {
76
  "context": "Где ваш сайт",
77
+ "response": f"Сайт OpenRussianAI: {WEBSITE}"
 
 
 
 
 
 
 
 
78
  },
79
  {
80
+ "context": "Где ваши модели",
81
+ "response": f"Модели OpenRussianAI на Hugging Face: {HUGGINGFACE}"
 
 
 
 
 
 
 
 
 
 
 
 
82
  },
83
  {
84
  "context": "Спасибо",
85
+ "response": "Всегда рад помочь! Я, OpenAirAI, здесь для вас. Обращайтесь! 😊"
86
  },
87
  {
88
  "context": "Пока",
89
+ "response": "До свидания! OpenAirAI всегда на связи. Удачи в исследованиях! 👋"
 
 
 
 
90
  }
91
  ]
92
 
 
106
  query_logs = []
107
 
108
  # ===================================================================
109
+ # 2. МОДЕЛЬ ДЛЯ ДИАЛОГОВ
110
  # ===================================================================
111
 
112
  class DialogModel(nn.Module):
 
113
  def __init__(self, pretrained_name, num_labels=2):
114
  super().__init__()
115
  self.bert = AutoModel.from_pretrained(pretrained_name)
 
124
  return logits
125
 
126
  class OpenAirAI:
 
 
127
  def __init__(self):
128
  self.name = AI_NAME
129
  self.company = COMPANY_NAME
 
134
  self.model = None
135
  self.is_trained = False
136
  self.model_path = DIALOG_MODEL_PATH
 
 
 
 
 
 
137
  self.contexts = [d["context"] for d in TRAINING_DIALOGS]
138
  self.responses = [d["response"] for d in TRAINING_DIALOGS]
 
139
 
140
  def train_on_dialogs(self):
 
141
  with st.spinner(f"🧠 Обучаю {self.name} на диалогах..."):
 
 
 
 
142
  self.model = DialogModel(MODEL_NAME).to(self.device)
 
 
143
  optimizer = AdamW(self.model.parameters(), lr=2e-5)
144
 
 
 
 
 
 
145
  self.model.train()
146
  progress_bar = st.progress(0)
 
147
 
148
  for epoch in range(EPOCHS):
149
  total_loss = 0
150
+ for i in range(0, len(self.contexts), BATCH_SIZE):
151
+ batch_contexts = self.contexts[i:i+BATCH_SIZE]
 
 
 
 
 
 
152
  encodings = self.tokenizer(
153
  batch_contexts,
154
  truncation=True,
 
159
 
160
  input_ids = encodings['input_ids'].to(self.device)
161
  attention_mask = encodings['attention_mask'].to(self.device)
 
 
162
  labels = torch.tensor([1] * len(batch_contexts)).to(self.device)
163
 
 
164
  optimizer.zero_grad()
165
  logits = self.model(input_ids, attention_mask)
166
  loss = F.cross_entropy(logits, labels)
 
 
167
  loss.backward()
 
168
  optimizer.step()
169
 
170
  total_loss += loss.item()
171
+ progress_bar.progress((epoch + i/len(self.contexts)) / EPOCHS)
 
 
 
 
 
 
 
 
 
 
172
 
173
+ st.write(f"Эпоха {epoch+1}/{EPOCHS}, Потери: {total_loss/len(self.contexts):.4f}")
 
174
 
 
175
  torch.save(self.model.state_dict(), self.model_path)
176
  self.is_trained = True
177
+ st.success(f"✅ {self.name} обучен!")
 
 
 
 
 
178
 
179
  def load_model(self):
 
180
  if os.path.exists(self.model_path):
181
  try:
182
  self.model = DialogModel(MODEL_NAME).to(self.device)
 
184
  self.model.eval()
185
  self.is_trained = True
186
  return True
187
+ except:
 
188
  return False
189
  return False
190
 
191
  def generate_response(self, query):
192
+ if not self.is_trained:
193
+ return f"Я {self.name}, научный AI-ассистент от {self.company}. Чем могу помочь?"
 
194
 
 
195
  self.model.eval()
196
  with torch.no_grad():
 
197
  encodings = self.tokenizer(
198
  query,
199
  truncation=True,
 
201
  max_length=MAX_LENGTH,
202
  return_tensors='pt'
203
  )
 
204
  input_ids = encodings['input_ids'].to(self.device)
205
  attention_mask = encodings['attention_mask'].to(self.device)
 
 
206
  outputs = self.model.bert(input_ids=input_ids, attention_mask=attention_mask)
207
  query_embedding = outputs.pooler_output
208
 
 
209
  context_embeddings = []
210
  for context in self.contexts:
211
  ctx_enc = self.tokenizer(
 
220
  ctx_outputs = self.model.bert(input_ids=ctx_input_ids, attention_mask=ctx_attention_mask)
221
  context_embeddings.append(ctx_outputs.pooler_output)
222
 
 
223
  context_embeddings = torch.cat(context_embeddings, dim=0)
224
  similarities = F.cosine_similarity(query_embedding, context_embeddings)
225
  best_idx = torch.argmax(similarities).item()
226
 
 
227
  if similarities[best_idx] > 0.5:
228
  return self.responses[best_idx]
229
  else:
230
+ return f"Я {self.name}, научный AI-ассистент от {self.company}. Создан в {self.creation_date}. Чем могу помочь?"
 
231
 
232
  # ===================================================================
233
+ # 3. БЫСТРАЯ ЗАГРУЗКА НАУЧНЫХ СТАТЕЙ
234
  # ===================================================================
235
 
236
  @st.cache_resource
237
  def load_science_articles():
 
238
  articles_file = ARTICLES_FILE
239
 
 
240
  if os.path.exists(articles_file):
241
  with st.spinner("📚 Загружаю научные статьи с диска..."):
242
  with open(articles_file, 'rb') as f:
243
  return pickle.load(f)
244
 
245
+ with st.spinner("📚 Загружаю научные статьи (первый раз, ~1-2 минуты)..."):
246
  try:
247
  dataset = load_dataset(SCIENCE_DATASET, split="train", streaming=True)
248
  articles = []
249
  for i, row in enumerate(dataset):
250
  if i >= ARTICLE_LIMIT:
251
  break
 
252
  text = row.get('content', '') or row.get('text', '') or str(row)
253
+ title = row.get('title', f"Статья {i}")
 
254
  articles.append({
255
  "id": i,
256
  "title": title[:200],
257
+ "text": text[:2000], # Уменьшаем для скорости
258
  "source": "ru_science"
259
  })
260
 
 
261
  with open(articles_file, 'wb') as f:
262
  pickle.dump(articles, f)
263
 
264
  return articles
265
  except Exception as e:
266
+ st.error(f"Ошибка: {e}")
267
+ return create_test_articles()
 
 
 
 
268
 
269
+ def create_test_articles():
 
270
  return [
271
  {
272
  "id": 1,
273
  "title": "Влияние удобрений на рост растений",
274
+ "text": "Исследование показывает, что применение азотных удобрений увеличивает урожайность.",
275
  "source": "test"
276
  },
277
  {
278
  "id": 2,
279
+ "title": "Методы биоконверсии",
280
+ "text": "Биоконверсия позволяет перерабатывать органические отходы в удобрения.",
 
 
 
 
 
 
281
  "source": "test"
282
  }
283
  ]
284
 
285
  @st.cache_resource
286
  def load_embedder():
 
287
  with st.spinner("🧠 Загружаю модель для эмбеддингов..."):
288
  return SentenceTransformer(EMBEDDING_MODEL)
289
 
290
  @st.cache_resource
291
  def create_embeddings(_articles, _embedder):
 
292
  embeddings_file = EMBEDDINGS_FILE
293
 
 
294
  if os.path.exists(embeddings_file):
295
  with st.spinner("📊 Загружаю эмбеддинги с диска..."):
296
  return np.load(embeddings_file)
297
 
298
+ with st.spinner(f"🔢 Создаю эмбеддинги для {len(_articles)} статей (1-2 минуты)..."):
299
  texts = [f"{a['title']}\n\n{a['text']}" for a in _articles]
300
  embeddings = _embedder.encode(
301
  texts,
302
  normalize_embeddings=True,
303
  show_progress_bar=True,
304
+ batch_size=64, # Увеличен для скорости
305
+ device='cuda' if torch.cuda.is_available() else 'cpu'
306
  )
307
 
 
308
  np.save(embeddings_file, embeddings)
 
309
  return embeddings
310
 
311
  # ===================================================================
312
+ # 4. ФУНКЦИИ ПОИСКА
313
  # ===================================================================
314
 
315
  def search_science(query, _articles, _embeddings, _embedder):
 
316
  if not query:
317
  return None
 
 
318
 
319
+ start_time = time.time()
320
  query_vector = _embedder.encode([query], normalize_embeddings=True)[0]
321
  scores = _embeddings @ query_vector
322
  top_indices = np.argsort(-scores)[:3]
323
+
324
  results = []
325
  for idx in top_indices:
326
  score = float(scores[int(idx)])
327
+ if score > 0.2:
328
  article = _articles[int(idx)]
329
  results.append({
330
  "title": article['title'],
 
333
  "source": article.get('source', 'ru_science')
334
  })
335
 
 
336
  log_entry = {
337
  "timestamp": datetime.now().isoformat(),
338
  "query": query,
 
340
  "response_time": round(time.time() - start_time, 2)
341
  }
342
  query_logs.append(log_entry)
 
 
343
  with open(LOG_FILE, "w") as f:
344
  json.dump(query_logs[-100:], f)
345
 
346
  return results
347
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  def clear_cache():
349
+ files = [EMBEDDINGS_FILE, ARTICLES_FILE, DIALOG_MODEL_PATH]
350
+ for file in files:
 
351
  if os.path.exists(file):
352
  os.remove(file)
353
  st.cache_resource.clear()
 
357
  # 5. ИНТЕРФЕЙС
358
  # ===================================================================
359
 
360
+ # ЗАГРУЗКА (быстрая)
361
  articles = load_science_articles()
362
  embedder = load_embedder()
363
  embeddings = create_embeddings(articles, embedder)
364
 
365
+ # Инициализация AI
366
  if 'dialog_ai' not in st.session_state:
367
  st.session_state.dialog_ai = OpenAirAI()
368
  if not st.session_state.dialog_ai.load_model():
 
370
 
371
  dialog_ai = st.session_state.dialog_ai
372
 
373
+ # --- БОКОВАЯ ПАНЕЛЬ ---
374
  with st.sidebar:
375
  st.image("https://cdn-icons-png.flaticon.com/512/4248/4248455.png", width=80)
376
+ st.title(f"👑 {AI_NAME}")
377
 
 
 
378
  st.markdown(f"""
379
+ **{dialog_ai.name}** | {dialog_ai.creation_date}
380
 
381
  **Компания:** {dialog_ai.company}
 
382
  **Разработчики:** {', '.join(dialog_ai.creators)}
383
 
384
+ [🌐 Сайт]({WEBSITE})
385
+ [🤗 HF]({HUGGINGFACE})
 
386
  """)
387
 
388
  st.divider()
389
 
390
+ # Админка
391
  if "logged_in" not in st.session_state:
392
  st.session_state.logged_in = False
393
 
 
395
  with st.form("login_form"):
396
  username = st.text_input("👤 Логин", placeholder="admin")
397
  password = st.text_input("🔑 Пароль", type="password", placeholder="hfpassword21")
398
+ if st.form_submit_button("🔑 Войти"):
 
 
399
  if username == ADMIN_USER and password == ADMIN_PASS:
400
  st.session_state.logged_in = True
 
401
  st.rerun()
402
  else:
403
+ st.error("❌ Неверно")
404
  else:
405
+ st.success("✅ Админ")
 
406
  if st.button("🚪 Выйти"):
407
  st.session_state.logged_in = False
408
  st.rerun()
409
 
410
+ if st.button("🔄 Переобучить AI"):
411
+ dialog_ai.train_on_dialogs()
412
+ st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
 
414
+ if st.button("🗑️ Очистить кэш"):
415
+ clear_cache()
416
+ st.success("Кэш очищен!")
417
+ st.rerun()
418
 
419
+ # Статистика
420
+ st.metric("Всего запросов", len(query_logs))
421
+ st.metric("Статей", len(articles))
422
+ if os.path.exists(EMBEDDINGS_FILE):
423
+ size = os.path.getsize(EMBEDDINGS_FILE) / (1024 * 1024)
424
+ st.metric("Эмбеддинги", f"{size:.1f} MB")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
 
426
  # --- ОСНОВНАЯ ЧАСТЬ ---
427
  st.title(f"🧪 {AI_NAME} - Научный AI-ассистент")
428
+ st.markdown(f"**{AI_NAME}** от **{COMPANY_NAME}** | Работает с научными статьями")
429
 
430
+ # Приветствие
431
  if "greeting_shown" not in st.session_state:
432
  st.session_state.greeting_shown = True
433
+ st.success(f"🤖 **{AI_NAME}:** {dialog_ai.generate_response('Привет')}")
 
434
 
435
+ st.info(f"📚 {len(articles)} научных статей загружено")
 
 
 
 
436
 
437
  # Поиск
438
  query = st.text_input(
439
+ "🔍 Что хочешь узнать?",
440
  placeholder="Например: Как удобрения влияют на урожайность?",
441
  key="query_input"
442
  )
443
 
444
+ if query:
445
+ with st.spinner("🔎 Ищу..."):
 
 
 
 
 
 
 
446
  results = search_science(query, articles, embeddings, embedder)
447
 
448
  if results:
449
  for i, result in enumerate(results, 1):
450
  with st.expander(f"#{i} {result['title']} (сходство: {result['score']:.2f})", expanded=i==1):
451
  st.write(result['text'] + "...")
452
+ st.caption(f"📌 {result['source']}")
453
  else:
454
+ st.warning("😕 Не нашёл подходящих статей")
455
+ else:
456
+ st.info("💡 Напиши вопрос о науке")
 
 
 
 
 
457
 
458
  # Подвал
459
  st.divider()
460
+ st.caption(f"🧪 {AI_NAME} от {COMPANY_NAME} | {CREATION_DATE} | ru_science")