summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-08-18 09:13:41 +0300
committerPaul Buetow <paul@buetow.org>2025-08-18 09:13:41 +0300
commit6d29ac7e4b2604b5c7df50f33f8ef2357709faf2 (patch)
tree6a164ee0a7c5a1f726447e8f29ab871af63528a1
parent3217d2738af345629e7da14c52fa4ee5cb288fe9 (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.md7
-rw-r--r--config.json.example1
-rw-r--r--internal/appconfig/config.go8
-rw-r--r--internal/hexailsp/run.go2
-rw-r--r--internal/lsp/handlers.go19
-rw-r--r--internal/lsp/server.go36
6 files changed, 51 insertions, 22 deletions
diff --git a/README.md b/README.md
index cb74f95..b8a85a4 100644
--- a/README.md
+++ b/README.md
@@ -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 {