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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -2
- data/README.md +7 -6
- data/examples/quick_demo.rb +1 -1
- data/examples/tools_example.rb +2 -2
- data/exe/llm-chain +1 -1
- data/lib/llm_chain/builders/memory_context.rb +24 -0
- data/lib/llm_chain/builders/prompt.rb +26 -0
- data/lib/llm_chain/builders/rag_documents.rb +25 -0
- data/lib/llm_chain/builders/retriever_context.rb +25 -0
- data/lib/llm_chain/builders/tool_responses.rb +27 -0
- data/lib/llm_chain/chain.rb +68 -81
- data/lib/llm_chain/interfaces/builders/memory_context_builder.rb +20 -0
- data/lib/llm_chain/interfaces/builders/prompt_builder.rb +23 -0
- data/lib/llm_chain/interfaces/builders/rag_documents_builder.rb +20 -0
- data/lib/llm_chain/interfaces/builders/retriever_context_builder.rb +22 -0
- data/lib/llm_chain/interfaces/builders/tool_responses_builder.rb +20 -0
- data/lib/llm_chain/interfaces/memory.rb +38 -0
- data/lib/llm_chain/interfaces/tool_manager.rb +87 -0
- data/lib/llm_chain/memory/array.rb +18 -1
- data/lib/llm_chain/memory/redis.rb +20 -3
- data/lib/llm_chain/system_diagnostics.rb +73 -0
- data/lib/llm_chain/tools/calculator.rb +117 -44
- data/lib/llm_chain/tools/tool_manager.rb +32 -87
- data/lib/llm_chain/tools/tool_manager_factory.rb +44 -0
- data/lib/llm_chain/tools/web_search.rb +167 -335
- data/lib/llm_chain/version.rb +1 -1
- data/lib/llm_chain.rb +55 -55
- metadata +16 -2
@@ -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
|
-
|
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
|
-
# @
|
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
|
-
#
|
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
|
-
#
|
38
|
-
|
39
|
-
|
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
|
-
|
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
|