diff options
| -rw-r--r-- | internal/hexaiaction/prompts.go | 18 | ||||
| -rw-r--r-- | internal/hexaicli/run.go | 17 | ||||
| -rw-r--r-- | internal/llmutils/temperature.go | 37 | ||||
| -rw-r--r-- | internal/llmutils/temperature_test.go | 79 | ||||
| -rw-r--r-- | internal/lsp/handlers_utils.go | 21 |
5 files changed, 125 insertions, 47 deletions
diff --git a/internal/hexaiaction/prompts.go b/internal/hexaiaction/prompts.go index 2424819..61775d4 100644 --- a/internal/hexaiaction/prompts.go +++ b/internal/hexaiaction/prompts.go @@ -42,22 +42,10 @@ func canonicalProvider(name string) string { return llmutils.CanonicalProvider(name) } +// selectActionTemperature resolves the effective temperature for a code action, +// delegating GPT-5 override logic to llmutils.ResolveTemperature. func selectActionTemperature(cfg actionConfig, provider string, entry appconfig.SurfaceConfig, model string) (float64, bool) { - core := cfg.CoreSection() - if entry.Temperature != nil { - return *entry.Temperature, true - } - if core.CodingTemperature != nil { - temp := *core.CodingTemperature - if provider == "openai" && strings.HasPrefix(strings.ToLower(model), "gpt-5") && temp == 0.2 { - temp = 1.0 - } - return temp, true - } - if provider == "openai" && strings.HasPrefix(strings.ToLower(model), "gpt-5") { - return 1.0, true - } - return 0, false + return llmutils.ResolveTemperature(provider, model, entry.Temperature, cfg.CoreSection().CodingTemperature) } func runRewrite(ctx context.Context, cfg actionConfig, client chatDoer, instruction, selection string) (string, error) { diff --git a/internal/hexaicli/run.go b/internal/hexaicli/run.go index d485e0c..25ab7c1 100644 --- a/internal/hexaicli/run.go +++ b/internal/hexaicli/run.go @@ -86,21 +86,10 @@ func buildCLIRequest(entry appconfig.SurfaceConfig, provider string, cfg appconf return req } +// cliTemperatureFromEntry resolves the effective temperature for a CLI request, +// delegating GPT-5 override logic to llmutils.ResolveTemperature. func cliTemperatureFromEntry(cfg appconfig.App, provider string, entry appconfig.SurfaceConfig, model string) (float64, bool) { - if entry.Temperature != nil { - return *entry.Temperature, true - } - if cfg.CodingTemperature != nil { - temp := *cfg.CodingTemperature - if provider == "openai" && strings.HasPrefix(strings.ToLower(model), "gpt-5") && temp == 0.2 { - temp = 1.0 - } - return temp, true - } - if provider == "openai" && strings.HasPrefix(strings.ToLower(model), "gpt-5") { - return 1.0, true - } - return 0, false + return llmutils.ResolveTemperature(provider, model, entry.Temperature, cfg.CodingTemperature) } func canonicalProvider(name string) string { 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) + } + } +} diff --git a/internal/lsp/handlers_utils.go b/internal/lsp/handlers_utils.go index 620b3a9..d1a9ec3 100644 --- a/internal/lsp/handlers_utils.go +++ b/internal/lsp/handlers_utils.go @@ -115,29 +115,14 @@ func surfaceConfigsFor(cfg appconfig.App, surface surfaceKind) []appconfig.Surfa } } +// chooseSurfaceTemperature resolves the effective temperature for a surface +// request, delegating GPT-5 override logic to llmutils.ResolveTemperature. func chooseSurfaceTemperature(surface surfaceKind, cfg appconfig.App, entry appconfig.SurfaceConfig, provider string, fallbackModel string) (float64, bool) { - if entry.Temperature != nil { - return *entry.Temperature, true - } - if cfg.CodingTemperature != nil { - temp := *cfg.CodingTemperature - effectiveModel := strings.TrimSpace(entry.Model) - if effectiveModel == "" { - effectiveModel = strings.TrimSpace(fallbackModel) - } - if provider == "openai" && strings.HasPrefix(strings.ToLower(effectiveModel), "gpt-5") && temp == 0.2 { - temp = 1.0 - } - return temp, true - } effectiveModel := strings.TrimSpace(entry.Model) if effectiveModel == "" { effectiveModel = strings.TrimSpace(fallbackModel) } - if provider == "openai" && strings.HasPrefix(strings.ToLower(effectiveModel), "gpt-5") { - return 1.0, true - } - return 0, false + return llmutils.ResolveTemperature(provider, effectiveModel, entry.Temperature, cfg.CodingTemperature) } // small helpers for LLM traffic stats |
