← 프로젝트 목록

CS 문의 자동 분류

AI(Bedrock) + Virtual Thread + NDJSON 스트리밍 — Full-stack 단독 구현

AWS BedrockSpring AIVirtual ThreadsNDJSONNext.jsFull-stackHexagonal

배경 및 제약조건

  • CS 운영팀이 고객 문의를 수동으로 분류하는 데 하루 수 시간 소요
  • 1000건 순차 처리 시 30분 소요 — Bedrock rate limit으로 무한 병렬 불가
  • 분류 진행 상황을 실시간으로 확인할 수 없으면 UX 불만
  • 백엔드(Spring Boot) + 프론트(Next.js) Full-stack 단독 구현

설계 구조

전체 흐름
엑셀 업로드 → 미리보기(5건) → 분류 실행 → 실시간 스트리밍 → 엑셀 다운로드

[Next.js 16] sk-admin-web
├── useClassify() 커스텀 훅 — ReadableStream 파싱
├── PreviewTable (컬럼 + 샘플 확인)
├── ResultTable (실시간 진행률 + 건별 결과)
└── Feature-Sliced Design (entities/features/app)

[Spring Boot 4.0] sk-admin-app
├── QuestionClassifyController
│   ├── /preview → JSON (컬럼 + 샘플 5건)
│   ├── /classify → StreamingResponseBody + NDJSON
│   └── /export → xlsx binary
├── QuestionClassifyService (Application — 얇은 오케스트레이터)
├── ParallelClassifyExecutor (Domain Service)
│   └── Virtual Threads + Semaphore(5)
├── ClassifyRetryPolicy (재시도 3회, 2초 간격)
└── QuestionClassifier (Bedrock ChatClient, Claude Haiku 4.5)

NDJSON 메시지 포맷:
{"type":"meta","total":100}
{"type":"result","index":1,"data":{"csId":"CS001","level1":"배송",...}}
{"type":"done","success":98,"failed":2}

Problem & Solution

Problem

1000건 순차 처리 30분 — Bedrock rate limit으로 무한 병렬도 불가

Solution

Java 25 Virtual Threads로 건별 경량 스레드 생성 + Semaphore(5)로 동시 호출 수 제한. Bedrock rate limit 보호하면서 처리 시간 5배 단축 (~3~4분)

Problem

수 분간 진행 상황을 알 수 없는 UX — SSE는 복잡, WebSocket은 과잉

Solution

StreamingResponseBody + NDJSON(줄 구분 JSON)으로 건별 결과를 실시간 전송. 프론트에서 ReadableStream으로 파싱하여 진행률 바 + 결과 테이블 즉시 렌더링

Problem

Spring AI 1.0의 Bedrock auto-configuration이 Boot 4.0.1과 호환되지 않아 ClassNotFoundException

Solution

auto-config 비활성화 + BedrockConfig에서 ChatClient 수동 빈 등록. retry/timeout/maxTokens 직접 설정

기술적 챌린지

엑셀 HTML 엔티티 처리

레거시 시스템 엑셀에 HTML 엔티티(&, <)가 포함되어 AI 분류 정확도 저하. 읽기/쓰기 양쪽에서 디코딩 처리

성과

  • CS 문의 분류 시간 대폭 단축 (1000건 기준 3~4분)
  • NDJSON 실시간 스트리밍으로 즉각적 UX
  • Virtual Threads + Semaphore로 Bedrock rate limit 보호 + 안정성
  • Full-stack 단독 구현 (백엔드 DDD/Hexagonal + 프론트 Next.js/FSD + AI Bedrock)