Skip to content

Hoya324/search-tuner

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Korean Search Tuner (KST)

LLM을 활용해 Elasticsearch 동의어 사전을 자동 생성하고, IR 지표로 검색 품질을 정량 측정하는 도구

Build


빠른 시작

git clone https://github.com/hoya324/search-tuner
cd search-tuner

# 1. 환경 변수 설정
cp .env.example .env
# .env 에서 LLM API Key 설정 (아래 LLM 제공자 섹션 참고)

# 2. 전체 스택 실행 (프론트엔드 + 백엔드 + MySQL + Elasticsearch)
docker compose -f docker-compose.local.yml up --build -d

# 3. 접속 확인
open http://localhost:3000          # 관리 UI
open http://localhost:8080/swagger-ui.html  # REST API 문서
open http://localhost:5601          # Kibana (ES 인덱스 탐색)

LLM 제공자

.env에서 원하는 제공자의 키와 모델을 설정합니다. 여러 키가 설정된 경우 우선순위가 높은 것이 사용됩니다.

우선순위 제공자 API Key 환경변수 모델 환경변수 기본 모델
3 Anthropic Claude ANTHROPIC_API_KEY ANTHROPIC_MODEL claude-3-5-haiku-20241022
2 Google Gemini GEMINI_API_KEY GEMINI_MODEL gemini-2.5-flash-lite
1 OpenAI OPENAI_API_KEY OPENAI_MODEL gpt-4o-mini
# .env 예시 (하나만 설정하면 됨)
GEMINI_API_KEY=your_key_here
GEMINI_MODEL=gemini-2.5-pro        # 선택 사항, 기본값 사용 가능

# OPENAI_API_KEY=your_key_here
# OPENAI_MODEL=gpt-4o

# ANTHROPIC_API_KEY=your_key_here
# ANTHROPIC_MODEL=claude-3-5-sonnet-20241022

앱 시작 시 선택된 제공자가 로그에 출력됩니다:

LLM provider selected: Gemini

새 제공자 추가는 search-tuner-infra-llm-{provider} 모듈을 추가하고 LlmProviderStrategy를 구현하면 됩니다.


해결하고 싶은 문제

한국 이커머스 검색의 두 가지 핵심 문제를 해결합니다.

문제 1 — "검색이 좋아졌다"를 측정할 수 없다

동의어 사전을 적용하면 정말 나아지는가? 얼마나? 이 프로젝트는 IR 지표(nDCG, P@K, MRR)와 paired t-test로 개선을 수치화합니다.

동의어 적용 전후 비교 예시:
nDCG@10: 0.72 → 0.85  (+17.7%)
P@5:     0.68 → 0.81  (+19.1%)
MRR:     0.74 → 0.88  (+18.3%)
p-value: 0.003  → 통계적으로 유의한 개선

문제 2 — 동의어 변경 시 다운타임이 발생한다

일반적인 ES 동의어 변경은 인덱스를 close → 수정 → open 해야 합니다. 이 프로젝트는 두 가지 무중단 전략을 제공합니다.

전략 방식 소요 시간 적합한 경우
Reload updateable: true + _reload_search_analyzers ~1초 동의어 파일만 변경
Blue-Green 신규 인덱스 빌드 → Alias 원자적 전환 전체 재색인 분석기/매핑 변경

주요 기능

동의어 사전 자동 생성

  • ES terms aggregation으로 상품명 상위 N개 추출
  • 500개씩 배치로 LLM에 전송 → 동의어 그룹 생성
  • 공통 term을 가진 그룹을 자동 병합 (Union-Find 방식)
  • confidence 점수(0~1)로 낮은 품질 그룹 필터링
POST /api/v1/synonyms/generate
→ [{"terms": ["나이키", "Nike", "NIKE"], "type": "EQUIVALENT", "confidence": 0.95},
   {"terms": ["캐구", "캐나다구스"], "type": "EQUIVALENT", "confidence": 0.91}]

검색 품질 평가 (IR Metrics)

100개 Golden Query Set 기반 자동 평가 파이프라인:

  • nDCG@10: 상위 10개 결과의 순위 품질 (0~1)
  • P@5: 상위 5개 중 관련 문서 비율
  • MRR: 첫 번째 관련 문서 순위의 역수 평균
  • Paired t-test: A/B 차이의 통계적 유의성 검증 (p < 0.05)
POST /api/v1/evaluation/compare
→ {"ndcgDelta": "+17.7%", "pValue": 0.003, "isSignificant": true}

Nori 분석기 추천

샘플 텍스트를 3가지 decompound 모드(none / discard / mixed)로 토크나이징한 결과를 LLM에게 보여주고 최적 설정을 추천받습니다.

POST /api/v1/analyzers/recommend
→ {"recommendation": "mixed", "reasoning": "복합명사 원형 보존으로 정밀도·재현율 모두 확보"}

아키텍처

헥사고날 아키텍처 (Ports & Adapters)

의존성은 항상 바깥 → 안쪽으로만 흐릅니다. search-tuner-core는 Spring, JPA, Elasticsearch를 전혀 모릅니다.

[REST Controller]  →  [Input Port]  →  [Application Service]  →  [Output Port]
  (infra-api)           (core)               (core)                  (core)
                                                                        ↑
                                             [ES Adapter] ─────────────┤
                                             [LLM Adapter] ────────────┤
                                             [JPA Adapter] ────────────┘

멀티모듈 구조

search-tuner
├── search-tuner-core                  # 순수 Kotlin 도메인 (Spring 의존성 없음)
│   ├── domain/                        # Product, SynonymSet, EvaluationResult ...
│   ├── port/in/                       # UseCase 인터페이스
│   ├── port/out/                      # LlmPort, ElasticsearchPort ...
│   └── service/metric/               # IrMetricCalculator (nDCG, P@K, MRR, t-test)
│
├── search-tuner-infra-llm             # LLM 공통 (인터페이스 + 어댑터 + 프롬프트)
│   └── provider/LlmProviderStrategy   # 전략 인터페이스 (buildChatClient())
│
├── search-tuner-infra-llm-gemini      # Gemini 전략 (priority=2, GEMINI_API_KEY)
├── search-tuner-infra-llm-openai      # OpenAI 전략 (priority=1, OPENAI_API_KEY)
├── search-tuner-infra-llm-claude      # Claude 전략 (priority=3, ANTHROPIC_API_KEY)
│
├── search-tuner-infra-es              # Elasticsearch Java Client 8.x 어댑터
├── search-tuner-infra-persistence     # Spring Data JPA + MySQL 어댑터
└── search-tuner-api                   # Spring Boot 진입점, REST 컨트롤러

LLM 제공자 선택 흐름:

앱 시작
  → Spring이 classpath에서 LlmProviderStrategy @Component 수집
  → LlmConfig: isAvailable()=true인 것 중 priority 최고값 선택
  → 선택된 전략의 buildChatClient() 호출 → ChatClient Bean 등록

주요 설계 결정

결정 이유
@Async 대신 Thread { }.start() Spring AOP는 같은 클래스 내부 호출 시 프록시를 거치지 않아 @Async 무효화
search-time 동의어 (index-time 아님) updateable: true로 재색인 없이 _reload_search_analyzers로 즉시 적용 가능
LLM reasoning 필드 강제 Chain-of-Thought 효과 → 판단 근거를 쓰게 해서 품질 향상
confidence threshold 0.7 억지 동의어 차단, 틀린 동의어가 검색 정밀도를 떨어뜨리는 것 방지
동의어 그룹 병합 알고리즘 LLM 배치별로 생성된 그룹이 공통 term을 공유할 수 있어 Union-Find 방식으로 병합
전략 패턴 + 모듈 분리 새 LLM 제공자 추가 = 새 모듈 하나만 추가, 기존 코드 무변경

API 목록

Method Endpoint 설명
POST /api/v1/index/full 전체 상품 색인 (비동기, jobId 반환)
GET /api/v1/index/jobs/{jobId} 색인 진행 상황 조회
GET /api/v1/products/search 상품 검색
POST /api/v1/synonyms/generate LLM으로 동의어 생성
GET /api/v1/synonyms 동의어 세트 목록
POST /api/v1/synonyms/{id}/apply ES에 동의어 적용 (RELOAD / BLUE_GREEN)
GET /api/v1/synonyms/{id}/download 동의어 파일 다운로드
POST /api/v1/analyzers/recommend Nori 분석기 설정 추천
POST /api/v1/evaluation/run 검색 품질 평가 실행
GET /api/v1/evaluation/results 평가 결과 목록
POST /api/v1/evaluation/compare A/B 비교 + 통계 검증
  • Swagger UI: http://localhost:8080/swagger-ui.html

로컬 개발 (IDE 디버깅 / 핫리로드가 필요한 경우)

# 인프라만 실행
docker compose -f docker-compose.local.yml up mysql elasticsearch kibana -d

# 백엔드 실행 (Java 21 필요)
./gradlew :search-tuner-api:bootRun

# 프론트엔드 실행 (별도 터미널, Node 20 + pnpm 필요)
cd search-tuner-frontend
pnpm install
pnpm dev   # http://localhost:3000

.env 파일이 프로젝트 루트에 있으면 앱 시작 시 자동 로드됩니다.

./gradlew build -x test          # 전체 빌드
./gradlew :search-tuner-core:test  # IR 지표 계산 단위 테스트

기술 스택

항목 버전
Kotlin 2.1.20
Spring Boot 3.4.3
Spring AI 1.0.0 GA
Elasticsearch 8.17 + Nori 형태소 분석기
Kibana 8.17
MySQL 8.0
Java 21

문서

문서 설명
Architecture 헥사고날 아키텍처, 멀티모듈 구조, 주요 기술 결정
PRD 프로젝트 요구사항, 시스템 설계, DB/ES 스키마, API 설계
Technical Solutions 검색 품질 평가 파이프라인(3-Layer), 무중단 동의어 업데이트 전략
Problem Solve LLM 동의어 품질 문제, 프롬프트 설계, 비용 추정, 위험 요소

학습 노트 (docs/study/)

문서 설명
01 - Setup ES + Nori 환경 구성, Docker 설정
02 - Search Basics ES 기본 검색, BM25, 쿼리 DSL
03 - Synonym Experiments 동의어 사전 실험 기록
04 - Analyzer Experiments Nori 분析器 설정 실험 기록
05 - Evaluation Experiments IR 지표(nDCG, P@K, MRR) 실험 기록
06 - LLM Prompt Tuning LLM 프롬프트 튜닝 실험 기록

About

LLM을 활용해 Elasticsearch 동의어 사전을 자동 생성하고, IR 지표로 검색 품질을 정량 측정하는 도구

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors