Fast API Server Architecture - 100-hours-a-week/5-yeosa-wiki GitHub Wiki

μ„œλΉ„μŠ€ 아킀텍쳐 λͺ¨λ“ˆν™”

app/
β”œβ”€β”€ main.py
β”œβ”€β”€ api/
β”‚   └── v1/
β”‚       β”œβ”€β”€ endpoints/
β”‚       β”‚   β”œβ”€β”€ album_embedding_router.py   # [앨범 μž„λ² λ”© μš”μ²­]
β”‚       β”‚   β”œβ”€β”€ album_tagging_router.py     # [νƒœκΉ… 뢄리 μš”μ²­]
β”‚       β”‚   β”œβ”€β”€ album_duplicate_router.py     # 쀑볡 νŒλ³„
β”‚       β”‚   β”œβ”€β”€ album_clustering_router.py    # 인물 ν΄λŸ¬μŠ€ν„°λ§
β”‚       β”‚   └── style_router.py             # [μŠ€νƒ€μΌ λ³€ν™˜ μš”μ²­]
β”‚       └── controllers/
β”‚           β”œβ”€β”€ album_embedding_controller.py
β”‚           β”œβ”€β”€ album_tagging_controller.py
β”‚           β”œβ”€β”€ album_duplicate_controller.py
β”‚           β”œβ”€β”€ album_clustering_controller.py
β”‚           └── style_controller.py
β”œβ”€β”€ services/
β”‚   β”œβ”€β”€ album/
β”‚   β”‚   β”œβ”€β”€ embedding_service.py        # 이미지 μž„λ² λ”© μΆ”μΆœ
β”‚   β”‚   β”œβ”€β”€ person_clustering.py        # μ‚¬λžŒ λΆ„λ₯˜ (κ΅°μ§‘ν™”)
β”‚   β”‚   β”œβ”€β”€ duplicate_detection.py      # 쀑볡 사진 νŒλ³„
β”‚   β”‚   └── tagging_service.py          # νƒœκΉ… 처리 (ex. beach, food)
β”‚   └── style/
β”‚       β”œβ”€β”€ gemini_service.py           # μ œλ―Έλ‚˜μ΄ 호좜 (μŠ€νƒ€μΌ ν”„λ‘¬ν”„νŠΈ 생성)
β”‚       β”œβ”€β”€ gpu_server_service.py       # GPU μ„œλ²„ μŠ€νƒ€μΌ λ³€ν™˜ μš”μ²­
β”œβ”€β”€ schemas/
β”‚   β”œβ”€β”€ album_schema.py                 # Pydantic: 앨범 생성 μš”μ²­/응닡
β”‚   └── style_schema.py                 # Pydantic: μŠ€νƒ€μΌ λ³€ν™˜ μš”μ²­/응닡
└──  utils/
    └──  image_converter.py              # base64 <-> PIL λ³€ν™˜ λ“±

흐름 μš”μ•½ (λΆ„λ¦¬λœ API κΈ°μ€€)

1. 이미지 μž„λ² λ”© μš”μ²­ (POST /api/v1/albums/embeddings)

  • album_embedding_router.py β†’ album_embedding_controller.py

    β†’ embedding_service.py

  • μž„λ² λ”©ν•΄μ„œ μΊμ‹œμ— μ €μž₯

  • ν΄λΌμ΄μ–ΈνŠΈμ— μž„λ² λ”© μ„±κ³΅ν–ˆμŒμ„ μ•Œλ¦Ό. μž„λ² λ”© 성곡 ν›„, νƒœκΉ…&쀑볡 νŒλ³„&인물 ν΄λŸ¬μŠ€ν„°λ§μ΄ λΉ„λ™κΈ°λ‘œ 진행됨.


2. νƒœκΉ… μš”μ²­ (POST /api/v1/albums/tags)

  • album_tagging_router.py β†’ album_tagging_controller.py

    β†’ tagging_service.py

  • 이미지에 λŒ€ν•΄ μŒμ‹, ν•΄λ³€ λ“± μ½˜ν…μΈ  기반 νƒœκ·Έ μΆ”μΆœ

πŸ“¦ 응닡 μ˜ˆμ‹œ:

{
  "tags": {
    "food": ["http://server:8000/img1.jpg", "http://server:8000/img5.jpg"],
    "beach": ["http://server:8000/img2.jpg", "http://server:8000/img6.jpg"]
  }
}

3. 쀑볡 이미지 νŒλ³„ μš”μ²­ (POST /api/v1/albums/duplicates)

  • album_duplicate_router.py β†’ album_duplicate_controller.py

    β†’ duplicate_detection.py

  • μœ μ‚¬λ„ 기반으둜 쀑볡 이미지 κ·Έλ£Ήν™”

πŸ“¦ 응닡 μ˜ˆμ‹œ:

{
  "duplicates": {
    "duplicate_group_1": ["http://server:8000/img4.jpg", "http://server:8000/img7.jpg"]
  }
}

4. 인물 ν΄λŸ¬μŠ€ν„°λ§ μš”μ²­ (POST /api/v1/albums/clusters)

  • album_clustering_router.py β†’ album_clustering_controller.py

    β†’ person_clustering.py

  • 동일 인물 κΈ°μ€€μœΌλ‘œ 이미지 κ·Έλ£Ήν™”

πŸ“¦ 응닡 μ˜ˆμ‹œ:

{
  "people_clusters": {
    "person1": ["http://server:8000/img1.jpg", "http://server:8000/img3.jpg"],
    "person2": ["http://server:8000/img2.jpg"]
  }
}

5. μŠ€νƒ€μΌ λ³€ν™˜ μš”μ²­ (POST /api/v1/style)

  • style_router.py β†’ style_controller.py

    β†’ image_downloader.py (이미지 λ‹€μš΄λ‘œλ“œ)

    β†’ gemini_service.py (이미지 기반 ν”„λ‘¬ν”„νŠΈ 생성)

    β†’ image_converter.py (PIL β†’ base64 λ³€ν™˜)

    β†’ gpu_server_service.py (base64 + ν”„λ‘¬ν”„νŠΈ 전달)

    β†’ s3_uploader.py (κ²°κ³Ό 이미지 S3 μ—…λ‘œλ“œ ν›„ URL λ°˜ν™˜)


πŸ“¦ 흐름 μš”μ•½

  1. ν΄λΌμ΄μ–ΈνŠΈ(λ°±μ—”λ“œ μ„œλ²„)κ°€ S3 이미지 URLκ³Ό μŠ€νƒ€μΌλͺ…을 μš”μ²­μ— λ‹΄μ•„ 전솑

  2. image_downloader.pyκ°€ ν•΄λ‹Ή URLμ—μ„œ 이미지λ₯Ό λ‹€μš΄λ‘œλ“œ (PIL μ΄λ―Έμ§€λ‘œ λ³€ν™˜)

  3. gemini_service.pyκ°€ λ‹€μš΄λ‘œλ“œλœ 이미지λ₯Ό Gemini에 μž…λ ₯ν•˜μ—¬

    β†’ 이미지 μ„€λͺ… 기반 μŠ€νƒ€μΌ ν”„λ‘¬ν”„νŠΈ 생성

  4. image_converter.pyμ—μ„œ ν•΄λ‹Ή 이미지λ₯Ό base64둜 인코딩

  5. gpu_server_service.pyκ°€ base64 이미지 + μŠ€νƒ€μΌ ν”„λ‘¬ν”„νŠΈλ₯Ό GPU μ„œλ²„μ— 전솑

  6. GPU μ„œλ²„κ°€ μŠ€νƒ€μΌ 이미지 생성 ν›„ λ°˜ν™˜λœ base64 이미지λ₯Ό λ‹€μ‹œ λ””μ½”λ”©

  7. κ²°κ³Ό 이미지λ₯Ό s3_uploader.pyκ°€ S3에 μ €μž₯

  8. ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ S3 URL을 μ‘λ‹΅μœΌλ‘œ λ°˜ν™˜


