Rulga commited on
Commit
68997ff
·
1 Parent(s): 3f6bcbe

Refactor dataset loading logic for improved performance and maintainability

Browse files
Files changed (1) hide show
  1. src/knowledge_base/dataset.py +293 -53
src/knowledge_base/dataset.py CHANGED
@@ -1,47 +1,126 @@
 
 
 
 
1
  import os
2
  import json
 
 
3
  from datetime import datetime
4
- from huggingface_hub import HfApi
5
  from config.settings import VECTOR_STORE_PATH
6
 
7
  class DatasetManager:
8
- def __init__(self, dataset_name="Rulga/status-law-knowledge-base"):
9
- self.api = HfApi()
 
 
 
 
 
 
 
10
  self.dataset_name = dataset_name
 
11
 
12
- def init_dataset_structure(self):
13
- """Инициализация структуры датасета на Hugging Face"""
 
 
 
 
 
14
  try:
 
 
 
 
 
 
 
15
  # Создаем пустые .gitkeep файлы для поддержания структуры
16
- self.api.upload_file(
17
- path_or_fileobj=b"",
18
- path_in_repo="vector_store/.gitkeep",
19
- repo_id=self.dataset_name,
20
- repo_type="dataset"
21
- )
22
 
23
- self.api.upload_file(
24
- path_or_fileobj=b"",
25
- path_in_repo="chat_history/.gitkeep",
26
- repo_id=self.dataset_name,
27
- repo_type="dataset"
28
- )
 
 
 
 
 
 
 
 
29
 
30
  return True, "Структура датасета успешно создана"
31
  except Exception as e:
32
  return False, f"Ошибка при создании структуры датасета: {str(e)}"
33
 
34
- def upload_vector_store(self):
35
- """Загрузка векторного хранилища в датасет"""
 
 
 
 
 
36
  try:
37
  # Проверяем наличие файлов
38
  index_path = os.path.join(VECTOR_STORE_PATH, "index.faiss")
39
  config_path = os.path.join(VECTOR_STORE_PATH, "index.pkl")
40
 
41
- if not (os.path.exists(index_path) and os.path.exists(config_path)):
42
- return False, "Файлы векторного хранилища не найдены"
 
 
 
43
 
44
  # Загружаем файлы
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  self.api.upload_file(
46
  path_or_fileobj=index_path,
47
  path_in_repo="vector_store/index.faiss",
@@ -56,73 +135,234 @@ class DatasetManager:
56
  repo_type="dataset"
57
  )
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  return True, "Векторное хранилище успешно загружено"
60
  except Exception as e:
61
  return False, f"Ошибка при загрузке векторного хранилища: {str(e)}"
62
 
63
- def download_vector_store(self):
64
- """Загрузка векторного хранилища из датасета"""
 
 
 
 
 
 
 
 
65
  try:
66
  # Создаем директорию если её нет
67
  os.makedirs(VECTOR_STORE_PATH, exist_ok=True)
68
 
69
- # Загружаем файлы
70
- self.api.hf_hub_download(
71
- repo_id=self.dataset_name,
72
- filename="vector_store/index.faiss",
73
- repo_type="dataset",
74
- local_dir=VECTOR_STORE_PATH
75
- )
76
 
77
- self.api.hf_hub_download(
78
- repo_id=self.dataset_name,
79
- filename="vector_store/index.pkl",
80
- repo_type="dataset",
81
- local_dir=VECTOR_STORE_PATH
82
- )
83
 
84
- return True, "Векторное хранилище успешно загружено"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  except Exception as e:
86
  return False, f"Ошибка при загрузке векторного хранилища: {str(e)}"
87
 
88
- def save_chat_history(self, conversation_id, messages):
89
- """Сохранение истории чата в датасет"""
 
 
 
 
 
 
 
 
 
90
  try:
91
  # Формируем имя файла с временной меткой
92
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
93
  filename = f"chat_history/{conversation_id}_{timestamp}.json"
94
 
95
- # Создаем временный файл
96
  chat_data = {
97
  "conversation_id": conversation_id,
98
  "timestamp": timestamp,
99
  "messages": messages
100
  }
101
 
