# task-2987 — 인슈위키 유튜브 임베딩 Gemini → Claude Sonnet 전환 (사전 확인 보고)

- **작업 ID**: task-2987 · **팀**: dev4-team (비슈누) — 원 배정 dev1 미수신으로 재배정
- **작성**: 2026-08-20
- **대상 저장소**: `/home/jay/projects/insuwiki` (조사 기준 트리: `/home/jay/.worktrees/task-2980-dev1`, HEAD `a6710f7`)
- **판정**: **사전 확인 3항목 완료 · 구현 미착수 · ANU/회장 결정 대기**
- **코드 변경**: **0줄** (task md 지시대로 승인 전 구현 금지)
- **결론 요지**: **Claude/Sonnet 계열 임베딩 전환은 불가능**하다. 그리고 조사 과정에서 **이 태스크의 전제보다 더 심각한 기존 결함**(현재 유사도 검색이 구조적으로 0건)을 실측 확인했다.

---

## S — 상황

task-2980 Phase 2 에서 요약 엔진은 Gemini → Claude Sonnet CLI 로 전환해 실증에 성공했다. 그러나 임베딩은 여전히 `google.generativeai` 에 묶여 있고, `GEMINI_API_KEY` 가 폐기되어 `insurance_chunks` 저장이 실패한다. 회장 지시는 "임베딩도 Sonnet 으로 전환"이다. task md 는 착수 전 3항목을 확인해 보고하고 승인받으라고 못박았다.

## C — 갈등

Claude 는 텍스트 생성 모델이라 요약과 같은 1:1 치환이 성립하지 않을 수 있다. 그런데 이 태스크의 진짜 갈등은 그보다 깊은 곳에 있었다. `insurance_chunks` 는 유튜브 파이프라인 전용이 아니라 **PDF 색인·유튜브 크롤러·nextapp 검색이 공유하는 컬렉션**이고, 생산자만 모델을 바꾸면 벡터 공간이 섞여 **검색이 조용히 오작동**한다.

## Q — 질문

임베딩을 Sonnet 으로 바꿀 수 있는가. 없다면 실행 가능한 대안은 무엇이며, 기존 데이터와 호환되는가.

## A — 답변

**1) Sonnet 임베딩은 불가능하다.** Anthropic 은 임베딩 모델을 제공하지 않는다(공식 문서 원문 확인 + API/SDK/CLI 3중 실측).

**2) 대안은 존재하나, 어느 것도 `scripts/youtube-pipeline/**` 안에서 끝나지 않는다.** 쿼리측 임베딩이 `nextapp/**` 에 있는데 이는 본 태스크 `allowed_resources` 밖이다.

**3) 기존 벡터는 3072차원이고, Firestore 벡터 검색 상한은 2048차원이다.** 즉 **기존 151건은 지금도 검색이 불가능**하다. 호환은커녕 현행 자체가 깨져 있다.

---

## ★ 사전 확인 1 — Claude 계열 임베딩 엔드포인트 실재 여부

**결론: 존재하지 않는다. (확정)**

| 확인 경로 | 실측 결과 |
|---|---|
| 로컬 Claude CLI | `/home/jay/.local/bin/claude` v2.1.228 — `--help` 에 `embed` 문자열 0건 |
| Anthropic Python SDK | v0.84.0 — `Anthropic` 클라이언트 속성에 `embeddings` **없음** (`messages`/`completions`/`models`/`beta` 만 존재) |
| REST 엔드포인트 차등 프로브 | `POST /v1/messages` → **HTTP 401** `authentication_error`(엔드포인트 존재) <br> `POST /v1/embeddings` → **HTTP 404** `not_found_error`(엔드포인트 부재) |
| 공식 문서 | platform.claude.com `/build-with-claude/embeddings` 원문: **"Anthropic does not offer its own embedding model."** — 대신 Voyage AI 안내 |

401 vs 404 차등 프로브를 쓴 이유는, 인증 없이 404 만 봐서는 "키가 없어서"인지 "경로가 없어서"인지 구분되지 않기 때문이다. 존재하는 엔드포인트는 401 을, 없는 경로는 404 를 반환하는 것이 대조로 확인됐다.

