Spaces:
Sleeping
Sleeping
| """ | |
| FastAPI ์ ํ๋ฆฌ์ผ์ด์ ๋ฉ์ธ ๋ชจ๋ | |
| """ | |
| import os | |
| import sys | |
| import logging | |
| import tempfile | |
| import traceback | |
| import time | |
| from fastapi import FastAPI, Request, HTTPException, Query, Body | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import JSONResponse | |
| from typing import List, Dict, Any, Optional, Union | |
| import json | |
| import base64 | |
| from io import BytesIO | |
| from PIL import Image | |
| # ์บ์ ๋๋ ํ ๋ฆฌ ์ค์ ๋ฐ ์ต์ ํ | |
| CACHE_DIRS = { | |
| 'TRANSFORMERS_CACHE': '/tmp/transformers_cache', | |
| 'HF_HOME': '/tmp/huggingface_cache', | |
| 'TORCH_HOME': '/tmp/torch_hub_cache', | |
| 'UPLOADS_DIR': '/tmp/uploads' | |
| } | |
| # ํ๊ฒฝ๋ณ์ ์ค์ | |
| for key, path in CACHE_DIRS.items(): | |
| os.environ[key] = path | |
| os.makedirs(path, exist_ok=True) | |
| # ์ถ๊ฐ ํ๊ฒฝ๋ณ์ ์ต์ ํ | |
| os.environ['HF_HUB_DISABLE_TELEMETRY'] = '1' | |
| os.environ['TRANSFORMERS_VERBOSITY'] = 'error' | |
| # ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ด๋ จ ํ๊ฒฝ ๋ณ์ (๊ธฐ๋ณธ๊ฐ ์ค์ ) | |
| # ์ค์ ํ๊ฒฝ์์๋ .env ํ์ผ์ด๋ ํ๊ฒฝ ๋ณ์๋ก ์ค์ ํด์ผ ํจ | |
| os.environ.setdefault('DB_HOST', 'localhost') | |
| os.environ.setdefault('DB_PORT', '3306') | |
| os.environ.setdefault('DB_USER', 'username') # ์ค์ ์ฌ์ฉ์ ๋ณ๊ฒฝ ํ์ | |
| os.environ.setdefault('DB_PASSWORD', 'password') # ์ค์ ์ฌ์ฉ์ ๋ณ๊ฒฝ ํ์ | |
| os.environ.setdefault('DB_NAME', 'foundlost') | |
| # ์ ํ๋ฆฌ์ผ์ด์ ํ๊ฒฝ ์ค์ (development, production, test) | |
| os.environ.setdefault('APP_ENV', 'development') | |
| # ๋ก๊น ์ค์ ๊ฐ์ | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', | |
| handlers=[ | |
| logging.StreamHandler(sys.stdout), | |
| logging.FileHandler('/tmp/app.log') | |
| ] | |
| ) | |
| logger = logging.getLogger(__name__) | |
| # ๋ชจ๋ธ ํด๋์ค ์ ์ - Spring Boot์ ํธํ๋๋๋ก ์์ | |
| from pydantic import BaseModel, Field | |
| class SpringMatchRequest(BaseModel): | |
| """Spring Boot์์ ๋ณด๋ด๋ ์์ฒญ ๊ตฌ์กฐ์ ๋ง์ถ ๋ชจ๋ธ""" | |
| category: Optional[int] = None | |
| title: Optional[str] = None | |
| color: Optional[str] = None | |
| content: Optional[str] = None | |
| detail: Optional[str] = None # Spring์์ detail์ด๋ผ๋ ํ๋๋ช ์ฌ์ฉ | |
| location: Optional[str] = None | |
| image_url: Optional[str] = None | |
| threshold: Optional[float] = 0.7 | |
| class MatchingResult(BaseModel): | |
| total_matches: int | |
| similarity_threshold: float | |
| matches: List[Dict[str, Any]] | |
| class MatchingResponse(BaseModel): | |
| success: bool | |
| message: str | |
| result: Optional[MatchingResult] = None | |
| # ๋ชจ๋ธ ์ด๊ธฐํ (์ฑ๊ธํค์ผ๋ก ๋ก๋) | |
| clip_model = None | |
| def get_clip_model(force_reload=False): | |
| """ | |
| ํ๊ตญ์ด CLIP ๋ชจ๋ธ ์ธ์คํด์ค๋ฅผ ๋ฐํ (์ฑ๊ธํค ํจํด) | |
| Args: | |
| force_reload (bool): ๋ชจ๋ธ ๊ฐ์ ์ฌ๋ก๋ฉ ์ฌ๋ถ | |
| """ | |
| global clip_model | |
| # ๋ชจ๋ธ ๋ก๋ฉ ์์ ์๊ฐ ๊ธฐ๋ก | |
| start_time = time.time() | |
| if clip_model is None or force_reload: | |
| try: | |
| # ๋ก๊น ๋ฐ ์ฑ๋ฅ ์ถ์ | |
| logger.info("๐ CLIP ๋ชจ๋ธ ์ด๊ธฐํ ์์...") | |
| # ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๊ธฐ๋ก (๊ฐ๋ฅํ ๊ฒฝ์ฐ) | |
| try: | |
| import psutil | |
| process = psutil.Process(os.getpid()) | |
| logger.info(f"๋ชจ๋ธ ๋ก๋ ์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋: {process.memory_info().rss / 1024 / 1024:.2f} MB") | |
| except ImportError: | |
| pass | |
| # ๋ชจ๋ธ ๋ก๋ | |
| from models.clip_model import KoreanCLIPModel | |
| clip_model = KoreanCLIPModel() | |
| # ๋ก๋ฉ ์๊ฐ ๋ก๊น | |
| load_time = time.time() - start_time | |
| logger.info(f"โ CLIP ๋ชจ๋ธ ๋ก๋ ์๋ฃ (์์์๊ฐ: {load_time:.2f}์ด)") | |
| # ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๊ธฐ๋ก (๊ฐ๋ฅํ ๊ฒฝ์ฐ) | |
| try: | |
| import psutil | |
| process = psutil.Process(os.getpid()) | |
| logger.info(f"๋ชจ๋ธ ๋ก๋ ํ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋: {process.memory_info().rss / 1024 / 1024:.2f} MB") | |
| except ImportError: | |
| pass | |
| return clip_model | |
| except Exception as e: | |
| # ์์ธํ ์๋ฌ ๋ก๊น | |
| logger.error(f"โ CLIP ๋ชจ๋ธ ์ด๊ธฐํ ์คํจ: {str(e)}") | |
| logger.error(f"์๋ฌ ์์ธ: {traceback.format_exc()}") | |
| # ์คํจ ์ None ๋ฐํ | |
| return None | |
| return clip_model | |
| # ๋ด๋ถ์ ์ผ๋ก ์ต๋๋ฌผ ๋ชฉ๋ก์ ๊ฐ์ ธ์ค๋ ํจ์ | |
| async def fetch_found_items(limit=100, offset=0): | |
| """ | |
| ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ต๋๋ฌผ ๋ชฉ๋ก์ ๊ฐ์ ธ์ค๋ ํจ์ | |
| Args: | |
| limit (int): ์กฐํํ ์ต๋ ํญ๋ชฉ ์ (๊ธฐ๋ณธ๊ฐ: 100) | |
| offset (int): ์กฐํ ์์ ์์น (๊ธฐ๋ณธ๊ฐ: 0) | |
| Returns: | |
| list: ์ต๋๋ฌผ ๋ฐ์ดํฐ ๋ชฉ๋ก | |
| """ | |
| try: | |
| # ํ๊ฒฝ๋ณ์ ํ์ธ - ํ ์คํธ ๋ชจ๋์ธ ๊ฒฝ์ฐ ์ํ ๋ฐ์ดํฐ ๋ฐํ | |
| if os.getenv('APP_ENV') == 'test': | |
| logger.info("ํ ์คํธ ๋ชจ๋: ์ํ ๋ฐ์ดํฐ ์ฌ์ฉ") | |
| # ์์ ๋ฐ์ดํฐ - ํ ์คํธ์ฉ | |
| sample_found_items = [ | |
| { | |
| "id": 1, | |
| "item_category_id": 1, | |
| "title": "๊ฒ์ ๊ฐ์ฃฝ ์ง๊ฐ", | |
| "color": "๊ฒ์ ์", | |
| "content": "๊ฐ๋จ์ญ ๊ทผ์ฒ์์ ๊ฒ์ ์ ๊ฐ์ฃฝ ์ง๊ฐ์ ๋ฐ๊ฒฌํ์ต๋๋ค.", | |
| "location": "๊ฐ๋จ์ญ", | |
| "image": None, | |
| "category": "์ง๊ฐ" | |
| }, | |
| { | |
| "id": 2, | |
| "item_category_id": 1, | |
| "title": "๊ฐ์ ๊ฐ์ฃฝ ์ง๊ฐ", | |
| "color": "๊ฐ์", | |
| "content": "์์ธ๋์ ๊ตฌ์ญ ๊ทผ์ฒ์์ ๊ฐ์ ๊ฐ์ฃฝ ์ง๊ฐ์ ๋ฐ๊ฒฌํ์ต๋๋ค.", | |
| "location": "์์ธ๋์ ๊ตฌ์ญ", | |
| "image": None, | |
| "category": "์ง๊ฐ" | |
| } | |
| ] | |
| return sample_found_items | |
| # ์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ฐ์ดํฐ ์กฐํ | |
| # ๊ธฐ๋ฅ์ด ๊ฒ์ฆ๋๋ฉด limit ๊ฐ์ ๋๋ฆด ์ ์์ | |
| logger.info(f"๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ต๋๋ฌผ ๋ฐ์ดํฐ ์กฐํ ์ค (limit: {limit}, offset: {offset})...") | |
| # db_connector ๋ชจ๋์ ํจ์ ํธ์ถ | |
| from db_connector import fetch_found_items as db_fetch_found_items | |
| found_items = await db_fetch_found_items(limit=limit, offset=offset) | |
| logger.info(f"๋ฐ์ดํฐ๋ฒ ์ด์ค์์ {len(found_items)}๊ฐ์ ์ต๋๋ฌผ ๋ฐ์ดํฐ ์กฐํ ์๋ฃ") | |
| return found_items | |
| except Exception as e: | |
| logger.error(f"์ต๋๋ฌผ ๋ฐ์ดํฐ ์กฐํ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}") | |
| logger.error(traceback.format_exc()) | |
| # ์ค๋ฅ ๋ฐ์ ์ ๋น ๋ชฉ๋ก ๋ฐํ | |
| return [] | |
| # FastAPI ์ ํ๋ฆฌ์ผ์ด์ ์์ฑ | |
| app = FastAPI( | |
| title="์ต๋๋ฌผ ์ ์ฌ๋ ๊ฒ์ API", | |
| description="ํ๊ตญ์ด CLIP ๋ชจ๋ธ์ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์ ๊ฒ์๊ธ๊ณผ ์ต๋๋ฌผ ๊ฐ์ ์ ์ฌ๋๋ฅผ ๊ณ์ฐํ๋ API", | |
| version="1.0.0" | |
| ) | |
| # CORS ๋ฏธ๋ค์จ์ด ์ค์ | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ด๋ฒคํธ | |
| async def startup_event(): | |
| """ | |
| ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ ์คํ๋๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ | |
| """ | |
| logger.info("์ ํ๋ฆฌ์ผ์ด์ ์์ ์ค...") | |
| try: | |
| # ๋ชจ๋ธ ์ฌ์ ๋ค์ด๋ก๋ (๋น๋๊ธฐ์ ์ผ๋ก) | |
| from models.clip_model import preload_clip_model | |
| preload_clip_model() | |
| logger.info("๋ชจ๋ธ ์ฌ์ ๋ค์ด๋ก๋ ์๋ฃ") | |
| # ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ํ ์คํธ | |
| if os.getenv('APP_ENV') != 'test': | |
| try: | |
| from db_connector import get_db_connection | |
| with get_db_connection() as connection: | |
| with connection.cursor() as cursor: | |
| cursor.execute("SELECT 1") | |
| result = cursor.fetchone() | |
| if result: | |
| logger.info("โ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ํ ์คํธ ์ฑ๊ณต") | |
| else: | |
| logger.warning("โ ๏ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ํ ์คํธ ๊ฒฐ๊ณผ ์์") | |
| except Exception as db_error: | |
| logger.error(f"โ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ํ ์คํธ ์คํจ: {str(db_error)}") | |
| logger.error(traceback.format_exc()) | |
| except Exception as e: | |
| logger.error(f"์์ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}") | |
| logger.error(traceback.format_exc()) | |
| # ์ ์ญ ์์ธ ์ฒ๋ฆฌ | |
| async def global_exception_handler(request: Request, exc: Exception): | |
| """ | |
| ์ ์ญ ์์ธ ์ฒ๋ฆฌ๊ธฐ | |
| """ | |
| logger.error(f"์์ฒญ ์ฒ๋ฆฌ ์ค ์์ธ ๋ฐ์: {str(exc)}") | |
| return JSONResponse( | |
| status_code=500, | |
| content={"success": False, "message": f"์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {str(exc)}"} | |
| ) | |
| # ์ ํธ๋ฆฌํฐ ๋ชจ๋ ์ํฌํธ | |
| from utils.similarity import calculate_similarity, find_similar_items, CATEGORY_WEIGHT, ITEM_NAME_WEIGHT, COLOR_WEIGHT, CONTENT_WEIGHT | |
| # ์ด ๋ฐ์ดํฐ ๊ฐ์ ์กฐํ ํจ์ ์ํฌํธ | |
| from db_connector import count_found_items | |
| # API ์๋ํฌ์ธํธ ์ ์ - Spring Boot์ ๋ง๊ฒ ์์ | |
| async def find_similar_items_api( | |
| request: dict, | |
| threshold: float = Query(0.7, description="์ ์ฌ๋ ์๊ณ๊ฐ (0.0 ~ 1.0)"), | |
| limit: int = Query(10, description="๋ฐํํ ์ต๋ ํญ๋ชฉ ์"), | |
| db_limit: int = Query(100, description="๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์กฐํํ ์ต๋๋ฌผ ์") | |
| ): | |
| """ | |
| Spring Boot์์ ๋ณด๋ด๋ ์์ฒญ ๊ตฌ์กฐ์ ๋ง์ถฐ ์ฌ์ฉ์ ๊ฒ์๊ธ๊ณผ ์ ์ฌํ ์ต๋๋ฌผ์ ์ฐพ๋ API | |
| Args: | |
| request (dict): ์์ฒญ ๋ฐ์ดํฐ | |
| threshold (float): ์ ์ฌ๋ ์๊ณ๊ฐ (๊ธฐ๋ณธ๊ฐ: 0.7) | |
| limit (int): ๋ฐํํ ์ต๋ ํญ๋ชฉ ์ (๊ธฐ๋ณธ๊ฐ: 10) | |
| db_limit (int): ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์กฐํํ ์ต๋๋ฌผ ์ (๊ธฐ๋ณธ๊ฐ: 100) | |
| """ | |
| try: | |
| logger.info(f"์ ์ฌ ์ต๋๋ฌผ ๊ฒ์ ์์ฒญ: threshold={threshold}, limit={limit}, db_limit={db_limit}") | |
| logger.debug(f"์์ฒญ ๋ฐ์ดํฐ: {request}") | |
| # ์์ฒญ ๋ฐ์ดํฐ ๋ณํ | |
| user_post = {} | |
| # ์ค์: lostItemId ์ ์ฅ | |
| lostItemId = request.get('lostItemId') | |
| # Spring Boot์์ ๋ณด๋ด๋ ํ๋๋ช ๋งคํ | |
| if 'category' in request: | |
| user_post['category'] = request['category'] | |
| elif 'itemCategoryId' in request: | |
| user_post['category'] = request['itemCategoryId'] | |
| # ์ ๋ชฉ ํ๋ | |
| if 'title' in request: | |
| user_post['item_name'] = request['title'] | |
| # ์์ ํ๋ | |
| if 'color' in request: | |
| user_post['color'] = request['color'] | |
| # ๋ด์ฉ ํ๋ (Spring Boot์์๋ detail๋ก ๋ณด๋) | |
| if 'detail' in request: | |
| user_post['content'] = request['detail'] | |
| elif 'content' in request: | |
| user_post['content'] = request['content'] | |
| # ์์น ํ๋ | |
| if 'location' in request: | |
| user_post['location'] = request['location'] | |
| # ์ด๋ฏธ์ง URL ํ๋ | |
| if 'image' in request and request['image']: | |
| user_post['image_url'] = request['image'] | |
| elif 'image_url' in request and request['image_url']: | |
| user_post['image_url'] = request['image_url'] | |
| # ์์ฒญ์ ๋ค์ด์จ threshold ๊ฐ์ด ์์ผ๋ฉด ์ฌ์ฉ | |
| if 'threshold' in request and request['threshold']: | |
| threshold = float(request['threshold']) | |
| # ์ด ๋ฐ์ดํฐ ๊ฐ์ ์กฐํ (์ฑ๋ฅ ์ธก์ ์ฉ) | |
| total_count = await count_found_items() | |
| logger.info(f"๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ด ์ด ์ต๋๋ฌผ ๊ฐ์: {total_count}") | |
| # ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ต๋๋ฌผ ๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ (์ง์ ๋ ๊ฐ์๋งํผ) | |
| found_items = await fetch_found_items(limit=db_limit, offset=0) | |
| logger.info(f"๊ฒ์ํ ์ต๋๋ฌผ ์: {len(found_items)}") | |
| # ์ฌ์ฉ์์๊ฒ ์งํ ์ํฉ ์๋ฆผ | |
| if len(found_items) == 0: | |
| return MatchingResponse( | |
| success=False, | |
| message="์ต๋๋ฌผ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋๋ฐ ์คํจํ์ต๋๋ค.", | |
| result=None | |
| ) | |
| # ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ฐ์ ธ์จ ๋น์จ ๊ณ์ฐ | |
| db_coverage = len(found_items) / max(1, total_count) * 100 | |
| logger.info(f"์ด ๋ฐ์ดํฐ ์ค {db_coverage:.2f}% ๊ฒ์ ({len(found_items)}/{total_count})") | |
| # CLIP ๋ชจ๋ธ ๋ก๋ | |
| clip_model_instance = get_clip_model() | |
| if clip_model_instance is None: | |
| return MatchingResponse( | |
| success=False, | |
| message="CLIP ๋ชจ๋ธ ๋ก๋์ ์คํจํ์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.", | |
| result=None | |
| ) | |
| # ์ ์ฌ๋ ๊ณ์ฐ ์์ ์๊ฐ ๊ธฐ๋ก | |
| start_time = time.time() | |
| # ์ ์ฌํ ํญ๋ชฉ ์ฐพ๊ธฐ | |
| similar_items = find_similar_items(user_post, found_items, threshold, clip_model_instance) | |
| # ์ ์ฌ๋ ๊ณ์ฐ ์์ ์๊ฐ | |
| similarity_time = time.time() - start_time | |
| logger.info(f"์ ์ฌ๋ ๊ณ์ฐ ์์ ์๊ฐ: {similarity_time:.2f}์ด (ํญ๋ชฉ๋น ํ๊ท : {similarity_time/max(1, len(found_items))*1000:.2f}ms)") | |
| # ์ ์ฌ๋ ์ธ๋ถ ์ ๋ณด ๋ก๊น | |
| logger.info("===== ์ ์ฌ๋ ์ธ๋ถ ์ ๋ณด =====") | |
| for idx, item in enumerate(similar_items[:5]): # ์์ 5๊ฐ๋ง ๋ก๊น | |
| logger.info(f"ํญ๋ชฉ {idx+1}: {item['item']['title']}") | |
| logger.info(f" ์ต์ข ์ ์ฌ๋: {item['similarity']:.4f}") | |
| details = item['details'] | |
| logger.info(f" ํ ์คํธ ์ ์ฌ๋: {details['text_similarity']:.4f}") | |
| if details['image_similarity'] is not None: | |
| logger.info(f" ์ด๋ฏธ์ง ์ ์ฌ๋: {details['image_similarity']:.4f}") | |
| category_sim = details['details']['category'] | |
| item_name_sim = details['details']['item_name'] | |
| color_sim = details['details']['color'] | |
| content_sim = details['details']['content'] | |
| logger.info(f" ์นดํ ๊ณ ๋ฆฌ ์ ์ฌ๋: {category_sim:.4f} (๊ฐ์ค์น: {CATEGORY_WEIGHT:.2f})") | |
| logger.info(f" ๋ฌผํ๋ช ์ ์ฌ๋: {item_name_sim:.4f} (๊ฐ์ค์น: {ITEM_NAME_WEIGHT:.2f})") | |
| logger.info(f" ์์ ์ ์ฌ๋: {color_sim:.4f} (๊ฐ์ค์น: {COLOR_WEIGHT:.2f})") | |
| logger.info(f" ๋ด์ฉ ์ ์ฌ๋: {content_sim:.4f} (๊ฐ์ค์น: {CONTENT_WEIGHT:.2f})") | |
| logger.info("==========================") | |
| # ๊ฒฐ๊ณผ ์ ํ | |
| similar_items = similar_items[:limit] | |
| # Spring Boot ์๋ต ํ์์ ๋ง๊ฒ ๊ฒฐ๊ณผ ๊ตฌ์ฑ | |
| matches = [] | |
| for item in similar_items: | |
| found_item = item['item'] | |
| # ์ต๋๋ฌผ ์ ๋ณด ๊ตฌ์ฑ (์ถ๊ฐ ํ๋ ํฌํจ) | |
| found_item_info = { | |
| "id": found_item["id"], | |
| "user_id": found_item.get("user_id", None), | |
| "item_category_id": found_item["item_category_id"], | |
| "title": found_item["title"], | |
| "color": found_item["color"], | |
| "lost_at": found_item.get("lost_at", None), | |
| "location": found_item["location"], | |
| "detail": found_item["content"], | |
| "image": found_item.get("image", None), | |
| "status": found_item.get("status", "ACTIVE"), | |
| "storedAt": found_item.get("storedAt", None), | |
| "majorCategory": found_item.get("majorCategory", None), # ์ถ๊ฐ: ๋๋ถ๋ฅ | |
| "minorCategory": found_item.get("minorCategory", None), # ์ถ๊ฐ: ์๋ถ๋ฅ | |
| "management_id": found_item.get("management_id", None) # ์ถ๊ฐ: ๊ด๋ฆฌ ๋ฒํธ | |
| } | |
| match_item = { | |
| "lostItemId": lostItemId, # ์์ฒญ ๋ฐ์ lostItemId ์ฌ์ฉ | |
| "foundItemId": found_item["id"], | |
| "item": found_item_info, | |
| "similarity": round(item["similarity"], 4) | |
| } | |
| matches.append(match_item) | |
| # ์๋ต ๊ฒฐ๊ณผ ๊ตฌ์ฑ | |
| result = { | |
| "total_matches": len(matches), | |
| "similarity_threshold": threshold, | |
| "matches": matches, | |
| "db_coverage_percent": round(db_coverage, 2) # ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฒ๋ฆฌ์ง ์ถ๊ฐ | |
| } | |
| response_data = { | |
| "success": True, | |
| "message": f"{len(matches)}๊ฐ์ ์ ์ฌํ ์ต๋๋ฌผ์ ์ฐพ์์ต๋๋ค. (์ด {len(found_items)}๊ฐ ์ค ๊ฒ์)", | |
| "result": result | |
| } | |
| # ์๋ต ๋ก๊น | |
| logger.info(f"์๋ต ๋ฐ์ดํฐ: {len(matches)}๊ฐ์ ์ ์ฌํ ์ต๋๋ฌผ ๋ฐ๊ฒฌ") | |
| return MatchingResponse(**response_data) | |
| except Exception as e: | |
| logger.error(f"API ํธ์ถ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}") | |
| logger.error(traceback.format_exc()) | |
| # ์คํ ํธ๋ ์ด์ค ๋ฐํ (๊ฐ๋ฐ์ฉ) | |
| error_response = { | |
| "success": False, | |
| "message": f"์์ฒญ ์ฒ๋ฆฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {str(e)}", | |
| "error_detail": traceback.format_exc() | |
| } | |
| return JSONResponse(status_code=500, content=error_response) | |
| async def test_endpoint(): | |
| """ | |
| API ํ ์คํธ์ฉ ์๋ํฌ์ธํธ | |
| Returns: | |
| dict: ํ ์คํธ ์๋ต | |
| """ | |
| return {"message": "API๊ฐ ์ ์์ ์ผ๋ก ์๋ ์ค์ ๋๋ค."} | |
| async def status(): | |
| """ | |
| API ์ํ ์๋ํฌ์ธํธ | |
| Returns: | |
| dict: API ์ํ ์ ๋ณด | |
| """ | |
| # CLIP ๋ชจ๋ธ ๋ก๋ ์๋ | |
| model = get_clip_model() | |
| return { | |
| "status": "ok", | |
| "models_loaded": model is not None, | |
| "version": "1.0.0" | |
| } | |
| # ๋ฃจํธ ์๋ํฌ์ธํธ | |
| async def root(): | |
| """ | |
| ๋ฃจํธ ์๋ํฌ์ธํธ - API ์ ๋ณด ์ ๊ณต | |
| """ | |
| return { | |
| "app_name": "์ต๋๋ฌผ ์ ์ฌ๋ ๊ฒ์ API", | |
| "version": "1.0.0", | |
| "description": "ํ๊ตญ์ด CLIP ๋ชจ๋ธ์ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์ ๊ฒ์๊ธ๊ณผ ์ต๋๋ฌผ ๊ฐ์ ์ ์ฌ๋๋ฅผ ๊ณ์ฐํฉ๋๋ค.", | |
| "api_endpoint": "/api/matching/find-similar", | |
| "test_endpoint": "/api/matching/test", | |
| "status_endpoint": "/api/status" | |
| } | |
| # ์ ํ๋ฆฌ์ผ์ด์ ์คํ | |
| if __name__ == "__main__": | |
| import uvicorn | |
| print("์๋ฒ ์คํ ์๋ ์ค...") | |
| try: | |
| uvicorn.run( | |
| "main:app", | |
| host="0.0.0.0", | |
| port=7860, # ํ๊น ํ์ด์ค ์คํ์ด์ค์์ ์ฌ์ฉํ ๊ธฐ๋ณธ ํฌํธ | |
| log_level="info", | |
| reload=True | |
| ) | |
| except Exception as e: | |
| print(f"์๋ฒ ์คํ ์ค ์ค๋ฅ ๋ฐ์: {e}") | |
| traceback.print_exc() |