diff options
| author | Paul Buetow <paul@buetow.org> | 2025-08-18 09:13:41 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-08-18 09:13:41 +0300 |
| commit | 6d29ac7e4b2604b5c7df50f33f8ef2357709faf2 (patch) | |
| tree | 6a164ee0a7c5a1f726447e8f29ab871af63528a1 | |
| parent | 3217d2738af345629e7da14c52fa4ee5cb288fe9 (diff) | |
feat(lsp): add coding_temperature knob and remove hardcoded temps\n\n- Add to app config and server options.\n- Use in LSP code actions and completions.\n- Default to provider temperature when not set.\n- Update README and config.json.example.
| -rw-r--r-- | README.md | 7 | ||||
| -rw-r--r-- | config.json.example | 1 | ||||
| -rw-r--r-- | internal/appconfig/config.go | 8 | ||||
| -rw-r--r-- | internal/hexailsp/run.go | 2 | ||||
| -rw-r--r-- | internal/lsp/handlers.go | 19 | ||||
| -rw-r--r-- | internal/lsp/server.go | 36 |
6 files changed, 51 insertions, 22 deletions
@@ -24,6 +24,7 @@ Hexai exposes a simple LLM provider interface. It supports OpenAI, GitHub Copilo "log_preview_limit": 100, "no_disk_io": true, "trigger_characters": [".", ":", "/", "_", ";", "?"], + "coding_temperature": 0.2, "provider": "ollama", "copilot_model": "gpt-4.1", "copilot_base_url": "https://api.githubcopilot.com", @@ -39,6 +40,7 @@ Hexai exposes a simple LLM provider interface. It supports OpenAI, GitHub Copilo * context_mode: minimal | window | file-on-new-func | always-full * provider: openai | copilot | ollama +* coding_temperature: single knob for LSP requests (optional; default uses provider temperature) * openai_model, openai_base_url, openai_temperature: OpenAI-only options * copilot_model, copilot_base_url, copilot_temperature: Copilot-only options * ollama_model, ollama_base_url, ollama_temperature: Ollama-only options @@ -84,8 +86,9 @@ Ensure `OPENAI_API_KEY` or `COPILOT_API_KEY` is set in your environment accordin `ollama_temperature` to override. Valid ranges depend on the provider, but typically `0.0`–`2.0`. * LSP vs CLI: The LSP sometimes overrides temperature for specific actions - (e.g., `0.1`–`0.2` for completions). The CLI uses the configured provider - default unless you change it. + using `coding_temperature` (if set). If `coding_temperature` is not set, + LSP calls use the provider default temperature. The CLI uses the configured + provider default unless you change it. Recommended ranges and use cases: diff --git a/config.json.example b/config.json.example index 04ecfb7..b8c729b 100644 --- a/config.json.example +++ b/config.json.example @@ -6,6 +6,7 @@ "log_preview_limit": 100, "no_disk_io": true, "trigger_characters": [".", ":", "/", "_", ";", "?"], + "coding_temperature": 0.2, "provider": "openai", diff --git a/internal/appconfig/config.go b/internal/appconfig/config.go index c377467..4fa3441 100644 --- a/internal/appconfig/config.go +++ b/internal/appconfig/config.go @@ -17,7 +17,9 @@ type App struct { ContextMode string `json:"context_mode"` ContextWindowLines int `json:"context_window_lines"` MaxContextTokens int `json:"max_context_tokens"` - LogPreviewLimit int `json:"log_preview_limit"` + LogPreviewLimit int `json:"log_preview_limit"` + // Single knob for LSP requests; if set, overrides hardcoded temps in LSP. + CodingTemperature *float64 `json:"coding_temperature"` TriggerCharacters []string `json:"trigger_characters"` Provider string `json:"provider"` @@ -48,6 +50,7 @@ func newDefaultConfig() App { ContextWindowLines: 120, MaxContextTokens: 4000, LogPreviewLimit: 100, + CodingTemperature: &t, OpenAITemperature: &t, OllamaTemperature: &t, CopilotTemperature: &t, @@ -115,6 +118,9 @@ func (a *App) mergeWith(other *App) { if other.LogPreviewLimit >= 0 { a.LogPreviewLimit = other.LogPreviewLimit } + if other.CodingTemperature != nil { // allow explicit 0.0 + a.CodingTemperature = other.CodingTemperature + } if len(other.TriggerCharacters) > 0 { a.TriggerCharacters = slices.Clone(other.TriggerCharacters) } diff --git a/internal/hexailsp/run.go b/internal/hexailsp/run.go index 2eee3aa..1beb93a 100644 --- a/internal/hexailsp/run.go +++ b/internal/hexailsp/run.go @@ -83,6 +83,8 @@ func RunWithFactory(logPath string, stdin io.Reader, stdout io.Writer, logger *l WindowLines: cfg.ContextWindowLines, MaxContextTokens: cfg.MaxContextTokens, + CodingTemperature: cfg.CodingTemperature, + Client: client, TriggerCharacters: cfg.TriggerCharacters, }) diff --git a/internal/lsp/handlers.go b/internal/lsp/handlers.go index 7903c73..d21c5b3 100644 --- a/internal/lsp/handlers.go +++ b/internal/lsp/handlers.go @@ -93,7 +93,12 @@ func (s *Server) handleCodeAction(req Request) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() messages := []llm.Message{{Role: "system", Content: sys}, {Role: "user", Content: user}} - if text, err := s.llmClient.Chat(ctx, messages, llm.WithMaxTokens(s.maxTokens), llm.WithTemperature(0.1)); err == nil { + // Build request options from server settings + opts := []llm.RequestOption{llm.WithMaxTokens(s.maxTokens)} + if s.codingTemperature != nil { + opts = append(opts, llm.WithTemperature(*s.codingTemperature)) + } + if text, err := s.llmClient.Chat(ctx, messages, opts...); err == nil { out := strings.TrimSpace(text) if out != "" { edit := WorkspaceEdit{Changes: map[string][]TextEdit{p.TextDocument.URI: {{Range: p.Range, NewText: out}}}} @@ -123,7 +128,11 @@ func (s *Server) handleCodeAction(req Request) { ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second) defer cancel() messages := []llm.Message{{Role: "system", Content: sys}, {Role: "user", Content: b.String()}} - if text, err := s.llmClient.Chat(ctx, messages, llm.WithMaxTokens(s.maxTokens), llm.WithTemperature(0.1)); err == nil { + opts := []llm.RequestOption{llm.WithMaxTokens(s.maxTokens)} + if s.codingTemperature != nil { + opts = append(opts, llm.WithTemperature(*s.codingTemperature)) + } + if text, err := s.llmClient.Chat(ctx, messages, opts...); err == nil { out := strings.TrimSpace(text) if out != "" { edit := WorkspaceEdit{Changes: map[string][]TextEdit{p.TextDocument.URI: {{Range: p.Range, NewText: out}}}} @@ -454,7 +463,11 @@ func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, fun s.llmSentBytesTotal += int64(sentSize) s.mu.Unlock() - text, err := s.llmClient.Chat(ctx, messages, llm.WithMaxTokens(s.maxTokens), llm.WithTemperature(0.2)) + opts := []llm.RequestOption{llm.WithMaxTokens(s.maxTokens)} + if s.codingTemperature != nil { + opts = append(opts, llm.WithTemperature(*s.codingTemperature)) + } + text, err := s.llmClient.Chat(ctx, messages, opts...) if err != nil { logging.Logf("lsp ", "llm completion error: %v", err) // Log updated averages after this request (even if failed) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 5f4423a..f1ca302 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -29,7 +29,9 @@ type Server struct { windowLines int maxContextTokens int noDiskIO bool - triggerChars []string + triggerChars []string + // If set, used as the LSP coding temperature for all LLM calls + codingTemperature *float64 // LLM request stats llmReqTotal int64 llmSentBytesTotal int64 @@ -40,14 +42,15 @@ type Server struct { // ServerOptions collects configuration for NewServer to avoid long parameter lists. type ServerOptions struct { - LogContext bool - MaxTokens int - ContextMode string - WindowLines int - MaxContextTokens int - - Client llm.Client - TriggerCharacters []string + LogContext bool + MaxTokens int + ContextMode string + WindowLines int + MaxContextTokens int + + Client llm.Client + TriggerCharacters []string + CodingTemperature *float64 } func NewServer(r io.Reader, w io.Writer, logger *log.Logger, opts ServerOptions) *Server { @@ -74,13 +77,14 @@ func NewServer(r io.Reader, w io.Writer, logger *log.Logger, opts ServerOptions) s.maxContextTokens = maxContextTokens s.startTime = time.Now() - s.llmClient = opts.Client - if len(opts.TriggerCharacters) == 0 { - s.triggerChars = []string{".", ":", "/", "_", ";", "?"} - } else { - s.triggerChars = append([]string{}, opts.TriggerCharacters...) - } - return s + s.llmClient = opts.Client + if len(opts.TriggerCharacters) == 0 { + s.triggerChars = []string{".", ":", "/", "_", ";", "?"} + } else { + s.triggerChars = append([]string{}, opts.TriggerCharacters...) + } + s.codingTemperature = opts.CodingTemperature + return s } func (s *Server) Run() error { |
