AWS 기술 블로그
하나투어의 Amazon Neptune과 Amazon Bedrock AgentCore를 활용한 여행상품 기획 에이전트 구축기
“오사카 3박 4일, 벚꽃 시즌, 예산 150만 원.” 이 조건 한 줄로 여행 패키지 기획 초안을 만들 수 있다면 어떨까요? 하나투어는 상품기획자(MD, Merchandiser)가 수작업으로 2~3일에 걸쳐 만들던 패키지 일정 초안을, 지식 그래프 기반의 AI 에이전트로 2~3분 만에 생성하는 시스템을 구축했습니다. 이 글에서는 하나투어가 두 번의 시행착오 끝에 Amazon Neptune 기반 GraphRAG(그래프 기반 검색 증강 생성)와 Amazon Bedrock AgentCore에 도달하기까지의 여정과, 그 과정에서 내린 기술적 의사결정을 공유합니다.
하나투어 소개
하나투어는 패키지 여행 상품을 기획하고 판매하는 국내 대표 여행 기업으로, 2025년 말 기준 누적 이용객 5,000만 명, 회원 수 900만 명을 보유하고 있습니다. 최근에는 여행 형태의 변화가 빠르게 나타나고 있습니다. 개별 자유여행(FIT) 이용객이 148만 명을 돌파해 역대 최대를 기록했고(2026년 상반기 발표 기준), 모바일 앱 MAU도 79만 명으로 사상 최대치를 경신했습니다. 분기 평균 100만 명 선의 해외 송출 규모를 유지하면서도, 고객의 취향과 여행 트렌드는 그 어느 때보다 빠르게 다양해지고 있습니다.
프로젝트 배경: MD의 기획 역량을 증폭하는 에이전트
MD가 사용하던 기존 기획 도구는 단일 요청을 처리하는 데 시간이 오래 걸렸고, 여러 요청을 동시에 처리하는 데에도 약했습니다. 빠르게 변하는 여행 트렌드를 상품에 반영하거나, 기존 상품을 조금씩 바꿔 새로운 변주를 대량으로 만들어내는 작업은 대부분 수작업에 의존했습니다.
프로젝트의 목표는 명확했습니다. MD가 수년간 축적한 패키지 상품의 완성도는 유지하면서, 빠르게 변하는 트렌드를 적은 노력으로 반영할 수 있도록 돕는 것입니다. 핵심은 MD를 대체하는 것이 아니라, MD가 더 빠르게 움직일 수 있도록 기획 역량을 증폭하는 데 있었습니다. 수작업으로 2~3일이 걸리던 초안 작업의 부담을 덜어, MD가 더 가치 있는 판단에 시간을 쓰도록 하는 것이 출발점이었습니다.
본 글에서는 이 목표에 도달하기까지의 두 차례 시행착오, 최종적으로 선택한 GraphRAG와 AgentCore 아키텍처, 그리고 도입 성과를 차례로 살펴보겠습니다.
두 번의 시행착오: 무엇이 막혔는가
1차 시도: 순차 생성과 반복 평가의 한계
첫 번째 접근은 직관적이었습니다. 하나투어의 여행상품 정보를 임베딩 시킨 RAG가 연결된 Chat 기반의 에이전트를 개발했습니다. MD분들이 채팅을 통해 일정 생성을 요청하면 일정생성 에이전트가 RAG를 참고하며 일정의 아웃라인을 잡고, 1일차부터 순서대로 일정을 생성한 뒤, 평가 에이전트가 만족할 때까지 생성과 평가를 반복하는 구조였습니다. 품질 면에서는 합리적인 설계였습니다.
그러나 4박 5일 일정 하나를 생성하는 데 약 20~30분이 걸렸습니다. 무한 반복 구조에서 생성 시간을 통제하기 어려웠고, 이 정도 응답 시간으로는 실무에서 활용되기 어려웠습니다. 시스템은 동작했지만 실사용으로 이어지지 못했습니다.
2차 시도: 그라운딩을 활용한 현실성, 그러나 자사 데이터의 부재
1차시도에서 방향을 바꿔, 외부 대규모 언어 모델(LLM, Large Language Model)에 외부 지도 서비스 기반의 그라운딩을 결합했습니다. 여기서 그라운딩(Grounding)이란 LLM의 출력을 검증 가능한 외부 출처·사실에 결부시켜 환각을 줄이고 답변을 확인 가능하게 만드는 개념이자 과정입니다. 실제로 존재하는 관광지와 식당, 현실적인 이동 동선이 일정에 반영되면서 결과물의 현실성이 크게 높아졌습니다. 일반적인 모델 지식만으로 짠 일정과는 분명한 차이가 있었습니다.
그러나 한 가지 문제가 남았습니다. 외부 그라운딩은 세상의 일반적인 정보는 잘 알지만, 하나투어가 수년간 축적한 자사 상품 데이터는 알지 못했습니다. 현실적이지만 하나투어답지 않은 일정이 나오는 것입니다. 빠르고 현실적이지만 자사 데이터가 빠진 결과물과, 자사 데이터는 담고 있지만 느리고 무거운 결과물 사이의 딜레마였습니다.
왜 일반 RAG로는 부족했는가
두 번의 시도는 공통된 한계를 드러냈습니다. 검색 결과가 충분하지 않으면 LLM은 존재하지 않는 관광지를 지어내거나, 실제로는 멀리 떨어진 장소를 한 동선에 묶는 식으로 환각(hallucination)을 일으켰습니다. 또한 새로운 트렌드 데이터를 추가할 때마다 검색 파이프라인과 인덱스를 다시 구성해야 했고, 그만큼 시스템 복잡도가 늘어났습니다.
근본 원인은 데이터를 다루는 관점에 있었습니다. 여행 패키지는 본질적으로 도시와 항공편, 호텔, 관광지가 서로 연결된 관계의 집합입니다. 그런데 일반적인 RAG는 이 패키지를 텍스트 청크로 분해해 벡터로 저장하면서 관계를 평탄화합니다. 특히 기획자 입장에서 “오사카 미식여행”과 “오사카 온천여행”은 테마가 달라 텍스트 유사도는 낮지만, 같은 도시와 노선을 공유하므로 매우 유사한 상품입니다. 벡터 유사도만으로는 이 구분을 해낼 수 없습니다.
여행 기획이라는 도메인에서 일반 RAG와 GraphRAG가 어떻게 갈라지는지 정리하면 다음과 같습니다.
| 관점 | 일반 RAG (벡터 검색) | GraphRAG (지식 그래프) |
|---|---|---|
| 데이터 표현 | 텍스트를 청크로 잘라 벡터로 저장 | 엔티티와 관계를 그래프로 저장 |
| 검색 방식 | 벡터 유사도(코사인) 비교 | 관계 순회(graph traversal) |
| 구조 보존 | 평탄화 과정에서 관계가 소실됨 | 엣지로 관계를 그대로 표현 |
| 유사도의 의미 | 텍스트가 비슷함 | 같은 도시·노선·호텔 등급을 공유함 |
| 다중 조건 질의 | 검색 후 별도 후처리 필요 | 한 번의 질의로 조건을 조합 |
| 그라운딩 | 상위 K개 문서를 참조 | 그래프 엔티티를 직접 참조 |
| 초기 구축 | 청크 분할·임베딩 파이프라인 | 그래프 스키마·엣지 설계 |
핵심 차이는 그라운딩 행에 있습니다. 일반 RAG가 “비슷한 텍스트를 찾는 것”이라면, GraphRAG는 “정확한 구조를 조립하는 것”입니다. 하나투어가 만드는 것은 한 편의 글이 아니라 도시·노선·호텔·관광지·트렌드가 맞물린 구조적 조합이므로, 검색 결과의 의미부터가 달랐습니다. 대신 그래프 스키마와 엣지를 먼저 설계해야 하는 초기 비용이 따르는데, 하나투어는 이 비용을 감수할 만하다고 판단했습니다.
그래서 관점을 바꾸기로 했습니다. 문서가 아니라 관계로 데이터를 다루기로 한 것입니다.
솔루션 개요: GraphRAG와 AgentCore의 결합
앞선 두 딜레마, 즉 현실성과 자사 데이터의 양립, 그리고 속도와 데이터 충실도의 양립을 동시에 풀기 위해 하나투어는 지식 그래프 기반의 GraphRAG와 서버리스 에이전트 호스팅을 결합했습니다. 이 구성은 엔터프라이즈 보안과 데이터 레지던시(data residency) 요건도 함께 충족합니다.
시스템 아키텍처
다음 다이어그램은 사용자 브라우저에서 그래프 데이터베이스까지 이어지는 전체 아키텍처를 보여줍니다.

전체 구조는 크게 세 계층으로 구성됩니다.
- 프론트엔드 계층 — Next.js로 구현한 웹 인터페이스가 MD의 입력(자연어 채팅 또는 구조화된 폼)을 받아 SSE(Server-Sent Events)로 생성 과정을 실시간으로 중계합니다.
- 에이전트 계층 — AgentCore Runtime이 여행 기획 에이전트를 서버리스로 호스팅하고, AgentCore Gateway가 에이전트와 도구를 MCP(Model Context Protocol)로 연결합니다.
- 데이터 계층 — AWS Lambda 함수가 Neptune에 저장된 지식 그래프를 조회하고, 생성된 상품은 Amazon DynamoDB에 저장됩니다.
각 계층은 SigV4 서명으로 인증되며, Neptune은 VPC 내부에 배치되어 그래프 데이터가 외부로 나가지 않습니다.
기술 스택
주요 구성 요소와 선택 이유는 다음과 같습니다.
| 구분 | 기술 | 선택 이유 |
|---|---|---|
| 지식 그래프 | Amazon Neptune | 도시·노선·호텔·관광지의 구조적 관계를 자연스럽게 표현하고, 벡터 유사도보다 정확한 관계 질의가 가능 |
| 에이전트 호스팅 | AgentCore Runtime | EC2·로드밸런서·인증 미들웨어를 직접 구축하지 않고 에이전트 로직에 집중 |
| 도구 연결 | AgentCore Gateway + AWS Lambda | 에이전트 추론과 도구 실행을 분리하고, Lambda를 독립적으로 배포·확장 |
| LLM | Claude Sonnet·Claude Opus (Amazon Bedrock) | 구조 생성과 상세 생성에 모델을 나눠 비용과 품질의 균형 확보 |
| 캐싱 | Amazon ElastiCache (Valkey) | 반복되는 그래프 조회 비용과 Neptune 부하를 절감 |
| 에이전트 프레임워크 | LangGraph | 생성 파이프라인을 그래프(DAG) 형태로 구성하고, 훅(Hook)으로 도구 호출을 투명하게 캐싱 |
여기서 Amazon Bedrock은 Claude를 비롯한 기반 모델을 API로 제공하는 완전관리형 서비스이며, ElastiCache는 완전관리형 인메모리 캐시 서비스입니다. LangGraph와 MCP는 오픈소스 기술로 에이전트 구성에 사용했습니다. 그래프 질의에는 openCypher를 사용했습니다. Neptune은 openCypher와 Gremlin을 모두 지원하는데, 하나투어는 SQL과 유사해 학습 곡선이 완만한 openCypher를 택했습니다.
핵심 구현: 관계로 다시 설계한 여행 기획
앞서 정리한 두 딜레마, 즉 현실성과 자사 데이터의 양립, 속도와 데이터 충실도의 양립을 여러 설계 결정으로 풀어 나갔습니다. 데이터를 관계로 다시 표현하는 지식 그래프에서 시작해, 빠르게 변하는 트렌드에 신선도를 부여하고, 생성 속도를 끌어올리고, 도구 연결을 확장 가능하게 만들고, 환각을 억제하는 그라운딩 원칙까지 차례로 살펴보겠습니다.
지식 그래프: 문서가 아닌 관계로
하나투어는 패키지 상품 데이터를 텍스트로 밀어 넣는 대신, 16종의 노드(국가, 도시, 호텔, 관광지, 항공 구간, 트렌드 등)와 20종의 엣지로 분해해 Neptune에 지식 그래프로 구축했습니다.
이제 에이전트는 도시 노드에서 출발해 엣지를 따라 호텔로, 다시 관광지로 이동하는 멀티 홉(multi-hop) 순회를 통해 실제 동선에 가까운 초안을 추론합니다. 예를 들어 “오사카에 있는 온천 호텔”을 찾는 질의는 도시에서 호텔로 이어지는 관계를 한 번에 순회합니다.
// 도시 → 호텔 관계를 순회하며 온천이 있는 호텔만 필터링
MATCH (c:City {name: $city})-[:HAS_HOTEL]->(h:Hotel)
WHERE h.has_onsen = true
RETURN h
그래프를 수작업으로 매핑한 것은 아닙니다. 1차 시도에서 사용했던 무거운 평가 에이전트를 스키마 검증 전용으로 전환했습니다. 데이터를 수집해 JSON으로 변환할 때 LLM이 스키마 규칙에 맞는지 검증하고, 통과한 데이터만 openCypher 쿼리로 Neptune에 자동 적재하는 파이프라인입니다.
트렌드 신선도: 시간이 지나면 흐려진다
여행 트렌드는 빠르게 바뀝니다. 작년 인기 촬영지가 올해는 관심에서 멀어지고, 새로 떠오른 맛집이 단기간에 핵심 코스가 되기도 합니다. 그래프에 자사 상품 데이터만 담겨 있다면 일정은 정확하지만 시의성이 떨어집니다. 그래서 하나투어는 별도의 트렌드 수집 에이전트로 다양한 매체에서 여행 지역별 트렌드를 수집해 Trend·TrendSpot 노드로 그래프에 적재하고, 각 트렌드를 도시 노드에 연결했습니다. 이렇게 하면 “오사카 지역의 최신 트렌드”처럼 위치와 결합된 질의가 가능해집니다.
문제는 신선도입니다. 한 번 수집한 트렌드를 그대로 두면 시간이 지나도 똑같은 비중으로 일정에 반영되어, 철 지난 콘텐츠가 계속 추천됩니다. 이를 막기 위해 각 트렌드 노드에 바이럴 점수(virality_score)와 감쇠율(decay_rate)을 부여하고, 경과 개월 수에 따라 유효 점수를 실시간으로 계산합니다.
# 수집 후 경과한 개월 수만큼 점수를 감쇠시켜 신선도를 반영
effective_score = virality_score * (1 - decay_rate) ** months_elapsed
감쇠율을 어떻게 잡느냐에 따라 트렌드의 수명이 달라집니다. 하나투어는 감쇠율을 기준으로 트렌드를 세 가지 등급(tier)으로 분류했습니다.
| 등급 | 감쇠율 | 성격 | 예시 |
|---|---|---|---|
| hot | 0.10 이하 | 최근 인기 | 맛집, 영화 촬영지 |
| steady | 0.10 초과 ~ 0.25 | 지속 — 검증된 안정 콘텐츠 | 테마파크, 유적지 |
| seasonal | 0.25 초과 | 단기 이벤트 — 빠르게 식는 콘텐츠 | 벚꽃 축제 |
등급이 나뉘면 트렌드를 어떤 비율로 섞을지 조절할 수 있습니다. 기본값은 hot 70% 대 steady 30%이며, MD가 “최신 트렌드 위주로”라고 요청하면 hot 비중을, “검증된 콘텐츠 위주로”라고 하면 steady 비중을 높이는 식으로 폼 슬라이더나 대화로 조정합니다.
시간 감쇠 함수를 점수에 적용하고 트렌드가 발생한 도시까지 묶어 “오사카 지역 트렌드 중 유효 점수가 일정 수준 이상인 것”을 한 번의 질의로 거르는 것은, 트렌드·장소·도시가 관계로 연결된 그래프 위에서이기에 가능했습니다.
2-Phase 생성: 구조와 상세를 나누다
1차 시도의 교훈을 반영해, 비용이 큰 LLM 추론을 꼭 필요한 곳에만 쓰도록 일정 생성을 다시 짰습니다. 핵심은 두 가지입니다. 첫째, 도시 묶기·동선·시간·식당 배분 같은 구조 계산은 LLM이 아니라 결정론 알고리즘이 맡습니다. 둘째, 각 날짜의 상세는 고성능 모델로 병렬 생성하고, 입력 해석 같은 가벼운 작업은 저렴한 모델로 처리합니다. 다음 다이어그램은 입력 해석부터 검증까지 이어지는 생성 파이프라인을 보여줍니다.

