feat: comprehensive token usage tracking for V2
All checks were successful
CI / Lint (pull_request) Successful in 10m18s
CI / Root Module (pull_request) Successful in 11m4s
CI / V2 Module (pull_request) Successful in 11m5s

Add provider-specific usage details, fix streaming usage, and return
usage from all high-level APIs (Chat.Send, Generate[T], Agent.Run).

Breaking changes:
- Chat.Send/SendMessage/SendWithImages now return (string, *Usage, error)
- Generate[T]/GenerateWith[T] now return (T, *Usage, error)
- Agent.Run/RunMessages now return (string, *Usage, error)

New features:
- Usage.Details map for provider-specific token breakdowns
  (reasoning, cached, audio, thoughts tokens)
- OpenAI streaming now captures usage via StreamOptions.IncludeUsage
- Google streaming now captures UsageMetadata from final chunk
- UsageTracker.Details() for accumulated detail totals
- ModelPricing and PricingRegistry for cost computation

Closes #2

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 04:33:18 +00:00
parent 7e1705c385
commit 5b687839b2
17 changed files with 684 additions and 61 deletions

View File

@@ -1,5 +1,7 @@
package llm
import "gitea.stevedudenhoeffer.com/steve/go-llm/v2/provider"
// Response represents the result of a completion request.
type Response struct {
// Text is the assistant's text content. Empty if only tool calls.
@@ -31,4 +33,45 @@ type Usage struct {
InputTokens int
OutputTokens int
TotalTokens int
Details map[string]int // provider-specific breakdown (e.g., cached, reasoning tokens)
}
// addUsage merges usage u into the receiver, accumulating token counts and details.
// If the receiver is nil, it returns a copy of u. If u is nil, it returns the receiver unchanged.
func addUsage(total *Usage, u *Usage) *Usage {
if u == nil {
return total
}
if total == nil {
cp := *u
if u.Details != nil {
cp.Details = make(map[string]int, len(u.Details))
for k, v := range u.Details {
cp.Details[k] = v
}
}
return &cp
}
total.InputTokens += u.InputTokens
total.OutputTokens += u.OutputTokens
total.TotalTokens += u.TotalTokens
if u.Details != nil {
if total.Details == nil {
total.Details = make(map[string]int, len(u.Details))
}
for k, v := range u.Details {
total.Details[k] += v
}
}
return total
}
// Re-export detail key constants from provider package for convenience.
const (
UsageDetailReasoningTokens = provider.UsageDetailReasoningTokens
UsageDetailCachedInputTokens = provider.UsageDetailCachedInputTokens
UsageDetailCacheCreationTokens = provider.UsageDetailCacheCreationTokens
UsageDetailAudioInputTokens = provider.UsageDetailAudioInputTokens
UsageDetailAudioOutputTokens = provider.UsageDetailAudioOutputTokens
UsageDetailThoughtsTokens = provider.UsageDetailThoughtsTokens
)