From f6df9991e586d42a540eab44eca69c69d7e6b062 Mon Sep 17 00:00:00 2001 From: easyzoom Date: Thu, 19 Feb 2026 10:51:40 +0800 Subject: [PATCH] feat(config): add context_window, summary_max_tokens, summary_temperature --- config/config.example.json | 3 ++ pkg/agent/instance.go | 85 +++++++++++++++++++++++-------------- pkg/agent/loop.go | 8 ++-- pkg/agent/loop_test.go | 11 +++++ pkg/agent/registry_test.go | 1 + pkg/config/config.go | 3 ++ pkg/config/config_test.go | 12 ++++++ pkg/config/defaults.go | 3 ++ pkg/migrate/config.go | 12 ++++++ pkg/migrate/migrate_test.go | 3 ++ 10 files changed, 104 insertions(+), 37 deletions(-) diff --git a/config/config.example.json b/config/config.example.json index b3a26f811..f578518b3 100644 --- a/config/config.example.json +++ b/config/config.example.json @@ -4,8 +4,11 @@ "workspace": "~/.picoclaw/workspace", "restrict_to_workspace": true, "model": "gpt4", + "context_window": 8192, "max_tokens": 8192, "temperature": 0.7, + "summary_max_tokens": 1024, + "summary_temperature": 0.3, "max_tool_iterations": 20 } }, diff --git a/pkg/agent/instance.go b/pkg/agent/instance.go index dfbef9fbc..5ee0296fc 100644 --- a/pkg/agent/instance.go +++ b/pkg/agent/instance.go @@ -15,22 +15,24 @@ import ( // AgentInstance represents a fully configured agent with its own workspace, // session manager, context builder, and tool registry. type AgentInstance struct { - ID string - Name string - Model string - Fallbacks []string - Workspace string - MaxIterations int - MaxTokens int - Temperature float64 - ContextWindow int - Provider providers.LLMProvider - Sessions *session.SessionManager - ContextBuilder *ContextBuilder - Tools *tools.ToolRegistry - Subagents *config.SubagentsConfig - SkillsFilter []string - Candidates []providers.FallbackCandidate + ID string + Name string + Model string + Fallbacks []string + Workspace string + MaxIterations int + ContextWindow int // Token limit used for summarization threshold and message-size guard (not the LLM response limit). + MaxTokens int // Max tokens allowed in a single LLM response (passed to provider Chat). + Temperature float64 + SummaryMaxTokens int + SummaryTemperature float64 + Provider providers.LLMProvider + Sessions *session.SessionManager + ContextBuilder *ContextBuilder + Tools *tools.ToolRegistry + Subagents *config.SubagentsConfig + SkillsFilter []string + Candidates []providers.FallbackCandidate } // NewAgentInstance creates an agent instance from config. @@ -78,11 +80,26 @@ func NewAgentInstance( maxIter = 20 } + contextWindow := defaults.ContextWindow + if contextWindow <= 0 { + contextWindow = 8192 + } + maxTokens := defaults.MaxTokens - if maxTokens == 0 { + if maxTokens <= 0 { maxTokens = 8192 } + sumMaxTok := defaults.SummaryMaxTokens + if sumMaxTok <= 0 { + sumMaxTok = 1024 + } + + sumTemp := defaults.SummaryTemperature + if sumTemp <= 0 { + sumTemp = 0.3 + } + temperature := 0.7 if defaults.Temperature != nil { temperature = *defaults.Temperature @@ -96,22 +113,24 @@ func NewAgentInstance( candidates := providers.ResolveCandidates(modelCfg, defaults.Provider) return &AgentInstance{ - ID: agentID, - Name: agentName, - Model: model, - Fallbacks: fallbacks, - Workspace: workspace, - MaxIterations: maxIter, - MaxTokens: maxTokens, - Temperature: temperature, - ContextWindow: maxTokens, - Provider: provider, - Sessions: sessionsManager, - ContextBuilder: contextBuilder, - Tools: toolsRegistry, - Subagents: subagents, - SkillsFilter: skillsFilter, - Candidates: candidates, + ID: agentID, + Name: agentName, + Model: model, + Fallbacks: fallbacks, + Workspace: workspace, + MaxIterations: maxIter, + ContextWindow: contextWindow, + MaxTokens: maxTokens, + Temperature: temperature, + SummaryMaxTokens: sumMaxTok, + SummaryTemperature: sumTemp, + Provider: provider, + Sessions: sessionsManager, + ContextBuilder: contextBuilder, + Tools: toolsRegistry, + Subagents: subagents, + SkillsFilter: skillsFilter, + Candidates: candidates, } } diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 3d9da2946..b02c43a55 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -977,8 +977,8 @@ func (al *AgentLoop) summarizeSession(agent *AgentInstance, sessionKey string) { nil, agent.Model, map[string]any{ - "max_tokens": 1024, - "temperature": 0.3, + "max_tokens": agent.SummaryMaxTokens, + "temperature": agent.SummaryTemperature, }, ) if err == nil { @@ -1027,8 +1027,8 @@ func (al *AgentLoop) summarizeBatch( nil, agent.Model, map[string]any{ - "max_tokens": 1024, - "temperature": 0.3, + "max_tokens": agent.SummaryMaxTokens, + "temperature": agent.SummaryTemperature, }, ) if err != nil { diff --git a/pkg/agent/loop_test.go b/pkg/agent/loop_test.go index 4414398b1..a991aa335 100644 --- a/pkg/agent/loop_test.go +++ b/pkg/agent/loop_test.go @@ -28,6 +28,7 @@ func TestRecordLastChannel(t *testing.T) { Defaults: config.AgentDefaults{ Workspace: tmpDir, Model: "test-model", + ContextWindow: 4096, MaxTokens: 4096, MaxToolIterations: 10, }, @@ -73,6 +74,7 @@ func TestRecordLastChatID(t *testing.T) { Defaults: config.AgentDefaults{ Workspace: tmpDir, Model: "test-model", + ContextWindow: 4096, MaxTokens: 4096, MaxToolIterations: 10, }, @@ -118,6 +120,7 @@ func TestNewAgentLoop_StateInitialized(t *testing.T) { Defaults: config.AgentDefaults{ Workspace: tmpDir, Model: "test-model", + ContextWindow: 4096, MaxTokens: 4096, MaxToolIterations: 10, }, @@ -154,6 +157,7 @@ func TestToolRegistry_ToolRegistration(t *testing.T) { Defaults: config.AgentDefaults{ Workspace: tmpDir, Model: "test-model", + ContextWindow: 4096, MaxTokens: 4096, MaxToolIterations: 10, }, @@ -200,6 +204,7 @@ func TestToolContext_Updates(t *testing.T) { Defaults: config.AgentDefaults{ Workspace: tmpDir, Model: "test-model", + ContextWindow: 4096, MaxTokens: 4096, MaxToolIterations: 10, }, @@ -231,6 +236,7 @@ func TestToolRegistry_GetDefinitions(t *testing.T) { Defaults: config.AgentDefaults{ Workspace: tmpDir, Model: "test-model", + ContextWindow: 4096, MaxTokens: 4096, MaxToolIterations: 10, }, @@ -275,6 +281,7 @@ func TestAgentLoop_GetStartupInfo(t *testing.T) { Defaults: config.AgentDefaults{ Workspace: tmpDir, Model: "test-model", + ContextWindow: 4096, MaxTokens: 4096, MaxToolIterations: 10, }, @@ -322,6 +329,7 @@ func TestAgentLoop_Stop(t *testing.T) { Defaults: config.AgentDefaults{ Workspace: tmpDir, Model: "test-model", + ContextWindow: 4096, MaxTokens: 4096, MaxToolIterations: 10, }, @@ -450,6 +458,7 @@ func TestToolResult_SilentToolDoesNotSendUserMessage(t *testing.T) { Defaults: config.AgentDefaults{ Workspace: tmpDir, Model: "test-model", + ContextWindow: 4096, MaxTokens: 4096, MaxToolIterations: 10, }, @@ -492,6 +501,7 @@ func TestToolResult_UserFacingToolDoesSendMessage(t *testing.T) { Defaults: config.AgentDefaults{ Workspace: tmpDir, Model: "test-model", + ContextWindow: 4096, MaxTokens: 4096, MaxToolIterations: 10, }, @@ -563,6 +573,7 @@ func TestAgentLoop_ContextExhaustionRetry(t *testing.T) { Defaults: config.AgentDefaults{ Workspace: tmpDir, Model: "test-model", + ContextWindow: 4096, MaxTokens: 4096, MaxToolIterations: 10, }, diff --git a/pkg/agent/registry_test.go b/pkg/agent/registry_test.go index 518bb441f..28d52ff65 100644 --- a/pkg/agent/registry_test.go +++ b/pkg/agent/registry_test.go @@ -30,6 +30,7 @@ func testCfg(agents []config.AgentConfig) *config.Config { Defaults: config.AgentDefaults{ Workspace: "/tmp/picoclaw-test-registry", Model: "gpt-4", + ContextWindow: 8192, MaxTokens: 8192, MaxToolIterations: 10, }, diff --git a/pkg/config/config.go b/pkg/config/config.go index b0684bb8d..00ebbf3c9 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -178,8 +178,11 @@ type AgentDefaults struct { ModelFallbacks []string `json:"model_fallbacks,omitempty"` ImageModel string `json:"image_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_IMAGE_MODEL"` ImageModelFallbacks []string `json:"image_model_fallbacks,omitempty"` + ContextWindow int `json:"context_window" env:"PICOCLAW_AGENTS_DEFAULTS_CONTEXT_WINDOW"` MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"` Temperature *float64 `json:"temperature,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"` + SummaryMaxTokens int `json:"summary_max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_SUMMARY_MAX_TOKENS"` + SummaryTemperature float64 `json:"summary_temperature" env:"PICOCLAW_AGENTS_DEFAULTS_SUMMARY_TEMPERATURE"` MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"` } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 0898217d6..b954558f5 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -215,6 +215,15 @@ func TestDefaultConfig_Model(t *testing.T) { } } +// TestDefaultConfig_ContextWindow verifies context_window has default value +func TestDefaultConfig_ContextWindow(t *testing.T) { + cfg := DefaultConfig() + + if cfg.Agents.Defaults.ContextWindow == 0 { + t.Error("ContextWindow should not be zero") + } +} + // TestDefaultConfig_MaxTokens verifies max tokens has default value func TestDefaultConfig_MaxTokens(t *testing.T) { cfg := DefaultConfig() @@ -337,6 +346,9 @@ func TestConfig_Complete(t *testing.T) { if cfg.Agents.Defaults.Temperature != nil { t.Error("Temperature should be nil when not provided") } + if cfg.Agents.Defaults.ContextWindow == 0 { + t.Error("ContextWindow should not be zero") + } if cfg.Agents.Defaults.MaxTokens == 0 { t.Error("MaxTokens should not be zero") } diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index 7654326e7..37db85a79 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -14,7 +14,10 @@ func DefaultConfig() *Config { RestrictToWorkspace: true, Provider: "", Model: "glm-4.7", + ContextWindow: 8192, MaxTokens: 8192, + SummaryMaxTokens: 1024, + SummaryTemperature: 0.3, Temperature: nil, // nil means use provider default MaxToolIterations: 20, }, diff --git a/pkg/migrate/config.go b/pkg/migrate/config.go index 2237a1429..e57bbd62f 100644 --- a/pkg/migrate/config.go +++ b/pkg/migrate/config.go @@ -75,12 +75,24 @@ func ConvertConfig(data map[string]any) (*config.Config, []string, error) { if v, ok := getString(defaults, "model"); ok { cfg.Agents.Defaults.Model = v } + if v, ok := getFloat(defaults, "context_window"); ok { + cfg.Agents.Defaults.ContextWindow = int(v) + } else if v, ok := getFloat(defaults, "max_tokens"); ok { + // Backward compat: old configs used max_tokens for both; set context_window from it. + cfg.Agents.Defaults.ContextWindow = int(v) + } if v, ok := getFloat(defaults, "max_tokens"); ok { cfg.Agents.Defaults.MaxTokens = int(v) } if v, ok := getFloat(defaults, "temperature"); ok { cfg.Agents.Defaults.Temperature = &v } + if v, ok := getFloat(defaults, "summary_max_tokens"); ok { + cfg.Agents.Defaults.SummaryMaxTokens = int(v) + } + if v, ok := getFloat(defaults, "summary_temperature"); ok { + cfg.Agents.Defaults.SummaryTemperature = v + } if v, ok := getFloat(defaults, "max_tool_iterations"); ok { cfg.Agents.Defaults.MaxToolIterations = int(v) } diff --git a/pkg/migrate/migrate_test.go b/pkg/migrate/migrate_test.go index b6b3d70aa..173f20a09 100644 --- a/pkg/migrate/migrate_test.go +++ b/pkg/migrate/migrate_test.go @@ -272,6 +272,9 @@ func TestConvertConfig(t *testing.T) { if cfg.Agents.Defaults.Model != "claude-3-opus" { t.Errorf("Model = %q, want %q", cfg.Agents.Defaults.Model, "claude-3-opus") } + if cfg.Agents.Defaults.ContextWindow != 4096 { + t.Errorf("ContextWindow = %d, want 4096 (backward compat from max_tokens)", cfg.Agents.Defaults.ContextWindow) + } if cfg.Agents.Defaults.MaxTokens != 4096 { t.Errorf("MaxTokens = %d, want %d", cfg.Agents.Defaults.MaxTokens, 4096) }