# 모델 벤치마크 — 단계별 GPT vs Gemini Pro 비교

> 테스트 일자: 2026-03-24
> 테스트 시나리오: SRD Part 1 (24,941자, 50~51씬), 미아 EP1 (45,117자, 115~119씬)
> 비교 모델: GPT-5.5 vs Gemini 3.1 Pro Preview

## 테스트 방법

### 1. 전체 캐스케이드 비교 (6단계 모델 교체 → 하류 전파)

**목적**: 6개 대규모 입력 GPT 단계를 gemini-pro로 교체했을 때 전체 파이프라인에 미치는 영향.

**방법**:
1. 기존 프로젝트의 GPT 결과를 `tests/model_comparison/{scenario}/`에 백업
2. `run_gemini_cascade.py` 스크립트로 step 4(visual_world_rules)부터 step 20(scene_verify)까지 15단계를 force 재실행
3. 6개 대상 단계는 gemini-pro, 나머지는 기본 모델 유지
4. 결과를 `tests/model_comparison/{scenario}_gemini/`에 저장
5. GPT vs Gemini 체크포인트를 단계별로 비교

**교체 대상 6단계**:
- `visual_world_rules` (GPT → gemini-pro)
- `entity_extract_character` (GPT → gemini-pro)
- `entity_extract_location` (GPT → gemini-pro)
- `entity_extract_prop` (GPT → gemini-pro)
- `scene_cinematography` (GPT → gemini-pro)
- `scene_dependency` (GPT → gemini-pro)

**캐스케이드 재실행 단계** (총 15단계):
```
visual_world_rules → scene_summary → entity_extract_character →
entity_extract_location → entity_extract_prop → entity_filter →
entity_review → entity_detail → entity_t2i → [DB sync] →
scene_director → scene_cinematography → scene_dependency →
outlook_extraction → scene_detail → scene_verify
```

**소요 시간**: SRD 18.9분, 미아 23.7분 (총 42.6분)

### 2. 개별 단계 비교 (scene_detail)

**목적**: scene_detail을 GPT vs Gemini Pro로 각각 실행하여 프롬프트 품질 비교.

**방법**:
1. 새 프로젝트(SRD v5, 미아 v2)에서 기본 모델(GPT)로 전체 분석 완료
2. GPT scene_detail 체크포인트를 `tests/model_comparison/scene_detail_comparison/`에 백업
3. `project_config={"scene_detail": {"model": "gemini-pro"}}`로 오버라이드하여 StepRunner.run(mode="force") 실행
4. Gemini 결과도 같은 디렉토리에 백업
5. 프롬프트 길이, 엔티티 사용, VE 위반 등 비교

### 3. 이전 세션 비교 (scene_director, outlook_extraction)

**목적**: 이전 v3 개발 세션에서 GPT vs Gemini Pro 직접 비교 (기존 확인된 결과 재확인).

**방법**: 동일 프로젝트에서 모델만 교체하여 개별 단계 재실행 후 결과 비교.

---

## 비교 결과

### visual_world_rules

| | SRD (GPT) | SRD (Gemini) | 미아 (GPT) | 미아 (Gemini) |
|---|---|---|---|---|
| rules 수 | **16** | 6 | **10** | 5 |
| director_notes 수 | **8** | 4 | **8** | 3 |
| era | 근미래 | 근미래 (디스토피아) | 현대, 2020년대 | 현대 |

**승자: GPT**

- GPT가 2~3배 더 많은 규칙과 director_notes 추출
- director_notes는 scene_director의 물리적 존재 판단 기준 — 누락 시 VE 오분류 위험
- SRD에서 GPT가 잡은 Gemini 누락 케이스:
  - 동녘 특이 케이스 (한 몸에 의식 2개)
  - 클론 교체 (옛 몸과 새 몸 동시 존재)
  - 멜트다운 시체 (물리적 충돌 대상)
  - 행사장 스크린 (영상 vs 실제 인물 구분)
- 미아에서 GPT가 잡은 Gemini 누락 케이스:
  - 가명 사용 판단 (박수영 = 숨이의 가명)
  - 신분 교체 본질 (법적 절차, 초자연 아님)
  - 뉴스 위장 구분 (허위 신고 가능)
  - 희림 시신 은닉 기준

### entity_extract_character

| | SRD (GPT) | SRD (Gemini) | 미아 (GPT) | 미아 (Gemini) |
|---|---|---|---|---|
| 인물 수 | 16 | **20** | **11** | 10 |
| 네이밍 | 일관적 | 단역 포함 | 풀네임 (강주현) | 짧은이름 (주현) |

**승자: 무승부 (GPT 약간 우세)**

- SRD: Gemini가 보좌관1/2, 사마엘, 헌병1 등 단역까지 발견 (20명 vs 16명)
  - 단, 추가 인물 중 다수는 entity_filter에서 제거됨 (Gemini removed 4 vs GPT removed 0)
