Projects

BGE-m3-ko 임베딩 서비스 개발 회고

FastAPI, SentenceTransformer, Milvus를 사용해 직무 필터링과 시맨틱 캐시를 지원하는 임베딩 서비스를 구현한 프로젝트입니다.

프로젝트 개요

이 프로젝트는 팀 서비스의 AI 기능에서 사용하는 임베딩 전용 서버입니다. 텍스트를 벡터로 변환하고, Milvus 벡터 DB에 저장한 뒤, 유사도 검색을 통해 직무 필터링과 시맨틱 캐시 기능을 지원합니다.

  • 구분: 카카오테크 부트캠프 팀 프로젝트
  • 역할: 임베딩 API 및 벡터 검색 서비스 구현
  • 주요 기술: FastAPI, Python, SentenceTransformer, dragonkue/BGE-m3-ko, Milvus
  • 링크: GitHub

이 서비스는 메인 AI 서버에서 직접 임베딩 모델과 벡터 DB를 들고 있지 않도록 분리한 보조 서버입니다. AI 서버는 HTTP API로 임베딩 서비스에 요청하고, 임베딩 서비스는 모델 로딩과 Milvus 컬렉션 관리를 담당합니다.

문제

AI 서버에는 직무 관련성 필터와 시맨틱 캐시가 필요했습니다. 예를 들어 사용자가 입력한 회사, 부서, 직무가 개발과 관련 있는지 판단해야 했고, 비슷한 자기소개 생성 요청은 매번 LLM을 호출하지 않고 이전 결과를 재사용할 수 있어야 했습니다.

이 기능을 문자열 비교만으로 처리하면 표현이 조금만 달라져도 같은 의미를 놓치기 쉽습니다. "백엔드 개발자", "서버 개발자", "Backend Engineer"처럼 표현은 다르지만 의미가 가까운 데이터를 다루기 위해서는 텍스트를 벡터로 변환하고 유사도 기반으로 검색할 수 있는 구조가 필요했습니다.

구현

FastAPI 서버는 앱 시작 시 dragonkue/BGE-m3-ko 모델과 Milvus 클라이언트를 초기화합니다. 모델은 싱글톤으로 관리해 요청마다 다시 로드하지 않도록 했고, 임베딩 결과는 cosine 검색에 맞게 정규화했습니다.

Client 또는 AI Server
  -> Embedding Service
  -> BGE-m3-ko SentenceTransformer
  -> 1024차원 embedding
  -> Milvus collection insert/search

임베딩 API

단일 텍스트 임베딩과 배치 임베딩을 각각 제공했습니다. 단일 요청은 /embed, 여러 문장을 한 번에 처리하는 요청은 /embed/batch로 분리했습니다.

배치 API를 둔 이유는 시드 데이터나 캐시 데이터를 넣을 때 매번 하나씩 모델을 호출하는 것보다 한 번에 처리하는 편이 효율적이기 때문입니다.

Milvus 컬렉션 관리

Milvus 컬렉션은 text, category, metadata, embedding 필드를 갖도록 구성했습니다. embedding 필드는 1024차원 FLOAT_VECTOR이고, 검색 metric은 COSINE을 사용했습니다.

컬렉션 생성 API를 별도로 제공하고, 데이터 삽입 시 컬렉션이 없으면 자동 생성하도록 처리했습니다. 덕분에 메인 AI 서버에서 job_roles, semantic_cache 같은 컬렉션을 사용할 때 초기 설정 부담을 줄일 수 있었습니다.

유사도 검색

검색은 두 가지 방식으로 제공했습니다.

  • /collection/{name}/search: 검색어를 텍스트로 받아 자동 임베딩 후 검색
  • /collection/{name}/search/vector: 이미 만들어진 벡터를 직접 받아 검색

메인 AI 서버 입장에서는 대부분 텍스트 검색 API만 사용하면 됩니다. 하지만 벡터를 직접 넘기는 API도 열어두면, 나중에 다른 모델이나 외부 임베딩 결과를 활용할 때 재사용할 수 있습니다.

고민한 점

가장 중요했던 부분은 AI 서버와 임베딩 서버의 책임 분리였습니다. LLM 호출, GitHub 분석, OCR 같은 기능을 처리하는 서버가 임베딩 모델 로딩과 Milvus 연결까지 모두 직접 관리하면 서버의 책임이 너무 커집니다.

그래서 임베딩과 벡터 검색을 독립된 HTTP 서비스로 분리했습니다. 이 구조에서는 메인 AI 서버가 임베딩 모델 구현을 몰라도 되고, 임베딩 모델을 바꾸거나 Milvus 설정을 바꿔도 API 계약만 유지하면 영향 범위를 줄일 수 있습니다.

또한 Milvus 컬렉션이 없을 때 단순히 실패시키는 대신, 삽입 단계에서는 자동 생성하도록 만들었습니다. 반면 검색 단계에서는 존재하지 않는 컬렉션을 404로 반환하게 해, 데이터가 없어서 검색할 수 없는 상황과 자동 생성이 필요한 상황을 구분했습니다.

배운 점

임베딩 서비스는 눈에 보이는 기능은 단순하지만, AI 기능의 품질과 비용에 직접 영향을 줍니다. 직무 필터링은 불필요한 LLM 호출을 줄이고, 시맨틱 캐시는 비슷한 요청을 재사용하게 해 응답 시간과 비용을 줄일 수 있습니다.

이번 프로젝트를 통해 벡터 검색은 모델만 붙이면 끝나는 기능이 아니라는 점을 배웠습니다. 임베딩 차원, 정규화 여부, metric type, 컬렉션 스키마, 메타데이터 구조까지 함께 맞아야 다른 서비스에서 안정적으로 사용할 수 있습니다.

특히 메인 AI 서버와 분리된 형태로 구현하면서, AI 기능도 일반 백엔드처럼 책임 경계와 API 계약을 먼저 정리해야 유지보수가 쉬워진다는 점을 경험했습니다.