김인서inseout
← 프로젝트

fairy

여행 일정 짜기에 피로를 느끼는 사람들을 위한 AI 여행 플래너.

Belle&Wise Studio 2025.09 — 2025.12 (3개월) · 이후 리팩터링 진행
PythonFastAPILangChain / LangGraph AWS EC2 · LambdaDynamoDBSQS DockerGitHub Actions
−60%
정보 수집 시간
동기 → 비동기 병렬 수집
40→58%
정보 정확도
검색 프로세스 도입 후
실행 시간 +20초를 감수
2:23
일정 생성 시간
2박 3일 기준 · 3:00에서 단축
25
배치 분할 단위
DynamoDB 쓰기 한도에 맞춤
fairy 채팅 화면
대화로 여행 정보를 받아 일정을 제안합니다
fairy 일정 결과 화면
생성된 일정과 장소 추천
Context

왜 만들었나

AX/DX 전환이 더딘
여행 산업

여행에 필요한 정보를 모으는 과정이 불편하고 오래 걸린다는 문제를, 그 과정을 AI 에이전트로 자동화해 풀었습니다. 목적지·기간·인원·교통편·기호를 입력받아 기상·교통·숙소·엔터테인먼트·음식점·카페 정보를 수집하고, 여행 일정 포맷에 맞춰 조합해 제안합니다. 질문을 던지면 장소를 추천하는 기능도 함께 넣었습니다.

Role

맡은 부분

인프라 · 에이전트
· 백엔드
  • AWS 인프라 아키텍처 설계 및 구성
  • 에이전트 시스템 설계와 구현 — 멀티 에이전트 계층 구조 라우팅
  • CI/CD 파이프라인 구축 (GitHub Actions + Docker 이미지)
  • DynamoDB 스키마 설계 및 구현
Architecture

시스템 구성

fairy 시스템 아키텍처 다이어그램
인프라 구성
fairy 에이전트 그래프 구조
에이전트 그래프 — supervisor가 범주별 서브그래프로 라우팅
  1. Subgraph 계층 구조 (Hierarchical Team)

    사용자 입력을 범주화하고, main supervisor가 범주에 맞는 서브그래프로 라우팅합니다. 질문 패턴에 맞는 그래프만 실행되므로 불필요한 노드가 돌지 않습니다.

  2. 람다가 아니라 EC2

    처음에는 유지 비용이 압도적으로 유리한 람다로 구성하려 했습니다. 그런데 테스트에서 람다의 무상태 특성과 실행 시간 제한에 걸렸습니다 — 그래프 실행 중 state를 메모리에 유지할 수 없었습니다. 비용이 싼 쪽이 아니라 동작하는 쪽을 골라 EC2로 갔습니다.

  3. Docker 기반 CI/CD

    K-PaaS 공모전에도 참가해 Kubernetes 환경과 AWS EC2 환경에서 동시에 운영해야 했습니다. 관리 부담을 줄이려 Docker 이미지로 일원화하고, GitHub Actions로 EC2 배포 파이프라인을 구성했습니다.

  4. 인증·인가

    Cognito로 사용자를 등록·관리하고, 발급된 JWT로 인증이 필요한 요청을 검증합니다.

Problems

부딪힌 문제와 해결

크롤링이 막혀 설계가 무너짐

문제
여행 정보 수집을 크롤링 기반으로 설계했는데, WAF와 동적 스크립팅으로 크롤링을 차단하는 서비스들의 보안 정책을 간과해 설계가 통째로 무너졌습니다. AI와 크롤링 봇의 확산으로 주요 여행 플랫폼이 데이터 무단 유출 방지를 강화하고 있다는 점을 사전에 충분히 검토하지 못한 것이 원인이었습니다.
선택
검색 엔진으로 키워드 기반 수집을 하고, 그 결과에서 장소 이름을 추출해 추가 웹 검색으로 장소 객체를 만드는 방식으로 전환했습니다.
받아들인 Trade-off

단순 LLM 웹 검색 기반 에이전트의 정보 정확도 40% 대비, 검색 프로세스 도입 후 58%. 약 20초의 실행 시간 증가정확도 18%p 향상을 맞바꿨습니다. 여행 일정에서는 틀린 정보가 느린 응답보다 나쁘다고 판단했습니다.