- 미아: GPT가 풀네임("강주현") 사용, Gemini는 짧은이름("주현") — 풀네임이 더 정확
- 공통 인물 수는 비슷 (SRD 13명, 미아 대부분 공통)

### entity_extract_location

| | SRD (GPT) | SRD (Gemini) | 미아 (GPT) | 미아 (Gemini) |
|---|---|---|---|---|
| 배경 수 | 12 | 14 | 9 | **12** |

**승자: Gemini 약간 우세**

- 미아에서 Gemini가 더 세밀하게 장소 분리 (12 vs 9)
  - Gemini 추가: 다리 위, 브런치 카페, 오피스텔, 차 안
- SRD에서는 비슷한 수, 다른 네이밍 (14 vs 12)
- 다만 SRD에서 이름이 크게 달라 VE 비교 시 전씬 차이 발생

### entity_extract_prop

| | SRD (GPT) | SRD (Gemini) | 미아 (GPT) | 미아 (Gemini) |
|---|---|---|---|---|
| 소품 수 | 8 | 8 | **8** | 6 |

**승자: GPT**

- SRD: 같은 수(8개), 같은 소품을 다른 이름으로 추출 (실질 무승부)
  - "1인 캡슐" vs "인체 보관 캡슐", "군용 헬기" vs "군용 헬리콥터"
- 미아: GPT가 서사 핵심 소품을 더 잘 잡음 (8 vs 6)
  - GPT만 추출: 위조 서류 묶음, 정숨이 다이어리, 차량 단기렌트 확인증, 과도
  - Gemini만 추출: 박카스 상자, 스타킹 포장물 (시각적이지만 서사 중요도 낮음)

### scene_cinematography

| | SRD | 미아 |
|---|---|---|
| Shot 차이 씬 | 48/51 (94%) | 105/119 (88%) |
| 완전 동일 씬 | 3/51 | ~14/119 |

**승자: 무승부 (GPT 약간 우세)**

- 거의 모든 씬에서 다른 선택
- GPT: 교과서적 영화 문법 (대화→two_shot, 권위자→low_angle, 추격→tracking)
- Gemini: 실험적, 다양한 조합 (split_lighting, dutch_angle 등)
- 이 단계는 최종 이미지 품질에 미치는 영향이 크지 않음
- **gpt-mini로 다운그레이드 가능** (목록에서 2개 고르기 수준의 작업)

### scene_dependency

| | SRD | 미아 |
|---|---|---|
| 차이 있는 씬 | 23/51 (45%) | 63/119 (53%) |

**승자: GPT**

- GPT의 핵심 강점: **장소 연속성(location_refs) 파악**
  - "블랙마켓 → 초이스유리방 → 강사장사무실 → 비밀실험실" 같은 권역 연결
  - "동녘의 집 → 동녘의 방 → 집 앞" 연결
  - "화장터" 재방문 인식
- Gemini: location_refs를 빈 배열[]로 자주 비움
  - 같은 건물 내 다른 방도 "장소 연관 없음"으로 처리 (명백한 오류)
- Gemini: character_refs에서 직전 씬 참조가 더 정확한 경향

### scene_director (이전 세션 결과)

| | GPT-5.5 | Gemini 3.1 Pro |
|---|---|---|
| SRD 소울라이드 오분류 | 씬3~8에서 강의원 물리적 존재로 오판 | 정확하게 제거 |
| 미아 씬 누락 | — | — |

**승자: Gemini Pro (확정)**

- GPT-5.5는 소울라이드/빙의 상황에서 조종자를 물리적 존재로 오분류
- Gemini Pro + director_notes 조합으로 정확한 판단
- 현재 기본 모델: gemini-pro (변경 불필요)

### outlook_extraction (이전 세션 + 캐스케이드 결과)

| | SRD (GPT) | SRD (Gemini) | 미아 (GPT) | 미아 (Gemini) |
|---|---|---|---|---|
| 아웃룩 수 | 16 | 16 | 19 | **25** |
| 씬 배정 수 | 49 | 49 | 75 | **117** |
| 누락 씬 | 0 | 0 | **44씬 누락** | 0 |

**승자: Gemini Pro (확정, 압도적)**

- SRD: 동일 (16개, 49배정)
- 미아 (119씬): GPT 75배정(44씬 누락) vs Gemini **117배정(누락 0)**
  - 31% 더 많은 아웃룩 (25 vs 19)
  - 56% 더 많은 배정 (117 vs 75)
- 대규모 씬(100+)에서 GPT가 후반부 씬을 누락하는 경향
- 현재 기본 모델: gemini-pro (변경 불필요)

### scene_detail

| | SRD (GPT) | SRD (Gemini) | 미아 (GPT) | 미아 (Gemini) |
|---|---|---|---|---|
| T2I 프롬프트 평균 길이 | **573자** | 394자 | **553자** | 386자 |
| representative_moment | **133자** | 50자 | **115자** | 47자 |
| 엔티티 ID 사용 | **443번** | 398번 | **781번** | 737번 |
| VE 위반 retry | ~2씬 | ~2씬 | 거의 없음 | **51씬** |

