tool_tailor 0.1.3 → 0.2.0

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: 33d87f65353ea72f0715a86b7759529f68f6678eb6a24d47fac0547bade1b59e
4
- data.tar.gz: 874c949f555e93aa445147d89981e1207e11fdc4520977a668e9724354ca7d95
3
+ metadata.gz: 840fcc0f9e8a7f5ccd4db0d8f4a8d88f2f344225d8f759442dc11f625d243300
4
+ data.tar.gz: 53289290ee809ea4e18acf08de8115d4c259b337823fd9ad506de6c335eb12db
5
5
  SHA512:
6
- metadata.gz: 4500dc6778e3bb5373a4630160b22a2ad7f0669ddb097d221eb93b3f1c27a93b48310ef4fcc434ec2c154ea004beb84d157bd1550d4ada0eb1e8fe7b8daa83da
7
- data.tar.gz: 036f48ffa4b31354b24c85392cf52520215afcb02afac201289e19e650f4491be5fa0122aca606fc6d421d833ee2b8a64533b53a5936ea90608d2197caecd2b2
6
+ metadata.gz: 682eea6fb802c1a1444cafbe077577594cd682c7e02071313b56c7e737c738575b82098cbc6a4a63287edf449a0cd0aed9c4dcb225386350e4b2653270dac585
7
+ data.tar.gz: 8606a2abd5309a6ab9f7d7fdfa1910c953d674708aa9dce5c2e9fee29168f979632b3f4b8567dd2c5aba68d6fdfc18a2aebc000f464d63f20cc005c7c0452925
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tool_tailor (0.1.3)
4
+ tool_tailor (0.2.0)
5
5
  yard (~> 0.9.36)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ToolTailor
2
2
 
3
- ToolTailor is a Ruby gem that converts methods to OpenAI JSON schemas for use with tools, making it easier to integrate with OpenAI's API.
3
+ ToolTailor is a Ruby gem that converts methods and classes to OpenAI JSON schemas for use with tools, making it easier to integrate with OpenAI's API.
4
4
 
5
5
  ## Installation
6
6
 
@@ -20,8 +20,12 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- ```rb
24
- class TestClass
23
+ ToolTailor can convert both methods and classes to JSON schemas:
24
+
25
+ ### Converting Methods
26
+
27
+ ```ruby
28
+ class WeatherService
25
29
  # Get the current weather in a given location.
26
30
  #
27
31
  # @param location [String] The city and state, e.g., San Francisco, CA.
@@ -31,105 +35,100 @@ class TestClass
31
35
  end
32
36
  end
33
37
 
