LLM을 활용한 맛집 추천

Doyeon Kim·2025년 9월 18일

1) 추천 파이프라인에서 LLM이 잘하는 일들

  1. 후처리·재랭킹(Reranking)
  • 후보 생성은 전통적으로: 위치/카테고리/가격/영업시간/인기지표 + 벡터 검색(임베딩)으로 50~200개 뽑기.
  • LLM은 이 후보를 사용자 의도·제약(예: 강남, 2만원대, 조용함) 에 맞춰 상위 10~20개로 정교하게 재정렬.
  • 프롬프트 팁: 각 후보를 name, score(기본랭킹), distance, price, tags, review_snippets로 넣고, JSON으로 상위 N개와 이유만 출력시키기.
  1. 취향 파악 대화(Preference Elicitation)
  • 첫 질문에 “매운 거/조용한 자리/주차/채식/술 종류/데이트/회식/가성비 vs 가심비” 등 중요 축을 2~3턴 내로 뽑아내기.
  • LLM이 답변을 구조화된 프로필(JSON) 로 축적 → 이후 랭커의 가중치로 사용.
  1. 리뷰 요약·증거 생성(Explainability)
  • 수천 개 리뷰를 LLM이 주제별 요약(맛/서비스/소음/대기/추천 메뉴/주의점) 으로 3~5줄로 압축.
  • 카드 UI에 “여기가 네 취향에 맞는 이유”를 리뷰 출처 스니펫과 함께 제시 → 전환율↑, 이탈↓.
  1. 정보 추출(IE) & 정규화
  • 크롤링/UGC에서 메뉴·가격·시그니처·영업시간·휴무·예약/대기 정책을 추출하고 누락 필드 채우기.
  • 상호명 변형(영문/한글/별칭) 엔티티 해소(디듀플), 지점 매칭에 LLM+규칙 혼합.
  1. 쿼리 이해 & 자연어 → 필터 변환
  • “강남에서 2만원대 파스타, 조용한 곳” → district:강남, category:파스타, price_max:20000, ambience:quiet함수호출(Function Calling).
  • 오타/혼동(‘마라탕’ vs ‘훠궈’) 교정, 시나리오 의도(데이트/회식/혼밥) 추론.
  1. 콘시어지 대화 & 일정/동선 설계
  • “토요일 6시에 친구 두 명, 비건 1명” → 후보 제안 → 지도·예약 API 호출 → 최종 코스/동선 제안.
  • 여러 식당을 테마별(늦게까지/반려견/루프탑) 로 묶는 플랜 생성.
  1. 품질·안전
  • 리뷰 불법/혐오/명예훼손 모더레이션(LLM 필터).
  • 영업시간/주소 변동 등 불확실성 문구 처리: “최근 정보 업데이트: 2025-08 기준, 방문 전 확인 권장”.

2) 아키텍처 한눈에

  • 오프라인

    • 크롤링/제휴 데이터 → 정제
    • 임베딩 생성(식당 프로필/메뉴/리뷰) & 인덱싱(Vector DB)
    • LLM로 리뷰 요약/키워드 추출/속성 채우기(배치)
  • 온라인

    1. 사용자 쿼리 → 의도/슬롯 추출(LLM 함수호출)
    2. 후보 생성: BM25 + 벡터 검색 + 지리/가용성 필터
    3. LLM 재랭킹(예산·분위기·동반자·거리·다양성 보장)
    4. 설명 생성: 요약/리뷰 근거/대안 2~3곳
    5. 예약/지도/대기 현황 툴 호출 → 최종 응답

비용/지연 최소화: 재랭킹은 32~64개만 LLM에 투입, 나머진 전통 랭커로 컷.


3) 바로 쓸 수 있는 프롬프트/스키마 예시

(A) 쿼리 이해 + 함수호출

System: 너는 맛집 추천 라우터야. 사용자의 자연어를 구조화해.
User: 강남역 근처 조용하고 파스타 맛있는 곳, 2만원대, 토요일 저녁 7시
Assistant (JSON only):
{
 "location": "강남역",
 "category": ["파스타","이탈리안"],
 "price_max": 20000,
 "ambience": ["조용함"],
 "datetime": "2025-09-20T19:00:00+09:00",
 "party_size": 2,
 "must_have": ["맛 좋은 파스타"]
}

(B) 재랭킹

System: 너는 레스토랑 재랭커야. Top N JSON만 반환.
Input:
- user_profile: {spice:"low", ambience:"quiet", price:"mid", wine:true}
- constraints: {area:"강남", budget_max:20000, time:"Sat 19:00"}
- candidates: [
 {"id":"r1","name":"A","base":0.82,"dist":0.7km,"price":$$,"tags":["파스타","와인","조용"],"reviews":[...snips...]},
 ...
]
Task: 제약을 만족하고, 유사 식당끼리 다양성을 확보하도록 상위 10개를 점수와 간단 사유로.
Output(JSON):
[{"id":"r7","score":0.91,"reason":"조용, 파스타 호평, 1.1km"}, ...]

(C) 리뷰 요약

System: 리뷰를 맛/서비스/분위기/대기/추천메뉴/주의점으로 6줄 이내 요약. 각 항목에 증거 스니펫 id 포함.

(D) 데이터 스키마(요지)

  • Restaurant{id, name, lat, lng, district, price_band, categories[], ambience[], features[], open_hours, reservable, rating_agg, review_topics{taste, service, noise, wait}}
  • MenuItem{id, rest_id, name, price, tags[]}
  • Embedding{rest_id, type: ["profile","review","menu"], vector}
  • UserProfile{id, hard_constraints, soft_prefs, dietary, history[], embedding}

4) 추천 품질을 끌어올리는 운영 팁

  • 다양성 제약(Diversity constraints): 상위 10개에 같은 체인/유사 메뉴가 몰리면 패널티.
  • 근거 제시(Evidence-first): “리뷰 128건 중 26건이 ‘조용함’을 언급” 같은 통계 + 스니펫.
  • 콜드스타트: 신상 맛집은 인근 유사점 임베딩으로 초기 점수 보정 + LLM로 메뉴/인스타 캡션 요약.
  • 피드백 루프: 클릭/전화/길찾기/예약 전환을 학습 신호로 수집 → 재랭커 보정.
  • 캐싱: 인기 쿼리(“강남 파스타”)는 LLM 출력 캐시 + 재검증 TTL.
  • 비용 관리: 긴 리뷰는 먼저 토픽별 chunk → 요약 → 재요약. 재랭킹 토큰 상한 설정.
  • 안전/법적: 휴무/가격 변동 불확실성 배지 표시, 사용자 생성 리뷰는 모더레이션 필수.

5) MVP → 고도화 로드맵

MVP (2~3주)

  • 파이프라인: 벡터 검색 + LLM 재랭킹 + 리뷰 요약
  • 쿼리 슬롯 추출(Function Calling)
  • 3~4개 핵심 시나리오 템플릿(데이트/회식/혼밥/가성비)

V1

  • 개인화(과거 클릭·저장 기반 가중치)
  • 대기 시간/예약 가능 여부 API 연동
  • 다국어 질의(한국어/영어/일본어)

V2

  • 그룹 추천(서로 다른 취향의 교집합)
  • 동선 최적화(카페/바 코스 자동 생성)
  • 비용 절감: 재랭커를 소형 모델(혹은 전용 reranker)로 교체, LLM은 설명/예외 처리에 집중

6) 품질 측정 지표(실전)

  • P\@3 / nDCG\@10 (오프라인: 편집자 레이블 셋)
  • CTR, 저장률, 전화/예약 전환, 이탈률, dwell time
  • 대화 길이 vs 성공률(몇 턴 내 해결?)
  • 불만 리포트율(휴무/정보오류)

7) 샘플 툴 스펙(의사코드)

// Function Calling 예시
type SearchParams = {
  area?: string; lat?: number; lng?: number; radius_km?: number;
  category?: string[]; price_max?: number; ambience?: string[];
  dietary?: string[]; open_at?: string; party_size?: number;
};

LLM이 위 타입으로 호출 → 백엔드가

  1. 필터링 + 벡터 후보 생성
  2. LLM 재랭킹 호출
  3. 결과 + 리뷰요약 + 지도/예약 링크 반환

0개의 댓글