이 글은 Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass에 제출하는 내용입니다.
제가 만든 것 (What I Built)
저는 Mumbai 근처 Vasai에 살고 있습니다. 제 책상에서 20분 이내에는 공원, 호수변, 그리고 세 개의 해변이 있습니다. 지난달 저는 그 어느 곳에도 가지 않았습니다. 가기 싫어서가 아니라, 인도 도시의 10월에는 시간(timing)이 모든 것이기 때문입니다. 오후는 38°C처럼 느껴지고, 공기질은 시간마다 변하며, 해안가는 만조 때와 어두워지기 직전에만 좋을 뿐입니다. 아무도 다섯 개의 앱을 확인해서 좋은 40분을 찾아내지 못합니다.
Sitspot은 당신이 선택한 몇몇 실제 장소를 감시하고, 그중 한 곳이 진정으로 좋은 기회를 제공할 때만 밖에 나가라고 알려줍니다.
- 당신은 3~5개의 장소(공원, 해변, 테라스 등)를 한 번 추가하기만 하면 됩니다. 이것이 당신이 화면에서 보내는 대부분의 시간입니다.
- 매시간 Sitspot은 각 장소에 대한 날씨, 공기질, 조수 간만의 차(tide timing), 태양 위치, 그리고 최근 eBird 관찰 기록을 가져오고, TabPFN으로 새 관찰이 얼마나 좋을지 예측합니다.
- 단순하고 검증된 의사 결정 함수 (decision function) (모델이 아닌 코드)가 각 장소 × 시간마다 점수를 매기고, 다섯 가지 엄격한 안전 규칙이 모든 것을 거부할 수 있습니다: 일몰 후 30분 동안은 해안가 금지, 만조 근처는 금지, 체감 온도가 ≥ 38°C이거나 US AQI가 ≥ 200인 곳은 불가, 조용한 시간대는 절대적입니다.
- 기회가 기준을 통과하면, Gemma가 짧고 구체적인 초대 메시지를 작성합니다(
데모 (Demo)
영상에 대한 솔직한 후기. 바사이(Vasai)의 공기는 제가 빌드하는 내내 위험했습니다. 저녁 대부분 US AQI가 200–299였고, 체감 온도는 35–39°C였습니다. Sitspot은 마땅히 그래야 할 일을 정확하게 수행하여 어디로도 초대하지 않았습니다. 심지어 "테스트 초대장 보내기(send a TEST invitation)" 버튼조차 안전 규칙을 통과하는 창문이 없으면 작동하지 않습니다. 따라서 녹화된 영상은 깨끗한 공기의 테스트 장소에서 촬영되었으며, 실제 파이프라인(Gemma 스크립트, 푸시, 음성, 방문, 현장 노트) 중 점수 임계값만 건너뛰고 안전 기능은 절대 사용하지 않은 테스트 초대장을 사용했습니다. 홈 화면에서는 제가 실제로 바사이에서 발견한 장소들이 _AQI 229 · 매우 나쁨(very unhealthy)_으로 표시되는 것을 볼 수 있습니다. 포켓 모드에서는 집 까치(House Crow) (여기에는 사방에 있습니다)와 오후 2시 50분에 실내에서 **올빼미(Barn Owl) — "가능성 있음"**을 감지했는데, 이는 거의 확실하게 오탐지입니다. 이 "가능성 있음"이 핵심입니다. 이 앱은 얼마나 확신하는지를 말하도록 설계되었으며 결코 새를 지어내지 않습니다.
예보에 따르면 이번 주말에는 공기가 개선될 것이며, 첫 번째 실제 새벽 초대는 제가 자주 가는 공원용으로 대기 중입니다.
코드 (Code)
당신의 장소들이 당신을 부릅니다. 주변의 몇몇 실제 장소를 오픈 데이터로 감시하며, 그중 진정으로 좋은 창문(window)이 열릴 때 알려줍니다. 오픈 모델: Gemma, BirdNET (온디바이스), TabPFN. Hacktoberfest 2026 Touch Grass.
Sitspot
당신의 장소들이 당신을 부릅니다.
실제로 방문할 수 있는 3~5곳의 장소(개울가, 요새, 해변, 테라스 등)를 추가합니다. Sitspot은 개방형 데이터(날씨, 공기질, 조수 간만, 태양광, 최근 eBird 목격 기록)를 사용하여 매시간 이 장소들을 모니터링합니다. 그중 정말 좋은 기회가 생기면 휴대폰이 진동하고, 이를 탭하면 브라우저에서 Sitspot과의 음성 대화가 시작됩니다(ElevenLabs 음성, Gemma 기반). 이 통화는 장소, 실제 수치를 포함한 이유, 그리고 떠나야 할 시간을 알려줍니다. 만약 방문하게 되면 "여기에 있습니다"를 누르고 휴대폰을 주머니에 넣은 뒤 걸어 나갑니다. 저녁에는 실제로 일어난 일을 바탕으로 짧은 현장 기록(field note)을 작성하며, 사용자가 수락하거나 거부한 내용을 통해 학습합니다.
실시간 데모: https://sitspot.vercel.app · 작성 글: docs/devto-post.md
전화 통화: ElevenLabs + Twilio 경로는 구현되었습니다(apps/api/src/delivery/call.ts), 하지만 새로운 Twilio 체험판(trial) 계정은 스트리밍되는 음성 통화를 AI 에이전트에게 차단합니다…
GitHub에서 보기
pnpm 모노레포: Next.js 16 PWA(apps/web), Hono API(apps/api), Temporal 워크플로우(workflows/), 그리고 정책, 에이전트, LLM 클라이언트, 데이터 수집기 및 DB를 위한 작은 패키지들로 구성되어 있습니다. TypeScript와 Python을 아우르는 약 300개의 테스트가 있으며, 푸시(push)할 때마다 CI에서 타입 체크와 테스트가 실행됩니다.
구축 방법
최적화된 단일 기능을 수행하는 개방형 모델:
Context-first, not tool-calling. 서버는 모든 사실(날씨, 조수 시간, 목격 기록, 사용자의 선호도)을 작은 컨텍스트 블록으로 수집하고; 모델은 오직 작성만 합니다. 그런 다음 검증기들이 모든 출력을 확인합니다: 모든 숫자는 사실에 나타나야 하고, 감지되거나 보고되지 않은 새는 이름이 언급될 수 없으며, 해안 스크립트는 '단단한 지대에 머물라'고 말해야 하고, 밤에는 절대로 해안을 장려해서는 안 되며, 확신어(confidence words)는 감지 점수와 일치해야 합니다. 실패하면 문제점을 피드백하여 한 번 재시도하고, 그 다음 결정론적 템플릿이 적용됩니다. 제가 진행한 40턴의 적대적 평가(Gemma 3 1B)에서, 원본 모델은 5%의 턴에서 존재하지 않는 새를 이름 지었습니다: 사용자에게 도달한 것은 0%였습니다. 사용자에게 전달된 안전하지 않은 조언 비율은 제가 에이전트 자체 내부에 해안-야간 규칙을 강제한 후 12.5% → 2.5%로 감소했습니다. 이 평가는 실제 사람이 발견하기 전에 시스템이 포착해낸 것입니다.
Temporal는 지속 가능하게 만듭니다. 하루에 한 가지 워크플로우가 시간별로 평가되며; 각 초대(invitation)는 자체적인 워크플로우입니다 (수면 → 전송 시간까지 대기 → 조건 재확인 → 스크립트 작성 → 전달 → 답변 대기 → 도착 대기 → 방문 종료 대기 → 메모 작성). 저는 모든 대기 지점에서 작업자(worker)를 20번 죽여봤습니다: 20/20이 재개되었고, 중복 알림은 0건이었습니다.
TabPFN에 대해 솔직히 말하자면. 저는 19,588개의 실제 eBird 체크리스트(2022–2026년, Palghar/Thane/Mumbai)와 Open-Meteo 기록을 결합하여 훈련했습니다. 보류된(held-out) 2026년 시즌에서 TabPFN은 ROC-AUC (0.649 대 0.650)에서 단순한 핫스팟 × 시간 기준선과 동률을 이루었습니다 — 제가 목표했던 +0.05에는 미치지 못했습니다. 하지만 적은 시간만 플래그를 지정하면서도 **초대 임계값(invite threshold)에서 최고의 정밀도(0.627 대 0.549)**를 보였습니다. 이는 사용자에게 가끔 방해해서는 안 되는 앱에 정확히 필요한 부분입니다. 주요 제한 사항은 eBird의 속도 제한 때문에 제가 일일 피드에 의존해야 했고, 따라서 행의 96%가 체크리스트 지속 시간을 포함하지 않으며 레이블이 노이즈가 많습니다. 결과 파일: evaluation/tabpfn_results.md.
기억(Memory). "평일에는 개울이 너무 멀다"라고 말하면 이것이 하나의 규칙이 됩니다. 평일의 개울 방문 시간대는 보류되며, 그 이유는 사용자가 직접 말한 문구를 인용합니다. 모든 선호도는 사용자의 실제 문장을 저장해야 합니다. 밤새도록 시스템은 사용자의 임계값을 조정하며 어떤 시간에 '예'라고 답하는지 학습합니다.
무료 호스팅(Hosting for ₹0). 웹 앱은 Vercel에 배포되어 있으며, API, Temporal worker 및 Ollama는 무료 ngrok 도메인을 사용하는 제 노트북에서 실행됩니다. Vercel이 /api를 프록시 처리하여 하나의 오리진으로 보이게 합니다. Postgres는 PGlite(임베디드, pgvector 포함)를 사용하며, DATABASE_URL을 통해 다른 Postgres로 교체할 수 있습니다.
작동하지 않았던 것들 (그리고 제가 대신 한 일). 저는 실제 전화 통화를 원했습니다. ElevenLabs와 Twilio를 연결하여 Twilio까지는 테스트했지만, 새로운 Twilio 체험 계정은 AI 에이전트에게 스트리밍 통화 오디오를 차단하기 때문에, "통화"는 브라우저에서 이루어집니다. 즉, 푸시 알림이 호출 페이지를 열고 Sitspot이 ElevenLabs를 통해 말하는 방식입니다. Twilio 직접 코드는 레포지토리에 있으며 유료 계정에서는 작동합니다.
오픈 혁신(Open Innovation)은 왜 중요한가?
-
검증 가능한 개인 정보 보호. Bird ID는 휴대폰에서 실행됩니다. 사용자의 마이크 오디오는 절대 주머니를 벗어나지 않으며, "집까마귀, 0.71, 07:31"만 전송됩니다. 폐쇄된 오디오 API를 사용하면 산책하는 모든 과정을 누군가의 서버로 스트리밍해야 합니다.
-
신호가 약한 곳에서도 작동합니다. 맹그로브나 해변은 커버리지가 좋지 않습니다. 모델이 브라우저에서 실행되며, 감지된 데이터는 IndexedDB에 대기열(queue)로 저장되었다가 나중에 동기화됩니다.
-
교체 가능한 두뇌(Swappable brains). 모든 LLM 호출은 하나의 OpenAI와 호환되는 클라이언트를 거칩니다. 저는 로컬의 Gemma 3 4B 스크립트를 AI Studio에서 Gemma 4 31B로 옮겼고, 지연 시간(latency)을 위해 음성 기능은 로컬 Gemma 3 1B를 유지했습니다. 환경 변수를 변경함으로써 가능했습니다. Gemma 4가 답변하기 전에 "생각하는" 과정이 나타나서 (그리고 이것은 끌 수 없는 문제였기 때문에), 저는 한 곳에서 이 기능을 제거했습니다.
-
개방된 데이터 입력, 정직한 숫자 출력. 수천 명의 조류 관찰자가 기록한 eBird 체크리스트, Open-Meteo 예보 및 기록, OpenStreetMap 타일, 그리고 제가 실제로 평가할 수 있는 개방형 표 형식 모델을 포함하여 기준선(baseline)과 연결되었다는 점까지 출판했습니다.
-
운영 비용은 들지 않습니다. 무료 티어, 노트북, 그리고 오픈 웨이트만 있으면 됩니다. 매시간 다섯 곳을 확인하는 항상 작동하는 에이전트는 폐쇄형 API에서는 상당한 월별 청구서가 될 것입니다.
나의 에이전트 세션 (My Agent Session)
코딩 에이전트로 Claude Code를 사용하여 구축했습니다: 이 에이전트는 PRD(제품 요구사항 정의서)를 작성하고, 이를 20개의 작업으로 분할했으며, 패키지별로 서브 에이전트를 병렬 실행했고, 모든 작업은 커밋 전에 테스트를 통과해야 했습니다.
수상 부문 (Prize Categories)
- Gemma 활용 최우수: Gemma 4 31B가 모든 초대장과 메모를 작성하고; Gemma 3 1B는 음성 에이전트의 두뇌로 로컬에서 실행됩니다. 둘 다 검증기(validators) 뒤에 있습니다.
- TabPFN 활용 최우수: 19.6k개의 실제 eBird 체크리스트로 학습된 시간별 '좋은 조류 관찰' 예측치이며, 위 명시된 정직한 결과와 함께 세 가지 기준선과 비교되었습니다.
- ElevenLabs 활용 최우수: Sitspot의 음성: ElevenLabs 에이전트로, 이 에이전트의 LLM(대규모 언어 모델)은 제가 가진 자체 오픈 모델 엔드포인트(Gemma)입니다. 따라서 음성은 동일한 사실과 안전 규칙에 기반합니다.
- Temporal 활용 최우수: 시그널(signals), 재확인 및 재시도를 포함하는 내구성 있는 초대장 워크플로우; 강제 작업자 종료 후에도 20/20으로 재개되며, 중복이 없습니다.
출처: TabPFN은 Prior Labs가 제작했습니다; BirdNET (Kahl et al., 2021, CC BY-NC-SA 4.0); eBird / Cornell Lab of Ornithology; Open-Meteo (CC BY 4.0); © OpenStreetMap 기여자.