KnowledgeSystem / RAG
·
2026-01-21 23:40
의미기반검색 프로토타입 구축
완료
보통
개발
📚 Wiki
📝 업무 내용
| 시작일 | 종료 예정일(목표) | 실제 종료일(완료) |
|---|---|---|
| 2026-01-21 | 2026-01-22 | 2026-01-23 |
1. 진행 상태
- [x] 인프라 설정: PostgreSQL
pgvector확장 활성화
CREATE EXTENSION vector;
- [x] 임베딩 모델 준비: Ollama에
bge-m3모델 풀다운 및 서빙 확인 - [x] 데이터 스키마 확장:
Knowledge모델에 1024차원embedding벡터 필드 추가 - [x] 임베딩 파이프라인:
- [x]
OllamaClient에embed_text비동기 메서드 추가 - [x] 게시글 저장/수정 시 요약과 함께 임베딩 값을 생성하여 저장
- [x]
- [x] 검색 엔진 교체: 1024차원 코사인 유사도 기반 검색 로직 구현
- [x] 하이브리드 검색 구현: 키워드 검색 점수(BM25)와 벡터 검색 점수를 합산하여 최적의 결과를 내는 RRF(Reciprocal Rank Fusion) 알고리즘 적용.
2. 주요 설정
- postgreSQL Extentions:
pgvector를 활용한 vector DB 사용 - 비동기 방식 통신 설정: async-await, sync_to_async, asyncio.gather 를 이용한 비동기 방식 처리
- 의미기반검색 & RRF 사용: 하이브리드 검색 방식 선택, 장애 발생시 RRF 방식으로 진행
- CosineDistance: 코사인 유사도 거리를 이용한 방식으로 일반적인 거리만 비교하는게 아닌 검색어가 가르키는 방향도 포함하여 계산
🌳 하위 업무 (Sub-tasks)
추가
등록된 하위 업무가 없습니다.
📦 Reference Keeper
[Sample] Django Model Field Reference
https://docs.djangoproject.com/en/5.1/ref/models/fields/
💻 기술 명세
# 샘플 파일 경로 /usr/tmp/sample1.jsp\n/src/main/config/settings_sample.py
async def list_documents(filters):
"""
Full-Text Search(FTS)와 Vector Search를 결합한 하이브리드 검색 로직
"""
# 1. N+1 문제 방지를 위한 Eager Loading 및 임베딩 필드 지연 로딩
qs = Document.objects.select_related('author').prefetch_related('tags').defer('embedding').all()
if filters.q:
try :
# AI 모델을 통한 키워드 벡터화 (병렬 처리를 위해 태스크 생성)
query_vector_task = ai_client.fetch_embedding(filters.q)
# 2. FTS(Full-Text Search) 쿼리 정의
search_query = SearchQuery(filters.q)
fts_qs = qs.annotate(
rank=SearchRank('search_vector', search_query)
).filter(search_vector=search_query).order_by('-rank')[:50]
# 3. 벡터 추출 대기 및 벡터 검색 수행
query_vector = await query_vector_task
if query_vector:
vector_qs = qs.annotate(
distance = CosineDistance('embedding', query_vector)
).order_by('distance')[:50]
# DB 쿼리를 비동기로 병렬 실행하여 레이턴시 최적화
v_task = sync_to_async(list)(vector_qs)
f_task = sync_to_async(list)(fts_qs)
v_results, f_results = await asyncio.gather(v_task, f_task)
# 4. RRF(Reciprocal Rank Fusion) 적용하여 순위 재조합
final_ids = apply_rrf_algorithm(v_results, f_results)
if final_ids:
# 결과 ID 순서를 보존하며 최종 QuerySet 반환
preserved_order = Case(
*[When(id=pk, then=Value(pos)) for pos, pk in enumerate(final_ids)],
output_field=IntegerField()
)
qs = Document.objects.filter(id__in=final_ids).order_by(preserved_order)
else:
qs = Document.objects.none()
else:
qs = fts_qs
except Exception as e:
# 보안을 위해 상세 에러는 내부 로그에만 기록
logger.error(f"Search filtering failed: {str(e)}")
# 장애 발생 시 키워드 기반 검색으로 안정적인 Fallback
qs = qs.annotate(rank=SearchRank('search_vector', SearchQuery(filters.q))).filter(search_vector=SearchQuery(filters.q)).order_by('-rank')
else:
qs = qs.order_by('-created_at')
# 필터링 조건 적용 (유형, 태그 등)
if filters.type: qs = qs.filter(type=filters.type)
if filters.tags: qs = qs.filter(tags__name__in=filters.tags).distinct()
return await sync_to_async(list)(qs)
-- 샘플 Query SELECT * FROM dual;