Merge PR #1341
This commit is contained in:
commit
fb6690e29a
4 changed files with 78 additions and 18 deletions
|
|
@ -305,6 +305,7 @@
|
|||
"tools": {
|
||||
"allow_read_paths": null,
|
||||
"allow_write_paths": null,
|
||||
"max_tools": 128,
|
||||
"web": {
|
||||
"enabled": true,
|
||||
"brave": {
|
||||
|
|
|
|||
|
|
@ -70,6 +70,9 @@ func NewAgentInstance(
|
|||
allowWritePaths := compilePatterns(cfg.Tools.AllowWritePaths)
|
||||
|
||||
toolsRegistry := tools.NewToolRegistry()
|
||||
if cfg.Tools.MaxTools > 0 {
|
||||
toolsRegistry.SetMaxTools(cfg.Tools.MaxTools)
|
||||
}
|
||||
|
||||
if cfg.Tools.IsToolEnabled("read_file") {
|
||||
maxReadFileSize := cfg.Tools.ReadFile.MaxReadFileSize
|
||||
|
|
|
|||
|
|
@ -732,6 +732,7 @@ type ReadFileToolConfig struct {
|
|||
type ToolsConfig struct {
|
||||
AllowReadPaths []string `json:"allow_read_paths" env:"PICOCLAW_TOOLS_ALLOW_READ_PATHS"`
|
||||
AllowWritePaths []string `json:"allow_write_paths" env:"PICOCLAW_TOOLS_ALLOW_WRITE_PATHS"`
|
||||
MaxTools int `json:"max_tools" env:"PICOCLAW_TOOLS_MAX_TOOLS"`
|
||||
Web WebToolsConfig `json:"web"`
|
||||
Cron CronToolsConfig `json:"cron"`
|
||||
Exec ExecConfig `json:"exec"`
|
||||
|
|
|
|||
|
|
@ -19,17 +19,36 @@ type ToolEntry struct {
|
|||
}
|
||||
|
||||
type ToolRegistry struct {
|
||||
tools map[string]*ToolEntry
|
||||
mu sync.RWMutex
|
||||
version atomic.Uint64 // incremented on Register/RegisterHidden for cache invalidation
|
||||
tools map[string]*ToolEntry
|
||||
mu sync.RWMutex
|
||||
version atomic.Uint64 // incremented on Register/RegisterHidden for cache invalidation
|
||||
maxTools int // 0 means use DefaultMaxTools
|
||||
}
|
||||
|
||||
const DefaultMaxTools = 128
|
||||
|
||||
func NewToolRegistry() *ToolRegistry {
|
||||
return &ToolRegistry{
|
||||
tools: make(map[string]*ToolEntry),
|
||||
}
|
||||
}
|
||||
|
||||
// SetMaxTools configures the maximum number of tool definitions returned by
|
||||
// ToProviderDefs. This prevents exceeding LLM API tool array limits.
|
||||
// A value of 0 or negative means use DefaultMaxTools.
|
||||
func (r *ToolRegistry) SetMaxTools(n int) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.maxTools = n
|
||||
}
|
||||
|
||||
func (r *ToolRegistry) getMaxTools() int {
|
||||
if r.maxTools > 0 {
|
||||
return r.maxTools
|
||||
}
|
||||
return DefaultMaxTools
|
||||
}
|
||||
|
||||
func (r *ToolRegistry) Register(tool Tool) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
|
@ -258,41 +277,77 @@ func (r *ToolRegistry) GetDefinitions() []map[string]any {
|
|||
|
||||
// ToProviderDefs converts tool definitions to provider-compatible format.
|
||||
// This is the format expected by LLM provider APIs.
|
||||
// The result is capped at maxTools (default 128) to respect LLM API limits.
|
||||
// Built-in tools are prioritized over MCP tools when truncation is needed.
|
||||
func (r *ToolRegistry) ToProviderDefs() []providers.ToolDefinition {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
sorted := r.sortedToolNames()
|
||||
definitions := make([]providers.ToolDefinition, 0, len(sorted))
|
||||
for _, name := range sorted {
|
||||
entry := r.tools[name]
|
||||
|
||||
if !entry.IsCore && entry.TTL <= 0 {
|
||||
continue
|
||||
}
|
||||
limit := r.getMaxTools()
|
||||
|
||||
toProviderDef := func(entry *ToolEntry) (providers.ToolDefinition, bool) {
|
||||
schema := ToolToSchema(entry.Tool)
|
||||
|
||||
// Safely extract nested values with type checks
|
||||
fn, ok := schema["function"].(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
return providers.ToolDefinition{}, false
|
||||
}
|
||||
|
||||
name, _ := fn["name"].(string)
|
||||
desc, _ := fn["description"].(string)
|
||||
params, _ := fn["parameters"].(map[string]any)
|
||||
|
||||
definitions = append(definitions, providers.ToolDefinition{
|
||||
return providers.ToolDefinition{
|
||||
Type: "function",
|
||||
Function: providers.ToolFunctionDefinition{
|
||||
Name: name,
|
||||
Description: desc,
|
||||
Parameters: params,
|
||||
},
|
||||
})
|
||||
}, true
|
||||
}
|
||||
return definitions
|
||||
|
||||
// Two-pass approach: built-in tools first, then MCP tools.
|
||||
var builtinDefs, mcpDefs []providers.ToolDefinition
|
||||
for _, name := range sorted {
|
||||
entry := r.tools[name]
|
||||
if !entry.IsCore && entry.TTL <= 0 {
|
||||
continue
|
||||
}
|
||||
def, ok := toProviderDef(entry)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if _, isMCP := entry.Tool.(*MCPTool); isMCP {
|
||||
mcpDefs = append(mcpDefs, def)
|
||||
} else {
|
||||
builtinDefs = append(builtinDefs, def)
|
||||
}
|
||||
}
|
||||
|
||||
total := len(builtinDefs) + len(mcpDefs)
|
||||
if total <= limit {
|
||||
return append(builtinDefs, mcpDefs...)
|
||||
}
|
||||
|
||||
// Truncation needed: keep all built-in tools, trim MCP tools.
|
||||
remaining := limit - len(builtinDefs)
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
dropped := len(mcpDefs) - remaining
|
||||
if remaining < len(mcpDefs) {
|
||||
mcpDefs = mcpDefs[:remaining]
|
||||
}
|
||||
|
||||
logger.WarnCF("tools", "Tool count exceeds limit, MCP tools truncated",
|
||||
map[string]any{
|
||||
"total": total,
|
||||
"limit": limit,
|
||||
"builtin": len(builtinDefs),
|
||||
"mcp_kept": len(mcpDefs),
|
||||
"dropped": dropped,
|
||||
})
|
||||
|
||||
return append(builtinDefs, mcpDefs...)
|
||||
}
|
||||
|
||||
// List returns a list of all registered tool names.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue