feat: Add API version support for Azure OpenAI in codebase indexing

- Add optional apiVersion field to OpenAI Compatible provider schema
- Update config manager to handle API version parameter
- Modify OpenAICompatibleEmbedder to append api-version query parameter
- Add API version input field to settings UI with validation
- Include comprehensive test coverage for API version functionality
- Add i18n support for new API version field

Fixes #5212: Azure OpenAI embedding requests now include required api-version parameter
This commit is contained in:
Roo Code 2025-06-30 09:45:32 +00:00
parent 3a8ba27615
commit 5ab3ae8f3a
8 changed files with 79 additions and 6 deletions

View file

@ -36,6 +36,7 @@ export const codebaseIndexProviderSchema = z.object({
codebaseIndexOpenAiCompatibleBaseUrl: z.string().optional(),
codebaseIndexOpenAiCompatibleApiKey: z.string().optional(),
codebaseIndexOpenAiCompatibleModelDimension: z.number().optional(),
codebaseIndexOpenAiCompatibleApiVersion: z.string().optional(),
})
export type CodebaseIndexProvider = z.infer<typeof codebaseIndexProviderSchema>

View file

@ -15,7 +15,7 @@ export class CodeIndexConfigManager {
private modelId?: string
private openAiOptions?: ApiHandlerOptions
private ollamaOptions?: ApiHandlerOptions
private openAiCompatibleOptions?: { baseUrl: string; apiKey: string; modelDimension?: number }
private openAiCompatibleOptions?: { baseUrl: string; apiKey: string; modelDimension?: number; apiVersion?: string }
private qdrantUrl?: string = "http://localhost:6333"
private qdrantApiKey?: string
private searchMinScore?: number
@ -55,6 +55,8 @@ export class CodeIndexConfigManager {
const openAiCompatibleModelDimension = this.contextProxy?.getGlobalState(
"codebaseIndexOpenAiCompatibleModelDimension",
) as number | undefined
const openAiCompatibleApiVersion =
this.contextProxy?.getGlobalState("codebaseIndexOpenAiCompatibleApiVersion") ?? ""
// Update instance variables with configuration
this.isEnabled = codebaseIndexEnabled || false
@ -84,6 +86,7 @@ export class CodeIndexConfigManager {
baseUrl: openAiCompatibleBaseUrl,
apiKey: openAiCompatibleApiKey,
modelDimension: openAiCompatibleModelDimension,
apiVersion: openAiCompatibleApiVersion || undefined,
}
: undefined
}
@ -100,7 +103,7 @@ export class CodeIndexConfigManager {
modelId?: string
openAiOptions?: ApiHandlerOptions
ollamaOptions?: ApiHandlerOptions
openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
openAiCompatibleOptions?: { baseUrl: string; apiKey: string; apiVersion?: string }
qdrantUrl?: string
qdrantApiKey?: string
searchMinScore?: number
@ -118,6 +121,7 @@ export class CodeIndexConfigManager {
openAiCompatibleBaseUrl: this.openAiCompatibleOptions?.baseUrl ?? "",
openAiCompatibleApiKey: this.openAiCompatibleOptions?.apiKey ?? "",
openAiCompatibleModelDimension: this.openAiCompatibleOptions?.modelDimension,
openAiCompatibleApiVersion: this.openAiCompatibleOptions?.apiVersion ?? "",
qdrantUrl: this.qdrantUrl ?? "",
qdrantApiKey: this.qdrantApiKey ?? "",
}
@ -185,6 +189,7 @@ export class CodeIndexConfigManager {
const prevOpenAiCompatibleBaseUrl = prev?.openAiCompatibleBaseUrl ?? ""
const prevOpenAiCompatibleApiKey = prev?.openAiCompatibleApiKey ?? ""
const prevOpenAiCompatibleModelDimension = prev?.openAiCompatibleModelDimension
const prevOpenAiCompatibleApiVersion = prev?.openAiCompatibleApiVersion ?? ""
const prevQdrantUrl = prev?.qdrantUrl ?? ""
const prevQdrantApiKey = prev?.qdrantApiKey ?? ""
@ -233,10 +238,12 @@ export class CodeIndexConfigManager {
const currentOpenAiCompatibleBaseUrl = this.openAiCompatibleOptions?.baseUrl ?? ""
const currentOpenAiCompatibleApiKey = this.openAiCompatibleOptions?.apiKey ?? ""
const currentOpenAiCompatibleModelDimension = this.openAiCompatibleOptions?.modelDimension
const currentOpenAiCompatibleApiVersion = this.openAiCompatibleOptions?.apiVersion ?? ""
if (
prevOpenAiCompatibleBaseUrl !== currentOpenAiCompatibleBaseUrl ||
prevOpenAiCompatibleApiKey !== currentOpenAiCompatibleApiKey ||
prevOpenAiCompatibleModelDimension !== currentOpenAiCompatibleModelDimension
prevOpenAiCompatibleModelDimension !== currentOpenAiCompatibleModelDimension ||
prevOpenAiCompatibleApiVersion !== currentOpenAiCompatibleApiVersion
) {
return true
}

View file

@ -92,6 +92,39 @@ describe("OpenAICompatibleEmbedder", () => {
"Base URL is required for OpenAI Compatible embedder",
)
})
it("should append api-version query parameter when provided", () => {
const apiVersion = "2023-05-15"
embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId, apiVersion)
expect(MockedOpenAI).toHaveBeenCalledWith({
baseURL: `${testBaseUrl}?api-version=${apiVersion}`,
apiKey: testApiKey,
})
expect(embedder).toBeDefined()
})
it("should handle api-version with existing query parameters", () => {
const baseUrlWithParams = "https://api.example.com/v1?existing=param"
const apiVersion = "2023-05-15"
embedder = new OpenAICompatibleEmbedder(baseUrlWithParams, testApiKey, testModelId, apiVersion)
expect(MockedOpenAI).toHaveBeenCalledWith({
baseURL: `${baseUrlWithParams}&api-version=${apiVersion}`,
apiKey: testApiKey,
})
expect(embedder).toBeDefined()
})
it("should not modify baseURL when api-version is not provided", () => {
embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
expect(MockedOpenAI).toHaveBeenCalledWith({
baseURL: testBaseUrl,
apiKey: testApiKey,
})
expect(embedder).toBeDefined()
})
})
describe("embedderInfo", () => {

View file

@ -35,8 +35,9 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
* @param baseUrl The base URL for the OpenAI-compatible API endpoint
* @param apiKey The API key for authentication
* @param modelId Optional model identifier (defaults to "text-embedding-3-small")
* @param apiVersion Optional API version for Azure OpenAI compatibility
*/
constructor(baseUrl: string, apiKey: string, modelId?: string) {
constructor(baseUrl: string, apiKey: string, modelId?: string, apiVersion?: string) {
if (!baseUrl) {
throw new Error("Base URL is required for OpenAI Compatible embedder")
}
@ -44,8 +45,15 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
throw new Error("API key is required for OpenAI Compatible embedder")
}
// For Azure OpenAI, we need to append the api-version query parameter to the baseURL
let finalBaseUrl = baseUrl
if (apiVersion) {
const separator = baseUrl.includes("?") ? "&" : "?"
finalBaseUrl = `${baseUrl}${separator}api-version=${apiVersion}`
}
this.embeddingsClient = new OpenAI({
baseURL: baseUrl,
baseURL: finalBaseUrl,
apiKey: apiKey,
})
this.defaultModelId = modelId || getDefaultModelId("openai-compatible")

View file

@ -11,7 +11,7 @@ export interface CodeIndexConfig {
modelId?: string
openAiOptions?: ApiHandlerOptions
ollamaOptions?: ApiHandlerOptions
openAiCompatibleOptions?: { baseUrl: string; apiKey: string; modelDimension?: number }
openAiCompatibleOptions?: { baseUrl: string; apiKey: string; modelDimension?: number; apiVersion?: string }
qdrantUrl?: string
qdrantApiKey?: string
searchMinScore?: number
@ -30,6 +30,7 @@ export type PreviousConfigSnapshot = {
openAiCompatibleBaseUrl?: string
openAiCompatibleApiKey?: string
openAiCompatibleModelDimension?: number
openAiCompatibleApiVersion?: string
qdrantUrl?: string
qdrantApiKey?: string
}

View file

@ -52,6 +52,7 @@ export class CodeIndexServiceFactory {
config.openAiCompatibleOptions.baseUrl,
config.openAiCompatibleOptions.apiKey,
config.modelId,
config.openAiCompatibleOptions.apiVersion,
)
}

View file

@ -144,6 +144,7 @@ export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
.int("Dimension must be an integer")
.positive("Dimension must be a positive number")
.optional(),
codebaseIndexOpenAiCompatibleApiVersion: z.string().optional(),
}),
}
@ -161,6 +162,7 @@ export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
codebaseIndexOpenAiCompatibleBaseUrl: apiConfig.codebaseIndexOpenAiCompatibleBaseUrl,
codebaseIndexOpenAiCompatibleApiKey: apiConfig.codebaseIndexOpenAiCompatibleApiKey,
codebaseIndexOpenAiCompatibleModelDimension: apiConfig.codebaseIndexOpenAiCompatibleModelDimension,
codebaseIndexOpenAiCompatibleApiVersion: apiConfig.codebaseIndexOpenAiCompatibleApiVersion,
})
return true
} catch {
@ -320,6 +322,24 @@ export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
}
style={{ width: "100%" }}></VSCodeTextField>
</div>
<div className="flex items-center gap-4 font-bold">
<div>{t("settings:codeIndex.openaiCompatibleApiVersionLabel")}</div>
</div>
<div>
<VSCodeTextField
value={apiConfiguration.codebaseIndexOpenAiCompatibleApiVersion || ""}
onInput={(e: any) =>
setApiConfigurationField(
"codebaseIndexOpenAiCompatibleApiVersion",
e.target.value,
)
}
placeholder="2023-05-15"
style={{ width: "100%" }}></VSCodeTextField>
<p className="text-vscode-descriptionForeground text-sm mt-1">
{t("settings:codeIndex.openaiCompatibleApiVersionDescription")}
</p>
</div>
</div>
)}

View file

@ -48,6 +48,8 @@
"openaiKeyLabel": "OpenAI Key:",
"openaiCompatibleBaseUrlLabel": "Base URL:",
"openaiCompatibleApiKeyLabel": "API Key:",
"openaiCompatibleApiVersionLabel": "API Version:",
"openaiCompatibleApiVersionDescription": "Required for Azure OpenAI. Common values: 2023-05-15, 2024-02-01, 2024-06-01. Leave empty for other OpenAI-compatible providers.",
"openaiCompatibleModelDimensionLabel": "Embedding Dimension:",
"openaiCompatibleModelDimensionPlaceholder": "e.g., 1536",
"openaiCompatibleModelDimensionDescription": "The embedding dimension (output size) for your model. Check your provider's documentation for this value. Common values: 384, 768, 1536, 3072.",