Back to projects

Belief Changer

Web

프로젝트 개요

Belief Changer는 사용자가 입력한 확언(affirmation)을 여러 사람의 대화 형태로 변환하고, 이를 TTS(Text-to-Speech)로 들려주는 웹 애플리케이션입니다. 인지과학의 자유 에너지 원리(Free Energy Principle)를 기반으로, 반복적인 청각 입력을 통해 믿음 체계를 점진적으로 재구성하는 것을 목표로 합니다.

이 프로젝트는 단순한 확언 반복 앱과는 다른 접근 방식을 취합니다. 자기 자신이 확언을 되뇌는 대신, 마치 주변 사람들이 나에 대해 긍정적으로 이야기하는 것처럼 들리도록 대화 스크립트를 생성합니다. 이러한 "타인의 시선" 시뮬레이션은 외부 감각 정보로 인식되어 더 효과적인 믿음 변화를 유도할 수 있습니다.

현재 React 19와 TypeScript를 기반으로 구축되었으며, Gemini AI를 활용한 자연스러운 대화 생성과 다중 TTS 제공자 지원을 특징으로 합니다. GitHub Pages를 통해 정적 사이트로 배포되어 누구나 쉽게 접근할 수 있습니다.

이론적 배경: 자유 에너지 원리

마르코프 블랭킷과 생성 모델

자유 에너지 원리(FEP)에 따르면, 우리와 외부 세계는 마르코프 블랭킷(Markov Blanket)이라는 경계로 구분됩니다. 우리는 외부 세계를 직접 인식할 수 없고, 오직 이 경계를 통해 전달되는 감각 정보만을 바탕으로 외부를 추론합니다. 즉, 우리 뇌는 외부 세계를 설명하는 내부 모델, 소위 생성 모델(Generative Model)을 구축하고 유지합니다.

이 모델의 핵심은 정밀도(precision) 개념입니다. 정밀도가 높은 믿음은 쉽게 바뀌지 않으며, 새로운 감각 정보가 들어와도 무시하거나 재해석하는 경향이 있습니다. 반대로 정밀도가 낮은 믿음은 새로운 증거에 따라 쉽게 업데이트됩니다.

믿음 변화의 두 가지 경로

생성 모델을 업데이트하는 방식은 크게 두 가지가 있습니다. 첫 번째는 지각적 추론(perceptual inference)으로, 들어오는 감각 정보에 맞게 내부 믿음을 수정하는 것입니다. 두 번째는 능동적 추론(active inference)으로, 감각 정보 자체를 바꾸기 위해 환경에 개입하는 것입니다. 두 경로 모두 자유 에너지를 최소화하고 예측 오류(surprise)를 줄이는 방향으로 작동합니다.

믿음의 층위와 변경 가능성

믿음에는 층위가 있습니다. 유전적으로 고정된 핵심 믿음(예: "나는 인간이다")은 변경이 거의 불가능합니다. 반면, 살아가며 형성된 후천적 믿음(가치관, 자아 이미지, 문화적 규범)은 적절한 조건에서 변경될 수 있습니다. Belief Changer가 목표로 하는 것은 바로 이 후천적 믿음 영역입니다.

믿음 재학습 메커니즘

특정 믿음을 변화시키려면 두 가지 조건이 필요합니다. 첫째, 기존 믿음의 정밀도를 낮춰야 합니다. 이는 기존 믿음과 충돌하는 예측 오류가 발생할 때의 불안감을 감쇠시키는 것으로 달성할 수 있습니다. 둘째, 새로운 믿음의 정밀도를 높여야 합니다. 반복 확언(Affirmation), 정밀한 시뮬레이션(Visualization), 실제로 그렇게 행동하기(Acting As If) 등의 방법이 여기에 해당합니다.

왜 "타인의 대화"인가

이 앱의 핵심 가설은 "남에게 그렇게 듣는 것"이 믿음 변화에 효과적이라는 점입니다. 자기 자신의 확언은 내부 모델로 처리되지만, 타인의 발화로 인식되는 음성은 외부 감각 입력으로 처리됩니다. 따라서 TTS로 여러 사람이 나에 대해 긍정적으로 이야기하는 것을 반복해서 듣는 것은, 단순한 자기 확언보다 더 강력한 믿음 재학습 효과를 가질 수 있습니다.

기술 스택

프론트엔드 프레임워크

React 19를 채택하여 최신 Concurrent 기능과 개선된 Suspense를 활용했습니다. 특히 React 19의 새로운 훅들과 자동 배칭(automatic batching)은 TTS 재생 중 상태 업데이트의 성능을 크게 개선했습니다. TypeScript를 전면 도입하여 컴포넌트 props, 상태, API 응답 모두에 엄격한 타입 안전성을 보장했습니다.

Vite를 빌드 도구로 선택한 이유는 개발 서버의 빠른 시작 속도와 Hot Module Replacement(HMR) 성능 때문입니다. 프로덕션 빌드 시에도 Rollup 기반의 최적화된 번들링을 제공하여 GitHub Pages 배포에 적합한 작은 번들 크기를 달성했습니다.

상태 관리

Zustand를 상태 관리 라이브러리로 선택했습니다. Redux에 비해 보일러플레이트가 적고, Context API보다 성능이 우수하며, 특히 여러 스토어를 분리하여 관리하기 용이합니다. 프로젝트에서는 확언 목록(affirmationStore), 생성된 대화(dialogueStore), 앱 설정(settingsStore)을 각각 독립적인 스토어로 관리합니다.

Zustand의 구독 기반 아키텍처는 TTS 재생 상태 추적에 특히 유용했습니다. 재생 중인 대화의 현재 위치, 재생/일시정지 상태, 음성 설정 변경 등을 세밀하게 추적하면서도 불필요한 리렌더링을 최소화할 수 있었습니다.

AI 대화 생성

Google의 Gemini API를 사용하여 확언을 자연스러운 대화 스크립트로 변환합니다. 단순히 확언을 반복하는 것이 아니라, 마치 여러 사람이 해당 주제에 대해 자연스럽게 대화하는 것처럼 스크립트를 생성합니다. 각 화자에게 고유한 성격과 말투를 부여하여 현실감을 높였습니다.

프롬프트 엔지니어링을 통해 대화가 지나치게 인위적이거나 과장되지 않도록 조정했습니다. 또한 생성된 대화에 화자 ID를 부여하여 TTS 재생 시 서로 다른 음성으로 구분되도록 했습니다.

TTS 시스템 아키텍처

TTS 기능은 확장 가능한 Provider 패턴으로 설계되었습니다. TTSManager 싱글톤이 전체 TTS 생명주기를 관리하며, 실제 음성 합성은 각 Provider에게 위임합니다. 현재 세 가지 Provider를 지원합니다.

Web Speech API Provider는 브라우저 내장 TTS를 사용하여 별도 API 키 없이 즉시 사용할 수 있습니다. 음성 품질은 시스템에 따라 다르지만, 무료로 사용할 수 있다는 장점이 있습니다. ElevenLabs Provider는 고품질 AI 음성을 제공하며, 특히 감정 표현과 자연스러운 억양에서 뛰어납니다. OpenAI TTS Provider는 안정적인 품질과 다양한 음성 옵션을 제공합니다.

각 Provider는 BaseHTTPProvider 추상 클래스를 확장하여 공통 로직(초기화, 캐싱, 에러 처리)을 재사용합니다. 이 설계 덕분에 새로운 TTS 서비스를 추가할 때 핵심 인터페이스만 구현하면 됩니다.

주요 기능

확언 관리

사용자는 변화시키고 싶은 믿음을 확언 형태로 입력합니다. 예를 들어 "나는 자신감 있게 발표한다", "나는 새로운 사람들과 쉽게 친해진다" 같은 문장을 작성할 수 있습니다. 입력된 확언은 로컬 스토리지에 저장되어 브라우저를 닫아도 유지됩니다.

확언 카드 UI를 통해 등록된 확언을 한눈에 확인하고, 수정하거나 삭제할 수 있습니다. 각 확언에 대해 개별적으로 대화 생성을 요청하거나, 여러 확언을 조합하여 하나의 대화 세션을 구성할 수도 있습니다.

AI 대화 생성

확언을 선택하고 생성 버튼을 누르면 Gemini AI가 해당 확언을 중심으로 한 대화 스크립트를 생성합니다. 대화는 2-4명의 화자가 참여하며, 각 화자는 확언의 내용을 자연스럽게 언급합니다. 생성된 대화는 JSON 형태로 구조화되어 각 발화에 화자 ID, 텍스트, 감정 힌트가 포함됩니다.

대화 생성 과정에서 로딩 상태와 에러 처리를 명확히 표시합니다. API 요청 실패 시 재시도 옵션을 제공하고, 생성된 대화가 마음에 들지 않으면 다시 생성할 수 있습니다.

