본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 05. 17. 21:03

Google Maps의 Ask Maps에서 영감을 받아, Codex로 AI 경로 제안 데모를 만들어 보았다

요약

Google Maps의 Ask Maps에서 영감을 받아, 사용자의 문맥적 요구사항에 맞춰 경로를 제안하는 'AI Route Concierge' 웹 데모를 개발했습니다. 이 앱은 출발지, 목적지, 일시, 인원 등 기본 정보를 입력받고, 자연어 채팅을 통해 '앉을 수 있는 가능성', '요금 비교'와 같은 모호한 우선 조건을 변경할 수 있습니다. 핵심은 AI가 경로 자체를 생성하는 것이 아니라, 사용자의 자연어를 조건으로 해석하고 기존의 가상(mock) 경로 후보들을 스코어링하여 재평가하고 설명하는 데 초점을 맞춘 MVP입니다.

핵심 포인트

  • 사용자 문맥에 따른 동적 경로 제안: '최단' 외에도 앉을 수 있는 가능성, 쾌적도 등 모호한 희망 사항을 자연어로 전달할 수 있습니다.
  • AI의 역할 분리 설계: LLM(Gemini API)은 경로 생성 대신, 사용자의 자연어 조건을 구조화된 JSON 형식으로 변환하고 스코어링 결과를 설명하는 데 집중합니다.
  • Mock 데이터 기반의 비교 분석: 실제 실시간 데이터를 사용하는 대신, 인원수나 이동 수단에 따른 요금/경로 후보 간의 장단점 비교(예: 신칸센 vs 자가용)를 시뮬레이션했습니다.
  • 기술 스택: Python과 Streamlit을 사용하여 프론트엔드를 구현하고, SQLite와 mock 데이터를 활용하여 MVP를 구성했습니다.

TL;DR

Google Maps의 Ask Maps에서 영감을 받아, 사용자의 문맥에 맞춰 경로를 제안하는 Web 데모를 만들었습니다.

만든 것은 AI Route Concierge라는 Streamlit 앱입니다. 출발지, 목적지, 일시, 인원, 이동 수단, 우선 조건을 입력하면, mock 데이터의 경로 후보를 스코어링하여 표시합니다. 또한, AI 채팅을 통해 "앉을 수 있는 경로를 우선해줘", "요코하마에서 오테마치까지 내일 8:30 도착, 재래선으로 앉아서 가고 싶어"와 같이 자연어(Natural Language)로 조건을 변경할 수 있습니다.

UI는 최종적으로 지도가 아닌 경로 후보 카드를 주인공으로 설정했습니다. AI 채팅은 항상 표시하지 않고, 필요할 때만 오른쪽에 열리는 형태로 구성했습니다.

선택한 경로는 역이나 SA(Service Area) 등의 경유 지점을 타임라인으로 확인할 수 있습니다.

이 기사에서는 요구사항 정의, 설계, 구현, UI 개선의 흐름을 짧게 정리합니다.

배경

일본, 특히 수도권에서는 목적지까지의 경로와 이동 수단이 여러 가지가 있습니다.

도쿄에서 나고야로 가는 경우에도 신칸센, 자가용, 고속버스 등이 있습니다. 혼자라면 신칸센이 명확하겠지만, 4인 가족이라면 자가용이 총액 면에서 더 저렴할 수도 있습니다.

출퇴근도 마찬가지입니다. "최단"만이 정답은 아니며, 조금 돌아가더라도 앉고 싶다거나, 환승을 줄이고 싶다거나, 혼잡을 피하고 싶다는 등의 희망 사항이 있습니다.

기존의 경로 검색 앱에도 "시간 우선", "요금 우선" 기능은 있지만, "15분 늦어도 좋으니 앉을 수 있는 가능성을 높이고 싶어"와 같은 모호한 희망을 자연어로 전달하고 문맥에 맞춰 재제안받는 경험은 아직 약하다고 느꼈습니다.

그래서 이번에는 AI가 사용자의 희망을 조건으로 해석하고, 여러 경로를 비교하여 설명하는 MVP를 만들었습니다.

요구사항 정의

처음에 결정한 것은 스코프(Scope)를 너무 넓히지 않는 것이었습니다.

