- 작성자: System Architect
- 작성일: 2025-12-29
- 버전: v1.0
- 관련 PRD: PROJECT_SPEC.md
- 상태: Review
AgenticLabeling은 AI 기반 자동 라벨링 시스템으로, 이미지/비디오에서 객체를 감지, 분할, 분류하고 객체 중심으로 데이터를 관리하여 고품질 학습 데이터셋을 구축하는 마이크로서비스 플랫폼입니다. 본 문서는 시스템 아키텍처, 데이터 모델, API 설계 및 구현 상세를 기술합니다.
- 멀티모델 파이프라인: Florence-2 (detection) → SAM2 (segmentation) → DINOv2 (classification)
- 객체 중심 관리: 개별 객체 단위 메타데이터, 임베딩, 트랙 관리
- 하이브리드 스토리지: SQLite (메타데이터) + ChromaDB (벡터 검색)
- 마이그레이션 준비: Postgres + Qdrant로 확장 가능한 설계
| 용어 | 정의 |
|---|---|
| Source | 원본 이미지/비디오 파일 |
| Object | 감지된 개별 객체 인스턴스 (bbox, mask, embedding 포함) |
| Track | 비디오 프레임 간 동일 객체의 시퀀스 (Re-ID) |
| Category | 객체 클래스 (예: person, car, dog) |
| Embedding | DINOv2 768차원 시각 특징 벡터 |
| Dataset | 내보내기용 객체 컬렉션 (train/val/test 스플릿) |
┌──────────────────────────────────┐
│ Gateway (CPU:8000) │
│ - API 라우팅 & 파이프라인 조율 │
│ - 헬스체크 프록시 │
└─────────────┬────────────────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Detection │ │ Segmentation │ │ Classification │
│ (GPU:8001) │ │ (GPU:8002) │ │ (GPU:8003) │
│ Florence-2 │ │ SAM2 Hiera B+ │ │ DINOv2 base │
│ large (0.7B) │ │ prompt-based │ │ 768d features │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└───────────────────────┼───────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Object Registry (CPU:8010) │
│ │
│ SQLite DB ChromaDB │
│ ├─ sources ├─ objects_dinov2_v1 │
│ ├─ objects └─ (HNSW cosine index) │
│ ├─ categories │
│ ├─ tracks Masks Storage │
│ ├─ datasets └─ sharded PNG files │
│ └─ embedding_outbox │
└──────────────────────────────────────────────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Labeling │ │ Training │ │ Evaluation │
│ (CPU:8004) │ │ (GPU:8005) │ │ (GPU:8007) │
│ CRUD (legacy) │ │ YOLOv8/v11 │ │ mAP, matrix │
└─────────────────┘ └────────┬────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ MLflow :5000 │
│ 실험 추적 │
└─────────────────┘
┌─────────────────┐
│ Redis :6379 │ (캐시, 작업큐)
└─────────────────┘
| 컴포넌트 | 역할 | 기술 스택 | 리소스 |
|---|---|---|---|
| Gateway | API 라우팅, 파이프라인 조율 | FastAPI, httpx | CPU |
| Detection Agent | 객체 감지 (grounding) | Florence-2-large (0.7B), PyTorch | GPU (VRAM ~4GB) |
| Segmentation Agent | 인스턴스 세그멘테이션 | SAM2 Hiera B+ (0.1B), PyTorch | GPU (VRAM ~2GB) |
| Classification Agent | Few-shot 분류, 임베딩 추출 | DINOv2-base (0.3B), PyTorch | GPU (VRAM ~2GB) |
| Object Registry | 객체 중심 데이터 관리, 벡터 검색 | SQLite, ChromaDB, FastAPI | CPU, Disk I/O |
| Labeling Agent | 라벨 CRUD (레거시, 통합 예정) | FastAPI, JSON 파일 | CPU |
| Training Agent | YOLO 모델 학습 | Ultralytics YOLOv8/v11, MLflow | GPU (VRAM ~6GB) |
| Evaluation Agent | 모델 평가 (mAP, confusion matrix) | Ultralytics, PyTorch | GPU |
| Data Manager | YOLO/COCO 포맷 내보내기 | FastAPI, Python | CPU |
| Redis | 결과 캐싱, 작업 큐 | Redis 7 | CPU, Memory |
| MLflow | 실험 추적, 모델 레지스트리 | MLflow 2.9 | CPU |
- 요청 수신: Gateway가 이미지, project_id, classes, confidence 수신
- 객체 감지: Detection Agent (Florence-2)가 bbox + labels 반환
- 인스턴스 세그멘테이션: Segmentation Agent (SAM2)가 bbox 기반 mask 생성
- 라벨 저장: Labeling Agent에 boxes, classes, masks 저장 (optional)
- 소스 등록: Object Registry에 source (이미지) 등록
- 배치 객체 등록: 모든 감지된 객체를 batch API로 등록
- SQLite에 메타데이터 저장 (bbox, confidence, category)
- 마스크 PNG 파일 저장 (sharded directory)
- 임베딩 outbox에 큐잉 후 ChromaDB 동기화
- 응답 반환: object_ids, source_id, 감지 통계 반환
- 임베딩 추출: Classification Agent (DINOv2)가 쿼리 이미지의 768d 임베딩 생성
- 벡터 검색: Object Registry가 ChromaDB에서 cosine similarity 기반 top-k 검색
- 메타데이터 조인: 검색된 object_id로 SQLite에서 전체 메타데이터 조회
- 필터 적용: category, min_confidence 필터 적용
- 결과 반환: 유사 객체 리스트 + similarity 점수
- 데이터셋 생성: filter_config, split_config로 데이터셋 정의
- 빌드: 필터 조건 (categories, min_confidence, is_validated)으로 객체 선택 → train/val/test 스플릿
- 내보내기: Data Manager가 YOLO/COCO 포맷으로 변환 (images/, labels/, data.yaml)
| 필드 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| source_id | TEXT | 고유 식별자 (src_{12자}) | PK |
| source_type | TEXT | 소스 타입 (image/video/text) | NOT NULL, CHECK |
| file_path | TEXT | 파일 경로 | - |
| file_name | TEXT | 파일명 | - |
| width | INTEGER | 이미지 너비 (px) | - |
| height | INTEGER | 이미지 높이 (px) | - |
| frame_count | INTEGER | 비디오 프레임 수 | - |
| fps | REAL | 비디오 FPS | - |
| metadata | JSON | 커스텀 메타데이터 | - |
| created_at | TIMESTAMP | 생성 시각 | DEFAULT NOW |
용도: 이미지/비디오 원본 파일 정보 관리. 모든 객체는 source_id로 연결.
| 필드 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| object_id | TEXT | 고유 식별자 (obj_{12자}) | PK |
| source_id | TEXT | 원본 소스 참조 | FK, NOT NULL |
| category_id | INTEGER | 카테고리 참조 | FK |
| frame_id | TEXT | 프레임 참조 (비디오용) | FK, nullable |
| project_id | TEXT | 프로젝트 참조 | - |
| bbox_x, bbox_y | REAL | 바운딩박스 좌상단 좌표 | NOT NULL |
| bbox_w, bbox_h | REAL | 바운딩박스 너비/높이 | NOT NULL |
| mask_path | TEXT | 마스크 파일 상대경로 | - |
| polygon | JSON | 폴리곤 좌표 (선택) | - |
| area | REAL | 객체 면적 (px^2) | - |
| confidence | REAL | 감지 신뢰도 (0.0-1.0) | - |
| detection_model | TEXT | 사용 모델명 (florence2) | - |
| classification_model | TEXT | 분류 모델명 | - |
| classification_confidence | REAL | 분류 신뢰도 | - |
| is_validated | BOOLEAN | 검수 여부 | DEFAULT FALSE |
| validated_by | TEXT | 검수자 ID | - |
| validated_at | TIMESTAMP | 검수 시각 | - |
| quality_score | REAL | 품질 점수 (0.0-1.0) | - |
| is_occluded | BOOLEAN | 가림 여부 | DEFAULT FALSE |
| is_truncated | BOOLEAN | 잘림 여부 | DEFAULT FALSE |
| is_difficult | BOOLEAN | 어려움 플래그 | DEFAULT FALSE |
| created_at | TIMESTAMP | 생성 시각 | DEFAULT NOW |
| updated_at | TIMESTAMP | 수정 시각 | DEFAULT NOW, Trigger |
용도: 감지된 개별 객체 인스턴스. 메타데이터, 기하 정보, 검증 상태 관리.
인덱스: source_id, category_id, project_id, confidence, is_validated, created_at
| 필드 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| category_id | INTEGER | 고유 식별자 | PK, AUTOINCREMENT |
| name | TEXT | 클래스명 (예: person, car) | UNIQUE, NOT NULL |
| supercategory | TEXT | 상위 카테고리 (예: vehicle) | - |
| synonyms | JSON | 동의어 리스트 | - |
| color | TEXT | 시각화 색상 (hex) | DEFAULT #808080 |
| description | TEXT | 설명 | - |
| created_at | TIMESTAMP | 생성 시각 | DEFAULT NOW |
용도: 클래스 정의 및 온톨로지 관리. YOLO class ID와 매핑.
| 필드 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| track_id | TEXT | 고유 식별자 (trk_{12자}) | PK |
| source_id | TEXT | 비디오 소스 참조 | FK, NOT NULL |
| category_id | INTEGER | 카테고리 참조 | FK |
| start_frame_idx | INTEGER | 시작 프레임 인덱스 | - |
| end_frame_idx | INTEGER | 종료 프레임 인덱스 | - |
| object_count | INTEGER | 객체 수 (자동 업데이트) | DEFAULT 0, Trigger |
| avg_confidence | REAL | 평균 신뢰도 | - |
| metadata | JSON | 커스텀 메타데이터 | - |
| created_at | TIMESTAMP | 생성 시각 | DEFAULT NOW |
용도: 비디오에서 동일 객체의 시간 시퀀스 관리. IoU/임베딩 기반 연결.
연결 테이블: track_objects (track_id, object_id, sequence_idx)
| 필드 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| dataset_id | TEXT | 고유 식별자 (ds_{12자}) | PK |
| name | TEXT | 데이터셋명 | NOT NULL |
| format | TEXT | 포맷 (yolo/coco) | DEFAULT yolo |
| version | TEXT | 버전 | DEFAULT 1.0 |
| split_config | JSON | 스플릿 비율 (train/val/test) | DEFAULT 0.8/0.1/0.1 |
| filter_config | JSON | 필터 조건 (categories, min_confidence) | - |
| category_mapping | JSON | 클래스 ID 매핑 | - |
| object_count | INTEGER | 객체 수 | DEFAULT 0 |
| export_path | TEXT | 내보내기 경로 | - |
| created_at | TIMESTAMP | 생성 시각 | DEFAULT NOW |
| exported_at | TIMESTAMP | 내보내기 시각 | - |
용도: 데이터셋 정의 및 내보내기 이력 관리.
연결 테이블: dataset_objects (dataset_id, object_id, split)
| 필드 | 타입 | 설명 | 제약조건 |
|---|---|---|---|
| id | INTEGER | 자동 증가 ID | PK, AUTOINCREMENT |
| object_id | TEXT | 객체 참조 | FK, NOT NULL |
| version_id | TEXT | 임베딩 버전 (dinov2_base_v1) | FK, NOT NULL |
| operation | TEXT | 작업 유형 (upsert/delete) | CHECK |
| embedding | BLOB | 임베딩 바이트 (768x4 bytes) | - |
| status | TEXT | 상태 (pending/synced/failed) | DEFAULT pending, CHECK |
| retry_count | INTEGER | 재시도 횟수 | DEFAULT 0 |
| error_message | TEXT | 오류 메시지 | - |
| created_at | TIMESTAMP | 생성 시각 | DEFAULT NOW |
| synced_at | TIMESTAMP | 동기화 시각 | - |
용도: SQLite → ChromaDB 임베딩 동기화 트랜잭션 아웃박스 패턴.
인덱스: status, object_id
┌───────────┐
│ Sources │───┐
└───────────┘ │
│ 1:N
▼
┌─────────┐ N:1 ┌────────────┐
│ Objects │◄──────────────│ Categories │
└─────────┘ └────────────┘
│
│ N:M (track_objects)
▼
┌─────────┐
│ Tracks │
└─────────┘
┌─────────┐
│ Objects │
└─────────┘
│
│ N:M (dataset_objects)
▼
┌──────────┐
│ Datasets │
└──────────┘
┌─────────┐ 1:1 ┌────────────────┐
│ Objects │──────────────▶│ Embedding │
└─────────┘ │ Outbox │
└────────────────┘
┌────────────────┐
│ ChromaDB │
│ objects_dinov2 │
│ (id=object_id) │
└────────────────┘
관계 설명:
- Source 1 → N Objects: 한 이미지/비디오에 여러 객체
- Category 1 → N Objects: 한 클래스에 여러 객체 인스턴스
- Track N ↔ M Objects: 트랙은 여러 객체 시퀀스 (track_objects 조인 테이블)
- Dataset N ↔ M Objects: 데이터셋은 필터링된 객체 서브셋 (dataset_objects 조인 테이블)
- Object 1 → 1 Embedding: outbox를 거쳐 ChromaDB에 동기화
| Method | Endpoint | 설명 |
|---|---|---|
| GET | /health | 게이트웨이 및 서비스 헬스체크 |
| POST | /detect | 객체 감지 (Detection Agent 프록시) |
| POST | /segment | 세그멘테이션 (Segmentation Agent 프록시) |
| POST | /detect_and_segment | 감지 + 세그멘테이션 파이프라인 |
| POST | /auto_label | 전체 자동 라벨링 파이프라인 |
| GET | /registry/stats | Registry 통계 (프록시) |
| GET | /registry/objects | 객체 검색 (프록시) |
| GET | /registry/categories | 카테고리 목록 (프록시) |
| Method | Endpoint | 설명 |
|---|---|---|
| POST | /sources | 소스 등록 |
| GET | /sources/{id} | 소스 조회 |
| POST | /objects | 단일 객체 등록 |
| POST | /objects/batch | 배치 객체 등록 |
| GET | /objects | 필터 검색 (category, project_id, min_confidence) |
| GET | /objects/{id} | 객체 조회 |
| PATCH | /objects/{id} | 객체 수정 (is_validated, quality_score) |
| DELETE | /objects/{id} | 객체 삭제 |
| GET | /objects/{id}/mask | 마스크 이미지 반환 (PNG) |
| POST | /objects/search/embedding | 임베딩 벡터 유사도 검색 |
| POST | /tracks | 트랙 생성 |
| GET | /tracks/{id} | 트랙 조회 |
| POST | /datasets | 데이터셋 생성 |
| POST | /datasets/{id}/build | 데이터셋 빌드 (필터 적용 + 스플릿) |
| GET | /categories | 카테고리 목록 |
| POST | /categories | 카테고리 생성 |
| POST | /sync/embeddings | 대기 중 임베딩 동기화 |
| GET | /stats | 통계 (객체 수, 카테고리별 분포) |
| Method | Endpoint | 설명 |
|---|---|---|
| GET | /health | 헬스체크 |
| POST | /detect | 객체 감지 (image, classes, confidence) |
| POST | /unload | 모델 언로드 (GPU 메모리 해제) |
| Method | Endpoint | 설명 |
|---|---|---|
| GET | /health | 헬스체크 |
| POST | /segment | bbox 기반 세그멘테이션 |
| POST | /segment_points | 포인트 프롬프트 기반 세그멘테이션 |
| POST | /unload | 모델 언로드 |
| Method | Endpoint | 설명 |
|---|---|---|
| GET | /health | 헬스체크 |
| POST | /support_set/load | Few-shot support set 로드 |
| POST | /classify | 이미지 분류 (threshold, top_k) |
| POST | /classify_batch | 배치 분류 |
| POST | /unload | 모델 언로드 |
설명: 전체 자동 라벨링 파이프라인 (감지 → 세그멘테이션 → 저장 → 등록)
Request (multipart/form-data)
image: UploadFile (이미지 파일)
project_id: str (프로젝트 ID)
image_id: str (이미지 고유 ID)
classes: str (쉼표 구분 클래스명, 예: "person,car,dog")
confidence: float = 0.5 (최소 신뢰도)
save: bool = true (labeling-agent에 저장 여부)
register: bool = true (object-registry에 등록 여부)
Response (200)
{
"success": true,
"data": {
"image_id": "img_001",
"detections": 3,
"boxes": [[10, 20, 100, 150], ...],
"labels": ["person", "car", "dog"],
"masks_count": 3,
"image_size": {"width": 1920, "height": 1080}
},
"saved": true,
"registered": true,
"registry": {
"source_id": "src_a1b2c3d4e5f6",
"object_ids": ["obj_x1y2z3", "obj_a4b5c6", "obj_d7e8f9"]
}
}Error Codes
| 코드 | 설명 |
|---|---|
| 400 | 잘못된 요청 (이미지 누락, classes 형식 오류) |
| 500 | 내부 서버 오류 (모델 추론 실패, DB 오류) |
| 503 | 서비스 연결 불가 (detection/segmentation agent down) |
처리 흐름:
- Gateway가 이미지 바이트 읽기
- Detection Agent 호출 → boxes, labels, scores 수신
- 감지 결과 없으면 early return
- Segmentation Agent 호출 (boxes 전달) → masks (base64) 수신
- (Optional) Labeling Agent에 저장
- Object Registry 호출:
- Source 등록 → source_id
- Batch Objects 등록 → object_ids
- 성공 응답 반환
설명: 동일 소스의 여러 객체를 배치로 등록 (트랜잭션 최적화)
Request
{
"source_id": "src_a1b2c3d4e5f6",
"project_id": "project_001",
"objects": [
{
"category": "person",
"bbox": [10, 20, 100, 150],
"confidence": 0.95,
"detection_model": "florence2",
"mask_base64": "iVBORw0KGgoAAAANSUhEUg...",
"embedding": [0.123, -0.456, ...]
},
{
"category": "car",
"bbox": [200, 300, 150, 100],
"confidence": 0.88,
"detection_model": "florence2",
"mask_base64": "iVBORw0KGgoAAAANSUhEUg..."
}
]
}Response (200)
{
"success": true,
"object_ids": ["obj_x1y2z3", "obj_a4b5c6"],
"count": 2
}구현 로직 (/home/coffin/dev/AgenticLabeling/services/object-registry/app/registry.py:202-260)
def register_objects_batch(source_id, objects_data, project_id):
# 1. 사전에 마스크 저장 (filesystem, 트랜잭션 외부)
# 2. 카테고리 ID 캐싱 (중복 쿼리 방지)
# 3. 배치 INSERT (단일 트랜잭션)
# 4. Embedding outbox 큐잉
# 5. 커밋 후 ChromaDB 동기화 (비동기 가능)성능 최적화:
- 카테고리 사전 조회 및 캐싱으로 N+1 쿼리 방지
- 마스크 파일 저장을 트랜잭션 전 처리
- 임베딩 동기화는 outbox 패턴으로 지연 처리
설명: 쿼리 임베딩과 유사한 객체 검색 (cosine similarity)
Request
{
"embedding": [0.123, -0.456, ..., 0.789], // 768차원 벡터
"top_k": 10,
"category": "person", // optional
"min_confidence": 0.7 // optional
}Response (200)
{
"success": true,
"data": [
{
"object_id": "obj_x1y2z3",
"category_name": "person",
"bbox_x": 10, "bbox_y": 20, "bbox_w": 100, "bbox_h": 150,
"confidence": 0.95,
"similarity": 0.92, // cosine similarity
"source_id": "src_a1b2c3",
"is_validated": true
},
...
],
"count": 10
}구현 로직 (/home/coffin/dev/AgenticLabeling/services/object-registry/app/registry.py:378-417)
def search_by_embedding(embedding, top_k, category, min_confidence):
# 1. ChromaDB에서 벡터 검색 (HNSW 인덱스)
# 2. 필터 조건 적용 (category, min_confidence)
# 3. SQLite에서 object_id로 전체 메타데이터 조인
# 4. 유사도 점수 추가 (1 - distance)벡터 검색 성능:
- ChromaDB HNSW 인덱스로 ANN (Approximate Nearest Neighbor)
- 1M 객체 기준 < 100ms 응답 목표
- Metadata filter는 ChromaDB에서 사전 필터링
설명: 필터 조건으로 객체 선택 후 train/val/test 스플릿
Request (데이터셋 생성 시 설정된 filter_config, split_config 사용)
{
"filter_config": {
"categories": ["person", "car"],
"min_confidence": 0.7,
"is_validated": true,
"project_id": "project_001"
},
"split_config": {
"train": 0.8,
"val": 0.1,
"test": 0.1
}
}Response (200)
{
"dataset_id": "ds_abc123",
"object_count": 1000,
"splits": {
"train": 800,
"val": 100,
"test": 100
}
}구현 로직 (/home/coffin/dev/AgenticLabeling/services/object-registry/app/registry.py:556-639)
def build_dataset(dataset_id):
# 1. 필터 조건으로 WHERE 절 구성
# 2. RANDOM() 정렬로 객체 셔플
# 3. 스플릿 비율로 object_ids 분할
# 4. dataset_objects 테이블에 INSERT (split 컬럼)
# 5. 통계 업데이트Florence-2 Detection (전체 구현: /home/coffin/dev/AgenticLabeling/services/detection-agent/app/detector.py)
def detect(image_bytes, classes, confidence):
# 1. Lazy loading (싱글톤 패턴)
# 2. CAPTION_TO_PHRASE_GROUNDING 태스크
# 3. 프롬프트: "A photo of person, and car, and dog."
# 4. 결과 파싱 후 요청 클래스만 필터링
# 5. bbox는 [x, y, w, h] 포맷특징:
- Zero-shot grounding (사전 학습 없이 텍스트로 클래스 지정)
- Florence-2는 신뢰도 점수 미제공 → 1.0으로 고정
- float32 명시적 사용 (dtype mismatch 방지)
SAM2 Segmentation (전체 구현: /home/coffin/dev/AgenticLabeling/services/segmentation-agent/app/segmenter.py)
def segment(image_bytes, boxes):
# 1. Hiera B+ 모델 로드 (bbox prompt 지원)
# 2. 각 bbox를 prompt로 변환
# 3. SAM2 predict (고품질 mask 생성)
# 4. Mask를 PNG 인코딩 후 base64 변환
# 5. 마스크별 면적 계산특징:
- Prompt-based segmentation (bbox/point/mask 프롬프트)
- 인스턴스별 독립 처리 (배치 처리 미지원)
- VRAM 효율적 (Hiera B+ 백본)
DINOv2 Embedding (전체 구현: /home/coffin/dev/AgenticLabeling/services/classification-agent/app/classifier.py)
class FeatureExtractor:
def extract_features(image):
# 1. AutoImageProcessor로 전처리
# 2. DINOv2 forward pass
# 3. pooler_output 추출 (768차원)
# 4. L2 정규화
return features.cpu().numpy().flatten()Few-shot 분류:
class CosineSimilarityClassifier:
def classify(image_bytes, threshold, top_k):
# 1. 쿼리 임베딩 추출
# 2. Support set과 코사인 유사도 계산
# 3. 클래스별 평균 유사도
# 4. threshold 적용 (미만이면 "Unknown")
# 5. Margin 계산 (confidence level)Hybrid Storage Sync (전체 구현: /home/coffin/dev/AgenticLabeling/services/object-registry/app/registry.py:666-743)
def register_object(source_id, category, bbox, embedding):
# SQLite 트랜잭션
with conn:
# 1. Objects 테이블 INSERT
# 2. Embedding outbox에 큐잉 (BLOB)
conn.commit()
# ChromaDB 동기화 (트랜잭션 외부)
_sync_embedding(object_id, embedding)Outbox 패턴:
- SQLite와 ChromaDB 간 eventual consistency 보장
- 실패 시 재시도 메커니즘 (retry_count < 3)
- 배치 동기화 API (/sync/embeddings)로 대량 처리
| 패키지 | 버전 | 용도 |
|---|---|---|
| transformers | >=4.37.0 | Florence-2 모델 로드 |
| torch | >=2.0.0 | PyTorch 백엔드 |
| Pillow | >=10.0.0 | 이미지 처리 |
| fastapi | >=0.104.0 | REST API |
| 패키지 | 버전 | 용도 |
|---|---|---|
| sam2 | custom | SAM2 모델 (Facebook Research) |
| torch | >=2.0.0 | PyTorch 백엔드 |
| opencv-python | >=4.8.0 | 이미지 전처리 |
| 패키지 | 버전 | 용도 |
|---|---|---|
| transformers | >=4.37.0 | DINOv2 모델 |
| torch | >=2.0.0 | PyTorch 백엔드 |
| clip | custom | CLIP 모델 (선택) |
| torchvision | >=0.15.0 | ResNet (선택) |
| 패키지 | 버전 | 용도 |
|---|---|---|
| fastapi | >=0.104.0 | REST API |
| chromadb | >=0.4.22 | 벡터 DB |
| numpy | >=1.24.0 | 임베딩 처리 |
| Pillow | >=10.0.0 | 마스크 이미지 처리 |
- 인증/인가: 현재 미구현 (프로덕션 배포 전 JWT/OAuth2 추가 필요)
- 입력값 검증: Pydantic 모델로 타입 및 제약조건 검증
- SQL Injection 방지: 파라미터화된 쿼리 사용 (sqlite3 placeholders)
- 파일 업로드 보안:
- 파일 타입 검증 (PIL로 이미지 검증)
- 파일명 해싱 (MD5 기반 sharding)
- 업로드 크기 제한 (FastAPI 설정)
- Rate Limiting: 현재 미구현 (Redis 기반 rate limiter 추가 권장)
- CORS 설정: Gateway에 CORSMiddleware 적용 (allow_origins=["*"] → 프로덕션에서 제한 필요)
- 민감 정보 암호화:
- 환경변수로 DB 경로 관리
- ChromaDB 암호화 미지원 (Qdrant 마이그레이션 시 TLS 적용)
- 에러 메시지 정제: 스택 트레이스 노출 방지 (Exception 핸들러)
-
API Gateway 보안:
- JWT 토큰 기반 인증 (프로젝트별 권한 관리)
- API 키 발급 (서비스 간 통신)
- TLS/HTTPS 강제
-
데이터 보안:
- SQLite → Postgres (row-level security)
- ChromaDB → Qdrant (TLS, authentication)
- 마스크 파일 암호화 (sensitive data)
-
네트워크 보안:
- 내부 네트워크 격리 (Gateway만 외부 노출)
- 서비스 간 mTLS (mutual TLS)
| 메트릭 | 목표 | 측정 방법 |
|---|---|---|
| 단일 이미지 처리 | < 3초 (detect + segment) | Gateway /auto_label 응답 시간 |
| 객체 검색 응답 | < 100ms (1M 객체 기준) | ChromaDB 벡터 검색 |
| 배치 처리량 | > 100 이미지/분 | GPU 병렬 처리 |
| 동시 요청 처리 | 10 concurrent requests | FastAPI async workers |
문제: 여러 모델이 동일 GPU 공유 시 VRAM 부족 해결책:
- 모델별 전용 GPU 할당 (Docker Compose device mapping)
- 동적 모델 로딩/언로딩 (/unload 엔드포인트)
- Float16 정밀도 사용 (정확도 허용 범위 내)
문제: 다중 스레드 쓰기 시 LOCKED 오류 해결책 (현재 적용):
- WAL 모드 활성화 (readers 병렬 처리)
- busy_timeout=30초 설정
- 배치 INSERT로 트랜잭션 최소화
향후 마이그레이션: Postgres로 전환 시 MVCC로 완전 동시성 지원
문제: 개별 객체마다 upsert 시 느림 해결책 (현재 적용):
- Outbox 패턴으로 배치 동기화
- 트랜잭션 커밋 후 비동기 처리
향후 마이그레이션: Qdrant cluster로 분산 처리
문제: HTTP 오버헤드 누적 해결책:
- 파이프라인 내 HTTP 재사용 (httpx.AsyncClient)
- 이미지 재전송 최소화 (메모리 캐싱)
향후 개선: gRPC 전환으로 직렬화 오버헤드 감소
# Detection Agent: HPA (Horizontal Pod Autoscaler)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: detection-agent
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: detection-agent
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: nvidia.com/gpu
target:
type: Utilization
averageUtilization: 70스케일링 기준:
- GPU 사용률 > 70% → 레플리카 증가
- 요청 큐 길이 > 100 → 레플리카 증가
- Redis 작업 큐 기반 오토스케일링
# Postgres + Qdrant StatefulSet
# 1. Postgres Primary-Replica 구성
# 2. Qdrant 3-node cluster (shard replication)
# 3. PVC (Persistent Volume Claim)로 데이터 영속성# 감지 결과 캐싱 (이미지 해시 기반)
cache_key = f"detect:{image_hash}:{classes}:{confidence}"
if cached := redis.get(cache_key):
return json.loads(cached)
# ... 모델 추론 ...
redis.setex(cache_key, ttl=3600, value=json.dumps(result))캐싱 대상:
- Detection 결과 (이미지 해시 + 파라미터)
- Classification support set features
- 자주 조회되는 객체 메타데이터
- 동시성 처리 향상 (MVCC)
- 트랜잭션 격리 수준 제어
- JSON 쿼리 성능 (JSONB)
- 복제 및 백업 자동화
1단계: 스키마 변환
-- SQLite schema.sql → PostgreSQL DDL
-- 변경 사항:
-- - AUTOINCREMENT → SERIAL
-- - JSON → JSONB
-- - TIMESTAMP DEFAULT CURRENT_TIMESTAMP → TIMESTAMPTZ DEFAULT NOW()
-- - CHECK 제약조건 유지2단계: 데이터 마이그레이션
# 마이그레이션 스크립트: scripts/migrate_sqlite_to_postgres.py
import sqlite3
import psycopg2
def migrate():
# 1. SQLite에서 테이블별 dump
# 2. Postgres COPY 명령으로 bulk insert
# 3. 시퀀스 재설정 (category_id 등)
# 4. 외래키 제약조건 재생성
# 5. 인덱스 재생성 (병렬 생성)3단계: Application 코드 수정
# Before (SQLite)
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
# After (PostgreSQL)
import psycopg2.extras
conn = psycopg2.connect(dsn)
cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)4단계: 배포 전략
- Blue-Green 배포
- Postgres replica에 데이터 동기화
- Read-only 모드로 검증
- 트래픽 전환 (DNS/Load Balancer)
- SQLite 백업 유지 (7일)
- 마이그레이션 중 dual-write (SQLite + Postgres)
- 검증 실패 시 SQLite로 롤백
- 프로덕션 안정성 (클러스터 지원)
- 성능 향상 (Rust 기반, 더 빠른 HNSW)
- 멀티테넌시 지원
- 정확한 메타데이터 필터링
1단계: Qdrant 클러스터 구성
# docker-compose.qdrant.yml
services:
qdrant-node1:
image: qdrant/qdrant:v1.7.0
ports:
- "6333:6333"
environment:
- QDRANT_CLUSTER__ENABLED=true
- QDRANT_CLUSTER__P2P__PORT=63352단계: 컬렉션 생성
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams
client = QdrantClient(url="http://qdrant:6333")
client.create_collection(
collection_name="objects_dinov2_v1",
vectors_config=VectorParams(size=768, distance=Distance.COSINE),
hnsw_config={"m": 16, "ef_construct": 100},
)3단계: 데이터 마이그레이션
# 마이그레이션 스크립트: scripts/migrate_chroma_to_qdrant.py
def migrate():
# 1. ChromaDB에서 전체 벡터 export
# 2. Qdrant batch upsert (1000개씩)
# 3. 메타데이터 매핑 (payload 구조 변환)
# 4. 인덱스 최적화 (optimize)4단계: Application 코드 수정
# Before (ChromaDB)
collection.query(
query_embeddings=[embedding],
n_results=10,
where={"category": "person"}
)
# After (Qdrant)
from qdrant_client.models import Filter, FieldCondition, MatchValue
client.search(
collection_name="objects_dinov2_v1",
query_vector=embedding,
limit=10,
query_filter=Filter(
must=[FieldCondition(key="category", match=MatchValue(value="person"))]
)
)5단계: 성능 벤치마크
- Latency: p50, p95, p99 측정
- Throughput: QPS (Queries Per Second)
- Recall@10 정확도 검증
- ChromaDB 데이터 유지
- Feature flag로 스토리지 선택
- A/B 테스트로 점진적 전환
# Dual-write 패턴 (마이그레이션 중)
class HybridVectorStore:
def __init__(self):
self.chroma = ChromaClient()
self.qdrant = QdrantClient()
self.use_qdrant = os.getenv("USE_QDRANT", "false") == "true"
def upsert(self, object_id, embedding, metadata):
if self.use_qdrant:
self.qdrant.upsert(...)
else:
self.chroma.upsert(...)
# Background sync for validation
if os.getenv("DUAL_WRITE", "false") == "true":
self._sync_to_other(object_id, embedding, metadata)| 대상 | 테스트 케이스 | 상태 |
|---|---|---|
| Florence2Detector.detect | 빈 이미지 처리 | - [ ] |
| Florence2Detector.detect | 클래스 필터링 정확도 | - [ ] |
| SAM2Segmenter.segment | bbox 경계 케이스 (이미지 밖) | - [ ] |
| FeatureExtractor.extract_features | 임베딩 차원 검증 (768d) | - [ ] |
| CosineSimilarityClassifier.classify | Threshold 경계값 테스트 | - [ ] |
| ObjectRegistry.register_objects_batch | 트랜잭션 롤백 시나리오 | - [ ] |
| ObjectRegistry.search_by_embedding | 빈 결과 처리 | - [ ] |
테스트 프레임워크: pytest, pytest-asyncio
| 시나리오 | 상태 |
|---|---|
| Gateway → Detection → Segmentation 파이프라인 | - [ ] |
| auto_label 전체 플로우 (등록 포함) | - [ ] |
| 배치 객체 등록 후 검색 | - [ ] |
| 임베딩 동기화 (outbox → ChromaDB) | - [ ] |
| 데이터셋 빌드 및 스플릿 검증 | - [ ] |
| 트랙 생성 및 객체 연결 | - [ ] |
테스트 환경: Docker Compose 테스트 스택 (in-memory DB)
# tests/performance/test_throughput.py
import asyncio
from locust import HttpUser, task, between
class AgenticLabelingUser(HttpUser):
wait_time = between(1, 3)
@task
def auto_label(self):
files = {"image": open("test_image.jpg", "rb")}
data = {
"project_id": "test",
"image_id": f"img_{uuid.uuid4()}",
"classes": "person,car",
"confidence": 0.5
}
self.client.post("/auto_label", files=files, data=data)벤치마크 대상:
- 동시 요청 처리 (10, 50, 100 users)
- GPU 메모리 사용률
- 객체 검색 레이턴시 (1K, 10K, 100K, 1M 객체)
| 환경 | 데이터베이스 | 벡터 DB | GPU | 복제 |
|---|---|---|---|---|
| Dev | SQLite (로컬 파일) | ChromaDB (embedded) | 1x GPU | 단일 인스턴스 |
| Staging | Postgres (RDS) | Qdrant (3-node) | 2x GPU | 2 replicas |
| Prod | Postgres (Primary-Replica) | Qdrant cluster | 5x GPU | 5 replicas + HPA |
현재 구성 (/home/coffin/dev/AgenticLabeling/docker-compose.yml):
services:
gateway:
ports: ["8000:8000"]
depends_on: [detection-agent, segmentation-agent, object-registry, redis]
detection-agent:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
volumes:
- model-cache:/root/.cache # 모델 가중치 캐싱시작 순서:
- redis, mlflow (인프라)
- object-registry (DB 초기화)
- detection-agent, segmentation-agent, classification-agent (GPU 모델 로드)
- gateway (헬스체크 대기)
apiVersion: apps/v1
kind: Deployment
metadata:
name: detection-agent
spec:
replicas: 3
selector:
matchLabels:
app: detection-agent
template:
spec:
containers:
- name: detection
image: agenticlabeling/detection-agent:v1.0
resources:
limits:
nvidia.com/gpu: 1
memory: 8Gi
requests:
memory: 4Gi
env:
- name: TORCH_DTYPE
value: "float16"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30apiVersion: apps/v1
kind: StatefulSet
metadata:
name: object-registry
spec:
serviceName: object-registry
replicas: 3
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi# alembic/versions/001_initial_schema.py
alembic upgrade head# scripts/backfill_embeddings.py
# SQLite에서 임베딩 누락 객체 추출 → Classification Agent 호출 → 재등록
python scripts/backfill_embeddings.py --batch-size 100GPU 메트릭 (DCGM Exporter):
- GPU 사용률, 메모리 사용량
- 추론 레이턴시 (히스토그램)
Application 메트릭 (FastAPI middleware):
- 요청 처리 시간 (endpoint별)
- 에러율 (4xx, 5xx)
- 동시 요청 수
데이터베이스 메트릭:
- SQLite: 쿼리 실행 시간, lock wait time
- ChromaDB/Qdrant: 벡터 검색 레이턴시, 인덱스 크기
구조화된 로그 (JSON 포맷):
import structlog
logger = structlog.get_logger()
logger.info("object_registered",
object_id=obj_id,
category=category,
confidence=confidence,
elapsed_ms=elapsed)로그 레벨:
- DEBUG: 모델 추론 상세 (개발 환경)
- INFO: 요청 처리 완료
- WARNING: 임베딩 동기화 실패 (재시도 대기)
- ERROR: 예외 발생 (스택 트레이스 포함)
| 버전 | 날짜 | 작성자 | 변경 내용 |
|---|---|---|---|
| v1.0 | 2025-12-29 | System Architect | 최초 작성 |
AgenticLabeling/
├── services/
│ ├── gateway/ # API 게이트웨이
│ │ ├── app/
│ │ │ └── main.py # 라우팅, 파이프라인 조율
│ │ ├── Dockerfile
│ │ └── requirements.txt
│ │
│ ├── detection-agent/ # Florence-2 감지
│ │ ├── app/
│ │ │ ├── main.py
│ │ │ ├── detector.py # 핵심 로직
│ │ │ └── schemas.py
│ │ ├── Dockerfile
│ │ └── requirements.txt
│ │
│ ├── segmentation-agent/ # SAM2 세그멘테이션
│ │ ├── app/
│ │ │ ├── main.py
│ │ │ ├── segmenter.py # 핵심 로직
│ │ │ └── schemas.py
│ │ ├── Dockerfile
│ │ └── requirements.txt
│ │
│ ├── classification-agent/ # DINOv2 분류
│ │ ├── app/
│ │ │ ├── main.py
│ │ │ ├── classifier.py # Few-shot 로직
│ │ │ └── schemas.py
│ │ ├── Dockerfile
│ │ └── requirements.txt
│ │
│ ├── object-registry/ # 객체 중심 관리
│ │ ├── app/
│ │ │ ├── main.py # REST API
│ │ │ ├── registry.py # 핵심 로직
│ │ │ └── schema.sql # DB 스키마
│ │ ├── Dockerfile
│ │ └── requirements.txt
│ │
│ ├── labeling-agent/ # 라벨 CRUD (레거시)
│ ├── training-agent/ # YOLO 학습
│ ├── data-manager/ # 데이터셋 내보내기
│ └── evaluation-agent/ # 모델 평가
│
├── data/
│ ├── registry/
│ │ ├── registry.db # SQLite
│ │ ├── chroma/ # ChromaDB
│ │ └── masks/ # 마스크 PNG (sharded)
│ │ ├── a1/
│ │ │ └── b2/
│ │ │ └── obj_xyz.png
│ ├── sources/ # 원본 이미지/비디오
│ └── exports/ # 내보낸 데이터셋
│
├── docs/
│ ├── tech-specs/
│ │ └── architecture-spec.md # 이 문서
│ ├── PROJECT_SPEC.md
│ └── plans/
│
├── scripts/
│ ├── migrate_sqlite_to_postgres.py
│ └── migrate_chroma_to_qdrant.py
│
├── k8s/ # Kubernetes 매니페스트 (예정)
│ ├── deployments/
│ ├── services/
│ └── statefulsets/
│
├── docker-compose.yml # 로컬 개발 환경
└── tests/
├── unit/
├── integration/
└── performance/
-
모델 문서:
-
인프라:
-
설계 패턴:
문서 작성: 2025-12-29 검토 요청: Service Architect, Detection Expert, Database Expert 승인 대기: Tech Lead