ruby_llm 0.1.0.pre8 → 0.1.0.pre10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38dc88557f16216a881c8cd0be702de77e8d0d332ab927f8c4fbfb804063a193
4
- data.tar.gz: b5cf788ab7d948081e6fdf7649197dcecaa3d596b5afa992fe1b2288cb7bb935
3
+ metadata.gz: 7d37a2d7434c6fba64700425cb86e29a6d04108f72e89e2a51273581adacb2a8
4
+ data.tar.gz: 45021416cdf96bf2ffdac2917a689515d519014d51054c6a4fe02bc60343685d
5
5
  SHA512:
6
- metadata.gz: 7058ba5bad5baf2fabab9c01a8dd076d2b5b408d6538c87b6d8940464fd575f7da1c3c95294794d8bbc5f5875abc8321f0dba8d3085ad449d3720a82ca4c9d30
7
- data.tar.gz: 1fe4831f21f43aa924cb86b4871d6318d3d98d910988b21339a327a2ddc818094e251586c37d48981d9ef8bdbd31ca91fd6a6cd55fbc7e47b2ad7d774abdc8a6
6
+ metadata.gz: 7ba9769ef1b6763e983540a02f42139bd4063bbdfb03b5999e33d78db82a46cb09b58778406b575f7b4d66105a42db8973c0a96939706ad80697aca01623a7dc
7
+ data.tar.gz: 0027621e9fbb2e2261eb4d2d8ef44672813cf9e0a5b523d8b850ca2ea1c52614671dbf914ca55bbe3cd5d029b163b4ac7308221f63afa4dae20230ce9ee88f4c
data/README.md CHANGED
@@ -39,7 +39,7 @@ chat.ask "What's the best way to learn Ruby?"
39
39
 
40
40
  ## Available Models
41
41
 
42
- RubyLLM gives you access to the latest models from multiple providers. Check what's available:
42
+ RubyLLM gives you access to the latest models from multiple providers:
43
43
 
44
44
  ```ruby
45
45
  # List all available models
@@ -79,49 +79,55 @@ puts "Conversation used #{last_message.input_tokens} input tokens and #{last_mes
79
79
 
80
80
  ## Using Tools
81
81
 
82
- Give your AI superpowers by letting it use Ruby tools. This opens up a world of possibilities - from performing calculations to fetching data:
82
+ Give Claude some Ruby superpowers by letting it call your code. Simply create a tool class:
83
83
 
84
84
  ```ruby
85
- # Define a calculator tool
86
- calculator = RubyLLM::Tool.define "calculate" do
87
- description "Performs basic arithmetic calculations"
88
- param :expression, type: "string"
89
- handler do |args|
85
+ class CalculatorTool < RubyLLM::Tool
86
+ description "Performs arithmetic calculations"
87
+
88
+ param :expression,
89
+ type: :string,
90
+ required: true,
91
+ desc: "A mathematical expression to evaluate (e.g. '2 + 2')"
92
+
93
+ private
94
+
95
+ def execute(args)
90
96
  eval(args[:expression]).to_s
91
- rescue => e
92
- { error: "Invalid expression: #{e.message}" }
93
97
  end
94
98
  end
99
+ ```
95
100
 
96
- # Use the tool in a conversation
97
- chat = RubyLLM.chat.with_tool calculator
101
+ Then use it in your conversations:
98
102
 
99
- # The model will automatically use the tool when needed
103
+ ```ruby
104
+ chat = RubyLLM.chat.with_tool(CalculatorTool)
105
+
106
+ # Claude will automatically use the calculator when appropriate
100
107
  chat.ask "What's 2+2?"
101
- # => "The result of 2 + 2 is 4."
108
+ # => "Let me calculate that for you. The result is 4."
102
109
 
103
- chat.ask "and what's 2+100000000000?"
104
- # => "The result of 2 + 100,000,000,000 is 100,000,000,002."
110
+ chat.ask "If I have 3 apples and multiply them by 5, how many do I have?"
111
+ # => "Let me help you calculate that. 3 × 5 = 15, so you would have 15 apples."
105
112
 
106
113
  # Add multiple tools
