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 +4 -4
- data/Gemfile +2 -0
- data/Gemfile.lock +10 -1
- data/README.md +76 -2
- data/lib/tool_tailor/version.rb +1 -1
- data/lib/tool_tailor.rb +121 -29
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33d87f65353ea72f0715a86b7759529f68f6678eb6a24d47fac0547bade1b59e
|
4
|
+
data.tar.gz: 874c949f555e93aa445147d89981e1207e11fdc4520977a668e9724354ca7d95
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4500dc6778e3bb5373a4630160b22a2ad7f0669ddb097d221eb93b3f1c27a93b48310ef4fcc434ec2c154ea004beb84d157bd1550d4ada0eb1e8fe7b8daa83da
|
7
|
+
data.tar.gz: 036f48ffa4b31354b24c85392cf52520215afcb02afac201289e19e650f4491be5fa0122aca606fc6d421d833ee2b8a64533b53a5936ea90608d2197caecd2b2
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
tool_tailor (0.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
|
-
|
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
|
data/lib/tool_tailor/version.rb
CHANGED
data/lib/tool_tailor.rb
CHANGED
@@ -1,38 +1,62 @@
|
|
1
1
|
require "tool_tailor/version"
|
2
|
-
require
|
3
|
-
require
|
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
|
36
|
+
# Extract parameters from the function definition
|
37
|
+
parameters = function.parameters.map do |_, name|
|
29
38
|
{
|
30
|
-
name:
|
31
|
-
type:
|
32
|
-
description:
|
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.
|
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 [
|
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
|
-
|
67
|
-
when "
|
68
|
-
|
69
|
-
when "
|
70
|
-
|
71
|
-
when "
|
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
|
-
|
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.
|
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-
|
11
|
+
date: 2024-05-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: yard
|