feat(embedder): OpenAI-compatible embedding provider - #803
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for OpenAI-compatible embedding providers (such as OpenAI, Ollama, vLLM, and LM Studio) as an alternative to local UniXcoder embeddings, allowing users to run without local torch and transformers dependencies. The feedback highlights critical issues with URL resolution in httpx due to a leading slash in the embeddings path and a missing trailing slash in the base URL. Additionally, the feedback suggests including configured embedding dimensions in the cache namespace to prevent retrieving stale cached vectors of incorrect dimensions, and recommends hardening the response parsing logic against malformed or buggy API responses.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Greptile SummaryThis PR adds OpenAI-compatible embedding support while keeping UniXcoder as the default. The main changes are:
Confidence Score: 5/5Safe to merge with minimal risk. The new provider is opt-in, the default UniXcoder path remains intact, and the changed code validates response counts and indices before caching or returning embeddings. Cache keys include provider, model, and configured dimensions, so switching embedding spaces does not replay stale vectors. Tests cover the main OpenAI-compatible request, batching, auth, cache, URL, dependency, and error paths. No files require special attention.
What T-Rex did
Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Caller as Graph updater / semantic search
participant Embedder as embedder.py
participant Cache as EmbeddingCache
participant Provider as OpenAI-compatible / UniXcoder
participant Store as Vector store
Caller->>Embedder: embed_code_batch(snippets)
Embedder->>Cache: get_many(snippets)
Cache-->>Embedder: cached vectors by index
alt OpenAI provider for uncached snippets
Embedder->>Provider: "POST /embeddings {model,input,dimensions?}"
Provider-->>Embedder: "data[{index, embedding}]"
Embedder->>Embedder: validate count and indices
else UniXcoder provider for uncached snippets
Embedder->>Provider: tokenize and run local model
Provider-->>Embedder: embeddings
end
Embedder->>Cache: put_many(uncached snippets, embeddings)
Embedder-->>Caller: vectors in request order
Caller->>Store: store/search embeddings
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Caller as Graph updater / semantic search
participant Embedder as embedder.py
participant Cache as EmbeddingCache
participant Provider as OpenAI-compatible / UniXcoder
participant Store as Vector store
Caller->>Embedder: embed_code_batch(snippets)
Embedder->>Cache: get_many(snippets)
Cache-->>Embedder: cached vectors by index
alt OpenAI provider for uncached snippets
Embedder->>Provider: "POST /embeddings {model,input,dimensions?}"
Provider-->>Embedder: "data[{index, embedding}]"
Embedder->>Embedder: validate count and indices
else UniXcoder provider for uncached snippets
Embedder->>Provider: tokenize and run local model
Provider-->>Embedder: embeddings
end
Embedder->>Cache: put_many(uncached snippets, embeddings)
Embedder-->>Caller: vectors in request order
Caller->>Store: store/search embeddings
Reviews (2): Last reviewed commit: "fix(embedder): dimension-aware cache nam..." | Re-trigger Greptile |
… malformed embedding responses
|
@greptile review |
|



Closes #267.
Adds an OpenAI-compatible embedding provider so embeddings can be computed by any server implementing the OpenAI embeddings API (OpenAI, Ollama, vLLM, LM Studio), removing the local torch/transformers requirement for semantic search.
Configuration
CGR_EMBEDDING_PROVIDER=openaiselects the provider (unixcoderremains the default; fully backward compatible).OPENAI_EMBEDDING_BASE_URL(defaulthttps://api.openai.com/v1),OPENAI_EMBEDDING_MODEL(defaulttext-embedding-3-small).OPENAI_EMBEDDING_API_KEYsent as a bearer token when set, falling back to theOPENAI_API_KEYenv var; local servers needing no key work without one.OPENAI_EMBEDDING_DIMENSIONSforwarded as thedimensionsrequest parameter,OPENAI_EMBEDDING_BATCH_SIZE(default 128),OPENAI_EMBEDDING_TIMEOUT(default 60s).Implementation
embedder.py; the UniXcoder torch path is unchanged and stays behind the existing dependency gate. The HTTP client is httpx, already a guaranteed transitive dependency, so no new packages.indexso out-of-order responses cannot scramble embeddings. Non-200 responses raise with status and body excerpt.has_semantic_dependencies()requires only the vector store dependency under the openai provider since embeddings are computed server-side.Tests
Strict RED then GREEN: commit one adds 17 failing specs (request shape, batching, ordering, caching, namespacing, auth header, base URL, error paths, dependency gating) driven through
httpx.MockTransport; commit two implements. Full suite: 5182 passed, 5 skipped.Docs
README, PYPI_README,
docs/sdk/semantic-search.md(full configuration table plus vector-dimension guidance), anddocs/guide/interactive-querying.md.