feat: Gemini architecture improvements (Singleflight, Tools Filtering)
This commit is contained in:
parent
b068f6ae44
commit
fa1524f247
2 changed files with 82 additions and 4 deletions
|
|
@ -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
75
pkg/agent/tools_filter.go
Normal 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
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue