Files
ragflow/internal/harness/core/tool_registry.go
Yingfeng 706e0d2d06 Refactor harness framework (#16271)
### What problem does this PR solve?

- Tools management
- Pregel engine wrapper for better usage
- UT race
- Coding style

### Type of change

- [x] Refactoring
2026-06-23 20:18:04 +08:00

203 lines
5.1 KiB
Go

package core
import (
"fmt"
"sync"
"ragflow/internal/harness/core/schema"
)
// ToolRegistry provides centralized tool management with aliases, categories,
// and filtering. It replaces raw []Tool slices for more flexible tool discovery.
type ToolRegistry struct {
mu sync.RWMutex
tools map[string]Tool // name -> tool
aliases map[string]string // alias -> canonical name
category map[string][]string // category -> tool names
}
// NewToolRegistry creates an empty ToolRegistry.
func NewToolRegistry() *ToolRegistry {
return &ToolRegistry{
tools: make(map[string]Tool),
aliases: make(map[string]string),
category: make(map[string][]string),
}
}
// Register adds a tool and optionally aliases and categories.
func (r *ToolRegistry) Register(tool Tool, opts ...RegistryOption) {
r.mu.Lock()
defer r.mu.Unlock()
name := tool.Name()
r.tools[name] = tool
for _, opt := range opts {
opt(name, r)
}
}
// RegistryOption configures a tool registration.
type RegistryOption func(name string, r *ToolRegistry)
// WithAlias registers an alias for the tool.
func WithAlias(alias string) RegistryOption {
return func(name string, r *ToolRegistry) {
r.aliases[alias] = name
}
}
// WithCategory assigns the tool to one or more categories.
func WithCategory(categories ...string) RegistryOption {
return func(name string, r *ToolRegistry) {
for _, cat := range categories {
r.category[cat] = append(r.category[cat], name)
}
}
}
// Lookup finds a tool by name or alias.
func (r *ToolRegistry) Lookup(name string) (Tool, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
if t, ok := r.tools[name]; ok {
return t, true
}
if canonical, ok := r.aliases[name]; ok {
t, ok := r.tools[canonical]
return t, ok
}
return nil, false
}
// LookupByCategory returns all tools in a category.
func (r *ToolRegistry) LookupByCategory(category string) []Tool {
r.mu.RLock()
defer r.mu.RUnlock()
names := r.category[category]
result := make([]Tool, 0, len(names))
for _, n := range names {
if t, ok := r.tools[n]; ok {
result = append(result, t)
}
}
return result
}
// AllTools returns all registered tools as a slice.
func (r *ToolRegistry) AllTools() []Tool {
r.mu.RLock()
defer r.mu.RUnlock()
result := make([]Tool, 0, len(r.tools))
for _, t := range r.tools {
result = append(result, t)
}
return result
}
// ToSlice converts the registry to a []Tool for use with existing APIs.
func (r *ToolRegistry) ToSlice() []Tool {
return r.AllTools()
}
// Merge merges another registry into this one. Conflicts are resolved by source winning.
// Uses a snapshot-then-apply pattern to avoid deadlock: other's data is read under
// RLock before locking r. Self-merge (r.Merge(r)) is handled as a no-op.
func (r *ToolRegistry) Merge(other *ToolRegistry) {
if r == other {
return
}
// Snapshot other's data under read lock.
other.mu.RLock()
tools := make(map[string]Tool, len(other.tools))
for k, v := range other.tools {
tools[k] = v
}
aliases := make(map[string]string, len(other.aliases))
for k, v := range other.aliases {
aliases[k] = v
}
categories := make(map[string][]string, len(other.category))
for k, v := range other.category {
categories[k] = append([]string{}, v...)
}
other.mu.RUnlock()
// Apply snapshot under our write lock.
r.mu.Lock()
defer r.mu.Unlock()
for name, tool := range tools {
r.tools[name] = tool
}
for alias, canonical := range aliases {
r.aliases[alias] = canonical
}
for cat, names := range categories {
r.category[cat] = append(r.category[cat], names...)
}
}
// Filter returns a new registry containing only tools matching the predicate.
func (r *ToolRegistry) Filter(fn func(Tool) bool) *ToolRegistry {
r.mu.RLock()
defer r.mu.RUnlock()
result := NewToolRegistry()
for _, t := range r.tools {
if fn(t) {
result.Register(t)
}
}
return result
}
// MustLookup looks up a tool by name and panics if not found (for use in init()).
func (r *ToolRegistry) MustLookup(name string) Tool {
t, ok := r.Lookup(name)
if !ok {
panic(fmt.Sprintf("tool '%s' not found in registry", name))
}
return t
}
// Unregister removes a tool by name.
func (r *ToolRegistry) Unregister(name string) {
r.mu.Lock()
defer r.mu.Unlock()
delete(r.tools, name)
for alias, canonical := range r.aliases {
if canonical == name {
delete(r.aliases, alias)
}
}
for cat, names := range r.category {
filtered := names[:0]
for _, n := range names {
if n != name {
filtered = append(filtered, n)
}
}
if len(filtered) == 0 {
delete(r.category, cat)
} else {
r.category[cat] = filtered
}
}
}
// ToolInfos returns metadata for all registered tools. When a tool implements
// ToolInfoProvider, its full structured info is used; otherwise a minimal
// Name+Description info is created.
func (r *ToolRegistry) ToolInfos() []*schema.ToolInfo {
r.mu.RLock()
defer r.mu.RUnlock()
infos := make([]*schema.ToolInfo, 0, len(r.tools))
for _, t := range r.tools {
if p, ok := t.(ToolInfoProvider); ok {
infos = append(infos, p.ToolInfo())
} else {
infos = append(infos, &schema.ToolInfo{Name: t.Name(), Description: t.Description()})
}
}
return infos
}