Skip to content

Commit 5ab3ae8

Browse files
committed
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
1 parent 3a8ba27 commit 5ab3ae8

File tree

8 files changed

+79
-6
lines changed

8 files changed

+79
-6
lines changed

packages/types/src/codebase-index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const codebaseIndexProviderSchema = z.object({
3636
codebaseIndexOpenAiCompatibleBaseUrl: z.string().optional(),
3737
codebaseIndexOpenAiCompatibleApiKey: z.string().optional(),
3838
codebaseIndexOpenAiCompatibleModelDimension: z.number().optional(),
39+
codebaseIndexOpenAiCompatibleApiVersion: z.string().optional(),
3940
})
4041

4142
export type CodebaseIndexProvider = z.infer<typeof codebaseIndexProviderSchema>

src/services/code-index/config-manager.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export class CodeIndexConfigManager {
1515
private modelId?: string
1616
private openAiOptions?: ApiHandlerOptions
1717
private ollamaOptions?: ApiHandlerOptions
18-
private openAiCompatibleOptions?: { baseUrl: string; apiKey: string; modelDimension?: number }
18+
private openAiCompatibleOptions?: { baseUrl: string; apiKey: string; modelDimension?: number; apiVersion?: string }
1919
private qdrantUrl?: string = "http://localhost:6333"
2020
private qdrantApiKey?: string
2121
private searchMinScore?: number
@@ -55,6 +55,8 @@ export class CodeIndexConfigManager {
5555
const openAiCompatibleModelDimension = this.contextProxy?.getGlobalState(
5656
"codebaseIndexOpenAiCompatibleModelDimension",
5757
) as number | undefined
58+
const openAiCompatibleApiVersion =
59+
this.contextProxy?.getGlobalState("codebaseIndexOpenAiCompatibleApiVersion") ?? ""
5860

5961
// Update instance variables with configuration
6062
this.isEnabled = codebaseIndexEnabled || false
@@ -84,6 +86,7 @@ export class CodeIndexConfigManager {
8486
baseUrl: openAiCompatibleBaseUrl,
8587
apiKey: openAiCompatibleApiKey,
8688
modelDimension: openAiCompatibleModelDimension,
89+
apiVersion: openAiCompatibleApiVersion || undefined,
8790
}
8891
: undefined
8992
}
@@ -100,7 +103,7 @@ export class CodeIndexConfigManager {
100103
modelId?: string
101104
openAiOptions?: ApiHandlerOptions
102105
ollamaOptions?: ApiHandlerOptions
103-
openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
106+
openAiCompatibleOptions?: { baseUrl: string; apiKey: string; apiVersion?: string }
104107
qdrantUrl?: string
105108
qdrantApiKey?: string
106109
searchMinScore?: number
@@ -118,6 +121,7 @@ export class CodeIndexConfigManager {
118121
openAiCompatibleBaseUrl: this.openAiCompatibleOptions?.baseUrl ?? "",
119122
openAiCompatibleApiKey: this.openAiCompatibleOptions?.apiKey ?? "",
120123
openAiCompatibleModelDimension: this.openAiCompatibleOptions?.modelDimension,
124+
openAiCompatibleApiVersion: this.openAiCompatibleOptions?.apiVersion ?? "",
121125
qdrantUrl: this.qdrantUrl ?? "",
122126
qdrantApiKey: this.qdrantApiKey ?? "",
123127
}
@@ -185,6 +189,7 @@ export class CodeIndexConfigManager {
185189
const prevOpenAiCompatibleBaseUrl = prev?.openAiCompatibleBaseUrl ?? ""
186190
const prevOpenAiCompatibleApiKey = prev?.openAiCompatibleApiKey ?? ""
187191
const prevOpenAiCompatibleModelDimension = prev?.openAiCompatibleModelDimension
192+
const prevOpenAiCompatibleApiVersion = prev?.openAiCompatibleApiVersion ?? ""
188193
const prevQdrantUrl = prev?.qdrantUrl ?? ""
189194
const prevQdrantApiKey = prev?.qdrantApiKey ?? ""
190195

@@ -233,10 +238,12 @@ export class CodeIndexConfigManager {
233238
const currentOpenAiCompatibleBaseUrl = this.openAiCompatibleOptions?.baseUrl ?? ""
234239
const currentOpenAiCompatibleApiKey = this.openAiCompatibleOptions?.apiKey ?? ""
235240
const currentOpenAiCompatibleModelDimension = this.openAiCompatibleOptions?.modelDimension
241+
const currentOpenAiCompatibleApiVersion = this.openAiCompatibleOptions?.apiVersion ?? ""
236242
if (
237243
prevOpenAiCompatibleBaseUrl !== currentOpenAiCompatibleBaseUrl ||
238244
prevOpenAiCompatibleApiKey !== currentOpenAiCompatibleApiKey ||
239-
prevOpenAiCompatibleModelDimension !== currentOpenAiCompatibleModelDimension
245+
prevOpenAiCompatibleModelDimension !== currentOpenAiCompatibleModelDimension ||
246+
prevOpenAiCompatibleApiVersion !== currentOpenAiCompatibleApiVersion
240247
) {
241248
return true
242249
}

src/services/code-index/embedders/__tests__/openai-compatible.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,39 @@ describe("OpenAICompatibleEmbedder", () => {
9292
"Base URL is required for OpenAI Compatible embedder",
9393
)
9494
})
95+
96+
it("should append api-version query parameter when provided", () => {
97+
const apiVersion = "2023-05-15"
98+
embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId, apiVersion)
99+
100+
expect(MockedOpenAI).toHaveBeenCalledWith({
101+
baseURL: `${testBaseUrl}?api-version=${apiVersion}`,
102+
apiKey: testApiKey,
103+
})
104+
expect(embedder).toBeDefined()
105+
})
106+
107+
it("should handle api-version with existing query parameters", () => {
108+
const baseUrlWithParams = "https://api.example.com/v1?existing=param"
109+
const apiVersion = "2023-05-15"
110+
embedder = new OpenAICompatibleEmbedder(baseUrlWithParams, testApiKey, testModelId, apiVersion)
111+
112+
expect(MockedOpenAI).toHaveBeenCalledWith({
113+
baseURL: `${baseUrlWithParams}&api-version=${apiVersion}`,
114+
apiKey: testApiKey,
115+
})
116+
expect(embedder).toBeDefined()
117+
})
118+
119+
it("should not modify baseURL when api-version is not provided", () => {
120+
embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
121+
122+
expect(MockedOpenAI).toHaveBeenCalledWith({
123+
baseURL: testBaseUrl,
124+
apiKey: testApiKey,
125+
})
126+
expect(embedder).toBeDefined()
127+
})
95128
})
96129

