
9개의 LLM 제공업체, RAG, 그리고 Next.js를 사용하여 AI 기반 인터랙티브 픽션 플랫폼을 구축한 방법
요약
9개의 LLM과 RAG 기술을 활용하여 일관성 있는 서사를 유지하는 AI 기반 인터랙티브 픽션 플랫폼 'Twistloom' 구축 과정을 소개합니다. 단순 텍스트 생성을 넘어 시맨틱 메모리와 구조화된 스토리 엔진을 통해 장기적인 이야기의 맥락을 유지하는 기술적 접근을 다룹니다.
핵심 포인트
- RAG 기반 시맨틱 메모리를 통한 장기적 서사 일관성 유지
- 9개의 LLM 제공업체를 활용한 분산형 AI 오케스트레이션 구현
- 사용자 커스텀 액션에 대응하는 동적 스토리 생성 엔진
- Next.js를 활용한 풀스택 AI 애플리케이션 아키텍처
어렸을 때, 나는 짧고 전개가 빠른 분기형 공포 서적들을 탐독하곤 했습니다. 어떤 페이지를 넘기느냐에 따라 결말이 달라지는 Goosebumps 시대의 '당신의 선택에 따른 악몽(choose-your-own-nightmare)' 이야기들 말이죠. 수년이 흐른 뒤, 풀스택 엔지니어(full-stack engineer)로서 거의 10년의 경력을 쌓은 후, 나는 한 가지 궁금증이 생겼습니다.
AI가 정말로 모든 것을 기억하는 인터랙티브 소설을 생성할 수 있을까?
미리 작성된 분기점이 있는 '당신의 선택에 따른 모험(choose-your-own-adventure)' 방식이 아닙니다. 50페이지 전에 당신이 무엇을 했는지 추적하고, 원한을 품으며, 칼을 꽂을 적절한 순간을 기다리는 살아있는 이야기 말입니다.
그 질문이 바로 Twistloom이 되었습니다. 호기심으로 시작한 프로젝트는 9개의 LLM 제공업체, RAG(Retrieval-Augmented Generation) 기반의 시맨틱 메모리(semantic memory), 그리고 망각을 거부하는 구조화된 스토리 엔진을 아우르는 분산형 AI 오케스트레이션(orchestration) 시스템으로 발전했습니다.
사람들은 AI 스토리텔링을 단일 프롬프트로 상상합니다
보통 사람들은 AI 소설을 다음과 같이 생각합니다:
공포 이야기를 써줘...
끝.
하지만 Twistloom을 구축하면서, 나는 가장 어려운 부분이 텍스트를 생성하는 것이 아니라는 점을 깨달았습니다. LLM은 그 부분에서 놀라울 정도로 뛰어납니다. 진짜 어려운 부분은 모든 독자가 지속적으로 이야기를 변화시키는 와중에 수백 페이지에 걸쳐 일관성을 유지하는 것입니다.
캐릭터의 성격이 갑자기 변해서는 안 됩니다. 사라진 열쇠가 마법처럼 다시 나타나서도 안 됩니다. 50페이지 전에 죽은 사람이 아무렇지 않게 장면 속으로 다시 걸어 들어와서도 안 됩니다. 생성되는 모든 페이지는 그 이전에 일어난 모든 일을 이해해야 합니다. 그리고 이 문제는 생성 그 자체보다 훨씬 더 흥미로운 문제라는 것이 밝혀졌습니다.
Twistloom의 실체
독자에게 Twistloom은 기만적일 정도로 단순해 보입니다.
당신은 이야기 아이디어를 입력합니다:
비가 그치지 않는 도시의 한 형사가 아직 죽지 않은 피해자들로부터 편지를 받기 시작한다.
불과 몇 분 만에, 완전한 분기형 심리 스릴러가 나타납니다. 모든 페이지는 AI에 의해 생성되고 당신의 캐릭터 상황에 맞춰 조정된 선택지로 끝납니다. 하나를 선택하면 세계가 반응합니다.
그리고 때로는, AI가 당신이 원하는 옵션을 전혀 제공하지 않을 수도 있습니다. 그럴 때는 직접 입력할 수 있습니다 — 커스텀 액션 등 무엇이든 가능합니다. 시스템은 그것이 타당한지 확인하고 점수를 매긴 뒤, 그럼에도 불구하고 그에 맞는 페이지를 생성합니다. 잠긴 문을 열려고 시도했나요? 그렇다면 당신이 그렇게 했을 때 어떤 일이 벌어지는지를 써 내려갈 것입니다.
세 가지 이야기 형태가 있습니다:
| 모드 | 느낌 |
|---|---|
| 소설 (Novel) | 직선 형태 — 하나의 결말을 향해 한 페이지씩 차례대로 진행됩니다. |
| ... |
마지막 모드는 사람들이 처음에는 믿기 어려워하는 방식입니다. 멀티버스 모드 (Multiverse Mode)에서는, 동일한 선택을 한 두 명의 독자라도 서로 다른 이야기를 마주할 수 있습니다. 시스템이 백그라운드에서 후보 분기들을 지속적으로 생성하기 때문에, 스포일러는 사실상 불가능합니다. 어떤 두 번의 플레이도 똑같을 수 없습니다.
선택지들은 이야기 전체에 울려 퍼집니다. 캐릭터들은 친절과 배신을 기억하고, 당신의 정신력 수치 (sanity meter)는 긴장감에 따라 오르내리며, 당신이 놓친 단서들은 그대로 묻혀 있습니다. 그리고 결말은 작가가 계획한 것이 아니라, 당신이 실제로 행한 것에 따라 적응합니다.
엔지니어링 과제는 AI가 아니라 — 메모리였습니다
LLM (Large Language Model)으로 글을 쓸 때 직면하는 문제는 이렇습니다. 텍스트 생성 호출은 컨텍스트 윈도우 (context window)에 집어넣은 내용 외에는 아무것도 기억하지 못합니다. 일관성 있는 300페이지 분량의 분기형 소설을 원한다면, 매번 책 전체를 붙여넣을 수는 없습니다.