**승자: GPT (확정)**

- GPT 프롬프트가 1.4배 더 김 (573 vs 394자) — 카메라 앵글, 조명, 인물 배치까지 상세
- representative_moment가 2.5배 더 상세 (133 vs 50자)
- 미아에서 Gemini L01 VE 위반 51씬 — 불필요한 retry + 강제 제거 (API 호출 2배)
- 현재 기본 모델: gpt (변경 불필요)

### entity_review (캐스케이드 결과)

| | SRD (GPT→Gemini) | 미아 (GPT→Gemini) |
|---|---|---|
| GPT 불필요 판정 | 1개 | 0개 |
| Gemini 불필요 판정 | 4개 | 0개 |

현재 기본 모델: gemini-pro (교차 검증 목적 — 다른 모델로 검증하는 것이 핵심)

---

## 최종 모델 배치 (확정)

| 단계 | 기본 모델 | 근거 |
|------|----------|------|
| scene_segmentation | gemini-flash | LLM regex 파서 정의 — 1차에서 성공 (GPT는 2차 필요) |
| episode_summary | gpt-mini | 단순 요약, 큰 모델 불필요 |
| **visual_world_rules** | **gpt** | rules 2~3배 + director_notes 2배 더 상세, 엣지 케이스 커버 |
| scene_split | gemini-flash | 단순 분할 |
| scene_save | - | LLM 미사용 |
| scene_summary | gpt-mini | 씬별 요약 |
| **entity_extract_character** | **gpt** | 풀네임 네이밍 일관성 |
| **entity_extract_location** | **gpt** | entity_extract 3종 동일 step명 공유, prop에서 GPT 우세 |
| **entity_extract_prop** | **gpt** | 서사 핵심 소품 추출 우수 (위조서류, 다이어리 등) |
| entity_filter | gpt-mini | 저빈도 필터링 |
| **entity_review** | **gemini-pro** | 교차 검증 (다른 모델 사용이 핵심) |
| entity_detail | gpt | 상세 기술 |
| entity_t2i | gemini-pro | T2I 프롬프트 생성 (description / visual_traits 는 source detail forward — LLM 의 unsourced trait 차단) |
| **scene_director** | **gemini-pro** | 소울라이드/빙의 물리적 존재 판별 정확 (GPT 오분류) |
| **scene_cinematography** | **gpt** | 교과서적 영화 문법, gpt-mini 다운그레이드 가능 |
| **scene_dependency** | **gpt** | 장소 연속성 파악 우수 (Gemini는 빈 배열 빈발) |
| **outlook_extraction** | **gemini-pro** | 119씬에서 GPT 44씬 누락 vs Gemini 0 누락 |
| **scene_detail** | **gpt** | 프롬프트 1.4배 더 상세, VE 위반 적음 |
| scene_verify | gpt | 교차 검증 |

---

## 비교 데이터 위치

```
tests/model_comparison/
├── srd_ep1/                       # SRD GPT 체크포인트 (19개)
├── srd_ep1_gemini/                # SRD Gemini 캐스케이드 결과 (15개)
│   └── comparison_report.txt
├── srd_ep1_gpt_full_backup/       # SRD 전체 체크포인트 백업 (22개)
├── mia_ep1/                       # 미아 GPT 체크포인트 (17개)
├── mia_ep1_gemini/                # 미아 Gemini 캐스케이드 결과 (15개)
│   └── comparison_report.txt
├── mia_ep1_gpt_full_backup/       # 미아 전체 체크포인트 백업 (18개)
├── scene_detail_comparison/       # scene_detail GPT vs Gemini 비교
│   ├── srd_scene_detail_gpt.json
│   ├── srd_scene_detail_gemini.json
│   ├── mia_scene_detail_gpt.json
│   └── mia_scene_detail_gemini.json
├── run_gemini_cascade.py          # 캐스케이드 비교 실행 스크립트
└── run_gemini_comparison.py       # 개별 단계 비교 스크립트 (초기 버전)
```

## 재현 방법

### 캐스케이드 비교 실행
```bash
cd backend
.venv/bin/python ../tests/model_comparison/run_gemini_cascade.py --scenario srd_ep1 --dry-run  # 계획 확인
.venv/bin/python ../tests/model_comparison/run_gemini_cascade.py --scenario srd_ep1             # 실행
.venv/bin/python ../tests/model_comparison/run_gemini_cascade.py --scenario srd_ep1 --compare   # 비교만
```

### 개별 단계 비교 (scene_detail 예시)
```python
from app.core.database import SessionLocal
from app.core.steps import STEP_CLASSES

db = SessionLocal()
project_config = {"scene_detail": {"model": "gemini-pro"}}  # 모델 오버라이드

cls = STEP_CLASSES["scene_detail"]
runner = cls(
    step_id="scene_detail",
    project_id=PROJECT_ID,
    episode_id=EPISODE_ID,
    db=db,
    project_config=project_config,
)
result = runner.run(mode="force")  # 강제 재실행
```
