B-0 도메인 라우팅(자동 분류) — 평가 하네스 배선

3.2절 도메인 라우팅을 자동 분류로 확정(수동 선택 안 함) — 근거·설계: _project/decisions/007-도메인-라우팅-자동분류-확정.md.

w2-domain-routing와 범위가 다르다 — 그 티켓은 A안(수동) vs B안(자동) 결정 자체(완료)이고, 이 티켓은 그 결정을 실제로 구현하는 작업 (하네스 배선 완료, 분류기 구현은 미착수)이다. 슬러그가 비슷해 보이지만 중복이 아니다.

끝낸 것: 평가 하네스에 B-0 배선 완료. services/core/eval/metrics/domain_routing.py (정확도 + 오분류 행렬, 규칙 기반), harness.pyDomainPredictor Protocol 추가(아직 None — “측정 불가 — 모듈 미구현”으로 정직하게 보고), 골든셋 domain 필드를 정답 라벨로 재사용. 테스트(test_domain_routing_metrics.py, test_harness.py 배선 검증) 포함 pytest services/core 33개 통과. 6.1절에 목표(정확도 ≥0.95) 반영.

남은 것: 실제 KcELECTRA 계열 분류기 구현·학습은 미착수 — 골든셋 7건(F-2 케이스 제외)만으로는 분류기를 학습시킬 수 없다. 2주차 이후 골든셋이 50개로 늘어나면 착수. 신뢰도 낮을 때 폴백(4개 인덱스 전체 검색)은 B-2 하이브리드 검색이 먼저 있어야 구현 가능 — 아직 그 모듈도 없음.

w2-domain-routing 과의 관계

이름이 겹쳐 보드에 두 장으로 보이지만 다른 일이다. 세션 종료 검사(③ 중복 티켓)가 슬러그로 경고하는 것이라 합치지 않는다.

티켓 무엇 상태
w2-domain-routing 방식 결정 — 수동 선택(A) vs 자동 분류(B) done (B안 확정, decisions/007)
이 티켓 구현 — 하네스 배선 + 분류기 학습 in-progress (배선만 끝남)

2026-08-26 갱신: 전제였던 골든셋 50건은 확보됐다(golden-set/v1-50.json, 도메인 라벨 있는 채점 표본 34건 — F-2 16건은 발화 텍스트가 없어 제외). 남은 전제는 B-2 검색이며, 그건 ES 인덱스 분할 결정이 나야 시작할 수 있다.

2026-08-27 — v1 구현·측정 (류준). 아직 in-progress 인 이유

분류기를 만들지 못했다. 학습 데이터가 없다. 골든셋은 평가 세트다 — 그걸로 학습시키고 그걸로 채점하면 정확도 1.0 이 나오는데 아무것도 측정하지 않은 숫자다.

대신 decisions/007 설계의 ② 폴백(4개 도메인 전체 검색)을 1차 경로로 먼저 만들었다. 새 도구를 들이지 않고(3.1절 투입자원 원칙) 학습 데이터 없이 지금 잴 수 있다.

ai/apps/retrieval/adapter/outbound/search_domain_router.py — 발화로 전 도메인을 검색해 상위 문서의 도메인에 1/rank 로 표를 준다. 신뢰도는 1등과 2등의 표 차이다. 점수가 아니라 순위를 쓰는 이유는 RRF 와 같다 — BM25 점수는 질의마다 스케일이 달라 임계값을 세울 수 없다.

실측 정확도 0.647 (22/34, 목표 ≥0.95). 오분류가 전부 finance 로 쏠린다 — 문서 수 불균형(34/27/21/20)이 그대로 편향이 됐다. 자세한 혼동 행렬은 w2-baseline 참고.

done 이 아닌 이유: 목표(≥0.95)에 못 미치고, 설계의 ①(KcELECTRA 분류기)이 아직 없다. 이 v1 은 그 분류기가 넘어야 할 기준선이다. 분류기 학습에는 골든셋이 아닌 별도 학습 데이터가 필요하다 — AI Hub 민원 데이터셋에 도메인 라벨이 있으므로 그쪽을 봐야 한다.

2026-08-27 (이어서) — AI Hub 데이터로 분류기를 학습했다. 그런데 v1 보다 낫지 않다

data/raw/aihub-minwon-qa 에 도메인 라벨이 붙은 실제 콜센터 전사가 있다. 골든셋을 쓰지 않고 학습할 수 있는 데이터다.

   
원본 레코드 221,155건 (고객 111,559 / 상담사 109,596)
고객 질문만·4자 이상·중복 제거 36,146건 (finance 8,798 · dasan 5,822 · shopping 9,522 · health 12,004)
학습 / 검증 32,533 / 3,613 (도메인 비율 유지 분할, seed 42)
베이스 models/kcelectra-base (decisions/010)
.venv/bin/python scripts/train_domain_classifier.py     # 2 epoch, 약 20분 (M5 MPS)