지도 앱을 만들려고 하면 철도, 버스, 도로, 시간표, 요금, 혼잡도, 지연, LLM, 채팅 UI까지 한꺼번에 방대해집니다. 이번에는 데모이므로 실 데이터의 망라성이 아니라 문맥에 따라 경로 평가가 달라지는 것을 주인공으로 삼았습니다.

MVP에서 수행하는 작업은 다음과 같습니다.

  • 출발지, 목적지, 경유지 지정
  • 출발/도착 일시 지정
  • 인원과 이동 수단 지정
  • 우선 조건 지정
  • mock 경로 후보의 랭킹
  • AI 채팅을 통한 자연어로의 조건 변경
  • 조건 변경 후의 재랭킹과 설명

반대로 실시간 교통 API, 실제 시간표, 실시간 지연 정보, 예약 및 결제, 턴 바이 턴(Turn-by-turn) 내비게이션은 구현하지 않습니다.

시나리오는 두 가지로 압축했습니다.

  • 도쿄→나고야 여행
  • 요코하마→오테마치의 장거리 출퇴근

여행에서는 "4명이라면 신칸센보다 자가용이 저렴하다"는 비교를, 출퇴근에서는 "최단은 아니지만 앉을 수 있는 가능성이 높다"는 비교를 표현합니다.

설계

AI에게 경로 그 자체를 만들게 하는 것이 아니라, 역할을 나누었습니다.

User input
→ Preference extraction
→ Route candidate lookup
...

포인트는 LLM에게 경로를 생성시키지 않는 것입니다.

경로 후보는 SQLite에 넣은 mock 데이터에서 가져옵니다. AI는 자연어를 조건으로 변환하는 것과 스코어링 결과를 설명하는 것에 집중했습니다. 이 방식이 MVP로서 동작이 더 안정적입니다.

기술 구성은 다음과 같습니다.

  • backend: Python
  • frontend: Streamlit
  • database: SQLite
  • route data: mock data
  • LLM: Gemini API, 미설정 시 로컬 처리

디렉토리 구성은 심플하게 만들었습니다.

app/
main.py
ai/
...

구현

mock 경로와 스코어링

경로 후보는 data/seed_routes.py에 mock으로 정의했습니다.

각 경로에는 소요 시간, 요금, 환승 횟수, 앉을 수 있는 가능성, 쾌적도, 가족 친화 점수 등을 부여했습니다.

특히 요금은 1인당 요금과 그룹 요금을 나누었습니다.

  • per_person: 1인당 요금
  • group: 그룹 전체 요금

이를 통해 신칸센은 1인분 x 인원수, 자가용은 그룹 총액으로 비교할 수 있습니다. 4인 여행이라면 자가용이 상위로 올라오기 쉬워지는 문맥을 표현할 수 있습니다.

스코어링에서는 다음과 같은 요소를 사용했습니다.

  • 소요 시간
  • 요금
  • 환승 횟수
  • 도보 시간
  • 앉을 수 있는 가능성
  • 혼잡도
  • 쾌적도
  • 안정성
  • 가족 친화성

사용자의 우선 조건에 따라 가중치를 변경합니다. 예를 들어 "앉을 수 있는 경로를 우선해줘"라고 입력하면, 앉을 수 있는 가능성의 가중치를 높입니다.

Gemini API로 자연문을 조건으로 변환

자연문으로 조건을 변경할 수 있도록, Gemini API가 조건 업데이트 JSON을 반환하도록 구현했습니다.

예를 들어 다음과 같은 입력을 상정하고 있습니다.

요코하마에서 오테마치까지 내일 8:30 도착, 재래선으로 앉아서 가고 싶어

Gemini에는 현재 조건이나 유효한 선택지를 전달하여, 다음과 같은 JSON을 반환받습니다.

