Update app.py
Browse files
app.py
CHANGED
|
@@ -13,7 +13,7 @@ from sklearn.preprocessing import normalize
|
|
| 13 |
from concurrent.futures import ThreadPoolExecutor
|
| 14 |
import requests
|
| 15 |
from fastapi import FastAPI, HTTPException, Query
|
| 16 |
-
from typing import List
|
| 17 |
import uvicorn
|
| 18 |
from starlette.requests import Request
|
| 19 |
from starlette.responses import HTMLResponse, JSONResponse
|
|
@@ -46,7 +46,7 @@ logging.info("Модель загружена успешно.")
|
|
| 46 |
|
| 47 |
# Jina AI Reranker API
|
| 48 |
JINA_API_URL = 'https://api.jina.ai/v1/rerank'
|
| 49 |
-
JINA_API_KEY = os.environ.get("JINA_API_KEY")
|
| 50 |
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"
|
|
@@ -173,14 +173,14 @@ def get_movie_data_from_db(conn, movie_ids):
|
|
| 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':
|
| 184 |
}
|
| 185 |
response = requests.get(JINA_DASHBOARD_API_URL, headers=headers, params=params)
|
| 186 |
response.raise_for_status()
|
|
@@ -190,10 +190,15 @@ def get_jina_ai_balance():
|
|
| 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}'")
|
| 196 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
# Получаем данные фильмов из БД
|
| 198 |
conn = get_db_connection()
|
| 199 |
movie_ids = [movie_id for movie_id, _ in results]
|
|
@@ -208,14 +213,16 @@ def rerank_with_api(query, results, top_k, rerank_top_k=None):
|
|
| 208 |
else:
|
| 209 |
logging.warning(f"Данные для фильма с ID {movie_id} не найдены в БД.")
|
| 210 |
|
|
|
|
|
|
|
| 211 |
headers = {
|
| 212 |
'Content-Type': 'application/json',
|
| 213 |
-
'Authorization': f'Bearer {JINA_API_KEY}'
|
| 214 |
}
|
| 215 |
data = {
|
| 216 |
"model": JINA_RERANKER_MODEL,
|
| 217 |
"query": query,
|
| 218 |
-
"top_n": rerank_top_k or top_k
|
| 219 |
"documents": documents
|
| 220 |
}
|
| 221 |
logging.info(f"Отправка данных на реранжировку (documents count): {len(data['documents'])}, top_n: {data['top_n']}")
|
|
@@ -236,13 +243,13 @@ def rerank_with_api(query, results, top_k, rerank_top_k=None):
|
|
| 236 |
logging.warning("Ответ от API не содержит ключа 'results'.")
|
| 237 |
|
| 238 |
logging.info("Переранжирование завершено.")
|
| 239 |
-
return reranked_results, True,
|
| 240 |
|
| 241 |
except requests.exceptions.RequestException as e:
|
| 242 |
logging.error(f"Ошибка при запросе к API реранжировщика: {e}")
|
| 243 |
-
return results, False,
|
| 244 |
|
| 245 |
-
def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None):
|
| 246 |
"""Внутренняя функция для поиска фильмов по запросу (используется и в Gradio, и в API)."""
|
| 247 |
start_time = time.time()
|
| 248 |
|
|
@@ -295,14 +302,18 @@ def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None
|
|
| 295 |
results = []
|
| 296 |
finally:
|
| 297 |
conn.close()
|
| 298 |
-
|
| 299 |
-
#
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
|
|
@@ -350,7 +361,7 @@ def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None
|
|
| 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",
|
|
@@ -377,12 +388,35 @@ def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None
|
|
| 377 |
}, 0
|
| 378 |
|
| 379 |
@app.get("/search/", response_model=dict)
|
| 380 |
-
async def api_search_movies(query: str = Query(..., description="Поисковый запрос"),
|
| 381 |
top_k: int = Query(25, description="Количество возвращаемых результатов"),
|
| 382 |
-
rerank_top_k: int = Query(None, description="Количество фильмов для передачи в реранкер (если не указано, то top_k*2)")
|
| 383 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
try:
|
| 385 |
-
results, _ = search_movies_internal(query, top_k, rerank_top_k)
|
| 386 |
return results
|
| 387 |
except Exception as e:
|
| 388 |
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
| 13 |
from concurrent.futures import ThreadPoolExecutor
|
| 14 |
import requests
|
| 15 |
from fastapi import FastAPI, HTTPException, Query
|
| 16 |
+
from typing import List, Optional
|
| 17 |
import uvicorn
|
| 18 |
from starlette.requests import Request
|
| 19 |
from starlette.responses import HTMLResponse, JSONResponse
|
|
|
|
| 46 |
|
| 47 |
# Jina AI Reranker API
|
| 48 |
JINA_API_URL = 'https://api.jina.ai/v1/rerank'
|
| 49 |
+
JINA_API_KEY = os.environ.get("JINA_API_KEY") # Используем переменную окружения
|
| 50 |
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"
|
|
|
|
| 173 |
logging.error(f"Ошибка при получении данных фильмов из БД: {e}")
|
| 174 |
return movie_data_dict
|
| 175 |
|
| 176 |
+
def get_jina_ai_balance(api_key: str):
|
| 177 |
"""Получает остаток баланса Jina AI."""
|
| 178 |
try:
|
| 179 |
headers = {
|
| 180 |
'Content-Type': 'application/json'
|
| 181 |
}
|
| 182 |
params = {
|
| 183 |
+
'api_key': api_key
|
| 184 |
}
|
| 185 |
response = requests.get(JINA_DASHBOARD_API_URL, headers=headers, params=params)
|
| 186 |
response.raise_for_status()
|
|
|
|
| 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, api_key=None):
|
| 194 |
"""Переранжирует результаты с помощью Jina AI Reranker API."""
|
| 195 |
logging.info(f"Начало переранжирования для запроса: '{query}'")
|
| 196 |
|
| 197 |
+
# Если rerank_top_k равен 0, не используем реранкер
|
| 198 |
+
if rerank_top_k == 0:
|
| 199 |
+
logging.info("Переранжирование отключено (rerank_top_k = 0).")
|
| 200 |
+
return results, False, 0
|
| 201 |
+
|
| 202 |
# Получаем данные фильмов из БД
|
| 203 |
conn = get_db_connection()
|
| 204 |
movie_ids = [movie_id for movie_id, _ in results]
|
|
|
|
| 213 |
else:
|
| 214 |
logging.warning(f"Данные для фильма с ID {movie_id} не найдены в БД.")
|
| 215 |
|
| 216 |
+
reranked_count = min(rerank_top_k or top_k*2, len(documents))
|
| 217 |
+
|
| 218 |
headers = {
|
| 219 |
'Content-Type': 'application/json',
|
| 220 |
+
'Authorization': f'Bearer {api_key or JINA_API_KEY}'
|
| 221 |
}
|
| 222 |
data = {
|
| 223 |
"model": JINA_RERANKER_MODEL,
|
| 224 |
"query": query,
|
| 225 |
+
"top_n": rerank_top_k or top_k*2,
|
| 226 |
"documents": documents
|
| 227 |
}
|
| 228 |
logging.info(f"Отправка данных на реранжировку (documents count): {len(data['documents'])}, top_n: {data['top_n']}")
|
|
|
|
| 243 |
logging.warning("Ответ от API не содержит ключа 'results'.")
|
| 244 |
|
| 245 |
logging.info("Переранжирование завершено.")
|
| 246 |
+
return reranked_results, True, reranked_count
|
| 247 |
|
| 248 |
except requests.exceptions.RequestException as e:
|
| 249 |
logging.error(f"Ошибка при запросе к API реранжировщика: {e}")
|
| 250 |
+
return results, False, reranked_count
|
| 251 |
|
| 252 |
+
def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: Optional[int] = None, jina_api_key: Optional[str] = None):
|
| 253 |
"""Внутренняя функция для поиска фильмов по запросу (используется и в Gradio, и в API)."""
|
| 254 |
start_time = time.time()
|
| 255 |
|
|
|
|
| 302 |
results = []
|
| 303 |
finally:
|
| 304 |
conn.close()
|
| 305 |
+
|
| 306 |
+
# Используем реранкер только если rerank_top_k не равен 0
|
| 307 |
+
if rerank_top_k != 0:
|
| 308 |
+
reranked_results, rerank_success, reranked_count = rerank_with_api(query, results, top_k, rerank_top_k, jina_api_key)
|
| 309 |
+
else:
|
| 310 |
+
reranked_results = results
|
| 311 |
+
rerank_success = False
|
| 312 |
+
reranked_count = 0
|
| 313 |
|
| 314 |
if not rerank_success:
|
| 315 |
logging.warning("Переранжировка не удалась, ис��ользуются сырые результаты.")
|
| 316 |
reranked_results = results[:top_k] # Используем срез для ограничения количества результатов
|
|
|
|
| 317 |
else:
|
| 318 |
reranked_results = reranked_results[:top_k]
|
| 319 |
|
|
|
|
| 361 |
search_time = time.time() - start_time
|
| 362 |
logging.info(f"Поиск выполнен за {search_time:.2f} секунд.")
|
| 363 |
|
| 364 |
+
jina_balance = get_jina_ai_balance(jina_api_key or JINA_API_KEY)
|
| 365 |
|
| 366 |
return {
|
| 367 |
"status": "success",
|
|
|
|
| 388 |
}, 0
|
| 389 |
|
| 390 |
@app.get("/search/", response_model=dict)
|
| 391 |
+
async def api_search_movies(query: str = Query(..., description="Поисковый запрос"),
|
| 392 |
top_k: int = Query(25, description="Количество возвращаемых результатов"),
|
| 393 |
+
rerank_top_k: Optional[int] = Query(None, description="Количество фильмов для передачи в реранкер (если не указано, то top_k*2)"),
|
| 394 |
+
jina_api_key: Optional[str] = Query(None, description="API ключ Jina AI (если не указан, используется значение из переменной окружения JINA_API_KEY)")):
|
| 395 |
+
"""
|
| 396 |
+
API endpoint для поиска фильмов.
|
| 397 |
+
|
| 398 |
+
Parameters
|
| 399 |
+
----------
|
| 400 |
+
query : str
|
| 401 |
+
Поисковый запрос.
|
| 402 |
+
top_k : int, optional
|
| 403 |
+
Количество возвращаемых результатов, по умолчанию 25.
|
| 404 |
+
rerank_top_k : Optional[int], optional
|
| 405 |
+
Количество фильмов для передачи в реранкер.
|
| 406 |
+
Если 0 - реранкер не используется.
|
| 407 |
+
Если не указано, то используется top_k*2.
|
| 408 |
+
По умолчанию None.
|
| 409 |
+
jina_api_key : Optional[str], optional
|
| 410 |
+
API ключ Jina AI. Если не указан, используется значение из переменной окружения JINA_API_KEY.
|
| 411 |
+
По умолчанию None.
|
| 412 |
+
|
| 413 |
+
Returns
|
| 414 |
+
-------
|
| 415 |
+
dict
|
| 416 |
+
Словарь с результатами поиска.
|
| 417 |
+
"""
|
| 418 |
try:
|
| 419 |
+
results, _ = search_movies_internal(query, top_k, rerank_top_k, jina_api_key)
|
| 420 |
return results
|
| 421 |
except Exception as e:
|
| 422 |
raise HTTPException(status_code=500, detail=str(e))
|