feat: BYOK embedding provider support (OpenAI, Voyage, Gemini)#39
Merged
iamvirul merged 6 commits intoVecGrep:mainfrom Mar 4, 2026
Merged
feat: BYOK embedding provider support (OpenAI, Voyage, Gemini)#39iamvirul merged 6 commits intoVecGrep:mainfrom
iamvirul merged 6 commits intoVecGrep:mainfrom
Conversation
Add EmbeddingProvider ABC and four concrete implementations: - LocalProvider: wraps existing ONNX/torch logic as instance state - OpenAIProvider: text-embedding-3-small (1536 dims), VECGREP_OPENAI_KEY - VoyageProvider: voyage-code-2 (1024 dims), VECGREP_VOYAGE_KEY - GeminiProvider: gemini-embedding-001 (768 dims), VECGREP_GEMINI_KEY Add PROVIDER_REGISTRY dict and get_provider(name) factory. Keep backward-compatible embed() free function (delegates to LocalProvider). All cloud providers lazy-import their SDK and raise RuntimeError with install instructions when the package or API key is missing.
- Convert hardcoded schema to _chunks_schema(dims) function; keep module-level schema = _chunks_schema(384) for backward compat - VectorStore.__init__ now accepts dims: int = 384; reads stored dims from meta table on open and uses them over the param - Add _get_meta(key) / _set_meta(key, value) helpers - Add set_provider_meta(provider, model, dims) / get_provider_meta() - Add drop_and_recreate_chunks(dims) for force re-index with dim change - Refactor touch_last_indexed() to use _set_meta - status() now includes provider, model, dims fields
- Import get_provider, EmbeddingProvider from embedder - Add _resolve_provider(path, requested, force) helper that reads stored provider from meta, enforces per-project lock, and errors on mismatch without force=True - _get_store(path, dims) now passes dims through to VectorStore - _do_index gains provider: str | None = None param: - Resolves provider early before acquiring index lock - Blocks watch=True with non-local provider (clear error message) - Passes emb_provider.dims to _get_store - Drops/recreates chunks table when force=True + dims change - Calls emb_provider.embed() instead of free-function embed() - Persists provider meta after successful index - index_codebase MCP tool exposes provider param with full docstring - search_code reads stored provider and uses matching embedder for query - get_index_status shows provider, model, dims in output - LiveSyncHandler._process_file reads stored provider and skips if non-local - _merkle_sync passes provider=None to _do_index (auto-reads stored)
Add openai, voyage, gemini, and cloud extras so users can install only what they need: pip install 'vecgrep[openai]', 'vecgrep[voyage]', 'vecgrep[gemini]', or 'vecgrep[cloud]' for all three at once. Base install remains lightweight with no cloud SDK dependencies.
- Switch GeminiProvider from deprecated google-generativeai to google-genai SDK - Correct gemini-embedding-001 dims from 768 → 3072 (actual API output) - Update optional dep: google-generativeai → google-genai>=1.0 - Add tests/test_providers.py: registry, LocalProvider embed/norm, cloud providers raise without keys, mocked API calls for all 3 providers - Extend tests/test_store.py: dynamic dims, drop_and_recreate_chunks, provider meta persistence, _get_meta/_set_meta helpers - Extend tests/test_server.py: provider locking (switch without force=error), cloud+watch guard, get_index_status provider fields, LiveSync skip for cloud providers, force=True allows provider switch - Fix embedder: catch ValueError when custom model already registered - Fix _resolve_provider: treat raw_stored=None (fresh index) as unset so any provider is allowed without force
…tional deps - Remove unused `embed` import from server.py (ruff F401) - Fix _resolve_provider: read raw meta key directly so a fresh/pre-BYOK index (stored provider = None) allows any provider without requiring force=True; only enforce lock when provider was explicitly stored - Regenerate uv.lock to include google-genai, openai, voyageai optional dependency trees so cloud extras install reproducibly
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
iamvirul
approved these changes
Mar 4, 2026
16 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Type of Change
Description
Closes #8.
Adds Bring-Your-Own-Key (BYOK) support for cloud embedding providers so users can index and search codebases with OpenAI, Voyage AI, or Google Gemini embeddings in addition to the default local model.
Key design decisions (from issue discussion):
force=Truewatch=Trueis blocked for cloud providers to prevent unbounded API costsProvider Table
local(default)all-MiniLM-L6-v2-code-search-512openaitext-embedding-3-smallVECGREP_OPENAI_KEYvoyagevoyage-code-2VECGREP_VOYAGE_KEYgeminigemini-embedding-001VECGREP_GEMINI_KEYHow to Use
Install with cloud extras:
Set the API key env var, then index:
Search uses the stored provider automatically — no need to specify it again:
Switching providers requires a full re-index with
force=True:Live watch is local-only (cloud providers will return a clear error):
Changes Made
src/vecgrep/embedder.py— Strategy pattern refactor:EmbeddingProviderABC +LocalProvider,OpenAIProvider,VoyageProvider,GeminiProviderimplementations;PROVIDER_REGISTRY+get_provider()factory; backward-compatibleembed()free function preservedsrc/vecgrep/store.py— Dynamic vector dims:_chunks_schema(dims)function,VectorStore(dims=384),_get_meta/_set_metahelpers,set_provider_meta/get_provider_meta,drop_and_recreate_chunks,status()now includes provider/model/dimssrc/vecgrep/server.py— Provider wiring:_resolve_provider()enforces per-project lock;_do_indexgainsproviderparam; cloud+watch guard;search_codereads stored provider for query embedding;get_index_statusshows provider/model/dims;LiveSyncHandlerskips non-local providerspyproject.toml— Optional dep groups:openai,voyage,gemini,clouduv.lock— Updated to include all optional dep treestests/test_providers.py(new) — Registry, LocalProvider shape/norm, cloud providers raise without keys/packages, mocked API calls for all 3 providerstests/test_store.py— Dynamic dims,drop_and_recreate_chunks, provider meta persistence, meta helperstests/test_server.py— Provider locking, cloud+watch guard, status fields, LiveSync skip for cloudTesting
pytest tests/)ruff check src/ tests/)uv sync --extra dev→ lint → pytest)gemini-embedding-001returns shape(1, 3072), norm1.0000index_codebase(path, provider="local")works as before (backward-compatible)index_codebase(path, provider="openai")without key → clearRuntimeErrorindex_codebase(path, provider="openai", watch=True)→ blocked with messageforce=True→ clear error about provider lock