Files
go-llm/v2/response.go
T
steve cbaf41f50c
CI / Root Module (push) Failing after 1m30s
CI / Lint (push) Failing after 1m1s
CI / V2 Module (push) Successful in 3m41s
feat(v2): add ReasoningLevel option; thinking/reasoning across providers
Introduces an opt-in level-based reasoning toggle (low/medium/high) that
each provider translates to its native parameter:

- Anthropic: thinking.budget_tokens (1024/8000/24000), with temperature
  forced to default and MaxTokens auto-grown above the budget.
- OpenAI/xAI/Groq via openaicompat: reasoning_effort string, gated by a
  new Rules.SupportsReasoning predicate so non-reasoning models don't
  receive the parameter. xAI uses Rules.MapReasoningEffort to remap
  "medium" to "high" since its API only accepts low|high.
- Google: thinking_config.thinking_budget + include_thoughts:true.
- DeepSeek: SupportsReasoning=false (reasoner is always-on; the
  reasoning_content trace was already extracted via openaicompat).

Reasoning content is surfaced as Response.Thinking on Complete and as
StreamEventThinking deltas during streaming. Provider-side: extracted
from Anthropic thinking content blocks, Google's part.Thought=true
parts, and the non-standard reasoning_content field that DeepSeek and
Groq emit (parsed out of raw JSON since openai-go doesn't type it).

Public API:
  - llm.ReasoningLevel + ReasoningLow/Medium/High constants
  - llm.WithReasoning(level) request option
  - Model.WithReasoning(level) for baked-in defaults
  - provider.Request.Reasoning, provider.Response.Thinking
  - provider.StreamEventThinking

Tests cover Rules-based gating, MapReasoningEffort, reasoning_content
extraction (Complete + Stream), Anthropic budget mapping, and
temperature suppression when thinking is enabled. Existing behavior is
unchanged when Reasoning is the empty string.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 03:58:42 +00:00

82 lines
2.4 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
// Thinking is the assistant's reasoning/thinking trace, when reasoning
// was requested and the provider exposed it. Empty otherwise.
Thinking 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
)