Rulga commited on
Commit
18a6e02
·
1 Parent(s): 6adc1c2

Added a web interface for managing models and launching additional training

Browse files
Files changed (2) hide show
  1. training/model_manager.py +423 -1
  2. web/training_interface.py +223 -0
training/model_manager.py CHANGED
@@ -106,4 +106,426 @@ class ModelManager:
106
  # Обновляем существующую запись
107
  self.registry["models"][i] = model_entry
108
  self.save_registry()
109
- return True, f"Мод
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  # Обновляем существующую запись
107
  self.registry["models"][i] = model_entry
108
  self.save_registry()
109
+ return True, f"Модель {model_id} версии {version} обновлена в реестре"
110
+
111
+ # Если модель новая, добавляем ее в реестр
112
+ self.registry["models"].append(model_entry)
113
+
114
+ # Если модель отмечена как активная, деактивируем все другие модели с тем же model_id
115
+ if is_active:
116
+ for i, model in enumerate(self.registry["models"]):
117
+ if model["model_id"] == model_id and model["version"] != version:
118
+ self.registry["models"][i]["is_active"] = False
119
+
120
+ self.save_registry()
121
+ return True, f"Модель {model_id} версии {version} добавлена в реестр"
122
+ except Exception as e:
123
+ return False, f"Ошибка при регистрации модели: {str(e)}"
124
+
125
+ def download_model(
126
+ self,
127
+ model_id: str,
128
+ version: str,
129
+ token: Optional[str] = None
130
+ ) -> Tuple[bool, str]:
131
+ """
132
+ Загрузка модели из Hugging Face Hub
133
+
134
+ Args:
135
+ model_id: Идентификатор модели
136
+ version: Версия модели
137
+ token: Токен доступа к Hugging Face Hub
138
+
139
+ Returns:
140
+ (успех, сообщение)
141
+ """
142
+ try:
143
+ # Находим модель в реестре
144
+ model_entry = None
145
+ for model in self.registry["models"]:
146
+ if model["model_id"] == model_id and model["version"] == version:
147
+ model_entry = model
148
+ break
149
+
150
+ if model_entry is None:
151
+ return False, f"Модель {model_id} версии {version} не найдена в реестре"
152
+
153
+ # Проверяем, что источник - это репозиторий Hugging Face
154
+ if not model_entry["source"].startswith("hf://"):
155
+ return False, "Источник модели не является репозиторием Hugging Face"
156
+
157
+ # Извлекаем имя репозитория
158
+ repo_id = model_entry["source"][5:]
159
+
160
+ # Путь для сохранения модели
161
+ local_path = model_entry["local_path"]
162
+
163
+ # Проверяем, существует ли уже директория с моделью
164
+ if os.path.exists(local_path):
165
+ # Если директория существует, проверяем наличие файлов модели
166
+ if os.path.exists(os.path.join(local_path, "pytorch_model.bin")) or \
167
+ os.path.exists(os.path.join(local_path, "adapter_model.bin")):
168
+ return True, f"Модель {model_id} версии {version} уже загружена"
169
+ else:
170
+ # Создаем директорию для модели
171
+ os.makedirs(local_path, exist_ok=True)
172
+
173
+ # Загружаем модель
174
+ logger.info(f"Загрузка модели {repo_id} в {local_path}...")
175
+ snapshot_download(
176
+ repo_id=repo_id,
177
+ local_dir=local_path,
178
+ token=token
179
+ )
180
+
181
+ return True, f"Модель {model_id} версии {version} успешно загружена"
182
+ except Exception as e:
183
+ return False, f"Ошибка при загрузке модели: {str(e)}"
184
+
185
+ def get_active_model(self, model_id: str) -> Optional[Dict[str, Any]]:
186
+ """
187
+ Получение активной версии модели
188
+
189
+ Args:
190
+ model_id: Идентификатор модели
191
+
192
+ Returns:
193
+ Словарь с информацией о модели или None, если модель не найдена
194
+ """
195
+ for model in self.registry["models"]:
196
+ if model["model_id"] == model_id and model.get("is_active", False):
197
+ return model
198
+ return None
199
+
200
+ def set_active_model(self, model_id: str, version: str) -> Tuple[bool, str]:
201
+ """
202
+ Установка активной версии модели
203
+
204
+ Args:
205
+ model_id: Идентификатор модели
206
+ version: Версия модели
207
+
208
+ Returns:
209
+ (успех, сообщение)
210
+ """
211
+ try:
212
+ # Проверяем, есть ли модель в реестре
213
+ model_found = False
214
+ for i, model in enumerate(self.registry["models"]):
215
+ if model["model_id"] == model_id:
216
+ if model["version"] == version:
217
+ model_found = True
218
+ self.registry["models"][i]["is_active"] = True
219
+ else:
220
+ self.registry["models"][i]["is_active"] = False
221
+
222
+ if not model_found:
223
+ return False, f"Модель {model_id} версии {version} не найдена в реестре"
224
+
225
+ self.save_registry()
226
+ return True, f"Модель {model_id} версии {version} установлена как активная"
227
+ except Exception as e:
228
+ return False, f"Ошибка при установке активной модели: {str(e)}"
229
+
230
+ def load_model(
231
+ self,
232
+ model_id: str,
233
+ version: Optional[str] = None,
234
+ device: str = "cuda" if os.environ.get("CUDA_VISIBLE_DEVICES") else "cpu"
235
+ ) -> Tuple[bool, Any, Any, str]:
236
+ """
237
+ Загрузка модели и токенизатора
238
+
239
+ Args:
240
+ model_id: Идентификатор модели
241
+ version: Версия модели (если None, загружается активная версия)
242
+ device: Устройство для загрузки модели
243
+
244
+ Returns:
245
+ (успех, модель, токенизатор, сообщение)
246
+ """
247
+ try:
248
+ # Определяем версию модели
249
+ if version is None:
250
+ model_entry = self.get_active_model(model_id)
251
+ if model_entry is None:
252
+ return False, None, None, f"Активная версия модели {model_id} не найдена"
253
+ else:
254
+ model_entry = None
255
+ for model in self.registry["models"]:
256
+ if model["model_id"] == model_id and model["version"] == version:
257
+ model_entry = model
258
+ break
259
+
260
+ if model_entry is None:
261
+ return False, None, None, f"Модель {model_id} версии {version} не найдена в реестре"
262
+
263
+ # Проверяем, загружена ли модель локально
264
+ local_path = model_entry["local_path"]
265
+ if not os.path.exists(local_path) or \
266
+ (not os.path.exists(os.path.join(local_path, "pytorch_model.bin")) and \
267
+ not os.path.exists(os.path.join(local_path, "adapter_model.bin"))):
268
+ # Если модель не загружена, пытаемся загрузить её
269
+ success, message = self.download_model(model_id, model_entry["version"])
270
+ if not success:
271
+ return False, None, None, message
272
+
273
+ # Загружаем токенизатор
274
+ logger.info(f"Загрузка токенизатора из {local_path}...")
275
+ tokenizer = AutoTokenizer.from_pretrained(
276
+ local_path,
277
+ trust_remote_code=True
278
+ )
279
+
280
+ # Загружаем модель
281
+ logger.info(f"Загрузка модели из {local_path}...")
282
+ model = AutoModelForCausalLM.from_pretrained(
283
+ local_path,
284
+ trust_remote_code=True,
285
+ device_map="auto" if device == "cuda" else None
286
+ )
287
+
288
+ return True, model, tokenizer, f"Модель {model_id} версии {model_entry['version']} успешно загружена"
289
+ except Exception as e:
290
+ return False, None, None, f"Ошибка при загрузке модели: {str(e)}"
291
+
292
+ def delete_model(self, model_id: str, version: str) -> Tuple[bool, str]:
293
+ """
294
+ Удаление модели из реестра и локального хранилища
295
+
296
+ Args:
297
+ model_id: Идентификатор модели
298
+ version: Версия модели
299
+
300
+ Returns:
301
+ (успех, сообщение)
302
+ """
303
+ try:
304
+ # Ищем модель в реестре
305
+ model_entry = None
306
+ model_index = -1
307
+ for i, model in enumerate(self.registry["models"]):
308
+ if model["model_id"] == model_id and model["version"] == version:
309
+ model_entry = model
310
+ model_index = i
311
+ break
312
+
313
+ if model_entry is None:
314
+ return False, f"Модель {model_id} версии {version} не найдена в реестре"
315
+
316
+ # Проверяем, активна ли модель
317
+ if model_entry.get("is_active", False):
318
+ return False, "Нельзя удалить активную модель. Сначала установите другую модель как активную."
319
+
320
+ # Удаляем директорию с моделью, если она существует
321
+ local_path = model_entry["local_path"]
322
+ if os.path.exists(local_path):
323
+ shutil.rmtree(local_path)
324
+
325
+ # Удаляем модель из реестра
326
+ self.registry["models"].pop(model_index)
327
+ self.save_registry()
328
+
329
+ return True, f"Модель {model_id} версии {version} успешно удалена"
330
+ except Exception as e:
331
+ return False, f"Ошибка при удалении модели: {str(e)}"
332
+
333
+ def list_models(self, model_id: Optional[str] = None) -> List[Dict[str, Any]]:
334
+ """
335
+ Получение списка моделей в реестре
336
+
337
+ Args:
338
+ model_id: Идентификатор модели для фильтрации (если None, возвращаются все модели)
339
+
340
+ Returns:
341
+ Список словарей с информацией о моделях
342
+ """
343
+ if model_id is None:
344
+ return self.registry["models"]
345
+ else:
346
+ return [model for model in self.registry["models"] if model["model_id"] == model_id]
347
+
348
+ def import_local_model(
349
+ self,
350
+ source_path: str,
351
+ model_id: str,
352
+ version: str,
353
+ description: str = "",
354
+ is_active: bool = False
355
+ ) -> Tuple[bool, str]:
356
+ """
357
+ Импорт локальной модели в реестр
358
+
359
+ Args:
360
+ source_path: Путь к директории с моделью
361
+ model_id: Идентификатор модели
362
+ version: Версия модели
363
+ description: Описание модели
364
+ is_active: Флаг активности модели
365
+
366
+ Returns:
367
+ (успех, сообщение)
368
+ """
369
+ try:
370
+ # Проверяем существование исходной директории
371
+ if not os.path.exists(source_path):
372
+ return False, f"Директория {source_path} не существует"
373
+
374
+ # Проверяем, что это директория с моделью
375
+ if not os.path.exists(os.path.join(source_path, "config.json")):
376
+ return False, f"Директория {source_path} не содержит модель трансформера"
377
+
378
+ # Создаем путь для модели в нашем хранилище
379
+ target_path = os.path.join(self.models_dir, f"{model_id}_{version}")
380
+
381
+ # Если директория уже существует, удаляем ее
382
+ if os.path.exists(target_path):
383
+ shutil.rmtree(target_path)
384
+
385
+ # Копируем файлы модели
386
+ shutil.copytree(source_path, target_path)
387
+
388
+ # Регистрируем модель в реестре
389
+ success, message = self.register_model(
390
+ model_id=model_id,
391
+ version=version,
392
+ source=f"local://{source_path}",
393
+ description=description,
394
+ is_active=is_active
395
+ )
396
+
397
+ if not success:
398
+ # Если регистрация не удалась, удаляем скопированные файлы
399
+ shutil.rmtree(target_path)
400
+ return False, message
401
+
402
+ return True, f"Модель успешно импортирована: {model_id} версии {version}"
403
+ except Exception as e:
404
+ return False, f"Ошибка при импорте модели: {str(e)}"
405
+
406
+ def export_model_metrics(self, output_file: str) -> Tuple[bool, str]:
407
+ """
408
+ Экспорт метрик всех моделей в JSON файл
409
+
410
+ Args:
411
+ output_file: Путь к выходному файлу
412
+
413
+ Returns:
414
+ (успех, сообщение)
415
+ """
416
+ try:
417
+ # Создаем словарь с метриками для каждой модели
418
+ metrics_data = {}
419
+
420
+ for model in self.registry["models"]:
421
+ model_key = f"{model['model_id']}_{model['version']}"
422
+ metrics_data[model_key] = {
423
+ "model_id": model["model_id"],
424
+ "version": model["version"],
425
+ "is_active": model.get("is_active", False),
426
+ "registration_date": model.get("registration_date", ""),
427
+ "metrics": model.get("metrics", {})
428
+ }
429
+
430
+ # Сохраняем в файл
431
+ with open(output_file, "w", encoding="utf-8") as f:
432
+ json.dump(metrics_data, f, ensure_ascii=False, indent=2)
433
+
434
+ return True, f"Метрики моделей успешно экспортированы в {output_file}"
435
+ except Exception as e:
436
+ return False, f"Ошибка при экспорте метрик: {str(e)}"
437
+
438
+ def update_model_metrics(
439
+ self,
440
+ model_id: str,
441
+ version: str,
442
+ metrics: Dict[str, Any]
443
+ ) -> Tuple[bool, str]:
444
+ """
445
+ Обновление метрик модели
446
+
447
+ Args:
448
+ model_id: Идентификатор модели
449
+ version: Версия модели
450
+ metrics: Словарь с метриками
451
+
452
+ Returns:
453
+ (успех, сообщение)
454
+ """
455
+ try:
456
+ # Ищем модель в реестре
457
+ model_found = False
458
+ for i, model in enumerate(self.registry["models"]):
459
+ if model["model_id"] == model_id and model["version"] == version:
460
+ # Обновляем метрики
461
+ self.registry["models"][i]["metrics"] = metrics
462
+ model_found = True
463
+ break
464
+
465
+ if not model_found:
466
+ return False, f"Модель {model_id} версии {version} не найдена в реестре"
467
+
468
+ self.save_registry()
469
+ return True, f"Метрики модели {model_id} версии {version} успешно обновлены"
470
+ except Exception as e:
471
+ return False, f"Ошибка при обновлении метрик: {str(e)}"
472
+
473
+ def get_model(
474
+ model_id: str = "saiga",
475
+ version: Optional[str] = None,
476
+ device: str = "cuda" if os.environ.get("CUDA_VISIBLE_DEVICES") else "cpu"
477
+ ) -> Tuple[Any, Any, Dict[str, Any]]:
478
+ """
479
+ Удобная функция для получения модели и токенизатора
480
+
481
+ Args:
482
+ model_id: Идентификатор модели
483
+ version: Версия модели (если None, загружается активная версия)
484
+ device: Устройство для загрузки модели
485
+
486
+ Returns:
487
+ (модель, токенизатор, информация о модели)
488
+ """
489
+ manager = ModelManager()
490
+ success, model, tokenizer, message = manager.load_model(
491
+ model_id=model_id,
492
+ version=version,
493
+ device=device
494
+ )
495
+
496
+ if not success:
497
+ logger.error(message)
498
+ raise ValueError(message)
499
+
500
+ # Получаем информацию о загруженной модели
501
+ if version is None:
502
+ model_info = manager.get_active_model(model_id)
503
+ else:
504
+ for m in manager.list_models(model_id):
505
+ if m["version"] == version:
506
+ model_info = m
507
+ break
508
+ else:
509
+ model_info = {}
510
+
511
+ return model, tokenizer, model_info
512
+
513
+ if __name__ == "__main__":
514
+ # Пример использования
515
+ manager = ModelManager()
516
+
517
+ # Регистрация базовой модели
518
+ success, message = manager.register_model(
519
+ model_id="saiga",
520
+ version="7b",
521
+ source="hf://IlyaGusev/saiga_7b_lora",
522
+ description="Базовая модель Saiga 7B с LoRA адаптерами",
523
+ is_active=True
524
+ )
525
+ print(message)
526
+
527
+ # Вывод списка моделей
528
+ models = manager.list_models()
529
+ print(f"В реестре {len(models)} моделей:")
530
+ for model in models:
531
+ print(f" - {model['model_id']} v{model['version']}: {model['description']}")
web/training_interface.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Веб-интерфейс для управления моделями и запуска дообучения
3
+ """
4
+
5
+ import os
6
+ import json
7
+ import gradio as gr
8
+ import pandas as pd
9
+ from datetime import datetime
10
+ from typing import List, Dict, Any, Tuple, Optional
11
+
12
+ from src.analytics.chat_analyzer import ChatAnalyzer
13
+ from src.training.fine_tuner import FineTuner, finetune_from_chat_history
14
+ from src.training.model_manager import ModelManager
15
+ from config.settings import MODEL_PATH, TRAINING_OUTPUT_DIR
16
+
17
+ # Инициализация менеджеров
18
+ model_manager = ModelManager()
19
+ chat_analyzer = ChatAnalyzer()
20
+
21
+ def get_models_df():
22
+ """
23
+ Получение датафрейма с моделями из реестра
24
+
25
+ Returns:
26
+ pandas.DataFrame: Датафрейм с моделями
27
+ """
28
+ models = model_manager.list_models()
29
+
30
+ if not models:
31
+ return pd.DataFrame(columns=["model_id", "version", "description", "is_active", "registration_date"])
32
+
33
+ # Создаем датафрейм
34
+ df = pd.DataFrame(models)
35
+
36
+ # Выбираем нужные колонки
37
+ columns = ["model_id", "version", "description", "is_active", "registration_date"]
38
+ df = df[columns]
39
+
40
+ # Сортируем по model_id и registration_date
41
+ df = df.sort_values(by=["model_id", "registration_date"], ascending=[True, False])
42
+
43
+ return df
44
+
45
+ def generate_chat_analysis():
46
+ """
47
+ Генерация аналитического отчета по истории чатов
48
+
49
+ Returns:
50
+ str: HTML-отчет
51
+ """
52
+ report = chat_analyzer.generate_analytics_report()
53
+
54
+ if not report or report.get("total_conversations", 0) == 0:
55
+ return "### Нет данных для анализа\nИстория чатов пуста или не может быть загружена."
56
+
57
+ # Формируем HTML-отчет
58
+ html = f"""
59
+ ### Аналитический отчет по истории чатов
60
+
61
+ #### Основные метрики
62
+ - **Всего диалогов:** {report['total_conversations']}
63
+ - **Пар вопрос-ответ для обучения:** {report['qa_pairs_count']}
64
+ - **Вопросы без ответов:** {report['failed_questions_count']}
65
+
66
+ #### Метрики удовлетворенности
67
+ - **Среднее число сообщений в диалоге:** {report['satisfaction_metrics']['avg_messages_per_conversation']:.2f}
68
+ - **Процент диалогов с дополнительными вопросами:** {report['satisfaction_metrics']['follow_up_questions_rate']:.2f}%
69
+ """
70
+
71
+ # Популярные вопросы
72
+ if report.get('common_questions'):
73
+ html += "\n\n#### Популярные вопросы\n"
74
+ for i, (question, count) in enumerate(report['common_questions'][:10], 1):
75
+ html += f"{i}. \"{question}\" ({count} раз)\n"
76
+
77
+ # Вопросы без ответов
78
+ if report.get('failed_questions'):
79
+ html += "\n\n#### Примеры вопросов без ответов\n"
80
+ for i, question in enumerate(report['failed_questions'][:5], 1):
81
+ html += f"{i}. \"{question}\"\n"
82
+
83
+ return html
84
+
85
+ def register_model_action(model_id, version, source, description, set_active):
86
+ """
87
+ Действие регистрации модели
88
+
89
+ Args:
90
+ model_id: Идентификатор модели
91
+ version: Версия модели
92
+ source: Источник модели
93
+ description: Описание модели
94
+ set_active: Установить как активную
95
+
96
+ Returns:
97
+ str: Результат операции
98
+ """
99
+ # Проверка входных данных
100
+ if not model_id or not version or not source:
101
+ return "Ошибка: все поля обязательны для заполнения"
102
+
103
+ # Регистрация модели
104
+ success, message = model_manager.register_model(
105
+ model_id=model_id,
106
+ version=version,
107
+ source=source,
108
+ description=description,
109
+ is_active=set_active
110
+ )
111
+
112
+ if not success:
113
+ return f"Ошибка: {message}"
114
+
115
+ # Если установлена опция загрузки модели, загружаем её
116
+ if source.startswith("hf://"):
117
+ success, download_message = model_manager.download_model(model_id, version)
118
+ if not success:
119
+ return f"Модель зарегистрирована, но не загружена: {download_message}"
120
+ message += f"\n{download_message}"
121
+
122
+ return message
123
+
124
+ def import_local_model_action(source_path, model_id, version, description, set_active):
125
+ """
126
+ Действие импорта локальной модели
127
+
128
+ Args:
129
+ source_path: Путь к директории с моделью
130
+ model_id: Идентификатор модели
131
+ version: Версия модели
132
+ description: Описание модели
133
+ set_active: Установить как активную
134
+
135
+ Returns:
136
+ str: Результат операции
137
+ """
138
+ # Проверка входных данных
139
+ if not source_path or not model_id or not version:
140
+ return "Ошибка: все поля обязательны для заполнения"
141
+
142
+ # Проверка существования директории
143
+ if not os.path.exists(source_path):
144
+ return f"Ошибка: директория {source_path} не существует"
145
+
146
+ # Импорт модели
147
+ success, message = model_manager.import_local_model(
148
+ source_path=source_path,
149
+ model_id=model_id,
150
+ version=version,
151
+ description=description,
152
+ is_active=set_active
153
+ )
154
+
155
+ return message
156
+
157
+ def set_active_model_action(model_row_index, models_df):
158
+ """
159
+ Действие установки активной модели
160
+
161
+ Args:
162
+ model_row_index: Индекс строки модели в датафрейме
163
+ models_df: Датафрейм с моделями
164
+
165
+ Returns:
166
+ str: Результат операции
167
+ """
168
+ try:
169
+ # Получаем информацию о выбранной модели
170
+ model_row = models_df.iloc[model_row_index]
171
+ model_id = model_row["model_id"]
172
+ version = model_row["version"]
173
+
174
+ # Устанавливаем как активную
175
+ success, message = model_manager.set_active_model(model_id, version)
176
+
177
+ return message
178
+ except Exception as e:
179
+ return f"Ошибка: {str(e)}"
180
+
181
+ def delete_model_action(model_row_index, models_df):
182
+ """
183
+ Действие удаления модели
184
+
185
+ Args:
186
+ model_row_index: Индекс строки модели в датафрейме
187
+ models_df: Датафрейм с моделями
188
+
189
+ Returns:
190
+ str: Результат операции
191
+ """
192
+ try:
193
+ # Получаем информацию о выбранной модели
194
+ model_row = models_df.iloc[model_row_index]
195
+ model_id = model_row["model_id"]
196
+ version = model_row["version"]
197
+
198
+ # Удаляем модель
199
+ success, message = model_manager.delete_model(model_id, version)
200
+
201
+ return message
202
+ except Exception as e:
203
+ return f"Ошибка: {str(e)}"
204
+
205
+ def start_finetune_action(
206
+ epochs,
207
+ batch_size,
208
+ learning_rate,
209
+ base_model_id,
210
+ new_model_id,
211
+ new_version,
212
+ description,
213
+ set_active
214
+ ):
215
+ """
216
+ Действие запуска дообучения модели
217
+
218
+ Args:
219
+ epochs: Количество эпох обучения
220
+ batch_size: Размер батча
221
+ learning_rate: Скорость обучения
222
+ base_model_id: Ид
223
+ """