Update app.py
Browse files
app.py
CHANGED
|
@@ -51,6 +51,9 @@ if JINA_API_KEY is None:
|
|
| 51 |
raise ValueError("JINA_API_KEY environment variable not set.")
|
| 52 |
JINA_RERANKER_MODEL = "jina-reranker-v2-base-multilingual"
|
| 53 |
|
|
|
|
|
|
|
|
|
|
| 54 |
# Имена таблиц
|
| 55 |
embeddings_table = "movie_embeddings"
|
| 56 |
query_cache_table = "query_cache"
|
|
@@ -170,6 +173,23 @@ def get_movie_data_from_db(conn, movie_ids):
|
|
| 170 |
logging.error(f"Ошибка при получении данных фильмов из БД: {e}")
|
| 171 |
return movie_data_dict
|
| 172 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
def rerank_with_api(query, results, top_k, rerank_top_k=None):
|
| 174 |
"""Переранжирует результаты с помощью Jina AI Reranker API."""
|
| 175 |
logging.info(f"Начало переранжирования для запроса: '{query}'")
|
|
@@ -195,10 +215,10 @@ def rerank_with_api(query, results, top_k, rerank_top_k=None):
|
|
| 195 |
data = {
|
| 196 |
"model": JINA_RERANKER_MODEL,
|
| 197 |
"query": query,
|
| 198 |
-
"top_n": rerank_top_k or top_k*2,
|
| 199 |
"documents": documents
|
| 200 |
}
|
| 201 |
-
logging.info(f"Отправка данных на реранжировку (documents count): {len(data['documents'])}")
|
| 202 |
|
| 203 |
try:
|
| 204 |
response = requests.post(JINA_API_URL, headers=headers, json=data)
|
|
@@ -216,11 +236,11 @@ def rerank_with_api(query, results, top_k, rerank_top_k=None):
|
|
| 216 |
logging.warning("Ответ от API не содержит ключа 'results'.")
|
| 217 |
|
| 218 |
logging.info("Переранжирование завершено.")
|
| 219 |
-
return reranked_results, True
|
| 220 |
|
| 221 |
except requests.exceptions.RequestException as e:
|
| 222 |
logging.error(f"Ошибка при запросе к API реранжировщика: {e}")
|
| 223 |
-
return results, False
|
| 224 |
|
| 225 |
def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None):
|
| 226 |
"""Внутренняя функция для поиска фильмов по запросу (используется и в Gradio, и в API)."""
|
|
@@ -250,6 +270,9 @@ def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None
|
|
| 250 |
logging.error(f"Ошибка при сохранении эмбеддинга запроса: {e}")
|
| 251 |
conn.rollback()
|
| 252 |
|
|
|
|
|
|
|
|
|
|
| 253 |
# Используем косинусное расстояние для поиска
|
| 254 |
try:
|
| 255 |
with conn.cursor() as cur:
|
|
@@ -263,7 +286,7 @@ def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None
|
|
| 263 |
FROM "{embeddings_table}" m, query_embedding
|
| 264 |
ORDER BY similarity DESC
|
| 265 |
LIMIT %s
|
| 266 |
-
""", (query_crc32, int(
|
| 267 |
|
| 268 |
results = cur.fetchall()
|
| 269 |
logging.info(f"Найдено {len(results)} предварительных результатов поиска.")
|
|
@@ -274,11 +297,12 @@ def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None
|
|
| 274 |
conn.close()
|
| 275 |
|
| 276 |
# Переранжируем результаты с помощью API
|
| 277 |
-
reranked_results, rerank_success = rerank_with_api(query, results, top_k, rerank_top_k)
|
| 278 |
-
|
| 279 |
if not rerank_success:
|
| 280 |
logging.warning("Переранжировка не удалась, используются сырые результаты.")
|
| 281 |
-
reranked_results = results[:top_k]
|
|
|
|
| 282 |
else:
|
| 283 |
reranked_results = reranked_results[:top_k]
|
| 284 |
|
|
@@ -317,7 +341,8 @@ def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None
|
|
| 317 |
"year": movie_data['year'],
|
| 318 |
"genres": [genre['name'] for genre in movie_data['genres']],
|
| 319 |
"description": movie_data.get('description', ''),
|
| 320 |
-
"relevance_score": score if rerank_success else (
|
|
|
|
| 321 |
})
|
| 322 |
else:
|
| 323 |
logging.warning(f"Данные для фильма с ID {movie_id} не найдены в БД.")
|
|
@@ -325,19 +350,30 @@ def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None
|
|
| 325 |
search_time = time.time() - start_time
|
| 326 |
logging.info(f"Поиск выполнен за {search_time:.2f} секунд.")
|
| 327 |
|
|
|
|
|
|
|
| 328 |
return {
|
| 329 |
"status": "success",
|
| 330 |
"results": formatted_results,
|
| 331 |
"search_time": search_time,
|
| 332 |
"total_movies": total_movies,
|
| 333 |
-
"searched_movies": searched_movies
|
|
|
|
|
|
|
|
|
|
| 334 |
}, search_time
|
| 335 |
|
| 336 |
except Exception as e:
|
| 337 |
logging.error(f"Ошибка при выполнении поиска: {e}")
|
| 338 |
return {
|
| 339 |
"status": "error",
|
| 340 |
-
"message": str(e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 341 |
}, 0
|
| 342 |
|
| 343 |
@app.get("/search/", response_model=dict)
|
|
@@ -603,4 +639,4 @@ async def root():
|
|
| 603 |
|
| 604 |
# Запускаем FastAPI
|
| 605 |
if __name__ == "__main__":
|
| 606 |
-
uvicorn.run(app, host="0.0.0.0"
|
|
|
|
| 51 |
raise ValueError("JINA_API_KEY environment variable not set.")
|
| 52 |
JINA_RERANKER_MODEL = "jina-reranker-v2-base-multilingual"
|
| 53 |
|
| 54 |
+
# Jina AI Dashboard API
|
| 55 |
+
JINA_DASHBOARD_API_URL = 'https://embeddings-dashboard-api.jina.ai/api/v1/api_key/user'
|
| 56 |
+
|
| 57 |
# Имена таблиц
|
| 58 |
embeddings_table = "movie_embeddings"
|
| 59 |
query_cache_table = "query_cache"
|
|
|
|
| 173 |
logging.error(f"Ошибка при получении данных фильмов из БД: {e}")
|
| 174 |
return movie_data_dict
|
| 175 |
|
| 176 |
+
def get_jina_ai_balance():
|
| 177 |
+
"""Получает остаток баланса Jina AI."""
|
| 178 |
+
try:
|
| 179 |
+
headers = {
|
| 180 |
+
'Content-Type': 'application/json'
|
| 181 |
+
}
|
| 182 |
+
params = {
|
| 183 |
+
'api_key': JINA_API_KEY
|
| 184 |
+
}
|
| 185 |
+
response = requests.get(JINA_DASHBOARD_API_URL, headers=headers, params=params)
|
| 186 |
+
response.raise_for_status()
|
| 187 |
+
data = response.json()
|
| 188 |
+
return data['wallet']['total_balance']
|
| 189 |
+
except requests.exceptions.RequestException as e:
|
| 190 |
+
logging.error(f"Ошибка при запросе к API баланса Jina AI: {e}")
|
| 191 |
+
return None
|
| 192 |
+
|
| 193 |
def rerank_with_api(query, results, top_k, rerank_top_k=None):
|
| 194 |
"""Переранжирует результаты с помощью Jina AI Reranker API."""
|
| 195 |
logging.info(f"Начало переранжирования для запроса: '{query}'")
|
|
|
|
| 215 |
data = {
|
| 216 |
"model": JINA_RERANKER_MODEL,
|
| 217 |
"query": query,
|
| 218 |
+
"top_n": rerank_top_k or top_k * 2,
|
| 219 |
"documents": documents
|
| 220 |
}
|
| 221 |
+
logging.info(f"Отправка данных на реранжировку (documents count): {len(data['documents'])}, top_n: {data['top_n']}")
|
| 222 |
|
| 223 |
try:
|
| 224 |
response = requests.post(JINA_API_URL, headers=headers, json=data)
|
|
|
|
| 236 |
logging.warning("Ответ от API не содержит ключа 'results'.")
|
| 237 |
|
| 238 |
logging.info("Переранжирование завершено.")
|
| 239 |
+
return reranked_results, True, data["top_n"]
|
| 240 |
|
| 241 |
except requests.exceptions.RequestException as e:
|
| 242 |
logging.error(f"Ошибка при запросе к API реранжировщика: {e}")
|
| 243 |
+
return results, False, data["top_n"]
|
| 244 |
|
| 245 |
def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None):
|
| 246 |
"""Внутренняя функция для поиска фильмов по запросу (используется и в Gradio, и в API)."""
|
|
|
|
| 270 |
logging.error(f"Ошибка при сохранении эмбеддинга запроса: {e}")
|
| 271 |
conn.rollback()
|
| 272 |
|
| 273 |
+
# Определяем количество фильмов для запроса из БД
|
| 274 |
+
db_limit = rerank_top_k or top_k * 2
|
| 275 |
+
|
| 276 |
# Используем косинусное расстояние для поиска
|
| 277 |
try:
|
| 278 |
with conn.cursor() as cur:
|
|
|
|
| 286 |
FROM "{embeddings_table}" m, query_embedding
|
| 287 |
ORDER BY similarity DESC
|
| 288 |
LIMIT %s
|
| 289 |
+
""", (query_crc32, int(db_limit)))
|
| 290 |
|
| 291 |
results = cur.fetchall()
|
| 292 |
logging.info(f"Найдено {len(results)} предварительных результатов поиска.")
|
|
|
|
| 297 |
conn.close()
|
| 298 |
|
| 299 |
# Переранжируем результаты с помощью API
|
| 300 |
+
reranked_results, rerank_success, reranked_count = rerank_with_api(query, results, top_k, rerank_top_k)
|
| 301 |
+
|
| 302 |
if not rerank_success:
|
| 303 |
logging.warning("Переранжировка не удалась, используются сырые результаты.")
|
| 304 |
+
reranked_results = results[:top_k] # Используем срез для ограничения количества результатов
|
| 305 |
+
reranked_count = 0
|
| 306 |
else:
|
| 307 |
reranked_results = reranked_results[:top_k]
|
| 308 |
|
|
|
|
| 341 |
"year": movie_data['year'],
|
| 342 |
"genres": [genre['name'] for genre in movie_data['genres']],
|
| 343 |
"description": movie_data.get('description', ''),
|
| 344 |
+
"relevance_score": score if rerank_success else (
|
| 345 |
+
movie_data_dict.get(movie_id, (None, None))[1] if movie_data_dict.get(movie_id,(None, None)) is not None else 0.0) # Сохраняем similarity, если нет реранжировки
|
| 346 |
})
|
| 347 |
else:
|
| 348 |
logging.warning(f"Данные для фильма с ID {movie_id} не найдены в БД.")
|
|
|
|
| 350 |
search_time = time.time() - start_time
|
| 351 |
logging.info(f"Поиск выполнен за {search_time:.2f} секунд.")
|
| 352 |
|
| 353 |
+
jina_balance = get_jina_ai_balance()
|
| 354 |
+
|
| 355 |
return {
|
| 356 |
"status": "success",
|
| 357 |
"results": formatted_results,
|
| 358 |
"search_time": search_time,
|
| 359 |
"total_movies": total_movies,
|
| 360 |
+
"searched_movies": searched_movies,
|
| 361 |
+
"returned_movies": len(formatted_results), # Количество возвращенных фильмов
|
| 362 |
+
"reranked_movies": reranked_count, # Количество фильмов, обработанных реранкером
|
| 363 |
+
"jina_balance": jina_balance # Остаток баланса Jina AI
|
| 364 |
}, search_time
|
| 365 |
|
| 366 |
except Exception as e:
|
| 367 |
logging.error(f"Ошибка при выполнении поиска: {e}")
|
| 368 |
return {
|
| 369 |
"status": "error",
|
| 370 |
+
"message": str(e),
|
| 371 |
+
"search_time": 0,
|
| 372 |
+
"total_movies": 0,
|
| 373 |
+
"searched_movies": 0,
|
| 374 |
+
"returned_movies": 0,
|
| 375 |
+
"reranked_movies": 0,
|
| 376 |
+
"jina_balance": None
|
| 377 |
}, 0
|
| 378 |
|
| 379 |
@app.get("/search/", response_model=dict)
|
|
|
|
| 639 |
|
| 640 |
# Запускаем FastAPI
|
| 641 |
if __name__ == "__main__":
|
| 642 |
+
uvicorn.run(app, host="0.0.0.0")
|