From a2daab06605d46c45556b67be2dd21bbc8d3adf1 Mon Sep 17 00:00:00 2001 From: Mingluan Mu Date: Tue, 2 Jun 2026 16:46:35 +0800 Subject: [PATCH] fix(billing): count tool call tokens when upstream omits usage When an upstream channel returns a tool/function-calling response without a usage object, OpenaiHandler estimates completion tokens locally via CountTextToken. The estimate only covered message content and reasoning content, ignoring tool call names and arguments, which undercounts completion tokens (and therefore billing) for tool-calling responses. Include each tool call's function name and arguments in the text passed to CountTextToken so the estimate reflects the full generated output. --- relay/channel/openai/relay-openai.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/relay/channel/openai/relay-openai.go b/relay/channel/openai/relay-openai.go index d6a354f71a22..788da980b423 100644 --- a/relay/channel/openai/relay-openai.go +++ b/relay/channel/openai/relay-openai.go @@ -240,7 +240,12 @@ func OpenaiHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Respo completionTokens := simpleResponse.Usage.CompletionTokens if completionTokens == 0 { for _, choice := range simpleResponse.Choices { - ctkm := service.CountTextToken(choice.Message.StringContent()+choice.Message.GetReasoningContent(), info.UpstreamModelName) + textContent := choice.Message.StringContent() + choice.Message.GetReasoningContent() + // Count tool call name/arguments too, otherwise completion tokens are undercounted when upstream omits usage for tool-calling responses. + for _, tool := range choice.Message.ParseToolCalls() { + textContent += tool.Function.Name + tool.Function.Arguments + } + ctkm := service.CountTextToken(textContent, info.UpstreamModelName) completionTokens += ctkm } }