mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 04:50:52 +08:00
feat(translator): add usage metadata aggregation for Claude and OpenAI responses
- Integrated input, output, reasoning, and total token tracking in response processing for Claude and OpenAI. - Ensured support for usage details even when specific fields are missing in the response. - Enhanced completion outputs with aggregated usage details for accurate reporting.
This commit is contained in:
@@ -32,6 +32,10 @@ type claudeToResponsesState struct {
|
|||||||
ReasoningBuf strings.Builder
|
ReasoningBuf strings.Builder
|
||||||
ReasoningPartAdded bool
|
ReasoningPartAdded bool
|
||||||
ReasoningIndex int
|
ReasoningIndex int
|
||||||
|
// usage aggregation
|
||||||
|
InputTokens int64
|
||||||
|
OutputTokens int64
|
||||||
|
UsageSeen bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var dataTag = []byte("data:")
|
var dataTag = []byte("data:")
|
||||||
@@ -77,6 +81,19 @@ func ConvertClaudeResponseToOpenAIResponses(ctx context.Context, modelName strin
|
|||||||
st.FuncArgsBuf = make(map[int]*strings.Builder)
|
st.FuncArgsBuf = make(map[int]*strings.Builder)
|
||||||
st.FuncNames = make(map[int]string)
|
st.FuncNames = make(map[int]string)
|
||||||
st.FuncCallIDs = make(map[int]string)
|
st.FuncCallIDs = make(map[int]string)
|
||||||
|
st.InputTokens = 0
|
||||||
|
st.OutputTokens = 0
|
||||||
|
st.UsageSeen = false
|
||||||
|
if usage := msg.Get("usage"); usage.Exists() {
|
||||||
|
if v := usage.Get("input_tokens"); v.Exists() {
|
||||||
|
st.InputTokens = v.Int()
|
||||||
|
st.UsageSeen = true
|
||||||
|
}
|
||||||
|
if v := usage.Get("output_tokens"); v.Exists() {
|
||||||
|
st.OutputTokens = v.Int()
|
||||||
|
st.UsageSeen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
// response.created
|
// response.created
|
||||||
created := `{"type":"response.created","sequence_number":0,"response":{"id":"","object":"response","created_at":0,"status":"in_progress","background":false,"error":null,"instructions":""}}`
|
created := `{"type":"response.created","sequence_number":0,"response":{"id":"","object":"response","created_at":0,"status":"in_progress","background":false,"error":null,"instructions":""}}`
|
||||||
created, _ = sjson.Set(created, "sequence_number", nextSeq())
|
created, _ = sjson.Set(created, "sequence_number", nextSeq())
|
||||||
@@ -227,7 +244,6 @@ func ConvertClaudeResponseToOpenAIResponses(ctx context.Context, modelName strin
|
|||||||
out = append(out, emitEvent("response.output_item.done", itemDone))
|
out = append(out, emitEvent("response.output_item.done", itemDone))
|
||||||
st.InFuncBlock = false
|
st.InFuncBlock = false
|
||||||
} else if st.ReasoningActive {
|
} else if st.ReasoningActive {
|
||||||
// close reasoning
|
|
||||||
full := st.ReasoningBuf.String()
|
full := st.ReasoningBuf.String()
|
||||||
textDone := `{"type":"response.reasoning_summary_text.done","sequence_number":0,"item_id":"","output_index":0,"summary_index":0,"text":""}`
|
textDone := `{"type":"response.reasoning_summary_text.done","sequence_number":0,"item_id":"","output_index":0,"summary_index":0,"text":""}`
|
||||||
textDone, _ = sjson.Set(textDone, "sequence_number", nextSeq())
|
textDone, _ = sjson.Set(textDone, "sequence_number", nextSeq())
|
||||||
@@ -244,7 +260,19 @@ func ConvertClaudeResponseToOpenAIResponses(ctx context.Context, modelName strin
|
|||||||
st.ReasoningActive = false
|
st.ReasoningActive = false
|
||||||
st.ReasoningPartAdded = false
|
st.ReasoningPartAdded = false
|
||||||
}
|
}
|
||||||
|
case "message_delta":
|
||||||
|
if usage := root.Get("usage"); usage.Exists() {
|
||||||
|
if v := usage.Get("output_tokens"); v.Exists() {
|
||||||
|
st.OutputTokens = v.Int()
|
||||||
|
st.UsageSeen = true
|
||||||
|
}
|
||||||
|
if v := usage.Get("input_tokens"); v.Exists() {
|
||||||
|
st.InputTokens = v.Int()
|
||||||
|
st.UsageSeen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
case "message_stop":
|
case "message_stop":
|
||||||
|
|
||||||
completed := `{"type":"response.completed","sequence_number":0,"response":{"id":"","object":"response","created_at":0,"status":"completed","background":false,"error":null}}`
|
completed := `{"type":"response.completed","sequence_number":0,"response":{"id":"","object":"response","created_at":0,"status":"completed","background":false,"error":null}}`
|
||||||
completed, _ = sjson.Set(completed, "sequence_number", nextSeq())
|
completed, _ = sjson.Set(completed, "sequence_number", nextSeq())
|
||||||
completed, _ = sjson.Set(completed, "response.id", st.ResponseID)
|
completed, _ = sjson.Set(completed, "response.id", st.ResponseID)
|
||||||
@@ -381,6 +409,24 @@ func ConvertClaudeResponseToOpenAIResponses(ctx context.Context, modelName strin
|
|||||||
if len(outputs) > 0 {
|
if len(outputs) > 0 {
|
||||||
completed, _ = sjson.Set(completed, "response.output", outputs)
|
completed, _ = sjson.Set(completed, "response.output", outputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reasoningTokens := int64(0)
|
||||||
|
if st.ReasoningBuf.Len() > 0 {
|
||||||
|
reasoningTokens = int64(st.ReasoningBuf.Len() / 4)
|
||||||
|
}
|
||||||
|
usagePresent := st.UsageSeen || reasoningTokens > 0
|
||||||
|
if usagePresent {
|
||||||
|
completed, _ = sjson.Set(completed, "response.usage.input_tokens", st.InputTokens)
|
||||||
|
completed, _ = sjson.Set(completed, "response.usage.input_tokens_details.cached_tokens", 0)
|
||||||
|
completed, _ = sjson.Set(completed, "response.usage.output_tokens", st.OutputTokens)
|
||||||
|
if reasoningTokens > 0 {
|
||||||
|
completed, _ = sjson.Set(completed, "response.usage.output_tokens_details.reasoning_tokens", reasoningTokens)
|
||||||
|
}
|
||||||
|
total := st.InputTokens + st.OutputTokens
|
||||||
|
if total > 0 || st.UsageSeen {
|
||||||
|
completed, _ = sjson.Set(completed, "response.usage.total_tokens", total)
|
||||||
|
}
|
||||||
|
}
|
||||||
out = append(out, emitEvent("response.completed", completed))
|
out = append(out, emitEvent("response.completed", completed))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,13 @@ type oaiToResponsesState struct {
|
|||||||
// function item done state
|
// function item done state
|
||||||
FuncArgsDone map[int]bool
|
FuncArgsDone map[int]bool
|
||||||
FuncItemDone map[int]bool
|
FuncItemDone map[int]bool
|
||||||
|
// usage aggregation
|
||||||
|
PromptTokens int64
|
||||||
|
CachedTokens int64
|
||||||
|
CompletionTokens int64
|
||||||
|
TotalTokens int64
|
||||||
|
ReasoningTokens int64
|
||||||
|
UsageSeen bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func emitRespEvent(event string, payload string) string {
|
func emitRespEvent(event string, payload string) string {
|
||||||
@@ -66,6 +73,35 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context,
|
|||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if usage := root.Get("usage"); usage.Exists() {
|
||||||
|
if v := usage.Get("prompt_tokens"); v.Exists() {
|
||||||
|
st.PromptTokens = v.Int()
|
||||||
|
st.UsageSeen = true
|
||||||
|
}
|
||||||
|
if v := usage.Get("prompt_tokens_details.cached_tokens"); v.Exists() {
|
||||||
|
st.CachedTokens = v.Int()
|
||||||
|
st.UsageSeen = true
|
||||||
|
}
|
||||||
|
if v := usage.Get("completion_tokens"); v.Exists() {
|
||||||
|
st.CompletionTokens = v.Int()
|
||||||
|
st.UsageSeen = true
|
||||||
|
} else if v := usage.Get("output_tokens"); v.Exists() {
|
||||||
|
st.CompletionTokens = v.Int()
|
||||||
|
st.UsageSeen = true
|
||||||
|
}
|
||||||
|
if v := usage.Get("output_tokens_details.reasoning_tokens"); v.Exists() {
|
||||||
|
st.ReasoningTokens = v.Int()
|
||||||
|
st.UsageSeen = true
|
||||||
|
} else if v := usage.Get("completion_tokens_details.reasoning_tokens"); v.Exists() {
|
||||||
|
st.ReasoningTokens = v.Int()
|
||||||
|
st.UsageSeen = true
|
||||||
|
}
|
||||||
|
if v := usage.Get("total_tokens"); v.Exists() {
|
||||||
|
st.TotalTokens = v.Int()
|
||||||
|
st.UsageSeen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nextSeq := func() int { st.Seq++; return st.Seq }
|
nextSeq := func() int { st.Seq++; return st.Seq }
|
||||||
var out []string
|
var out []string
|
||||||
|
|
||||||
@@ -85,6 +121,12 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context,
|
|||||||
st.MsgItemDone = make(map[int]bool)
|
st.MsgItemDone = make(map[int]bool)
|
||||||
st.FuncArgsDone = make(map[int]bool)
|
st.FuncArgsDone = make(map[int]bool)
|
||||||
st.FuncItemDone = make(map[int]bool)
|
st.FuncItemDone = make(map[int]bool)
|
||||||
|
st.PromptTokens = 0
|
||||||
|
st.CachedTokens = 0
|
||||||
|
st.CompletionTokens = 0
|
||||||
|
st.TotalTokens = 0
|
||||||
|
st.ReasoningTokens = 0
|
||||||
|
st.UsageSeen = false
|
||||||
// response.created
|
// response.created
|
||||||
created := `{"type":"response.created","sequence_number":0,"response":{"id":"","object":"response","created_at":0,"status":"in_progress","background":false,"error":null}}`
|
created := `{"type":"response.created","sequence_number":0,"response":{"id":"","object":"response","created_at":0,"status":"in_progress","background":false,"error":null}}`
|
||||||
created, _ = sjson.Set(created, "sequence_number", nextSeq())
|
created, _ = sjson.Set(created, "sequence_number", nextSeq())
|
||||||
@@ -503,6 +545,19 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context,
|
|||||||
if len(outputs) > 0 {
|
if len(outputs) > 0 {
|
||||||
completed, _ = sjson.Set(completed, "response.output", outputs)
|
completed, _ = sjson.Set(completed, "response.output", outputs)
|
||||||
}
|
}
|
||||||
|
if st.UsageSeen {
|
||||||
|
completed, _ = sjson.Set(completed, "response.usage.input_tokens", st.PromptTokens)
|
||||||
|
completed, _ = sjson.Set(completed, "response.usage.input_tokens_details.cached_tokens", st.CachedTokens)
|
||||||
|
completed, _ = sjson.Set(completed, "response.usage.output_tokens", st.CompletionTokens)
|
||||||
|
if st.ReasoningTokens > 0 {
|
||||||
|
completed, _ = sjson.Set(completed, "response.usage.output_tokens_details.reasoning_tokens", st.ReasoningTokens)
|
||||||
|
}
|
||||||
|
total := st.TotalTokens
|
||||||
|
if total == 0 {
|
||||||
|
total = st.PromptTokens + st.CompletionTokens
|
||||||
|
}
|
||||||
|
completed, _ = sjson.Set(completed, "response.usage.total_tokens", total)
|
||||||
|
}
|
||||||
out = append(out, emitRespEvent("response.completed", completed))
|
out = append(out, emitRespEvent("response.completed", completed))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user