출제할 때마다 LLM을 부르면 안 된다 — 퀴즈 풀 사전생성
출제 버튼을 누를 때마다 LLM을 부르면 안 됩니다. 롤플레이 학습 챗봇에 도메인 퀴즈 기능을 붙이면서 가장 먼저 내린 결정이 이것이었습니다. 언뜻 보면 "문제 하나 만들어 줘"는 LLM에게 가장 쉬운 일처럼 보입니다. 하지만 사용자가 퀴즈를 풀 때마다 매번 모델을 호출하는 구조는, 만들어 놓고 보니 셋 다 나쁜 선택이었습니다. 느리고, 돈이 들고, 품질이 흔들립니다. 이 글은 그래서 퀴즈를 미리 만들어 풀(pool)로 저장하기로 한 이야기입니다.
[시리즈] S2S·LLM 음성 에이전트 엔지니어링 63편 · 현재 10-6편
← 이전: 10-5 AI 고객 페르소나 프롬프트 엔지니어링 / 다음 → 10-7 대화가 끝나면 채점한다
- 10-4 멀티 LLM 백엔드 스위칭
- 10-5 AI 고객 페르소나 프롬프트 엔지니어링
- 10-6 퀴즈 풀 사전생성 (현재 글)
- 10-7 대화가 끝나면 채점한다
- 10-8 혼자 쓰는 챗봇의 현실
전체 목차 펼치기 (63편)
Part 1 — 음성 AI가 느린 이유: 레이턴시 진단 (4편)
- 1-1 AI 보이스봇이 답답한 이유 — Cascaded 레이턴시
- 1-2 1초의 벽 — 지연이 UX를 결정하는 이유
- 1-3 지연 줄이기 — 스트리밍·청크로 TTFT 단축
- 1-4 RAG 병목과 오디오 버퍼링 제어
Part 2 — 텍스트를 넘어: End-to-End S2S의 원리 (4편)
- 2-1 End-to-End S2S 작동 원리
- 2-2 OpenAI Realtime API vs 오픈소스 S2S
- 2-3 음성 토큰(Audio Token)이란
- 2-4 감정·톤 조절이 CS를 바꾸는 이유
Part 3 — 실시간 대화와 에이전트화: 인터럽트·MCP·도구 (5편)
- 3-1 실시간 인터럽트(끼어들기) 감지
- 3-2 실시간 음성에 MCP가 필요한 이유
- 3-3 스트리밍 중 MCP 초고속 데이터 룩업
- 3-4 통화 중 외부 API 도구 호출
- 3-5 실시간 세션 관리 — 통화 맥락·고객 데이터
Part 4 — 인프라·세션·보안: 프로덕션 백엔드 (5편)
- 4-1 동적 컨텍스트 윈도우 관리
- 4-2 WebRTC·WebSocket 초저지연 음성
- 4-3 비동기 이벤트 루프와 동시 콜 처리
- 4-4 트래픽 스파이크를 견디는 확장성 설계
- 4-5 실시간 음성 보안 — 암호화·PII 마스킹
Part 5 — 운영·품질·평가: 에이전틱 워크플로우와 신뢰성 (7편)
- 5-1 에이전틱 워크플로우 — 응대를 넘어 완결로
- 5-2 상용화의 한계와 트러블슈팅
- 5-3 보이스 에이전트 평가(Eval) 설계
- 5-4 관측성(Observability) — 어디서 끊기나
- 5-5 가드레일과 환각 제어
- 5-6 상담사 전환(Handoff) 설계
- 5-7 프롬프트 A/B 테스트와 튜닝
Part 6 — 음성 경험 고도화·실전 통합: 한국어·UX·전화망·엣지 (5편)
- 6-1 한국어 음성 AI의 난관 — 존댓말·코드스위칭
- 6-2 턴테이킹과 시맨틱 VAD
- 6-3 보이스 페르소나 디자인
- 6-4 SIP·PSTN 전화망 연동
- 6-5 온디바이스·엣지 S2S와 프라이버시
Part 7 — S2S 도입 전략·구매 의사결정 (4편)
Part 8 — LLM 콜봇 PoC 현장 (6편)
- 8-1 LLM 콜봇 PoC — 검증의 갭
- 8-2 STT 정확도 93% 목표가 부족한 이유
- 8-3 금융 콜봇에서 소수점이 중요한 이유
- 8-4 TTFT 1.5초, 콜봇 스트리밍 파이프라인 설계
- 8-5 콜봇 SLM: 30B vs 70B 트레이드오프
- 8-6 PoC와 본사업 사이의 성능 괴리
Part 9 — 상담어시스트: 상담사 화면을 바꾸는 실시간 AI (7편)
- 9-1 상담어시스트란 무엇인가 — 실시간 STT가 상담사 화면을 바꾸는 구조
- 9-2 상담어시스트의 뼈대 — STT·인텐트·KMS 파이프라인
- 9-3 추천의 근거 — KMS·RAG 지식 그라운딩
- 9-4 통화 중 놓친 고지를 잡는 실시간 필수 안내 체크
- 9-5 AI가 상담 품질을 자동 채점하는 법 — AutoQA
- 9-6 신입 상담사에게 어시스트가 더 필요한 이유
- 9-7 온프레미스 상담어시스트 — GPU 제약과 모델 선택
Part 10 — 직접 만든 롤플레이 챗봇: RAG 에이전트 구축기 (8편)
- 10-1 왜 롤플레이 챗봇을 직접 만들었나
- 10-2 전체 아키텍처 한 장
- 10-3 RAG 파이프라인과 로컬 임베딩
- 10-4 멀티 LLM 백엔드 스위칭
- 10-5 AI 고객 페르소나 프롬프트 엔지니어링
- 10-6 퀴즈 풀 사전생성 (현재 글)
- 10-7 대화가 끝나면 채점한다
- 10-8 혼자 쓰는 챗봇의 현실
Part 11 — KMS 실무 — 지식관리시스템의 설계와 운영 현실 (8편)
실시간 생성의 함정 — 느리고, 비싸고, 흔들린다
처음 설계 초안에서는 출제를 실시간 생성으로 잡았습니다. 사용자가 카테고리와 난이도를 고르면 그 자리에서 LLM에게 "이 조건으로 문제 하나 만들어 줘"라고 요청하는 방식입니다. 코드가 가장 단순하고, 매번 새로운 문제가 나오니 신선해 보였습니다. 그런데 몇 번 돌려 보자마자 세 가지 문제가 동시에 드러났습니다.
첫째, 느립니다. 문제 하나를 생성하는 데 모델 응답을 통째로 기다려야 합니다. 로컬 모델이든 클라우드든 수 초가 걸리고, 그 시간 동안 화면은 그저 로딩 상태로 멈춰 있습니다. 퀴즈는 여러 문제를 연달아 푸는 흐름인데, 문제마다 이 지연이 끼면 학습 리듬이 끊깁니다. 둘째, 돈이 듭니다. 클라우드 백엔드를 쓸 때 출제 한 번이 곧 호출 한 번입니다. 사용자가 열 문제를 풀면 열 번을 호출하고, 그것도 대부분 매번 비슷비슷한 문제를 다시 만들어 내는 데 쓰입니다. 셋째, 품질이 흔들립니다. 같은 프롬프트여도 생성 결과는 매번 다릅니다. 어떤 문제는 명료하고 어떤 문제는 보기가 모호하거나 정답이 두 개처럼 읽힙니다. 실시간으로 뽑히는 문제는 사람이 미리 검수할 수가 없습니다.
정리하면 실시간 생성은 레이턴시·비용·일관성 세 축에서 모두 지는 구조였습니다. 셋 중 하나만 문제였어도 감수했겠지만, 세 개가 겹치니 다른 길을 찾아야 했습니다.
해법 — 퀴즈를 미리 만들어 풀로 저장한다
그림 1. 요청 시점에 LLM을 부르지 않고, 미리 채워둔 풀에서 조건에 맞는 문항을 즉시 꺼내 반환한다.
해법은 단순합니다. 출제 시점에 문제를 만들지 않는 것입니다. 대신 문제를 미리 만들어 두고, 출제할 때는 그 저장소에서 하나 꺼내 오기만 합니다. 이 저장소는 quiz_pool.json이라는 파일 하나로 뒀습니다. 카테고리별·난이도별로 미리 생성한 문제들이 이 파일 안에 담겨 있습니다.
이렇게 바꾸자 앞의 세 문제가 그대로 뒤집힙니다. 출제는 파일에서 문제 하나를 로딩하는 일이 되므로 레이턴시가 사실상 0이 됩니다. LLM을 아예 태우지 않으니 출제당 비용도 0입니다. 그리고 문제들은 미리 만들어 둔 것이라 사람이 한 번 훑어보고 어색한 것을 걸러낼 수 있어 품질이 고정됩니다. 사용자가 보는 순간에는 그저 준비된 문제 은행에서 문제가 즉시 나오는 경험이 됩니다.
여기서 핵심은 "생성"과 "출제"를 시간축에서 분리한 것입니다. 생성은 무겁고 느린 작업이지만 사용자가 기다리는 순간에 일어날 필요가 없습니다. 사용자가 기다리는 순간에 필요한 것은 오직 빠른 로딩뿐입니다. 두 일을 다른 시점으로 떼어 놓자, 각자 가장 잘하는 자리에 놓이게 됩니다.
오프라인 생성 — 8개 카테고리 × 난이도로 채운다
그렇다면 풀은 언제, 어떻게 채우는가. 이를 위해 generate_quiz_pool이라는 별도 스크립트를 뒀습니다. 이건 사용자 요청과 무관하게 오프라인에서 한 번 돌리는 배치 작업입니다. 여기서만 LLM을 호출합니다. 카테고리와 난이도 조합을 순회하면서 각 조합마다 문제를 여러 개 생성하고, 결과를 quiz_pool.json에 쌓습니다.
카테고리는 도메인을 8개 축으로 나눴습니다. IPCC 아키텍처, 음성 기술, AI·LLM, 상담 운영, 도입·구축, 비용·라이선스, AICC 기능, 그리고 현장에서 통용되는 용어와 정식 용어의 대조입니다. 마지막 축은 특히 신입에게 유용합니다. 현장에서 실제로 오가는 표현과 문서상의 정식 명칭이 다른 경우가 많기 때문입니다. 각 카테고리는 다시 난이도로 갈라집니다. 이렇게 카테고리 × 난이도의 격자를 만들고, 그 칸마다 문제를 채워 넣으면 풀 전체가 도메인을 고르게 덮게 됩니다.
이 구조의 장점은 갱신이 독립적이라는 점입니다. 새 주제를 추가하고 싶으면 생성 스크립트를 한 번 더 돌려 풀을 다시 채우면 됩니다. 운영 중인 서비스는 파일을 읽기만 하므로 갱신과 출제가 서로를 방해하지 않습니다. 생성 비용은 채워 넣는 그 한 번에만 발생하고, 이후 수많은 출제는 전부 공짜로 이 자산을 재사용합니다.
이 패턴이 맞는 곳과 아닌 곳
다만 이 결정을 모든 기능에 그대로 적용하지는 않았습니다. 같은 챗봇 안에서도 사전생성이 맞는 곳과 실시간이 맞는 곳이 갈립니다. 기준은 하나입니다. 콘텐츠가 정적인가, 동적인가입니다.
퀴즈처럼 "미리 만들어 둬도 되는" 정적 콘텐츠에는 사전생성이 맞습니다. 문제 은행은 사용자가 누구든, 언제 풀든 같은 문제 집합에서 나와도 아무 문제가 없습니다. 오히려 미리 검수된 고정 품질이 장점입니다. 반면 롤플레이 대화처럼 사용자의 직전 발화에 실시간으로 반응해야 하는 동적 콘텐츠에는 사전생성이 통하지 않습니다. AI 고객의 다음 대사는 사용자가 방금 뭐라고 말했느냐에 따라 매번 달라져야 하므로, 이건 그 자리에서 생성할 수밖에 없습니다. 미리 만들어 둘 수 있는 대화란 존재하지 않습니다.
그래서 같은 프로젝트 안에서 두 방식이 공존합니다. 퀴즈는 풀에서 꺼내 오고, 롤플레이는 매 턴 실시간으로 생성합니다. "LLM을 부르지 말라"는 건 절대 규칙이 아니라, 콘텐츠의 성격을 보고 부를 자리와 안 부를 자리를 나누라는 뜻이었습니다. 덧붙이면 이 챗봇은 필자가 개인 학습용으로 직접 만든 것이고, 이 글은 2026년 7월 시점의 개발 기록으로 시나리오와 사례는 익명화·재구성했으며 특정 회사·고객사·계약 정보는 담지 않았습니다.
자주 묻는 질문(FAQ)
Q. 문제를 미리 만들어 두면 최신성이 떨어지지 않나요?
어느 정도는 그렇습니다. 다만 여기서 다루는 도메인 지식은 매일 바뀌는 종류가 아니라, 아키텍처·용어·운영 개념처럼 비교적 안정적인 내용입니다. 이런 콘텐츠는 실시간 생성이 주는 "매번 새로움"보다 미리 검수된 품질이 더 가치 있습니다. 최신성이 필요하면 생성 스크립트를 주기적으로 다시 돌려 풀을 갱신하면 되고, 이때도 비용은 갱신 시점에 한 번만 발생합니다. 반대로 사용자의 발화에 따라 실시간으로 달라져야 하는 콘텐츠라면 애초에 사전생성 대상이 아니며, 그건 실시간 생성으로 두면 됩니다.
돌아보면 이 결정의 핵심은 퀴즈 생성 기술이 아니라, 무거운 일을 사용자가 기다리는 순간 밖으로 밀어낸다는 발상 하나였습니다.
댓글
댓글 쓰기