mirror of
https://github.com/alibaba/higress.git
synced 2026-06-06 11:17:29 +08:00
Feat/new api path support (#3620)
This commit is contained in:
116
plugins/wasm-go/extensions/ai-proxy/test/api_paths.go
Normal file
116
plugins/wasm-go/extensions/ai-proxy/test/api_paths.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
wasmtest "github.com/higress-group/wasm-go/pkg/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func openAICustomEndpointConfig(customURL string) json.RawMessage {
|
||||
data, _ := json.Marshal(map[string]interface{}{
|
||||
"provider": map[string]interface{}{
|
||||
"type": "openai",
|
||||
"apiTokens": []string{"sk-openai-test-custom-endpoint"},
|
||||
"modelMapping": map[string]string{
|
||||
"*": "gpt-4o-mini",
|
||||
},
|
||||
"openaiCustomUrl": customURL,
|
||||
},
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
var openAICustomAudioTranscriptionsEndpointConfig = openAICustomEndpointConfig("https://custom.openai.com/v1/audio/transcriptions")
|
||||
var openAICustomAudioTranslationsEndpointConfig = openAICustomEndpointConfig("https://custom.openai.com/v1/audio/translations")
|
||||
var openAICustomRealtimeEndpointConfig = openAICustomEndpointConfig("https://custom.openai.com/v1/realtime")
|
||||
var openAICustomRealtimeSessionsEndpointConfig = openAICustomEndpointConfig("https://custom.openai.com/v1/realtime/sessions")
|
||||
|
||||
func RunApiPathRegressionTests(t *testing.T) {
|
||||
wasmtest.RunTest(t, func(t *testing.T) {
|
||||
t.Run("openai direct custom endpoint audio transcriptions", func(t *testing.T) {
|
||||
host, status := wasmtest.NewTestHost(openAICustomAudioTranscriptionsEndpointConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/audio/transcriptions"},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
require.Equal(t, types.HeaderStopIteration, action)
|
||||
|
||||
requestHeaders := host.GetRequestHeaders()
|
||||
pathValue, hasPath := wasmtest.GetHeaderValue(requestHeaders, ":path")
|
||||
require.True(t, hasPath)
|
||||
require.Equal(t, "/v1/audio/transcriptions", pathValue)
|
||||
})
|
||||
|
||||
t.Run("openai direct custom endpoint audio translations", func(t *testing.T) {
|
||||
host, status := wasmtest.NewTestHost(openAICustomAudioTranslationsEndpointConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/audio/translations"},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
require.Equal(t, types.HeaderStopIteration, action)
|
||||
|
||||
requestHeaders := host.GetRequestHeaders()
|
||||
pathValue, hasPath := wasmtest.GetHeaderValue(requestHeaders, ":path")
|
||||
require.True(t, hasPath)
|
||||
require.Equal(t, "/v1/audio/translations", pathValue)
|
||||
})
|
||||
|
||||
t.Run("openai direct custom endpoint realtime", func(t *testing.T) {
|
||||
host, status := wasmtest.NewTestHost(openAICustomRealtimeEndpointConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/realtime"},
|
||||
{":method", "GET"},
|
||||
{"Connection", "Upgrade"},
|
||||
{"Upgrade", "websocket"},
|
||||
{"Sec-WebSocket-Version", "13"},
|
||||
{"Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="},
|
||||
})
|
||||
require.True(t, action == types.ActionContinue || action == types.HeaderStopIteration)
|
||||
|
||||
requestHeaders := host.GetRequestHeaders()
|
||||
pathValue, hasPath := wasmtest.GetHeaderValue(requestHeaders, ":path")
|
||||
require.True(t, hasPath)
|
||||
require.Equal(t, "/v1/realtime", pathValue)
|
||||
})
|
||||
|
||||
t.Run("openai non-direct endpoint appends mapped realtime suffix", func(t *testing.T) {
|
||||
host, status := wasmtest.NewTestHost(openAICustomRealtimeSessionsEndpointConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/realtime"},
|
||||
{":method", "GET"},
|
||||
{"Connection", "Upgrade"},
|
||||
{"Upgrade", "websocket"},
|
||||
{"Sec-WebSocket-Version", "13"},
|
||||
{"Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="},
|
||||
})
|
||||
require.True(t, action == types.ActionContinue || action == types.HeaderStopIteration)
|
||||
|
||||
requestHeaders := host.GetRequestHeaders()
|
||||
pathValue, hasPath := wasmtest.GetHeaderValue(requestHeaders, ":path")
|
||||
require.True(t, hasPath)
|
||||
require.Equal(t, "/v1/realtime/sessions/realtime", pathValue)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
@@ -243,6 +243,84 @@ func RunOpenAIOnHttpRequestHeadersTests(t *testing.T) {
|
||||
require.Contains(t, authValue, "sk-openai-test123456789", "Authorization should contain OpenAI API token")
|
||||
})
|
||||
|
||||
// 测试OpenAI请求头处理(语音转写接口)
|
||||
t.Run("openai audio transcriptions request headers", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(basicOpenAIConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/audio/transcriptions"},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
|
||||
require.Equal(t, types.HeaderStopIteration, action)
|
||||
|
||||
requestHeaders := host.GetRequestHeaders()
|
||||
require.NotNil(t, requestHeaders)
|
||||
|
||||
hostValue, hasHost := test.GetHeaderValue(requestHeaders, ":authority")
|
||||
require.True(t, hasHost)
|
||||
require.Equal(t, "api.openai.com", hostValue)
|
||||
|
||||
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
|
||||
require.True(t, hasPath)
|
||||
require.Contains(t, pathValue, "/v1/audio/transcriptions", "Path should contain audio transcriptions endpoint")
|
||||
})
|
||||
|
||||
// 测试OpenAI请求头处理(语音翻译接口)
|
||||
t.Run("openai audio translations request headers", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(basicOpenAIConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/audio/translations"},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
|
||||
require.Equal(t, types.HeaderStopIteration, action)
|
||||
|
||||
requestHeaders := host.GetRequestHeaders()
|
||||
require.NotNil(t, requestHeaders)
|
||||
|
||||
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
|
||||
require.True(t, hasPath)
|
||||
require.Contains(t, pathValue, "/v1/audio/translations", "Path should contain audio translations endpoint")
|
||||
})
|
||||
|
||||
// 测试OpenAI请求头处理(实时接口,WebSocket握手)
|
||||
t.Run("openai realtime websocket handshake request headers", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(basicOpenAIConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/realtime?model=gpt-4o-realtime-preview"},
|
||||
{":method", "GET"},
|
||||
{"Connection", "Upgrade"},
|
||||
{"Upgrade", "websocket"},
|
||||
{"Sec-WebSocket-Version", "13"},
|
||||
{"Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="},
|
||||
})
|
||||
|
||||
// WebSocket 握手本身不应依赖请求体。受测试框架限制,某些场景可能仍返回 HeaderStopIteration。
|
||||
require.True(t, action == types.ActionContinue || action == types.HeaderStopIteration)
|
||||
|
||||
requestHeaders := host.GetRequestHeaders()
|
||||
require.NotNil(t, requestHeaders)
|
||||
|
||||
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
|
||||
require.True(t, hasPath)
|
||||
require.Contains(t, pathValue, "/v1/realtime", "Path should contain realtime endpoint")
|
||||
require.Contains(t, pathValue, "model=gpt-4o-realtime-preview", "Query parameters should be preserved")
|
||||
})
|
||||
|
||||
// 测试OpenAI请求头处理(图像生成接口)
|
||||
t.Run("openai image generation request headers", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(basicOpenAIConfig)
|
||||
@@ -305,6 +383,61 @@ func RunOpenAIOnHttpRequestHeadersTests(t *testing.T) {
|
||||
// 对于直接路径,应该保持原有路径
|
||||
require.Contains(t, pathValue, "/v1/chat/completions", "Path should be preserved for direct custom path")
|
||||
})
|
||||
|
||||
// 测试OpenAI自定义域名请求头处理(间接路径语音转写)
|
||||
t.Run("openai custom domain indirect path audio transcriptions request headers", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(openAICustomDomainIndirectPathConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/audio/transcriptions"},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
|
||||
require.Equal(t, types.HeaderStopIteration, action)
|
||||
|
||||
requestHeaders := host.GetRequestHeaders()
|
||||
require.NotNil(t, requestHeaders)
|
||||
|
||||
hostValue, hasHost := test.GetHeaderValue(requestHeaders, ":authority")
|
||||
require.True(t, hasHost)
|
||||
require.Equal(t, "custom.openai.com", hostValue, "Host should be changed to custom domain")
|
||||
|
||||
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
|
||||
require.True(t, hasPath)
|
||||
require.Contains(t, pathValue, "/api/audio/transcriptions", "Path should be rewritten with indirect custom prefix")
|
||||
})
|
||||
|
||||
// 测试OpenAI自定义域名请求头处理(间接路径 realtime,WebSocket握手)
|
||||
t.Run("openai custom domain indirect path realtime websocket handshake request headers", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(openAICustomDomainIndirectPathConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/realtime?model=gpt-4o-realtime-preview"},
|
||||
{":method", "GET"},
|
||||
{"Connection", "Upgrade"},
|
||||
{"Upgrade", "websocket"},
|
||||
{"Sec-WebSocket-Version", "13"},
|
||||
{"Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ=="},
|
||||
})
|
||||
|
||||
// WebSocket 握手本身不应依赖请求体。受测试框架限制,某些场景可能仍返回 HeaderStopIteration。
|
||||
require.True(t, action == types.ActionContinue || action == types.HeaderStopIteration)
|
||||
|
||||
requestHeaders := host.GetRequestHeaders()
|
||||
require.NotNil(t, requestHeaders)
|
||||
|
||||
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
|
||||
require.True(t, hasPath)
|
||||
require.Contains(t, pathValue, "/api/realtime", "Path should be rewritten with indirect custom prefix")
|
||||
require.Contains(t, pathValue, "model=gpt-4o-realtime-preview", "Query parameters should be preserved")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,22 @@ var qwenEnableCompatibleConfig = func() json.RawMessage {
|
||||
return data
|
||||
}()
|
||||
|
||||
// 测试配置:qwen original + 兼容模式(用于覆盖 provider.GetApiName 分支)
|
||||
var qwenOriginalCompatibleConfig = func() json.RawMessage {
|
||||
data, _ := json.Marshal(map[string]interface{}{
|
||||
"provider": map[string]interface{}{
|
||||
"type": "qwen",
|
||||
"apiTokens": []string{"sk-qwen-original-compatible"},
|
||||
"modelMapping": map[string]string{
|
||||
"*": "qwen-turbo",
|
||||
},
|
||||
"qwenEnableCompatible": true,
|
||||
"protocol": "original",
|
||||
},
|
||||
})
|
||||
return data
|
||||
}()
|
||||
|
||||
// 测试配置:qwen文件ID配置
|
||||
var qwenFileIdsConfig = func() json.RawMessage {
|
||||
data, _ := json.Marshal(map[string]interface{}{
|
||||
@@ -159,6 +175,15 @@ var qwenConflictConfig = func() json.RawMessage {
|
||||
return data
|
||||
}()
|
||||
|
||||
func hasUnsupportedAPINameError(errorLogs []string) bool {
|
||||
for _, log := range errorLogs {
|
||||
if strings.Contains(log, "unsupported API name") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func RunQwenParseConfigTests(t *testing.T) {
|
||||
test.RunGoTest(t, func(t *testing.T) {
|
||||
// 测试基本qwen配置解析
|
||||
@@ -403,6 +428,29 @@ func RunQwenOnHttpRequestHeadersTests(t *testing.T) {
|
||||
require.True(t, hasPath)
|
||||
require.Contains(t, pathValue, "/compatible-mode/v1/chat/completions", "Path should use compatible mode path")
|
||||
})
|
||||
|
||||
// 测试qwen兼容模式请求头处理(responses接口)
|
||||
t.Run("qwen compatible mode responses request headers", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(qwenEnableCompatibleConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/responses"},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
|
||||
require.Equal(t, types.HeaderStopIteration, action)
|
||||
|
||||
requestHeaders := host.GetRequestHeaders()
|
||||
require.NotNil(t, requestHeaders)
|
||||
|
||||
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
|
||||
require.True(t, hasPath)
|
||||
require.Contains(t, pathValue, "/api/v2/apps/protocols/compatible-mode/v1/responses", "Path should use compatible mode responses path")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -651,6 +699,112 @@ func RunQwenOnHttpRequestBodyTests(t *testing.T) {
|
||||
}
|
||||
require.True(t, hasCompatibleLogs, "Should have compatible mode processing logs")
|
||||
})
|
||||
|
||||
// 测试qwen请求体处理(兼容模式 responses接口)
|
||||
t.Run("qwen compatible mode responses request body", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(qwenEnableCompatibleConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/responses"},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
|
||||
requestBody := `{"model":"qwen-turbo","input":"test"}`
|
||||
action := host.CallOnHttpRequestBody([]byte(requestBody))
|
||||
|
||||
require.Equal(t, types.ActionContinue, action)
|
||||
|
||||
processedBody := host.GetRequestBody()
|
||||
require.NotNil(t, processedBody)
|
||||
require.Contains(t, string(processedBody), "qwen-turbo", "Model name should be preserved in responses request")
|
||||
})
|
||||
|
||||
// 测试qwen请求体处理(非兼容模式 responses接口应报不支持)
|
||||
t.Run("qwen non-compatible mode responses request body unsupported", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(basicQwenConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/responses"},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
require.Equal(t, types.HeaderStopIteration, action)
|
||||
|
||||
requestHeaders := host.GetRequestHeaders()
|
||||
require.NotNil(t, requestHeaders)
|
||||
|
||||
pathValue, hasPath := test.GetHeaderValue(requestHeaders, ":path")
|
||||
require.True(t, hasPath)
|
||||
require.Contains(t, pathValue, "/v1/responses", "Path should remain unchanged when responses is unsupported")
|
||||
|
||||
requestBody := `{"model":"qwen-turbo","input":"test"}`
|
||||
bodyAction := host.CallOnHttpRequestBody([]byte(requestBody))
|
||||
require.Equal(t, types.ActionContinue, bodyAction)
|
||||
|
||||
hasUnsupportedErr := hasUnsupportedAPINameError(host.GetErrorLogs())
|
||||
require.True(t, hasUnsupportedErr, "Should log unsupported API name for non-compatible responses")
|
||||
})
|
||||
|
||||
// 覆盖 qwen.GetApiName 中以下分支:
|
||||
// - qwenCompatibleTextEmbeddingPath => ApiNameEmbeddings
|
||||
// - qwenCompatibleResponsesPath => ApiNameResponses
|
||||
// - qwenAsyncAIGCPath => ApiNameQwenAsyncAIGC
|
||||
// - qwenAsyncTaskPath => ApiNameQwenAsyncTask
|
||||
t.Run("qwen original protocol get api name coverage for compatible embeddings responses and async paths", func(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
path string
|
||||
}{
|
||||
{
|
||||
name: "compatible embeddings path",
|
||||
path: "/compatible-mode/v1/embeddings",
|
||||
},
|
||||
{
|
||||
name: "compatible responses path",
|
||||
path: "/api/v2/apps/protocols/compatible-mode/v1/responses",
|
||||
},
|
||||
{
|
||||
name: "async aigc path",
|
||||
path: "/api/v1/services/aigc/custom-async-endpoint",
|
||||
},
|
||||
{
|
||||
name: "async task path",
|
||||
path: "/api/v1/tasks/task-123",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
host, status := test.NewTestHost(qwenOriginalCompatibleConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", tc.path},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
// 测试框架中 action 可能表现为 Continue 或 HeaderStopIteration,
|
||||
// 这里关注的是后续 body 阶段不出现 unsupported API name。
|
||||
require.True(t, action == types.ActionContinue || action == types.HeaderStopIteration)
|
||||
|
||||
requestBody := `{"model":"qwen-turbo","input":"test"}`
|
||||
bodyAction := host.CallOnHttpRequestBody([]byte(requestBody))
|
||||
require.Equal(t, types.ActionContinue, bodyAction)
|
||||
|
||||
hasUnsupportedErr := hasUnsupportedAPINameError(host.GetErrorLogs())
|
||||
require.False(t, hasUnsupportedErr, "Path should be recognized by qwen.GetApiName in original protocol")
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -986,6 +1140,51 @@ func RunQwenOnHttpResponseBodyTests(t *testing.T) {
|
||||
require.Contains(t, responseStr, "chat.completion", "Response should contain chat completion object")
|
||||
require.Contains(t, responseStr, "qwen-turbo", "Response should contain model name")
|
||||
})
|
||||
|
||||
// 测试qwen响应体处理(兼容模式 responses 接口透传)
|
||||
t.Run("qwen compatible mode responses response body", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(qwenEnableCompatibleConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/responses"},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
|
||||
requestBody := `{"model":"qwen-turbo","input":"test"}`
|
||||
host.CallOnHttpRequestBody([]byte(requestBody))
|
||||
|
||||
responseHeaders := [][2]string{
|
||||
{":status", "200"},
|
||||
{"Content-Type", "application/json"},
|
||||
}
|
||||
host.CallOnHttpResponseHeaders(responseHeaders)
|
||||
|
||||
responseBody := `{
|
||||
"id": "resp-123",
|
||||
"object": "response",
|
||||
"status": "completed",
|
||||
"output": [{
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [{
|
||||
"type": "output_text",
|
||||
"text": "hello"
|
||||
}]
|
||||
}]
|
||||
}`
|
||||
action := host.CallOnHttpResponseBody([]byte(responseBody))
|
||||
require.Equal(t, types.ActionContinue, action)
|
||||
|
||||
processedResponseBody := host.GetResponseBody()
|
||||
require.NotNil(t, processedResponseBody)
|
||||
responseStr := string(processedResponseBody)
|
||||
require.Contains(t, responseStr, "\"object\": \"response\"", "Responses API payload should be passthrough in compatible mode")
|
||||
require.Contains(t, responseStr, "\"text\": \"hello\"", "Assistant content should be preserved")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user