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 κΈ°μ€)
POST /api/v1/albums/embeddings
)
1. μ΄λ―Έμ§ μλ² λ© μμ² (-
album_embedding_router.py
βalbum_embedding_controller.py
β
embedding_service.py
-
μλ² λ©ν΄μ μΊμμ μ μ₯
-
ν΄λΌμ΄μΈνΈμ μλ² λ© μ±κ³΅νμμ μλ¦Ό. μλ² λ© μ±κ³΅ ν, νκΉ &μ€λ³΅ νλ³&μΈλ¬Ό ν΄λ¬μ€ν°λ§μ΄ λΉλκΈ°λ‘ μ§νλ¨.
POST /api/v1/albums/tags
)
2. νκΉ
μμ² (-
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"]
}
}
POST /api/v1/albums/duplicates
)
3. μ€λ³΅ μ΄λ―Έμ§ νλ³ μμ² (-
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"]
}
}
POST /api/v1/albums/clusters
)
4. μΈλ¬Ό ν΄λ¬μ€ν°λ§ μμ² (-
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"]
}
}
POST /api/v1/style
)
5. μ€νμΌ λ³ν μμ² (-
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 λ°ν)
π¦ νλ¦ μμ½
-
ν΄λΌμ΄μΈνΈ(λ°±μλ μλ²)κ° S3 μ΄λ―Έμ§ URLκ³Ό μ€νμΌλͺ μ μμ²μ λ΄μ μ μ‘
-
image_downloader.py
κ° ν΄λΉ URLμμ μ΄λ―Έμ§λ₯Ό λ€μ΄λ‘λ (PIL μ΄λ―Έμ§λ‘ λ³ν) -
gemini_service.py
κ° λ€μ΄λ‘λλ μ΄λ―Έμ§λ₯Ό Geminiμ μ λ ₯νμ¬β μ΄λ―Έμ§ μ€λͺ κΈ°λ° μ€νμΌ ν둬ννΈ μμ±
-
image_converter.py
μμ ν΄λΉ μ΄λ―Έμ§λ₯Ό base64λ‘ μΈμ½λ© -
gpu_server_service.py
κ° base64 μ΄λ―Έμ§ + μ€νμΌ ν둬ννΈλ₯Ό GPU μλ²μ μ μ‘ -
GPU μλ²κ° μ€νμΌ μ΄λ―Έμ§ μμ± ν λ°νλ base64 μ΄λ―Έμ§λ₯Ό λ€μ λμ½λ©
-
κ²°κ³Ό μ΄λ―Έμ§λ₯Ό
s3_uploader.py
κ° S3μ μ μ₯ -
ν΄λΌμ΄μΈνΈμκ² 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
}
}
]
}
}
}