This commit is contained in:
OpenClaw-User 2026-03-14 17:12:52 +08:00
commit fb6690e29a
4 changed files with 78 additions and 18 deletions

View file

@ -305,6 +305,7 @@
"tools": { "tools": {
"allow_read_paths": null, "allow_read_paths": null,
"allow_write_paths": null, "allow_write_paths": null,
"max_tools": 128,
"web": { "web": {
"enabled": true, "enabled": true,
"brave": { "brave": {

View file

@ -70,6 +70,9 @@ func NewAgentInstance(
allowWritePaths := compilePatterns(cfg.Tools.AllowWritePaths) allowWritePaths := compilePatterns(cfg.Tools.AllowWritePaths)
toolsRegistry := tools.NewToolRegistry() toolsRegistry := tools.NewToolRegistry()
if cfg.Tools.MaxTools > 0 {
toolsRegistry.SetMaxTools(cfg.Tools.MaxTools)
}
if cfg.Tools.IsToolEnabled("read_file") { if cfg.Tools.IsToolEnabled("read_file") {
maxReadFileSize := cfg.Tools.ReadFile.MaxReadFileSize maxReadFileSize := cfg.Tools.ReadFile.MaxReadFileSize

View file

@ -732,6 +732,7 @@ type ReadFileToolConfig struct {
type ToolsConfig struct { type ToolsConfig struct {
AllowReadPaths []string `json:"allow_read_paths" env:"PICOCLAW_TOOLS_ALLOW_READ_PATHS"` AllowReadPaths []string `json:"allow_read_paths" env:"PICOCLAW_TOOLS_ALLOW_READ_PATHS"`
AllowWritePaths []string `json:"allow_write_paths" env:"PICOCLAW_TOOLS_ALLOW_WRITE_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"` Web WebToolsConfig `json:"web"`
Cron CronToolsConfig `json:"cron"` Cron CronToolsConfig `json:"cron"`
Exec ExecConfig `json:"exec"` Exec ExecConfig `json:"exec"`

View file

@ -22,14 +22,33 @@ type ToolRegistry struct {
tools map[string]*ToolEntry tools map[string]*ToolEntry
mu sync.RWMutex mu sync.RWMutex
version atomic.Uint64 // incremented on Register/RegisterHidden for cache invalidation version atomic.Uint64 // incremented on Register/RegisterHidden for cache invalidation
maxTools int // 0 means use DefaultMaxTools
} }
const DefaultMaxTools = 128
func NewToolRegistry() *ToolRegistry { func NewToolRegistry() *ToolRegistry {
return &ToolRegistry{ return &ToolRegistry{
tools: make(map[string]*ToolEntry), 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) { func (r *ToolRegistry) Register(tool Tool) {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
@ -258,41 +277,77 @@ func (r *ToolRegistry) GetDefinitions() []map[string]any {
// ToProviderDefs converts tool definitions to provider-compatible format. // ToProviderDefs converts tool definitions to provider-compatible format.
// This is the format expected by LLM provider APIs. // 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 { func (r *ToolRegistry) ToProviderDefs() []providers.ToolDefinition {
r.mu.RLock() r.mu.RLock()
defer r.mu.RUnlock() defer r.mu.RUnlock()
sorted := r.sortedToolNames() sorted := r.sortedToolNames()
definitions := make([]providers.ToolDefinition, 0, len(sorted)) limit := r.getMaxTools()
for _, name := range sorted {
entry := r.tools[name]
if !entry.IsCore && entry.TTL <= 0 {
continue
}
toProviderDef := func(entry *ToolEntry) (providers.ToolDefinition, bool) {
schema := ToolToSchema(entry.Tool) schema := ToolToSchema(entry.Tool)
// Safely extract nested values with type checks
fn, ok := schema["function"].(map[string]any) fn, ok := schema["function"].(map[string]any)
if !ok { if !ok {
continue return providers.ToolDefinition{}, false
} }
name, _ := fn["name"].(string) name, _ := fn["name"].(string)
desc, _ := fn["description"].(string) desc, _ := fn["description"].(string)
params, _ := fn["parameters"].(map[string]any) params, _ := fn["parameters"].(map[string]any)
return providers.ToolDefinition{
definitions = append(definitions, providers.ToolDefinition{
Type: "function", Type: "function",
Function: providers.ToolFunctionDefinition{ Function: providers.ToolFunctionDefinition{
Name: name, Name: name,
Description: desc, Description: desc,
Parameters: params, 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. // List returns a list of all registered tool names.