mirror of
https://github.com/alibaba/higress.git
synced 2026-05-24 20:57:31 +08:00
fix: remove non-standard thinking/reasoning_max_tokens from Claude-to… (#3667)
This commit is contained in:
148
plugins/wasm-go/extensions/ai-proxy/test/openrouter.go
Normal file
148
plugins/wasm-go/extensions/ai-proxy/test/openrouter.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
"github.com/higress-group/wasm-go/pkg/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var basicOpenRouterConfig = func() json.RawMessage {
|
||||
data, _ := json.Marshal(map[string]interface{}{
|
||||
"provider": map[string]interface{}{
|
||||
"type": "openrouter",
|
||||
"apiTokens": []string{"sk-openrouter-test"},
|
||||
},
|
||||
})
|
||||
return data
|
||||
}()
|
||||
|
||||
func RunOpenRouterClaudeAutoConversionTests(t *testing.T) {
|
||||
test.RunGoTest(t, func(t *testing.T) {
|
||||
t.Run("claude thinking budget_tokens is converted to reasoning.max_tokens", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(basicOpenRouterConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
// Send request with Claude /v1/messages path to trigger auto-conversion
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/messages"},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
require.Equal(t, types.HeaderStopIteration, action)
|
||||
|
||||
// Claude request body with thinking enabled
|
||||
requestBody := `{
|
||||
"model": "anthropic/claude-sonnet-4",
|
||||
"max_tokens": 8000,
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
"thinking": {"type": "enabled", "budget_tokens": 10000}
|
||||
}`
|
||||
|
||||
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)
|
||||
|
||||
// reasoning.max_tokens should be set from budget_tokens
|
||||
reasoning, ok := bodyMap["reasoning"].(map[string]interface{})
|
||||
require.True(t, ok, "reasoning field should be present")
|
||||
assert.Equal(t, float64(10000), reasoning["max_tokens"],
|
||||
"reasoning.max_tokens should preserve the original budget_tokens value")
|
||||
|
||||
// reasoning_effort should be removed (OpenRouter uses reasoning.max_tokens instead)
|
||||
assert.NotContains(t, bodyMap, "reasoning_effort",
|
||||
"reasoning_effort should be removed")
|
||||
|
||||
// Non-standard fields should not be present
|
||||
assert.NotContains(t, bodyMap, "thinking",
|
||||
"thinking should not be in the final request")
|
||||
assert.NotContains(t, bodyMap, "reasoning_max_tokens",
|
||||
"reasoning_max_tokens should not be in the final request")
|
||||
})
|
||||
|
||||
t.Run("claude without thinking uses default transformation", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(basicOpenRouterConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/messages"},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
require.Equal(t, types.HeaderStopIteration, action)
|
||||
|
||||
requestBody := `{
|
||||
"model": "anthropic/claude-sonnet-4",
|
||||
"max_tokens": 1000,
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
}`
|
||||
|
||||
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)
|
||||
|
||||
// No reasoning fields should be present
|
||||
assert.NotContains(t, bodyMap, "reasoning")
|
||||
assert.NotContains(t, bodyMap, "reasoning_effort")
|
||||
assert.NotContains(t, bodyMap, "thinking")
|
||||
assert.NotContains(t, bodyMap, "reasoning_max_tokens")
|
||||
})
|
||||
|
||||
t.Run("claude thinking disabled does not set reasoning", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(basicOpenRouterConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/messages"},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
require.Equal(t, types.HeaderStopIteration, action)
|
||||
|
||||
// thinking disabled with budget_tokens (dirty input)
|
||||
requestBody := `{
|
||||
"model": "anthropic/claude-sonnet-4",
|
||||
"max_tokens": 1000,
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
"thinking": {"type": "disabled", "budget_tokens": 5000}
|
||||
}`
|
||||
|
||||
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)
|
||||
|
||||
// Should NOT have reasoning.max_tokens since thinking was disabled
|
||||
assert.NotContains(t, bodyMap, "reasoning",
|
||||
"reasoning should not be set when thinking is disabled")
|
||||
assert.NotContains(t, bodyMap, "thinking")
|
||||
assert.NotContains(t, bodyMap, "reasoning_max_tokens")
|
||||
})
|
||||
})
|
||||
}
|
||||
138
plugins/wasm-go/extensions/ai-proxy/test/zhipuai.go
Normal file
138
plugins/wasm-go/extensions/ai-proxy/test/zhipuai.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
"github.com/higress-group/wasm-go/pkg/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var basicZhipuAIConfig = func() json.RawMessage {
|
||||
data, _ := json.Marshal(map[string]interface{}{
|
||||
"provider": map[string]interface{}{
|
||||
"type": "zhipuai",
|
||||
"apiTokens": []string{"sk-zhipuai-test"},
|
||||
},
|
||||
})
|
||||
return data
|
||||
}()
|
||||
|
||||
func RunZhipuAIClaudeAutoConversionTests(t *testing.T) {
|
||||
test.RunGoTest(t, func(t *testing.T) {
|
||||
t.Run("claude thinking enabled sets thinking enabled for zhipuai", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(basicZhipuAIConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/messages"},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
require.Equal(t, types.HeaderStopIteration, action)
|
||||
|
||||
requestBody := `{
|
||||
"model": "glm-4",
|
||||
"max_tokens": 1000,
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
"thinking": {"type": "enabled", "budget_tokens": 8192}
|
||||
}`
|
||||
|
||||
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)
|
||||
|
||||
// ZhipuAI should have thinking=enabled (converted from reasoning_effort)
|
||||
thinking, ok := bodyMap["thinking"].(map[string]interface{})
|
||||
require.True(t, ok, "thinking field should be present")
|
||||
assert.Equal(t, "enabled", thinking["type"])
|
||||
|
||||
// reasoning_effort should be removed (ZhipuAI doesn't recognize it)
|
||||
assert.NotContains(t, bodyMap, "reasoning_effort")
|
||||
})
|
||||
|
||||
t.Run("claude without thinking sets thinking disabled for zhipuai", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(basicZhipuAIConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/messages"},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
require.Equal(t, types.HeaderStopIteration, action)
|
||||
|
||||
requestBody := `{
|
||||
"model": "glm-4",
|
||||
"max_tokens": 1000,
|
||||
"messages": [{"role": "user", "content": "Hello"}]
|
||||
}`
|
||||
|
||||
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)
|
||||
|
||||
// ZhipuAI should explicitly set thinking=disabled
|
||||
thinking, ok := bodyMap["thinking"].(map[string]interface{})
|
||||
require.True(t, ok, "thinking field should be present for disabled state")
|
||||
assert.Equal(t, "disabled", thinking["type"])
|
||||
})
|
||||
|
||||
t.Run("claude thinking disabled sets thinking disabled for zhipuai", func(t *testing.T) {
|
||||
host, status := test.NewTestHost(basicZhipuAIConfig)
|
||||
defer host.Reset()
|
||||
require.Equal(t, types.OnPluginStartStatusOK, status)
|
||||
|
||||
action := host.CallOnHttpRequestHeaders([][2]string{
|
||||
{":authority", "example.com"},
|
||||
{":path", "/v1/messages"},
|
||||
{":method", "POST"},
|
||||
{"Content-Type", "application/json"},
|
||||
})
|
||||
require.Equal(t, types.HeaderStopIteration, action)
|
||||
|
||||
requestBody := `{
|
||||
"model": "glm-4",
|
||||
"max_tokens": 1000,
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
"thinking": {"type": "disabled"}
|
||||
}`
|
||||
|
||||
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)
|
||||
|
||||
// ZhipuAI should explicitly set thinking=disabled
|
||||
thinking, ok := bodyMap["thinking"].(map[string]interface{})
|
||||
require.True(t, ok, "thinking field should be present for disabled state")
|
||||
assert.Equal(t, "disabled", thinking["type"])
|
||||
|
||||
// No reasoning fields
|
||||
assert.NotContains(t, bodyMap, "reasoning_effort")
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user