opex792 commited on
Commit
6d1368c
·
verified ·
1 Parent(s): 8e945d6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +229 -68
app.py CHANGED
@@ -1,78 +1,231 @@
1
  import gradio as gr
2
  from sentence_transformers import SentenceTransformer, util
3
- import json
4
  import os
5
  import time
6
  import threading
7
  import queue
8
  import torch
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  # Загружаем модель
11
- model_name = "HIT-TMG/KaLM-embedding-multilingual-mini-instruct-v1.5"
12
  model = SentenceTransformer(model_name)
13
- # model.max_seq_length = 8192 # Убираем явное ограничение длины последовательности
14
 
15
- # Имя файла для сохранения эмбеддингов
16
- embeddings_file = f"movie_embeddings_{model_name.replace('/', '_')}.json"
17
- # Имя файла для сохранения эмбеддингов запросов
18
- query_embeddings_file = f"query_embeddings_{model_name.replace('/', '_')}.json"
 
 
19
 
20
  # Загружаем данные из файла movies.json
21
  try:
 
22
  with open("movies.json", "r", encoding="utf-8") as f:
23
  movies_data = json.load(f)
24
  except FileNotFoundError:
25
  print("Ошибка: Файл movies.json не найден.")
26
  movies_data = []
27
 
28
- # Загружаем эмбеддинги фильмов
29
- if os.path.exists(embeddings_file):
30
- with open(embeddings_file, "r", encoding="utf-8") as f:
31
- movie_embeddings = json.load(f)
32
- print("Загружены эмбеддинги фильмов из файла.")
33
- else:
34
- movie_embeddings = {}
35
-
36
- # Загружаем эмбеддинги запросов
37
- if os.path.exists(query_embeddings_file):
38
- with open(query_embeddings_file, "r", encoding="utf-8") as f:
39
- query_embeddings = json.load(f)
40
- print("Загружены эмбеддинги запросов из файла.")
41
- else:
42
- query_embeddings = {}
43
-
44
  # Очередь для необработанных фильмов
45
  movies_queue = queue.Queue()
46
  for movie in movies_data:
47
- if movie["name"] not in movie_embeddings:
48
- movies_queue.put(movie)
49
 
50
  # Флаг, указывающий, что обработка фильмов завершена
51
  processing_complete = False
52
  # Флаг, указывающий, что выполняется поиск
53
  search_in_progress = False
54
 
55
- # Блокировка для доступа к movie_embeddings
56
- movie_embeddings_lock = threading.Lock()
57
 
58
  # Размер пакета для обработки эмбеддингов
59
- batch_size = 32 # Увеличиваем размер пакета в 2 раза
60
-
61
- # Инструкция для запроса
62
- query_prompt = "Инструкция: Найди релевантные фильмы по запросу. \n Запрос: "
63
-
64
- def encode_string(text, prompt=None):
65
- """Кодирует строку в эмбеддинг с использованием инструкции, если она задана."""
66
- if prompt:
67
- return model.encode(text, prompt=prompt, convert_to_tensor=True, normalize_embeddings=True)
68
- else:
69
- return model.encode(text, convert_to_tensor=True, normalize_embeddings=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
  def process_movies():
72
  """
73
- Обрабатывает фильмы из очереди, создавая для них эмбеддинги.
74
  """
75
  global processing_complete
 
 
 
 
 
76
  while True:
77
  if search_in_progress:
78
  time.sleep(1) # Ждем, пока поиск не завершится
@@ -98,40 +251,36 @@ def process_movies():
98
  ]
99
 
100
  print(f"Создаются эмбеддинги для фильмов: {', '.join(titles)}...")
101
- embeddings = model.encode(embedding_strings, convert_to_tensor=True, batch_size=batch_size, normalize_embeddings=True).tolist()
102
 