**따라서 "요약처럼 `_call_claude_cli` 로 치환한다"는 접근은 성립하지 않는다.** Sonnet 에게 "이 문장의 임베딩 벡터를 출력하라"고 시켜 숫자를 받아내는 식의 우회는 결정론적이지도, 의미공간을 이루지도 않는다. 이는 임베딩이 아니라 난수 생성이며, task md 가 금지한 "억지 우회 구현"에 해당하므로 시도하지 않았다.

---

## ★ 사전 확인 3 — 기존 벡터 차원 실측 (★ 순서 조정: 2번 판단의 전제라서 먼저 보고)

Firestore 프로덕션 직접 조회 실측이다.

| 컬렉션 | 문서 수 | 실제 벡터 차원 | 선언된 인덱스 차원 |
|---|---|---|---|
| `insurance_chunks` | 151 (전부 `sourceType='youtube'`) | **3072 — 151건 전부** | `firestore.indexes.json` = **768** |
| `youtube_knowledge` | 152 | **3072 — 151건**, `0` — 1건(task-2980 E2E 산출물, `embeddingFailed=True`) | **768** |

### 여기서 드러난 기존 결함 (본 태스크 이전부터 존재)

`find_nearest` 를 실제로 쏴서 확인했다.

```
query_vector 768차원  → HTTP OK, hits = 0
query_vector 3072차원 → InvalidArgument 400: "Vectors must be at most 2048 dimensions."
```

정리하면:

1. 저장된 벡터는 전부 **3072차원**이다. 생산자는 `functions/src/crawlYoutubeChannels.ts` 의 `gemini-embedding-001`(기본 3072차원).
2. 그런데 **Firestore 벡터 검색의 하드 상한은 2048차원**이다. 3072 벡터로는 질의 자체가 400 으로 거부된다.
3. 선언된 벡터 인덱스는 768차원이고, 실제 소비자(`nextapp/src/app/api/ai/vector-search/route.ts`, `.../terms/[productId]/search/route.ts`)는 `text-embedding-004`(768차원)로 질의한다. 이 768 질의는 정상 실행되지만 **매칭 대상이 하나도 없어 hits = 0** 이다.

→ **인슈위키의 `insurance_chunks` 유사도 검색은 현재 구조적으로 0건을 반환한다.** 유튜브 파이프라인의 임베딩 실패 여부와 무관하게, 기존 151건은 영구히 검색되지 않는 상태다. 이는 본 태스크의 범위를 넘어서는 별건 결함이며, 회장 보고 대상으로 판단해 에스컬레이션한다.

### 파이프라인 자체의 차원

`scripts/youtube-pipeline` 은 `EMBEDDING_MODEL=text-embedding-004`(768차원)로 쓰도록 되어 있다. 즉 파이썬 파이프라인과 TS 크롤러가 **같은 컬렉션에 서로 다른 차원**을 쓰는 구조였다. 파이프라인이 성공했더라도 768 vs 3072 혼재가 발생했을 것이다.

---

## ★ 사전 확인 2 — 대안 3종 비교

비용 산정 기준: 유튜브 파이프라인 부하는 1회 실행당 영상 최대 40건 × 요약 8,000자 이내다. 연간으로 환산해도 **수백만 토큰 규모**로, 아래 어느 유료 옵션을 골라도 **실질 과금은 0에 수렴**한다. 따라서 비용은 결정 변수가 아니며, **키 의존·벡터공간 일관성·재색인 비용**이 실제 판단 기준이다.

### (a) 로컬 임베딩 모델 (sentence-transformers)

**★ task md 의 "GPU 는 이미 복구돼 사용 가능하다"는 전제는 이 용도에 한해 성립하지 않는다.** 실측 결과:

```
GPU: NVIDIA GeForce GTX 1060 6GB, compute capability sm_61
설치된 torch 2.10.0+cu128 지원 아키텍처: sm_70 sm_75 sm_80 sm_86 sm_90 sm_100 sm_120
→ CUDA error: no kernel image is available for execution on the device
```

Whisper 전사가 GPU 로 도는 것은 faster-whisper 가 CTranslate2 라는 **별도 CUDA 빌드**를 쓰기 때문이고, PyTorch 경로는 sm_61 을 버려서 쓸 수 없다. GPU 를 쓰려면 sm_61 지원 torch 로 다운그레이드해야 하는데, 이는 Whisper 스택까지 흔드는 위험한 변경이다.

**CPU 실측은 문제없이 통과했다.**

```
jhgan/ko-sroberta-multitask (CPU) → dim = 768, 모델 로드 3.4초, 2,000자 4건 인코딩 1.25초
```

- **장점**: API 키 **완전 불필요**(회장 지시의 본래 취지인 "키 의존 절단"에 정확히 부합) · 과금 0원 · 외부 장애 무관 · **768차원이라 이미 배포된 벡터 인덱스와 그대로 일치** · 같은 저장소 `scripts/kakao_knowledge/vector_store.py` 가 이미 sentence-transformers 를 쓰는 **사내 선례 존재**
- **단점**: CPU 추론(부하량 대비 충분하나 대량 재색인은 느림) · 모델 품질이 상용 API 대비 열위 · 파이썬 전용이라 **TS 측(functions/nextapp)에서 같은 벡터를 만들려면 별도 서비스화 필요** ← 이것이 핵심 난점
- **비용**: 0원

### (b) 다른 임베딩 전용 API

- **Voyage AI** — Anthropic 공식 권장. `voyage-4-lite` **$0.02/1M 토큰**, `voyage-4` $0.06, `voyage-4-large` $0.12, **무료 200M 토큰** 제공. 차원 1024 기본이며 **256/512/2048 선택 가능**(2048 상한 이내). 금융 특화 `voyage-finance-2`(1024) 존재.
- **장점**: 품질 최상위권 · 다국어/한국어 양호 · 차원 선택으로 Firestore 2048 제약 회피 용이 · 파이썬/TS 양쪽에서 동일 벡터 생성 가능(HTTP API)
- **단점**: **신규 API 키가 다시 필요** — 회장이 끊으려던 "키 수명에 파이프라인이 묶이는 구조"가 그대로 재현된다 · 외부 과금 계정 신설 · 전량 재색인 필요
- **비용**: 무료 한도 200M 토큰 내에서 사실상 0원

### (c) 신규 Gemini 키 발급 후 현행 유지

- **장점**: **코드 변경 0줄** · 생산자/소비자가 이미 같은 모델(`text-embedding-004`, 768)이라 벡터공간 일관성 확보가 가장 쉬움 · 즉시 복구
- **단점**: 회장이 제거하려는 실패 원인(키 폐기 = 파이프라인 정지)을 **그대로 존치** · 게다가 **기존 151건 3072 문제는 이 옵션으로 해결되지 않는다**(재색인 별도 필요)
- **비용**: `text-embedding-004` 는 무료 티어 범위. 0원

### 팀장 권고

**단기 복구가 목적이면 (c), 회장 지시의 취지(키 의존 절단)를 이행하려면 (a) 다.**

다만 어느 쪽을 고르든 **"유튜브 파이프라인만 바꾸는 것으로는 문제가 해결되지 않는다"**는 점을 분명히 보고한다. 임베딩은 생산자와 소비자가 **같은 모델**이어야만 의미가 있고, 현재 소비자는 `nextapp/**` 에 있으며 이는 본 태스크 `allowed_resources.paths` 밖이다. 생산자만 바꾸면 차원은 맞더라도 벡터공간이 달라져 **검색이 조용히 틀린 결과를 낸다** — 지금처럼 0건을 반환하는 것보다 오히려 위험하다(금소법 리스크).

따라서 권고는 다음과 같다.

1. **임베딩 모델을 인슈위키 전 구간에서 하나로 통일하는 별도 태스크**로 승격 (생산자 3곳 + 소비자 2곳 + 인덱스 정의 + 재색인)
2. 그 통일 모델로 **(a) 로컬 768** 채택 — 이미 배포된 768 인덱스와 일치해 `firestore.indexes.json` 변경이 불필요하고, 키 의존이 사라진다. TS 측은 파이썬 임베딩을 내부 HTTP 엔드포인트로 노출해 공유한다(Whisper 서버와 동일 패턴, 선례 있음).
3. 기존 3072 데이터 **151건 전량 재색인** — 어떤 선택지에서도 불가피하다.

---

## 3 Step Why

- **1st Why — 왜 Sonnet 전환이 불가능한가?** → **A**: Anthropic 은 임베딩 모델 자체를 만들지 않는다. CLI·SDK·REST·공식문서 4중으로 확인했고, `/v1/embeddings` 는 404 다.
- **2nd Why — 왜 우회 구현을 하지 않았는가?** → **B**: 생성 모델에게 숫자 배열을 뱉게 하는 방식은 결정론적이지 않고 거리 계산이 성립하는 벡터공간을 이루지 못한다. "전환 완료"로 보고할 수는 있으나 검색 품질은 난수와 같아진다. task md 가 명시적으로 금지한 행위다.
- **3rd Why — 왜 그냥 대안 하나를 골라 바로 구현하지 않았는가?** → **C**: 실측 결과 문제의 위치가 파이프라인이 아니라 **컬렉션 전체의 벡터공간 불일치**였다. 소비자 코드가 `allowed_resources` 밖이라 범위 내 구현으로는 "검색이 실제로 동작함"을 증명할 수 없다. 범위를 몰래 넘기지 않고 승인을 받는 것이 규칙이자 정답이다.

A-B-C 일관성 확인됨.

---

## 수정 파일별 검증 상태

## 레벨: 코드 수정 없음

본 실행은 task md 가 명령한 **착수 전 사전 확인 단계**이며, 승인 전 구현 금지 지시에 따라 소스 변경이 0건이다. 아래는 변경 파일이 아니라 **조사·실측 대상 파일**이다.

| 파일 | 조사 요지 | 검증 방법 | 상태 |
|---|---|---|---|
| /home/jay/.worktrees/task-2980-dev1/scripts/youtube-pipeline/youtube_pipeline/summarizer.py | generate_embedding 이 genai.embed_content 에 결박(768차원 선언) | 소스 판독 + import 추적 | verified |
| /home/jay/.worktrees/task-2980-dev1/scripts/youtube-pipeline/youtube_pipeline/config.py | EMBEDDING_MODEL=text-embedding-004 (768) | 소스 판독 | verified |
| /home/jay/.worktrees/task-2980-dev1/functions/src/crawlYoutubeChannels.ts | gemini-embedding-001(3072)로 동일 컬렉션에 기록 — 차원 혼재의 실제 원인 | 소스 판독 + Firestore 실측 대조 | verified |
| /home/jay/.worktrees/task-2980-dev1/functions/src/pdfIndexing.ts | text-embedding-004(768)로 동일 컬렉션 기록 — 생산자 간 불일치 | 소스 판독 | verified |
| /home/jay/.worktrees/task-2980-dev1/nextapp/src/app/api/ai/vector-search/route.ts | 소비자 질의 임베딩 = text-embedding-004(768) · allowed_resources 밖 | 소스 판독 | verified |
| /home/jay/.worktrees/task-2980-dev1/firestore.indexes.json | insurance_chunks·youtube_knowledge 벡터 인덱스 dimension=768 | JSON 파싱 실측 | verified |
| /home/jay/.worktrees/task-2980-dev1/scripts/kakao_knowledge/vector_store.py | 사내 sentence-transformers 선례(로컬 임베딩 실현성 근거) | 소스 판독 | verified |

---

## L1 스모크테스트

- **서버 재시작**: 해당없음 — 코드 변경 0건. 상시 데몬 대상 작업이 아니다.
- **실 API/DB 응답 확인** (전부 실호출):
  - Anthropic `POST /v1/messages` → **HTTP 401** `authentication_error` (엔드포인트 존재 대조군)
  - Anthropic `POST /v1/embeddings` → **HTTP 404** `not_found_error` (부재 확정)
  - Gemini `text-embedding-004:embedContent` 실호출 → **HTTP 400 `API_KEY_INVALID`** (키 폐기 상태 재확인)
  - Firestore `insurance_chunks` 실조회 → 151건, 차원 3072 × 151
  - Firestore `youtube_knowledge` 실조회 → 152건, 3072 × 151 + 0 × 1, `embeddingFailed` 1건
  - Firestore `find_nearest` 실호출 → 768차원 질의 **hits 0** / 3072차원 질의 **400 "at most 2048 dimensions"**
  - 로컬 임베딩 실호출 → `jhgan/ko-sroberta-multitask` CPU, **dim 768**, 로드 3.4초 · 2,000자 4건 1.25초
  - GPU 실측 → `torch.cuda.get_device_capability(0)` = **(6,1)**, torch 지원 arch = sm_70 이상 → 커널 부재 에러