그래서 Twistloom은 이야기를 단순한 평면 텍스트로 취급하지 않습니다. 모든 페이지는 구조화된 서사 상태 스냅샷 (structured narrative state snapshot)을 포함합니다. 백엔드에서는 보이지 않는 곳에서 다음 사항들을 추적합니다:
- 캐릭터의 심리, 원형(archetype), 그리고 안정성
- 관계 및 상호작용 이력
- 발견된 단서 및 미해결된 미스터리
- 장소, 위치 및 그 분위기
- 사실 이력(facts history) — 엔티티별 지속적인 사실의 진화
- 세계의 시계, 분위기, 날씨, 날짜 및 시간
- 장면의 추진력(momentum) 및 서사적 흐름(narrative threads)
- 숨겨진 상태(hidden state) — 진실 수준, 위협 근접도, 현실 안정성, 그리고 계획된 결말
AI가 다음 페이지를 작성할 때, 단순히 이전 문단만 받는 것이 아닙니다. AI는 _세계의 상태(state of the world)_를 전달받습니다. 일관성은 더 이상 막연한 희망이 아니라 데이터 구조가 됩니다.
그리고 상태(state)의 양은 매우 방대합니다. 재구성을 빠르게 유지하기 위해, 저는 **하이브리드 델타 + 체크포인트 아키텍처 (hybrid delta + checkpoint architecture)**를 구축했습니다. 스냅샷은 5페이지마다 저장(persist)되며, 그 사이의 모든 것은 증분 델타(incremental deltas)로 저장됩니다. 이야기 상태를 재구성하는 것은 처음부터 모든 페이지를 다시 재생하는 것보다 대략 90% 더 빠릅니다.
컨텍스트 윈도우(context windows)가 충분하지 않을 때: Jina AI + pgvector를 사용한 RAG
구조화된 상태가 있더라도 한계는 존재합니다. 캐릭터의 배경 이야기, 세계관 설정(lore), 30장 전에 발견된 단서 등 — 이 모든 것을 매번 모델에 보낼 수는 없습니다.
이 지점에서 **검색 증강 생성 (Retrieval-Augmented Generation, RAG)**이 등장합니다.
Twistloom은 **Jina AI 임베딩 (embeddings)**을 사용하여 서사적 기억을 임베딩하고, 이를 pgvector를 사용하여 PostgreSQL에 직접 저장합니다. 모델에게 소설 전체를 다시 재생하는 대신, 생성 시점에 현재 장면에 가장 관련성이 높은 기억만을 검색하여 가져옵니다.
저는 의미론적 기억(semantic memory)을 5개의 전용 임베딩 테이블로 나누었습니다 (1024차원, 빠른 근사 최근접 이웃(ANN) 검색을 위해 HNSW 인덱스 적용):
page_embeddings — 서사적 산문 그 자체
character_embeddings — 모든 인물이 누구인지, 그리고 그들이 어떻게 느끼는지
place_embeddings — 장면이 발생하는 장소
...
페이지가 작성되기 직전에, 엔진은 현재의 컨텍스트 (context)를 임베딩 (embedding)하고, 이 테이블들 전반에서 가장 가까운 메모리들을 쿼리 (query)하며, 오직 관련 있는 파편들만을 프롬프트 (prompt)에 주입합니다. 임베딩은 각 페이지가 영구 저장된 후 '발사 후 망각 (fire-and-forget)' 방식으로 삽입됩니다. 델타 체인 리플레이 (delta-chain replay) 중에는 절대 삽입되지 않으므로, 시맨틱 메모리 (semantic memory)가 상태 재구성 (state reconstruction) 속도를 늦추는 일은 결코 발생하지 않습니다.
그 결과, 프롬프트 크기를 폭발적으로 늘리지 않으면서도 그 어떤 컨텍스트 윈도우 (context window)가 허용하는 것보다 훨씬 더 많은 것을 기억하는 스토리 엔진이 탄생했습니다.
하나의 AI 제공업체로는 충분하지 않았다
대부분의 AI 애플리케이션은 단일 제공업체 (provider)에 의존합니다. 그것은 작동합니다 — 작동하지 않게 될 때까지는 말이죠.
속도 제한 (Rate limits). 지역적 장애 (Regional outages). 모델 지원 중단 (Model deprecations). 일시적인 오류 (Temporary failures). 독자가 장면 중간에 머물며 다음 페이지를 기다리고 있는 경험에서, 제공업체의 장애는 단순한 에러 로그가 아니라 — 망쳐버린 독서 세션입니다.
그래서 Twistloom은 모든 제공업체를 공통 인터페이스 (common interface) 뒤로 추상화하며, 9개의 서로 다른 LLM 제공업체를 폭포수 (waterfall) 방식으로 오케스트레이션 (orchestrate)합니다:
gemini → github → cohere → mistral → groq → cerebras → nvidia → openrouter → cloudflare
만약 한 제공업체의 속도 제한이 걸리거나, 성능이 저하되거나, 다운되면, 요청은 자동으로 다음 제공업체로 넘어갑니다. 비즈니스 로직은 어떤 제공업체가 페이지를 생성했는지 전혀 신경 쓰지 않습니다 — 제공업체들은 동일한 계약 (contract)을 구현하는 교체 가능한 구현체들일 뿐입니다. 독자의 경험은 특정 벤더에게 어떤 일이 일어나든 상관없이 계속됩니다.
전체 설계를 형성한 하나의 통찰: 회복탄력성 (resilience)은 마지막에 덧붙이는 기능이 아닙니다. 그것은 제공업체 인터페이스를 정의하는 방식에서 첫날부터 내리는 아키텍처적 결정입니다. 만약 당신의 코드가 단일 SDK를 대상으로 작성되었다면, 나중에 두 번째 제공업체를 추가하는 것은 재작성(rewrite)이 됩니다. 하지만 계약 (contract)을 대상으로 작성되었다면, 그것은 설정 변경 (config change)일 뿐입니다.
모든 페이지는 두 번 생성된다
제가 빠르게 배운 또 다른 사실은 이것입니다: LLM은 때때로 놀라울 정도로 좋은 이야기를 만들어내지만 — 때때로 완전히 말도 안 되는 소리를 하기도 한다는 점입니다.
단일 응답을 신뢰하는 대신, 모든 생성 과정은 **두 번의 호출 파이프라인 (two-call pipeline)**을 거칩니다:
- **기본 호출 (primary call)**이 페이지, 액션(actions), 그리고 상태 업데이트(state updates)를 생성합니다.
- 두 번째 호출 (second call) — 다른 모델과 다른 프롬프트를 사용하며 — 평가자 판사(evaluator judge) 역할을 수행합니다. 이 단계에서는 페이지가 독자에게 전달되기 전에 품질, 일관성(coherence), 정확성(correctness)을 점수화합니다.

그뿐만 아니라, 모든 페이지는 캐논 검증 (canon validation) (runCanonValidationPass())을 실행합니다. 엔진은 타임라인 모순, 캐릭터 지식 위반, 설정된 사실과의 충돌, 그리고 성격 불일치를 체크합니다. 만약 페이지가 캐논(canon)을 위반하면, 이는 플래그가 지정되어 다시 작성됩니다. 즉, 독자가 세 장(chapter) 뒤에서 화를 내며 발견하기 전에 생성 단계에서 처리됩니다.
커스텀 액션(Custom actions)은 자체적인 3단계 게이트 파이프라인(three-gate pipeline)을 거칩니다:
- 게이트 0 (클라이언트): 정규 표현식(regex) 안전 패턴이 프롬프트 인젝션(prompt injection)을 방어합니다.
- 게이트 1 (AI): 개연성(plausibility) + 전개(progression) 점수화 — 이 액션이 이야기를 앞으로 진행시킬 것인가?
- 게이트 2 (생성): 전체 캐논 검증이 완료된 결과 페이지.
그리고 분기 계약(branching contract)은 관례가 아닌 데이터베이스 계층에서 강제됩니다. 소설 모드(Novel mode)는 페이지당 하나의 액션과 하나의 목적지만을 허용합니다. 인터랙티브(Interactive) 모드는 26개의 액션을 허용합니다. 멀티버스(Multiverse) 모드는 26개의 액션과 무제한의 목적지를 허용합니다. 백엔드는 모드 계약을 위반하는 모든 것을 거부하므로, 모델이 무엇을 출력하든 스토리 그래프(story graph)는 구조적으로 유효하게 유지됩니다.
생성 비용이 많이 들기 때문에, 책은 **비동기적 (asynchronously)**으로 생성됩니다. GitHub Actions 워커가 무거운 작업을 처리하는 동안, 사용자는 폴링(polling)을 통해 진행 상황이 스트리밍되는 것을 지켜볼 수 있습니다. 책 생성을 시작해 두고 완료되면 다시 돌아오면 됩니다.
기술 스택 (The tech stack)
전체 시스템은 제가 꿈꾸던 JAMstack 위에서 구동됩니다:
프론트엔드 (Frontend)
- Next.js 16 (App Router, ISR, Server Components) + React 19
- 전체 스택에 걸친 TypeScript
- 긴장감 기반 테마 (tension-driven theming)를 적용한 Tailwind CSS v4 — 이야기의 긴장감이 높아짐에 따라 UI 색상이 실제로 더 어둡게 변합니다.
- 영어 및 인도네시아어 로케일(locale) 지원을 위한 next-intl
- 서버 상태 관리를 위한 TanStack Query, 클라이언트 상태 관리를 위한 Zustand
- 쿠키 기반 JWT 인증을 위한 NextAuth v5, 관리자용 리치 에디터(rich editor)를 위한 TipTap
백엔드 (Backend)
- Hono — 초고속, 런타임 불가지론적 (runtime-agnostic) HTTP 프레임워크
- 타입 안전한 (type-safe) SQL을 위한 Drizzle ORM
- 시맨틱 메모리 (semantic memory)를 위한 pgvector가 포함된 Neon PostgreSQL (서버리스)
- 비동기 생성 러너 (async generation runner)로서의 GitHub Actions
- 크레딧 경제를 위한 Stripe, 미디어를 위한 ImageKit, 이메일을 위한 Resend
AI
- 자동 폴백 (fallback) 기능이 있는 9개의 LLM 제공업체
- Jina AI 임베딩 (embeddings) + pgvector RAG 파이프라인
- 구조화된 JSON 생성, 2단계 호출 평가 (two-call evaluation), 캐논 검증 (canon validation)
모든 것은 Vercel 서버리스에 배포됩니다. 공개 페이지는 ISR 캐싱(60~300초)이 적용되며, 독자 페이지는 빠른 로딩을 위해 캐싱됩니다. 생성 UX는 클릭 시 첫 번째 네트워크 요청이 완료되기 전에도 모달이 즉시 열리도록 설계되었으며, 이야기가 조각조각 조립됨에 따라 진행 상황이 스트리밍됩니다.
단순한 AI 스토리 생성기 그 이상
처음 시작했을 때, 저는 제가 분기형 공포 생성기를 만들고 있다고 생각했습니다.
몇 달간의 단독 작업 끝에, 저는 제가 훨씬 더 큰 무언가를 만들고 있다는 것을 깨달았습니다. 바로 **AI 네이티브 인터랙티브 픽션 플랫폼 (AI-native interactive fiction platform)**입니다. 단순한 텍스트 생성기가 아니라, 독자들이 살아있는 이야기를 경험하고, 궁극적으로 작가들이 AI를 대체재가 아닌 창의적 파트너로서 함께 앉게 되는 플랫폼입니다.
저의 장기적인 비전은 AI가 작성한 저질 콘텐츠 (slop)를 만드는 것이 아닙니다. 실제 작가들에게 다음과 같은 코파일럿 (co-pilot)을 제공하는 것입니다:
- 그들의 세계관 (lore)을 기억하고
- 그들의 캐릭터를 이해하며
- 그들의 설정 (canon)을 보호하고
- 펜을 빼앗지 않으면서 아이디어를 제안하는 것
AI는 당신을 대신해서 쓰는 것이 아니라, 당신과 함께 써야 합니다. 그것이 제가 가장 중요하게 생각하는 차이점입니다.
현재 이 플랫폼은 무료로 사용 가능합니다. 비상업적 무료 티어(free-tier) LLM API 키를 사용하고 있어 아직 법적으로 상업화할 수 없기 때문입니다. 크레딧 경제(credit economy), 구독(subscriptions), 그리고 게임화된 시스템(gamified systems)은 모두 구축되어 준비가 완료된 상태이며, 설정 하나만 바꾸면 바로 상업적 전환이 가능합니다.
내가 배운 것
이 프로젝트는 저에게 예상치 못한 무언가를 가르쳐 주었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기