Merge PR #1341
This commit is contained in:
commit
fb6690e29a
4 changed files with 78 additions and 18 deletions
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"`
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue