tool_tailor 0.1.1 → 0.1.3

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: 5b242798d32a1e165bcfee93e5562b97015ad554acdd6837e3ee729b3b27e6ed
4
- data.tar.gz: 3a9a097551149899b80e71b00dc936c6942dd213d66d97fb040485aeadd1408d
3
+ metadata.gz: 33d87f65353ea72f0715a86b7759529f68f6678eb6a24d47fac0547bade1b59e
4
+ data.tar.gz: 874c949f555e93aa445147d89981e1207e11fdc4520977a668e9724354ca7d95
5
5
  SHA512:
6
- metadata.gz: 342c3b07cb9ee1b01ead72205ce15b7b1cde92f10b3ba3d16cdfc18114d76c04b6c4bdd64a3a8c10fa0c1c3927248d4b3d7e8586edaca1c0d7e13ac1f49b018e
7
- data.tar.gz: 347ff4fe7ab0d29164d48e97a38400e4d8bb7816c81b791baab903ebdc764f8b4fcf97156dc256e56d66d6eddca92185c80740c488889342ffb9efaeabbd8aec
6
+ metadata.gz: 4500dc6778e3bb5373a4630160b22a2ad7f0669ddb097d221eb93b3f1c27a93b48310ef4fcc434ec2c154ea004beb84d157bd1550d4ada0eb1e8fe7b8daa83da
7
+ data.tar.gz: 036f48ffa4b31354b24c85392cf52520215afcb02afac201289e19e650f4491be5fa0122aca606fc6d421d833ee2b8a64533b53a5936ea90608d2197caecd2b2
data/Gemfile CHANGED
@@ -5,3 +5,5 @@ gemspec
5
5
 
6
6
  gem "rake", "~> 12.0"
7
7
  gem "rspec", "~> 3.0"
8
+
9
+ gem "super_diff", "~> 0.9.0", :group => :test
data/Gemfile.lock CHANGED
@@ -1,13 +1,17 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tool_tailor (0.1.1)
4
+ tool_tailor (0.1.3)
5
5
  yard (~> 0.9.36)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
+ attr_extras (7.1.0)
10
11
  diff-lcs (1.5.1)
12
+ optimist (3.1.0)
13
+ patience_diff (1.2.0)
14
+ optimist (~> 3.0)
11
15
  rake (12.3.3)
12
16
  rspec (3.13.0)
13
17
  rspec-core (~> 3.13.0)
@@ -22,6 +26,10 @@ GEM
22
26
  diff-lcs (>= 1.2.0, < 2.0)
23
27
  rspec-support (~> 3.13.0)
24
28
  rspec-support (3.13.1)
29
+ super_diff (0.9.0)
30
+ attr_extras (>= 6.2.4)
31
+ diff-lcs
32
+ patience_diff
25
33
  yard (0.9.36)
26
34
 
27
35
  PLATFORMS
@@ -30,6 +38,7 @@ PLATFORMS
30
38
  DEPENDENCIES
31
39
  rake (~> 12.0)
32
40
  rspec (~> 3.0)
41
+ super_diff (~> 0.9.0)
33
42
  tool_tailor!
34
43
 
35
44
  BUNDLED WITH
data/README.md CHANGED
@@ -26,12 +26,15 @@ class TestClass
26
26
  #
27
27
  # @param location [String] The city and state, e.g., San Francisco, CA.
28
28
  # @param unit [String] The unit of temperature, either 'celsius' or 'fahrenheit'.
29
- # @param api_key [Float] The API key for the weather service.
30
- def get_current_weather(location, unit = 'celsius', api_key: nil)
29
+ def get_current_weather(location:, unit: 'celsius')
31
30
  # Function implementation goes here
32
31
  end
33
32
  end
34
33
 
34
+ # Simple
35
+ ToolTailor.convert(TestClass.instance_method(:get_current_weather))
36
+
37
+ # Unbound method with to_json_schema
35
38
  TestClass.instance_method(:get_current_weather).to_json_schema # => {
36
39
  # "type" => "function",
37
40
  # "function" => {
@@ -56,6 +59,77 @@ TestClass.instance_method(:get_current_weather).to_json_schema # => {
56
59
  # "required" => ["location", "unit", "api_key"]
57
60
  # }
58
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
+ # }
90
+ ```
91
+
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?",
103
+ },
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"
112
+ }
113
+ }
114
+ },
115
+ )
116
+
117
+ message = response.dig("choices", 0, "message")
118
+
119
+ if message["role"] == "assistant" && message["tool_calls"]
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
+ )
126
+
127
+ case function_name
128
+ when "get_current_weather"
129
+ TestClass.get_current_weather(**args)
130
+ end
131
+ end
132
+ # => "The weather is nice 🌞"
59
133
  ```
60
134
 
61
135
  ## Development
@@ -1,3 +1,3 @@
1
1
  module ToolTailor
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.3"
3
3
  end
data/lib/tool_tailor.rb CHANGED
@@ -1,38 +1,62 @@
1
1
  require "tool_tailor/version"
2
- require 'json'
3
- require 'yard'
2
+ require "json"
3
+ require "yard"
4
4
 
5
5
  module ToolTailor
6
6
  class Error < StandardError; end
7
7
 
8
8
  # Converts a function to a JSON schema representation.
9
9
  #
10
- # @param function [Method] The function to convert.
10
+ # @param function [Method, UnboundMethod] The function to convert.
11
11
  # @return [String] The JSON schema representation of the function.
12
12
  # @raise [ArgumentError] If the provided object is not a Method or UnboundMethod.
13
+ #
14
+ # @example
15
+ # def example_method(param1, param2)
16
+ # # method implementation
17
+ # end
18
+ #
19
+ # ToolTailor.convert(method(:example_method))
13
20
  def self.convert(function)
14
21
  unless function.is_a?(Method) || function.is_a?(UnboundMethod)
15
22
  raise ArgumentError, "Unsupported object type: #{function.class}"
16
23
  end
17
24
 
25
+ # Ensure only named arguments are allowed
26
+ unless function.parameters.all? { |type, _| type == :keyreq || type == :key }
27
+ raise ArgumentError, "Only named arguments are supported"
28
+ end
29
+
18
30
  file_path, line_number = function.source_location
19
31
  YARD.parse(file_path)
20
32
 
21
- # Construct the correct identifier for the YARD object
22
33
  method_path = "#{function.owner}##{function.name}"
23
34
  yard_object = YARD::Registry.at(method_path)
24
- raise "Documentation for #{method_path} not found." if yard_object.nil?
25
-
26
- function_description = yard_object.docstring
27
35
 
28
- parameters = yard_object.tags('param').map do |tag|
36
+ # Extract parameters from the function definition
37
+ parameters = function.parameters.map do |_, name|
29
38
  {
30
- name: tag.name,
31
- type: type_mapping(tag.types.first),
32
- description: tag.text
39
+ name: name.to_s,
40
+ type: "string",
41
+ description: ""
33
42
  }
34
43
  end
35
44
 
45
+ function_description = ""
46
+
47
+ if yard_object
48
+ function_description = yard_object.docstring
49
+
50
+ yard_object.tags("param").each do |tag|
51
+ param_name = tag.name.chomp(':')
52
+ param = parameters.find { |p| p[:name] == param_name }
53
+ if param
54
+ param[:type] = type_mapping(tag.types.first)
55
+ param[:description] = tag.text
56
+ end
57
+ end
58
+ end
59
+
36
60
  {
37
61
  type: "function",
38
62
  function: {
@@ -49,7 +73,7 @@ module ToolTailor
49
73
  }
50
74
  ]
51
75
  end.to_h,
52
- required: parameters.map { |param| param[:name].to_s }
76
+ required: function.parameters.select { |type, _| type == :keyreq }.map { |_, name| name.to_s }
53
77
  }
54
78
  }
55
79
  }.to_json
@@ -57,33 +81,101 @@ module ToolTailor
57
81
 
58
82
  # Maps Ruby types to JSON schema types.
59
83
  #
60
- # @param type [Class] The Ruby type to map.
84
+ # @param type [String] The Ruby type to map.
61
85
  # @return [String] The corresponding JSON schema type.
62
86
  # @raise [ArgumentError] If the provided type is not supported.
63
87
  def self.type_mapping(type)
64
88
  case type
65
- when "String"
66
- 'string'
67
- when "Integer"
68
- 'integer'
69
- when "Float"
70
- 'number'
71
- when "TrueClass", "FalseClass"
72
- 'boolean'
73
- when "Array"
74
- 'array'
75
- when "Hash"
76
- 'object'
77
- when "NilClass"
78
- 'null'
89
+ when "String" then "string"
90
+ when "Integer" then "integer"
91
+ when "Float" then "number"
92
+ when "TrueClass", "FalseClass" then "boolean"
93
+ when "Array" then "array"
94
+ when "Hash" then "object"
95
+ when "NilClass" then "null"
79
96
  else
80
- # raise ArgumentError, "Unsupported type: #{type} #{type.class}"
81
- 'string'
97
+ raise ArgumentError, "Unsupported type: #{type} #{type.class}"
82
98
  end
83
99
  end
84
100
  end
85
101
 
86
102
  class UnboundMethod
103
+ # Converts an UnboundMethod to a JSON schema.
104
+ #
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
+ # @return [String] The JSON schema representation of the method.
138
+ def to_json_schema
139
+ ToolTailor.convert(self)
140
+ end
141
+ end
142
+
143
+ class Method
144
+ # Converts a Method to a JSON schema.
145
+ #
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
+ # @return [String] The JSON schema representation of the method.
87
179
  def to_json_schema
88
180
  ToolTailor.convert(self)
89
181
  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.1
4
+ version: 0.1.3
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-23 00:00:00.000000000 Z
11
+ date: 2024-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: yard