오픈 웨이트 타로 리더를 만들고 나서, 백업 모델이 실제 작업을 수행하고 있다는 것을 알게 되다
요약
오픈 웨이트 AI 기반 타로 리더 'Pocket Oracle'을 개발한 경험을 공유합니다. 이 앱은 78장 카드 덱, 구조화된 해석, ElevenLabs 나레이션 기능을 제공하며, 특히 백업 모델이 주력 모델의 역할을 대신하는 상황을 통해 제품 출시와 실제 작동 간의 차이를 배웠습니다.
핵심 포인트
- 오픈 웨이트 AI를 활용한 타로 리더 앱 'Pocket Oracle' 개발 및 배포 경험 공유.
- AI 해석과 나레이션 기능은 네트워크 연결이 필요하며, 오프라인 사용도 가능함.
- 독립 타로 리더를 위한 구조화된 초안 제공을 목표로 했으나, 아직 가설 단계임.
이 글은 Hacktoberfest Open-Source AI Challenge — Week 2: Side Hustle 제출물입니다.
애플리케이션은 작동했다. 리딩은 설득력이 있었다. 그리고 잘못된 모델이 대부분의 작업을 수행하고 있었다.
제가 Pocket Oracle이라는 오픈 웨이트 AI 기반 타로 리더를 만들면서 발견한 불편한 사실이었습니다. 앱은 정상적으로 보였습니다: 질문을 받고, 카드를 해석하며, 보기 좋게 구조화된 답변을 반환했습니다. 하지만 백업 모델이 제가 충분한 시간을 들이지 못한 주력 모델을 조용히 구제하고 있었습니다.
이 문제를 해결하는 과정에서 유용한 AI 제품을 출시하는 것과 첫 리딩을 작동시키는 것 사이의 차이에 대해 더 많이 배웠습니다. 또한 두 가지 교훈을 얻었습니다: 접근성은 오디오 추가 이상의 것이며, 지불할지 확신하지 못하는 잠재 고객도 '그럴 수도 있다'고 말하는 사람만큼 가치 있을 수 있다는 것입니다.
제가 만든 것
**Pocket Oracle**은 AI 지원 리딩 워크플로우를 갖춘 설치 가능한 이중 언어(bilingual) 라이더-웨이트 타로 그리모어입니다.
브라우저 경험에는 완전한 78장 카드 덱, 검색 가능한 카드 의미 및 상징, 개별 대응 관계, 그리고 한 장 또는 세 장의 과거/현재/미래 스프레드가 포함되어 있습니다. 카드를 뽑은 후, 방문자는 질문을 하고 오픈 웨이트 언어 모델로부터 구조화된 해석을 요청할 수 있습니다. 선택적으로 ElevenLabs 액션이 생성된 리딩을 나레이션합니다.
또한 영구적인 태양/달 외모 토글(Sun/Moon appearance toggle), 더 명확한 키보드 포커스와 탐색 기능, 개선된 대비, 그리고 선택된 카드 콘텐츠를 브라우저 내장 음성 엔진으로 전달하는 소리 내어 읽기(Read aloud) 버튼을 추가했습니다. 인터페이스와 카드 데이터베이스는 영어 및 브라질 포르투갈어로 이용 가능합니다.
덱, 탐색 도구, 그리고 카드 뽑기는 PWA에 캐시되면 오프라인으로 작동합니다. AI 해석과 ElevenLabs 나레이션은 네트워크 연결이 필요합니다. 계정 요구 사항이나 결제 과정은 없습니다.
부업 가설
저의 첫 타겟 고객은 카드는 이미 이해하고 있지만, 라이브 세션을 클라이언트(특히 초보자)에게 명확하고 잘 정리된 설명으로 풀어내는 데 시간을 쓰는 독립 타로 리더입니다.
구조화된 초안과 선택적 내레이션 읽기는 실무자가 매번 해석을 처음부터 만들 필요 없이 더욱 완성도 높은 경험을 제공하는 데 도움이 될 수 있습니다. 이는 궁극적으로 건당 비용, 브랜드 보고서 또는 소액 구독료를 정당화할 수도 있습니다.
이 모든 것은 여전히 가설입니다. Pocket Oracle은 결제 시스템이 없고, 유료 고객이 없으며, 검증된 가격 책정 지점이 없습니다. 이 버전의 목적은 비즈니스인 척하기 전에 작동하는 도구를 만들고 솔직한 피드백을 얻는 것이었습니다.
데모 (Demo)
jrpasolini.tech에서 Pocket Oracle 사용해 보기
대안적인 프런트엔드 배포: Render · GitHub Pages
전체 경험을 사용해 보려면:
- 앱을 열고 영어 또는 포르투갈어를 선택합니다.
- 78장의 카드 중 하나를 탐색하거나 원카드 또는 쓰리카드 스프레드를 뽑습니다.
- 질문을 입력하고 AI로 이 스프레드 해석하기를 선택합니다.
- 선택적으로 읽기 듣기를 선택합니다.
- Sun/Moon 테마와 카드 상세 페이지에 있는 별도의 소리 내어 읽기 (Read aloud) 버튼을 사용해 봅니다.
AI 읽기는 추론(inference) 및 폴백(fallback)에 따라 30~100초가 걸릴 수 있습니다. 선택적 유료 음성 서비스는 방문자가 요청할 때만 호출됩니다.
코드 (Code)
소스 코드 — jrpasolini/pocket-oracle
저는 2026년 10월 15일 주차 2번 과제 기간 동안 새로운 Pocket Oracle 저장소와 애플리케이션을 시작했습니다. 이 프로젝트는 제가 이전 Tarot Rider-Waite project에서 얻은 아이디어와 재사용된 타로 콘텐츠를 기반으로 합니다. 이번 주에 78장의 카드 설명이 모두 처음부터 작성된 것처럼 가장하기보다는, 해당 출처를 명시했습니다. 이 과제를 위해 새로운 AI 워크플로우, 양방향 애플리케이션 통합, 모델 캐스케이드(model cascade), 검증(validation), 보호된 서술(protected narration), 보안 강화(security hardening), 접근성 개선(accessibility improvements) 및 테스트가 개발되었습니다. 저장소에는 적용 가능한 라이선스와 제3자 고지(third-party notices)가 문서화되어 있습니다.
구축 방법 (How I Built It)
1. 인터페이스는 작게 유지하고, 신뢰 경계(trust boundary)를 서버에 두기
저는 프론트엔드 프레임워크를 도입하기보다는 의도적으로 순수 HTML, CSS, JavaScript 및 서비스 워커를 사용했습니다. api.jrpasolini.tech의 Cloudflare Worker가 AI 통합을 담당합니다.
Browser / installable PWA
served from jrpasolini.tech (.tech domain) via Render
GitHub Pages mirror available
...
공개 프론트엔드는 Render를 통해 jrpasolini.tech에서 제공되는 정적 사이트이며, GitHub Pages 미러가 있습니다. Worker만이 유일한 서버 측 구성 요소입니다. 브라우저는 권위 있는 카드 의미가 아닌 카드 **이름(names)**을 전송합니다. Worker는 이를 자체의 표준 78장 카드 데이터셋과 대조하여 해석합니다. 이렇게 함으로써 수정된 브라우저 페이로드가 모델 프롬프트에 사용되는 참조 설명을 조용히 대체하는 것을 방지할 수 있습니다.
해석 응답은 정의된 JSON 구조(제목, 도입부, 카드 해석, 답변, 가이드라인 및 3카드 스프레드를 위한 종합)를 따릅니다. 유효하지 않거나 일관성 없는 출력은 거부되며, 선택된 카드와 위치는 모델의 산문에서 맹목적으로 신뢰하는 것이 아니라 검증된 데이터에서 복원됩니다.
원래 질문은 애플리케이션 데이터베이스에 의도적으로 저장되지 않습니다. 하지만 이것이 로컬 또는 제로-리텐션(zero-retention) AI는 아닙니다: 질문 데이터는 Cloudflare Workers AI로 전송되며, 내레이션이 요청된 경우 승인된 리딩 텍스트는 ElevenLabs로 전송됩니다. 제공업체의 데이터 처리 정책은 여전히 적용됩니다.
2. 폴백 버그: 성공적인 응답 뒤에 숨겨진 실패한 가정
저는 Cloudflare Workers AI에서 주 모델로 Gemma 4 26B A4B (@cf/google/gemma-4-26b-a4b-it)를, 백업 모델로 Llama 3.3 70B (@cf/meta/llama-3.3-70b-instruct-fp8-fast)를 선택했습니다.
처음에는 각 시도에 너무 적은 시간을 할애했습니다. Gemma가 자주 이 짧은 시간 창을 초과했기 때문에 시스템이 Llama를 호출했습니다. 앱은 그럴듯한 해석을 반환하며 성공적으로 보였지만, 제가 테스트하던 모델은 간신히 끝낼 기회조차 얻지 못하고 있었습니다.
성공적인 HTTP 응답이 의도한 모델의 성공을 증명하는 것은 아닙니다.
저는 모델 메타데이터를 노출하고 검사한 다음 시간 예산을 재작업했습니다. 이제 Gemma는 시도당 최대 60초, 총 Worker 예산 100초 이내로 사용할 수 있으며, 프론트엔드는 요청 상한선 105초를 가집니다. 적격 실패는 여전히 Llama로 폴백할 수 있습니다.
동일한 입력 및 테스트 조건 하에 기록된 여섯 가지 세 카드 개발 실행 결과가 있습니다:
| Run | 리딩을 제공한 모델 | 종단 간 시간 |
|---|---|---|
| 1 | Gemma | 12.0 s |
| ... | ||
| Gemma는 이 작은 테스트에서 여섯 번의 요청 중 다섯 번을 완료했으며, 전체 여섯 건에 걸쳐 중앙값은 33.55초였습니다. 여섯 번째 리딩은 진정한 폴백이 왜 중요한지를 보여주었습니다: Gemma가 시도 창을 소진했고, Llama는 전체 예산 내에서 결과를 반환했습니다. |
이것은 일반적인 벤치마크가 아닌 운영 샘플입니다. 타임아웃은 또한 애플리케이션이 기다리는 시간에도 제한을 두며, 원격으로 이미 실행 중인 추론을 반드시 취소하는 것은 아닙니다. 이는 실제 클라우드 비용을 평가할 때 중요합니다.
3. 신뢰할 수 있는 AI 기능에는 관찰 가능한 실패가 필요하다
실패를 재시도(retry) 뒤에 숨기기보다는 명시적인 제품 동작으로 다루는 법을 배웠습니다.
Worker는 응답 유효성 검사 오류, 시도별 시간 초과, 일시적인 제공업체 장애, 그리고 소진된 계정 할당량을 구별합니다. 임시적인 용량 문제는 폴백(fallback)을 시도하는 것을 정당화할 수 있습니다. 반면에 계정 전체의 일일 할당량 실패(3036)는 동일하게 소진된 계정에서 다른 모델을 불필요하게 재시도하지 않고 HTTP 429를 반환합니다.
코드는 Gemma에 대해 프롬프트 수준 JSON 지침을 사용하고, Llama 폴백의 경우 지원되는 스키마 형식 옵션을 사용한 다음, 두 경우 모두 애플리케이션 측 유효성 검사를 수행합니다. 프런트엔드에서는 실제로 표시된 해석을 어떤 모델이 생성했는지 보고합니다.
개발 과정 중 별도의 AI 기반 보안 감사를 통해 두 개의 높은 심각도(high-severity) 및 여섯 개의 중간 심각도(medium-severity) 발견 사항을 식별했습니다. 저희는 해당 발견 사항들을 처리하고 회귀 커버리지(regression coverage)를 추가했습니다. 중요한 결정 두 가지는 ElevenLabs 자격 증명을 클라이언트 측에 보관하지 않은 것과, 사용자 편집 가능한 설명을 신뢰하기보다는 Worker에서 타로 메타데이터를 해결한 것이었습니다.
감사 및 테스트 통과는 위험을 줄여주지만, 라이브 사이트가 취약점으로부터 자유롭다는 것을 확립하지는 못합니다. 테스트 스위트, 배포 노트, 그리고 커밋 기록은 이러한 결정들을 검사 가능하게 만듭니다.
4. ElevenLabs: 유료 생성을 의도적인 동작으로 만들기
ElevenLabs는 eleven_v4 음성 모델을 사용하여 선택적 음성 해석 기능을 제공합니다. API 자격 증명은 서버에 남아 있습니다.
해석이 승인되면, Worker는 단기적인 HMAC 서명된 내레이션 권한을 발행할 수 있습니다. 브라우저는 방문자가 내레이션 동작을 클릭할 때 이를 제시합니다. 이는 어떤 리딩이 유료 음성 생성을 위해 전송될 수 있는지 제한하며, 호출자가 임의의 음성 텍스트를 자유롭게 제출하도록 허용하지는 않습니다.
권한은 10분 후에 만료되지만, 만료되기 전에 재사용할 수 있습니다. 따라서 서명만으로는 충분하지 않습니다. 속도 제한(rate limits), 계정 할당량(account quotas), 지출 모니터링(expenditure monitoring) 등도 여전히 중요합니다.
다국어 테스트 과정에서 하나의 미묘한 점이 발견되었습니다. 현재 저희 Worker는 생성된 단락의 언어를 자동으로 감지하는 것이 아니라, English/Portuguese 인터페이스 설정에서 파생된 language_code를 ElevenLabs에 전송하고 있습니다. 모델 자체의 다국어 기능만으로는 모든 교차 언어 조합에서 올바른 내레이션 발음을 보장하지 못합니다.
5. 접근성(Accessibility)이 ‘완료’의 정의를 바꾸다
처음에는 내레이션 리딩을 추가하는 것이 가장 큰 접근성 개선이라고 생각했습니다. 하지만 일상적인 시각 테스트를 통해 작동하는 API로는 절대 발견할 수 없는 문제점들을 노출하게 되었습니다.
다크 모드에서 일부 과거/현재/미래 라벨과 카드 캡션이 충분한 대비(contrast)를 갖추지 못했습니다. 언어 선택기는 라이트 테마 배경을 유지하고 있었습니다. 세 장의 카드를 보는 액션은 클릭할 수 있음에도 비활성화된 것처럼 보였습니다.
저는 이러한 불일치성을 수정하고, 활성 상태와 실제로 비활성화된 상태를 시각적으로 명확히 구분했으며, 관련 색상 대비에 대한 자동 회귀 검사(automated regression checks)를 추가했습니다.
결과적인 접근성 작업에는 다음이 포함됩니다:
- 더 일관된 대비 및 포커스 지표(focus indicators)를 갖춘 영구적인 Sun/Moon 테마.
- 콘텐츠로 건너뛰기(skip-to-content) 링크와 개선된 키보드 상호 작용(keyboard interaction).
- 선택적 유료 ElevenLabs 내레이션과는 별개로, 브라우저의 내장 음성 엔진을 사용하여 카드 콘텐츠를 읽어주는 음성으로 읽기(Read aloud) 버튼.
- 포르투갈어/영어 선택기를 포함한 현지화되고 읽기 쉬운 제어 장치들.
브라우저 음성 기능은 사용 가능한 운영 체제(operating-system)의 음성에 의존합니다. 따라서 그 품질과 오프라인 가용성은 다릅니다. 그리고 이러한 변경 사항들은 WCAG 인증이나 스크린 리더를 사용하는 사람이 모든 흐름을 독립적으로 완료할 수 있다는 증거는 아닙니다. 그것은 여전히 직접적인 보조 기술(assistive-technology) 테스트가 필요합니다.
저시력(low vision)을 가진 사람의 후속 실습 평가를 통해 그 한계가 부각되었습니다. 시각적 수정과 자동 대비 확인에도 불구하고, 그녀는 충분히 확대했음에도 여전히 일부 옅은 텍스트가 읽기 어렵다고 느꼈습니다. 이는 다른 컨트롤이 대비 테스트를 통과했다고 해서 무시할 수 없는 미해결 사용성(usability) 발견 사항입니다.
6. 실제로는 유용했던 명백한 언어 버그
또 다른 후기 단계 테스트에서는 영어 인터페이스가 포르투갈어로 작성된 AI 해석을 표시하는 것을 보여주었습니다. 저의 첫 번째 본능은 그것을 “수정”하는 것이었습니다. 하지만 방문자는 포르투갈어로 질문했습니다.
추가 수동 테스트를 통해 유용한 패턴이 밝혀졌습니다. 인터페이스가 다른 언어로 설정되어 있더라도, 영어와 포르투갈어 질문에 해당 질문의 언어로 답변을 받을 수 있었습니다. 스페인어 질문의 경우, 응답은 인터페이스 언어로 돌아왔습니다.
이 구분이 중요합니다. 인터페이스 로케일(interface locale), 질문 언어(question language), 그리고 모델 출력 언어(model-output language)는 항상 같은 것은 아닙니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기