97130
describe("embedderInfo", () => {

src/services/code-index/embedders/openai-compatible.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,25 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
3535
* @param baseUrl The base URL for the OpenAI-compatible API endpoint
3636
* @param apiKey The API key for authentication
3737
* @param modelId Optional model identifier (defaults to "text-embedding-3-small")
38+
* @param apiVersion Optional API version for Azure OpenAI compatibility
3839
*/
39-
constructor(baseUrl: string, apiKey: string, modelId?: string) {
40+
constructor(baseUrl: string, apiKey: string, modelId?: string, apiVersion?: string) {
4041
if (!baseUrl) {
4142
throw new Error("Base URL is required for OpenAI Compatible embedder")
4243
}
4344
if (!apiKey) {
4445
throw new Error("API key is required for OpenAI Compatible embedder")
4546
}
4647

48+
// For Azure OpenAI, we need to append the api-version query parameter to the baseURL
49+
let finalBaseUrl = baseUrl
50+
if (apiVersion) {
51+
const separator = baseUrl.includes("?") ? "&" : "?"
52+
finalBaseUrl = `${baseUrl}${separator}api-version=${apiVersion}`
53+
}
54+
4755
this.embeddingsClient = new OpenAI({
48-
baseURL: baseUrl,
56+
baseURL: finalBaseUrl,
4957
apiKey: apiKey,
5058
})
5159
this.defaultModelId = modelId || getDefaultModelId("openai-compatible")

src/services/code-index/interfaces/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface CodeIndexConfig {
1111
modelId?: string
1212
openAiOptions?: ApiHandlerOptions
1313
ollamaOptions?: ApiHandlerOptions
14-
openAiCompatibleOptions?: { baseUrl: string; apiKey: string; modelDimension?: number }
14+
openAiCompatibleOptions?: { baseUrl: string; apiKey: string; modelDimension?: number; apiVersion?: string }
1515
qdrantUrl?: string
1616
qdrantApiKey?: string
1717
searchMinScore?: number
@@ -30,6 +30,7 @@ export type PreviousConfigSnapshot = {
3030
openAiCompatibleBaseUrl?: string
3131
openAiCompatibleApiKey?: string
3232
openAiCompatibleModelDimension?: number
33+
openAiCompatibleApiVersion?: string
3334
qdrantUrl?: string
3435
qdrantApiKey?: string
3536
}

src/services/code-index/service-factory.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export class CodeIndexServiceFactory {
5252
config.openAiCompatibleOptions.baseUrl,
5353
config.openAiCompatibleOptions.apiKey,
5454
config.modelId,
55+
config.openAiCompatibleOptions.apiVersion,
5556
)
5657
}
5758

webview-ui/src/components/settings/CodeIndexSettings.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
144144
.int("Dimension must be an integer")
145145
.positive("Dimension must be a positive number")
146146
.optional(),
147+
codebaseIndexOpenAiCompatibleApiVersion: z.string().optional(),
147148
}),
148149
}
149150

@@ -161,6 +162,7 @@ export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
161162
codebaseIndexOpenAiCompatibleBaseUrl: apiConfig.codebaseIndexOpenAiCompatibleBaseUrl,
162163
codebaseIndexOpenAiCompatibleApiKey: apiConfig.codebaseIndexOpenAiCompatibleApiKey,
163164
codebaseIndexOpenAiCompatibleModelDimension: apiConfig.codebaseIndexOpenAiCompatibleModelDimension,
165+
codebaseIndexOpenAiCompatibleApiVersion: apiConfig.codebaseIndexOpenAiCompatibleApiVersion,
164166
})
165167
return true
166168
} catch {
@@ -320,6 +322,24 @@ export const CodeIndexSettings: React.FC<CodeIndexSettingsProps> = ({
320322
}
321323
style={{ width: "100%" }}></VSCodeTextField>
322324
</div>
325+
<div className="flex items-center gap-4 font-bold">
326+
<div>{t("settings:codeIndex.openaiCompatibleApiVersionLabel")}</div>
327+
</div>
328+
<div>
329+
<VSCodeTextField
330+
value={apiConfiguration.codebaseIndexOpenAiCompatibleApiVersion || ""}
331+
onInput={(e: any) =>
332+
setApiConfigurationField(
333+
"codebaseIndexOpenAiCompatibleApiVersion",
334+
e.target.value,
335+
)
336+
}
337+
placeholder="2023-05-15"
338+
style={{ width: "100%" }}></VSCodeTextField>
339+
<p className="text-vscode-descriptionForeground text-sm mt-1">
340+
{t("settings:codeIndex.openaiCompatibleApiVersionDescription")}
341+
</p>
342+
</div>
323343
</div>
324344
)}
325345

webview-ui/src/i18n/locales/en/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
"openaiKeyLabel": "OpenAI Key:",
4949
"openaiCompatibleBaseUrlLabel": "Base URL:",
5050
"openaiCompatibleApiKeyLabel": "API Key:",
51+
"openaiCompatibleApiVersionLabel": "API Version:",
52+
"openaiCompatibleApiVersionDescription": "Required for Azure OpenAI. Common values: 2023-05-15, 2024-02-01, 2024-06-01. Leave empty for other OpenAI-compatible providers.",
5153
"openaiCompatibleModelDimensionLabel": "Embedding Dimension:",
5254
"openaiCompatibleModelDimensionPlaceholder": "e.g., 1536",
5355
"openaiCompatibleModelDimensionDescription": "The embedding dimension (output size) for your model. Check your provider's documentation for this value. Common values: 384, 768, 1536, 3072.",

0 commit comments

Comments
 (0)