Skip to content

pearseona/Back

 
 

Repository files navigation

RRF Search - 한국어 하이브리드 검색 시스템

PMI RAG (Retrieval-Augmented Generation) 시스템으로, BM25와 Vector Search를 결합한 하이브리드 검색을 제공합니다.

주요 기능

  • 하이브리드 검색: BM25 with Kiwi 형태소 분석기 + Vector Similarity Search(cosin 유사도 비교)
  • RRF (Reciprocal Rank Fusion): 두 검색 결과를 효과적으로 통합
  • 한국어 최적화: Kiwi 형태소 분석기 기반 전처리
  • LLM 기반 필터링: Claude Sonnet을 사용한 쿼리 전처리 및 결과 필터링
  • 고성능: 인덱스 캐싱으로 빠른 검색 속도

시스템 아키텍처

사용자 쿼리
    │
    ▼
┌──────────────────────────────────┐
│   Claude Sonnet 쿼리 전처리       │
│   (오타 수정, 불필요한 단어 제거)  │
└──────────┬───────────────────────┘
           │
    ┌──────┴──────┐
    │             │
    ▼             ▼
┌─────────┐  ┌─────────────┐
│   BM25  │  │   Vector    │
│ Search  │  │   Search    │
│(형태소)  │  │ (KURE-v1)   │
└────┬────┘  └──────┬──────┘
     │              │
     └──────┬───────┘
            │
            ▼
    ┌───────────────┐
    │  RRF 융합     │
    │  (k=60)       │
    └───────┬───────┘
            │
            ▼
    ┌───────────────┐
    │ Claude Sonnet │
    │  필터링 & 랭킹 │
    └───────┬───────┘
            │
            ▼
       최종 결과

자연어 기반 패널 검색 시스템 - 캡스톤 발표 자료

시스템 개요

이 시스템은 하이브리드 검색(BM25 + Vector Search) + LLM 기반 필터링을 결합한 고도화된 패널 검색 엔진입니다. 사용자가 자연어로 질문하면 AI가 최적의 패널을 찾아주는 RAG(Retrieval-Augmented Generation) 기반 시스템입니다.skax+1


주요 흐름 (Pipeline)

1단계: 쿼리 전처리 (Claude Sonnet API)

  • 사용자 입력 → LLM이 검색에 최적화된 쿼리로 정제
  • 결과 개수 추출 (예: "20명" → result_count=20)
  • 출생년도, 거주지역, 성별 키워드 추출

2단계: 병렬 검색 (멀티스레드)

BM25 검색 (키워드 기반)

  • 한국어 형태소 분석 → 토큰화 → BM25 점수 계산
  • 캐시된 인덱스 파일(bm25_index.pkl) 사용

Vector 검색 (의미 기반)

  • 쿼리 임베딩 벡터화 → PostgreSQL pgvector로 코사인 유사도 검색
  • 동시 실행으로 검색 속도 최적화

3단계: RRF 융합 (Reciprocal Rank Fusion)

pythonrrf_score = 1/(k + bm25_rank) + 1/(k + vector_rank)

  • 두 검색 결과를 통합하여 더 정확한 순위 생성
  • k=60 (하이퍼파라미터)

4단계: 도메인 필터링

  • 쿼리에서 "OTT", "음용경험" 등 도메인 키워드 감지
  • JSON 필드의 정확한 키 매칭으로 관련 패널만 추출

5단계: LLM 최종 필터링 (조건부)

  • result_count ≤ 100: Claude Sonnet이 각 패널의 적합도 판단
  • result_count > 100: RRF 결과 직접 반환 (속도 우선)

각 단계별 상세 과정

전처리 파이프라인 (db_search.py)

A. 텍스트 전처리 (preprocess_text)

  1. 정규화: 특수문자/이모티콘 제거, ㅋㅋㅋ 제거
  2. 형태소 분석: Kiwi로 한국어 토큰화
  3. 품사 필터링: 명사(NN*), 동사(VV), 형용사(VA)만 추출
  4. 불용어 제거: "이", "가", "을", "를" 등 제거
  5. 단일 문자 제거: 의미 없는 한글자 단어 제거

B. 사용자 사전 강화

pythoncore_keywords = ['OTT', 'OTT구독', '음용경험', '음주경험'] kiwi.add_user_word(keyword, 'NNG', 1.0)

  • 도메인 특화 복합명사를 단일 토큰으로 인식

C. 멀티 필터링 시스템

  • 출생년도 필터: JSON 필드 매칭 (info_text::jsonb->>'출생년도')
  • 거주지역 필터: 17개 광역시도 OR 조건 검색
  • 성별 필터: "남자"/"여자" 정규화 후 적용

사용한 주요 라이브러리

검색 엔진

라이브러리 용도 버전 권장
rank-bm25 BM25 알고리즘 구현 최신
pgvector PostgreSQL 벡터 검색 0.2.0+
psycopg2 PostgreSQL 연결 2.9+

자연어 처리

라이브러리 용도 특징
kiwipiepy 한국어 형태소 분석 빠르고 정확, C++ 기반
numpy 벡터 연산 임베딩 처리

웹 서버 & AI

라이브러리 용도 비고
FastAPI REST API 서버 비동기 지원
Claude Sonnet API LLM 쿼리 전처리/필터링 Anthropic
pydantic 입력 검증 FastAPI 내장

멀티스레딩

pythonThreadPoolExecutor *# Python 표준 라이브러리*

  • BM25 + Vector 검색 병렬 실행으로 검색 속도 2배 향상

비교군 라이브러리 (대안)

형태소 분석기

라이브러리 장점 단점
Kiwi ⭐ (채택) 매우 빠름, 정확도 높음, 한국어에 강점, 사용하기 좋음 -
Mecab-ko 오픈소스, 커뮤니티 큼 설치 복잡
KoNLPy (Okt) 사용 간편 느림, 정확도 낮음

벡터 데이터베이스

솔루션 장점 단점
pgvector ⭐ (채택) PostgreSQL 통합, 안정적,
현재 데이터 규모에서 적합함 대규모에서 느림
Pinecone 관리형, 확장성 좋음 비용 높음
FAISS 매우 빠름 별도 인프라 필요
Milvus 오픈소스, 확장성 복잡한 설정

BM25 구현

라이브러리 선택 이유
rank-bm25 ⭐ (채택) 간단, 캐싱 지원,
현재 데이터 규모에서 적합함
Elasticsearch 프로덕션 급,
메모리의 데이터가 1KB ~ 1GB 이상.

키워드 필터링을 통해 보완

성별, 거주지역, 출생년도

LLM 모델

모델 장점 단점
Claude Sonnet ⭐ (채택) 추론 능력 우수 API 비용
GPT-4 범용성 높음 비용 높음
오픈소스 LLM (Llama) 무료 정확도 낮음, 인프라 필요

예상 질문 5개

Q1. BM25와 Vector 검색을 왜 둘 다 사용하나요?

A: 각각의 장단점을 보완하기 위해서입니다.

  • BM25: 정확한 키워드 매칭에 강함 (예: "OTT 구독자" → "OTT" 단어 포함 문서)
  • Vector: 의미적 유사도 파악 (예: "넷플릭스 이용자" ≈ "OTT 구독자")
  • RRF 융합으로 두 방식의 장점을 결합하여 정확도 향상skax

Q2. RRF의 k=60 값은 어떻게 결정했나요?

A: RRF 상수 k는 순위의 영향력을 조절하는 하이퍼파라미터입니다.

  • k가 작을수록: 상위 랭크의 영향력 증가 (극단적 선호)
  • k가 클수록: 전체 순위를 고르게 고려 (안정적)
  • k=60은 정보 검색 분야의 경험적 기본값으로, 실험을 통해 최적화 가능합니다

Q3. LLM 필터링을 왜 조건부로 수행하나요?

A: 비용과 속도의 트레이드오프 때문입니다.

  • result_count > 100: 대량 결과가 필요한 경우 → LLM 호출 비용/시간이 과도하게 증가
  • result_count ≤ 100: 소량 정밀 검색 → LLM으로 각 패널의 적합도를 세밀하게 평가
  • 실제 사용 시 95%는 100명 이하 요청이므로 대부분 LLM 필터링 적용

Q4. 형태소 분석이 검색 품질에 왜 중요한가요?

A: 한국어의 교착어 특성 때문입니다.mainlineit+1

  • "음용경험이" → [음용경험(명사), 이(조사)] 분리
  • "OTT를" → [OTT(명사), 를(조사)] 분리
  • 불용어(조사) 제거 후 핵심 키워드만 추출하여 검색 정확도 향상
  • 사용자 사전으로 "OTT구독"을 단일 토큰으로 인식하여 복합명사 검색 강화

Q5. 실제 성능은 어느 정도인가요?

A: 코드의 로그 구조로 측정 가능합니다.

  • 검색 속도: (36,000개 문서 기준)
    • 101개 이상 검색시 약 8-10초 , 최종적으로 llm_filter를 거치지 않고, RRF순위로 반환
    • 100개 이하 검색시 약 20-60초,
  • 정확도: RRF 상위 30개 중 LLM이 최종 선택 → 높은 정밀도
  • 확장성: BM25 인덱스 캐싱 + 배치 처리로 10만 개 이상 문서도 처리 가능
  • 메모리: 배치 크기 조절로 최적화 (기본 5,000개/배치)

Q6. 검색속도는 얼마나

A: 데이터 크기에 따라 다릅니다. 100개

101개 이상이면 8~9초,


시스템 아키텍처 요약

`text사용자 쿼리 입력

[Claude Sonnet] 쿼리 정제 + 필터 추출 ↓ ┌─────────────────┴─────────────────┐ │ [BM25 검색] [Vector 검색] │ (병렬) │ - Kiwi 형태소 분석 - 쿼리 임베딩 │ │ - 캐시 인덱스 사용 - pgvector 검색 cosin └─────────────────┬─────────────────┘`

[키워드 필터링] (조출생년도, 성별, 거주지역, 도메인 필터링) 연관성 향상

`↓

[RRF 융합] 두 검색 결과 통합 ↓ [LLM 필터링] (조건부 result_count <= 100) 최종 적합도 판단 ↓ JSON 결과 반환`

이 시스템은 키워드 매칭의 정확성 + 의미 검색의 유연성 + LLM의 추론 능력을 결합한 패널 검색 솔루션입니다.

검색 방법 비교

방법 기술 장점 단점
FTS (현재 사용) Python BM25 + Kiwi 한국어 최적화, 형태소 분석, 빠름 인덱스 재구축 필요
Vector Search KURE-v1 embeddings 의미론적 유사도, 동의어 처리 짧은 쿼리에 약함
RRF 랭크 융합 두 방법의 장점 결합 -

한국어 전처리 과정

preprocess_text() 함수가 수행하는 단계:

  1. 정규화: 특수문자, 이모티콘 제거

    "운동 좋아하구!! ㅋㅋㅋ ♥" → "운동 좋아하구"
    
  2. 형태소 분석: Kiwi 사용

    "운동 좋아하는" → [('운동', 'NNG'), ('좋아하', 'VV'), ('는', 'ETM')]
    
  3. 품사 필터링: 명사, 동사, 형용사만 추출

    [('운동', 'NNG'), ('좋아하', 'VV')] → ['운동', '좋아하']
    
  4. 불용어 제거: 조사, 접속사 등 제거

    ['이', '운동', '을', '좋아하'] → ['운동', '좋아하']
    
  5. 단일 문자 제거: 의미 없는 한글자 제거

    ['운', '동', '좋아하'] → ['좋아하']
    

API 엔드포인트

POST /ask

하이브리드 검색 수행

요청:

{
  "query": "운동 좋아하는 20대 여성",
  "count": 300
}

응답:

{
  "original_query": "운동 좋아하는 20대 여성",
  "clean_query": "운동 좋아하는 20대 여성",
  "result": [
    {
      "id": "uuid-1234",
      "info_text": {...}
    }
  ],
  "metrics": {
    "total_time": "2.34s",
    "final_count": 10
  }
}

GET /

헬스 체크

응답:

{
  "message": "RRF Search API with Sonnet running"
}

인덱스 업데이트

데이터베이스에 새 문서가 추가되거나 변경된 경우:

# 기존 인덱스 백업 (선택)
mv bm25_index.pkl bm25_index.pkl.backup

# 새 인덱스 구축
python build_index.py

# 서버 재시작 (FastAPI reload 모드라면 자동)

자동 업데이트 (Cron)

# crontab -e
# 매일 새벽 3시에 인덱스 재구축
0 3 * * * cd /path/to/RRF_Search && python build_index.py

성능 최적화

검색 속도

문서 수 인덱스 로드 검색 시간 총 응답 시간
1만 ~200ms ~20ms ~2-3초
5만 ~1s ~50ms ~2-3초
10만 ~2s ~100ms ~3-4초

총 응답 시간에는 LLM 호출 시간(~1-2초) 포함

메모리 사용

문서 수 인덱스 크기 메모리 사용
1만 ~30 MB ~200 MB
5만 ~150 MB ~800 MB
10만 ~300 MB ~1.5 GB

테스트

1. BM25 검색 테스트

python test_bm25.py

다음을 테스트합니다:

  • BM25, FTS, Vector 검색 비교
  • RRF 융합 결과
  • Sonnet 전처리 + 검색 파이프라인

2. 한국어 FTS 진단

python test_fts_korean.py

다음을 확인합니다:

  • FTS 토큰화 방식
  • 다양한 검색 방법 비교
  • pg_trgm 사용 가능 여부

문제 해결

1. 인덱스 파일이 없음

✗ BM25 인덱스 캐시 파일(bm25_index.pkl)을 찾을 수 없습니다.

해결: python build_index.py 실행

2. 패키지 미설치

ModuleNotFoundError: No module named 'kiwipiepy'

해결: pip install -r requirements.txt

3. 데이터베이스 연결 실패

✗ 데이터베이스 연결 실패: connection refused

확인:

  • .env 파일에 올바른 비밀번호 설정
  • 데이터베이스 서버 실행 중인지 확인
  • 네트워크 연결 확인

4. 메모리 부족

해결:

  • 서버 메모리 증설
  • 배치 크기 조정 (build_index.py)
  • Redis 캐시 사용 (추후 개선)

기술 스택

백엔드

  • FastAPI: REST API 프레임워크
  • PostgreSQL: 메인 데이터베이스
  • pgvector: 벡터 유사도 검색

NLP & 검색

  • Kiwi (kiwipiepy): 한국어 형태소 분석
  • rank-bm25: BM25 알고리즘 구현
  • KURE-v1: 한국어 문장 임베딩 (1024차원)

LLM

  • Claude Sonnet 4.5: 쿼리 전처리 및 결과 필터링
  • AWS Bedrock: LLM 호스팅 (ap-southeast-2)

데이터베이스

  • AWS RDS PostgreSQL: 데이터 저장소 (ap-southeast-2)

다음 단계 개선 사항

  • Redis 기반 인덱스 캐싱 (멀티프로세스 지원)
  • 증분 인덱스 업데이트 (전체 재구축 불필요)
  • 멀티프로세싱 기반 인덱스 구축 (속도 향상)
  • 검색 결과 캐싱 (동일 쿼리 재사용)
  • 로깅 및 모니터링 추가
  • A/B 테스트를 위한 검색 방법 선택 옵션

참고 자료

라이선스

이 프로젝트는 PMI 프로젝트의 일부입니다.

지원

문제가 발생하면 다음 문서를 참고하세요:

About

하이브리드 검색(BM25 + Vector Search) + LLM 기반 필터링을 결합한 고도화된 패널 검색 엔진

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages