3. 시스템 아키텍처

[상담원 브라우저]
       │ React 대시보드 (자막 / 카드 / 경고 / 종결 모달)
       │
   WebSocket (양방향)
       │
[Node.js 게이트웨이]
       │  - 오디오 청크 중계
       │  - 자막/카드/경고 푸시
       │
       ├──→ [Google STT] ──→ 부분 전사 결과
       │
       ▼
[FastAPI 코어]
       │
       ├─ C-5 마스킹 모듈 ──→ 자막·저장 양쪽 앞단
       │
       ├─ 트리거 판정 모듈
       │     └─ 발화 완결성 / 침묵 길이 / 의도 신호
       │
       ├─ 검색 모듈 ──→ [Elasticsearch]
       │                  ├─ 도메인 라우팅 (4종 — 아래 3.2절)
       │                  ├─ nori 형태소 (BM25)
       │                  ├─ dense_vector (임베딩)
       │                  └─ RRF 병합
       │
       ├─ 생성 모듈 ──→ [Ollama: EXAONE 4.0 1.2B] 근거 기반 요약
       │
       ├─ 컴플라이언스 모듈 ──→ [분류기]
       │
       └─ F-2 게이트 모듈 ──→ 같은 ES 인덱스 조회 + 규칙 판정
       │
       ▼
   [PostgreSQL]
     ├─ call            통화 메타
     ├─ transcript      마스킹된 전사
     ├─ recommendation  추천 이력
     ├─ closure         종결 사유 + 근거 필드   ← F-2
     └─ eval_result     평가 결과

F-2는 새 구성요소가 아니라 기존 FastAPI 코어 내 모듈로 들어간다.

3.1 도구 매핑

투입자원 활용 방식 필수도
Python 전체 백엔드 필수
FastAPI 검색·생성·게이트 API 필수
Node.js WebSocket 게이트웨이 필수
WebSocket 실시간 양방향 통신 필수
Google STT 스트리밍 음성인식 + 화자분리 필수
Elasticsearch nori + dense_vector 하이브리드 검색 필수
HuggingFace Transformers ① 임베딩 모델(KoE5) ② 컴플라이언스 분류기 파인튜닝(KcELECTRA-base + klue/roberta-base 대조군) ③ NER(P6·P7, koelectra-ner) 필수
Ollama 카드 요약 생성(B-4) 서빙 — 2026-08-26 추가 필수
React 상담원 대시보드 필수
PostgreSQL 통화 메타·전사·추천·종결·평가 결과 필수
Git 형상관리 필수
Docker 서비스 컨테이너화 필수
matplotlib 오류율별 성능 곡선, 레이턴시 분포 필수
VSCode / Ubuntu 개발 환경 / 서버 OS 필수
Kubernetes / AWS Console 배포 선택
VirtualBox 로컬 테스트 환경 선택
OpenCV 본 주제에서 활용처 없음 제외

목록 밖 도구 없음 원칙에 대한 예외 (2026-08-26): Ollama는 원래 투입자원 목록에 없던 도구다. 실측 결과 HuggingFace Transformers 직접 로드로는 레이턴시 목표를 못 맞춰(아래) 사용자가 직접 지시한 스택(HuggingFace + Ollama, AWS 배포)으로 예외를 받아들였다. 상용 API가 아니라 로컬/오픈 웨이트 서빙 도구라는 점에서 “상용 API 도입 금지” 원칙은 지킨다. 근거: _project/decisions/009-생성모델-EXAONE-Ollama-확정.md.

생성 모델 — EXAONE 4.0 1.2B로 확정 (2026-08-26, _project/decisions/010)

EleutherAI/polyglot-ko-1.3b(HF Transformers, MPS)를 실제로 로드해 250토큰 생성을 측정한 결과 7.6~7.7초로 목표(3~5초)를 크게 초과했고, instruction 튜닝이 안 된 베이스 모델이라 요약 지시를 따르지 못하고 원문을 반복 출력했다(품질도 실패).

Ollama(llama.cpp/GGUF) 서빙으로 후보를 비교(중국 출처 모델 제외), 1차로 exaone3.5:2.4b(3.63초, 71.6 tok/s)로 확정했다가, 같은 날 Opus 교차검증으로 나온 EXAONE-4.0-1.2B를 추가로 실측해 다시 교체했다:

모델 출처 250토큰 전체 처리량
llama3.2:3b Meta 🇺🇸 2.75s(111토큰, 자연종료) 57 tok/s — 지시 이행 불완전
exaone3.5:2.4b LG AI Research 🇰🇷 3.63s (250토큰 강제 도달) 71.6 tok/s
EXAONE-4.0-1.2B LG AI Research 🇰🇷 2.01~2.14s (250토큰 강제 도달) ~126 tok/s

EXAONE-4.0-1.2B를 Ollama로 서빙하는 쪽으로 확정. exaone3.5:2.4b보다 약 1.7배 빠르고 GGUF 크기는 절반(812MB), 품질도 손색없다. 단, 하이브리드 reasoning 모델이라 기본 상태로는 num_predict 예산을 추론에 다 쓰고 실제 답을 못 낸다 (Qwen3에서 먼저 겪은 것과 같은 실패 모드, 실측 확인). /api/chat 엔드포인트 + "think": false 옵션을 반드시 함께 써야 한다/api/generate에서는 think:false 가 반영되지 않았다. 라이선스는 EXAONE AI Model License Agreement 1.2 - NC(비상업, 원문 확인) — 팀 포트폴리오 프로젝트라 문제없다고 판단. 재현: scripts/test_generation_latency.py (HF 버전, 비교용), scripts/test_ollama_latency.py.

[V2 확인 완료] 개발 환경은 Apple M5 MacBook Air(통합메모리 24GB) / Apple M4 MacBook(통합메모리 16GB) 2대 — CUDA GPU는 없고 Metal(MPS/llama.cpp) 가속만 가능하다. bitsandbytes 등 CUDA 전용 양자화 도구는 이 환경에서 쓸 수 없다.

임베딩 모델 — KoE5로 확정 (2026-08-26, _project/decisions/010)

기존 ko-sroberta-multitask는 아키텍처(RoBERTa, 512 포지션)와 무관하게 sentence_bert_config.jsonmax_seq_length: 128이 박혀 있어 SentenceTransformer로 쓰면 128토큰에서 조용히 잘린다(설정 파일로 실측 확인). 지식베이스 조항 103건을 실제 토크나이즈한 결과 8.7%가 128토큰을 초과했다. nlpai-lab/KoE5 (intfloat/multilingual-e5-large 파인튜닝, MIT)는 이 truncation 설정이 없어 512토큰 그대로 쓰고, 임베딩 차원도 1024(기존 768) — ES dense_vector 매핑 차원을 768→1024로 바꿔야 한다(4주차 인덱스 생성 전).

컴플라이언스 분류기·NER — 유지 + 대조군 추가 (2026-08-26)

beomi/KcELECTRA-base(C-1~4·B-0)와 monologg/koelectra-base-v3-naver-ner(C-5 NER)는 그대로 유지한다 — 둘 다 이미 비중국 출처이고, KcELECTRA는 댓글 코퍼스 학습이라 STT 오탈자에 강해(5주차 오류 내성 실험과 성격이 맞는다. 대신 klue/roberta-base를 분류기 대조군으로 추가해 5주차에 실측 비교한다. NER의 P7(상세주소)은 NAVER NER 코퍼스에 전용 태그가 없어 규칙 기반 보강이 필요하다(미착수). 생성 모델과 달리 인코더는 파인튜닝 헤드 없이 정확도를 잴 수 없어 이번 세션에서는 결정만 하고 실측은 4~5주차로 미룬다.

캐시 저장소: 별도 저장소를 두지 않는다. 프로세스 인메모리 LRU + PostgreSQL 영속화로 처리한다.

3.2 도메인 라우팅 — 자동 분류 (2026-08-26 확정)

4개 데모 도메인을 지원하면서 검색 모듈이 통화의 도메인을 알아야 하는 문제가 생겼다. 수동 선택(상담원이 통화 시작 시 도메인을 고름) 대신 자동 분류로 확정했다 — 근거·설계 상세: _project/decisions/007-도메인-라우팅-자동분류-확정.md.

고객 초반 발화(STT 결과)
   ↓
① KcELECTRA 계열 분류기(B-0) — 4클래스 분류 + 신뢰도 점수
   (C-1~C-4 컴플라이언스 분류기와 같은 계열, 새 도구 없음)
   ↓
② 신뢰도 낮으면 폴백 — 4개 도메인 인덱스 전부에 하이브리드 검색을 돌려
   최고 스코어 도메인 채택 (기존 검색 모듈 재사용, 기본 경로 아님)
   ↓
call.domain 확정 (2026-08-26 DB 스키마 정리로 추가, `_project/decisions/006`)

평가는 6.1절 B-0 도메인 분류 정확도(목표 ≥0.95)로 하네스에 배선했다 (ai/apps/evaluation/metrics/domain_routing.py) — 실제 KcELECTRA 분류기 구현은 아직 미착수, 학습 데이터 확보(골든셋 확대) 이후 진행한다.


← 개발목차로 돌아가기