장소나 항공편을 환각(Hallucination)하지 않는 AI 여행 플래너를 만든 방법
요약
AI 여행 플래너 Rotavi를 구축하며 LLM의 환각 문제를 해결하기 위해 추론과 사실을 분리하는 아키텍처를 설계했습니다. LLM은 여행의 구조(Skeleton)만 생성하고, 실제 장소와 항공편 정보는 권위 있는 API를 통해 채우는 방식을 사용합니다.
핵심 포인트
- LLM을 사실의 출처가 아닌 구조를 잡는 크리에이티브 디렉터로 활용
- 추론(LLM)과 사실(API)을 분리하여 환각 현상 근본적 차단
- 구조화된 JSON 스켈레톤 생성 후 리졸버 레이어를 통해 실제 데이터 매핑
- Gemini Flash와 같은 저렴한 모델과 공격적인 캐싱으로 비용 최적화
저는 소프트웨어 엔지니어 Emir입니다. 지난 몇 달 동안 혼자서 AI 여행 플래너인 Rotavi를 구축해 왔습니다. 저는 제품 전체의 기반이 된 단 하나의 아키텍처(Architectural) 결정 사항을 공유하고자 합니다. 왜냐하면 이것이 대부분의 "AI 여행 플래너" 데모들이 틀리는 지점이기 때문입니다: 모델이 사실을 지어내는 것을 허용하지 않는 것입니다.
문제점
제가 시도해 본 모든 AI 여행 도구는 두 가지를 제대로 수행하지 못했습니다:
- 환각(Hallucination) 발생 — 존재하지 않는 레스토랑, 잘못된 주소의 호텔, 또는 실재하지 않는 항공편을 자신 있게 추천했습니다.
- 비자 무시 — 특히 경유(transit) 비자의 경우, 저렴한 경유 노선을 선택했다가 미처 알지 못했던 비자가 필요하여 공항에 발이 묶이는 상황이 발생할 수 있습니다.
이 두 가지 모두 동일한 근본 원인에서 비롯됩니다: 언어 모델(Language Model)이 사실의 출처(Source of facts) 역할을 하도록 방치하는 것입니다.
핵심 아이디어: 추론과 사실의 분리
해결책은 지루하지만 효과적입니다. LLM은 오직 계획의 **구조(Structure)**만을 잡으며, 결코 사실을 제공하지 않습니다.
- 모델은 여행의 _형태(Shape)_를 결정합니다 — 며칠 동안 갈 것인지, 리듬은 어떠한지, 각 슬롯에 어떤 종류의 장소가 어울리는지, 그리고 서사적 흐름(Narrative flow)을 결정합니다.
- 모든 사실적 세부 사항 — 장소 이름, 평점, 주소, 항공편 옵션, 링크 — 은 모델의 기억이 아닌 **권위 있는 API(Authoritative APIs)**로부터 가져옵니다.
따라서 모델은 레스토랑을 "회상(Recall)"하지 않습니다. 대신 _"2일 차 오후: 구시가지 근처의 평점이 좋은 전통 레스토랑"_과 같은 의도(Intent)를 출력하며, 장소 API(Places API)에 대한 실제 쿼리가 해당 슬롯을 현재 영업 중인 실제 장소로 채웁니다. 만약 API가 아무것도 반환하지 않는다면, 해당 슬롯은 무언가를 지어내는 대신 빈 상태로 남게 됩니다.
파이프라인, 구체적으로
- 짧은 설문 조사: 여행자 수, 프로필, 선호도 (음식, 역사, 자연, 밤문화, 속도).
- LLM이 구체적인 사실이 아닌 의도를 설명하는 타입화된 _슬롯(slots)_들로 구성된 구조화된 일자별 스켈레톤 (skeleton) (JSON)을 생성합니다.
- 리졸버 (resolver) 레이어가 실제 소스로부터 각 슬롯을 채웁니다: 장소를 위한 장소 API (places API), 경로를 위한 항공권 검색 API (flight-search API), 통화 데이터 등.
- 검증 (Validation): 리졸버가 실제 데이터로 뒷받침할 수 없는 모든 것은 삭제되거나 플래그(flag)가 지정됩니다. 모델이 그 공백을 채울 수 없도록 합니다.
- 렌더링: 일자별 일정, 원터치 지도 링크, PDF, 통화 참고 사항.
도움이 되었던 사고 모델: LLM을 백과사전이 아닌 크리에이티브 디렉터(creative director)처럼 취급하는 것입니다.
API 비용을 합리적으로 유지하기
두 가지가 대부분의 역할을 수행했습니다:
- 공격적인 캐싱 (Cache aggressively). 동일한 도시/쿼리는 많은 사용자에게 동일한 실제 데이터로 리졸브됩니다. API를 다시 호출하는 대신 이를 캐싱하세요. 덕분에 월간 청구 금액을 아주 작게 유지할 수 있었습니다.
- 충분한 경우 작고 저렴한 모델 사용. 구조화 작업에는 프런티어 모델 (frontier model)이 필요하지 않습니다. 빠르고 저렴한 모델(저는 Gemini Flash를 사용합니다)이 이를 잘 처리합니다. "환각 없음 (no hallucination)" 보장은 모델의 크기가 아니라 아키텍처에서 나오며, 이것이 바로 핵심입니다.
비자: 동일한 철학의 확장 적용
환승 비자 규칙은 다중 조건부입니다: 여권, 경유 국가, 에어사이드 (airside) 이탈 여부, 이미 보유하고 있는 다른 비자 등. 이것이 바로 LLM이 자신 있게 말하면서도 틀리기 쉬운 바로 그런 종류의 정보입니다. 따라서 모델이 추측하는 것이 아니라, **권위 있는 데이터 위에 구축된 구조화된 규칙 엔진 (structured rule engine over authoritative data)**을 사용합니다.
솔직한 범위: 비자/환승 엔진은 현재 **터키 여권 (Turkish passport)**을 대상으로 합니다. 이는 저의 실제 상황이며 규칙을 직접 검증할 수 있는 부분이기 때문입니다. 범위를 확장하는 것은 로드맵에 포함되어 있습니다.
스택 및 개인적인 노트
궁금해하실 분들을 위해: Next.js + Supabase, 구조화를 위한 빠른 LLM (Gemini Flash), 그리고 실제 데이터를 위한 검색/장소 API를 사용했습니다.
혼자 작업하면서 얻은 핵심 교훈은 평소와 같습니다. 범위를 냉정하게 좁히고, 일주일마다 하나의 우선순위에 집중하며, 코드와 콘텐츠 생성 속도를 높이기 위해 AI에 의존하되, 사실 관계(facts)에 대해서는 절대 신뢰하지 않는 것입니다.
직접 사용해 보세요 / 오류가 발생하는 부분을 알려주세요
현재 서비스 중이며 무료로 이용 가능합니다. 현재 4개 대륙, 39개국, 400개 이상의 도시를 지원합니다: https://rotavi.app/en?utm_source=devto&utm_medium=referral&utm_campaign=backlink
이 커뮤니티 여러분의 피드백을 진심으로 기다리고 있습니다. 특히 resolver/validation(해결사/검증) 접근 방식과 아키텍처(architecture)를 어떻게 개선하면 좋을지에 대해 의견을 듣고 싶습니다. 댓글을 통해 어떤 부분이라도 더 깊이 있게 논의할 준비가 되어 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기