Skip to content

feat: pluggable embedding backends (OpenAI-compatible / local-gguf + resilient fallback)#9

Closed
epruseal wants to merge 1 commit into
upstream-main-basefrom
feat/embedding-backends
Closed

feat: pluggable embedding backends (OpenAI-compatible / local-gguf + resilient fallback)#9
epruseal wants to merge 1 commit into
upstream-main-basefrom
feat/embedding-backends

Conversation

@epruseal

@epruseal epruseal commented Jun 17, 2026

Copy link
Copy Markdown
Owner

feat: pluggable embedding backends (OpenAI-compatible / local-gguf + resilient fallback)

배경 / 문제

ChromaDB 기본 임베딩 함수(all-MiniLM-L6-v2, ONNX, 384d)는 영어 특화라 한국어 검색
품질이 낮다. 동일 코퍼스에서 실측 결과:

백엔드 top-1 MRR
minilm (기본, 384d) 0/5 0.285
KURE-v1 (1024d) 5/5 1.000

변경 요약

EMBEDDING_BACKEND 환경변수로 임베딩 함수를 교체할 수 있게 한다. 기본값은 local이라
기존 동작 100% 보존(아무 설정도 안 하면 지금과 똑같이 minilm 사용).

  • EMBEDDING_BACKEND=local (기본): ChromaDB 기본 EF. 기존 컬렉션 opencrab_vectors 그대로.
  • EMBEDDING_BACKEND=openai: OpenAI 호환 /v1/embeddings API를 쓰는 범용 경로. 임베딩
    모델은 자유롭게 선택 — 실제 OpenAI 클라우드 모델(text-embedding-3-small/-large 등)도,
    로컬·자체호스팅 서버(LM Studio·Ollama·vLLM·HF TEI)에 올린 모델도 모두 가능하다. 모델은
    OPENAI_EMBED_MODEL, 차원은 EMBED_DIM, 컬렉션은 EMBED_COLLECTION으로 지정한다.
    • 추천 모델(한국어): 자체호스팅이라면 KURE-v1(1024d, BGE-M3 기반, 한국어 SOTA)을
      추천한다. 번들된 로컬 GGUF 폴백도 KURE-v1이라, KURE primary면 추가 설정 없이 폴백까지
      바로 동작한다.
    • 클라우드 OpenAI 모델을 쓸 경우 EMBED_DIM을 해당 모델 차원에 맞추고(예: -3-small=1536),
      로컬 GGUF 폴백은 차원이 달라 부적합하므로 LOCAL_GGUF_PATH로 동일 차원 GGUF를 지정하거나
      폴백에 의존하지 않는다(원격만 사용). → 자세한 제약은 아래 "임베딩 모델 혼용 금지" 참고.

신규 컴포넌트

파일 역할
stores/openai_embedding.py OpenAIEmbeddingFunction — httpx로 /v1/embeddings 직접 호출. OPENAI_API_KEY 설정 시 Authorization: Bearer, 미설정 시 무인증(LM Studio 등). L2 재정규화.
stores/llamacpp_embedding.py LlamaCppEmbeddingFunction — 로컬 GGUF 폴백. lazy load, 미존재 시 HuggingFace 자동 다운로드(KURE-v1-Q4_K_M).
stores/resilient_embedding.py ResilientEmbeddingFunction — primary 장애 시 fallback 자동 전환. health TTL 캐시로 불필요한 타임아웃 재시도 방지.

기존 파일 변경

  • config.py: 임베딩 관련 Settings 필드 추가(아래 환경변수). 기존 필드/동작 불변.
  • stores/chroma_store.py: embedding_function: Any = None 파라미터 추가. None이면 기존
    기본 EF 사용 → 하위호환. 주입 시 해당 EF로 add/query.
  • stores/factory.py: make_vector_store()embedding_backend=="openai" 분기만 추가.
    make_graph_store/make_doc_store/make_sql_store는 변경 없음.

호환성 / 롤백

  • 기본값 local이라 설정 안 하면 기존과 동일. 새 의존성도 openai 경로에서만 로드(lazy import).
  • EMBEDDING_BACKEND 미설정 또는 local로 되돌리면 즉시 롤백, 기존 컬렉션 보존.
  • name()="kure_v1" 고정 → 컬렉션 메타데이터 일관 → 재시작/폴백 전환 시 동일 컬렉션 재사용.

⚠️ 임베딩 모델 혼용 금지

하나의 Chroma 컬렉션은 하나의 임베딩 모델·차원으로만 채워야 한다. 색인(add)과
질의(query)는 반드시 같은 모델로 수행되어야 하며, 서로 다른 모델의 벡터를 한 컬렉션에
섞으면 검색이 무의미해진다(차원 불일치 또는 의미공간 불일치). 한 컬렉션 = 한 모델
원칙이다. (openai는 모델이 아니라 *백엔드(전송 방식)*임에 유의 — 실제 OpenAI 모델, KURE,
KoSimCSE 등 어떤 모델이든 이 백엔드로 쓸 수 있고, 그중 무엇을 골랐든 컬렉션 안에서 섞지 말 것.)

  • local(minilm 384d)과 openai 백엔드의 컬렉션은 분리되어 있어
    (opencrab_vectorsEMBED_COLLECTION, 기본 opencrab_vectors_kure) 자동으로 충돌하지 않는다.
  • openai 백엔드 안에서도 모델을 바꾸면(예: KURE 1024d → KoSimCSE 768d) 반드시 새
    EMBED_COLLECTION을 쓰고 전량 재색인
    해야 한다. 기존 컬렉션에 다른 모델로 덧쓰면 안 된다.
  • primary(원격)와 fallback(로컬 GGUF)은 동일 모델·차원이어야 한다. ResilientEF는 둘이
    같은 KURE-v1임을 전제로 name()="kure_v1" 하나로 컬렉션을 공유한다. 서로 다른 모델을
    primary/fallback으로 두면 안 된다.

