Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[fix]: preserve GLM-5.3-Flash max reasoning effort on OpenAI-compatible routes [@runbgp](https://github.com/runbgp)
- fix: give a Bedrock message a placeholder text block instead of a null `content` field when it has no text and no tool calls - `BedrockMessage.Content` has no `omitempty`, so a message with empty text and no tool calls (or an empty `tool_calls` array) serialized as `content:null`, which Converse rejects with "Member must not be null" (#2765)
- [fix]: marshal required nullable response fields as null [@PSR94](https://github.com/PSR94)
- fix: accept top-level arrays from OpenAI-compatible model APIs [@dani29](https://github.com/dani29)
Expand Down
56 changes: 56 additions & 0 deletions core/providers/openai/glmeffort_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package openai

import (
"encoding/json"
"testing"

"github.com/bytedance/sonic"
"github.com/maximhq/bifrost/core/schemas"
"github.com/stretchr/testify/require"
)

// Exercise the inbound OpenAI dialect and the outbound shared converter used
// by vLLM, including Hugging Face namespaces and both streaming request shapes.
func TestGLM53FlashReasoningEffortRoundTrip(t *testing.T) {
for _, model := range []string{"glm-5.3-flash", "zai-org/GLM-5.3-Flash"} {
for _, stream := range []bool{false, true} {
for _, effort := range []string{"", "low", "high", "max"} {
t.Run(model+"/"+effort+"/stream="+map[bool]string{false: "false", true: "true"}[stream], func(t *testing.T) {
payload := map[string]interface{}{
"model": "vllm/" + model,
"messages": []map[string]string{{"role": "user", "content": "What is 7 + 5?"}},
"stream": stream,
"max_tokens": 100,
"temperature": 0.0,
}
if effort != "" {
payload["reasoning_effort"] = effort
}
raw, err := json.Marshal(payload)
require.NoError(t, err)
var inbound OpenAIChatRequest
require.NoError(t, sonic.Unmarshal(raw, &inbound))
ctx := schemas.NewBifrostContext(nil, schemas.NoDeadline)
request := inbound.ToBifrostChatRequest(ctx)
require.Equal(t, schemas.VLLM, request.Provider)
require.Equal(t, model, request.Model)
outbound := ToOpenAIChatRequest(ctx, request)
require.NotNil(t, outbound)
outbound.Stream = inbound.Stream
wire, err := json.Marshal(outbound)
require.NoError(t, err)
var body map[string]interface{}
require.NoError(t, json.Unmarshal(wire, &body))
if effort == "" {
require.NotContains(t, body, "reasoning_effort")
} else {
require.Equal(t, effort, body["reasoning_effort"])
require.Equal(t, effort, *request.Params.Reasoning.Effort, "conversion must not mutate input")
}
require.Equal(t, stream, body["stream"])
require.Equal(t, 0.0, body["temperature"])
})
}
}
}
}
2 changes: 2 additions & 0 deletions core/providers/openai/responses_marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ func TestNormalizeOpenAIReasoningEffort(t *testing.T) {
{"provider-prefixed gpt-5.6 keeps max", "openai/gpt-5.6", "max", "max"},
{"deepseek-v4 keeps max", "deepseek-v4", "max", "max"},
{"glm-5.2 keeps max", "glm-5.2", "max", "max"},
{"glm-5.3-flash keeps max", "glm-5.3-flash", "max", "max"},
{"namespaced glm-5.3-flash keeps max", "vllm/zai-org/GLM-5.3-Flash", "max", "max"},
{"gpt-5.5 downgrades max to xhigh", "gpt-5.5", "max", "xhigh"},
{"gpt-5.2 downgrades max to xhigh", "gpt-5.2", "max", "xhigh"},
{"gpt-5.5 keeps xhigh", "gpt-5.5", "xhigh", "xhigh"},
Expand Down
3 changes: 2 additions & 1 deletion core/providers/openai/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ func acceptsMaxEffort(model string) bool {
modelLower := bareModelLower(model)
return strings.Contains(modelLower, "gpt-5.6") ||
strings.Contains(modelLower, "deepseek-v4") ||
strings.Contains(modelLower, "glm-5.2")
strings.Contains(modelLower, "glm-5.2") ||
strings.Contains(modelLower, "glm-5.3-flash")
}

// bareModelLower strips any provider prefix and lowercases, so the effort
Expand Down
166 changes: 166 additions & 0 deletions tests/e2e/api/collections/provider-harness.json
Original file line number Diff line number Diff line change
Expand Up @@ -140504,6 +140504,172 @@
]
}
]
},
{
"name": "69. GLM-5.3-Flash max effort survives vLLM conversion",
"description": "Regression for GLM-5.3-Flash max being downgraded to high in the shared OpenAI converter. Requires a vllm key serving zai-org/GLM-5.3-Flash and client.allow_per_request_raw_override=true in the isolated harness gateway. Assert outbound raw_request, never infer effort from generated text or token counts. HF model card: https://huggingface.co/zai-org/GLM-5.3-Flash#note. Related: #6162 and #6054.",
"item": [
{
"name": "vllm/zai-org/GLM-5.3-Flash max effort chat completion",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "x-bf-send-back-raw-request",
"value": "true"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"model\": \"vllm/zai-org/GLM-5.3-Flash\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"What is 7 + 5? Answer with the number only.\"\n }\n ],\n \"reasoning_effort\": \"max\",\n \"max_tokens\": 100,\n \"temperature\": 0,\n \"stream\": false\n}"
},
"url": {
"raw": "{{baseUrl}}/openai/v1/chat/completions",
"host": [
"{{baseUrl}}"
],
"path": [
"openai",
"v1",
"chat",
"completions"
]
}
},
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"if ([401, 403, 429, 500, 502, 503, 504].indexOf(pm.response.code) !== -1) { return; }",
"var txt = pm.response.text();",
"pm.test('request succeeds', function () { pm.expect(pm.response.code, txt).to.be.below(400); });",
"var chunks = [];",
"chunks.push(pm.response.json());",
"var rr;",
"chunks.forEach(function (chunk) { var raw = (chunk.extra_fields || {}).raw_request; if (raw) { rr = typeof raw === 'string' ? JSON.parse(raw) : raw; } });",
"pm.test('outbound reasoning effort is preserved', function () {",
" pm.expect(rr, 'raw_request missing; enable allow_per_request_raw_override in the harness gateway').to.be.an('object');",
" pm.expect(rr.reasoning_effort).to.eql('max');",
"});"
]
}
}
]
},
{
"name": "vllm/zai-org/GLM-5.3-Flash max effort streaming",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "x-bf-send-back-raw-request",
"value": "true"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"model\": \"vllm/zai-org/GLM-5.3-Flash\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"What is 7 + 5? Answer with the number only.\"\n }\n ],\n \"reasoning_effort\": \"max\",\n \"max_tokens\": 100,\n \"temperature\": 0,\n \"stream\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/openai/v1/chat/completions",
"host": [
"{{baseUrl}}"
],
"path": [
"openai",
"v1",
"chat",
"completions"
]
}
},
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"if ([401, 403, 429, 500, 502, 503, 504].indexOf(pm.response.code) !== -1) { return; }",
"var txt = pm.response.text();",
"pm.test('request succeeds', function () { pm.expect(pm.response.code, txt).to.be.below(400); });",
"var chunks = [];",
"txt.split('\\n').forEach(function (line) { if (line.indexOf('data:') !== 0) { return; } var data = line.slice(5).trim(); if (data && data !== '[DONE]') { chunks.push(JSON.parse(data)); } });",
"var rr;",
"chunks.forEach(function (chunk) { var raw = (chunk.extra_fields || {}).raw_request; if (raw) { rr = typeof raw === 'string' ? JSON.parse(raw) : raw; } });",
"pm.test('outbound reasoning effort is preserved', function () {",
" pm.expect(rr, 'raw_request missing; enable allow_per_request_raw_override in the harness gateway').to.be.an('object');",
" pm.expect(rr.reasoning_effort).to.eql('max');",
"});",
"pm.test('stream terminates', function () { pm.expect(txt).to.include('data: [DONE]'); });"
]
}
}
]
},
{
"name": "vllm/zai-org/GLM-5.3-Flash low effort chat completion",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "x-bf-send-back-raw-request",
"value": "true"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"model\": \"vllm/zai-org/GLM-5.3-Flash\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"What is 7 + 5? Answer with the number only.\"\n }\n ],\n \"reasoning_effort\": \"low\",\n \"max_tokens\": 100,\n \"temperature\": 0,\n \"stream\": false\n}"
},
"url": {
"raw": "{{baseUrl}}/openai/v1/chat/completions",
"host": [
"{{baseUrl}}"
],
"path": [
"openai",
"v1",
"chat",
"completions"
]
}
},
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"if ([401, 403, 429, 500, 502, 503, 504].indexOf(pm.response.code) !== -1) { return; }",
"var txt = pm.response.text();",
"pm.test('request succeeds', function () { pm.expect(pm.response.code, txt).to.be.below(400); });",
"var chunks = [];",
"chunks.push(pm.response.json());",
"var rr;",
"chunks.forEach(function (chunk) { var raw = (chunk.extra_fields || {}).raw_request; if (raw) { rr = typeof raw === 'string' ? JSON.parse(raw) : raw; } });",
"pm.test('outbound reasoning effort is preserved', function () {",
" pm.expect(rr, 'raw_request missing; enable allow_per_request_raw_override in the harness gateway').to.be.an('object');",
" pm.expect(rr.reasoning_effort).to.eql('low');",
"});"
]
}
}
]
}
]
}
]
}