πŸ“ μš”μ²­ μ˜ˆμ‹œ

{
  "image_url": "https://your-bucket.s3.amazonaws.com/originals/img123.jpg",
  "style_name": "watercolor"


πŸ“ 응닡 μ˜ˆμ‹œ

{
  "image_url": "https://your-bucket.s3.amazonaws.com/converted/img123_watercolor.jpg"
}

🧩 전체 λͺ¨λ“ˆ 흐름

λͺ¨λ“ˆ μ—­ν• 
image_downloader.py 이미지 URLμ—μ„œ 파일 λ‹€μš΄λ‘œλ“œ β†’ PIL 이미지 λ°˜ν™˜
gemini_service.py λ‹€μš΄λ‘œλ“œλœ 이미지λ₯Ό 기반으둜 μŠ€νƒ€μΌ ν”„λ‘¬ν”„νŠΈ 생성
image_converter.py PIL 이미지λ₯Ό base64 λ¬Έμžμ—΄λ‘œ 인코딩
gpu_server_service.py base64 이미지 + ν”„λ‘¬ν”„νŠΈ β†’ GPU μ„œλ²„λ‘œ 전솑
s3_uploader.py κ²°κ³Ό 이미지λ₯Ό S3에 μ—…λ‘œλ“œν•˜κ³  URL λ°˜ν™˜

μ˜ˆμ‹œ: GPU μ„œλ²„λ‘œ λ³΄λ‚΄λŠ” 데이터 포맷

{
  "prompt": "ghibli style, 1girl, smiling, close-up, soft lighting, warm color tones, ((masterpiece)), ((best quality)), <lora:ghibli_style_offset:1.1>",
  "negative_prompt": "(worst quality, low quality:1.4), bad anatomy, blurry, jpeg artifacts, watermark, text",
  "seed": 1178811602,
  "steps": 20,
  "width": 512,
  "height": 768,
  "cfg_scale": 8,
  "sampler_name": "Euler a",
  "denoising_strength": 0.5,
  "init_images": ["<base64둜 μΈμ½”λ”©λœ 원본 이미지>"],
  "restore_faces": true,
  "enable_hr": false,
  "override_settings": {
    "sd_model_checkpoint": "anyloraCheckpoint_bakedvaeBlessedFp16",
    "CLIP_stop_at_last_layers": 2
  },
  "alwayson_scripts": {
    "ControlNet": {
      "args": [
        {
          "enabled": true,
          "module": "mediapipe_face",
          "model": "control_v2p_sd15_mediapipe_face [0653d983]",
          "control_mode": "ControlNet is more important",
          "resize_mode": "Crop and Resize",
          "image": {
            "image": "<face_img_base64>",
            "mask": null
          }
        },
        {
          "enabled": true,
          "module": "depth_midas",
          "model": "diffusers_xl_depth_mid [39c49e13]",
          "control_mode": "My prompt is more important",
          "resize_mode": "Crop and Resize",
          "image": {
            "image": "<depth_img_base64>",
            "mask": null
          }
        },
        {
          "enabled": true,
          "module": "softedge_hed",
          "model": "control_v11p_sd15_softedge [a8575a2a]",
          "control_mode": "My prompt is more important",
          "resize_mode": "Crop and Resize",
          "image": {
            "image": "<softedge_img_base64>",
            "mask": null
          }
        },
        {
          "enabled": true,
          "module": "canny",
          "model": "diffusers_xl_canny_mid [112a778d]",
          "control_mode": "My prompt is more important",
          "resize_mode": "Crop and Resize",
          "threshold_a": 100,
          "threshold_b": 200,
          "image": {
            "image": "<canny_img_base64>",
            "mask": null
          }
        }
      ]
    }
  }
}