환경변수

변수 기본값 설명
EMBEDDING_BACKEND local local 또는 openai
OPENAI_API_BASE http://localhost:1234/v1 OpenAI 호환 서버 base URL
OPENAI_EMBED_MODEL text-embedding-kure-v1 임베딩 모델 id. 서버/제공자가 인식하는 값(예: text-embedding-3-small, KURE 서빙 모델명 등)
OPENAI_API_KEY (빈 값) 설정 시 Bearer 인증, 미설정 시 무인증
OPENAI_TIMEOUT 8.0 HTTP 타임아웃(초)
EMBED_DIM 1024 임베딩 차원. 선택한 모델에 맞춰야 함(KURE=1024, text-embedding-3-small=1536)
EMBED_COLLECTION opencrab_vectors_kure openai 백엔드 컬렉션. 모델·차원별로 분리할 것
LOCAL_GGUF_PATH (빈 값) 폴백 GGUF 경로. 미설정 시 ~/.cache/opencrab/models에 자동 다운로드

테스트 / 검증

  • py_compile 6파일 통과.
  • import 스모크: OpenAIEmbeddingFunction.name()=="kure_v1", Bearer 헤더 분기, _l2_normalize,
    빈입력 패스스루, ResilientEF 체인 생성 모두 정상(네트워크 없이).
  • _DEFAULT_GGUF_DIR~/.cache/opencrab/models로 해석되고 XDG_CACHE_HOME 존중 확인.
  • 운영 환경(localcrab) 실측: KURE 서버 ping OK, 임베딩 1024d, ontology_query 3건 정상
    (감리 제1조 1.0235 / FP 대가산정 0.9963 / CBD 표준산출물 1.0054).

비고

  • 코드/주석/문서는 특정 벤더에 종속되지 않게 일반화("OpenAI 호환 서버"). LM Studio 언급은
    호환 서버의 예시로만 남김.
  • 폴백 기본 모델 mykor/KURE-v1-gguf(Q4_K_M, ~438MB)는 한국어 임베딩용 공개 모델. 다른 모델은
    LOCAL_GGUF_PATH로 지정 가능.
  • 경량 대안 (CPU 부담 시): KURE-v1은 BGE-M3 기반(약 560M, 1024d)이라 CPU만으로는 무겁다.
    CPU 자원이 부족하면 한국어 경량 임베딩 BM-K/KoSimCSE-roberta
    (RoBERTa-base, 약 110M, 768d)를 추천한다. KURE보다 가볍고 빠르지만 한국어 전용이며 검색
    품질은 다소 낮을 수 있다.
    • 이 PR이 머지되면 코드 수정 없이 사용 가능: OpenAI 호환 서버(예: HF
      Text-Embeddings-Inference)에 KoSimCSE를 서빙하고 OPENAI_EMBED_MODEL,
      EMBED_DIM=768, 별도 EMBED_COLLECTION만 지정하면 된다(전량 재색인 필요).
    • 로컬 GGUF 폴백 경로는 GGUF 빌드가 있어야 하므로 KoSimCSE에는 기본 적용되지 않는다
      (GGUF가 없으면 EMBEDDING_BACKEND=openai의 primary만 사용).

…resilient fallback)

기존 ChromaDB 기본 EF(all-MiniLM-L6-v2, 384d)는 영어 특화라 한국어 검색 품질이
낮다(실측 top-1 0/5, MRR 0.285). EMBEDDING_BACKEND 환경변수로 임베딩 함수를
교체할 수 있게 하여 KURE-v1(1024d) 등 OpenAI 호환 서버 모델로 품질을 개선한다
(실측 top-1 5/5, MRR 1.000).

- EMBEDDING_BACKEND=local(기본): 기존 ChromaDB EF 그대로 → 100% 하위호환.
- EMBEDDING_BACKEND=openai: OpenAI 호환 /v1/embeddings 서버(LM Studio·vLLM·
  Ollama·실제 OpenAI) + 로컬 GGUF 폴백.
- OpenAIEmbeddingFunction: httpx 직접 호출, OPENAI_API_KEY 설정 시 Bearer 인증.
- LlamaCppEmbeddingFunction: 로컬 GGUF 폴백(lazy load, 자동 다운로드).
- ResilientEmbeddingFunction: primary 장애 시 폴백 자동 전환(health TTL).

환경변수: EMBEDDING_BACKEND, OPENAI_API_BASE, OPENAI_EMBED_MODEL, OPENAI_API_KEY,
OPENAI_TIMEOUT, EMBED_DIM, EMBED_COLLECTION, LOCAL_GGUF_PATH.
@epruseal

Copy link
Copy Markdown
Owner Author

검토 완료 — upstream(AlexAI-MCP#7)으로 제출. 머지 없이 닫음.

@epruseal epruseal closed this Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant