mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-11 06:05:45 +08:00
### What problem does this PR solve? - Tools management - Pregel engine wrapper for better usage - UT race - Coding style ### Type of change - [x] Refactoring
203 lines
5.1 KiB
Go
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
|
|
}
|