사용자가 닫고 싶어 하는 AI 앱을 만들다
요약
사용자가 앱을 사용한 후 닫는 경험에 초점을 맞춘 AI 어드벤처 가이드 'OFFLINE'이 소개되었습니다. 이 앱은 로컬 Gemma 모델을 활용하여 야외 미션을 생성하며, 스트릭이나 알림 같은 중독성 요소를 의도적으로 배제했습니다. 사용자에게 필요한 것과 시간을 입력받아 감각적이고 짧은 단계의 미션을 제공한 후, 사용자가 앱을 닫도록 유도하는 것이 핵심 성공 조건입니다.
핵심 포인트
- 로컬 Gemma 모델을 활용하여 야외 활동 미션 생성
- 스트릭, 배지 등 중독성 요소를 의도적으로 제거함
- 사용자에게 '앱을 닫는 것'이 가장 좋은 상호작용 경험 제공
이 글은 Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass에 제출하는 내용입니다.
대부분의 AI 앱은 사용자가 계속 대화하도록 만들어집니다. 저는 그 반대의 성공 조건을 가진 앱을 만들고 싶었습니다. 즉, 가장 좋은 상호작용은 이 앱을 닫는 것입니다.
OFFLINE은 탐험 지도 스타일의 웹 애플리케이션입니다. 사용자에게 사용 가능한 시간과 필요한 것이 무엇인지 알려주면, 로컬 Gemma 모델이 작고 기묘하며 감각적인 단계들로 구성된 짧은 야외 미션을 작성해 주고, 앱은 방해가 되지 않게 사라집니다. 마지막 메시지는 항상 같습니다.
끝났습니다. OFFLINE을 닫으세요.
제가 만든 것
OFFLINE은 불필요해지도록 설계된 AI 어드벤처 가이드입니다.
이 흐름은 약 30초 분량의 화면 시간을 차지합니다:
- 체크인. 사용 가능한 시간(15, 30, 60 또는 90+분)과 필요한 것(머리를 맑게 하기, 탐험하기, 움직이기, 혼자 있기 또는 놀라게 해주기)을 선택하고, 선택적으로 대략적인 위치(도시, 공원, 마을)를 말합니다. GPS는 없으며 브라우저에서 아무것도 요청하지 않습니다.
- 미션 받기. Gemma가 전체 내용을 작성합니다:
무엇을 의도적으로 갖추지 않았는가: 스트릭(streaks), 배지(badges), 포인트(points), 계정(accounts), 기록(history), 알림(notifications) 또는 채팅창(chat box). 이 모든 것들은 사람들을 화면에 다시 붙잡아 둘 것이고, 이는 목적과 정반대다.
코드
GitHub logo NS007-dev / offline
OFFLINE
불필요해지도록 설계된 AI 어드벤처 가이드.
Noorin Sakhi가 제작했습니다.
**Hacktoberfest 2026, Challenge 2: Open-Source AI ("Touch Grass")**를 위해 구축되었습니다.
OFFLINE이란 무엇인가
사용자가 앱을 열고 자신이 가진 시간과 필요한 것을 말하면, **로컬 Gemma 모델(local Gemma model)**이 짧은 야외 미션을 작성해줍니다. 예를 들어, _"가장 흥미로운 소리가 나는 쪽으로 몸을 돌려라."_와 같은 감각적이고 최적화되지 않은 몇 가지 단계입니다. 그런 다음 앱은 휴대폰을 내려놓으라고 지시합니다. 돌아왔을 때만 다음 단계를 보여주며, 마지막 메시지는 "끝났다. OFFLINE을 닫아라." 입니다.
이것은 의도적으로 채팅봇(chatbot), 하이킹 플래너(hiking planner) 또는 지도 앱(map app)이 아닙니다.
왜 존재하는가
대부분의 AI 제품들은 사용자가 계속 대화하도록 구축됩니다. OFFLINE의 성공 조건은 그 반대입니다: OFFLINE과 가장 좋은 상호작용은 OFFLINE을 닫는 것입니다. 피드, 스트릭, 배지 또는 계정은 없습니다. 화면은…
약 5분 안에 실행할 수 있습니다 (Python 3.10 이상, Node 20.19 이상 또는 22.12 이상, 그리고 Ollama가 필요합니다):
# 1. Gemma 모델 (어떤 Gemma 태그든 작동함)
ollama pull gemma3:1b # 작고 빠름; 또는 기기가 감당할 수 있다면 더 큰 모델을 사용하세요
...
API 키도, 계정도, 클라우드 연결도 없습니다. UI를 통해 Gemma를 테스트하고 싶다면 python3 -m app.cli --time 30 --need surprise 명령어를 터미널에서 실행하면 미션이 생성됩니다.
어떻게 만들었는가
스택(The stack)
React + TypeScript + Vite -> FastAPI -> Ollama -> Gemma (open weights)
|
validate -> safety screen -> retry once -> hand-written fallback
Gemma가 모든 미션을 작성합니다. 이 앱은 메인 경로에 위한 미션 템플릿을 포함하고 있지 않습니다. 대신, 사용자가 Gemma에게 짧은 근황 보고를 보내고 Ollama의 구조화된 출력(structured outputs)을 사용하여 특정 스키마와 일치하는 JSON을 요청합니다. 손으로 작성한 미션은 오직 대체 수단으로만 존재하며, 앱이 사용자에게 현재 어떤 종류의 미션을 접하고 있는지 알려줍니다.
Gemma를 단순히 정확한 것을 넘어 흥미롭게 만들기
제가 처음 만든 미션들은 안전하고 지루했습니다. "심호흡을 하고 주변 환경에 주의를 기울여 보세요" 같은 것은 퀘스트가 아닙니다. 세 가지 변경 사항이 대부분의 문제를 해결했습니다:
- 목소리(Voice). 시스템 프롬프트(system prompt)를 통해 Gemma에게 조용하고 약간 신비로운 게임 마스터 역할을 부여하고, 진부한 표현("현재에 머무르기", "자연과 연결되기", "순간을 받아들이기" 등)은 아예 금지했습니다.
- 단계별 반전(Twist per step). 모든 단계는 하나의 구체적인 것을 요청하고, 게임처럼 느껴지게 하는 작은 규칙을 포함해야 합니다. 단순히 "산책하기"가 아니라, _"가장 조용한 방향으로 열 걸음 걷기"_와 같은 방식입니다.
- 랜덤 렌즈(Random lens). 각 요청에는 12가지 렌즈(소리, 색상, 나이, 모서리, 빛과 그림자, 작은 것들, 질감, 방향, 고요함, 사람의 흔적, 날씨와 공기, 위를 바라보기) 중 하나가 포함되며, Gemma는 이 렌즈를 미션 전반에 걸쳐 녹여내어 두 요청이 같을 확률을 낮춥니다.
LENSES = [
"소리: 당신이 듣는 것, 어디서 오는지, 귀에 간신히 들리는 소리",
"나이: 당신보다 훨씬 오래전에 존재했던 것들, 그리고 오늘을 초월할 것들",
...
감사(Audit)할 수 없는 모델은 절대 신뢰하지 마세요
작은 로컬 모델이라도 때때로 지침을 무시하는 경우가 있습니다. 이 앱은 사용자들에게 야외를 걸어 다니라고 안내하기 때문에, 저는 Gemma가 반환하는 모든 것을 신뢰할 수 없는 입력으로 취급합니다. 안전성은 세 가지 계층으로 구성되어 있습니다:
| 레이어 | 역할 |
|---|---|
| 프롬프트 | Gemma에게 규칙을 알려줍니다: 일반적인 공공장소만 허용; 무단 침입, 등반, 위험한 도로 횡단, 낯선 사람 접근, 표지판 무시, 고의로 길 잃기, 특수 장비 사용, 구매 행위 또는 야간 활동은 금지됩니다. |
| ... | |
| 임무가 실패하면 백엔드에서 한 번 재시도하고 Gemma에게 무엇이 잘못되었는지 정확히 알려줍니다("3단계에 지침이 없습니다; 소요 시간이 41분으로, 이용 가능한 시간 30분을 초과합니다"). 만약 다시 실패하거나 Ollama가 작동하지 않거나 느리거나 모델을 찾지 못하면, 동일한 검증기(validator)를 통과하는 손글씨 임무와 함께 그 이유를 설명하는 정직한 공지가 제공됩니다. 이 앱은 Gemma가 쓰지 않은 내용을 쓴 것처럼 절대 가장하지 않습니다. |
테스트에서 발견된 문제점들
저는 30개의 백엔드 테스트와 15개의 브라우저 테스트를 작성했습니다. 백엔드 테스트는 제가 명령을 내려 오작동하게 만들 수 있는 가상의 Ollama 서버를 대상으로 실행됩니다: 잘못된 JSON, 안전하지 않은 임무, 느린 응답, 누락된 모델 또는 아예 서버가 없는 경우 등입니다. 이 과정에서 배운 점은 다음과 같습니다:
- 제가 만든 안전 필터가 자체 폴백(fallback)을 차단했습니다. '휴대폰을 주머니에 넣으세요'라는 문구를 휴대폰 사용으로 간주했기 때문입니다. 저는 규칙을 좁히고 회귀 테스트를 추가했습니다.
- 스크린 리더 사용자에게는 2단계부터 제목이 없었습니다. 접근성 검사로 이를 발견했고, 수정되었습니다.
- '팝인(pop-in)' 애니메이션이 애니메이션 도중에 버튼 크기를 41px로 축소하여 제 44px 터치 대상 영역(tap-target) 검사에 실패했습니다. 저는 스케일링을 제거했습니다.
제가 측정하지 않은 부분에 대한 솔직한 언급: 자동화 테스트는 대체 모델(stand-in model)을 사용하므로, Gemma의 임무가 얼마나 좋은지를 증명하는 것이 아니라 파이프라인 자체를 증명합니다. 이 부분은 제가 직접 실제 임무를 실행해 보면서 판단했습니다(위 필드 테스트 참고).
디자인 과정
저는 인터페이스를 세 번 재설계했으며, 이곳에서 가장 많이 배웠습니다:
- 차분한 숲 녹색 테마. 괜찮아 보였고, 손가락으로 터치하며 넘기는 단어 슬라이드쇼 같은 다른 모든 앱처럼 느껴졌다.
- 밝고 독특한 스티커 스타일. 너무 유아적이었고, 여전히 앱처럼 보였다.
- 탐험 지도. 양피지 질감에 등고선이 있고, 바늘이 흔들리며 안착하는 나침반, 빨간 X 표시가 된 점선 경로, 그리고 야간 차트의 '폰 다운' 화면. 목표는 장치 위에 있다는 느낌 자체가 들지 않는 페이지를 만드는 것이었다.
설계 단계부터 고려한 접근성 및 개인 정보 보호
선택지는 실제 라디오 버튼(화살표 키 작동)이며, 모든 화면에서 제목이 포커스를 가져가고, 터치 영역은 최소 44px이다. 또한 prefers-reduced-motion 설정 하에서는 모든 움직임이 비활성화된다. '폰 다운' 타이머는 시계와 비교하므로, 화면이 잠겨도 정확하게 유지된다. 쿠키나 localStorage, 제3자 요청이 없으며, 테스트를 통해 브라우저가 항상 localhost와만 통신한다는 것을 확인했다.
AI 페어 프로그래머와 함께 구축하다
투명성을 기하기 위해: 나는 작성된 빌드 사양(build spec)을 사용하여 Claude를 코딩 파트너로 삼아 긴 질의응답 과정을 거쳐 OFFLINE을 만들었다. 제품 호출은 내가 했고, 모든 것은 내 자체 머신에서 실행했으며, 실제 오류들(실패한 ollama pull, 모델 이름 불일치 등)에 부딪히고, 디자인이 적절하지 않을 때는 방향을 수정했다. 런타임에는 폐쇄된 의존성이 전혀 없다. 앱 안에 있는 유일한 AI는 로컬에서 실행되는 오픈 웨이트 Gemma이다.
열린 혁신은 왜 중요한가?
나는 기술과 함께 보내는 시간을 줄이려고 노력하는 사람들을 위해 OFFLINE을 만들었다. 클라우드 API는 그것을 조용히 훼손할 것이고, 오픈 모델은 네 가지 방식으로 이를 해결한다:
- 프라이버시는 약속이 아니라 사실입니다. 미션은 사용자 기기에 작성됩니다. 신뢰할 서버도 없고, 유출될 키도 없으며, 언제 왜 밖에 나가고 싶었는지에 대한 기록(log)도 없습니다. Gemma가 볼 수 있는 유일한 맥락은 "30분 동안 머리를 식히는 공원"뿐입니다.
- 떠난 후에는 신호가 필요하지 않습니다. 미션은 떠나기 전에 생성됩니다. 그 이후부터 모든 단계와 타이머는 페이지 내에 존재하며, 앱은 더 이상 네트워크 요청을 하지 않습니다. 와이파이 범위를 벗어나도 주머니 속에서 미션을 가지고 걸어 나갈 수 있습니다.
- 단어 하나를 바꾸면 모델을 교체할 수 있습니다. 모델은 단일 환경 변수(
OFFLINE_MODEL)입니다. 코드를 건드리지 않고, 작고 빠른 Gemma와 더 크고 창의적인 모델로 같은 노트북에서 전환하며 미션의 차이를 즉시 확인할 수 있습니다. 폐쇄된 API는 그 선택을 저에게 맡기지 않았을 것입니다. - 실행 비용이 들지 않습니다. 미션당 요금도, 사용량 제한(rate limit)도, 청구 페이지도 없습니다. 30초 동안 열어보도록 의도된 앱에 계량기가 달려 있을 필요는 없습니다.
더 조용한 이유도 있습니다. 모델의 출력을 **신뢰할 수 없는 입력(untrusted input)**으로 취급하는 앱은 모델, 프롬프트, 샘플링(sampling), 스키마(schema)를 직접 제어할 때 구축하기가 훨씬 쉽습니다. 온도를 조정하고, 출력 형태를 제한하며, 실패 사례를 로컬에서 재현할 수 있습니다. 사람들을 바깥세상으로 내보내는 프로젝트의 경우, 그 통제력이 안전 기능이 됩니다.
경품 부문 (Prize Categories)
- Gemma 활용 최우수: Gemma는 Ollama를 통해 로컬에서 실행되며, 제목, 전제, 단계, 성찰(reflection), 마무리 문구까지 모든 미션을 끝에서부터 끝까지 작성합니다. JSON 스키마로 제약되고, 음성 및 순환하는 창의적 렌즈에 의해 안내되며, 검증 및 안전 점검을 거치고, 하나의 설정으로 Gemma 크기별 모델 간 전환이 가능합니다.
향후 계획 (What's Next)
OFFLINE은 의도적으로 작게 유지되었으며, 저는 계속 그렇게 하고 싶습니다. 만약 제가 계속 구축한다면 다음과 같을 것입니다:
• 부드러운 맥락(Gentle context), 휘발성 유지: 임무가 시간에 맞게 조정되는 선택적 일광 및 날씨 정보로, 저장되지 않습니다.
• '찾아냈다' 유형의 단계: 무언가를 '포착(spot)'하도록 요청하고, 반사된 이미지에서 그것을 읽어내는 임무입니다.
• 타이머 종료 시 소리: 휴대폰을 주머니에 넣는 사람들을 위한 부드러운 벨소리.
• 실제 현장 테스트: 노트북이 아닌 셀룰러 환경에서의 실제 휴대폰 필드 테스트.
알려진 한계점: 안전 화면은 키워드 필터일 뿐, 보장이 아닙니다. OFFLINE은 주변 환경을 알지 못하므로, 어떤 장소가 존재하는지 확인할 수 없습니다. 소형 모델(Small models)은 실행할 때마다 다르고, 화면이 잠겨 있을 때는 진동을 줄 수 없습니다.
Noorin Sakhi가 제작했습니다. 사용해 보시고, 당신의 임무가 무엇이었는지, 그리고 어떤 점들을 발견했는지 알려주시면 감사하겠습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기