{
"origin": "横浜",
"destination": "大手町",
...

단, LLM의 출력을 그대로 신뢰하는 것이 아니라, 앱 측에서 유효한 키(key)만 검증하여 Preferences에 반영합니다. API 키가 없거나 실패한 경우에는 로컬의 간이 파서(parser)로 폴백(fallback)합니다.

설명문 생성

경로 후보 카드(route candidate card)의 설명문도 Gemini를 사용할 수 있는 경우에는 API로 생성하고, 사용할 수 없는 경우에는 템플릿으로 돌아갑니다.

여기서 주의한 점은 LLM이 새로운 교통 정보를 만들어내지 않도록 하는 것입니다. 전달하는 정보는 스코어링(scoring)된 후보, 총액, 소요 시간, 평가 포인트, 주의 사항만으로 제한하고, "전달된 데이터만 사용할 것"이라고 프롬프트(prompt)로 제약을 걸었습니다.

또한, Streamlit은 화면 조작 시마다 재실행되기 때문에, 동일한 조건으로 설명문을 반복해서 생성하지 않도록 캐싱(caching)했습니다.

UI 개선 흐름

처음에는 "Google Maps x AI"라는 발상에서 지도 표시를 중심으로 생각했습니다.

하지만 직접 만들어 확인해 보니, 이번 데모에서 가장 보여주고 싶은 것은 지도가 아니라 경로 후보가 문맥에 따라 어떻게 변하는가였습니다.

그래서 UI는 몇 번이고 다시 만들었습니다.

지도를 없애고 경로 후보를 주인공으로

초기 안에서는 지도 스타일의 표시를 준비했었지만, mock 데이터 데모에서는 지도의 정확도보다 후보 비교가 더 중요했습니다.

최종적으로는 지도를 제외하고 경로 후보 카드를 메인으로 삼았습니다. 카드에서는 소요 시간, 총액, 환승 횟수, 앉을 수 있는 가능성, 이유, 주의 사항을 볼 수 있도록 했습니다.

3열에서 2열로

처음에는 조건, 경로 후보, 채팅을 3열로 나열했습니다.

하지만 경로 검색 UI로서는 시선이 분산되고 경로 후보 영역이 좁아집니다. 그래서 조건 바(bar)를 상단에 모으고, 하단은 "경로 후보"와 "AI 채팅"의 2열로 구성했습니다.

조건 바를 경로 검색 UI에 가깝게

조건 입력은 상단에 집약했습니다.

  • 출발지
  • 경유지
  • 목적지
  • 목적
  • 출발/도착 일시
  • 인원
  • 이동 수단
  • 우선 조건
  • 환승 상한

인원은 - / 숫자 / + 형태의 스테퍼(stepper)로 만들었고, 경유지는 에비나 SA ×와 같은 칩(chip) 형태로 여러 개를 관리할 수 있도록 했습니다.

AI 채팅을 옵션화

채팅은 편리하지만, 항상 표시하면 경로 후보가 보기 어려워집니다.

따라서 AI 채팅은 기본적으로 닫아두고, 필요할 때만 오른쪽에 열리는 형태로 만들었습니다. 이력이 길어져도 보기 편하도록 이력 부분만 스크롤할 수 있는 틀로 구성했습니다.

Streamlit의 폼(form)에 position: sticky를 적용하면 입력란을 클릭할 수 없게 되는 경우가 있어, 최종적으로는 오른쪽 패널 전체를 고정하여 이력의 높이를 확보하는 방식으로 구현했습니다.

선택 중인 경로의 상세 정보 추가

카드만으로는 "구체적으로 어디를 통과하는지"가 조금 보기 어려웠기 때문에, 선택 중인 경로의 상세 정보를 추가했습니다. mock 데이터의 segments를 사용하여 역, SA, 환승 지점을 타임라인(timeline)으로 표시하고 있습니다.

동작 예시

초기 조건에서는 도쿄에서 나고야까지 4명이 여행하는 상황을 가정합니다.

  • 인원: 4명
  • 이동 수단: 자가용, 재래선, 신칸센, 고속버스
  • 우선 조건: 요금, 쾌적성

이 경우 추천은 자가용이 됩니다. 4명 총액이 신칸센보다 저렴하고, 짐이나 휴식의 자유도도 높기 때문입니다.

다음으로 채팅에 다음과 같이 입력합니다.

요코하마에서 오테마치까지 내일 8:30 도착, 재래선으로 앉아서 가고 싶어

그러면 요코하마→오테마치 출퇴근 시나리오로 전환되며, 이동 수단은 재래선, 우선 조건은 앉을 수 있는 가능성과 혼잡 회피가 됩니다. 최단 경로가 아니더라도 앉을 수 있는 가능성이 높은 경로가 상위에 나타납니다.

테스트

최소한의 테스트로서 자연문 파서(parser)와 스코어링에 단위 테스트(unit test)를 추가했습니다.

.venv/bin/python -m unittest discover -s tests

주로 다음 사항을 확인하고 있습니다.

  • 가족 여행에서는 요금과 쾌적성을 중시할 경우 자가용이 1위가 된다
  • 출퇴근 시 "앉을 가능성을 높이고 싶다"라고 입력하면, 앉을 가능성을 중시하는 경로가 1위가 된다
  • 시나리오 전환 시, 이전 시나리오의 인원수나 우선 조건이 유지되지 않는다
  • Gemini API 실패 시 로컬 처리로 폴백 (Fallback) 할 수 있다
  • 설명문이 동일한 조건일 경우 캐싱(Caching)된다

요약 및 배움

Ask Maps에서 착안하여 AI 경로 제안 앱의 MVP (Minimum Viable Product)를 만들었습니다. Mock 데이터임에도 불구하고, 자연어로 조건을 변경하고 경로 순위를 재계산하며 그 이유를 설명하는 단계까지는 구현할 수 있었습니다.

한편, 이 MVP는 완성품이 아니라 "AI를 어디에 넣어야 가치가 발생하는가"를 확인하기 위한 시제품입니다. 경로 생성 그 자체를 AI에게 맡기는 것이 아니라, 후보 경로 위에 사용자 문맥(Context)과 설명을 얹는다는 역할 분담에서 현실적인 AI 경로 제안의 가능성을 느꼈습니다.

직접 만들어보며 보인 제품의 강점, 제품화할 때의 과제, 그리고 AI Coding으로 진행하며 느낀 반성점을 정리합니다.

제품의 강점

  • 문맥을 다룰 수 있음: "시간순", "요금순"만으로는 포착하기 어려운 희망 사항을 경로 평가에 반영할 수 있습니다.
  • 총액이나 인원수를 고려할 수 있음: 4명 이동 시 신칸센보다 자가용이 유리하다는 판단을 표현할 수 있습니다.
  • 쾌적성을 평가에 넣을 수 있음: "조금 늦더라도 앉고 싶다", "혼잡을 피하고 싶다"와 같은 희망 사항을 다룰 수 있습니다.
  • AI의 역할을 한정할 수 있음: 경로 생성이 아니라, 희망 사항의 구조화와 이유 설명에 AI를 사용하는 구성으로 만들 수 있습니다.

제품화의 과제

  • 실제 데이터 연동: 철도, 버스, 도로, 요금, 혼잡도, 지연 정보를 어떻게 취득하고 업데이트할지가 큰 과제입니다.
  • 경로 탐색의 정밀도: 제안의 신뢰성은 경로 후보 생성과 스코어링 (Scoring)의 품질에 크게 의존합니다.
  • LLM 비용: 매번 LLM을 호출하면 느리고 비용이 많이 드는 경험이 되기 쉽습니다.
  • API 호출 감소: 캐싱, 규칙 기반 (Rule-based) 처리, LLM 호출 조건 분기가 필요합니다.
  • 가드레일 (Guardrail): 존재하지 않는 경로, 단정적인 지연 정보, 위험한 이동 제안을 피하기 위한 제어가 필요합니다.

AI Coding의 반성

  • UI 이미지는 먼저 확정할 것: 모호한 상태로 만들면 수정 작업이 늘어나 토큰을 대량으로 소비하게 됩니다.
  • 와이어프레임 (Wireframe)을 준비할 것: 지도 중심에서 경로 후보 중심으로 중간에 변경했기 때문에, 처음에 간단한 화면안이 있었다면 좋았을 것입니다.
  • 입력 항목은 늘어나기 쉬움: AI 채팅이 있더라도 명시적인 폼 (Form)을 추가하다 보면 UI가 무거워집니다.
  • AI 입력과 폼의 역할을 나눌 것: 자연문으로 충분한 조건과 명시적으로 선택하게 하고 싶은 조건을 정리할 필요가 있습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0