AI Hub 검증 정확도 0.8148 (epoch 1 0.7789 → epoch 2 0.8148). 도메인별 dasan 0.572 · finance 0.793 · health 0.850 · shopping 0.939.

⚠ 골든셋에서는 v1 보다 낮다

채점 대상 검색 기반 v1 분류기
B(검색) 14건 0.857 0.786
전체 34건 0.647 0.588

학습이 안 된 게 아니라 분포가 다르다. AI Hub 는 실제 콜센터 전사이고(“카드를 잃어버렸어요”), 골든셋 발화는 우리가 시나리오로 쓴 문장이다(“카드를 잃어버렸는데 신고하기 전에 이미 누가 써버린 돈은 저는 못 돌려받는 거예요?”). 길이도 어투도 다르다.

분류기를 기본으로 켜지 않았다. --domain-router auto 는 모델이 있으면 쓰지만, 지금 수치로는 v1 이 더 낫다. 어느 쪽으로 쟀는지는 실행할 때 출력에 찍힌다.

채점 방법 자체가 틀려 있었다 (같이 고침)

처음엔 도메인 라벨이 있는 34건 전체로 쟀다. 그런데 그중 20건이 C·C-5 항목이고 그 발화에는 도메인 단서가 없다 — “본인 확인을 위해서 주민등록번호를 불러주시겠어요?” 가 어느 도메인인지 텍스트만 보고는 알 수 없다. 실제로 그 20건에서는 두 방식 다 0.45~0.50, 사실상 찍기였다.

B-0 은 decisions/007 대로 통화 초반 고객 발화로 판정하는 것이므로 harness.py 의 채점 대상을 B 항목으로 좁혔다. 측정할 수 없는 것을 섞어 재고 있었다(절대 원칙 10).

⚠ n=14 로는 “≥0.95” 를 판정할 수 없다

14건에서 0.95 를 넘으려면 14/14 여야 한다(13/14 = 0.929). 만점 말고는 통과가 없고, 두 방식의 차이(12/14 vs 11/14)도 한 건이라 의미를 두기 어렵다. 3주차 골든셋 150건 확장 때 도메인 판정용 표본을 따로 늘려야 이 목표를 잴 수 있다.

다음에 해볼 것 (아직 안 함)

done 이 아닌 이유: 목표 미달이고, 그보다 목표를 판정할 표본이 없다.

2026-08-27 (이어서 2) — epoch 4 + 초반턴 증강. 골든셋은 한 자리도 안 움직였다

앞선 결과(2 epoch, 증강 없음)에서 “epoch 을 더 늘려볼 것 / 골든셋 문체에 적응시킬 것”을 남겼다. 둘 다 해봤다.

골든셋으로 학습하지 않았다 — 평가 세트라 학습에 쓰면 그 라벨로 다시 잴 수 없다. 대신 실제 데이터를 골든셋 문체에 가깝게 만들었다: AI Hub 고객 발화는 짧은 턴으로 쪼개져 있고("카드를 잃어버렸어요") 골든셋은 완결된 한 문장이라, 한 대화의 앞쪽 고객 턴을 이어 붙였다(load_opening_samples). 지어낸 문장이 아니라 실제 전사를 잇는 것이고, B-0 의 실제 입력(통화 초반 발화)과도 더 맞는다. +13,204건.

방식 AI Hub 검증 골든셋 (n=14)
검색 기반 v1 0.857 (12/14)
분류기 2 epoch · 증강 없음 0.815 0.786 (11/14)
분류기 4 epoch · 증강 포함 0.879 0.786 (11/14)

AI Hub 는 +6.4%p 올랐는데 골든셋은 혼동 행렬까지 동일하다. 틀린 3건이 완전히 같다.

틀린 3건 — 확신을 갖고 틀린다

GS-016  dasan → finance (0.771)  「제가 직접 못 가서 그런데 가족이 대신 등본을 발급받을 수 있나요」
GS-018  shopping → finance (0.965)  「상자를 열어보니까 물건이 배송 중에 깨져서 왔던데 어떻게 처리되나요」
GS-021  health → finance (0.819)  「이 정도 진료를 받으면 대략 비용이 얼마나 나오는지 미리 알 수 있나요」

전부 finance 로 가고 신뢰도가 0.77~0.97 이다. 헷갈리는 게 아니라 확신하며 틀린다. 발급·처리·비용 이 AI Hub 금융 데이터에 압도적으로 많은 탓으로 보이고, 데이터를 더 넣고 더 오래 학습할수록 이 편향은 오히려 강해진다. 실제로 그렇게 됐다.

두 방식이 서로 다른 데서 틀린다

v1 은 위 3건을 다 맞히고 대신 GS-002(저상버스 도착 정보)·GS-019(사이즈 교환)를 틀린다. 지식베이스에 등본 발급·배송 파손·진료비 조항이 실제로 있어 문서를 직접 찾아가는 방식이 문체 차이에 둔감하다.

겹치지 않는다 → 합치면 둘 다 넘길 여지가 있다. RRF 병합 함수는 이미 있다(decisions/021).

dasan 은 개선됐다 (그래도 최하위)

AI Hub 검증 도메인별: dasan 0.572 → 0.746, finance 0.871, health 0.887, shopping 0.953. 초반턴 증강이 dasan 에 +1,778건 들어간 효과로 보인다. 여전히 최하위다.

dasan 보강 — 데이터로 확인한 원인 세 가지

  1. 표본이 절반뿐이다 — dasan 7,600건 = health(16,237)의 47%
  2. 어휘 겹침이 모든 쌍 중 가장 크다 — 상위 400단어 기준 dasan↔finance 0.47 (health 0.39 · shopping 0.29). 수도요금·납부·지원금·요금 이 금융 어휘와 정면으로 겹친다
  3. 고유어는 있는데 드물다시내버스·지하철·대중교통·서울시 는 뚜렷하지만, dasan 상위 60단어 중 남과 공유하는 것은 전부 기능어(있나요·어떻게·되나요·제가…)다. 신호 단어가 드물게 나타나므로 표본이 적으면 충분히 못 본다 — ①과 ②가 곱해진다

해법 순서

  방법 비용 비고
1 클래스 가중치 — dasan 손실에 ~2배 몇 줄 오버샘플링보다 낫다(복제하면 그 문장에 과적합). 표본 부족을 가리는 것이지 없애는 게 아니다
2 다산콜DB 6,614건 전사 정성윤 님 w2-stt-batch 의존 data/raw/seoul-minwon-audio/민원상담_음성/다산콜DB/wav 만 있고 전사가 없다. 전사하면 dasan 이 7,600 → 14,000+ 로 불균형이 사라진다. : 전사본은 STT 오류 특성을 그대로 가져 지금의 “깨끗한 텍스트 vs 실제 입력” 간극까지 메운다 — 5주차 오류 내성 실험과 이어진다
3 요금/납부 문맥 분리 1·2 뒤에도 남으면. 지금 규칙으로 손대면 실측 없이 튜닝하는 것

⚠ 방법론 결함 하나 (기록해 둔다)

이번 학습은 epoch 2→4 와 초반턴 증강을 한꺼번에 바꿨다. 그래서 골든셋이 그대로인 것은 확실하지만 AI Hub 상승분이 어느 쪽 공인지는 말할 수 없다. --no-opening-turns 플래그를 넣어 뒀으니 분리 측정은 가능하다. 애초에 하나씩 갔어야 맞다.

기본값은 v1 이다

run_eval.py --domain-router auto(기본)는 분류기가 있어도 v1 을 쓴다. 더 나쁜 쪽이 조용히 끼어들면 안 된다. 분류기를 보려면 --domain-router model 로 명시한다.

done 이 아닌 이유: 목표(0.95) 미달이고, n=14 로는 그 목표를 판정할 수도 없다.


⚠ 2026-08-28 — 완료가 아니라 취소로 닫는다 (류준)

도메인이 다산 하나가 되면서(_project/decisions/201) 분류할 도메인이 없어졌다. 칸반 상태 어휘가 todo|in-progress|done 셋뿐이라 done 으로 옮겼지만, 이 티켓의 완료 조건은 달성되지 않았다 — 아래를 그대로 남긴다(절대 원칙 8).

   
달성한 것 검색 기반 v1 라우터 · KcELECTRA 분류기 학습 · 하네스 배선 · 실측 비교
미달 목표였던 분류 정확도 ≥0.95 를 넘지 못했다. 최종 실측 0.857(B 케이스 14건)
지운 것 ai/apps/training/ 전체 · search_domain_router.py · scripts/train_domain_classifier.py
남긴 것 hub 의 DomainRoutingPort·DomainClassificationDTO (계약, 구현체 없음 — 장민석 소관)

이 실험에서 남는 사실: AI Hub 전사 데이터로 학습한 분류기(0.879)가 골든셋 문체에 전혀 전이되지 않았다 — 혼동 행렬이 검색 기반 v1 과 동일했다. 분포 차이가 그만큼 컸다. A-3·C-6·D 에서 모델을 학습할 때 같은 함정을 밟지 않도록 이 기록을 남긴다.