생성 흐름은 다음과 같습니다.
입력 해석·후보 선별 — 사용자 요청을 파싱하고 동행·출발지를 추론하며, 날씨를 조회하고 관광지 후보 풀을 선별합니다. 이런 가벼운 단계에는 저렴하고 빠른 Claude Haiku를 사용합니다.
일정 생성 — 후보를 도시별로 묶고(클러스터링) 동선을 정렬하고 시간창에 맞춰 배치하며 식당을 날짜별로 미리 배분하는 ‘골격’은 결정론 알고리즘이 빠르고 재현 가능하게 처리합니다. 그 위에서 각 날짜의 상세 일정(관광지, 식사, 트렌드, 추천 사유)은 고성능 모델 Claude Opus가 날짜별로 병렬 생성합니다. 관광지·식당을 날짜별로 미리 배분하므로 중복도 방지됩니다.
검증 — 먼저 LLM을 호출하지 않는 결정론 검증이 도시 동선·호텔 연속성·일자 패턴 같은 구조 정합성을 1~2초 안에 확인하고, 이어서 Opus가 각 날짜의 의미적 정합성(시간 초과, 동선 현실성, 트렌드 비율 등)을 검증합니다.
부분 재생성 — 문제가 발견되면 전체를 다시 만들지 않고 해당 날짜만 다시 생성한 뒤 재검증하며, 모두 통과할 때까지(최대 5회) 반복합니다.
가벼운 작업은 저렴한 Haiku로, 일정 생성·검증 같은 핵심 추론은 Opus로 나누고, 동선·배분 같은 구조 계산은 LLM 대신 결정론 알고리즘으로 처리한 데다 날짜별 병렬 생성과 부분 재생성을 더한 덕분에, 8일 일정 기준 약 300초 → 약 60초 수준으로 생성 시간을 단축했습니다. 또한 생성·검증·재생성 단계를 독립적으로 재시도할 수 있어, 문제가 생긴 부분만 다시 만들면 됩니다.
MCP Gateway: 확장 가능한 도구 연결
에이전트와 그래프 데이터베이스를 잇는 핵심은 AgentCore Gateway입니다. Lambda 함수는 openCypher 쿼리 하나에만 집중하는 단일 책임 구조로 구성했고, MCP 프로토콜 변환과 AWS IAM 기반 인증은 앞단의 Gateway가 담당합니다.
| 구성 요소 | 역할 |
|---|---|
| AgentCore Gateway | MCP 프로토콜 변환, IAM 기반 인증, 도구 라우팅 |
| AWS Lambda | 단일 openCypher 쿼리 실행 (Neptune 조회/쓰기, DynamoDB CRUD) |
이 구조의 가치는 확장성에 있습니다. 새로운 트렌드 수집 도구를 추가하려면 기존 파이프라인을 수정할 필요 없이 Gateway에 스키마를 등록하고 Lambda 함수만 추가하면 됩니다.
환각을 줄이는 그라운딩 원칙
LLM은 정보가 부족하면 알지 못하는 내용을 지어내려는 경향이 있습니다. 이를 막기 위해 하나투어는 한 가지 원칙을 적용했습니다. 오직 그래프에서 찾은 데이터만 사용한다는 것입니다.
이 원칙에 따라 에이전트는 노선 데이터에 있는 항공편만 사용하고, Neptune에 존재하는 관광지와 호텔만 배정하며, 유효성을 통과한 트렌드만 반영합니다. 그래프 데이터가 LLM이 생성할 수 있는 범위를 정의하는 셈입니다. 그래프 데이터의 품질에 이 그라운딩 원칙이 더해지면서 여행지 환각을 상당 부분 줄일 수 있었습니다.
여기에 더해, Phase별로 필요한 컨텍스트만 전달하는 필터링과 Bedrock의 프롬프트 캐싱(Prompt Caching)을 적용해 LLM 입력 토큰도 크게 줄였습니다. 같은 그래프 데이터를 모든 단계에 통째로 넘기는 대신, 구조 생성에는 노선·호텔 요약만, 상세 생성에는 해당 일자 도시의 데이터만 추려 전달하는 방식입니다.
도입 성과
가장 큰 변화는 기획 시간입니다. 수작업으로 2~3일이 걸리던 초안 작업이 이제 2~3분으로 줄었습니다. MD의 검토는 여전히 필요하지만, 반복 작업에 쏟던 시간을 더 가치 있는 판단에 쓸 수 있게 되었습니다.
| 항목 | 이전 | 현재 |
|---|---|---|
| 초안 기획 시간 | 약 2~3일 (수작업) | 약 2~3분 |
| 8일 일정 생성 시간 | 약 750초 (단일 패스) | 약 75초 (2-Phase) |
| LLM 입력 토큰 | 기준 (100%) | 약 76% 절감 |
| 인프라 운영 | EC2·로드밸런서·오토스케일링·인증 미들웨어 직접 관리 | AgentCore의 다양한 Serverless 모듈 활용 |
상품 한 건당 AI 기획 비용도 낮은 수준을 유지하고 있어, 변주를 대량으로 만들어내는 작업에도 부담이 적습니다.
개발 조직 입장에서는 인프라 운영 부담에서 벗어난 점이 큰 성과였습니다. 직접 구축했다면 EC2와 로드밸런서, 오토스케일링, 인증 미들웨어까지 관리해야 했을 구성이, AgentCore를 사용하면서 agentcore deploy 명령 한 줄로 정리되었습니다.
다만 이 시스템은 MD를 대체하지 않습니다. 생성된 초안은 MD의 검토를 거쳐 완성되며, 시스템의 역할은 반복적인 초안 작업의 시간을 줄여 MD가 더 중요한 판단에 집중하도록 돕는 데 있습니다.
결론 및 향후 계획
하나투어의 여행상품 기획 에이전트 구축 과정에서 얻은 교훈은 다음과 같습니다.
- 구조적 연결이 본질인 도메인에서는 데이터를 문서가 아니라 관계로 다뤄야 합니다. 여행 상품처럼 도시·노선·호텔·관광지가 얽힌 데이터는 텍스트 청크로 평탄화하는 순간 가장 중요한 정보를 잃습니다.
- “그래프에서 찾은 데이터만 사용한다”는 그라운딩 원칙이 환각을 줄이는 핵심이었습니다. 그래프 데이터가 LLM의 생성 범위를 정의하도록 만들어, 추측을 근거 있는 탐색으로 바꿨습니다.
- 서버리스 에이전트 호스팅으로 인프라 운영 부담을 덜고 에이전트 로직에 집중했습니다. 직접 구축했다면 관리해야 했을 EC2·로드밸런서·오토스케일링·인증 미들웨어를 떠안지 않게 되었습니다.
하나투어는 현재 시스템을 고도화하고 있습니다. 데이터는 판매 상품을 우선으로 채우고 이후 관광지와 세부 POI(Point of Interest)로 단계적으로 보강하며, MD 피드백을 반영해 인터페이스를 개선하고 있습니다. 하나투어가 가지고 있는 다양한 여행관련 노하우와 MD분들이 가지고 있는 암묵지들을 형식지로 녹여내어 GraphRAG에 담고, 여행지의 실시간 정보(날씨, 현지상황, 이벤트 등)가 필요한 부분은 검색엔진, 외부 MCP/API를 AgentCore의 플랫폼 중립적인 특성을 활용해 그라운딩하여 보완하는 하이브리드 방향도 검토하고 있습니다.
이번 사례에서 다룬 서비스에 대한 자세한 내용은 Amazon Neptune과 Amazon Bedrock AgentCore 문서에서 확인하시기 바랍니다. 구조적 관계가 중요한 데이터를 다루며 AI 에이전트 도입을 고민하는 팀에게 이 경험이 참고가 되기를 바랍니다.