# task-2989 — 아누 시스템 공용 임베딩 서비스 신설 + 인슈위키 벡터검색 복구

**레벨**: Lv.3 (신규 공용 인프라) · **팀**: dev4-team (비슈누) · **회장 승인 완료** (2026-08-20)

## 배경
task-2987 사전조사 결과, 두 가지가 확정됐다(ANU 재측정으로 전건 확인).

1. **Claude/Sonnet 계열은 임베딩 API 를 제공하지 않는다.** 회장 지시 "임베딩도 Sonnet 으로"를 문자 그대로 이행할 수 없다.
2. **인슈위키 벡터 검색은 지금까지 한 번도 작동한 적이 없다.** 차원이 세 갈래로 어긋나 있다.
   - 생산자 `functions/src/crawlYoutubeChannels.ts:689` = `gemini-embedding-001` → **3072차원** 저장
   - 선언된 인덱스 `firestore.indexes.json:144,157` = **768차원**
   - 소비자 `nextapp/src/app/api/ai/vector-search/route.ts:357` = `text-embedding-004` → **768차원** 질의
   - 결과: 768 질의는 매칭 0건, 3072 질의는 Firestore 상한(2048) 초과로 400 거부
   - 즉 지금 쌓인 151건은 **검색으로 꺼낼 수 없는 상태**다. Gemini 키 폐기 이전부터 깨져 있었다.

## 회장 결정 (2026-08-20)
임베딩을 **아누 시스템**으로 처리한다. 단, InsuRo 의 `server/anu_provider.py` 는 **아누 시스템 본체가 아니라
InsuRo 유료 과금 커넥터**이므로(토큰 차감·요금제 등급용) **그대로 두고 건드리지 않는다.**

임베딩은 특정 사업의 기능이 아니라 **공용 인프라**다. 따라서 아누 시스템 쪽(`/home/jay/workspace/`)에
독립적으로 두고, 인슈위키와 (향후) InsuRo 가 동등하게 호출한다.

```
/home/jay/workspace/   ← 아누 시스템
     └── 공용 임베딩 서비스 (로컬 모델 · 768차원 · 외부 키 0)
              ↑                        ↑
          인슈위키                (향후) InsuRo
```

## ★ 실행 환경 제약 (ANU 실측 — 설계의 전제)
- `crawlYoutubeChannels` 는 **Firebase Cloud Function** 이다 (`onSchedule`, region `asia-northeast3`).
  **GCP 에서 실행되므로 이 서버의 로컬 임베딩 서비스에 접근할 수 없다.**
- 반면 파이썬 파이프라인은 **로컬 systemd 타이머**로 돈다 (`insuwiki-youtube-pipeline.timer`).
  로컬 서비스 호출이 가능하다.
- 현재 클라우드 스케줄은 **PAUSED** 상태다.

**따라서 임베딩 생성 책임을 로컬 파이썬 파이프라인으로 일원화하는 방안을 1순위로 검토하라.**
클라우드 함수가 로컬 서비스를 부르게 하려면 네트워크 노출이 필요한데, 보안 부담이 크고 권장하지 않는다.
다른 설계가 낫다고 판단되면 근거와 함께 제안하되, **구현 전에 ANU 승인**을 받는다.

## 요구사항

### 1. 아누 시스템 공용 임베딩 서비스 신설
- 위치: `/home/jay/workspace/` 하위 (경로·구조는 기존 관례를 따라 제안하라)
- **외부 API 키를 쓰지 않는다.** 로컬 임베딩 모델을 사용한다. 이것이 회장 지시의 핵심 취지다.
- **출력 차원은 768로 고정**한다. 이미 배포된 벡터 인덱스와 일치시켜 인덱스 재배포를 피한다.
- 사내 선례를 참고하라: `/home/jay/projects/insuwiki/scripts/kakao_knowledge/vector_store.py`
  가 이미 `SentenceTransformer(device="cpu")` 로 동작 중이다. 바퀴를 다시 만들지 말 것.
- 호출 방식(HTTP 서비스 / 파이썬 모듈)은 **소비자가 누구인지 보고 결정**하라. 근거를 보고서에 남긴다.
- 모델 로딩 비용이 크므로 매 호출마다 로드하지 않도록 한다.

### 2. 인슈위키 파이프라인 배선
- `scripts/youtube-pipeline/youtube_pipeline/summarizer.py:104 generate_embedding()` 이
  `genai.embed_content` 대신 **아누 시스템 임베딩 서비스**를 호출하도록 바꾼다.
- `google.generativeai` 의존이 임베딩에서 제거되면 관련 import·설정을 정리한다.
- 실패 시 조용히 넘어가지 말고 명시적으로 기록한다(현행 `embeddingFailed` 방식 유지).

### 3. 3072 오염원 차단
- `functions/src/crawlYoutubeChannels.ts` 의 **임베딩 생성을 중단**시킨다(수집·저장 기능은 유지).
- 이유: 로컬 파이프라인이 768로 넣는데 클라우드가 3072를 계속 넣으면 **혼재가 재발**한다.
- 클라우드 함수에서 임베딩을 빼면, 임베딩 생성 지점이 로컬 한 곳으로 모인다.

### 4. 전량 재색인
- 기존 `youtube_knowledge` / `insurance_chunks` 의 3072차원 벡터를 **768로 다시 계산**한다.
- **원문(제목·설명·본문)은 절대 삭제하지 않는다.** 벡터 필드만 갱신한다.
- 재색인 스크립트는 **중단·재개가 가능**해야 한다(전량 실패 시 처음부터 다시 도는 일이 없도록).
- 재색인 전 대상 건수를 실측해 보고하고, 완료 후 건수를 대조한다.

## 검증 (전부 실측 증거 필요)
1. 임베딩 서비스 단독 호출 → **768 길이 벡터** 반환 확인
2. 파이프라인이 실제 영상 1건을 처리 → `insurance_chunks` 에 저장되고 `embedding_len` 이 **0이 아닌 값**
3. **★ 유사도 검색이 실제로 `hits > 0` 을 반환**하는 것을 쿼리 1건으로 실증.
   이게 이 태스크의 핵심 완료 조건이다. 저장만 되고 검색이 0건이면 실패다.
4. 재색인 전후 문서 건수 대조 — **원문 유실 0건**
5. `pytest tests/` 회귀 유지 (task-2987 시점 기준선 **83 passed**. 불일치 시 근거와 함께 보고)
6. 외부 API 키 없이 전 과정이 도는 것을 확인 (`GEMINI_API_KEY` 미설정 상태로 실행)

## 범위 · 금지
**허용**: `/home/jay/workspace/` 신규 임베딩 서비스 · `scripts/youtube-pipeline/**` ·
`functions/src/crawlYoutubeChannels.ts`(임베딩 제거 한정) · 재색인 스크립트 · 테스트

**금지**:
- **`/home/jay/projects/InsuRo/**` 전체.** 특히 `server/anu_provider.py` 는 회장이 "그대로 둔다"고 결정했다.
- **`.pyc` 신규 커밋.** 이 저장소는 `.gitignore` 에 `__pycache__` 규칙이 없어 `git add -A` 시 딸려 들어간다.
  커밋 전 `git status` 로 확인하라. **가능하면 이번 기회에 `.gitignore` 에 `__pycache__/` 와 `*.pyc` 를 추가**하고
  추적 중인 `.pyc` 36개를 untrack 하라 (회장 승인된 위생 조치다. 키 회전은 별건이니 손대지 말 것).
- `firestore.indexes.json` 의 dimension 변경 — 768을 유지하는 것이 이 설계의 전제다.

## allowed_resources (본 task의 capability)

```yaml
allowed_resources:
  paths:
    - "services/embedding/**"
    - "utils/**"
    - "scripts/youtube-pipeline/**"
    - "functions/src/crawlYoutubeChannels.ts"
    - "scripts/reindex/**"
    - "tests/**"
    - ".gitignore"
    - "memory/reports/task-2989.md"
  forbidden_paths:
    - "firestore.indexes.json"
    - "firestore.rules"
    - "**/*.pyc"
    - ".github/**"
  commands:
    - "pytest"
    - "python3 -m py_compile"
    - "npm run build"
  merge_policy: "tiered"
  ttl_hours: 72
```

> InsuRo 저장소(`/home/jay/projects/InsuRo/**`)는 위 목록에 없다. **접근 자체가 범위 밖**이다.

## 운영 계약
- 대상 저장소가 workspace 밖(`/home/jay/projects/insuwiki`)이므로 `TASKCTL_CWD` 를 사용한다.
- 워크플로우: `/home/jay/workspace/prompts/DIRECT-WORKFLOW.md` · QC: `/home/jay/workspace/teams/shared/QC-RULES.md`
- `WORKSPACE_ROOT=/home/jay/workspace` · `CHAT_ID=6937032012` · 수집자 key `ANU_KEY=c119085addb0f8b7`
- 완료 경로는 `finish-task.sh` 실행이 유일하다. 수동 `.done` 생성 금지.
- **설계안(호출 방식·서비스 위치·클라우드 함수 처리)은 구현 전에 ANU 승인**을 받는다. 재색인도 승인 후 실행한다.

## 완료 조건
임베딩 서비스 신설(외부 키 0·768차원) · 파이프라인 배선 · 3072 오염원 차단 · 전량 재색인 ·
**유사도 검색 hits > 0 실증** · 원문 유실 0 · 회귀 유지

## 보고
`memory/reports/task-2989.md` 작성 후 표준 완료 콜백 등록. 콜백 프롬프트 **UTF-8 3900 bytes 이하**.

## goal_assertions (auto-generated)
- `pytest tests/`