107
- chat.with_tools calculator, other_tool, another_tool
114
+ chat.with_tools(CalculatorTool, WeatherTool, DatabaseTool)
108
115
  ```
109
116
 
110
- Tools let you seamlessly integrate Ruby code with AI capabilities. Define tools for anything - database queries, API calls, custom business logic - and let the AI use them naturally in conversation.
117
+ Tools let you seamlessly integrate your Ruby code with AI capabilities. The model will automatically decide when to use your tools and handle the results appropriately.
111
118
 
112
- ## Choosing the Right Model
113
-
114
- RubyLLM gives you easy access to model capabilities:
119
+ Need to debug a tool? RubyLLM automatically logs all tool calls and their results when debug logging is enabled:
115
120
 
116
121
  ```ruby
117
- model = RubyLLM.models.find 'claude-3-5-sonnet-20241022'
122
+ ENV['RUBY_LLM_DEBUG'] = 'true'
118
123
 
119
- model.context_window # => 200000
120
- model.max_tokens # => 8192
121
- model.supports_vision # => true
122
- model.supports_json_mode # => true
124
+ chat.ask "What's 123 * 456?"
125
+ # D, -- RubyLLM: Tool calculator called with: {"expression" => "123 * 456"}
126
+ # D, -- RubyLLM: Tool calculator returned: "56088"
123
127
  ```
124
128
 
129
+ Create tools for anything - database queries, API calls, custom business logic - and let Claude use them naturally in conversation.
130
+
125
131
  ## Coming Soon
126
132
 
127
133
  - Rails integration for seamless database and Active Record support
data/lib/ruby_llm/chat.rb CHANGED
@@ -26,7 +26,8 @@ module RubyLLM
26
26
  def with_tool(tool)
27
27
  raise Error, "Model #{@model.id} doesn't support function calling" unless @model.supports_functions
28
28
 
29
- @tools[tool.name] = tool
29
+ tool_instance = tool.is_a?(Class) ? tool.to_tool : tool
30
+ @tools[tool_instance.name.to_sym] = tool_instance
30
31
  self
31
32
  end
32
33
 
@@ -45,14 +46,14 @@ module RubyLLM
45
46
  response = @provider.complete(messages, tools: @tools, model: @model.id, &block)
46
47
 
47
48
  if response.tool_call?
48
- handle_tool_calls response
49
+ handle_tool_calls response, &block
49
50
  else
50
51
  add_message response
51
52
  response
52
53
  end
53
54
  end
54
55
 
55
- def handle_tool_calls(response)
56
+ def handle_tool_calls(response, &block)
56
57
  add_message response
57
58
 
58
59
  response.tool_calls.each_value do |tool_call|
@@ -60,11 +61,11 @@ module RubyLLM
60
61
  add_tool_result tool_call.id, result if result
61
62
  end
62
63
 
63
- complete
64
+ complete(&block)
64
65
  end
65
66
 
66
67
  def execute_tool(tool_call)
67
- tool = tools[tool_call.name]
68
+ tool = tools[tool_call.name.to_sym]
68
69
  args = tool_call.arguments
69
70
  tool.call(args)
70
71
  end
@@ -85,8 +86,8 @@ module RubyLLM
85
86
 
86
87
  def ensure_valid_tools
87
88
  tools.each_key do |name|
88
- unless name.is_a?(String) && tools[name].is_a?(RubyLLM::Tool)
89
- raise Error, 'Tools should be of the format {<name>: <RubyLLM::Tool>}'
89
+ unless name.is_a?(Symbol) && tools[name].is_a?(RubyLLM::Tool)
90
+ raise Error, 'Tools should be of the format {<name.to_sym>: <RubyLLM::Tool>}'
90
91
  end
91
92
  end
92
93
  end
data/lib/ruby_llm/tool.rb CHANGED
@@ -1,100 +1,90 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # lib/ruby_llm/tool.rb
3
4
  module RubyLLM
4
5
  class Tool
5
- class Parameter
6
- attr_reader :name, :type, :description, :required
7
-
8
- def initialize(name, type: 'string', description: nil, required: true)
9
- @name = name
10
- @type = type
11
- @description = description
12
- @required = required
6
+ class << self
7
+ def description(text = nil)
8
+ return @description unless text
9
+
10
+ @description = text
13
11
  end
14
12
 
15
- def to_h
16
- {
17
- type: type,
18
- description: description,
13
+ def param(name, type:, desc: nil, required: true)
14
+ param = Parameter.new(
15
+ name,
16
+ type: type.to_s,
17
+ description: desc,
19
18
  required: required
20
- }.compact
19
+ )
20
+ parameters[name] = param
21
21
  end
22
- end
23
22
 
24
- class Builder
25
- def initialize(tool)
26
- @tool = tool
23
+ def parameters
24
+ @parameters ||= {}
27
25
  end
28
26
 
29
- def description(text)
30
- @tool.instance_variable_set(:@description, text)
31
- self
27
+ def name
28
+ super
29
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
30
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
31
+ .downcase
32
+ .delete_suffix('_tool')
32
33
  end
33
34
 
34
- def param(name, type: 'string', description: nil, required: true)
35
- @tool.parameters[name] = Parameter.new(name, type: type, description: description, required: required)
36
- self
37
- end
35
+ def to_tool
36
+ tool_instance = new
38
37
 
39
- def handler(&block)
40
- @tool.instance_variable_set(:@handler, block)
41
- @tool
42
- end
43
- end
38
+ def tool_instance.name
39
+ self.class.name
40
+ end
44
41
 
45
- attr_reader :name, :description, :parameters, :handler
42
+ def tool_instance.description
43
+ self.class.description
44
+ end
46
45
 
47
- def self.define(name, &block)
48
- tool = new(name)
49
- builder = Builder.new(tool)
50
- builder.instance_eval(&block)
51
- tool
52
- end
46
+ def tool_instance.parameters
47
+ self.class.parameters
48
+ end
53
49
 
54
- def initialize(name)
55
- @name = name
56
- @parameters = {}
50
+ tool_instance
51
+ end
57
52
  end
58
53
 
59
54
  def call(args)
60
- raise Error, "No handler defined for tool #{name}" unless @handler
61
-
62
- begin
63
- RubyLLM.logger.debug "Calling tool #{name}(#{args.inspect})"
64
- args = symbolize_keys(args)
65
- result = @handler.call(args)
66
- RubyLLM.logger.debug "Tool #{name}(#{args.inspect}) returned: #{result.inspect}"
67
- result
68
- rescue StandardError => e
69
- RubyLLM.logger.error "Tool #{name}(#{args.inspect}) failed with error #{e.message}"
70
- { error: e.message }
71
- end
55
+ RubyLLM.logger.debug "Tool #{name} called with: #{args.inspect}"
56
+ result = execute(args.transform_keys(&:to_sym))
57
+ RubyLLM.logger.debug "Tool #{name} returned: #{result.inspect}"
58
+ result
59
+ rescue StandardError => e
60
+ RubyLLM.logger.error "Tool #{name} failed with error: #{e.message}"
61
+ { error: e.message }
72
62
  end
73
63
 
74
- class << self
75
- def from_method(method, description: nil)
76
- define(method.name.to_s) do
77
- description description if description
78
-
79
- method.parameters.each do |type, name|
80
- param name, required: (type == :req)
81
- end
64
+ private
82
65
 
83
- handler do |args|
84
- method.owner.new.public_send(method.name, **args)
85
- end
86
- end
87
- end
66
+ def execute(args)
67
+ raise NotImplementedError, 'Subclasses must implement #execute'
88
68
  end
69
+ end
89
70
 
90
- private
71
+ # Using the existing Parameter class from Tool.rb
72
+ class Parameter
73
+ attr_reader :name, :type, :description, :required
91
74
 
92
- def symbolize_keys(hash)
93
- hash.transform_keys do |key|
94
- key.to_sym
95
- rescue StandardError
96
- key
97
- end
75
+ def initialize(name, type: 'string', description: nil, required: true)
76
+ @name = name
77
+ @type = type
78
+ @description = description
79
+ @required = required
80
+ end
81
+
82
+ def to_h
83
+ {
84
+ type: type,
85
+ description: description,
86
+ required: required
87
+ }.compact
98
88
  end
99
89
  end
100
90
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyLLM
4
- VERSION = '0.1.0.pre8'
4
+ VERSION = '0.1.0.pre10'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre8
4
+ version: 0.1.0.pre10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino