XAI Advanced Usage Reporting
The Go-AI SDK provides sophisticated token usage tracking for XAI (formerly Twitter/X AI) models, including support for cached tokens and reasoning tokens.
Overview
XAI models report detailed token usage information that goes beyond basic input/output counts:
- Cached tokens: Tokens read from cache (prompt caching)
- Reasoning tokens: Tokens used for extended reasoning (Grok models)
The Usage struct also has TextTokens/ImageTokens fields for a
text/image input split, but xAI's current Responses API usage conversion
does not populate them — see Multi-Modal Token Tracking.
Basic Usage
package main
import (
"context"
"fmt"
"log"
"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/providers/xai"
)
func main() {
provider := xai.New(xai.Config{
APIKey: "your-xai-api-key",
})
model, _ := provider.LanguageModel("grok-beta")
result, err := ai.GenerateText(context.Background(), ai.GenerateTextOptions{
Model: model,
Prompt: "Explain quantum computing",
})
if err != nil {
log.Fatal(err)
}
// Access detailed usage information
usage := result.Usage
fmt.Printf("Total tokens: %d\n", *usage.TotalTokens)
fmt.Printf("Input tokens: %d\n", *usage.InputTokens)
fmt.Printf("Output tokens: %d\n", *usage.OutputTokens)
// Access detailed breakdowns if available
if usage.InputDetails != nil {
if usage.InputDetails.CacheReadTokens != nil {
fmt.Printf("Cache hits: %d tokens\n", *usage.InputDetails.CacheReadTokens)
}
if usage.InputDetails.NoCacheTokens != nil {
fmt.Printf("Non-cached: %d tokens\n", *usage.InputDetails.NoCacheTokens)
}
}
if usage.OutputDetails != nil {
if usage.OutputDetails.ReasoningTokens != nil {
fmt.Printf("Reasoning: %d tokens\n", *usage.OutputDetails.ReasoningTokens)
}
}
}
Usage Structure
type Usage struct {
// Basic counts
InputTokens *int64 // Total input tokens
OutputTokens *int64 // Total output tokens (including reasoning)
TotalTokens *int64 // Sum of input and output
// Detailed breakdowns
InputDetails *InputTokenDetails
OutputDetails *OutputTokenDetails
// Raw API response data
Raw map[string]interface{}
}
type InputTokenDetails struct {
NoCacheTokens *int64 // Tokens not from cache
CacheReadTokens *int64 // Tokens read from cache
CacheWriteTokens *int64 // Tokens written to cache
TextTokens *int64 // Text-only input tokens
ImageTokens *int64 // Image input tokens
}
type OutputTokenDetails struct {
TextTokens *int64 // Regular output tokens
ReasoningTokens *int64 // Extended reasoning tokens
}
Cached Token Tracking
xAI's Responses API (the only language model path xAI uses — Chat Completions
was removed) reports input_tokens as the total, with cached tokens broken
out separately under input_tokens_details.cached_tokens. The SDK derives
NoCacheTokens by subtracting the cached count from the total:
// API Response:
// input_tokens: 200
// input_tokens_details: { cached_tokens: 150 }
// SDK converts to:
InputTokens: 200 // Total, taken directly from input_tokens
InputDetails: {
NoCacheTokens: 50, // 200 - 150
CacheReadTokens: 150, // From input_tokens_details.cached_tokens
}
InputDetails is only populated when cached_tokens > 0; otherwise it stays
nil. The SDK does not special-case cached_tokens exceeding input_tokens
— if the API ever reports that, NoCacheTokens would be negative.
Reasoning Token Tracking
For models with extended reasoning (like Grok with reasoning mode), xAI's
output_tokens field is already the full total (text plus reasoning). The
SDK breaks out the reasoning portion from output_tokens_details.reasoning_tokens
and derives the text-only count by subtraction — it does not sum two
separate fields:
// API Response:
// output_tokens: 278
// output_tokens_details: { reasoning_tokens: 228 }
// SDK converts to:
OutputTokens: 278 // Total, taken directly from output_tokens
OutputDetails: {
TextTokens: 50, // 278 - 228
ReasoningTokens: 228, // From output_tokens_details.reasoning_tokens
}
OutputDetails is only populated when reasoning_tokens > 0.
Multi-Modal Token Tracking
xAI's Responses API usage conversion does not currently populate
InputDetails.TextTokens / InputDetails.ImageTokens — those fields stay
nil for xAI models even on multi-modal requests. If you need a text/image
token split, use a provider that supports it (see the Token usage
differentiation guide
for current per-provider support).
Complete Example: Cost Tracking
package main
import (
"context"
"fmt"
"log"
"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/provider/types"
"github.com/digitallysavvy/go-ai/pkg/providers/xai"
)
// XAI pricing (example - check current pricing)
const (
COST_PER_1K_INPUT = 0.01
COST_PER_1K_OUTPUT = 0.03
COST_PER_1K_CACHED = 0.001 // Cached tokens are cheaper
)
func calculateCost(usage types.Usage) float64 {
var cost float64
// Input cost (accounting for cache)
if usage.InputDetails != nil {
// Non-cached input tokens
if usage.InputDetails.NoCacheTokens != nil {
tokens := float64(*usage.InputDetails.NoCacheTokens) / 1000.0
cost += tokens * COST_PER_1K_INPUT
}
// Cached tokens (cheaper)
if usage.InputDetails.CacheReadTokens != nil {
tokens := float64(*usage.InputDetails.CacheReadTokens) / 1000.0
cost += tokens * COST_PER_1K_CACHED
}
} else if usage.InputTokens != nil {
// Fallback if no detailed breakdown
tokens := float64(*usage.InputTokens) / 1000.0
cost += tokens * COST_PER_1K_INPUT
}
// Output cost
if usage.OutputTokens != nil {
tokens := float64(*usage.OutputTokens) / 1000.0
cost += tokens * COST_PER_1K_OUTPUT
}
return cost
}
func main() {
provider := xai.New(xai.Config{
APIKey: "your-xai-api-key",
})
model, _ := provider.LanguageModel("grok-beta")
result, err := ai.GenerateText(context.Background(), ai.GenerateTextOptions{
Model: model,
Prompt: "Write a detailed analysis of...",
})
if err != nil {
log.Fatal(err)
}
// Calculate cost
cost := calculateCost(result.Usage)
// Print detailed breakdown
fmt.Println("=== Token Usage ===")
fmt.Printf("Total tokens: %d\n", result.Usage.GetTotalTokens())
if result.Usage.InputDetails != nil {
fmt.Println("\nInput breakdown:")
if result.Usage.InputDetails.NoCacheTokens != nil {
fmt.Printf(" Non-cached: %d tokens\n", *result.Usage.InputDetails.NoCacheTokens)
}
if result.Usage.InputDetails.CacheReadTokens != nil {
fmt.Printf(" Cached: %d tokens\n", *result.Usage.InputDetails.CacheReadTokens)
}
if result.Usage.InputDetails.TextTokens != nil {
fmt.Printf(" Text: %d tokens\n", *result.Usage.InputDetails.TextTokens)
}
if result.Usage.InputDetails.ImageTokens != nil {
fmt.Printf(" Images: %d tokens\n", *result.Usage.InputDetails.ImageTokens)
}
}
if result.Usage.OutputDetails != nil {
fmt.Println("\nOutput breakdown:")
if result.Usage.OutputDetails.TextTokens != nil {
fmt.Printf(" Text: %d tokens\n", *result.Usage.OutputDetails.TextTokens)
}
if result.Usage.OutputDetails.ReasoningTokens != nil {
fmt.Printf(" Reasoning: %d tokens\n", *result.Usage.OutputDetails.ReasoningTokens)
}
}
fmt.Printf("\nEstimated cost: $%.4f\n", cost)
}
Advanced: Budget Enforcement
type UsageBudget struct {
MaxInputTokens int64
MaxOutputTokens int64
MaxTotalCost float64
}
func (b *UsageBudget) Check(usage types.Usage) error {
if usage.InputTokens != nil && *usage.InputTokens > b.MaxInputTokens {
return fmt.Errorf("input tokens (%d) exceeds budget (%d)",
*usage.InputTokens, b.MaxInputTokens)
}
if usage.OutputTokens != nil && *usage.OutputTokens > b.MaxOutputTokens {
return fmt.Errorf("output tokens (%d) exceeds budget (%d)",
*usage.OutputTokens, b.MaxOutputTokens)
}
cost := calculateCost(usage)
if cost > b.MaxTotalCost {
return fmt.Errorf("cost ($%.4f) exceeds budget ($%.2f)",
cost, b.MaxTotalCost)
}
return nil
}
// Usage
budget := UsageBudget{
MaxInputTokens: 5000,
MaxOutputTokens: 2000,
MaxTotalCost: 0.50,
}
result, err := ai.GenerateText(ctx, opts)
if err != nil {
log.Fatal(err)
}
if err := budget.Check(result.Usage); err != nil {
log.Printf("Budget violation: %v", err)
}
Raw API Data
All raw token data from the API is preserved in the Raw field:
result, err := ai.GenerateText(ctx, opts)
if err != nil {
log.Fatal(err)
}
// Access raw API response
rawData := result.Usage.Raw
// Get direct API fields. Raw is populated via a plain json.Unmarshal into
// map[string]interface{}, so JSON numbers decode as float64, not int64.
if cachedTokens, ok := rawData["cached_tokens"].(float64); ok {
fmt.Printf("Raw cached_tokens: %.0f\n", cachedTokens)
}
if reasoningTokens, ok := rawData["reasoning_tokens"].(float64); ok {
fmt.Printf("Raw reasoning_tokens: %.0f\n", reasoningTokens)
}
// Nested structures
if details, ok := rawData["prompt_tokens_details"].(map[string]interface{}); ok {
fmt.Printf("Prompt token details: %+v\n", details)
}
Token Calculation Examples
Example 1: Basic Request (No Cache, No Reasoning)
API Response:
input_tokens: 100
output_tokens: 50
SDK Output:
InputTokens: 100
OutputTokens: 50
TotalTokens: 150 (100 + 50, computed by the SDK)
InputDetails: nil
OutputDetails: nil
Example 2: With Cache
API Response:
input_tokens: 200
input_tokens_details: { cached_tokens: 150 }
SDK Output:
InputTokens: 200
InputDetails: {
NoCacheTokens: 50 (200 - 150)
CacheReadTokens: 150
}
Example 3: With Cache and Reasoning
API Response:
input_tokens: 4142
input_tokens_details: { cached_tokens: 2000 }
output_tokens: 300
output_tokens_details: { reasoning_tokens: 200 }
SDK Output:
InputTokens: 4142
OutputTokens: 300
TotalTokens: 4442 (4142 + 300, computed by the SDK)
InputDetails: {
NoCacheTokens: 2142 (4142 - 2000)
CacheReadTokens: 2000
}
OutputDetails: {
TextTokens: 100 (300 - 200)
ReasoningTokens: 200
}
Best Practices
-
Always check for nil: Token details may not always be present
if usage.InputDetails != nil && usage.InputDetails.CacheReadTokens != nil {// Use cached token count} -
Use detailed breakdowns for cost calculation: Don't rely on basic counts alone
-
Monitor cache effectiveness:
if usage.InputDetails != nil {cacheRate := float64(*usage.InputDetails.CacheReadTokens) /float64(*usage.InputTokens) * 100fmt.Printf("Cache hit rate: %.1f%%\n", cacheRate)} -
Track reasoning token usage: Reasoning tokens can significantly increase costs
-
Log raw data for debugging: The
Rawfield contains all original API data
Comparison with TypeScript SDK
The Go implementation matches the TypeScript AI SDK's usage tracking:
| TypeScript | Go | Notes |
|---|---|---|
usage.inputTokens.total | InputTokens | Total input |
usage.inputTokens.noCache | InputDetails.NoCacheTokens | Non-cached |
usage.inputTokens.cacheRead | InputDetails.CacheReadTokens | Cached |
usage.outputTokens.total | OutputTokens | Total output |
usage.outputTokens.text | OutputDetails.TextTokens | Text output |
usage.outputTokens.reasoning | OutputDetails.ReasoningTokens | Reasoning |
usage.raw | Raw | Raw API data |
The cached token inclusivity logic and reasoning token additive behavior match the TypeScript implementation exactly.
See Also
- XAI Provider
- Cost Optimization
- Usage Tracking
- Prompt Caching