llm_chain 0.5.4 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interfaces/tool_manager'
4
+
1
5
  module LLMChain
2
6
  module Tools
3
- class ToolManager
7
+ # ToolManager manages registration, selection, and execution of tools in LLMChain.
8
+ # Implements the LLMChain::Interfaces::ToolManager interface.
9
+ class ToolManager < Interfaces::ToolManager
4
10
  attr_reader :tools
5
11
 
6
12
  def initialize(tools: [])
@@ -9,9 +15,8 @@ module LLMChain
9
15
  end
10
16
 
11
17
  # Register a new tool instance.
12
- #
13
18
  # @param tool [LLMChain::Tools::Base]
14
- # @raise [ArgumentError] if object does not inherit from Tools::Base
19
+ # @return [void]
15
20
  def register_tool(tool)
16
21
  unless tool.is_a?(Base)
17
22
  raise ArgumentError, "Tool must inherit from LLMChain::Tools::Base"
@@ -20,38 +25,38 @@ module LLMChain
20
25
  end
21
26
 
22
27
  # Unregister a tool by name.
28
+ # @param name [String]
29
+ # @return [void]
23
30
  def unregister_tool(name)
24
31
  @tools.delete(name.to_s)
25
32
  end
26
33
 
27
34
  # Fetch a tool by its name.
35
+ # @param name [String]
36
+ # @return [LLMChain::Tools::Base, nil]
28
37
  def get_tool(name)
29
38
  @tools[name.to_s]
30
39
  end
31
40
 
32
- # @return [Array<LLMChain::Tools::Base>] list of registered tools
41
+ # List all registered tools.
42
+ # @return [Array<LLMChain::Tools::Base>]
33
43
  def list_tools
34
44
  @tools.values
35
45
  end
36
46
 
37
- # Build JSON schemas for all registered tools.
38
- def get_tools_schema
39
- @tools.values.map(&:to_schema)
40
- end
41
-
42
- # Find tools whose {Tools::Base#match?} returns `true` for the prompt.
47
+ # Find tools whose #match? returns true for the prompt.
48
+ # @param prompt [String]
49
+ # @return [Array<LLMChain::Tools::Base>]
43
50
  def find_matching_tools(prompt)
44
51
  @tools.values.select { |tool| tool.match?(prompt) }
45
52
  end
46
53
 
47
54
  # Execute every matching tool and collect results.
48
- #
49
55
  # @param prompt [String]
50
56
  # @param context [Hash]
51
57
  # @return [Hash] mapping tool name → result hash
52
58
  def execute_tools(prompt, context: {})
53
59
  matching_tools = find_matching_tools(prompt)
54
-
55
60
  results = {}
56
61
  matching_tools.each do |tool|
57
62
  begin
@@ -69,108 +74,45 @@ module LLMChain
69
74
  }
70
75
  end
71
76
  end
72
-
73
77
  results
74
78
  end
75
79
 
76
- # Execute a single tool by name.
77
- #
78
- # @param name [String]
79
- # @param prompt [String]
80
- # @param context [Hash]
81
- # @return [Hash] result wrapper
82
- def execute_tool(name, prompt, context: {})
83
- tool = get_tool(name)
84
- raise ArgumentError, "Tool '#{name}' not found" unless tool
85
-
86
- begin
87
- result = tool.call(prompt, context: context)
88
- {
89
- success: true,
90
- result: result,
91
- formatted: tool.format_result(result)
92
- }
93
- rescue => e
94
- {
95
- success: false,
96
- error: e.message,
97
- formatted: "Error in #{name}: #{e.message}"
98
- }
99
- end
100
- end
101
-
102
- # Create default toolset (Calculator, WebSearch, CodeInterpreter, DateTime).
103
- def self.create_default_toolset
104
- tools = [
105
- Calculator.new,
106
- WebSearch.new,
107
- CodeInterpreter.new,
108
- DateTime.new
109
- ]
110
-
111
- new(tools: tools)
112
- end
113
-
114
- # Build toolset from a config array.
115
- def self.from_config(config)
116
- tools = []
117
-
118
- config.each do |tool_config|
119
- tool_class = tool_config[:class] || tool_config['class']
120
- tool_options = tool_config[:options] || tool_config['options'] || {}
121
-
122
- case tool_class.to_s.downcase
123
- when 'calculator'
124
- tools << Calculator.new
125
- when 'web_search', 'websearch'
126
- tools << WebSearch.new(**tool_options)
127
- when 'code_interpreter', 'codeinterpreter'
128
- tools << CodeInterpreter.new(**tool_options)
129
- else
130
- raise ArgumentError, "Unknown tool class: #{tool_class}"
131
- end
132
- end
133
-
134
- new(tools: tools)
135
- end
136
-
137
80
  # Format tool execution results for inclusion into an LLM prompt.
