모델 API 설계 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki

API 명세 페이지

보안 정책

외부 클라이언트(브라우저 등)에서의 직접 접근은 차단되며, 허용된 도메인(예: https://backend.example.com)을 통해 프록시된 요청만 처리

1. 엔드포인트 목록 및 기능 설명

기능 Method Endpoint 설명
앨범 생성 (임베딩) POST /api/albums/embeddings 입력 이미지들을 받아 CLIP 기반 임베딩을 생성하고 캐싱
앨범 생성 (중복 판별) POST /api/albums/duplicates 입력 이미지들의 임베딩을 바탕으로 유사도를 계산하고 중복 이미지 그룹화
앨범 생성 (품질 측정) POST /api/albums/quality 이미지 품질(흐림 등)을 평가하고 낮은 품질 이미지를 필터링
앨범 생성 (카테고리 분류) POST /api/albums/categories 입력 이미지들을 사전에 정의된 카테고리로 분류
앨범 생성 (하이라이트 점수) POST /api/albums/scores 카테고리 내 이미지들의 대표성/품질 기반 하이라이트 점수 계산
앨범 생성 (인물 클러스터링) POST /api/albums/people 얼굴 임베딩을 통해 인물별로 이미지 클러스터링 수행
사진 스타일 변환 POST /api/style 지정한 스타일(e.g., ghibli, pixel, comic 등)로 이미지 변환 요청

2. API 요청/응답 예시

/api/albums/embeddings

  • Request
{
  "images": [
    "http://server:8000/img1.jpg",
    "http://server:8000/img2.jpg"
  ]
}
  • Response
{
  "message": "success",
  "data": null
}

/api/albums/duplicates

  • Request
{
  "images": [
	  "http://server:8000/img1.jpg",
	  "http://server:8000/img2.jpg",
	  "http://server:8000/img3.jpg",
	  "http://server:8000/img4.jpg"
	]
}
  • Response
{
  "message": "success",
  "data": [
    ["http://server:8000/img1.jpg", "http://server:8000/img3.jpg"],
    ["http://server:8000/img2.jpg", "http://server:8000/img4.jpg"]
  ]
}

/api/albums/quality

  • Request
{
  "images": [
	  "http://server:8000/img1.jpg",
	  "http://server:8000/img2.jpg",
	  "http://server:8000/img3.jpg",
	  "http://server:8000/img4.jpg"
	]
}
  • Response
{
  "message": "success",
  "data": [
    "http://server:8000/img2.jpg",
    "http://server:8000/img3.jpg"
  ]
}

/api/albums/categories

  • Request
{
  "images": [
	  "http://server:8000/img1.jpg",
	  "http://server:8000/img2.jpg",
	  "http://server:8000/img3.jpg",
	  "http://server:8000/img4.jpg"
	]
}
  • Response
{
  "message": "success",
  "data": [
    {
      "category": "food",
      "images": ["http://server:8000/img1.jpg"]
    },
    {
      "category": "beach",
      "images": ["http://server:8000/img2.jpg"]
    },
    {
      "category": "others",
      "images": ["http://server:8000/img3.jpg", "http://server:8000/img4.jpg"]
    }
  ]
}

/api/albums/scores

  • Request
{
  "categories": [
    {
      "category": "food",
      "images": ["http://server:8000/img1.jpg"]
    },
    {
      "category": "beach",
      "images": ["http://server:8000/img2.jpg"]
    }
  ]
}
  • Response
{
  "message": "success",
  "data": [
    {
      "category": "food",
      "images": [
        { "image": "http://server:8000/img1.jpg", "score": 0.91 }
      ]
    },
    {
      "category": "beach",
      "images": [
        { "image": "http://server:8000/img2.jpg", "score": 0.86 }
      ]
    }
  ]
}

/api/albums/people

  • Request
{
  "images": [
	  "http://server:8000/img1.jpg",
	  "http://server:8000/img2.jpg",
	  "http://server:8000/img3.jpg",
	  "http://server:8000/img4.jpg"
	]
}
  • Response
{
  "message": "success",
  "data": [
    ["http://server:8000/img1.jpg", "http://server:8000/img3.jpg"],
    ["http://server:8000/img2.jpg"]
  ]
}

/api/style

  • Request
{
  "image": "http://server:8000/img1.jpg",
  "style": "ghibli"
}
  • Response
{
  "message": "success",
  "data": "http://server:8000/img1_styled.jpg"
}

▸ 실패 응답 예시

  • 400 Bad Request – 필드 누락 등 잘못된 요청 형식

    {
      "message": "invalid_request",
      "data": null
    }
    
  • 403 Forbidden – 인가되지 않은 접근

    {
      "message": "unauthorized_server",
      "data": null
    }
    
  • 422 Unprocessable Entity – URL 유효성 실패

    {
        "message" : "invalid_image_url",
        "data" : [
            "http://server:8000/img4.jpg",
            "http://server:8000/img6.jpg"
        ]
    }
    
  • 428 Precondition Required – 이미지 임베딩이 캐시되어 있지 않음

    {
        "message" : "invalid_image_url",
        "data" : [
            "http://server:8000/img4.jpg",
            "http://server:8000/img6.jpg"
        ]
    }
    
  • 500 Internal Server Error – 서버 처리 중 예기치 못한 오류

    {
      "message": "internal_server_error",
      "data": null
    }
    

3. 입력/출력 스키마

▸ 공통 입력 스키마

from pydantic import BaseModel, HttpUrl, constr
from typing import List

# /embeddings, /duplicates, /quality, /categories, /people 등에서 공통 사용
class ImageListRequest(BaseModel):
    images: List[HttpUrl]

# /scores 요청용
class ImageCategoryGroup(BaseModel):
    category: str
    images: List[HttpUrl]

class CategoryScoreRequest(BaseModel):
    categories: List[ImageCategoryGroup]

# /style 요청용
class StyleRequest(BaseModel):
    image: HttpUrl
    style: constr(strip_whitespace=True, min_length=1)

▸ 공통 출력 스키마

from typing import Optional, List, Union
from pydantic import BaseModel, HttpUrl
from typing_extensions import Literal

# 공통 메시지 포함
class BaseMessage(BaseModel):
    message: str

# /embeddings
class EmptySuccessResponse(BaseMessage):
    data: None

# /quality
class QualityResponse(BaseMessage):
    data: List[HttpUrl]

# /duplicates, /people
class ClusteredImageResponse(BaseMessage):
    data: List[List[HttpUrl]]

# /categories
class CategoryGroup(BaseModel):
    category: str
    images: List[HttpUrl]

class CategoryResponse(BaseMessage):
    data: List[CategoryGroup]

# /scores
class ScoredImage(BaseModel):
    image: HttpUrl
    score: float

class ScoredCategory(BaseModel):
    category: str
    images: List[ScoredImage]

class ScoreResponse(BaseMessage):
    data: List[ScoredCategory]

# /style
class StyleResponse(BaseMessage):
    data: HttpUrl

▸ 예외 응답 스키마

from typing import Optional, List, Dict
from pydantic import BaseModel, HttpUrl
from typing_extensions import Literal

# 400, 500용
class ErrorResponse(BaseModel):
    message: str
    data: Optional[None] = None

# 403: 인가되지 않은 요청
class UnauthorizedServerResponse(BaseModel):
    message: Literal["unauthorized_server"]
    data: Optional[None] = None

# 422: URL 유효성 오류
class InvalidImageUrlResponse(BaseModel):
    message: Literal["invalid_image_url"]
    data: List[HttpUrl]

# 428: 캐시 미존재로 인한 선행 임베딩 필요
class EmbeddingRequiredResponse(BaseModel):
    message: Literal["embedding_required"]
    data: List[HttpUrl]

▸ 각 API와 연결 예시

API Endpoint Request Model Success Response Error Response
/api/albums/embeddings ImageListRequest EmptySuccessResponse UnauthorizedServerResponse,
InvalidImageUrlResponse, ErrorResponse
/api/albums/duplicates ImageListRequest ClusteredImageResponse UnauthorizedServerResponse, InvalidImageUrlResponse, EmbeddingRequiredResponse, ErrorResponse
/api/albums/quality ImageListRequest QualityResponse UnauthorizedServerResponse, InvalidImageUrlResponse, EmbeddingRequiredResponse, ErrorResponse
/api/albums/categories ImageListRequest CategoryResponse UnauthorizedServerResponse, InvalidImageUrlResponse, EmbeddingRequiredResponse, ErrorResponse
/api/albums/scores CategoryScoreRequest ScoreResponse UnauthorizedServerResponse, InvalidImageUrlResponse, EmbeddingRequiredResponse, ErrorResponse
/api/albums/people ImageListRequest ClusteredImageResponse UnauthorizedServerResponse, InvalidImageUrlResponse, EmbeddingRequiredResponse, ErrorResponse
/api/style StyleRequest StyleResponse UnauthorizedServerResponse, InvalidImageUrlResponse, ErrorResponse

4. 서비스 구조에서의 역할 및 연동 관계

아키텍처 요약

(Client)
   │
   ▼
[FastAPI Endpoint] ──> [Controller] ──> [Service Logic]
                                      │
                                      ├─> Embedding
                                      ├─> Duplicate Detection
                                      ├─> Quality Assessment
                                      ├─> Category Classification
                                      ├─> Highlight Scoring
                                      └─> People Clustering

                      ↕               ↕
               [core/cache]    [core/image_loader]

  • 공통 데이터 흐름:

    1. 사용자로부터 이미지 URL 리스트 수신
    2. 서비스에 해당하는 controller로 전달
    3. 임베딩 → 캐싱 → 후속 연산 등 수행
    4. 각 서비스는 services/ 내 모듈 호출
    5. services/ 내 모듈은 필요시 core/, utils/ 내 모듈 호출
    6. 결과는 controller를 통해 응답 JSON으로 변환
  • 동기/비동기 흐름 정리:

    embedding ──> category  ──> score
    					├─> duplicate
    					└─> quality
    ────────────> people
    ────────────> style
    
  • 공통 에러 응답

    Status message data 예시
    400 "invalid_request" null
    403 “unauthorized_server” null
    422 "invalid_image_url" { "images": ["invalid_url1", "url2"] }
    428 "embedding_required" { "missing": ["url1", "url2", ...] }
    500 "internal_server_error" null