diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-16 04:06:39 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-16 04:06:39 +0200 |
| commit | d985ff9b90cb8476301b2b611023a4332b47a2f0 (patch) | |
| tree | fa5987d2633991182f90805155b509dd6f3a1125 /internal/llmutils | |
| parent | 8f31040cc388943601cfd8a026ea85f0790e66c2 (diff) | |
Centralize GPT-5 temperature override into llmutils.ResolveTemperature
Eliminates identical temperature resolution logic duplicated in
hexaiaction, hexaicli, and lsp packages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/llmutils')
| -rw-r--r-- | internal/llmutils/temperature.go | 37 | ||||
| -rw-r--r-- | internal/llmutils/temperature_test.go | 79 |
2 files changed, 116 insertions, 0 deletions
diff --git a/internal/llmutils/temperature.go b/internal/llmutils/temperature.go new file mode 100644 index 0000000..8e10c66 --- /dev/null +++ b/internal/llmutils/temperature.go @@ -0,0 +1,37 @@ +// Package llmutils provides shared utilities for LLM configuration. +// ResolveTemperature centralizes the GPT-5 temperature override logic +// that was previously duplicated across hexaiaction, hexaicli, and lsp. +package llmutils + +import "strings" + +// isGPT5 returns true when the model name indicates an OpenAI GPT-5 variant. +func isGPT5(provider, model string) bool { + return provider == "openai" && + strings.HasPrefix(strings.ToLower(model), "gpt-5") +} + +// ResolveTemperature picks the effective temperature from an optional +// per-surface override (entryTemp) or the global coding temperature +// (codingTemp). For OpenAI GPT-5 models the default coding temperature +// of 0.2 is automatically raised to 1.0, and when no temperature is +// configured at all GPT-5 defaults to 1.0. +// +// Returns (temperature, true) when a value was resolved, or (0, false) +// when no temperature should be sent (let the provider choose). +func ResolveTemperature(provider, model string, entryTemp, codingTemp *float64) (float64, bool) { + if entryTemp != nil { + return *entryTemp, true + } + if codingTemp != nil { + temp := *codingTemp + if isGPT5(provider, model) && temp == 0.2 { + temp = 1.0 + } + return temp, true + } + if isGPT5(provider, model) { + return 1.0, true + } + return 0, false +} diff --git a/internal/llmutils/temperature_test.go b/internal/llmutils/temperature_test.go new file mode 100644 index 0000000..2c4089e --- /dev/null +++ b/internal/llmutils/temperature_test.go @@ -0,0 +1,79 @@ +package llmutils + +import "testing" + +func floatPtr(v float64) *float64 { return &v } + +func TestResolveTemperature_EntryTempTakesPrecedence(t *testing.T) { + temp, ok := ResolveTemperature("openai", "gpt-5.0", floatPtr(0.5), floatPtr(0.2)) + if !ok || temp != 0.5 { + t.Fatalf("expected entry temp 0.5, got %v ok=%v", temp, ok) + } +} + +func TestResolveTemperature_CodingTempUsed(t *testing.T) { + temp, ok := ResolveTemperature("openai", "gpt-4.1", nil, floatPtr(0.3)) + if !ok || temp != 0.3 { + t.Fatalf("expected coding temp 0.3, got %v ok=%v", temp, ok) + } +} + +func TestResolveTemperature_GPT5UpgradesDefault02(t *testing.T) { + temp, ok := ResolveTemperature("openai", "gpt-5.1", nil, floatPtr(0.2)) + if !ok || temp != 1.0 { + t.Fatalf("expected upgraded temp 1.0 for gpt-5 with 0.2, got %v ok=%v", temp, ok) + } +} + +func TestResolveTemperature_GPT5DefaultWhenNoTemp(t *testing.T) { + temp, ok := ResolveTemperature("openai", "gpt-5.0-preview", nil, nil) + if !ok || temp != 1.0 { + t.Fatalf("expected default 1.0 for gpt-5, got %v ok=%v", temp, ok) + } +} + +func TestResolveTemperature_NoTempNonGPT5(t *testing.T) { + _, ok := ResolveTemperature("openai", "gpt-4.1", nil, nil) + if ok { + t.Fatal("expected no temperature for non-gpt-5 with no config") + } +} + +func TestResolveTemperature_NonOpenAIProviderNoUpgrade(t *testing.T) { + temp, ok := ResolveTemperature("anthropic", "gpt-5.0", nil, floatPtr(0.2)) + if !ok || temp != 0.2 { + t.Fatalf("expected 0.2 for non-openai provider, got %v ok=%v", temp, ok) + } +} + +func TestResolveTemperature_GPT5CodingTempNon02NotUpgraded(t *testing.T) { + temp, ok := ResolveTemperature("openai", "gpt-5.0", nil, floatPtr(0.7)) + if !ok || temp != 0.7 { + t.Fatalf("expected 0.7 (no upgrade for non-0.2), got %v ok=%v", temp, ok) + } +} + +func TestResolveTemperature_CaseInsensitiveModel(t *testing.T) { + temp, ok := ResolveTemperature("openai", "GPT-5.0", nil, nil) + if !ok || temp != 1.0 { + t.Fatalf("expected 1.0 for case-insensitive GPT-5, got %v ok=%v", temp, ok) + } +} + +func TestIsGPT5(t *testing.T) { + tests := []struct { + provider, model string + want bool + }{ + {"openai", "gpt-5.0", true}, + {"openai", "GPT-5.1-preview", true}, + {"openai", "gpt-4.1", false}, + {"anthropic", "gpt-5.0", false}, + {"openai", "", false}, + } + for _, tt := range tests { + if got := isGPT5(tt.provider, tt.model); got != tt.want { + t.Errorf("isGPT5(%q, %q) = %v, want %v", tt.provider, tt.model, got, tt.want) + } + } +} |
