여행 가격을 검색했는데 왜 여행 후기만 나올까?
제목 앞에서 추출하던 추가 단어를 핵심 질문에 맞는 검색 구문으로 바꾸고, 플랫폼 문서를 글감별로 중복 없이 배정한 과정을 설명합니다.
목차
들어가며
AI가 블로그 글 세 개를 작성한다고 가정해 보겠습니다.
주제는 모두 ‘홋카이도 온천 여행’이지만, 각 글에서 답해야 할 질문은 다릅니다.
- 온천 여행 패키지의 가격은 얼마일까?
- 실제 여행객들은 어떤 후기를 남겼을까?
- 예약을 취소하면 수수료는 얼마나 발생할까?
세 질문에 필요한 자료는 서로 다릅니다. 가격을 설명하려면 요금 정보가 필요하고, 취소 수수료를 설명하려면 환불 규정이 필요합니다.
그런데 기존 시스템에서는 세 글 모두 **‘홋카이도 온천 여행 패키지’**라는 동일한 검색어를 사용하고 있었습니다.
제목에서 가장 먼저 발견된 단어를 검색어에 추가하는 방식이었기 때문입니다. ‘가격’, ‘후기’, ‘취소 수수료’라는 핵심 질문은 검색에 반영되지 않았습니다.
이 문제는 제가 개발하던 AI 블로그 콘텐츠 생성 서비스 ‘위블로그’에서 발견했습니다.
위블로그는 사용자가 키워드를 입력하면 AI가 세 가지 글감 후보를 제안하고, 각 글에 필요한 자료를 검색한 뒤 목차와 본문을 생성하는 서비스입니다.
저는 이 과정에서 두 가지를 개선했습니다.
- 검색어 생성: 제목 앞의 단어를 기계적으로 선택하는 대신, AI가 글의 핵심 질문을 파악해 검색어를 생성하도록 변경했습니다.
- 출처 배정: 여러 글이 동일한 검색 결과를 공유하더라도, 같은 플랫폼 문서를 중복해서 배정하지 않도록 변경했습니다.
이번 글에서는 문제를 발견한 과정, 기존 알고리즘의 한계, LLM을 활용한 검색어 생성, 그리고 검색 자료의 중복 배정을 방지한 구현 과정을 살펴보겠습니다.
1. 제목과 본문으로 이어질 목차가 따로 놀았습니다
제목에서 약속한 내용을 목차가 설명하지 못했습니다
글감을 고를 때는 제목을 보고 어떤 글이 나올지 예상합니다. ‘투자 전 확인할 리스크’라는 제목이라면 어떤 위험이 있고 무엇을 확인해야 하는지 기대하게 됩니다. 그런데 목차를 열어 보면 실적이나 전망을 설명하는 데 그치는 경우가 있었습니다.
sk하이닉스 영업이익 2026에서 생성된 글감이 그런 사례였습니다.
| 씨앗키워드 | 생성된 제목 | 저장된 목차의 주요 내용 |
|---|---|---|
sk하이닉스 영업이익 2026 | sk하이닉스 영업이익 2026 투자 전 반드시 확인해야 할 5가지 리스크 체크리스트 | 분기 실적, 증권사 전망, 이익률 전망, 전망과 실제의 차이 |
목차가 씨앗키워드와 무관한 것은 아닙니다. 하지만 영업이익 실적과 전망을 설명하는 것만으로 제목에서 약속한 ‘다섯 가지 리스크’가 정리되지는 않습니다. 본문을 쓰기 전의 목차 단계부터 제목과 내용의 방향이 어긋나 있었습니다.
출처가 여러 개 있다고 해결되는 문제도 아니었습니다. 선부동 라프리모 평면도 구조와 배치 특징이라는 글감에는 출처가 네 개 있었지만, 저장된 자료는 단지 개요와 거래 정보 중심이었습니다. 목차에 들어간 구체적인 구조 설명을 뒷받침할 내용은 저장 자료에서 명확히 확인되지 않았습니다.
이런 결과가 검색어 하나 때문에 생겼다고 단정할 수는 없었습니다. 다만 제목에서 약속한 내용을 쓰려면, 먼저 그 내용에 답할 자료를 찾고 있는지 확인해야 했습니다.
씨앗에 ‘추가 단어’를 붙여 글감별로 검색했습니다
일반 검색의 검색어는 다음과 같이 구성했습니다.
일반 검색어 = 씨앗키워드의 앵커 + 글감별 추가 단어씨앗키워드는 검색할 기본 주제를 정합니다. 여기에 붙이는 ‘추가 단어’는 해당 글감에서 무엇을 더 알아볼지 검색 범위를 좁히는 역할입니다.
실제 저장 기록을 보면 같은 씨앗에서도 후보별로 다른 단어가 붙었습니다.
| 씨앗키워드 | 추가 단어 | 실제 저장 검색어 |
|---|---|---|
인천 송도 아파트 뷰 현실 | 프리미엄 가격 실제 | 인천 송도 아파트 뷰 현실 프리미엄 가격 실제 |
인천 송도 아파트 뷰 현실 | 현장 | 인천 송도 아파트 뷰 현실 현장 |
인천 송도 아파트 뷰 현실 | 실거주 후기 본 | 인천 송도 아파트 뷰 현실 실거주 후기 본 |
문제는 이 추가 단어를 고르는 방식이었습니다. 제목에서 중요한 의미를 판단한 것이 아니라, 앞에서부터 단어를 읽으며 제외 조건을 통과한 단어를 선택했습니다. 씨앗에 이미 포함된 단어나 제외어 등을 걸러내기는 했지만, 남은 단어 중 무엇이 질문의 핵심인지를 비교하지는 않았습니다.
추가 단어 수에 제한을 둔 것은 검색 범위를 지나치게 좁히지 않기 위해서였습니다. 하지만 개수만 제한해서는 적절한 검색어가 되지 않았습니다. 무엇을 남기는지가 더 중요했습니다.
2. 앞에 나온 단어가 질문의 핵심은 아니었습니다
단어 수를 줄이기 전에 선택 기준을 봐야 했습니다
씨앗 300개의 후보 A·B·C를 정리한 검색어 비교표에는 다음과 같은 저장값이 있었습니다.
| 씨앗키워드 | 추출된 추가 단어 | 실제 저장 검색어 |
|---|---|---|
광범위 피부질환 연고 | 구매 전 반드시 | 광범위 피부질환 연고 구매 전 반드시 |
인천 송도 아파트 뷰 현실 | 실거주 후기 본 | 인천 송도 아파트 뷰 현실 실거주 후기 본 |
구매 전 반드시는 문장을 이어 가는 표현입니다. 연고의 성분이나 부작용처럼 무엇을 조사해야 하는지는 알려주지 않습니다. 실거주 후기 본에는 유효한 단어와 불필요한 표현이 섞여 있습니다.
이런 상태에서 추가 단어를 세 개에서 첫 한 개로 줄이면 검색어는 짧아집니다. 하지만 앞의 단어가 핵심 질문을 나타내지 못한다면, 뒤에 있던 유효한 단어까지 사라집니다.
초기 가설은 같은 원본 입력을 끝까지 살펴보고, 첫 번째 단어 대신 구체적인 조사 대상을 나타내는 단어 하나를 선택하자는 것이었습니다. 이때까지는 ‘추가 단어를 더 잘 고르는 문제’로 접근했습니다.
‘패키지’가 선택된 이유는 중요해서가 아니라 앞에 있어서였습니다
이후 별도로 확보한 씨앗 20개·글감 60개의 현행 결과를 검토했습니다. 앞선 300개 검색어 비교와는 다른 자료입니다.
홋카이도 온천 여행의 실제 검색 기록에서는 서로 다른 세 질문에 모두 같은 추가 단어가 붙었습니다.
| 씨앗키워드 | 검색 당시 가안 제목 | 선택된 추가 단어 | 실제 일반 검색어 |
|---|---|---|---|
홋카이도 온천 여행 | 홋카이도 온천 여행 패키지 가격은 지역과 일정에 따라 어떻게 달라지나요? | 패키지 | 홋카이도 온천 여행 패키지 |
홋카이도 온천 여행 | 홋카이도 온천 여행 패키지 실제 후기에서 자주 언급되는 장점은? | 패키지 | 홋카이도 온천 여행 패키지 |
홋카이도 온천 여행 | 홋카이도 온천 여행 패키지 예약 시 취소 수수료와 환불 기준은? | 패키지 | 홋카이도 온천 여행 패키지 |
세 제목 모두 씨앗에 해당하는 표현 뒤에 패키지가 나옵니다. 당시 방식은 제목을 앞에서부터 살펴보고 추출 조건을 통과한 첫 단어 하나를 사용했기 때문에, 뒤에 있는 가격·후기·취소 수수료에 도달하기 전에 선택이 끝났습니다.
패키지가 각 질문에서 가장 중요한 단어라고 판단한 결과가 아니었습니다. 문장에 등장하는 순서가 검색 의도의 우선순위를 대신하고 있었습니다.
그런데 당시 품질표에서는 이 단어를 ‘적절’로 분류했습니다. 여행의 상품 유형을 패키지로 좁혀 준다는 기준에서는 맞는 평가였습니다. 이 결과는 추출 로직뿐 아니라 평가 기준의 한계도 보여줬습니다.
가격을 알고 싶은 글감에는 금액과 조건을 설명하는 자료가, 후기를 다루는 글감에는 실제 경험 자료가, 취소 조건을 다루는 글감에는 수수료와 환불 기준이 필요합니다. 단어가 주제와 관련 있다는 이유만으로는 세 질문을 구별할 수 없었습니다.
판단 기준을 ‘씨앗과 관련 있는 단어인가’에서 ‘씨앗과 합쳤을 때 글감이 원하는 답을 드러내는가’로 바꿨습니다. 단어 하나에 한정하기보다 핵심 질문을 짧은 검색 구문으로 표현할 필요가 있었습니다.
같은 제목에 두 방식을 적용해 비교했습니다
다음 PoC에서는 Grok이 제목을 만들 때 두 필드를 함께 생성하도록 했습니다.
core_question: 글이 답해야 할 핵심 질문 한 문장query_focus: 씨앗키워드에 붙일 짧은 검색 표현
기존의 ‘추가 단어’ 자리를 질문에 맞는 ‘검색 표현’으로 바꾼 것입니다. 한 단어일 수도 있고, 텐트 비용처럼 여러 단어로 된 짧은 구문일 수도 있습니다.
같은 씨앗 20개를 기존 방식과 개선 방식에 각각 입력하고, 씨앗당 세 후보씩 생성했습니다. 방식별로 60개 결과이며, 요청 모델은 grok-4, 실제 응답 모델은 40회 모두 grok-4.3이었습니다.
두 방식은 제목도 다르게 생성할 수 있습니다. 검색어 구성의 차이를 보기 위해 새로 생성된 제목을 고정하고, 여기에 기존 v3 알고리즘을 적용한 재현값도 비교했습니다.
| 씨앗키워드 | 동일하게 고정한 제목 | 기존 v3 추가 단어 | 새 검색 표현 |
|---|---|---|---|
야외웨딩 우천 대비 | 야외웨딩 비 올 때 텐트 대여 비용은 얼마예요? | 올 | 텐트 비용 |
야외웨딩 우천 대비 | 야외웨딩 우천 실제 후기에서 본 대비 사례 | 실제 | 실제 후기 |
아파트 욕실 방수공사 | 아파트 욕실 방수공사 순서와 각 단계별 소요 기간 | 순서 | 공사 순서 |
첫 사례의 완성 검색어는 다음과 같이 달라졌습니다.
기존 v3 재현: 야외웨딩 우천 대비 올새 방식: 야외웨딩 우천 대비 텐트 비용올은 제목 앞부분에서 추출된 표현이지만, 무엇을 찾으려는지는 알려주지 못합니다. 텐트 비용은 같은 씨앗에 붙었을 때 조사 대상이 분명해집니다. 두 번째 사례도 실제에서 실제 후기로 바뀌면서 경험 자료를 찾으려는 의도가 드러났습니다.
반면 욕실 방수공사 사례에서는 여전히 소요 기간이 빠졌습니다. 표현이 자연스러워졌다고 질문의 중요한 조건이 모두 보존되는 것은 아니었습니다. 전체 60개 중 31개에 검토 메모가 붙었지만, 의미 누락뿐 아니라 씨앗 반복과 제목 자체의 검토 사항도 포함되어 있어 이를 실패 건수로 세지는 않았습니다.
이 비교에서 확인한 것은 검색 표현의 변화입니다. 같은 제목에 기존 알고리즘을 적용한 재현값과 새 생성값을 비교했으며, 실제 검색 결과의 품질까지 검증한 것은 아닙니다.
3. 생성한 검색 의도를 저장하고 실제 검색에 연결했습니다
질문을 검색 표현으로 바꾸는 일은 LLM에 맡겼습니다
새 지시는 제목의 대상·조건·비교 상대를 보존하면서, 사람이 검색할 만한 짧은 표현을 만들도록 했습니다. 기본은 1~2어절이고 필요한 경우 최대 3어절까지 허용했습니다.
길이를 줄이기 위해 중요한 대상을 빼거나, 제목에 없는 지역·연도·조건을 추가하지 않도록 했습니다. 반대로 제목의 모든 세부사항을 검색어에 넣도록 하지는 않았습니다. 높을 때처럼 검색하려는 상황을 나타내는 자연스러운 표현도 허용했습니다.
PoC에 저장된 야외웨딩 결과의 주요 필드는 다음과 같습니다.
{ "target_keyword": "야외웨딩 우천 대비", "title": "야외웨딩 비 올 때 텐트 대여 비용은 얼마예요?", "core_question": "야외웨딩 우천 대비로 텐트 대여 비용은 얼마나 들까요?", "query_focus": "텐트 비용"}코드는 씨앗의 앵커와 query_focus를 결합합니다.
야외웨딩 우천 대비 + 텐트 비용→ 야외웨딩 우천 대비 텐트 비용core_question 전체를 검색어로 사용하는 것은 아닙니다. 핵심 질문은 의도를 명시하고 기록하는 데 사용하고, 실제 일반 검색에는 query_focus를 붙입니다.
후보 사이의 차이도 단어의 중복 여부만으로 판단하지 않도록 했습니다. 신청 자격과 신청 서류는 ‘신청’을 공유하지만 필요한 답은 다릅니다. 운영 코드에서는 공통 단어를 허용하고, 정규화한 검색 표현 전체가 동일한 경우에 대체 처리를 합니다.
제목이 바뀌어도 생성 당시의 검색 의도를 유지했습니다
생성한 질문과 검색 표현, 확정한 검색어를 함께 저장하고 일반 검색에서 그대로 사용했습니다. 이후 목차를 만들면서 카드 제목이 바뀌어도 검색어를 다시 추정하지 않도록, 생성 당시 제목과 의도를 snapshot으로 남겼습니다.
4. 검색어를 바꿔도 출처가 비슷해지는 경로가 남아 있었습니다
일반 검색 결과에는 일반 문서와 플랫폼 문서가 함께 들어옵니다. 일반 문서는 필터를 거쳐 최대 세 개를 고르고, 플랫폼 문서는 현재 검색 결과와 공유 후보 풀에서 먼저 찾습니다. 배정할 문서가 없고 보완 검색도 완료하지 않은 플랫폼만 추가로 검색합니다.

