feat: Gemini architecture improvements (Singleflight, Tools Filtering)

This commit is contained in:
Bernardo 2026-03-10 19:30:18 +01:00
parent b068f6ae44
commit fa1524f247
2 changed files with 82 additions and 4 deletions

View file

@ -19,6 +19,8 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
"golang.org/x/sync/singleflight"
"github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels" "github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/commands" "github.com/sipeed/picoclaw/pkg/commands"
@ -49,6 +51,7 @@ type AgentLoop struct {
transcriber voice.Transcriber transcriber voice.Transcriber
cmdRegistry *commands.Registry cmdRegistry *commands.Registry
version string version string
sf singleflight.Group
wg sync.WaitGroup wg sync.WaitGroup
} }
@ -980,7 +983,7 @@ func (al *AgentLoop) runLLMIteration(
}) })
// Build tool definitions // Build tool definitions
providerToolDefs := agent.Tools.ToProviderDefs() providerToolDefs := al.selectRelevantTools(agent, opts.UserMessage)
// Log LLM request details // Log LLM request details
logger.DebugCF("agent", "LLM request", logger.DebugCF("agent", "LLM request",
@ -1410,15 +1413,15 @@ func (al *AgentLoop) maybeSummarize(agent *AgentInstance, sessionKey, channel, c
if len(newHistory) > agent.SummarizeMessageThreshold || tokenEstimate > threshold { if len(newHistory) > agent.SummarizeMessageThreshold || tokenEstimate > threshold {
summarizeKey := agent.ID + ":" + sessionKey summarizeKey := agent.ID + ":" + sessionKey
if _, loading := al.summarizing.LoadOrStore(summarizeKey, true); !loading { al.sf.Do(summarizeKey, func() (interface{}, error) {
al.wg.Add(1) al.wg.Add(1)
go func() { go func() {
defer al.wg.Done() defer al.wg.Done()
defer al.summarizing.Delete(summarizeKey)
logger.Debug("Memory threshold reached. Optimizing conversation history...") logger.Debug("Memory threshold reached. Optimizing conversation history...")
al.summarizeSession(agent, sessionKey) al.summarizeSession(agent, sessionKey)
}() }()
} return nil, nil
})
} }
} }

75
pkg/agent/tools_filter.go Normal file
View file

@ -0,0 +1,75 @@
package agent
import (
"strings"
"github.com/sipeed/picoclaw/pkg/providers"
)
// selectRelevantTools filters available tools based on user intent to reduce context.
func (al *AgentLoop) selectRelevantTools(agent *AgentInstance, userMsg string) []providers.ToolDefinition {
allTools := agent.Tools.ToProviderDefs()
if len(allTools) <= 5 {
return allTools
}
lowerMsg := strings.ToLower(userMsg)
// Essential tools are always included.
essentialTools := map[string]bool{
"read_file": true,
"list_dir": true,
"google": true, // Web search is often needed for verification
}
// Keyword mappings for contextual tools.
toolKeywords := map[string][]string{
"write_file": {"write", "save", "create", "file", "code"},
"edit_file": {"edit", "modify", "change", "file", "code", "replace", "fix"},
"append_file": {"append", "add", "log", "file"},
"exec": {"run", "execute", "shell", "command", "terminal", "install", "build", "cat", "ls", "git"},
"vps": {"vps", "server", "remote", "ssh", "cloud"},
"voice_call": {"call", "voice", "phone", "speak", "tell", "say"},
"spawn": {"spawn", "acp", "harness", "agent", "protocol"},
}
relevant := make([]providers.ToolDefinition, 0)
for _, tool := range allTools {
name := tool.Function.Name
// 1. Always include essential tools.
if essentialTools[name] {
relevant = append(relevant, tool)
continue
}
// 2. Include based on keywords.
if kws, ok := toolKeywords[name]; ok {
matched := false
for _, kw := range kws {
if strings.Contains(lowerMsg, kw) {
matched = true
break
}
}
if matched {
relevant = append(relevant, tool)
continue
}
}
// 3. Fallback: if tool has no keywords defined, include it by default?
// To be safe and avoid context bloat, we only include tools with defined keywords if they match.
// If a tool is NOT in our map, we'll include it for now to avoid breaking unknown tools.
if _, ok := toolKeywords[name]; !ok {
relevant = append(relevant, tool)
}
}
// Safety: ensure we don't return an empty tool set if any were available.
if len(relevant) == 0 && len(allTools) > 0 {
return allTools[:min(len(allTools), 5)]
}
return relevant
}