근본 해결책은 지도 서비스·정부 오픈 API 등 신뢰할 수 있는 플랫폼의 공식 API 연동 또는 자체 수집 파이프라인 구축입니다. 그 경우 58%보다 더 올릴 수 있다고 보고 있습니다.

DynamoDB 배치 쓰기 한도 초과

문제
단위 테스트에서는 나오지 않던 문제였습니다. 실제 서비스 형태의 통합 테스트에서 채팅 기록이 DynamoDB 배치 쓰기 한도인 25건을 넘으면 저장에 실패했습니다. 단위 테스트와 통합 테스트의 데이터 규모 차이를 미리 고려하지 못한 것이 원인이었습니다.
선택
DB 저장 시점을 그래프 종료 시점으로 일원화하고, SQS를 도입해 채팅 기록을 최대 25건 단위로 분할해 큐에 전달한 뒤 람다가 순차 처리하도록 구성했습니다. 쓰기에 실패해도 메시지가 큐에 남아 데이터가 누락되지 않습니다.

에이전트 그래프 실행 시간

문제
초기에는 메인 관리자 노드가 작업 계획과 각 노드의 지시 사항을 생성해 위임하는 단일 그래프였습니다. 2박 3일 일정 생성에 평균 약 3분이 걸려 기대 수준을 넘었습니다.
분석
LangSmith와 자체 구축한 그래프 state 스냅샷 추적으로 병목을 짚었습니다. 실제 흐름은 “데이터 수집 → 일정 생성 → 포매팅” 순이었고, 수집 단계의 API 호출이 동기로 이루어져 idle 시간이 대부분을 차지하고 있었습니다.
선택
두 가지를 병행했습니다. 단일 그래프를 서브그래프 계층 구조로 전환해 관리자 노드의 지시 생성 과정을 없애 추론 시간을 줄였고, 수집 단계의 API 호출을 동기에서 비동기로 바꿔 날씨·음식점·카페·숙소·엔터테인먼트·교통 정보를 병렬로 모았습니다.
결과
수집 시간 60% 이상 단축, 일정 생성 평균 2분 23초(약 30초 단축).

섣부른 라이브러리 도입 — 5주치를 다시 썼습니다

실패 기록
경위
일정을 JSON으로 출력해 프론트엔드에서 인터랙티브하게 수정하는 경험을 주려 했습니다. 답변 생성 중 빈 화면이 노출되는 UX 문제를 풀려고 라이브 스트리밍을 지원하는 라이브러리를 도입했는데, 그 라이브러리가 JSON 형식의 최종 출력을 지원하지 않아 기존 설계와 정면으로 충돌했습니다. 기존 설계와 신규 라이브러리의 호환성을 충분히 검토하지 않고 도입을 결정한 것이 원인이었습니다.
대가
5주치 작업량을 재작성했고, 스트리밍은 살렸지만 인터랙티브한 일정 수정 UX는 구현하지 못한 채 마무리했습니다.
이후 기준으로 삼은 것

새 기술을 도입하기 전에 기존 데이터 흐름과의 호환성을 먼저 검증한다. 이 기준은 이후 작업에서 계속 지키고 있습니다 — 라이브러리를 “좋아 보여서” 넣지 않고, 우리 출력 계약을 만족시키는지부터 확인합니다.

향후에는 해당 라이브러리를 걷어내고 LangChain/LangGraph가 지원하는 기능만으로 메시지 스트리밍과 JSON 출력을 동시에 처리하는 구조로 개선할 계획입니다.

Data Model

DynamoDB 스키마

테이블 4종 전체 보기
테이블주요 속성
sessionssessionID (PK) title(AI로 요약한 채팅 세션 제목) · createdAt · updatedAt
chatschatID (PK)
sessionID (SK)
content · role (USER | AI) · timestamp
itineraryitineraryID (PK)
userID (SK)
sessionID · content (md 형식의 일정 계획) · timestamp
usersemail (PK) username · createdAt · updatedAt

AI 서비스 특성상 변경 사항이 잦아, 스키마 변경 없이 유연하게 대응할 수 있는 DynamoDB를 골랐습니다. 관리형 서비스라 오토스케일링에 이점이 있고 프리티어 혜택으로 유지 비용도 낮았습니다.