34
- # Simple
35
- ToolTailor.convert(TestClass.instance_method(:get_current_weather))
36
-
37
- # Unbound method with to_json_schema
38
- TestClass.instance_method(:get_current_weather).to_json_schema # => {
39
- # "type" => "function",
40
- # "function" => {
41
- # "name" => "get_current_weather",
42
- # "description" => "Get the current weather in a given location.",
43
- # "parameters" => {
44
- # "type" => "object",
45
- # "properties" => {
46
- # "location" => {
47
- # "type" => "string",
48
- # "description" => "The city and state, e.g., San Francisco, CA."
49
- # },
50
- # "unit" => {
51
- # "type" => "string",
52
- # "description" => "The unit of temperature, either 'celsius' or 'fahrenheit'."
53
- # },
54
- # "api_key" => {
55
- # "type" => "number",
56
- # "description" => "The API key for the weather service."
57
- # }
58
- # },
59
- # "required" => ["location", "unit", "api_key"]
60
- # }
61
- # }
62
-
63
- # Bound method with to_json_schema
64
- example_instance = TestClass.new
65
- example_instance.method(:get_current_weather).to_json_schema # => {
66
- # "type" => "function",
67
- # "function" => {
68
- # "name" => "get_current_weather",
69
- # "description" => "Get the current weather in a given location.",
70
- # "parameters" => {
71
- # "type" => "object",
72
- # "properties" => {
73
- # "location" => {
74
- # "type" => "string",
75
- # "description" => "The city and state, e.g., San Francisco, CA."
76
- # },
77
- # "unit" => {
78
- # "type" => "string",
79
- # "description" => "The unit of temperature, either 'celsius' or 'fahrenheit'."
80
- # },
81
- # "api_key" => {
82
- # "type" => "number",
83
- # "description" => "The API key for the weather service."
84
- # }
85
- # },
86
- # "required" => ["location", "unit", "api_key"]
87
- # }
88
- # }
89
- # }
38
+ # Convert an instance method
39
+ schema = ToolTailor.convert(WeatherService.instance_method(:get_current_weather))
40
+
41
+ # Using to_json_schema on an unbound method
42
+ schema = WeatherService.instance_method(:get_current_weather).to_json_schema
43
+
44
+ # Using to_json_schema on a bound method
45
+ weather_service = WeatherService.new
46
+ schema = weather_service.method(:get_current_weather).to_json_schema
47
+ ```
48
+
49
+ ### Converting Classes
50
+
51
+ When passing a class, ToolTailor assumes you want to use the `new` method and generates the schema based on the `initialize` method:
52
+
53
+ ```ruby
54
+ class User
55
+ # Create a new user
56
+ #
57
+ # @param name [String] The user's name
58
+ # @param age [Integer] The user's age
59
+ def initialize(name:, age:)
60
+ @name = name
61
+ @age = age
62
+ end
63
+ end
64
+
65
+ # Convert a class
66
+ schema = ToolTailor.convert(User)
67
+
68
+ # or
69
+ schema = User.to_json_schema
70
+
71
+ # This is equivalent to:
72
+ schema = ToolTailor.convert(User.instance_method(:initialize))
90
73
  ```
91
74
 
92
- And with [ruby-openai](https://github.com/alexrudall/ruby-openai):
93
-
94
- ```rb
95
- response =
96
- client.chat(
97
- parameters: {
98
- model: "gpt-4o",
99
- messages: [
100
- {
101
- "role": "user",
102
- "content": "What is the weather like in San Francisco?",
75
+ The resulting schema will look like this:
76
+
77
+ ```ruby
78
+ {
79
+ "type" => "function",
80
+ "function" => {
81
+ "name" => "User",
82
+ "description" => "Create a new user",
83
+ "parameters" => {
84
+ "type" => "object",
85
+ "properties" => {
86
+ "name" => {
87
+ "type" => "string",
88
+ "description" => "The user's name"
103
89
  },
104
- ],
105
- tools: [
106
- TestClass.instance_method(:get_current_weather).to_json_schema
107
- ],
108
- tool_choice: {
109
- type: "function",
110
- function: {
111
- name: "get_current_weather"
90
+ "age" => {
91
+ "type" => "integer",
92
+ "description" => "The user's age"
112
93
  }
113
- }
114
- },
115
- )
94
+ },
95
+ "required" => ["name", "age"]
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### Using with ruby-openai
102
+
103
+ Here's an example of how to use ToolTailor with the [ruby-openai](https://github.com/alexrudall/ruby-openai) gem:
104
+
105
+ ```ruby
106
+ response = client.chat(
107
+ parameters: {
108
+ model: "gpt-4",
109
+ messages: [
110
+ { role: "user", content: "Create a user named Alice who is 30 years old" }
111
+ ],
112
+ tools: [ToolTailor.convert(User)],
113
+ tool_choice: { type: "function", function: { name: "User" } }
114
+ }
115
+ )
116
116
 
117
117
  message = response.dig("choices", 0, "message")
118
118
 
119
119
  if message["role"] == "assistant" && message["tool_calls"]
120
120
  function_name = message.dig("tool_calls", 0, "function", "name")
121
- args =
122
- JSON.parse(
123
- message.dig("tool_calls", 0, "function", "arguments"),
124
- { symbolize_names: true },
125
- )
121
+ args = JSON.parse(
122
+ message.dig("tool_calls", 0, "function", "arguments"),
123
+ { symbolize_names: true }
124
+ )
126
125
 
127
126
  case function_name
128
- when "get_current_weather"
129
- TestClass.get_current_weather(**args)
127
+ when "User"
128
+ user = User.new(**args)
129
+ puts "Created user: #{user.name}, age #{user.age}"
130
130
  end
131
131
  end
132
- # => "The weather is nice 🌞"
133
132
  ```
134
133
 
135
134
  ## Development
@@ -140,10 +139,12 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
140
139
 
141
140
  ## Contributing
142
141
 
143
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/tool_tailor. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/tool_tailor/blob/master/CODE_OF_CONDUCT.md).
142
+ Bug reports and pull requests are welcome on GitHub at https://github.com/kieranklaassen/tool_tailor. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/kieranklaassen/tool_tailor/blob/master/CODE_OF_CONDUCT.md).
144
143
 
145
144
  ## License
146
145
 
147
146
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
148
147
 
149
148
  ## Code of Conduct
149
+
150
+ Everyone interacting in the ToolTailor project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/kieranklaassen/tool_tailor/blob/master/CODE_OF_CONDUCT.md).
@@ -1,3 +1,3 @@
1
1
  module ToolTailor
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/tool_tailor.rb CHANGED
@@ -5,36 +5,56 @@ require "yard"
5
5
  module ToolTailor
6
6
  class Error < StandardError; end
7
7
 
8
- # Converts a function to a JSON schema representation.
8
+ # Converts a function or class to a JSON schema representation.
9
9
  #
10
- # @param function [Method, UnboundMethod] The function to convert.
11
- # @return [String] The JSON schema representation of the function.
12
- # @raise [ArgumentError] If the provided object is not a Method or UnboundMethod.
10
+ # @param object [Method, UnboundMethod, Class] The function or class to convert.
11
+ # @return [String] The JSON schema representation of the function or class.
12
+ # @raise [ArgumentError] If the provided object is not a Method, UnboundMethod, or Class.
13
13
  #
14
14
  # @example
15
- # def example_method(param1, param2)
15
+ # def example_method(param1:, param2:)
16
16
  # # method implementation
17
17
  # end
18
18
  #
19
19
  # ToolTailor.convert(method(:example_method))
20
- def self.convert(function)
21
- unless function.is_a?(Method) || function.is_a?(UnboundMethod)
22
- raise ArgumentError, "Unsupported object type: #{function.class}"
20
+ #
21
+ # @example
22
+ # class ExampleClass
23
+ # def initialize(param1:, param2:)
24
+ # # initialization
25
+ # end
26
+ # end
27
+ #
28
+ # ToolTailor.convert(ExampleClass)
29
+ def self.convert(object)
30
+ case object
31
+ when Method, UnboundMethod
32
+ convert_method(object)
33
+ when Class
34
+ convert_class(object)
35
+ else
36
+ raise ArgumentError, "Unsupported object type: #{object.class}"
23
37
  end
38
+ end
24
39
 
40
+ # Converts a method to a JSON schema representation.
41
+ #
42
+ # @param method [Method, UnboundMethod] The method to convert.
43
+ # @return [String] The JSON schema representation of the method.
44
+ def self.convert_method(method)
25
45
  # Ensure only named arguments are allowed
26
- unless function.parameters.all? { |type, _| type == :keyreq || type == :key }
46
+ unless method.parameters.all? { |type, _| type == :keyreq || type == :key }
27
47
  raise ArgumentError, "Only named arguments are supported"
28
48
  end
29
49
 
30
- file_path, line_number = function.source_location
50
+ file_path, line_number = method.source_location
31
51
  YARD.parse(file_path)
32
52
 
33
- method_path = "#{function.owner}##{function.name}"
53
+ method_path = "#{method.owner}##{method.name}"
34
54
  yard_object = YARD::Registry.at(method_path)
35
55
 
36
- # Extract parameters from the function definition
37
- parameters = function.parameters.map do |_, name|
56
+ # Extract parameters from the method definition
57
+ parameters = method.parameters.map do |_, name|
38
58
  {
39
59
  name: name.to_s,
40
60
  type: "string",
@@ -42,10 +62,10 @@ module ToolTailor
42
62
  }
43
63
  end
44
64
 
45
- function_description = ""
65
+ method_description = ""
46
66
 
47
67
  if yard_object
48
- function_description = yard_object.docstring
68
+ method_description = yard_object.docstring
49
69
 
50
70
  yard_object.tags("param").each do |tag|
51
71
  param_name = tag.name.chomp(':')
@@ -60,8 +80,8 @@ module ToolTailor
60
80
  {
61
81
  type: "function",
62
82
  function: {
63
- name: function.name.to_s,
64
- description: function_description,
83
+ name: method.name.to_s,
84
+ description: method_description,
65
85
  parameters: {
66
86
  type: "object",
67
87
  properties: parameters.map do |param|
@@ -73,12 +93,29 @@ module ToolTailor
73
93
  }
74
94
  ]
75
95
  end.to_h,
76
- required: function.parameters.select { |type, _| type == :keyreq }.map { |_, name| name.to_s }
96
+ required: method.parameters.select { |type, _| type == :keyreq }.map { |_, name| name.to_s }
77
97
  }
78
98
  }
79
99
  }.to_json
80
100
  end
81
101
 
102
+ def self.convert_class(klass)
103
+ initialize_method = klass.instance_method(:initialize)
104
+ schema = JSON.parse(convert_method(initialize_method))
105
+ schema["function"]["name"] = klass.name
106
+
107
+ # Re-parse YARD documentation for the class
108
+ file_path, _ = initialize_method.source_location
109
+ YARD.parse(file_path)
110
+ class_object = YARD::Registry.at(klass.name)
111
+
112
+ if class_object
113
+ schema["function"]["description"] = class_object.docstring.to_s
114
+ end
115
+
116
+ schema.to_json
117
+ end
118
+
82
119
  # Maps Ruby types to JSON schema types.
83
120
  #
84
121
  # @param type [String] The Ruby type to map.
@@ -102,38 +139,6 @@ end
102
139
  class UnboundMethod
103
140
  # Converts an UnboundMethod to a JSON schema.
104
141
  #
105
- # @example
106
- # class ExampleClass
107
- # # @param name [String] The name of the person.
108
- # # @param age [Integer] The age of the person.
109
- # def greet(name, age)
110
- # puts "Hello, #{name}! You are #{age} years old."
111
- # end
112
- # end
113
- #
114
- # ExampleClass.instance_method(:greet).to_json_schema
115
- # # => {
116
- # # "type" => "function",
117
- # # "function" => {
118
- # # "name" => "greet",
119
- # # "description" => "",
120
- # # "parameters" => {
121
- # # "type" => "object",
122
- # # "properties" => {
123
- # # "name" => {
124
- # # "type" => "string",
125
- # # "description" => "The name of the person."
126
- # # },
127
- # # "age" => {
128
- # # "type" => "integer",
129
- # # "description" => "The age of the person."
130
- # # }
131
- # # },
132
- # # "required" => ["name", "age"]
133
- # # }
134
- # # }
135
- # # }
136
- #
137
142
  # @return [String] The JSON schema representation of the method.
138
143
  def to_json_schema
139
144
  ToolTailor.convert(self)
@@ -143,40 +148,17 @@ end
143
148
  class Method
144
149
  # Converts a Method to a JSON schema.
145
150
  #
146
- # @example
147
- # class ExampleClass
148
- # # @param name [String] The name of the person.
149
- # # @param age [Integer] The age of the person.
150
- # def greet(name, age)
151
- # puts "Hello, #{name}! You are #{age} years old."
152
- # end
153
- # end
154
- #
155
- # ExampleClass.new.method(:greet).to_json_schema
156
- # # => {
157
- # # "type" => "function",
158
- # # "function" => {
159
- # # "name" => "greet",
160
- # # "description" => "",
161
- # # "parameters" => {
162
- # # "type" => "object",
163
- # # "properties" => {
164
- # # "name" => {
165
- # # "type" => "string",
166
- # # "description" => "The name of the person."
167
- # # },
168
- # # "age" => {
169
- # # "type" => "integer",
170
- # # "description" => "The age of the person."
171
- # # }
172
- # # },
173
- # # "required" => ["name", "age"]
174
- # # }
175
- # # }
176
- # # }
177
- #
178
151
  # @return [String] The JSON schema representation of the method.
179
152
  def to_json_schema
180
153
  ToolTailor.convert(self)
181
154
  end
182
155
  end
156
+
157
+ class Class
158
+ # Converts a Class to a JSON schema.
159
+ #
160
+ # @return [String] The JSON schema representation of the class's initialize method.
161
+ def to_json_schema
162
+ ToolTailor.convert(self)
163
+ end
164
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tool_tailor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kieran Klaassen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-05-30 00:00:00.000000000 Z
11
+ date: 2024-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: yard