103
- with movie_embeddings_lock:
104
- for title, embedding in zip(titles, embeddings):
105
- movie_embeddings[title] = embedding
106
- # Сохраняем эмбеддинги в файл после обработки каждого пакета
107
- with open(embeddings_file, "w", encoding="utf-8") as f:
108
- json.dump(movie_embeddings, f, ensure_ascii=False, indent=4)
109
- print(f"Эмбеддинги для фильмов: {', '.join(titles)} созданы и сохранены.")
110
 
 
111
  print("Обработка фильмов завершена.")
112
 
113
- def get_query_embedding(query):
114
  """
115
- Возвращает эмбеддинг для запроса с инструкцией.
116
- Если эмбеддинг уже создан, возвращает его из словаря.
117
- Иначе создает эмбеддинг, сохраняет его и возвращает.
118
  """
119
- if query in query_embeddings:
120
- print(f"Эмбеддинг для запроса '{query}' уже существует.")
121
- return query_embeddings[query]
122
- else:
123
- print(f"Создается эмбеддинг для запроса '{query}'...")
124
- embedding = encode_string(query, prompt=query_prompt).tolist()
125
- query_embeddings[query] = embedding
126
- # Сохраняем эмбеддинги запросов в файл
127
- with open(query_embeddings_file, "w", encoding="utf-8") as f:
128
- json.dump(query_embeddings, f, ensure_ascii=False, indent=4)
129
- print(f"Эмбеддинг для запроса '{query}' создан и сохранен.")
130
- return embedding
131
 
132
  def search_movies(query, top_k=10):