102
- temp_file = f"temp_{conversation_id}.json"
103
- with open(temp_file, "w", encoding="utf-8") as f:
104
- json.dump(chat_data, f, ensure_ascii=False, indent=2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
- # Загружаем файл в датасет
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  self.api.upload_file(
108
- path_or_fileobj=temp_file,
109
  path_in_repo=filename,
110
  repo_id=self.dataset_name,
111
  repo_type="dataset"
112
  )
113
 
114
- # Удаляем временный файл
115
- os.remove(temp_file)
116
-
117
- return True, "История чата сохранена"
118
  except Exception as e:
119
- return False, f"Ошибка при сохранении истории чата: {str(e)}"
120
 
121
- def test_dataset_connection():
122
- """Тестовая функция для проверки подключения к датасету"""
 
 
 
 
 
 
 
 
123
  try:
124
- manager = DatasetManager()
125
  success, message = manager.init_dataset_structure()
 
 
 
 
126
  print(f"Тест инициализации: {message}")
127
 
128
  return True, "Подключение к датасету работает"
 
1
+ """
2
+ Модуль для управления датасетом на Hugging Face Hub
3
+ """
4
+
5
  import os
6
  import json
7
+ import tempfile
8
+ from typing import Tuple, List, Dict, Any, Optional
9
  from datetime import datetime
10
+ from huggingface_hub import HfApi, HfFolder
11
  from config.settings import VECTOR_STORE_PATH
12
 
13
  class DatasetManager:
14
+ def __init__(self, dataset_name="Rulga/status-law-knowledge-base", token: Optional[str] = None):
15
+ """
16
+ Инициализация менеджера датасетов
17
+
18
+ Args:
19
+ dataset_name: Имя датасета на Hugging Face Hub
20
+ token: Токен доступа к Hugging Face Hub (если не задан, берется из ~/.huggingface/token)
21
+ """
22
+ self.api = HfApi(token=token)
23
  self.dataset_name = dataset_name
24
+ self.token = token if token else HfFolder.get_token()
25
 
26
+ def init_dataset_structure(self) -> Tuple[bool, str]:
27
+ """
28
+ Инициализация структуры датасета на Hugging Face
29
+
30
+ Returns:
31
+ (успех, сообщение)
32
+ """
33
  try:
34
+ # Проверяем существование репозитория
35
+ try:
36
+ self.api.repo_info(repo_id=self.dataset_name, repo_type="dataset")
37
+ except Exception:
38
+ # Если репозиторий не существует, создаем его
39
+ self.api.create_repo(repo_id=self.dataset_name, repo_type="dataset", private=True)
40
+
41
  # Создаем пустые .gitkeep файлы для поддержания структуры
42
+ directories = ["vector_store", "chat_history", "documents"]
 
 
 
 
 
43
 
44
+ for directory in directories:
45
+ with tempfile.NamedTemporaryFile(delete=False) as temp:
46
+ temp_path = temp.name
47
+
48
+ try:
49
+ self.api.upload_file(
50
+ path_or_fileobj=temp_path,
51
+ path_in_repo=f"{directory}/.gitkeep",
52
+ repo_id=self.dataset_name,
53
+ repo_type="dataset"
54
+ )
55
+ finally:
56
+ if os.path.exists(temp_path):
57
+ os.remove(temp_path)
58
 
59
  return True, "Структура датасета успешно создана"
60
  except Exception as e:
61
  return False, f"Ошибка при создании структуры датасета: {str(e)}"
62
 
63
+ def upload_vector_store(self) -> Tuple[bool, str]:
64
+ """
65
+ Загрузка векторного хранилища в датасет
66
+
67
+ Returns:
68
+ (успех, сообщение)
69
+ """
70
  try:
71
  # Проверяем наличие файлов
72
  index_path = os.path.join(VECTOR_STORE_PATH, "index.faiss")
73
  config_path = os.path.join(VECTOR_STORE_PATH, "index.pkl")
74
 
75
+ if not os.path.exists(index_path):
76
+ return False, f"Файл векторного хранилища не найден: {index_path}"
77
+
78
+ if not os.path.exists(config_path):
79
+ return False, f"Файл конфигурации не найден: {config_path}"
80
 
81
  # Загружаем файлы
82
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
83
+
84
+ # Сначала сохраняем старые версии файлов в архивной директории, если они существуют
85
+ try:
86
+ # Проверяем наличие старых файлов
87
+ self.api.hf_hub_download(
88
+ repo_id=self.dataset_name,
89
+ filename="vector_store/index.faiss",
90
+ repo_type="dataset"
91
+ )
92
+
93
+ # Если файл существует, создаем архивную копию
94
+ self.api.upload_file(
95
+ path_or_fileobj=index_path,
96
+ path_in_repo=f"vector_store/archive/index_{timestamp}.faiss",
97
+ repo_id=self.dataset_name,
98
+ repo_type="dataset"
99
+ )
100
+
101
+ self.api.upload_file(
102
+ path_or_fileobj=config_path,
103
+ path_in_repo=f"vector_store/archive/index_{timestamp}.pkl",
104
+ repo_id=self.dataset_name,
105
+ repo_type="dataset"
106
+ )
107
+ except Exception:
108
+ # Если файлов нет, создаем директорию для архива
109
+ with tempfile.NamedTemporaryFile(delete=False) as temp:
110
+ temp_path = temp.name
111
+
112
+ try:
113
+ self.api.upload_file(
114
+ path_or_fileobj=temp_path,
115
+ path_in_repo="vector_store/archive/.gitkeep",
116
+ repo_id=self.dataset_name,
117
+ repo_type="dataset"
118
+ )
119
+ finally:
120
+ if os.path.exists(temp_path):
121
+ os.remove(temp_path)
122
+
123
+ # Загружаем текущие файлы
124
  self.api.upload_file(
125
  path_or_fileobj=index_path,
126
  path_in_repo="vector_store/index.faiss",
 
135
  repo_type="dataset"
136
  )
137
 
138
+ # Обновляем метаданные о последнем обновлении
139
+ metadata = {
140
+ "last_update": timestamp,
141
+ "version": "1.0"
142
+ }
143
+
144
+ with tempfile.NamedTemporaryFile(mode="w+", suffix=".json", delete=False) as temp:
145
+ json.dump(metadata, temp, ensure_ascii=False, indent=2)
146
+ temp_name = temp.name
147
+
148
+ try:
149
+ self.api.upload_file(
150
+ path_or_fileobj=temp_name,
151
+ path_in_repo="vector_store/metadata.json",
152
+ repo_id=self.dataset_name,
153
+ repo_type="dataset"
154
+ )
155
+ finally:
156
+ if os.path.exists(temp_name):
157
+ os.remove(temp_name)
158
+
159
  return True, "Векторное хранилище успешно загружено"
160
  except Exception as e:
161
  return False, f"Ошибка при загрузке векторного хранилища: {str(e)}"
162
 
163
+ def download_vector_store(self, force: bool = False) -> Tuple[bool, str]:
164
+ """
165
+ Загрузка векторного хранилища из датасета
166
+
167
+ Args:
168
+ force: Принудительная загрузка даже если локальные файлы существуют
169
+
170
+ Returns:
171
+ (успех, сообщение)
172
+ """
173
  try:
174
  # Создаем директорию если её нет
175
  os.makedirs(VECTOR_STORE_PATH, exist_ok=True)
176
 
177
+ # Проверяем наличие локальных файлов
178
+ index_path = os.path.join(VECTOR_STORE_PATH, "index.faiss")
179
+ config_path = os.path.join(VECTOR_STORE_PATH, "index.pkl")
 
 
 
 
180
 
181
+ if not force and os.path.exists(index_path) and os.path.exists(config_path):
182
+ return True, "Локальные файлы векторного хранилища уже существуют"
 
 
 
 
183
 
184
+ # Загружаем файлы
185
+ try:
186
+ # Пробуем получить метаданные для проверки существования файлов
187
+ self.api.hf_hub_download(
188
+ repo_id=self.dataset_name,
189
+ filename="vector_store/metadata.json",
190
+ repo_type="dataset",
191
+ local_dir=VECTOR_STORE_PATH
192
+ )
193
+
194
+ # Загружаем файлы векторного хранилища
195
+ self.api.hf_hub_download(
196
+ repo_id=self.dataset_name,
197
+ filename="vector_store/index.faiss",
198
+ repo_type="dataset",
199
+ local_dir=VECTOR_STORE_PATH
200
+ )
201
+
202
+ self.api.hf_hub_download(
203
+ repo_id=self.dataset_name,
204
+ filename="vector_store/index.pkl",
205
+ repo_type="dataset",
206
+ local_dir=VECTOR_STORE_PATH
207
+ )
208
+
209
+ return True, "Векторное хранилище успешно загружено"
210
+ except Exception as download_error:
211
+ return False, f"Ошибка при загрузке файлов: {str(download_error)}"
212
  except Exception as e:
213
  return False, f"Ошибка при загрузке векторного хранилища: {str(e)}"
214
 
215
+ def save_chat_history(self, conversation_id: str, messages: List[Dict[str, Any]]) -> Tuple[bool, str]:
216
+ """
217
+ Сохранение истории чата в датасет
218
+
219
+ Args:
220
+ conversation_id: Идентификатор беседы
221
+ messages: Список сообщений в формате [{role: str, content: str}]
222
+
223
+ Returns:
224
+ (успех, сообщение)
225
+ """
226
  try:
227
  # Формируем имя файла с временной меткой
228
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
229
  filename = f"chat_history/{conversation_id}_{timestamp}.json"
230
 
231
+ # Создаем данные для сохранения
232
  chat_data = {
233
  "conversation_id": conversation_id,
234
  "timestamp": timestamp,
235
  "messages": messages
236
  }
237
 
238
+ # Используем безопасный временный файл
239
+ with tempfile.NamedTemporaryFile(mode="w+", suffix=".json", delete=False, encoding="utf-8") as temp:
240
+ json.dump(chat_data, temp, ensure_ascii=False, indent=2)
241
+ temp_name = temp.name
242
+
243
+ try:
244
+ # Загружаем файл в датасет
245
+ self.api.upload_file(
246
+ path_or_fileobj=temp_name,
247
+ path_in_repo=filename,
248
+ repo_id=self.dataset_name,
249
+ repo_type="dataset"
250
+ )
251
+ finally:
252
+ # Удаляем временный файл в любом случае
253
+ if os.path.exists(temp_name):
254
+ os.remove(temp_name)
255
+
256
+ return True, "История чата сохранена"
257
+ except Exception as e:
258
+ return False, f"Ошибка при сохранении истории чата: {str(e)}"
259
+
260
+ def get_chat_history(self, conversation_id: Optional[str] = None) -> Tuple[bool, Any]:
261
+ """
262
+ Получение истории чатов из датасета
263
+
264
+ Args:
265
+ conversation_id: Идентификатор беседы (если None, возвращает все чаты)
266
+
267
+ Returns:
268
+ (успех, история чатов или сообщение об ошибке)
269
+ """
270
+ try:
271
+ # Получаем список файлов в директории chat_history
272
+ files = self.api.list_repo_files(
273
+ repo_id=self.dataset_name,
274
+ repo_type="dataset",
275
+ path="chat_history"
276
+ )
277
 
278
+ # Фильтруем файлы по conversation_id, если он указан
279
+ if conversation_id:
280
+ files = [f for f in files if f.startswith(f"chat_history/{conversation_id}_")]
281
+
282
+ # Если файлов нет, возвращаем пустой список
283
+ if not files or all(f.endswith(".gitkeep") for f in files):
284
+ return True, []
285
+
286
+ # Создаем временную директорию для загрузки файлов
287
+ with tempfile.TemporaryDirectory() as temp_dir:
288
+ chat_histories = []
289
+
290
+ for file in files:
291
+ if file.endswith(".gitkeep"):
292
+ continue
293
+
294
+ # Загружаем файл
295
+ local_file = self.api.hf_hub_download(
296
+ repo_id=self.dataset_name,
297
+ filename=file,
298
+ repo_type="dataset",
299
+ local_dir=temp_dir
300
+ )
301
+
302
+ # Читаем содержимое файла
303
+ with open(local_file, "r", encoding="utf-8") as f:
304
+ chat_data = json.load(f)
305
+ chat_histories.append(chat_data)
306
+
307
+ # Сортируем по временной метке
308
+ chat_histories.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
309
+
310
+ return True, chat_histories
311
+ except Exception as e:
312
+ return False, f"Ошибка при получении истории чатов: {str(e)}"
313
+
314
+ def upload_document(self, file_path: str, document_id: Optional[str] = None) -> Tuple[bool, str]:
315
+ """
316
+ Загрузка документа в датасет
317
+
318
+ Args:
319
+ file_path: Путь к файлу документа
320
+ document_id: Идентификатор документа (если None, используется имя файла)
321
+
322
+ Returns:
323
+ (успех, сообщение)
324
+ """
325
+ try:
326
+ if not os.path.exists(file_path):
327
+ return False, f"Файл не найден: {file_path}"
328
+
329
+ # Если document_id не указан, используем имя файла
330
+ if document_id is None:
331
+ document_id = os.path.basename(file_path)
332
+
333
+ # Добавляем временную метку к имени файла
334
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
335
+ filename = f"documents/{document_id}_{timestamp}{os.path.splitext(file_path)[1]}"
336
+
337
+ # Загружаем файл
338
  self.api.upload_file(
339
+ path_or_fileobj=file_path,
340
  path_in_repo=filename,
341
  repo_id=self.dataset_name,
342
  repo_type="dataset"
343
  )
344
 
345
+ return True, f"Документ успешно загружен: {filename}"
 
 
 
346
  except Exception as e:
347
+ return False, f"Ошибка при загрузке документа: {str(e)}"
348
 
349
+ def test_dataset_connection(token: Optional[str] = None) -> Tuple[bool, str]:
350
+ """
351
+ Тестовая функция для проверки подключения к датасету
352
+
353
+ Args:
354
+ token: Токен доступа к Hugging Face Hub
355
+
356
+ Returns:
357
+ (успех, сообщение)
358
+ """
359
  try:
360
+ manager = DatasetManager(token=token)
361
  success, message = manager.init_dataset_structure()
362
+
363
+ if not success:
364
+ return False, message
365
+
366
  print(f"Тест инициализации: {message}")
367
 
368
  return True, "Подключение к датасету работает"