feat: capture Gemini cached content tokens in cost tracking
- Add CachedContentTokenCount to UsageMetadata parsing for both streaming (helpers.go) and non-streaming (gemini.go) requests - CacheReadTokens now populated from Gemini cachedContentTokenCount - Add uint32Ptr helper for nil-safe uint32 pointer creation
This commit is contained in:
@@ -2,5 +2,5 @@
|
|||||||
"files": {},
|
"files": {},
|
||||||
"turnCycles": 0,
|
"turnCycles": 0,
|
||||||
"maxCycles": 3,
|
"maxCycles": 3,
|
||||||
"lastUpdated": "2026-04-27T01:09:48.183Z"
|
"lastUpdated": "2026-04-27T01:12:44.352Z"
|
||||||
}
|
}
|
||||||
@@ -2,14 +2,14 @@ package providers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
"gophergate/internal/config"
|
"gophergate/internal/config"
|
||||||
"gophergate/internal/models"
|
"gophergate/internal/models"
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GeminiProvider struct {
|
type GeminiProvider struct {
|
||||||
@@ -54,10 +54,10 @@ type GeminiContent struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GeminiPart struct {
|
type GeminiPart struct {
|
||||||
Text string `json:"text,omitempty"`
|
Text string `json:"text,omitempty"`
|
||||||
InlineData *GeminiInlineData `json:"inlineData,omitempty"`
|
InlineData *GeminiInlineData `json:"inlineData,omitempty"`
|
||||||
FunctionCall *GeminiFunctionCall `json:"functionCall,omitempty"`
|
FunctionCall *GeminiFunctionCall `json:"functionCall,omitempty"`
|
||||||
FunctionResponse *GeminiFunctionResponse `json:"functionResponse,omitempty"`
|
FunctionResponse *GeminiFunctionResponse `json:"functionResponse,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GeminiInlineData struct {
|
type GeminiInlineData struct {
|
||||||
@@ -265,9 +265,10 @@ func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.Unified
|
|||||||
FinishReason string `json:"finishReason"`
|
FinishReason string `json:"finishReason"`
|
||||||
} `json:"candidates"`
|
} `json:"candidates"`
|
||||||
UsageMetadata struct {
|
UsageMetadata struct {
|
||||||
PromptTokenCount uint32 `json:"promptTokenCount"`
|
PromptTokenCount uint32 `json:"promptTokenCount"`
|
||||||
CandidatesTokenCount uint32 `json:"candidatesTokenCount"`
|
CandidatesTokenCount uint32 `json:"candidatesTokenCount"`
|
||||||
TotalTokenCount uint32 `json:"totalTokenCount"`
|
TotalTokenCount uint32 `json:"totalTokenCount"`
|
||||||
|
CachedContentTokenCount uint32 `json:"cachedContentTokenCount"`
|
||||||
} `json:"usageMetadata"`
|
} `json:"usageMetadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,6 +325,7 @@ func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.Unified
|
|||||||
PromptTokens: geminiResp.UsageMetadata.PromptTokenCount,
|
PromptTokens: geminiResp.UsageMetadata.PromptTokenCount,
|
||||||
CompletionTokens: geminiResp.UsageMetadata.CandidatesTokenCount,
|
CompletionTokens: geminiResp.UsageMetadata.CandidatesTokenCount,
|
||||||
TotalTokens: geminiResp.UsageMetadata.TotalTokenCount,
|
TotalTokens: geminiResp.UsageMetadata.TotalTokenCount,
|
||||||
|
CacheReadTokens: uint32Ptr(geminiResp.UsageMetadata.CachedContentTokenCount),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,3 +496,10 @@ func (p *GeminiProvider) ChatCompletionStream(ctx context.Context, req *models.U
|
|||||||
|
|
||||||
return ch, nil
|
return ch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func uint32Ptr(v uint32) *uint32 {
|
||||||
|
if v > 0 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -134,10 +134,10 @@ func BuildOpenAIBody(request *models.UnifiedRequest, messagesJSON []interface{},
|
|||||||
}
|
}
|
||||||
|
|
||||||
type openAIUsage struct {
|
type openAIUsage struct {
|
||||||
PromptTokens uint32 `json:"prompt_tokens"`
|
PromptTokens uint32 `json:"prompt_tokens"`
|
||||||
CompletionTokens uint32 `json:"completion_tokens"`
|
CompletionTokens uint32 `json:"completion_tokens"`
|
||||||
TotalTokens uint32 `json:"total_tokens"`
|
TotalTokens uint32 `json:"total_tokens"`
|
||||||
PromptTokensDetails *struct {
|
PromptTokensDetails *struct {
|
||||||
CachedTokens uint32 `json:"cached_tokens"`
|
CachedTokens uint32 `json:"cached_tokens"`
|
||||||
} `json:"prompt_tokens_details"`
|
} `json:"prompt_tokens_details"`
|
||||||
CompletionTokensDetails *struct {
|
CompletionTokensDetails *struct {
|
||||||
@@ -308,6 +308,7 @@ func StreamGemini(ctx io.ReadCloser, ch chan<- *models.ChatCompletionStreamRespo
|
|||||||
PromptTokens: geminiChunk.UsageMetadata.PromptTokenCount,
|
PromptTokens: geminiChunk.UsageMetadata.PromptTokenCount,
|
||||||
CompletionTokens: geminiChunk.UsageMetadata.CandidatesTokenCount,
|
CompletionTokens: geminiChunk.UsageMetadata.CandidatesTokenCount,
|
||||||
TotalTokens: geminiChunk.UsageMetadata.TotalTokenCount,
|
TotalTokens: geminiChunk.UsageMetadata.TotalTokenCount,
|
||||||
|
CacheReadTokens: uint32Ptr(geminiChunk.UsageMetadata.CachedContentTokenCount),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user