From 623c8da8d8e8d93ba562b814dcf2c54a9be42736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Tue, 23 Sep 2025 18:49:55 +0800 Subject: [PATCH] fix(ai-proxy): Fix Azure OpenAI Response API handling and service URL type detection (#2948) --- .../extensions/ai-proxy/provider/azure.go | 12 ++- .../extensions/ai-proxy/provider/provider.go | 2 +- .../wasm-go/extensions/ai-proxy/test/azure.go | 84 +++++++++++++++++++ 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/plugins/wasm-go/extensions/ai-proxy/provider/azure.go b/plugins/wasm-go/extensions/ai-proxy/provider/azure.go index 1a68af01b..ea763abf0 100644 --- a/plugins/wasm-go/extensions/ai-proxy/provider/azure.go +++ b/plugins/wasm-go/extensions/ai-proxy/provider/azure.go @@ -38,6 +38,7 @@ var ( ApiNameFiles: true, ApiNameRetrieveFile: true, ApiNameRetrieveFileContent: true, + ApiNameResponses: true, } regexAzureModelWithPath = regexp.MustCompile("/openai/deployments/(.+?)(?:/(.*)|$)") ) @@ -100,8 +101,15 @@ func (m *azureProviderInitializer) CreateProvider(config ProviderConfig) (Provid } log.Debugf("azureProvider: found default model from serviceUrl: %s", defaultModel) } else { - serviceUrlType = azureServiceUrlTypeDomainOnly - log.Debugf("azureProvider: no default model found in serviceUrl") + // If path doesn't match the /openai/deployments pattern, + // check if it's a custom full path or domain only + if serviceUrl.Path != "" && serviceUrl.Path != "/" { + serviceUrlType = azureServiceUrlTypeFull + log.Debugf("azureProvider: using custom full path: %s", serviceUrl.Path) + } else { + serviceUrlType = azureServiceUrlTypeDomainOnly + log.Debugf("azureProvider: no default model found in serviceUrl") + } } log.Debugf("azureProvider: serviceUrlType=%d", serviceUrlType) diff --git a/plugins/wasm-go/extensions/ai-proxy/provider/provider.go b/plugins/wasm-go/extensions/ai-proxy/provider/provider.go index aecfb71ff..b90082241 100644 --- a/plugins/wasm-go/extensions/ai-proxy/provider/provider.go +++ b/plugins/wasm-go/extensions/ai-proxy/provider/provider.go @@ -825,7 +825,7 @@ func ExtractStreamingEvents(ctx wrapper.HttpContext, chunk []byte) []StreamEvent continue } - if lineStartIndex != -1 { + if lineStartIndex != -1 && valueStartIndex != -1 { value := string(body[valueStartIndex:i]) currentEvent.SetValue(currentKey, value) } else { diff --git a/plugins/wasm-go/extensions/ai-proxy/test/azure.go b/plugins/wasm-go/extensions/ai-proxy/test/azure.go index d1c34ace9..53da958f1 100644 --- a/plugins/wasm-go/extensions/ai-proxy/test/azure.go +++ b/plugins/wasm-go/extensions/ai-proxy/test/azure.go @@ -146,6 +146,20 @@ var azureInvalidConfigMissingToken = func() json.RawMessage { return data }() +// 测试配置:Azure OpenAI Response API配置 +var azureResponseAPIConfig = func() json.RawMessage { + data, _ := json.Marshal(map[string]interface{}{ + "provider": map[string]interface{}{ + "type": "azure", + "apiTokens": []string{ + "sk-azure-multi", + }, + "azureServiceUrl": "https://multi-resource.openai.azure.com/openai/responses?api-version=2025-04-01-preview", + }, + }) + return data +}() + func RunAzureParseConfigTests(t *testing.T) { test.RunGoTest(t, func(t *testing.T) { // 测试基本Azure OpenAI配置解析 @@ -203,6 +217,17 @@ func RunAzureParseConfigTests(t *testing.T) { require.NotNil(t, config) }) + // 测试Azure Response API 配置解析 + t.Run("azure response api config", func(t *testing.T) { + host, status := test.NewTestHost(azureResponseAPIConfig) + defer host.Reset() + require.Equal(t, types.OnPluginStartStatusOK, status) + + config, err := host.GetMatchConfig() + require.NoError(t, err) + require.NotNil(t, config) + }) + // 测试Azure OpenAI无效配置(缺少azureServiceUrl) t.Run("azure invalid config missing url", func(t *testing.T) { host, status := test.NewTestHost(azureInvalidConfigMissingUrl) @@ -411,6 +436,61 @@ func RunAzureOnHttpRequestBodyTests(t *testing.T) { require.Equal(t, "gpt-4", model, "Model should be mapped correctly") }) + // 测试Azure OpenAI Response API 处理 + t.Run("azure response api request body", func(t *testing.T) { + host, status := test.NewTestHost(azureResponseAPIConfig) + defer host.Reset() + require.Equal(t, types.OnPluginStartStatusOK, status) + + // 设置请求头 + action := host.CallOnHttpRequestHeaders([][2]string{ + {":authority", "example.com"}, + {":path", "/responses/v1/responses"}, + {":method", "POST"}, + {"Content-Type", "application/json"}, + }) + require.Equal(t, types.HeaderStopIteration, action) + + // 设置请求体 + requestBody := `{ + "input": [ + { + "role": "user", + "content": [ + { + "type": "input_text", + "text": "Explain quantum computing" + } + ] + } + ], + "model": "gpt-5", + "reasoning": { + "effort": "medium" + } + }` + action = host.CallOnHttpRequestBody([]byte(requestBody)) + require.Equal(t, types.ActionContinue, action) + + // 验证请求体是否被正确处理 + transformedBody := host.GetRequestBody() + require.NotNil(t, transformedBody) + + var bodyMap map[string]interface{} + err := json.Unmarshal(transformedBody, &bodyMap) + require.NoError(t, err) + + model, exists := bodyMap["model"] + require.True(t, exists, "Model should exist in request body") + require.Equal(t, "gpt-5", model, "Model should be mapped correctly") + + // 验证请求路径是否被正确转换 + requestHeaders := host.GetRequestHeaders() + pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path") + require.True(t, hasPath, "Path header should exist") + require.Equal(t, pathValue, "/openai/responses?api-version=2025-04-01-preview", "Path should not equal Azure response api path") + }) + // 测试Azure OpenAI请求体处理(仅部署配置) t.Run("azure deployment only request body", func(t *testing.T) { host, status := test.NewTestHost(azureDeploymentOnlyConfig) @@ -566,6 +646,10 @@ func RunAzureOnHttpResponseBodyTests(t *testing.T) { } ] }` + action = host.CallOnHttpResponseHeaders([][2]string{ + {"Content-Type", "application/json"}, + }) + require.Equal(t, types.ActionContinue, action) action = host.CallOnHttpRequestBody([]byte(requestBody)) require.Equal(t, types.ActionContinue, action)