mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add OpenRouter as an embedding provider for code indexing
- Add OpenRouter to EmbedderProvider type and related interfaces - Create OpenRouterEmbedder class that extends OpenAICompatibleEmbedder - Add OpenRouter embedding models to EMBEDDING_MODEL_PROFILES - Update service factory to handle OpenRouter embedder creation - Configure OpenRouter to use existing API key from chat model settings - Support OpenRouter embedding models including OpenAI, Cohere, and Voyage models Closes #8972
This commit is contained in:
parent
d7ea6d679e
commit
c162cf6922
8 changed files with 134 additions and 3 deletions
|
|
@ -20,6 +20,7 @@ export class CodeIndexConfigManager {
|
|||
private geminiOptions?: { apiKey: string }
|
||||
private mistralOptions?: { apiKey: string }
|
||||
private vercelAiGatewayOptions?: { apiKey: string }
|
||||
private openRouterOptions?: { apiKey: string; baseUrl?: string }
|
||||
private qdrantUrl?: string = "http://localhost:6333"
|
||||
private qdrantApiKey?: string
|
||||
private searchMinScore?: number
|
||||
|
|
@ -71,6 +72,9 @@ export class CodeIndexConfigManager {
|
|||
const geminiApiKey = this.contextProxy?.getSecret("codebaseIndexGeminiApiKey") ?? ""
|
||||
const mistralApiKey = this.contextProxy?.getSecret("codebaseIndexMistralApiKey") ?? ""
|
||||
const vercelAiGatewayApiKey = this.contextProxy?.getSecret("codebaseIndexVercelAiGatewayApiKey") ?? ""
|
||||
// Use existing openRouterApiKey from chat model settings for embeddings
|
||||
const openRouterApiKey = this.contextProxy?.getSecret("openRouterApiKey") ?? ""
|
||||
const openRouterBaseUrl = codebaseIndexConfig.codebaseIndexEmbedderBaseUrl ?? ""
|
||||
|
||||
// Update instance variables with configuration
|
||||
this.codebaseIndexEnabled = codebaseIndexEnabled ?? true
|
||||
|
|
@ -108,6 +112,8 @@ export class CodeIndexConfigManager {
|
|||
this.embedderProvider = "mistral"
|
||||
} else if (codebaseIndexEmbedderProvider === "vercel-ai-gateway") {
|
||||
this.embedderProvider = "vercel-ai-gateway"
|
||||
} else if (codebaseIndexEmbedderProvider === "openrouter") {
|
||||
this.embedderProvider = "openrouter"
|
||||
} else {
|
||||
this.embedderProvider = "openai"
|
||||
}
|
||||
|
|
@ -129,6 +135,9 @@ export class CodeIndexConfigManager {
|
|||
this.geminiOptions = geminiApiKey ? { apiKey: geminiApiKey } : undefined
|
||||
this.mistralOptions = mistralApiKey ? { apiKey: mistralApiKey } : undefined
|
||||
this.vercelAiGatewayOptions = vercelAiGatewayApiKey ? { apiKey: vercelAiGatewayApiKey } : undefined
|
||||
this.openRouterOptions = openRouterApiKey
|
||||
? { apiKey: openRouterApiKey, baseUrl: openRouterBaseUrl || undefined }
|
||||
: undefined
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -147,6 +156,7 @@ export class CodeIndexConfigManager {
|
|||
geminiOptions?: { apiKey: string }
|
||||
mistralOptions?: { apiKey: string }
|
||||
vercelAiGatewayOptions?: { apiKey: string }
|
||||
openRouterOptions?: { apiKey: string; baseUrl?: string }
|
||||
qdrantUrl?: string
|
||||
qdrantApiKey?: string
|
||||
searchMinScore?: number
|
||||
|
|
@ -167,6 +177,8 @@ export class CodeIndexConfigManager {
|
|||
geminiApiKey: this.geminiOptions?.apiKey ?? "",
|
||||
mistralApiKey: this.mistralOptions?.apiKey ?? "",
|
||||
vercelAiGatewayApiKey: this.vercelAiGatewayOptions?.apiKey ?? "",
|
||||
openRouterApiKey: this.openRouterOptions?.apiKey ?? "",
|
||||
openRouterBaseUrl: this.openRouterOptions?.baseUrl ?? "",
|
||||
qdrantUrl: this.qdrantUrl ?? "",
|
||||
qdrantApiKey: this.qdrantApiKey ?? "",
|
||||
}
|
||||
|
|
@ -192,6 +204,7 @@ export class CodeIndexConfigManager {
|
|||
geminiOptions: this.geminiOptions,
|
||||
mistralOptions: this.mistralOptions,
|
||||
vercelAiGatewayOptions: this.vercelAiGatewayOptions,
|
||||
openRouterOptions: this.openRouterOptions,
|
||||
qdrantUrl: this.qdrantUrl,
|
||||
qdrantApiKey: this.qdrantApiKey,
|
||||
searchMinScore: this.currentSearchMinScore,
|
||||
|
|
@ -234,6 +247,11 @@ export class CodeIndexConfigManager {
|
|||
const qdrantUrl = this.qdrantUrl
|
||||
const isConfigured = !!(apiKey && qdrantUrl)
|
||||
return isConfigured
|
||||
} else if (this.embedderProvider === "openrouter") {
|
||||
const apiKey = this.openRouterOptions?.apiKey
|
||||
const qdrantUrl = this.qdrantUrl
|
||||
const isConfigured = !!(apiKey && qdrantUrl)
|
||||
return isConfigured
|
||||
}
|
||||
return false // Should not happen if embedderProvider is always set correctly
|
||||
}
|
||||
|
|
@ -269,6 +287,8 @@ export class CodeIndexConfigManager {
|
|||
const prevGeminiApiKey = prev?.geminiApiKey ?? ""
|
||||
const prevMistralApiKey = prev?.mistralApiKey ?? ""
|
||||
const prevVercelAiGatewayApiKey = prev?.vercelAiGatewayApiKey ?? ""
|
||||
const prevOpenRouterApiKey = prev?.openRouterApiKey ?? ""
|
||||
const prevOpenRouterBaseUrl = prev?.openRouterBaseUrl ?? ""
|
||||
const prevQdrantUrl = prev?.qdrantUrl ?? ""
|
||||
const prevQdrantApiKey = prev?.qdrantApiKey ?? ""
|
||||
|
||||
|
|
@ -307,6 +327,8 @@ export class CodeIndexConfigManager {
|
|||
const currentGeminiApiKey = this.geminiOptions?.apiKey ?? ""
|
||||
const currentMistralApiKey = this.mistralOptions?.apiKey ?? ""
|
||||
const currentVercelAiGatewayApiKey = this.vercelAiGatewayOptions?.apiKey ?? ""
|
||||
const currentOpenRouterApiKey = this.openRouterOptions?.apiKey ?? ""
|
||||
const currentOpenRouterBaseUrl = this.openRouterOptions?.baseUrl ?? ""
|
||||
const currentQdrantUrl = this.qdrantUrl ?? ""
|
||||
const currentQdrantApiKey = this.qdrantApiKey ?? ""
|
||||
|
||||
|
|
@ -337,6 +359,10 @@ export class CodeIndexConfigManager {
|
|||
return true
|
||||
}
|
||||
|
||||
if (prevOpenRouterApiKey !== currentOpenRouterApiKey || prevOpenRouterBaseUrl !== currentOpenRouterBaseUrl) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for model dimension changes (generic for all providers)
|
||||
if (prevModelDimension !== currentModelDimension) {
|
||||
return true
|
||||
|
|
@ -395,6 +421,7 @@ export class CodeIndexConfigManager {
|
|||
geminiOptions: this.geminiOptions,
|
||||
mistralOptions: this.mistralOptions,
|
||||
vercelAiGatewayOptions: this.vercelAiGatewayOptions,
|
||||
openRouterOptions: this.openRouterOptions,
|
||||
qdrantUrl: this.qdrantUrl,
|
||||
qdrantApiKey: this.qdrantApiKey,
|
||||
searchMinScore: this.currentSearchMinScore,
|
||||
|
|
|
|||
53
src/services/code-index/embedders/openrouter.ts
Normal file
53
src/services/code-index/embedders/openrouter.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { OpenAICompatibleEmbedder } from "./openai-compatible"
|
||||
import { IEmbedder, EmbedderInfo } from "../interfaces/embedder"
|
||||
import { getDefaultModelId } from "../../../shared/embeddingModels"
|
||||
import { MAX_ITEM_TOKENS } from "../constants"
|
||||
import { t } from "../../../i18n"
|
||||
|
||||
/**
|
||||
* OpenRouter embedder implementation that wraps the OpenAI Compatible embedder
|
||||
* with configuration for OpenRouter's embedding API.
|
||||
*
|
||||
* Supported models:
|
||||
* - openai/text-embedding-3-small (dimension: 1536)
|
||||
* - openai/text-embedding-3-large (dimension: 3072)
|
||||
* - openai/text-embedding-ada-002 (dimension: 1536)
|
||||
* - cohere/embed-english-v3.0 (dimension: 1024)
|
||||
* - cohere/embed-multilingual-v3.0 (dimension: 1024)
|
||||
* - voyage/voyage-3 (dimension: 1024)
|
||||
* - voyage/voyage-3-lite (dimension: 512)
|
||||
*/
|
||||
export class OpenRouterEmbedder extends OpenAICompatibleEmbedder implements IEmbedder {
|
||||
private static readonly OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
|
||||
private static readonly DEFAULT_MODEL = "openai/text-embedding-3-small"
|
||||
private readonly modelId: string
|
||||
|
||||
/**
|
||||
* Creates a new OpenRouter embedder
|
||||
* @param apiKey The OpenRouter API key for authentication
|
||||
* @param modelId The model ID to use (defaults to openai/text-embedding-3-small)
|
||||
* @param baseUrl Optional custom base URL for OpenRouter API (defaults to https://openrouter.ai/api/v1)
|
||||
*/
|
||||
constructor(apiKey?: string, modelId?: string, baseUrl?: string) {
|
||||
if (!apiKey) {
|
||||
throw new Error(t("embeddings:validation.apiKeyRequired"))
|
||||
}
|
||||
|
||||
// Use the provided base URL or default to OpenRouter's API URL
|
||||
const openRouterBaseUrl = baseUrl || OpenRouterEmbedder.OPENROUTER_BASE_URL
|
||||
|
||||
// Initialize the parent OpenAI Compatible embedder with OpenRouter configuration
|
||||
super(openRouterBaseUrl, apiKey, modelId || OpenRouterEmbedder.DEFAULT_MODEL, MAX_ITEM_TOKENS)
|
||||
|
||||
this.modelId = modelId || getDefaultModelId("openrouter")
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about this embedder
|
||||
*/
|
||||
override get embedderInfo(): EmbedderInfo {
|
||||
return {
|
||||
name: "openrouter",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ export interface CodeIndexConfig {
|
|||
geminiOptions?: { apiKey: string }
|
||||
mistralOptions?: { apiKey: string }
|
||||
vercelAiGatewayOptions?: { apiKey: string }
|
||||
openRouterOptions?: { apiKey: string; baseUrl?: string }
|
||||
qdrantUrl?: string
|
||||
qdrantApiKey?: string
|
||||
searchMinScore?: number
|
||||
|
|
@ -37,6 +38,8 @@ export type PreviousConfigSnapshot = {
|
|||
geminiApiKey?: string
|
||||
mistralApiKey?: string
|
||||
vercelAiGatewayApiKey?: string
|
||||
openRouterApiKey?: string
|
||||
openRouterBaseUrl?: string
|
||||
qdrantUrl?: string
|
||||
qdrantApiKey?: string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,14 @@ export interface EmbeddingResponse {
|
|||
}
|
||||
}
|
||||
|
||||
export type AvailableEmbedders = "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral" | "vercel-ai-gateway"
|
||||
export type AvailableEmbedders =
|
||||
| "openai"
|
||||
| "ollama"
|
||||
| "openai-compatible"
|
||||
| "gemini"
|
||||
| "mistral"
|
||||
| "vercel-ai-gateway"
|
||||
| "openrouter"
|
||||
|
||||
export interface EmbedderInfo {
|
||||
name: AvailableEmbedders
|
||||
|
|
|
|||
|
|
@ -70,7 +70,14 @@ export interface ICodeIndexManager {
|
|||
}
|
||||
|
||||
export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error"
|
||||
export type EmbedderProvider = "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral" | "vercel-ai-gateway"
|
||||
export type EmbedderProvider =
|
||||
| "openai"
|
||||
| "ollama"
|
||||
| "openai-compatible"
|
||||
| "gemini"
|
||||
| "mistral"
|
||||
| "vercel-ai-gateway"
|
||||
| "openrouter"
|
||||
|
||||
export interface IndexProgressUpdate {
|
||||
systemStatus: IndexingState
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { OpenAICompatibleEmbedder } from "./embedders/openai-compatible"
|
|||
import { GeminiEmbedder } from "./embedders/gemini"
|
||||
import { MistralEmbedder } from "./embedders/mistral"
|
||||
import { VercelAiGatewayEmbedder } from "./embedders/vercel-ai-gateway"
|
||||
import { OpenRouterEmbedder } from "./embedders/openrouter"
|
||||
import { EmbedderProvider, getDefaultModelId, getModelDimension } from "../../shared/embeddingModels"
|
||||
import { QdrantVectorStore } from "./vector-store/qdrant-client"
|
||||
import { codeParser, DirectoryScanner, FileWatcher } from "./processors"
|
||||
|
|
@ -79,6 +80,15 @@ export class CodeIndexServiceFactory {
|
|||
throw new Error(t("embeddings:serviceFactory.vercelAiGatewayConfigMissing"))
|
||||
}
|
||||
return new VercelAiGatewayEmbedder(config.vercelAiGatewayOptions.apiKey, config.modelId)
|
||||
} else if (provider === "openrouter") {
|
||||
if (!config.openRouterOptions?.apiKey) {
|
||||
throw new Error(t("embeddings:serviceFactory.openRouterConfigMissing"))
|
||||
}
|
||||
return new OpenRouterEmbedder(
|
||||
config.openRouterOptions.apiKey,
|
||||
config.modelId,
|
||||
config.openRouterOptions.baseUrl,
|
||||
)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
|
|
|
|||
|
|
@ -292,6 +292,7 @@ export interface WebviewMessage {
|
|||
| "gemini"
|
||||
| "mistral"
|
||||
| "vercel-ai-gateway"
|
||||
| "openrouter"
|
||||
codebaseIndexEmbedderBaseUrl?: string
|
||||
codebaseIndexEmbedderModelId: string
|
||||
codebaseIndexEmbedderModelDimension?: number // Generic dimension for all providers
|
||||
|
|
|
|||
|
|
@ -2,7 +2,14 @@
|
|||
* Defines profiles for different embedding models, including their dimensions.
|
||||
*/
|
||||
|
||||
export type EmbedderProvider = "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral" | "vercel-ai-gateway" // Add other providers as needed
|
||||
export type EmbedderProvider =
|
||||
| "openai"
|
||||
| "ollama"
|
||||
| "openai-compatible"
|
||||
| "gemini"
|
||||
| "mistral"
|
||||
| "vercel-ai-gateway"
|
||||
| "openrouter" // Add other providers as needed
|
||||
|
||||
export interface EmbeddingModelProfile {
|
||||
dimension: number
|
||||
|
|
@ -70,6 +77,19 @@ export const EMBEDDING_MODEL_PROFILES: EmbeddingModelProfiles = {
|
|||
"mistral/codestral-embed": { dimension: 1536, scoreThreshold: 0.4 },
|
||||
"mistral/mistral-embed": { dimension: 1024, scoreThreshold: 0.4 },
|
||||
},
|
||||
openrouter: {
|
||||
// OpenAI models available via OpenRouter
|
||||
"openai/text-embedding-3-small": { dimension: 1536, scoreThreshold: 0.4 },
|
||||
"openai/text-embedding-3-large": { dimension: 3072, scoreThreshold: 0.4 },
|
||||
"openai/text-embedding-ada-002": { dimension: 1536, scoreThreshold: 0.4 },
|
||||
// Cohere models
|
||||
"cohere/embed-english-v3.0": { dimension: 1024, scoreThreshold: 0.4 },
|
||||
"cohere/embed-multilingual-v3.0": { dimension: 1024, scoreThreshold: 0.4 },
|
||||
// Voyage models
|
||||
"voyage/voyage-embeddings-v3": { dimension: 1024, scoreThreshold: 0.4 },
|
||||
"voyage/voyage-3": { dimension: 1024, scoreThreshold: 0.4 },
|
||||
"voyage/voyage-3-lite": { dimension: 512, scoreThreshold: 0.4 },
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -163,6 +183,9 @@ export function getDefaultModelId(provider: EmbedderProvider): string {
|
|||
case "vercel-ai-gateway":
|
||||
return "openai/text-embedding-3-large"
|
||||
|
||||
case "openrouter":
|
||||
return "openai/text-embedding-3-small"
|
||||
|
||||
default:
|
||||
// Fallback for unknown providers
|
||||
console.warn(`Unknown provider for default model ID: ${provider}. Falling back to OpenAI default.`)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue