feat: add configurable summary and compression trigger thresholds

- Add CompressionTriggerRatio, SummaryTriggerMessages, SummaryTriggerRatio to config
- Pass maxTokens and temperature to subagent for consistent LLM settings
- Update ToolLoop to support configurable MaxTokens and Temperature
- Update default config values and documentation
- Fix hardcoded values (8192, 0.7) to use configurable parameters
This commit is contained in:
Candlest 2026-02-16 22:05:35 +08:00
parent 13e4028d42
commit 6464ca4880
8 changed files with 159 additions and 68 deletions

View file

@ -198,7 +198,10 @@ picoclaw onboard
"model": "glm-4.7", "model": "glm-4.7",
"max_tokens": 8192, "max_tokens": 8192,
"temperature": 0.7, "temperature": 0.7,
"max_tool_iterations": 20 "max_tool_iterations": 20,
"compression_trigger_ratio": 0.75,
"summary_trigger_messages": 20,
"summary_trigger_ratio": 0.75
} }
}, },
"providers": { "providers": {
@ -650,6 +653,30 @@ The subagent has access to tools (message, web_search, etc.) and can communicate
* `PICOCLAW_HEARTBEAT_ENABLED=false` to disable * `PICOCLAW_HEARTBEAT_ENABLED=false` to disable
* `PICOCLAW_HEARTBEAT_INTERVAL=60` to change interval * `PICOCLAW_HEARTBEAT_INTERVAL=60` to change interval
### Threshold Configuration
PicoClaw supports customizable conversation summary and compression trigger thresholds to prevent premature context truncation.
| Configuration | Default | Description |
|--------------|---------|-------------|
| `max_tokens` | `8192` | Context window size and LLM output token limit |
| `compression_trigger_ratio` | `0.75` | Trigger force compression when context exceeds `max_tokens × compression_trigger_ratio` |
| `summary_trigger_messages` | `20` | Trigger automatic summary when history message count exceeds this value (set to 0 to disable message count-based triggering) |
| `summary_trigger_ratio` | `0.75` | Trigger automatic summary when history token estimate exceeds `max_tokens × summary_trigger_ratio` |
**Environment variables:**
* `PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS=16384` Increase context window
* `PICOCLAW_AGENTS_DEFAULTS_COMPRESSION_TRIGGER_RATIO=0.9` Raise compression trigger threshold (e.g., from 75% to 90%)
* `PICOCLAW_AGENTS_DEFAULTS_SUMMARY_TRIGGER_MESSAGES=50` Delay message count-based triggering (e.g., from 20 to 50 messages)
* `PICOCLAW_AGENTS_DEFAULTS_SUMMARY_TRIGGER_RATIO=0.85` Raise summary trigger threshold (e.g., from 75% to 85%)
**Usage recommendations:**
- **Longer conversations**: Increase all thresholds for longer conversations
- **Faster response**: Decrease thresholds for quicker summarization and compression
- **Token-only control**: Set `summary_trigger_messages=0` to only use `summary_trigger_ratio` for summary triggering
### Providers ### Providers
> [!NOTE] > [!NOTE]

View file

@ -204,7 +204,10 @@ picoclaw onboard
"model": "glm-4.7", "model": "glm-4.7",
"max_tokens": 8192, "max_tokens": 8192,
"temperature": 0.7, "temperature": 0.7,
"max_tool_iterations": 20 "max_tool_iterations": 20,
"compression_trigger_ratio": 0.75,
"summary_trigger_messages": 20,
"summary_trigger_ratio": 0.75
} }
}, },
"providers": { "providers": {
@ -521,6 +524,30 @@ Agent 读取 HEARTBEAT.md
* `PICOCLAW_HEARTBEAT_ENABLED=false` 禁用 * `PICOCLAW_HEARTBEAT_ENABLED=false` 禁用
* `PICOCLAW_HEARTBEAT_INTERVAL=60` 更改间隔 * `PICOCLAW_HEARTBEAT_INTERVAL=60` 更改间隔
### 对话阈值配置 (Threshold Configuration)
PicoClaw 支持自定义对话总结和压缩触发阈值,避免过早截断上下文。
| 配置项 | 默认值 | 描述 |
| --- | --- | --- |
| `max_tokens` | `8192` | 上下文窗口大小和 LLM 输出 token 上限 |
| `compression_trigger_ratio` | `0.75` | 当上下文超过 `max_tokens × compression_trigger_ratio` 时触发强制压缩forceCompression |
| `summary_trigger_messages` | `20` | 当历史消息数超过此值时触发自动总结(设为 0 可禁用按条数触发) |
| `summary_trigger_ratio` | `0.75` | 当历史消息 token 估算超过 `max_tokens × summary_trigger_ratio` 时触发自动总结 |
**环境变量:**
* `PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS=16384` 提升上下文窗口
* `PICOCLAW_AGENTS_DEFAULTS_COMPRESSION_TRIGGER_RATIO=0.9` 提高压缩触发阈值(例如从 75% 提升到 90%
* `PICOCLAW_AGENTS_DEFAULTS_SUMMARY_TRIGGER_MESSAGES=50` 延迟按条数触发(例如从 20 条提升到 50 条)
* `PICOCLAW_AGENTS_DEFAULTS_SUMMARY_TRIGGER_RATIO=0.85` 提高总结触发阈值(例如从 75% 提升到 85%
**使用建议:**
- **延长对话**: 提高所有阈值,让对话保持更长时间
- **快速响应**: 降低阈值,更快触发总结和压缩
- **仅按 token 控制**: 设置 `summary_trigger_messages=0`,仅使用 `summary_trigger_ratio` 控制总结触发
### 提供商 (Providers) ### 提供商 (Providers)
> [!NOTE] > [!NOTE]

View file

@ -6,7 +6,10 @@
"model": "glm-4.7", "model": "glm-4.7",
"max_tokens": 8192, "max_tokens": 8192,
"temperature": 0.7, "temperature": 0.7,
"max_tool_iterations": 20 "max_tool_iterations": 20,
"compression_trigger_ratio": 0.75,
"summary_trigger_messages": 20,
"summary_trigger_ratio": 0.75
} }
}, },
"channels": { "channels": {

View file

@ -31,19 +31,23 @@ import (
) )
type AgentLoop struct { type AgentLoop struct {
bus *bus.MessageBus bus *bus.MessageBus
provider providers.LLMProvider provider providers.LLMProvider
workspace string workspace string
model string model string
contextWindow int // Maximum context window size in tokens contextWindow int // Maximum context window size in tokens
maxIterations int maxIterations int
sessions *session.SessionManager sessions *session.SessionManager
state *state.Manager state *state.Manager
contextBuilder *ContextBuilder contextBuilder *ContextBuilder
tools *tools.ToolRegistry tools *tools.ToolRegistry
running atomic.Bool running atomic.Bool
summarizing sync.Map // Tracks which sessions are currently being summarized summarizing sync.Map // Tracks which sessions are currently being summarized
channelManager *channels.Manager channelManager *channels.Manager
compressionTriggerRatio float64
summaryTriggerMessages int
summaryTriggerRatio float64
temperature float64
} }
// processOptions configures how a message is processed // processOptions configures how a message is processed
@ -114,7 +118,14 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
toolsRegistry := createToolRegistry(workspace, restrict, cfg, msgBus) toolsRegistry := createToolRegistry(workspace, restrict, cfg, msgBus)
// Create subagent manager with its own tool registry // Create subagent manager with its own tool registry
subagentManager := tools.NewSubagentManager(provider, cfg.Agents.Defaults.Model, workspace, msgBus) subagentManager := tools.NewSubagentManager(
provider,
cfg.Agents.Defaults.Model,
cfg.Agents.Defaults.MaxTokens,
cfg.Agents.Defaults.Temperature,
workspace,
msgBus,
)
subagentTools := createToolRegistry(workspace, restrict, cfg, msgBus) subagentTools := createToolRegistry(workspace, restrict, cfg, msgBus)
// Subagent doesn't need spawn/subagent tools to avoid recursion // Subagent doesn't need spawn/subagent tools to avoid recursion
subagentManager.SetTools(subagentTools) subagentManager.SetTools(subagentTools)
@ -137,17 +148,21 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
contextBuilder.SetToolsRegistry(toolsRegistry) contextBuilder.SetToolsRegistry(toolsRegistry)
return &AgentLoop{ return &AgentLoop{
bus: msgBus, bus: msgBus,
provider: provider, provider: provider,
workspace: workspace, workspace: workspace,
model: cfg.Agents.Defaults.Model, model: cfg.Agents.Defaults.Model,
contextWindow: cfg.Agents.Defaults.MaxTokens, // Restore context window for summarization contextWindow: cfg.Agents.Defaults.MaxTokens,
maxIterations: cfg.Agents.Defaults.MaxToolIterations, maxIterations: cfg.Agents.Defaults.MaxToolIterations,
sessions: sessionsManager, sessions: sessionsManager,
state: stateManager, state: stateManager,
contextBuilder: contextBuilder, contextBuilder: contextBuilder,
tools: toolsRegistry, tools: toolsRegistry,
summarizing: sync.Map{}, summarizing: sync.Map{},
compressionTriggerRatio: cfg.Agents.Defaults.CompressionTriggerRatio,
summaryTriggerMessages: cfg.Agents.Defaults.SummaryTriggerMessages,
summaryTriggerRatio: cfg.Agents.Defaults.SummaryTriggerRatio,
temperature: cfg.Agents.Defaults.Temperature,
} }
} }
@ -443,8 +458,8 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M
"model": al.model, "model": al.model,
"messages_count": len(messages), "messages_count": len(messages),
"tools_count": len(providerToolDefs), "tools_count": len(providerToolDefs),
"max_tokens": 8192, "max_tokens": al.contextWindow,
"temperature": 0.7, "temperature": al.temperature,
"system_prompt_len": len(messages[0].Content), "system_prompt_len": len(messages[0].Content),
}) })
@ -463,8 +478,8 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M
maxRetries := 2 maxRetries := 2
for retry := 0; retry <= maxRetries; retry++ { for retry := 0; retry <= maxRetries; retry++ {
response, err = al.provider.Chat(ctx, messages, providerToolDefs, al.model, map[string]interface{}{ response, err = al.provider.Chat(ctx, messages, providerToolDefs, al.model, map[string]interface{}{
"max_tokens": 8192, "max_tokens": al.contextWindow,
"temperature": 0.7, "temperature": al.temperature,
}) })
if err == nil { if err == nil {
@ -723,9 +738,9 @@ func (al *AgentLoop) updateToolContexts(channel, chatID string) {
func (al *AgentLoop) maybeSummarize(sessionKey, channel, chatID string) { func (al *AgentLoop) maybeSummarize(sessionKey, channel, chatID string) {
newHistory := al.sessions.GetHistory(sessionKey) newHistory := al.sessions.GetHistory(sessionKey)
tokenEstimate := al.estimateTokens(newHistory) tokenEstimate := al.estimateTokens(newHistory)
threshold := al.contextWindow * 75 / 100 threshold := int(float64(al.contextWindow) * al.summaryTriggerRatio)
if len(newHistory) > 20 || tokenEstimate > threshold { if len(newHistory) > al.summaryTriggerMessages || tokenEstimate > threshold {
if _, loading := al.summarizing.LoadOrStore(sessionKey, true); !loading { if _, loading := al.summarizing.LoadOrStore(sessionKey, true); !loading {
go func() { go func() {
defer al.summarizing.Delete(sessionKey) defer al.summarizing.Delete(sessionKey)
@ -887,8 +902,8 @@ func (al *AgentLoop) summarizeSession(sessionKey string) {
toSummarize := history[:len(history)-4] toSummarize := history[:len(history)-4]
// Oversized Message Guard // Oversized Message Guard
// Skip messages larger than 50% of context window to prevent summarizer overflow // Skip messages larger than compression_trigger_ratio of context window to prevent summarizer overflow
maxMessageTokens := al.contextWindow / 2 maxMessageTokens := int(float64(al.contextWindow) * al.compressionTriggerRatio)
validMessages := make([]providers.Message, 0) validMessages := make([]providers.Message, 0)
omitted := false omitted := false

View file

@ -59,13 +59,16 @@ type AgentsConfig struct {
} }
type AgentDefaults struct { type AgentDefaults struct {
Workspace string `json:"workspace" env:"PICOCLAW_AGENTS_DEFAULTS_WORKSPACE"` Workspace string `json:"workspace" env:"PICOCLAW_AGENTS_DEFAULTS_WORKSPACE"`
RestrictToWorkspace bool `json:"restrict_to_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"` RestrictToWorkspace bool `json:"restrict_to_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"`
Provider string `json:"provider" env:"PICOCLAW_AGENTS_DEFAULTS_PROVIDER"` Provider string `json:"provider" env:"PICOCLAW_AGENTS_DEFAULTS_PROVIDER"`
Model string `json:"model" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"` Model string `json:"model" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"`
MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"` MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"`
Temperature float64 `json:"temperature" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"` Temperature float64 `json:"temperature" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"`
MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"` MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
CompressionTriggerRatio float64 `json:"compression_trigger_ratio" env:"PICOCLAW_AGENTS_DEFAULTS_COMPRESSION_TRIGGER_RATIO"`
SummaryTriggerMessages int `json:"summary_trigger_messages" env:"PICOCLAW_AGENTS_DEFAULTS_SUMMARY_TRIGGER_MESSAGES"`
SummaryTriggerRatio float64 `json:"summary_trigger_ratio" env:"PICOCLAW_AGENTS_DEFAULTS_SUMMARY_TRIGGER_RATIO"`
} }
type ChannelsConfig struct { type ChannelsConfig struct {
@ -219,13 +222,16 @@ func DefaultConfig() *Config {
return &Config{ return &Config{
Agents: AgentsConfig{ Agents: AgentsConfig{
Defaults: AgentDefaults{ Defaults: AgentDefaults{
Workspace: "~/.picoclaw/workspace", Workspace: "~/.picoclaw/workspace",
RestrictToWorkspace: true, RestrictToWorkspace: true,
Provider: "", Provider: "",
Model: "glm-4.7", Model: "glm-4.7",
MaxTokens: 8192, MaxTokens: 8192,
Temperature: 0.7, Temperature: 0.7,
MaxToolIterations: 20, MaxToolIterations: 20,
CompressionTriggerRatio: 0.75,
SummaryTriggerMessages: 20,
SummaryTriggerRatio: 0.75,
}, },
}, },
Channels: ChannelsConfig{ Channels: ChannelsConfig{

View file

@ -26,6 +26,8 @@ type SubagentManager struct {
mu sync.RWMutex mu sync.RWMutex
provider providers.LLMProvider provider providers.LLMProvider
defaultModel string defaultModel string
maxTokens int
temperature float64
bus *bus.MessageBus bus *bus.MessageBus
workspace string workspace string
tools *ToolRegistry tools *ToolRegistry
@ -33,11 +35,13 @@ type SubagentManager struct {
nextID int nextID int
} }
func NewSubagentManager(provider providers.LLMProvider, defaultModel, workspace string, bus *bus.MessageBus) *SubagentManager { func NewSubagentManager(provider providers.LLMProvider, defaultModel string, maxTokens int, temperature float64, workspace string, bus *bus.MessageBus) *SubagentManager {
return &SubagentManager{ return &SubagentManager{
tasks: make(map[string]*SubagentTask), tasks: make(map[string]*SubagentTask),
provider: provider, provider: provider,
defaultModel: defaultModel, defaultModel: defaultModel,
maxTokens: maxTokens,
temperature: temperature,
bus: bus, bus: bus,
workspace: workspace, workspace: workspace,
tools: NewToolRegistry(), tools: NewToolRegistry(),
@ -123,17 +127,17 @@ After completing the task, provide a clear summary of what was done.`
sm.mu.RLock() sm.mu.RLock()
tools := sm.tools tools := sm.tools
maxIter := sm.maxIterations maxIter := sm.maxIterations
maxTokens := sm.maxTokens
temperature := sm.temperature
sm.mu.RUnlock() sm.mu.RUnlock()
loopResult, err := RunToolLoop(ctx, ToolLoopConfig{ loopResult, err := RunToolLoop(ctx, ToolLoopConfig{
Provider: sm.provider, Provider: sm.provider,
Model: sm.defaultModel, Model: sm.defaultModel,
MaxTokens: maxTokens,
Temperature: temperature,
Tools: tools, Tools: tools,
MaxIterations: maxIter, MaxIterations: maxIter,
LLMOptions: map[string]any{
"max_tokens": 4096,
"temperature": 0.7,
},
}, messages, task.OriginChannel, task.OriginChatID) }, messages, task.OriginChannel, task.OriginChatID)
sm.mu.Lock() sm.mu.Lock()

View file

@ -39,7 +39,7 @@ func (m *MockLLMProvider) GetContextWindow() int {
// TestSubagentTool_Name verifies tool name // TestSubagentTool_Name verifies tool name
func TestSubagentTool_Name(t *testing.T) { func TestSubagentTool_Name(t *testing.T) {
provider := &MockLLMProvider{} provider := &MockLLMProvider{}
manager := NewSubagentManager(provider, "test-model", "/tmp/test", nil) manager := NewSubagentManager(provider, "test-model", 4096, 0.7, "/tmp/test", nil)
tool := NewSubagentTool(manager) tool := NewSubagentTool(manager)
if tool.Name() != "subagent" { if tool.Name() != "subagent" {
@ -50,7 +50,7 @@ func TestSubagentTool_Name(t *testing.T) {
// TestSubagentTool_Description verifies tool description // TestSubagentTool_Description verifies tool description
func TestSubagentTool_Description(t *testing.T) { func TestSubagentTool_Description(t *testing.T) {
provider := &MockLLMProvider{} provider := &MockLLMProvider{}
manager := NewSubagentManager(provider, "test-model", "/tmp/test", nil) manager := NewSubagentManager(provider, "test-model", 4096, 0.7, "/tmp/test", nil)
tool := NewSubagentTool(manager) tool := NewSubagentTool(manager)
desc := tool.Description() desc := tool.Description()
@ -65,7 +65,7 @@ func TestSubagentTool_Description(t *testing.T) {
// TestSubagentTool_Parameters verifies tool parameters schema // TestSubagentTool_Parameters verifies tool parameters schema
func TestSubagentTool_Parameters(t *testing.T) { func TestSubagentTool_Parameters(t *testing.T) {
provider := &MockLLMProvider{} provider := &MockLLMProvider{}
manager := NewSubagentManager(provider, "test-model", "/tmp/test", nil) manager := NewSubagentManager(provider, "test-model", 4096, 0.7, "/tmp/test", nil)
tool := NewSubagentTool(manager) tool := NewSubagentTool(manager)
params := tool.Parameters() params := tool.Parameters()
@ -115,7 +115,7 @@ func TestSubagentTool_Parameters(t *testing.T) {
// TestSubagentTool_SetContext verifies context setting // TestSubagentTool_SetContext verifies context setting
func TestSubagentTool_SetContext(t *testing.T) { func TestSubagentTool_SetContext(t *testing.T) {
provider := &MockLLMProvider{} provider := &MockLLMProvider{}
manager := NewSubagentManager(provider, "test-model", "/tmp/test", nil) manager := NewSubagentManager(provider, "test-model", 4096, 0.7, "/tmp/test", nil)
tool := NewSubagentTool(manager) tool := NewSubagentTool(manager)
tool.SetContext("test-channel", "test-chat") tool.SetContext("test-channel", "test-chat")
@ -129,7 +129,7 @@ func TestSubagentTool_SetContext(t *testing.T) {
func TestSubagentTool_Execute_Success(t *testing.T) { func TestSubagentTool_Execute_Success(t *testing.T) {
provider := &MockLLMProvider{} provider := &MockLLMProvider{}
msgBus := bus.NewMessageBus() msgBus := bus.NewMessageBus()
manager := NewSubagentManager(provider, "test-model", "/tmp/test", msgBus) manager := NewSubagentManager(provider, "test-model", 4096, 0.7, "/tmp/test", msgBus)
tool := NewSubagentTool(manager) tool := NewSubagentTool(manager)
tool.SetContext("telegram", "chat-123") tool.SetContext("telegram", "chat-123")
@ -185,7 +185,7 @@ func TestSubagentTool_Execute_Success(t *testing.T) {
func TestSubagentTool_Execute_NoLabel(t *testing.T) { func TestSubagentTool_Execute_NoLabel(t *testing.T) {
provider := &MockLLMProvider{} provider := &MockLLMProvider{}
msgBus := bus.NewMessageBus() msgBus := bus.NewMessageBus()
manager := NewSubagentManager(provider, "test-model", "/tmp/test", msgBus) manager := NewSubagentManager(provider, "test-model", 4096, 0.7, "/tmp/test", msgBus)
tool := NewSubagentTool(manager) tool := NewSubagentTool(manager)
ctx := context.Background() ctx := context.Background()
@ -208,7 +208,7 @@ func TestSubagentTool_Execute_NoLabel(t *testing.T) {
// TestSubagentTool_Execute_MissingTask tests error handling for missing task // TestSubagentTool_Execute_MissingTask tests error handling for missing task
func TestSubagentTool_Execute_MissingTask(t *testing.T) { func TestSubagentTool_Execute_MissingTask(t *testing.T) {
provider := &MockLLMProvider{} provider := &MockLLMProvider{}
manager := NewSubagentManager(provider, "test-model", "/tmp/test", nil) manager := NewSubagentManager(provider, "test-model", 4096, 0.7, "/tmp/test", nil)
tool := NewSubagentTool(manager) tool := NewSubagentTool(manager)
ctx := context.Background() ctx := context.Background()
@ -259,7 +259,7 @@ func TestSubagentTool_Execute_NilManager(t *testing.T) {
func TestSubagentTool_Execute_ContextPassing(t *testing.T) { func TestSubagentTool_Execute_ContextPassing(t *testing.T) {
provider := &MockLLMProvider{} provider := &MockLLMProvider{}
msgBus := bus.NewMessageBus() msgBus := bus.NewMessageBus()
manager := NewSubagentManager(provider, "test-model", "/tmp/test", msgBus) manager := NewSubagentManager(provider, "test-model", 4096, 0.7, "/tmp/test", msgBus)
tool := NewSubagentTool(manager) tool := NewSubagentTool(manager)
// Set context // Set context
@ -288,7 +288,7 @@ func TestSubagentTool_ForUserTruncation(t *testing.T) {
// Create a mock provider that returns very long content // Create a mock provider that returns very long content
provider := &MockLLMProvider{} provider := &MockLLMProvider{}
msgBus := bus.NewMessageBus() msgBus := bus.NewMessageBus()
manager := NewSubagentManager(provider, "test-model", "/tmp/test", msgBus) manager := NewSubagentManager(provider, "test-model", 4096, 0.7, "/tmp/test", msgBus)
tool := NewSubagentTool(manager) tool := NewSubagentTool(manager)
ctx := context.Background() ctx := context.Background()

View file

@ -20,6 +20,8 @@ import (
type ToolLoopConfig struct { type ToolLoopConfig struct {
Provider providers.LLMProvider Provider providers.LLMProvider
Model string Model string
MaxTokens int
Temperature float64
Tools *ToolRegistry Tools *ToolRegistry
MaxIterations int MaxIterations int
LLMOptions map[string]any LLMOptions map[string]any
@ -55,10 +57,17 @@ func RunToolLoop(ctx context.Context, config ToolLoopConfig, messages []provider
// 2. Set default LLM options // 2. Set default LLM options
llmOpts := config.LLMOptions llmOpts := config.LLMOptions
if llmOpts == nil { if llmOpts == nil {
llmOpts = map[string]any{ llmOpts = map[string]any{}
"max_tokens": 4096, }
"temperature": 0.7,
} // Set max_tokens if not specified in LLMOptions
if _, ok := llmOpts["max_tokens"]; !ok && config.MaxTokens > 0 {
llmOpts["max_tokens"] = config.MaxTokens
}
// Set temperature if not specified in LLMOptions
if _, ok := llmOpts["temperature"]; !ok && config.Temperature > 0 {
llmOpts["temperature"] = config.Temperature
} }
// 3. Call LLM // 3. Call LLM