- **스크린샷**: 해당없음 (UI 변경 0건 · 백엔드 조사 전용)

---

## 회귀 기준선 (참고 — 불일치 보고)

```
cd /home/jay/.worktrees/task-2980-dev1/scripts/youtube-pipeline
python3 -m pytest tests/ -q  →  101 passed, 1 warning
```

task md 검증 5항은 기준선을 **83 passed** 로 적고 있으나, 조사 시점 실측은 **101 passed** 다. 같은 scope(`tests/`)에서 측정했으므로 계수 오류가 아니라, task-2980 worktree 가 보고서 작성 시점(HEAD `1b8d0eb`) 이후 Phase 3 커밋(`a6710f7` 등)으로 전진하면서 테스트가 늘어난 **기준선 드리프트**다. 구현 승인 시 회귀 기준선은 **101** 로 갱신해야 한다.

---

## trip-wire 5종 (실측)

| trip-wire | 실측값 | 근거 |
|---|---|---|
| Critical7 | 0 | 코드 변경 0건 |
| PII net-new | 0 | 신규 커밋 0건. Gemini 키 프로브 시 URL 의 key 파라미터를 정규식으로 마스킹 후 출력 |
| 회귀 실패 | 0 | pytest 101 passed, 0 failed |
| forbidden_paths 침범 | 0 | `__pycache__`/`*.pyc`/`.github`/`firestore.rules` 미접촉. 파일 쓰기는 `memory/reports/task-2987.md` 단 1건 |
| nonce | task-2987 | 발사 task_id 와 일치 |

`.pyc` 주의사항 준수: 본 실행은 저장소에 **커밋을 만들지 않았고**, `git add` 를 수행하지 않았다. 조사 과정에서 파이썬 모듈을 import 했으므로 `__pycache__` 가 갱신됐을 수 있으나 스테이징·커밋한 바 없다. 키 로테이션은 task md 지시대로 **손대지 않았다**.

---

## ★ 에스컬레이션 (ANU·회장 결정 필요)

### 1. 회장 지시 "임베딩도 Sonnet 으로" 는 이행 불가

Anthropic 이 임베딩 모델을 제공하지 않는다. 대체 방향 결정이 필요하다 — **(a) 로컬 768 / (b) Voyage AI / (c) 신규 Gemini 키**. 팀장 권고는 **(a)**, 단기 복구만 원하시면 **(c)**.

### 2. [신규 발견] 인슈위키 벡터 검색이 현재 0건을 반환한다

기존 151건이 3072차원인데 Firestore 상한은 2048차원이다. 임베딩 실패와 무관하게 **이미 검색 불능**이다. 본 태스크 범위 밖의 별건이며, 우선순위 판단이 필요하다.

### 3. 범위(allowed_resources) 확장 승인 요청

소비자 코드가 `nextapp/**` 에 있어 현행 범위(`scripts/youtube-pipeline/**`·`functions/src/**`·`tests/**`)만으로는 task md 검증 4항("유사도 검색이 실제로 동작하는지 실증")을 **원리적으로 충족할 수 없다**. 범위를 넓히거나, 검증 4항을 후속 태스크로 분리하는 결정이 필요하다.

### 4. 전량 재색인 승인

어떤 선택지를 택하든 기존 151건 재색인이 불가피하다. 재색인은 `insurance_chunks` 를 건드리므로 운영 데이터 변경 승인이 필요하다.

---

## 다음 단계 (승인 후)

1. 채택 모델 확정 → `generate_embedding()` 교체 (`scripts/youtube-pipeline`)
2. 소비자·타 생산자 정합 (범위 승인 시)
3. `google-generativeai` 의존 제거 — 요약은 이미 CLI 전환됐으므로 임베딩까지 끊으면 `requirements.txt` 에서 삭제 가능
4. E2E 재수행 → `insurance_chunks` 증가 실증 → 유사도 검색 1건 실증
5. pytest **101** 유지 확인 → `finish-task.sh` 로 종결

## 세션 통계
- 총 도구 호출: 0회

