mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-22 06:40:21 +08:00
Merge pull request #3991 from fengshao1227/fix/anthropic-usage-cache-creation-tokens
fix(apicompat): Responses↔Anthropic 转换补齐 cache_creation_input_tokens
This commit is contained in:
@@ -102,9 +102,10 @@ func AnthropicToResponsesResponse(resp *AnthropicResponse) *ResponsesResponse {
|
||||
resp.Usage.CacheReadInputTokens +
|
||||
resp.Usage.CacheCreationInputTokens
|
||||
out.Usage = &ResponsesUsage{
|
||||
InputTokens: totalInputTokens,
|
||||
OutputTokens: resp.Usage.OutputTokens,
|
||||
TotalTokens: totalInputTokens + resp.Usage.OutputTokens,
|
||||
InputTokens: totalInputTokens,
|
||||
OutputTokens: resp.Usage.OutputTokens,
|
||||
TotalTokens: totalInputTokens + resp.Usage.OutputTokens,
|
||||
CacheCreationInputTokens: resp.Usage.CacheCreationInputTokens,
|
||||
}
|
||||
if resp.Usage.CacheReadInputTokens > 0 {
|
||||
out.Usage.InputTokensDetails = &ResponsesInputTokensDetails{
|
||||
@@ -497,9 +498,10 @@ func makeResponsesCompletedEvent(
|
||||
// back to match OpenAI Responses semantics where input_tokens is the total.
|
||||
totalInputTokens := state.InputTokens + state.CacheReadInputTokens + state.CacheCreationInputTokens
|
||||
usage := &ResponsesUsage{
|
||||
InputTokens: totalInputTokens,
|
||||
OutputTokens: state.OutputTokens,
|
||||
TotalTokens: totalInputTokens + state.OutputTokens,
|
||||
InputTokens: totalInputTokens,
|
||||
OutputTokens: state.OutputTokens,
|
||||
TotalTokens: totalInputTokens + state.OutputTokens,
|
||||
CacheCreationInputTokens: state.CacheCreationInputTokens,
|
||||
}
|
||||
if state.CacheReadInputTokens > 0 {
|
||||
usage.InputTokensDetails = &ResponsesInputTokensDetails{
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package apicompat
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnthropicUsageFromResponsesUsage_CacheCreation(t *testing.T) {
|
||||
usage := &ResponsesUsage{
|
||||
InputTokens: 20,
|
||||
OutputTokens: 5,
|
||||
CacheCreationInputTokens: 6,
|
||||
InputTokensDetails: &ResponsesInputTokensDetails{
|
||||
CachedTokens: 4,
|
||||
},
|
||||
}
|
||||
|
||||
got := anthropicUsageFromResponsesUsage(usage)
|
||||
|
||||
assert.Equal(t, 10, got.InputTokens, "input = total(20) - cache_read(4) - cache_creation(6)")
|
||||
assert.Equal(t, 5, got.OutputTokens)
|
||||
assert.Equal(t, 4, got.CacheReadInputTokens)
|
||||
assert.Equal(t, 6, got.CacheCreationInputTokens, "cache creation must be preserved")
|
||||
}
|
||||
|
||||
func TestAnthropicUsageFromResponsesUsage_NoCacheCreation(t *testing.T) {
|
||||
usage := &ResponsesUsage{
|
||||
InputTokens: 10,
|
||||
OutputTokens: 5,
|
||||
InputTokensDetails: &ResponsesInputTokensDetails{
|
||||
CachedTokens: 3,
|
||||
},
|
||||
}
|
||||
|
||||
got := anthropicUsageFromResponsesUsage(usage)
|
||||
|
||||
assert.Equal(t, 7, got.InputTokens)
|
||||
assert.Equal(t, 3, got.CacheReadInputTokens)
|
||||
assert.Equal(t, 0, got.CacheCreationInputTokens)
|
||||
}
|
||||
|
||||
func TestResponsesEventToAnthropicEvents_StreamingCacheCreation(t *testing.T) {
|
||||
state := NewResponsesEventToAnthropicState()
|
||||
state.MessageStartSent = true
|
||||
|
||||
completedEvt := &ResponsesStreamEvent{
|
||||
Type: "response.completed",
|
||||
Response: &ResponsesResponse{
|
||||
Status: "completed",
|
||||
Usage: &ResponsesUsage{
|
||||
InputTokens: 20,
|
||||
OutputTokens: 5,
|
||||
CacheCreationInputTokens: 6,
|
||||
InputTokensDetails: &ResponsesInputTokensDetails{
|
||||
CachedTokens: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
events := ResponsesEventToAnthropicEvents(completedEvt, state)
|
||||
|
||||
var deltaEvt *AnthropicStreamEvent
|
||||
for i := range events {
|
||||
if events[i].Type == "message_delta" {
|
||||
deltaEvt = &events[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, deltaEvt, "should have message_delta event")
|
||||
require.NotNil(t, deltaEvt.Usage)
|
||||
assert.Equal(t, 6, deltaEvt.Usage.CacheCreationInputTokens, "streaming cache_creation must be preserved")
|
||||
assert.Equal(t, 10, deltaEvt.Usage.InputTokens, "input = 20 - 4(read) - 6(creation)")
|
||||
assert.Equal(t, 4, deltaEvt.Usage.CacheReadInputTokens)
|
||||
}
|
||||
|
||||
func TestAnthropicToResponsesResponse_CacheCreation(t *testing.T) {
|
||||
resp := AnthropicResponse{
|
||||
ID: "msg_test",
|
||||
Type: "message",
|
||||
Role: "assistant",
|
||||
Model: "claude-opus-4-6",
|
||||
Usage: AnthropicUsage{
|
||||
InputTokens: 10,
|
||||
OutputTokens: 5,
|
||||
CacheReadInputTokens: 4,
|
||||
CacheCreationInputTokens: 6,
|
||||
},
|
||||
StopReason: "end_turn",
|
||||
}
|
||||
|
||||
out := AnthropicToResponsesResponse(&resp)
|
||||
|
||||
require.NotNil(t, out.Usage)
|
||||
assert.Equal(t, 20, out.Usage.InputTokens, "total = input(10) + cache_read(4) + cache_creation(6)")
|
||||
assert.Equal(t, 6, out.Usage.CacheCreationInputTokens, "cache creation must round-trip")
|
||||
}
|
||||
@@ -100,15 +100,16 @@ func anthropicUsageFromResponsesUsage(usage *ResponsesUsage) AnthropicUsage {
|
||||
cachedTokens = usage.InputTokensDetails.CachedTokens
|
||||
}
|
||||
|
||||
inputTokens := usage.InputTokens - cachedTokens
|
||||
inputTokens := usage.InputTokens - cachedTokens - usage.CacheCreationInputTokens
|
||||
if inputTokens < 0 {
|
||||
inputTokens = 0
|
||||
}
|
||||
|
||||
return AnthropicUsage{
|
||||
InputTokens: inputTokens,
|
||||
OutputTokens: usage.OutputTokens,
|
||||
CacheReadInputTokens: cachedTokens,
|
||||
InputTokens: inputTokens,
|
||||
OutputTokens: usage.OutputTokens,
|
||||
CacheReadInputTokens: cachedTokens,
|
||||
CacheCreationInputTokens: usage.CacheCreationInputTokens,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,9 +182,10 @@ type ResponsesEventToAnthropicState struct {
|
||||
// OutputIndexToBlockIdx maps Responses output_index → Anthropic content block index.
|
||||
OutputIndexToBlockIdx map[int]int
|
||||
|
||||
InputTokens int
|
||||
OutputTokens int
|
||||
CacheReadInputTokens int
|
||||
InputTokens int
|
||||
OutputTokens int
|
||||
CacheReadInputTokens int
|
||||
CacheCreationInputTokens int
|
||||
|
||||
ResponseID string
|
||||
Model string
|
||||
@@ -258,9 +260,10 @@ func FinalizeResponsesAnthropicStream(state *ResponsesEventToAnthropicState) []A
|
||||
StopReason: stopReason,
|
||||
},
|
||||
Usage: &AnthropicUsage{
|
||||
InputTokens: state.InputTokens,
|
||||
OutputTokens: state.OutputTokens,
|
||||
CacheReadInputTokens: state.CacheReadInputTokens,
|
||||
InputTokens: state.InputTokens,
|
||||
OutputTokens: state.OutputTokens,
|
||||
CacheReadInputTokens: state.CacheReadInputTokens,
|
||||
CacheCreationInputTokens: state.CacheCreationInputTokens,
|
||||
},
|
||||
},
|
||||
AnthropicStreamEvent{Type: "message_stop"},
|
||||
@@ -578,6 +581,7 @@ func resToAnthHandleCompleted(evt *ResponsesStreamEvent, state *ResponsesEventTo
|
||||
state.InputTokens = usage.InputTokens
|
||||
state.OutputTokens = usage.OutputTokens
|
||||
state.CacheReadInputTokens = usage.CacheReadInputTokens
|
||||
state.CacheCreationInputTokens = usage.CacheCreationInputTokens
|
||||
}
|
||||
if evt.Response != nil {
|
||||
if evt.Response.Usage != nil {
|
||||
@@ -585,6 +589,7 @@ func resToAnthHandleCompleted(evt *ResponsesStreamEvent, state *ResponsesEventTo
|
||||
state.InputTokens = usage.InputTokens
|
||||
state.OutputTokens = usage.OutputTokens
|
||||
state.CacheReadInputTokens = usage.CacheReadInputTokens
|
||||
state.CacheCreationInputTokens = usage.CacheCreationInputTokens
|
||||
}
|
||||
switch evt.Response.Status {
|
||||
case "incomplete":
|
||||
@@ -605,9 +610,10 @@ func resToAnthHandleCompleted(evt *ResponsesStreamEvent, state *ResponsesEventTo
|
||||
StopReason: stopReason,
|
||||
},
|
||||
Usage: &AnthropicUsage{
|
||||
InputTokens: state.InputTokens,
|
||||
OutputTokens: state.OutputTokens,
|
||||
CacheReadInputTokens: state.CacheReadInputTokens,
|
||||
InputTokens: state.InputTokens,
|
||||
OutputTokens: state.OutputTokens,
|
||||
CacheReadInputTokens: state.CacheReadInputTokens,
|
||||
CacheCreationInputTokens: state.CacheCreationInputTokens,
|
||||
},
|
||||
},
|
||||
AnthropicStreamEvent{Type: "message_stop"},
|
||||
|
||||
Reference in New Issue
Block a user