| 기법 | 적용 시점 | 해결하는 문제 |
|---|---|---|
| HyDE (Hypothetical Document Embeddings) | 검색 시 (쿼리 변환) | 질문 ↔ 후기 문체 불일치 |
| Contextual Retrieval | 저장 시 (임베딩 생성) | 짧은 후기의 문맥 부재 |
임베딩 모델은 같은 분포의 텍스트끼리 가깝게 학습된다. 의문문과 평서문은 분포가 다르기 때문에, 아무리 의미가 비슷해도 벡터 공간에서 멀어진다.
HyDE의 핵심 아이디어는 단순하다.
"질문을 검색하지 말고, 질문의 답처럼 생긴 문장을 검색하라"
LLM에게 질문을 주고 실제 후기처럼 생긴 가상 문서(Hypothetical Document) 를 생성하게 한 뒤, 그 텍스트로 pgvector를 검색한다. 가상 문서가 설령 사실과 다르더라도, 임베딩 공간에서 실제 후기에 가까운 위치에 있는 것이 핵심이다.
[기존 방식]
질문(의문문) ──임베딩──→ 벡터 ──코사인 유사도──→ 후기 벡터들
문체 불일치로 거리 발생 ↑
[HyDE 적용]
질문 ──LLM──→ 가상 후기(평서문) ──임베딩──→ 벡터 ──코사인 유사도──→ 후기 벡터들
문체 일치, 거리 감소 ↑
초기 버전에서는 긍정/부정 방향 구분 없이 가상 후기를 생성했다. "배송이 오래 걸리나요?" 라고 물어봐도 LLM이 긍정 후기를 생성하는 경우가 있었고, 실제로 방향성 개선 전 부정 쿼리에서 Precision@3이 0.83 → 0.58로 하락한 이력이 있다.
이를 해결하기 위해 프롬프트에 쿼리의 방향성(긍정/부정)을 명시적으로 감지하고 반영하도록 개선했다.
당신은 쇼핑몰 구매 후기 작성자입니다.
아래 질문에 해당하는 실제 구매 후기를 3개 작성하세요.
규칙:
- 질문이 긍정적 경험을 묻는다면 → 긍정적 후기 작성 (예: "배송 빠름", "포장 꼼꼼")
- 질문이 부정적 경험을 묻는다면 → 부정적 후기 작성 (예: "배송 너무 느림", "일주일 걸림")
- 각 후기는 실제 고객이 쓴 것처럼 자연스럽게, 10~30자 이내로
- 후기만 작성하고 번호나 설명은 붙이지 마세요
public List<String> search(Long sellerId, String query) {
String searchQuery = generateHypotheticalReview(query);
boolean isNegative = NEGATIVE_KEYWORDS.stream().anyMatch(query::contains);
boolean isPositive = !isNegative && POSITIVE_KEYWORDS.stream().anyMatch(query::contains);
String scoreFilter = isNegative ? " AND score <= 2" : isPositive ? " AND score >= 4" : "";
List<Document> documents = vectorStore.similaritySearch(
SearchRequest.builder()
.query(searchQuery)
.topK(TOP_K)
.similarityThreshold(SIMILARITY_THRESHOLD)
.filterExpression("sellerId == " + sellerId + scoreFilter)
.build()
);
return documents.stream()
.map(Document::getText)
.toList();
}