dev.to

RevSofia, a Car Meetup app that people will love to use

RevSofia, 자동차 모임 채팅을 일정으로 바꾸는 앱

RevSofia는 자동차 모임 채팅을 Gemma 4로 분석해 일정 초안을 만드는 소피아 지역 모임 서비스입니다. 시간대 처리, 입력 검증, 비용 악용 방지와 사람의 승인 절차를 구현하며 실제 테스트에서 발견한 오류와 수정 과정도 공개합니다.

에디터 노트

이 프로젝트에서 설득력 있는 부분은 모델의 추출 결과를 곧바로 일정으로 공개하지 않는 설계입니다. 상대 날짜가 과거로 계산된 사례를 미해결 항목으로 돌리고, 차종을 지어낸 사례도 사람의 검토 단계에서 걸러내도록 했습니다. 시간대 테스트와 ICS 검증은 모임 앱에서 실제로 참석자가 잘못된 시간에 도착하는 문제를 겨냥합니다. 다만 성능을 보여주는 채팅은 짧은 예시이며, 200개 메시지와 속어가 섞인 실제 대화는 아직 검증 대상으로 남아 있습니다. 댓글도 서머타임과 검토 절차를 긍정적으로 보면서 모델의 차종 환각 사례를 뒷받침합니다. 현재 게시판의 모임 네 건 역시 예시 데이터이므로, 공개된 구현의 완성도와 실제 커뮤니티에서의 추출 정확도는 구분해서 볼 필요가 있습니다.

AI 요약

소피아의 자동차 모임은 대개 그룹 채팅에서 정하지만, 메시지가 쌓이면 날짜와 장소, 참석자를 다시 찾아야 합니다. RevSofia는 이 채팅을 일정 초안으로 바꾸는 모임 게시판입니다. 누구나 참여하는 일반 모임과 BMW, Mercedes-Benz, Audi/VW Group 동호회 모임을 지원합니다. 일정에는 휴대전화 캘린더에 추가하는 기능이 있고, 모임별 페이지에서 월간 달력과 예정·지난 모임을 확인합니다. 채팅 기능은 자체 제공하지 않고 Discord를 연결합니다. 승인된 모임은 Discord에 자동 게시되며, 읽는 사람의 시간대에 맞춰 시간을 표시합니다.

채팅에서 모임 초안으로

사용자가 영어 또는 불가리아어 채팅을 붙여 넣으면 오픈 웨이트 모델 Gemma 4, 구체적으로 gemma-4-31B-it가 날짜와 시간, 만남 장소와 목적지, 참석자와 차량, 소속 동호회, 대화에서 정하지 못한 항목을 추출합니다. 날짜가 "일요일"처럼 상대적으로 적히면 소피아 현지 날짜를 기준으로 해석합니다. 모델은 Discord에 붙여 넣을 공지문도 작성합니다. 사용자는 초안을 수정한 뒤 검토를 요청합니다. 요청은 GitHub 이슈로 등록되며 사람이 승인하기 전에는 공개되지 않습니다.

게시판의 모임 데이터는 저장소 안 JSON 파일로 관리합니다. Node 빌드 스크립트가 데이터를 검증해 웹사이트 데이터와 모임별 ICS 캘린더 파일을 생성합니다. 모임 승인은 파일을 병합하는 작업입니다. 정적 사이트는 GitHub Pages에서 제공하고, DigitalOcean Function이 모델 호출을 담당합니다. 브라우저에서 모델 키를 다루지 않도록 키를 함수에 보관합니다. 함수는 입력 채팅 길이를 8,000자로 제한하고 모델이 반환한 JSON을 빌드 단계와 같은 검증 모듈로 확인합니다. 잘못된 동호회 이름이 들어오면 일반 모임으로 처리해 사이트 데이터가 깨지지 않게 합니다.

실서비스에서 드러난 오류와 방어

개발자는 실제 모델 호출에서 발견한 문제와 수정 내용을 공개합니다. 저녁에 실행한 테스트에서 "토요일 아침"을 이미 지난 토요일로 해석했고, 생성한 공지문에는 오늘 날짜 옆에 "내일"이라고 적기도 했습니다. 함수에 현재 소피아 시각을 전달하고, 시작 시간이 과거라면 미해결 항목으로 표시하도록 바꿨습니다.

영어 채팅에도 불가리아어 공지문이 나오는 문제가 있었습니다. 모델이 언어를 고르게 하지 않고 함수가 입력 언어를 판별해 지시하도록 수정했습니다. 처음에는 키릴 문자와 라틴 문자 수를 비교했지만, 사용자 이름과 지도 링크, 차종이 라틴 문자로 적힌 불가리아어 채팅을 제대로 분류하지 못했습니다. 사용자 이름과 링크를 먼저 제거하고, 남은 텍스트에서 키릴 문자가 약 3분의 1 이상이면 불가리아어로 판단합니다.

배포 환경에서는 CORS 설정도 실패했습니다. DigitalOcean 게이트웨이가 와일드카드 출처를 추가하는데 함수가 사이트 출처를 다시 더해, 허용 출처가 두 값으로 응답됐습니다. project.yml에 web-custom-options: true를 설정해 함수가 CORS를 맡도록 했고, 허용 출처도 사이트로 제한했습니다. 또 CORS만으로는 curl 같은 비브라우저 요청을 막지 못해 반복 호출로 모델 비용이 발생할 수 있습니다. Cloudflare Turnstile의 일회용 토큰을 방문자 IP와 호스트 이름에 맞춰 검증한 뒤에만 모델을 호출합니다. 위조 토큰은 약 0.3초 만에 403 응답을 받고 모델 토큰을 쓰지 않습니다.

시간대, 테스트와 운영 방식

불가리아는 10월 25일 서머타임에서 겨울 시간으로 전환합니다. 글은 이 날짜의 오전 10시 모임이 UTC+2이며 ICS에는 08:00Z로 기록돼야 한다고 설명합니다. 이 시간대 사례를 테스트에 넣었습니다. 전체 테스트는 node:test로 작성한 57개이며 별도 테스트 프레임워크는 사용하지 않았습니다. 모임 파일이 main 브랜치에 추가되면 GitHub Actions가 Discord 웹훅으로 공지를 올립니다. 웹훅은 Actions secrets에 보관하며, 사용자 입력에 @everyone이 포함돼도 멘션이 작동하지 않도록 설정했습니다. 예시 모임과 이미 지난 모임은 게시하지 않습니다.

개발자는 예시 채팅에서 모델이 실제로 반환한 결과도 제시합니다. 불가리아어 대화에서 일요일 오전 9시 30분, 시메오놉스코 쇼세 주유소, 즐라트니테 모스토베 목적지와 참석자 세 명을 추출했고, 참석할 수 없다고 말한 사람은 명단에서 제외했습니다. 브랜드가 섞여 있어 일반 모임으로 분류하고 종료 시각은 미정으로 남겼습니다. 이 호출은 약 5.4초 걸렸다고 합니다. 단, 현재 게시판의 모임 네 건은 실제 일정이 아니라 예시 데이터입니다. 개발자는 실제 Discord에서 수집한 200개 메시지 규모의 대화와 속어, 오타를 다음 검증 대상으로 꼽습니다.

비용은 DigitalOcean Serverless Inference에서 입력 토큰 100만 개당 약 0.18달러라고 제시합니다. 모델은 OpenAI 호환 엔드포인트로 호출하며, 다른 오픈 모델로 바꿀 때는 설정 한 줄을 바꾸는 구조입니다. 프로젝트는 프레임워크, 데이터베이스, npm 의존성 없이 구성했다고 설명합니다. 개인정보가 담긴 동호회 채팅을 어느 모델 제공자에게 보낼지 운영자가 선택할 수 있다는 점도 오픈 웨이트 모델을 택한 이유로 들었습니다.

dev.to 반응

  • @humam_moin — 시간대 처리와 사람의 검토 절차가 흥미롭습니다. AI가 세부 정보를 추출해도 작은 부분을 정확히 처리해야 도구가 실제로 쓸모 있어집니다.
    • @alexgeorgiev17 — 감사합니다. 모델 자체는 솔직히 쉬운 부분이고 지루한 세부 사항에서 문제가 생깁니다. 서머타임 전환 때문에 모임 시간이 한 시간 어긋나면 사람들이 텅 빈 주차장에서 기다리게 됩니다. 검토 단계도 유지합니다. 차를 말하지 않은 사람에게 모델이 "E-series" 차량을 지어낸 적이 있습니다.

원문: dev.to / 번역·요약: Trawling