detached 85a3c3b

Add temperature option

commit
85a3c3b73ec7be46abfcadc1b696d349eb9f1e58
author
patriceckhart · · 2026-06-14 09:42 UTC
parent
798174c
signature
unsigned
refs
tag: v0.2.31
8 files · +87 -25
M packages/agent/args.go +14 -0
@@ -3,6 +3,7 @@ package agent
33 import (
44 "fmt"
55 "os"
6+ "strconv"
67 "strings"
78
89 "github.com/patriceckhart/zot/packages/tui"
@@ -37,6 +38,7 @@ type Args struct {
3738 SystemPrompt string
3839 AppendSystemPrompt []string
3940 Reasoning string
41+ Temperature *float32
4042
4143 Continue bool
4244 Resume bool
@@ -201,6 +203,17 @@ func ParseArgs(in []string) (Args, error) {
201203 default:
202204 return a, fmt.Errorf("--reasoning must be off|minimum|low|medium|high|maximum")
203205 }
206+ case "--temperature":
207+ v, err := want(&i, arg)
208+ if err != nil {
209+ return a, err
210+ }
211+ f, err := strconv.ParseFloat(v, 32)
212+ if err != nil || f < 0 || f > 2 {
213+ return a, fmt.Errorf("--temperature must be a number between 0 and 2")
214+ }
215+ t := float32(f)
216+ a.Temperature = &t
204217 case "--session":
205218 v, err := want(&i, arg)
206219 if err != nil {
@@ -361,6 +374,7 @@ func PrintHelp(version string) {
361374 row{"--api-key KEY", "api key for this run (env / auth.json fallback)"},
362375 row{"--base-url URL", "override provider api base url"},
363376 row{"--reasoning off|minimum|low|medium|high|maximum", "set thinking level on supported models"},
377+ row{"--temperature N", "sampling temperature, 0 to 2 (omit for provider default)"},
364378 )
365379 section("prompt and session flags",
366380 row{"--system-prompt TEXT", "replace the default system prompt"},
@@ -3,6 +3,7 @@ package agent
3 import (3 import (
4 "fmt"4 "fmt"
5 "os"5 "os"
6+ "strconv"
6 "strings"7 "strings"
7 8
8 "github.com/patriceckhart/zot/packages/tui"9 "github.com/patriceckhart/zot/packages/tui"
@@ -37,6 +38,7 @@ type Args struct {
37 SystemPrompt string38 SystemPrompt string
38 AppendSystemPrompt []string39 AppendSystemPrompt []string
39 Reasoning string40 Reasoning string
41+ Temperature *float32
40 42
41 Continue bool43 Continue bool
42 Resume bool44 Resume bool
@@ -201,6 +203,17 @@ func ParseArgs(in []string) (Args, error) {
201 default:203 default:
202 return a, fmt.Errorf("--reasoning must be off|minimum|low|medium|high|maximum")204 return a, fmt.Errorf("--reasoning must be off|minimum|low|medium|high|maximum")
203 }205 }
206+ case "--temperature":
207+ v, err := want(&i, arg)
208+ if err != nil {
209+ return a, err
210+ }
211+ f, err := strconv.ParseFloat(v, 32)
212+ if err != nil || f < 0 || f > 2 {
213+ return a, fmt.Errorf("--temperature must be a number between 0 and 2")
214+ }
215+ t := float32(f)
216+ a.Temperature = &t
204 case "--session":217 case "--session":
205 v, err := want(&i, arg)218 v, err := want(&i, arg)
206 if err != nil {219 if err != nil {
@@ -361,6 +374,7 @@ func PrintHelp(version string) {
361 row{"--api-key KEY", "api key for this run (env / auth.json fallback)"},374 row{"--api-key KEY", "api key for this run (env / auth.json fallback)"},
362 row{"--base-url URL", "override provider api base url"},375 row{"--base-url URL", "override provider api base url"},
363 row{"--reasoning off|minimum|low|medium|high|maximum", "set thinking level on supported models"},376 row{"--reasoning off|minimum|low|medium|high|maximum", "set thinking level on supported models"},
377+ row{"--temperature N", "sampling temperature, 0 to 2 (omit for provider default)"},
364 )378 )
365 section("prompt and session flags",379 section("prompt and session flags",
366 row{"--system-prompt TEXT", "replace the default system prompt"},380 row{"--system-prompt TEXT", "replace the default system prompt"},
A packages/agent/args_test.go +19 -0
@@ -0,0 +1,19 @@
1+package agent
2+
3+import "testing"
4+
5+func TestParseArgsTemperatureAllowsZero(t *testing.T) {
6+ args, err := ParseArgs([]string{"--temperature", "0"})
7+ if err != nil {
8+ t.Fatalf("ParseArgs returned %v", err)
9+ }
10+ if args.Temperature == nil || *args.Temperature != 0 {
11+ t.Fatalf("Temperature = %v; want 0", args.Temperature)
12+ }
13+}
14+
15+func TestParseArgsTemperatureRejectsOutOfRange(t *testing.T) {
16+ if _, err := ParseArgs([]string{"--temperature", "2.1"}); err == nil {
17+ t.Fatal("ParseArgs accepted out-of-range temperature")
18+ }
19+}
@@ -0,0 +1,19 @@
1+package agent
2+
3+import "testing"
4+
5+func TestParseArgsTemperatureAllowsZero(t *testing.T) {
6+ args, err := ParseArgs([]string{"--temperature", "0"})
7+ if err != nil {
8+ t.Fatalf("ParseArgs returned %v", err)
9+ }
10+ if args.Temperature == nil || *args.Temperature != 0 {
11+ t.Fatalf("Temperature = %v; want 0", args.Temperature)
12+ }
13+}
14+
15+func TestParseArgsTemperatureRejectsOutOfRange(t *testing.T) {
16+ if _, err := ParseArgs([]string{"--temperature", "2.1"}); err == nil {
17+ t.Fatal("ParseArgs accepted out-of-range temperature")
18+ }
19+}
M packages/agent/build.go +15 -8
@@ -17,14 +17,15 @@ import (
1717
1818 // Resolved is the effective configuration after merging CLI, config, defaults.
1919 type Resolved struct {
20- Provider string
21- Model string
22- Credential string // api key or oauth access token
23- AuthMethod string // "apikey" | "oauth" | "" (no credential yet)
24- AccountID string // ChatGPT account id (for openai oauth), "" otherwise
25- BaseURL string
26- CWD string
27- Reasoning string
20+ Provider string
21+ Model string
22+ Credential string // api key or oauth access token
23+ AuthMethod string // "apikey" | "oauth" | "" (no credential yet)
24+ AccountID string // ChatGPT account id (for openai oauth), "" otherwise
25+ BaseURL string
26+ CWD string
27+ Reasoning string
28+ Temperature *float32
2829
2930 ToolRegistry core.Registry
3031 ToolSummary []ToolSummary
@@ -492,6 +493,10 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
492493 })
493494
494495 reasoning := provider.NormalizeReasoning(firstNonEmpty(args.Reasoning, cfg.Reasoning))
496+ temperature := args.Temperature
497+ if temperature == nil {
498+ temperature = cfg.Temperature
499+ }
495500
496501 max := args.MaxSteps // 0 = unlimited
497502
@@ -504,6 +509,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
504509 BaseURL: args.BaseURL,
505510 CWD: args.CWD,
506511 Reasoning: reasoning,
512+ Temperature: temperature,
507513 ToolRegistry: reg,
508514 ToolSummary: summaries,
509515 SystemPrompt: sys,
@@ -778,6 +784,7 @@ func (r Resolved) NewAgent() *core.Agent {
778784 a.MaxSteps = r.MaxSteps
779785 a.MaxTokens = r.MaxOutput
780786 a.Reasoning = r.Reasoning
787+ a.Temperature = r.Temperature
781788 return a
782789 }
783790
@@ -17,14 +17,15 @@ import (
17 17
18 // Resolved is the effective configuration after merging CLI, config, defaults.18 // Resolved is the effective configuration after merging CLI, config, defaults.
19 type Resolved struct {19 type Resolved struct {
20- Provider string20+ Provider string
21- Model string21+ Model string
22- Credential string // api key or oauth access token22+ Credential string // api key or oauth access token
23- AuthMethod string // "apikey" | "oauth" | "" (no credential yet)23+ AuthMethod string // "apikey" | "oauth" | "" (no credential yet)
24- AccountID string // ChatGPT account id (for openai oauth), "" otherwise24+ AccountID string // ChatGPT account id (for openai oauth), "" otherwise
25- BaseURL string25+ BaseURL string
26- CWD string26+ CWD string
27- Reasoning string27+ Reasoning string
28+ Temperature *float32
28 29
29 ToolRegistry core.Registry30 ToolRegistry core.Registry
30 ToolSummary []ToolSummary31 ToolSummary []ToolSummary
@@ -492,6 +493,10 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
492 })493 })
493 494
494 reasoning := provider.NormalizeReasoning(firstNonEmpty(args.Reasoning, cfg.Reasoning))495 reasoning := provider.NormalizeReasoning(firstNonEmpty(args.Reasoning, cfg.Reasoning))
496+ temperature := args.Temperature
497+ if temperature == nil {
498+ temperature = cfg.Temperature
499+ }
495 500
496 max := args.MaxSteps // 0 = unlimited501 max := args.MaxSteps // 0 = unlimited
497 502
@@ -504,6 +509,7 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
504 BaseURL: args.BaseURL,509 BaseURL: args.BaseURL,
505 CWD: args.CWD,510 CWD: args.CWD,
506 Reasoning: reasoning,511 Reasoning: reasoning,
512+ Temperature: temperature,
507 ToolRegistry: reg,513 ToolRegistry: reg,
508 ToolSummary: summaries,514 ToolSummary: summaries,
509 SystemPrompt: sys,515 SystemPrompt: sys,
@@ -778,6 +784,7 @@ func (r Resolved) NewAgent() *core.Agent {
778 a.MaxSteps = r.MaxSteps784 a.MaxSteps = r.MaxSteps
779 a.MaxTokens = r.MaxOutput785 a.MaxTokens = r.MaxOutput
780 a.Reasoning = r.Reasoning786 a.Reasoning = r.Reasoning
787+ a.Temperature = r.Temperature
781 return a788 return a
782 }789 }
783 790
M packages/agent/config.go +5 -4
@@ -16,10 +16,11 @@ import (
1616
1717 // Config is the persisted user configuration.
1818 type Config struct {
19- Provider string `json:"provider"`
20- Model string `json:"model"`
21- Reasoning string `json:"reasoning"`
22- Theme string `json:"theme"`
19+ Provider string `json:"provider"`
20+ Model string `json:"model"`
21+ Reasoning string `json:"reasoning"`
22+ Temperature *float32 `json:"temperature,omitempty"`
23+ Theme string `json:"theme"`
2324
2425 // InlineImagesEnabled controls whether zot draws screenshots inline
2526 // when the terminal supports an image protocol. nil/missing means
@@ -16,10 +16,11 @@ import (
16 16
17 // Config is the persisted user configuration.17 // Config is the persisted user configuration.
18 type Config struct {18 type Config struct {
19- Provider string `json:"provider"`19+ Provider string `json:"provider"`
20- Model string `json:"model"`20+ Model string `json:"model"`
21- Reasoning string `json:"reasoning"`21+ Reasoning string `json:"reasoning"`
22- Theme string `json:"theme"`22+ Temperature *float32 `json:"temperature,omitempty"`
23+ Theme string `json:"theme"`
23 24
24 // InlineImagesEnabled controls whether zot draws screenshots inline25 // InlineImagesEnabled controls whether zot draws screenshots inline
25 // when the terminal supports an image protocol. nil/missing means26 // when the terminal supports an image protocol. nil/missing means
M packages/agent/sdk/sdk.go +4 -0
@@ -61,6 +61,9 @@ type Config struct {
6161 // ("low", "medium", "high"). Empty = no reasoning.
6262 Reasoning string
6363
64+ // Temperature sets the sampling temperature. Nil = provider default.
65+ Temperature *float32
66+
6467 // MaxSteps caps the agent loop iterations per Prompt call.
6568 // 0 uses the default (50).
6669 MaxSteps int
@@ -112,6 +115,7 @@ func New(cfg Config) (*Runtime, error) {
112115 SystemPrompt: cfg.SystemPrompt,
113116 AppendSystemPrompt: cfg.AppendSystemPrompt,
114117 Reasoning: cfg.Reasoning,
118+ Temperature: cfg.Temperature,
115119 MaxSteps: cfg.MaxSteps,
116120 Tools: cfg.Tools,
117121 NoTools: cfg.NoTools,
@@ -61,6 +61,9 @@ type Config struct {
61 // ("low", "medium", "high"). Empty = no reasoning.61 // ("low", "medium", "high"). Empty = no reasoning.
62 Reasoning string62 Reasoning string
63 63
64+ // Temperature sets the sampling temperature. Nil = provider default.
65+ Temperature *float32
66+
64 // MaxSteps caps the agent loop iterations per Prompt call.67 // MaxSteps caps the agent loop iterations per Prompt call.
65 // 0 uses the default (50).68 // 0 uses the default (50).
66 MaxSteps int69 MaxSteps int
@@ -112,6 +115,7 @@ func New(cfg Config) (*Runtime, error) {
112 SystemPrompt: cfg.SystemPrompt,115 SystemPrompt: cfg.SystemPrompt,
113 AppendSystemPrompt: cfg.AppendSystemPrompt,116 AppendSystemPrompt: cfg.AppendSystemPrompt,
114 Reasoning: cfg.Reasoning,117 Reasoning: cfg.Reasoning,
118+ Temperature: cfg.Temperature,
115 MaxSteps: cfg.MaxSteps,119 MaxSteps: cfg.MaxSteps,
116 Tools: cfg.Tools,120 Tools: cfg.Tools,
117 NoTools: cfg.NoTools,121 NoTools: cfg.NoTools,
M packages/core/agent.go +12 -10
@@ -15,12 +15,13 @@ import (
1515 // Agent is a stateful conversation bound to a provider client, a model,
1616 // and a set of tools.
1717 type Agent struct {
18- Client provider.Client
19- Model string
20- System string
21- Tools Registry
22- MaxSteps int
23- Reasoning string
18+ Client provider.Client
19+ Model string
20+ System string
21+ Tools Registry
22+ MaxSteps int
23+ Reasoning string
24+ Temperature *float32
2425
2526 // MaxTokens caps the model's output tokens per turn. Zero leaves
2627 // the field unset on the provider request, letting each provider
@@ -520,10 +521,11 @@ func (a *Agent) oneTurn(ctx context.Context, sink func(AgentEvent)) (provider.St
520521 // next in-process request is rejected by providers like Anthropic
521522 // with "tool_use ids were found without tool_result blocks". The
522523 // repair is pure and a no-op on already-valid transcripts.
523- Messages: repairToolUseResultPairs(a.Messages()),
524- Tools: a.Tools.Specs(),
525- Reasoning: a.Reasoning,
526- MaxTokens: a.MaxTokens,
524+ Messages: repairToolUseResultPairs(a.Messages()),
525+ Tools: a.Tools.Specs(),
526+ Reasoning: a.Reasoning,
527+ MaxTokens: a.MaxTokens,
528+ Temperature: a.Temperature,
527529 }
528530 stream, err := a.Client.Stream(ctx, req)
529531 if err != nil {
@@ -15,12 +15,13 @@ import (
15 // Agent is a stateful conversation bound to a provider client, a model,15 // Agent is a stateful conversation bound to a provider client, a model,
16 // and a set of tools.16 // and a set of tools.
17 type Agent struct {17 type Agent struct {
18- Client provider.Client18+ Client provider.Client
19- Model string19+ Model string
20- System string20+ System string
21- Tools Registry21+ Tools Registry
22- MaxSteps int22+ MaxSteps int
23- Reasoning string23+ Reasoning string
24+ Temperature *float32
24 25
25 // MaxTokens caps the model's output tokens per turn. Zero leaves26 // MaxTokens caps the model's output tokens per turn. Zero leaves
26 // the field unset on the provider request, letting each provider27 // the field unset on the provider request, letting each provider
@@ -520,10 +521,11 @@ func (a *Agent) oneTurn(ctx context.Context, sink func(AgentEvent)) (provider.St
520 // next in-process request is rejected by providers like Anthropic521 // next in-process request is rejected by providers like Anthropic
521 // with "tool_use ids were found without tool_result blocks". The522 // with "tool_use ids were found without tool_result blocks". The
522 // repair is pure and a no-op on already-valid transcripts.523 // repair is pure and a no-op on already-valid transcripts.
523- Messages: repairToolUseResultPairs(a.Messages()),524+ Messages: repairToolUseResultPairs(a.Messages()),
524- Tools: a.Tools.Specs(),525+ Tools: a.Tools.Specs(),
525- Reasoning: a.Reasoning,526+ Reasoning: a.Reasoning,
526- MaxTokens: a.MaxTokens,527+ MaxTokens: a.MaxTokens,
528+ Temperature: a.Temperature,
527 }529 }
528 stream, err := a.Client.Stream(ctx, req)530 stream, err := a.Client.Stream(ctx, req)
529 if err != nil {531 if err != nil {
M packages/core/agent_retry_test.go +14 -0
@@ -144,3 +144,17 @@ func TestAgentPropagatesMaxTokens(t *testing.T) {
144144 t.Fatalf("request MaxTokens = %d; want 64000 (Agent.MaxTokens not propagated)", client.lastReq.MaxTokens)
145145 }
146146 }
147+
148+func TestAgentPropagatesTemperature(t *testing.T) {
149+ client := &captureClient{}
150+ a := NewAgent(client, "fake-model", "system", Registry{})
151+ temp := float32(0)
152+ a.Temperature = &temp
153+
154+ if err := a.Prompt(context.Background(), "hello", nil, nil); err != nil {
155+ t.Fatalf("Prompt returned %v", err)
156+ }
157+ if client.lastReq.Temperature == nil || *client.lastReq.Temperature != temp {
158+ t.Fatalf("request Temperature = %v; want %v", client.lastReq.Temperature, temp)
159+ }
160+}
@@ -144,3 +144,17 @@ func TestAgentPropagatesMaxTokens(t *testing.T) {
144 t.Fatalf("request MaxTokens = %d; want 64000 (Agent.MaxTokens not propagated)", client.lastReq.MaxTokens)144 t.Fatalf("request MaxTokens = %d; want 64000 (Agent.MaxTokens not propagated)", client.lastReq.MaxTokens)
145 }145 }
146 }146 }
147+
148+func TestAgentPropagatesTemperature(t *testing.T) {
149+ client := &captureClient{}
150+ a := NewAgent(client, "fake-model", "system", Registry{})
151+ temp := float32(0)
152+ a.Temperature = &temp
153+
154+ if err := a.Prompt(context.Background(), "hello", nil, nil); err != nil {
155+ t.Fatalf("Prompt returned %v", err)
156+ }
157+ if client.lastReq.Temperature == nil || *client.lastReq.Temperature != temp {
158+ t.Fatalf("request Temperature = %v; want %v", client.lastReq.Temperature, temp)
159+ }
160+}
M packages/core/compact.go +4 -3
@@ -47,9 +47,10 @@ func (a *Agent) Compact(ctx context.Context, keepTail int, sink func(delta strin
4747 prompt := "<conversation>\n" + transcript + "\n</conversation>\n\n" + compactionPrompt
4848
4949 req := provider.Request{
50- Model: a.Model,
51- System: summarizationSystem,
52- MaxTokens: 4096,
50+ Model: a.Model,
51+ System: summarizationSystem,
52+ MaxTokens: 4096,
53+ Temperature: a.Temperature,
5354 Messages: []provider.Message{
5455 {
5556 Role: provider.RoleUser,
@@ -47,9 +47,10 @@ func (a *Agent) Compact(ctx context.Context, keepTail int, sink func(delta strin
47 prompt := "<conversation>\n" + transcript + "\n</conversation>\n\n" + compactionPrompt47 prompt := "<conversation>\n" + transcript + "\n</conversation>\n\n" + compactionPrompt
48 48
49 req := provider.Request{49 req := provider.Request{
50- Model: a.Model,50+ Model: a.Model,
51- System: summarizationSystem,51+ System: summarizationSystem,
52- MaxTokens: 4096,52+ MaxTokens: 4096,
53+ Temperature: a.Temperature,
53 Messages: []provider.Message{54 Messages: []provider.Message{
54 {55 {
55 Role: provider.RoleUser,56 Role: provider.RoleUser,

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close