Files
go-llm/v2/response.go
Steve Dudenhoeffer 5b687839b2
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
feat: comprehensive token usage tracking for V2
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>
2026-03-02 04:33:18 +00:00

78 lines
2.2 KiB
Go

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.
Text string
// ToolCalls contains any tool invocations the assistant requested.
ToolCalls []ToolCall
// Usage contains token usage information (if available from provider).
Usage *Usage
// message is the full assistant message for this response.
message Message
}
// Message returns the full assistant Message for this response,
// suitable for appending to the conversation history.
func (r Response) Message() Message {
return r.message
}
// HasToolCalls returns true if the response contains tool call requests.
func (r Response) HasToolCalls() bool {
return len(r.ToolCalls) > 0
}
// Usage captures token consumption.
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
)