81
+ # @param results [Hash]
82
+ # @return [String]
138
83
  def format_tool_results(results)
139
84
  return "" if results.empty?
140
-
141
85
  formatted_results = results.map do |tool_name, result|
142
86
  "#{tool_name}: #{result[:formatted]}"
143
87
  end
144
-
145
88
  "Tool Results:\n#{formatted_results.join("\n\n")}"
146
89
  end
147
90
 
148
91
  # Human-readable list of available tools.
92
+ # @return [String]
149
93
  def tools_description
150
94
  descriptions = @tools.values.map do |tool|
151
95
  "- #{tool.name}: #{tool.description}"
152
96
  end
153
-
154
97
  "Available tools:\n#{descriptions.join("\n")}"
155
98
  end
156
99
 
157
100
  # Determine if prompt likely needs tool usage.
101
+ # @param prompt [String]
102
+ # @return [Boolean]
158
103
  def needs_tools?(prompt)
159
- # Check for explicit tool usage requests
160
104
  return true if prompt.match?(/\b(use tool|call tool|execute|calculate|search|run code)\b/i)
161
-
162
- # Check if there are any matching tools
163
105
  find_matching_tools(prompt).any?
164
106
  end
165
107
 
166
108
  # Auto-select and execute best tools for prompt.
109
+ # @param prompt [String]
110
+ # @param context [Hash]
111
+ # @return [Hash]
167
112
  def auto_execute(prompt, context: {})
168
113
  return {} unless needs_tools?(prompt)
169
-
170
- # Limit the number of tools executed at once
171
114
  matching_tools = find_matching_tools(prompt)
172
115
  selected_tools = select_best_tools(matching_tools, prompt)
173
-
174
116
  results = {}
175
117
  selected_tools.each do |tool|
176
118
  begin
@@ -188,15 +130,19 @@ module LLMChain
188
130
  }
189
131
  end
190
132
  end
191
-
192
133
  results
193
134
  end
194
135
 
136
+ # Build JSON schemas for all registered tools.
137
+ # @return [Array<Hash>]
138
+ def get_tools_schema
139
+ @tools.values.map(&:to_schema)
140
+ end
141
+
195
142
  private
196
143
 
197
144
  # Simple heuristic to rank matching tools.
198
- def select_best_tools(tools, prompt, limit: 3)
199
- # Simple prioritization logic
145
+ def select_best_tools(tools, prompt, limit: 3)
200
146
  prioritized = tools.sort_by do |tool|
201
147
  case tool.name
202
148
  when 'calculator'
@@ -209,7 +155,6 @@ module LLMChain
209
155
  1
210
156
  end
211
157
  end
212
-
213
158
  prioritized.first(limit)
214
159
  end
215
160
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LLMChain
4
+ module Tools
5
+ # Factory for creating ToolManager instances with default or custom toolsets.
6
+ module ToolManagerFactory
7
+ # Create a ToolManager with the default set of tools.
8
+ # @return [ToolManager]
9
+ def self.create_default_toolset
10
+ tools = [
11
+ Calculator.new,
12
+ WebSearch.new,
13
+ CodeInterpreter.new,
14
+ DateTime.new
15
+ ]
16
+ ToolManager.new(tools: tools)
17
+ end
18
+
19
+ # Create a ToolManager from a config array.
20
+ # @param config [Array<Hash>] tool config hashes
21
+ # @return [ToolManager]
22
+ def self.from_config(config)
23
+ tools = []
24
+ config.each do |tool_config|
25
+ tool_class = tool_config[:class] || tool_config['class']
26
+ tool_options = tool_config[:options] || tool_config['options'] || {}
27
+ case tool_class.to_s.downcase
28
+ when 'calculator'
29
+ tools << Calculator.new
30
+ when 'web_search', 'websearch'
31
+ tools << WebSearch.new(**tool_options)
32
+ when 'code_interpreter', 'codeinterpreter'
33
+ tools << CodeInterpreter.new(**tool_options)
34
+ when 'date_time', 'datetime'
35
+ tools << DateTime.new
36
+ else
37
+ raise ArgumentError, "Unknown tool class: #{tool_class}"
38
+ end
39
+ end
40
+ ToolManager.new(tools: tools)
41
+ end
42
+ end
43
+ end
44
+ end