출제할 때마다 LLM을 부르면 안 된다 — 퀴즈 풀 사전생성


출제 버튼을 누를 때마다 LLM을 부르면 안 됩니다. 롤플레이 학습 챗봇에 도메인 퀴즈 기능을 붙이면서 가장 먼저 내린 결정이 이것이었습니다. 언뜻 보면 "문제 하나 만들어 줘"는 LLM에게 가장 쉬운 일처럼 보입니다. 하지만 사용자가 퀴즈를 풀 때마다 매번 모델을 호출하는 구조는, 만들어 놓고 보니 셋 다 나쁜 선택이었습니다. 느리고, 돈이 들고, 품질이 흔들립니다. 이 글은 그래서 퀴즈를 미리 만들어 풀(pool)로 저장하기로 한 이야기입니다.

[시리즈] S2S·LLM 음성 에이전트 엔지니어링 63편 · 현재 10-6편

← 이전: 10-5 AI 고객 페르소나 프롬프트 엔지니어링 / 다음 → 10-7 대화가 끝나면 채점한다

전체 목차 펼치기 (63편)

Part 1 — 음성 AI가 느린 이유: 레이턴시 진단 (4편)

Part 2 — 텍스트를 넘어: End-to-End S2S의 원리 (4편)

Part 3 — 실시간 대화와 에이전트화: 인터럽트·MCP·도구 (5편)

Part 4 — 인프라·세션·보안: 프로덕션 백엔드 (5편)

Part 5 — 운영·품질·평가: 에이전틱 워크플로우와 신뢰성 (7편)

Part 6 — 음성 경험 고도화·실전 통합: 한국어·UX·전화망·엣지 (5편)

Part 7 — S2S 도입 전략·구매 의사결정 (4편)

Part 8 — LLM 콜봇 PoC 현장 (6편)

Part 9 — 상담어시스트: 상담사 화면을 바꾸는 실시간 AI (7편)

Part 10 — 직접 만든 롤플레이 챗봇: RAG 에이전트 구축기 (8편)

Part 11 — KMS 실무 — 지식관리시스템의 설계와 운영 현실 (8편)

실시간 생성의 함정 — 느리고, 비싸고, 흔들린다

처음 설계 초안에서는 출제를 실시간 생성으로 잡았습니다. 사용자가 카테고리와 난이도를 고르면 그 자리에서 LLM에게 "이 조건으로 문제 하나 만들어 줘"라고 요청하는 방식입니다. 코드가 가장 단순하고, 매번 새로운 문제가 나오니 신선해 보였습니다. 그런데 몇 번 돌려 보자마자 세 가지 문제가 동시에 드러났습니다.

첫째, 느립니다. 문제 하나를 생성하는 데 모델 응답을 통째로 기다려야 합니다. 로컬 모델이든 클라우드든 수 초가 걸리고, 그 시간 동안 화면은 그저 로딩 상태로 멈춰 있습니다. 퀴즈는 여러 문제를 연달아 푸는 흐름인데, 문제마다 이 지연이 끼면 학습 리듬이 끊깁니다. 둘째, 돈이 듭니다. 클라우드 백엔드를 쓸 때 출제 한 번이 곧 호출 한 번입니다. 사용자가 열 문제를 풀면 열 번을 호출하고, 그것도 대부분 매번 비슷비슷한 문제를 다시 만들어 내는 데 쓰입니다. 셋째, 품질이 흔들립니다. 같은 프롬프트여도 생성 결과는 매번 다릅니다. 어떤 문제는 명료하고 어떤 문제는 보기가 모호하거나 정답이 두 개처럼 읽힙니다. 실시간으로 뽑히는 문제는 사람이 미리 검수할 수가 없습니다.

정리하면 실시간 생성은 레이턴시·비용·일관성 세 축에서 모두 지는 구조였습니다. 셋 중 하나만 문제였어도 감수했겠지만, 세 개가 겹치니 다른 길을 찾아야 했습니다.

해법 — 퀴즈를 미리 만들어 풀로 저장한다

퀴즈 풀 사전생성 — 그리드에서 즉시 꺼내기 카테고리 8개와 난이도 축으로 이루어진 격자에 퀴즈가 미리 채워져 있다. 요청이 오면 LLM을 그 자리에서 부르지 않고, 격자에서 조건에 맞는 칸 하나를 즉시 꺼내 반환한다. 즉시 반환8개 카테고리 × 난이도 사전 생성 풀

그림 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. 문제를 미리 만들어 두면 최신성이 떨어지지 않나요?
어느 정도는 그렇습니다. 다만 여기서 다루는 도메인 지식은 매일 바뀌는 종류가 아니라, 아키텍처·용어·운영 개념처럼 비교적 안정적인 내용입니다. 이런 콘텐츠는 실시간 생성이 주는 "매번 새로움"보다 미리 검수된 품질이 더 가치 있습니다. 최신성이 필요하면 생성 스크립트를 주기적으로 다시 돌려 풀을 갱신하면 되고, 이때도 비용은 갱신 시점에 한 번만 발생합니다. 반대로 사용자의 발화에 따라 실시간으로 달라져야 하는 콘텐츠라면 애초에 사전생성 대상이 아니며, 그건 실시간 생성으로 두면 됩니다.

돌아보면 이 결정의 핵심은 퀴즈 생성 기술이 아니라, 무거운 일을 사용자가 기다리는 순간 밖으로 밀어낸다는 발상 하나였습니다.

댓글

이 블로그의 인기 게시물

STT 인식률 93%의 함정 — 왜 그 숫자는 콜봇 목표가 될 수 없는가

AI 보이스봇이 답답한 이유: Cascaded 구조 레이턴시 완전 분석

음성 토큰(Audio Token)이란? LLM이 소리를 이해하는 원리