Class: LittleGhost::ToolRegistry

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/little_ghost/tool_registry.rb

Overview

ToolRegistry turns an agent's tool declarations into the exact set a model can call during one run. It validates names, binds run collaborators, and closes owned tool instances with the run.

binding = LittleGhost::Tool::Binding.new
registry = LittleGhost::ToolRegistry.new([HelpCenterLookupTool], binding:)
registry.names # => ["policy_lookup"]

Entries may be Tool instances, Tool subclasses, nested arrays, or provider classes that implement tools(binding). Names must be unique, contain only letters, numbers, underscores, or hyphens, and be at most 64 characters. Owned tools that implement close are closed once in reverse order.

Constant Summary collapse

MAX_NAME_LENGTH =

:nodoc:

64
NAME_PATTERN =

:nodoc:

/\A[a-zA-Z0-9_-]+\z/

Instance Method Summary collapse

Constructor Details

#initialize(tools = [], binding: Tool::Binding.new) ⇒ ToolRegistry

Binds newly instantiated tools to binding.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/little_ghost/tool_registry.rb', line 23

def initialize(tools = [], binding: Tool::Binding.new)
  @tools = {}
  @binding = binding
  @closed = false
  @closed_tool_ids = {}
  supplied_instances = Array(tools).flatten.grep(Tool).uniq(&:object_id)
  add(tools)
rescue => error
  begin
    close
  rescue
    nil
  end
  begin
    close_instances(supplied_instances)
  rescue
    nil
  end
  raise error
end

Instance Method Details

#closeObject

Closes every owned tool that responds to close.



80
81
82
83
84
85
# File 'lib/little_ghost/tool_registry.rb', line 80

def close
  return if @closed

  @closed = true
  close_instances(@tools.each_value.to_a)
end

#each(&block) ⇒ Object

Yields each registered tool instance.



93
94
95
# File 'lib/little_ghost/tool_registry.rb', line 93

def each(&block)
  @tools.each_value(&block)
end

#fetch(name) ⇒ Object

Finds the named tool or raises ToolError when it is unavailable.



88
89
90
# File 'lib/little_ghost/tool_registry.rb', line 88

def fetch(name)
  @tools.fetch(name.to_s) { raise ToolError, "Unknown tool: #{name}" }
end

#namesObject

Lists the frozen model-visible tool names.



103
104
105
# File 'lib/little_ghost/tool_registry.rb', line 103

def names
  @tools.keys.freeze
end

#register(tool, replace: false) ⇒ Object

Registers tool and returns self. When replace is true, replaced owned tools are closed after the new entries have been validated.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/little_ghost/tool_registry.rb', line 46

def register(tool, replace: false)
  raise Error, "Tool registry is closed" if @closed

  instances = []
  existing_ids = @tools.each_value.to_h { |instance| [instance.object_id, true] }
  resolve(tool, instances, binding: @binding)
  seen = []
  names = instances.map do |instance|
    raise ConfigurationError, "Tools must inherit from LittleGhost::Tool" unless instance.is_a?(Tool)

    name = instance.class.tool_name
    validate_name!(name)
    validate_description!(instance.class.description)
    raise ConfigurationError, "Tool name collision: #{name}" if @tools.key?(name) && !replace
    raise ConfigurationError, "Tool name collision: #{name}" if seen.include?(name)

    seen << name
    name
  end

  replaced = names.filter_map { |name| @tools[name] if replace }
  names.zip(instances).each { |name, instance| @tools[name] = instance }
  close_instances(replaced)
  self
rescue => error
  begin
    close_instances(instances.to_a.reject { |instance| existing_ids&.key?(instance.object_id) })
  rescue
    nil
  end
  raise error
end

#specificationsObject

Collects the frozen model-facing tool specifications.



98
99
100
# File 'lib/little_ghost/tool_registry.rb', line 98

def specifications
  map { |tool| tool.class.specification }.freeze
end