fix(usage): WS passthrough effort 提取补入映射后模型候选

Fixes #3924
This commit is contained in:
li
2026-07-10 11:56:47 +08:00
parent ddb1a210ce
commit b9b013a088
3 changed files with 50 additions and 8 deletions
@@ -1015,7 +1015,7 @@ func TestPassthroughUsageMeta_TracksReasoningEffortAcrossTurns(t *testing.T) {
firstOut, firstBlocked, firstErr := svc.applyOpenAIFastPolicyToWSResponseCreate(context.Background(), account, capturedSessionModel, firstFrame)
require.NoError(t, firstErr)
require.Nil(t, firstBlocked)
meta.initFromFirstFrame(firstOut)
meta.initFromFirstFrame(firstOut, capturedSessionModel)
require.NotNil(t, meta.reasoningEffort.Load())
require.Equal(t, "medium", *meta.reasoningEffort.Load())
@@ -1032,7 +1032,7 @@ func TestPassthroughUsageMeta_TracksReasoningEffortAcrossTurns(t *testing.T) {
out, blocked, policyErr := svc.applyOpenAIFastPolicyToWSResponseCreate(context.Background(), account, model, payload)
if policyErr == nil && blocked == nil &&
strings.TrimSpace(gjson.GetBytes(payload, "type").String()) == "response.create" {
meta.updateFromResponseCreate(out, requestModelForThisFrame)
meta.updateFromResponseCreate(out, model, requestModelForThisFrame)
}
return out, blocked, policyErr
}
@@ -142,12 +142,12 @@ func newOpenAIWSPassthroughUsageMeta(initialRequestModel string, firstFrame []by
return meta
}
func (m *openAIWSPassthroughUsageMeta) initFromFirstFrame(policyOutput []byte) {
func (m *openAIWSPassthroughUsageMeta) initFromFirstFrame(policyOutput []byte, mappedModel string) {
if m == nil {
return
}
m.serviceTier.Store(extractOpenAIServiceTierFromBody(policyOutput))
m.reasoningEffort.Store(extractOpenAIReasoningEffortFromBody(policyOutput, m.sessionRequestModel))
m.reasoningEffort.Store(extractOpenAIReasoningEffortFromBody(policyOutput, mappedModel, m.sessionRequestModel))
}
func (m *openAIWSPassthroughUsageMeta) updateSessionRequestModel(payload []byte) {
@@ -169,12 +169,12 @@ func (m *openAIWSPassthroughUsageMeta) requestModelForFrame(payload []byte) stri
return m.sessionRequestModel
}
func (m *openAIWSPassthroughUsageMeta) updateFromResponseCreate(policyOutput []byte, requestModelForFrame string) {
func (m *openAIWSPassthroughUsageMeta) updateFromResponseCreate(policyOutput []byte, mappedModel string, requestModelForFrame string) {
if m == nil {
return
}
m.serviceTier.Store(extractOpenAIServiceTierFromBody(policyOutput))
m.reasoningEffort.Store(extractOpenAIReasoningEffortFromBody(policyOutput, requestModelForFrame))
m.reasoningEffort.Store(extractOpenAIReasoningEffortFromBody(policyOutput, mappedModel, requestModelForFrame))
}
func openAIWSPassthroughRequestModelForFrame(payload []byte) string {
@@ -311,7 +311,7 @@ func (s *OpenAIGatewayService) proxyResponsesWebSocketV2Passthrough(
// 因此使用 atomic.Pointer[string] 在 filterrunClientToUpstream
// goroutine)和 OnTurnComplete / final resultrunUpstreamToClient
// goroutine)之间同步当前 turn 的 usage metadata。
usageMeta.initFromFirstFrame(firstClientMessage)
usageMeta.initFromFirstFrame(firstClientMessage, capturedSessionModel)
promptCacheKey := strings.TrimSpace(gjson.GetBytes(firstClientMessage, "prompt_cache_key").String())
wsURL, err := s.buildOpenAIResponsesWSURL(account)
@@ -455,7 +455,7 @@ func (s *OpenAIGatewayService) proxyResponsesWebSocketV2Passthrough(
// service_tier 时按 default 处理,billing 应如实反映。
if policyErr == nil && blocked == nil &&
strings.TrimSpace(gjson.GetBytes(payload, "type").String()) == "response.create" {
usageMeta.updateFromResponseCreate(out, requestModelForThisFrame)
usageMeta.updateFromResponseCreate(out, model, requestModelForThisFrame)
}
return out, blocked, policyErr
},
@@ -0,0 +1,42 @@
//go:build unit
package service
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestWSPassthroughUsageMeta_InitFromFirstFrame_MappedModelCandidate(t *testing.T) {
body := []byte(`{"type":"response.create","model":"sol","reasoning":{"effort":"max"}}`)
meta := newOpenAIWSPassthroughUsageMeta("sol", body)
meta.initFromFirstFrame(body, "gpt-5.6-sol")
got := meta.reasoningEffort.Load()
require.NotNil(t, got, "reasoning effort should be set")
require.Equal(t, "max", *got, "mapped model gpt-5.6-sol should preserve max")
}
func TestWSPassthroughUsageMeta_InitFromFirstFrame_NonGPT56FallsBackToXHigh(t *testing.T) {
body := []byte(`{"type":"response.create","model":"gpt-5.4","reasoning":{"effort":"max"}}`)
meta := newOpenAIWSPassthroughUsageMeta("gpt-5.4", body)
meta.initFromFirstFrame(body, "gpt-5.4")
got := meta.reasoningEffort.Load()
require.NotNil(t, got)
require.Equal(t, "xhigh", *got, "non-5.6 model should normalize max to xhigh")
}
func TestWSPassthroughUsageMeta_UpdateFromResponseCreate_MappedModelCandidate(t *testing.T) {
body := []byte(`{"type":"response.create","model":"sol","reasoning":{"effort":"max"}}`)
meta := newOpenAIWSPassthroughUsageMeta("sol", body)
meta.updateFromResponseCreate(body, "gpt-5.6-sol", "sol")
got := meta.reasoningEffort.Load()
require.NotNil(t, got)
require.Equal(t, "max", *got, "mapped model should preserve max on multi-turn update")
}