133
  """
134
- Ищет наиболее похожие фильмы по запросу с использованием инструкции.
135
 
136
  Args:
137
  query: Текстовый запрос.
@@ -145,12 +294,24 @@ def search_movies(query, top_k=10):
145
  start_time = time.time()
146
  print(f"\n\033[1mПоиск по запросу: '{query}'\033[0m")
147
 
 
 
 
 
 
148
  print(f"Начало соз��ания эмбеддинга для запроса: {time.strftime('%Y-%m-%d %H:%M:%S')}")
149
- query_embedding_tensor = encode_string(query, prompt=query_prompt)
 
 
 
 
 
150
  print(f"Окончание создания эмбеддинга для запроса: {time.strftime('%Y-%m-%d %H:%M:%S')}")
151
 
152
- with movie_embeddings_lock:
153
- current_movie_embeddings = movie_embeddings.copy()
 
 
154
 
155
  if not current_movie_embeddings:
156
  search_in_progress = False
@@ -158,7 +319,7 @@ def search_movies(query, top_k=10):
158
 
159
  # Преобразуем эмбеддинги фильмов в тензор
160
  movie_titles = list(current_movie_embeddings.keys())
161
- movie_embeddings_tensor = torch.tensor(list(current_movie_embeddings.values()))
162
 
163
  print(f"Начало поиска похожих фильмов: {time.strftime('%Y-%m-%d %H:%M:%S')}")
164
  # Используем util.semantic_search для поиска похожих фильмов
 
1
  import gradio as gr
2
  from sentence_transformers import SentenceTransformer, util
 
3
  import os
4
  import time
5
  import threading
6
  import queue
7
  import torch
8
+ import psycopg2
9
+ import zlib
10
+ from urllib.parse import urlparse
11
+
12
+ # Настройки базы данных PostgreSQL
13
+ DATABASE_URL = os.environ.get("postgres://avnadmin:[email protected]:22054/Kinopoisk")
14
+ if DATABASE_URL is None:
15
+ raise ValueError("DATABASE_URL environment variable not set.")
16
+
17
+ parsed_url = urlparse(DATABASE_URL)
18
+ db_params = {
19
+ "host": parsed_url.hostname,
20
+ "port": parsed_url.port,
21
+ "database": parsed_url.path.lstrip("/"),
22
+ "user": parsed_url.username,
23
+ "password": parsed_url.password,
24
+ "sslmode": "require"
25
+ }
26
 
27
  # Загружаем модель
28
+ model_name = "BAAI/bge-m3"
29
  model = SentenceTransformer(model_name)
 
30
 
31
+ # Имена таблиц
32
+ embeddings_table = "movie_embeddings"
33
+ query_cache_table = "query_cache"
34
+
35
+ # Максимальный размер таблицы кэша запросов в байтах (50MB)
36
+ MAX_CACHE_SIZE = 50 * 1024 * 1024
37
 
38
  # Загружаем данные из файла movies.json
39
  try:
40
+ import json
41
  with open("movies.json", "r", encoding="utf-8") as f:
42
  movies_data = json.load(f)
43
  except FileNotFoundError:
44
  print("Ошибка: Файл movies.json не найден.")
45
  movies_data = []
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  # Очередь для необработанных фильмов
48
  movies_queue = queue.Queue()
49
  for movie in movies_data:
50
+ movies_queue.put(movie)
 
51
 
52
  # Флаг, указывающий, что обработка фильмов завершена
53
  processing_complete = False
54
  # Флаг, указывающий, что выполняется поиск
55
  search_in_progress = False
56
 
57
+ # Блокировка для доступа к базе данных
58
+ db_lock = threading.Lock()
59
 
60
  # Размер пакета для обработки эмбеддингов
61
+ batch_size = 32
62
+
63
+ def get_db_connection():
64
+ """Устанавливает соединение с базой данных."""
65
+ try:
66
+ conn = psycopg2.connect(**db_params)
67
+ return conn
68
+ except Exception as e:
69
+ print(f"Ошибка подключения к базе данных: {e}")
70
+ return None
71
+
72
+ def create_embeddings_table():
73
+ """Создает таблицу для хранения эмбеддингов фильмов, если она не существует."""
74
+ conn = get_db_connection()
75
+ if conn is None:
76
+ return
77
+
78
+ with conn.cursor() as cur:
79
+ cur.execute(f"""
80
+ CREATE TABLE IF NOT EXISTS {embeddings_table} (
81
+ movie_id INTEGER,
82
+ embedding_crc32 BIGINT PRIMARY KEY,
83
+ string_crc32 BIGINT,
84
+ model_name TEXT,
85
+ embedding vector(1024)
86
+ );
87
+ """)
88
+ conn.commit()
89
+ conn.close()
90
+
91
+ def create_query_cache_table():
92
+ """Создает таблицу для кэширования эмбеддингов запросов, если она не существует."""
93
+ conn = get_db_connection()
94
+ if conn is None:
95
+ return
96
+
97
+ with conn.cursor() as cur:
98
+ cur.execute(f"""
99
+ CREATE TABLE IF NOT EXISTS {query_cache_table} (
100
+ query_crc32 BIGINT PRIMARY KEY,
101
+ query TEXT,
102
+ model_name TEXT,
103
+ embedding vector(1024),
104
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
105
+ );
106
+ CREATE INDEX IF NOT EXISTS idx_query_crc32 ON {query_cache_table} (query_crc32);
107
+ CREATE INDEX IF NOT EXISTS idx_created_at ON {query_cache_table} (created_at);
108
+ """)
109
+ conn.commit()
110
+ conn.close()
111
+
112
+ def create_trigger_function():
113
+ """Создает функцию и триггер для автоматического удаления старых записей из таблицы кэша запросов"""
114
+ conn = get_db_connection()
115
+ if conn:
116
+ with conn.cursor() as cur:
117
+ cur.execute(f"""
118
+ CREATE OR REPLACE FUNCTION manage_query_cache_size()
119
+ RETURNS TRIGGER AS $$
120
+ DECLARE
121
+ table_size BIGINT;
122
+ row_to_delete RECORD;
123
+ BEGIN
124
+ SELECT pg_total_relation_size('{query_cache_table}') INTO table_size;
125
+ IF table_size > {MAX_CACHE_SIZE} THEN
126
+ FOR row_to_delete IN
127
+ SELECT query_crc32
128
+ FROM {query_cache_table}
129
+ ORDER BY created_at ASC
130
+ LOOP
131
+ DELETE FROM {query_cache_table} WHERE query_crc32 = row_to_delete.query_crc32;
132
+ SELECT pg_total_relation_size('{query_cache_table}') INTO table_size;
133
+ EXIT WHEN table_size <= {MAX_CACHE_SIZE};
134
+ END LOOP;
135
+ END IF;
136
+ RETURN NEW;
137
+ END;
138
+ $$ LANGUAGE plpgsql;
139
+
140
+ CREATE OR REPLACE TRIGGER trg_manage_query_cache_size
141
+ AFTER INSERT ON {query_cache_table}
142
+ FOR EACH ROW
143
+ EXECUTE PROCEDURE manage_query_cache_size();
144
+ """)
145
+ conn.commit()
146
+ conn.close()
147
+
148
+ # Создаем таблицы, индексы и триггер при запуске приложения
149
+ create_embeddings_table()
150
+ create_query_cache_table()
151
+ create_trigger_function()
152
+
153
+ def calculate_crc32(text):
154
+ """Вычисляет CRC32 для строки."""
155
+ return zlib.crc32(text.encode('utf-8')) & 0xFFFFFFFF
156
+
157
+ def encode_string(text):
158
+ """Кодирует строку в эмбеддинг."""
159
+ return model.encode(text, convert_to_tensor=True, normalize_embeddings=True)
160
+
161
+ def insert_embedding(conn, movie_id, embedding_string, model_name, embedding):
162
+ """Вставляет эмбеддинг фильма в базу данных."""
163
+ embedding_crc32 = calculate_crc32(str(embedding.tolist()))
164
+ string_crc32 = calculate_crc32(embedding_string)
165
+ with conn.cursor() as cur:
166
+ try:
167
+ cur.execute(
168
+ f"""
169
+ INSERT INTO {embeddings_table} (movie_id, embedding_crc32, string_crc32, model_name, embedding)
170
+ VALUES (%s, %s, %s, %s, %s)
171
+ ON CONFLICT (embedding_crc32) DO NOTHING;
172
+ """,
173
+ (movie_id, embedding_crc32, string_crc32, model_name, embedding.tolist())
174
+ )
175
+ conn.commit()
176
+ return True
177
+ except Exception as e:
178
+ print(f"Ошибка при вставке эмбеддинга фильма: {e}")
179
+ conn.rollback()
180
+ return False
181
+
182
+ def insert_query_embedding(conn, query, model_name, embedding):
183
+ """Вставляет эмбеддинг запроса в таблицу кэша."""
184
+ query_crc32 = calculate_crc32(query)
185
+ with conn.cursor() as cur:
186
+ try:
187
+ cur.execute(
188
+ f"""
189
+ INSERT INTO {query_cache_table} (query_crc32, query, model_name, embedding)
190
+ VALUES (%s, %s, %s, %s)
191
+ ON CONFLICT (query_crc32) DO UPDATE SET created_at = DEFAULT;
192
+ """,
193
+ (query_crc32, query, model_name, embedding.tolist())
194
+ )
195
+ conn.commit()
196
+ print(f"Эмбеддинг для запроса '{query}' сохранен в кэше.")
197
+ return True
198
+ except Exception as e:
199
+ print(f"Ошибка при вставке эмбеддинга запроса: {e}")
200
+ conn.rollback()
201
+ return False
202
+
203
+ def get_movie_embeddings(conn):
204
+ """Загружает все эмбеддинги фильмов из базы данных."""
205
+ movie_embeddings = {}
206
+ with conn.cursor() as cur:
207
+ cur.execute(f"SELECT movie_id, embedding FROM {embeddings_table}")
208
+ rows = cur.fetchall()
209
+ for row in rows:
210
+ movie_id, embedding = row
211
+ # Находим название фильма по его ID
212
+ for movie in movies_data:
213
+ if movie['id'] == movie_id:
214
+ title = movie["name"]
215
+ movie_embeddings[title] = torch.tensor(embedding)
216
+ break
217
+ return movie_embeddings
218
 
219
  def process_movies():
220
  """
221
+ Обрабатывает фильмы из очереди, создавая для них эмбеддинги и сохраняя их в базу данных.
222
  """
223
  global processing_complete
224
+ conn = get_db_connection()
225
+ if conn is None:
226
+ processing_complete = True
227
+ return
228
+
229
  while True:
230
  if search_in_progress:
231
  time.sleep(1) # Ждем, пока поиск не завершится
 
251
  ]
252
 
253
  print(f"Создаются эмбеддинги для фильмов: {', '.join(titles)}...")
254
+ embeddings = model.encode(embedding_strings, convert_to_tensor=True, batch_size=batch_size, normalize_embeddings=True)
255
 
256
+ with db_lock:
257
+ for movie, embedding, embedding_string in zip(batch, embeddings, embedding_strings):
258
+ if insert_embedding(conn, movie['id'], embedding_string, model_name, embedding):
259
+ print(f"Эмбеддинг для фильма '{movie['name']}' сохранен в базе данных.")
260
+ else:
261
+ print(f"Ошибка сохранения эмбеддинга для фильма '{movie['name']}'.")
 
262
 
263
+ conn.close()
264
  print("Обработка фильмов завершена.")
265
 
266
+ def get_query_embedding_from_db(conn, query):
267
  """
268
+ Пытается получить эмбеддинг запроса из базы данных по CRC32.
269
+ Возвращает эмбеддинг, если найден, иначе None.
 
270
  """
271
+ query_crc32 = calculate_crc32(query)
272
+ with conn.cursor() as cur:
273
+ cur.execute(f"SELECT embedding FROM {query_cache_table} WHERE query_crc32 = %s AND model_name = %s", (query_crc32, model_name))
274
+ result = cur.fetchone()
275
+ if result:
276
+ print(f"Эмбеддинг для запроса '{query}' найден в кэше.")
277
+ return torch.tensor(result[0])
278
+ else:
279
+ return None
 
 
 
280
 
281
  def search_movies(query, top_k=10):
282
  """
283
+ Ищет наиболее похожие фильмы по запросу.
284
 
285
  Args:
286
  query: Текстовый запрос.
 
294
  start_time = time.time()
295
  print(f"\n\033[1mПоиск по запросу: '{query}'\033[0m")
296
 
297
+ conn = get_db_connection()
298
+ if conn is None:
299
+ search_in_progress = False
300
+ return "<p>Ошибка подключения к базе данных.</p>"
301
+
302
  print(f"Начало соз��ания эмбеддинга для запроса: {time.strftime('%Y-%m-%d %H:%M:%S')}")
303
+ query_embedding_tensor = get_query_embedding_from_db(conn, query)
304
+
305
+ if query_embedding_tensor is None:
306
+ query_embedding_tensor = encode_string(query)
307
+ # Вставляем эмбеддинг запроса в базу данных
308
+ insert_query_embedding(conn, query, model_name, query_embedding_tensor)
309
  print(f"Окончание создания эмбеддинга для запроса: {time.strftime('%Y-%m-%d %H:%M:%S')}")
310
 
311
+ with db_lock:
312
+ current_movie_embeddings = get_movie_embeddings(conn)
313
+
314
+ conn.close()
315
 
316
  if not current_movie_embeddings:
317
  search_in_progress = False
 
319
 
320
  # Преобразуем эмбеддинги фильмов в тензор
321
  movie_titles = list(current_movie_embeddings.keys())
322
+ movie_embeddings_tensor = torch.stack(list(current_movie_embeddings.values()))
323
 
324
  print(f"Начало поиска похожих фильмов: {time.strftime('%Y-%m-%d %H:%M:%S')}")
325
  # Используем util.semantic_search для поиска похожих фильмов