두 경로에서 선정한 문서를 합친 뒤 수집 가능한 원문을 가져와 목차 작성에 사용합니다. 원문 수집에 실패하거나 수집 대상에서 제외된 문서는 검색 요약을 유지하므로, 문서가 배정됐다는 것과 원문을 확보했다는 것은 다릅니다. 그림은 이 순서를 요약한 것이며, 재시도와 세부 필터는 생략했습니다.
플랫폼 보완 검색은 씨앗을 공유했습니다
일반 검색에서 네이버 블로그·유튜브·나무위키 자료를 얻지 못하면 별도 검색으로 보완했습니다. 이미 site:·intitle:로 범위를 좁힌 상태에서 추가 단어까지 붙이면 자료가 부족해져, 이 검색은 씨앗의 핵심 대상을 유지했습니다. 같은 씨앗의 후보들은 검색 결과를 캐시로 공유해 중복 비용을 줄였습니다.
따라서 새 검색 표현은 일반 검색에만 적용했습니다. 플랫폼 보완 검색은 기존 검색어를 유지하되, 찾은 문서를 후보들에게 배정하는 방식을 바꿨습니다.
검색을 공유하더라도 같은 문서만 줄 필요는 없었습니다
기존에는 적합한 플랫폼 문서가 여러 개여도 상위 한 개만 공유해, 서로 다른 글감이 같은 자료를 받을 수 있었습니다. 이를 플랫폼별 최대 세 개를 보관하고, 같은 사이트·생성 배치·씨앗의 후보마다 미배정 문서를 최대 한 개씩 받는 방식으로 바꿨습니다.
적합한 문서가 한 개뿐이면 한 후보만 받고, 나머지 후보의 슬롯은 비워 둡니다. 같은 문서로 출처 개수를 채우지 않습니다.
검색 깊이 10, 기존 URL·상업성·관련성 필터, 일반 문서 상한 세 개와 카드의 플랫폼별 상한 한 개는 유지했습니다. 문서 식별자를 통일하고 DB의 lease·UNIQUE 제약으로 동시 배정의 중복도 막았습니다.
배정은 추가 LLM 호출 없이 처리 순서와 중복 방지 규칙으로 동작합니다. 질문과 문서의 의미를 다시 평가하는 것은 아니므로, 가격 질문에 가격 문서가 배정된다고 보장하지는 않습니다.
마무리하며
이번 작업은 제목과 본문으로 이어질 목차가 따로 놀고, 서로 다른 글감이 비슷한 자료를 가져오는 문제에서 출발했습니다.
실제 검색어를 보니 제목 앞에서 추출한 단어가 질문의 핵심을 대신하고 있었습니다. 패키지처럼 주제와 관련 있는 단어도 가격·후기·취소라는 차이를 지울 수 있었습니다. 그래서 추가 단어의 개수나 관련성보다, 씨앗과 합쳤을 때 필요한 답이 드러나는지를 기준으로 삼았습니다.
핵심 질문과 짧은 검색 표현을 만드는 일은 Grok에 맡겼습니다. 코드는 그 의도를 저장하고 실제 일반 검색에 사용하며, 잘못된 필드를 처리하고 플랫폼 문서를 중복 없이 배정하도록 했습니다.
검색어가 질문을 더 잘 드러내는 사례와 출처 배정 구조의 변화는 확인했습니다. 그다음 확인할 것은 확보한 자료가 실제로 그 질문에 답하는지입니다. 검색어가 달라진 것과 근거가 좋아진 것을 구분하며 개선을 이어가야 합니다.
댓글