Files
go-llm/v2/generate.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

57 lines
1.9 KiB
Go

package llm
import (
"context"
"encoding/json"
"fmt"
"gitea.stevedudenhoeffer.com/steve/go-llm/v2/internal/schema"
)
const structuredOutputToolName = "structured_output"
// Generate sends a single user prompt to the model and parses the response into T.
// T must be a struct. The model is forced to return structured output matching T's schema
// by using a hidden tool call internally.
// Returns the parsed value, token usage, and any error.
func Generate[T any](ctx context.Context, model *Model, prompt string, opts ...RequestOption) (T, *Usage, error) {
return GenerateWith[T](ctx, model, []Message{UserMessage(prompt)}, opts...)
}
// GenerateWith sends the given messages to the model and parses the response into T.
// T must be a struct. The model is forced to return structured output matching T's schema
// by using a hidden tool call internally.
// Returns the parsed value, token usage, and any error.
func GenerateWith[T any](ctx context.Context, model *Model, messages []Message, opts ...RequestOption) (T, *Usage, error) {
var zero T
s := schema.FromStruct(zero)
tool := Tool{
Name: structuredOutputToolName,
Description: "Return your response as structured data using this function. You MUST call this function with your response.",
Schema: s,
}
// Append WithTools as the last option so it overrides any user-provided tools.
opts = append(opts, WithTools(NewToolBox(tool)))
resp, err := model.Complete(ctx, messages, opts...)
if err != nil {
return zero, nil, err
}
// Find the structured_output tool call in the response.
for _, tc := range resp.ToolCalls {
if tc.Name == structuredOutputToolName {
var result T
if err := json.Unmarshal([]byte(tc.Arguments), &result); err != nil {
return zero, resp.Usage, fmt.Errorf("failed to parse structured output: %w", err)
}
return result, resp.Usage, nil
}
}
return zero, resp.Usage, ErrNoStructuredOutput
}