Skip to content

Commit 2e12a3e

Browse files
committed
fix(azure): correct Azure OpenAI API URL construction and auth
- Fixes Azure OpenAI authentication issues by implementing the correct URL format: /openai/deployments/{deployment-id}/{operation}
1 parent a473e04 commit 2e12a3e

File tree

2 files changed

+29
-15
lines changed

2 files changed

+29
-15
lines changed

azure/azure.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import (
3838
// WithEndpoint configures this client to connect to an Azure OpenAI endpoint.
3939
//
4040
// - endpoint - the Azure OpenAI endpoint to connect to. Ex: https://<azure-openai-resource>.openai.azure.com
41-
// - apiVersion - the Azure OpenAI API version to target (ex: 2024-06-01). See [Azure OpenAI apiversions] for current API versions. This value cannot be empty.
41+
// - apiVersion - the Azure OpenAI API version to target (ex: 2024-10-21). See [Azure OpenAI apiversions] for current API versions. This value cannot be empty.
4242
//
4343
// This function should be paired with a call to authenticate, like [azure.WithAPIKey] or [azure.WithTokenCredential], similar to this:
4444
//
@@ -54,8 +54,6 @@ func WithEndpoint(endpoint string, apiVersion string) option.RequestOption {
5454
endpoint += "/"
5555
}
5656

57-
endpoint += "openai/"
58-
5957
withQueryAdd := option.WithQueryAdd("api-version", apiVersion)
6058
withEndpoint := option.WithBaseURL(endpoint)
6159

@@ -72,7 +70,7 @@ func WithEndpoint(endpoint string, apiVersion string) option.RequestOption {
7270

7371
return requestconfig.RequestOptionFunc(func(rc *requestconfig.RequestConfig) error {
7472
if apiVersion == "" {
75-
return errors.New("apiVersion is an empty string, but needs to be set. See https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versioning for details.")
73+
return errors.New("apiVersion is an empty string, but needs to be set. See https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versioning for details")
7674
}
7775

7876
if err := withQueryAdd.Apply(rc); err != nil {
@@ -129,18 +127,18 @@ func WithAPIKey(apiKey string) option.RequestOption {
129127
// jsonRoutes have JSON payloads - we'll deserialize looking for a .model field in there
130128
// so we won't have to worry about individual types for completions vs embeddings, etc...
131129
var jsonRoutes = map[string]bool{
132-
"/openai/completions": true,
133-
"/openai/chat/completions": true,
134-
"/openai/embeddings": true,
135-
"/openai/audio/speech": true,
136-
"/openai/images/generations": true,
130+
"/completions": true,
131+
"/chat/completions": true,
132+
"/embeddings": true,
133+
"/audio/speech": true,
134+
"/images/generations": true,
137135
}
138136

139137
// audioMultipartRoutes have mime/multipart payloads. These are less generic - we're very much
140138
// expecting a transcription or translation payload for these.
141139
var audioMultipartRoutes = map[string]bool{
142-
"/openai/audio/transcriptions": true,
143-
"/openai/audio/translations": true,
140+
"/audio/transcriptions": true,
141+
"/audio/translations": true,
144142
}
145143

146144
// getReplacementPathWithDeployment parses the request body to extract out the Model parameter (or equivalent)
@@ -178,7 +176,8 @@ func getJSONRoute(req *http.Request) (string, error) {
178176
}
179177

180178
escapedDeployment := url.PathEscape(v.Model)
181-
return strings.Replace(req.URL.Path, "/openai/", "/openai/deployments/"+escapedDeployment+"/", 1), nil
179+
// Convert path from /chat/completions to /openai/deployments/{deployment-id}/chat/completions
180+
return "/openai/deployments/" + escapedDeployment + req.URL.Path, nil
182181
}
183182

184183
func getAudioMultipartRoute(req *http.Request) (string, error) {
@@ -223,7 +222,8 @@ func getAudioMultipartRoute(req *http.Request) (string, error) {
223222
}
224223

225224
escapedDeployment := url.PathEscape(string(modelBytes))
226-
return strings.Replace(req.URL.Path, "/openai/", "/openai/deployments/"+escapedDeployment+"/", 1), nil
225+
// Convert path from /audio/transcriptions to /openai/deployments/{deployment-id}/audio/transcriptions
226+
return "/openai/deployments/" + escapedDeployment + req.URL.Path, nil
227227
}
228228
}
229229
}

azure/azure_test.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func TestJSONRoute(t *testing.T) {
2525
t.Fatal(err)
2626
}
2727

28-
req, err := http.NewRequest("POST", "/openai/chat/completions", bytes.NewReader(serializedBytes))
28+
req, err := http.NewRequest("POST", "/chat/completions", bytes.NewReader(serializedBytes))
2929

3030
if err != nil {
3131
t.Fatal(err)
@@ -65,7 +65,7 @@ func TestGetAudioMultipartRoute(t *testing.T) {
6565
t.Fatal(err)
6666
}
6767

68-
req, err := http.NewRequest("POST", "/openai/audio/transcriptions", bytes.NewReader(buff.Bytes()))
68+
req, err := http.NewRequest("POST", "/audio/transcriptions", bytes.NewReader(buff.Bytes()))
6969

7070
if err != nil {
7171
t.Fatal(err)
@@ -115,3 +115,17 @@ func TestNoRouteChangeNeeded(t *testing.T) {
115115
t.Fatalf("replacementpath didn't match: %s", replacementPath)
116116
}
117117
}
118+
119+
func TestAPIKeyAuthentication(t *testing.T) {
120+
// Test that the API key option is created successfully
121+
apiKeyOption := WithAPIKey("test-api-key")
122+
123+
// Verify the option is not nil
124+
if apiKeyOption == nil {
125+
t.Fatal("Expected API key option to be created")
126+
}
127+
128+
// This test verifies the option is created correctly.
129+
// The actual header setting happens in the middleware chain.
130+
t.Log("API key option created successfully")
131+
}

0 commit comments

Comments
 (0)