TTS 재생

생성된 대화는 DialoguePlayer 컴포넌트를 통해 재생됩니다. 선택한 TTS Provider에 따라 다른 음성으로 대화가 합성되며, 각 화자별로 다른 음성을 할당하여 실제 대화처럼 느껴지도록 했습니다.

재생 컨트롤에는 재생/일시정지, 이전/다음 발화 이동, 전체 반복 등의 기능이 있습니다. 현재 재생 중인 발화는 UI에서 하이라이트되어 텍스트를 따라 읽기 쉽습니다. 백그라운드 재생도 지원하여 다른 작업을 하면서 들을 수 있습니다.

설정 및 개인화

설정 페이지에서 TTS Provider를 선택하고, 각 Provider별 API 키를 입력할 수 있습니다. 음성 속도, 피치 조절 기능도 제공하여 사용자 취향에 맞게 조정할 수 있습니다. 모든 설정은 로컬 스토리지에 저장되어 다음 방문 시에도 유지됩니다.

다크 테마를 기본으로 적용하여 장시간 사용 시 눈의 피로를 줄였습니다. 커스텀 CSS 테마 시스템을 구축하여 색상, 타이포그래피, 간격을 중앙에서 관리합니다.

개발 과정

설계 및 아키텍처

프로젝트 초기에 핵심 도메인 모델을 정의했습니다. 확언(Affirmation), 대화(Dialogue), 발화(Utterance), 화자(Speaker) 등의 타입을 먼저 설계하고, 이를 기반으로 컴포넌트와 스토어 구조를 결정했습니다. 특히 TTS 시스템의 확장성을 고려하여 Provider 패턴을 도입한 것이 후반 개발에서 큰 도움이 되었습니다.

컴포넌트 계층은 페이지(Pages), 기능 컴포넌트(Components), 서비스(Services), 스토어(Store)로 명확히 분리했습니다. 각 레이어는 단방향 의존성을 유지하여 테스트와 유지보수를 용이하게 했습니다.

TTS Provider 통합

가장 도전적이었던 부분은 여러 TTS Provider를 통합하는 것이었습니다. 각 서비스마다 API 형태, 인증 방식, 응답 포맷이 달랐기 때문입니다. 이를 해결하기 위해 공통 인터페이스(TTSProvider)를 정의하고, 각 Provider가 이를 구현하도록 했습니다.

HTTP 기반 Provider들(ElevenLabs, OpenAI)은 BaseHTTPProvider를 통해 공통 로직을 공유합니다. 여기에는 요청 재시도, 응답 캐싱, 에러 정규화 등이 포함됩니다. Web Speech API는 브라우저 내장 기능이므로 별도의 추상화 없이 직접 구현했습니다.

성능 최적화

TTS 재생 중 UI 반응성을 유지하기 위해 여러 최적화를 적용했습니다. 음성 합성 요청은 현재 발화보다 한두 개 앞서 미리 요청하는 프리페칭 전략을 사용합니다. 또한 합성된 오디오 데이터를 메모리에 캐싱하여 같은 발화를 반복 재생할 때 API 호출을 줄입니다.

Zustand 스토어의 선택적 구독(selective subscription)을 활용하여 상태 변경 시 필요한 컴포넌트만 리렌더링되도록 했습니다. 특히 재생 진행 상태는 초당 여러 번 업데이트될 수 있으므로, 불필요한 리렌더링을 방지하는 것이 중요했습니다.

결과 및 향후 계획

프로젝트의 핵심 목표였던 "확언을 대화로 변환하여 TTS로 재생하는 것"을 성공적으로 구현했습니다. Gemini AI가 생성하는 대화의 품질이 기대 이상이었고, 다중 TTS Provider 지원으로 사용자에게 선택권을 제공했습니다. 개인적으로 실제 사용하면서 확언 청취 경험이 단순 반복보다 훨씬 몰입감 있다고 느꼈습니다.

기술적으로는 TypeScript의 엄격한 타입 시스템과 Zustand의 유연한 상태 관리를 결합하는 패턴에 익숙해졌습니다. 또한 여러 외부 API를 통합하는 Provider 패턴의 설계와 구현 경험을 쌓았습니다.

향후에는 대화 생성 품질을 더욱 개선하고, 사용자가 화자 성격을 직접 설정할 수 있는 기능을 추가할 계획입니다. 또한 오프라인 사용을 위해 생성된 대화와 합성된 음성을 로컬에 저장하는 기능도 고려하고 있습니다.

링크