tool_tailor 0.1.1 → 0.1.2
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.lock +1 -1
- data/README.md +73 -2
- data/lib/tool_tailor/version.rb +1 -1
- data/lib/tool_tailor.rb +89 -22
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: abc31d6d69830be0126f8b6f61aee408be9e17bdb91423b68e9166f30efaf21f
|
4
|
+
data.tar.gz: 7eb42498a63836eeac46c6996d2e32f34303430b58dcdd36cb89d833cbadeaef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04d10928c7453fbd20980d3c23a6bbbe8cf000baa25f2ffce903910b21607088762f300fde528ee4c7f0b8673ba58a65224926c261486ff033d87c5d25601468
|
7
|
+
data.tar.gz: 779238cd2409d9d6c669cab52f0d286fc79cf3c9335082d88bf62d871f23ec6439fe479f465d25fab0bd13d2285e6522ee026ea9e5257b019cda483244c28664
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -26,12 +26,12 @@ 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
|
+
# Unbound method
|
35
35
|
TestClass.instance_method(:get_current_weather).to_json_schema # => {
|
36
36
|
# "type" => "function",
|
37
37
|
# "function" => {
|
@@ -56,6 +56,77 @@ TestClass.instance_method(:get_current_weather).to_json_schema # => {
|
|
56
56
|
# "required" => ["location", "unit", "api_key"]
|
57
57
|
# }
|
58
58
|
# }
|
59
|
+
|
60
|
+
# Bound method
|
61
|
+
example_instance = TestClass.new
|
62
|
+
example_instance.method(:get_current_weather).to_json_schema # => {
|
63
|
+
# "type" => "function",
|
64
|
+
# "function" => {
|
65
|
+
# "name" => "get_current_weather",
|
66
|
+
# "description" => "Get the current weather in a given location.",
|
67
|
+
# "parameters" => {
|
68
|
+
# "type" => "object",
|
69
|
+
# "properties" => {
|
70
|
+
# "location" => {
|
71
|
+
# "type" => "string",
|
72
|
+
# "description" => "The city and state, e.g., San Francisco, CA."
|
73
|
+
# },
|
74
|
+
# "unit" => {
|
75
|
+
# "type" => "string",
|
76
|
+
# "description" => "The unit of temperature, either 'celsius' or 'fahrenheit'."
|
77
|
+
# },
|
78
|
+
# "api_key" => {
|
79
|
+
# "type" => "number",
|
80
|
+
# "description" => "The API key for the weather service."
|
81
|
+
# }
|
82
|
+
# },
|
83
|
+
# "required" => ["location", "unit", "api_key"]
|
84
|
+
# }
|
85
|
+
# }
|
86
|
+
# }
|
87
|
+
```
|
88
|
+
|
89
|
+
And with [ruby-openai](https://github.com/alexrudall/ruby-openai):
|
90
|
+
|
91
|
+
```rb
|
92
|
+
response =
|
93
|
+
client.chat(
|
94
|
+
parameters: {
|
95
|
+
model: "gpt-4o",
|
96
|
+
messages: [
|
97
|
+
{
|
98
|
+
"role": "user",
|
99
|
+
"content": "What is the weather like in San Francisco?",
|
100
|
+
},
|
101
|
+
],
|
102
|
+
tools: [
|
103
|
+
TestClass.instance_method(:get_current_weather).to_json_schema
|
104
|
+
],
|
105
|
+
tool_choice: {
|
106
|
+
type: "function",
|
107
|
+
function: {
|
108
|
+
name: "get_current_weather"
|
109
|
+
}
|
110
|
+
}
|
111
|
+
},
|
112
|
+
)
|
113
|
+
|
114
|
+
message = response.dig("choices", 0, "message")
|
115
|
+
|
116
|
+
if message["role"] == "assistant" && message["tool_calls"]
|
117
|
+
function_name = message.dig("tool_calls", 0, "function", "name")
|
118
|
+
args =
|
119
|
+
JSON.parse(
|
120
|
+
message.dig("tool_calls", 0, "function", "arguments"),
|
121
|
+
{ symbolize_names: true },
|
122
|
+
)
|
123
|
+
|
124
|
+
case function_name
|
125
|
+
when "get_current_weather"
|
126
|
+
TestClass.get_current_weather(**args)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
# => "The weather is nice 🌞"
|
59
130
|
```
|
60
131
|
|
61
132
|
## Development
|
data/lib/tool_tailor/version.rb
CHANGED
data/lib/tool_tailor.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
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
13
|
def self.convert(function)
|
@@ -18,14 +18,13 @@ module ToolTailor
|
|
18
18
|
file_path, line_number = function.source_location
|
19
19
|
YARD.parse(file_path)
|
20
20
|
|
21
|
-
# Construct the correct identifier for the YARD object
|
22
21
|
method_path = "#{function.owner}##{function.name}"
|
23
22
|
yard_object = YARD::Registry.at(method_path)
|
24
23
|
raise "Documentation for #{method_path} not found." if yard_object.nil?
|
25
24
|
|
26
25
|
function_description = yard_object.docstring
|
27
26
|
|
28
|
-
parameters = yard_object.tags(
|
27
|
+
parameters = yard_object.tags("param").map do |tag|
|
29
28
|
{
|
30
29
|
name: tag.name,
|
31
30
|
type: type_mapping(tag.types.first),
|
@@ -57,33 +56,101 @@ module ToolTailor
|
|
57
56
|
|
58
57
|
# Maps Ruby types to JSON schema types.
|
59
58
|
#
|
60
|
-
# @param type [
|
59
|
+
# @param type [String] The Ruby type to map.
|
61
60
|
# @return [String] The corresponding JSON schema type.
|
62
61
|
# @raise [ArgumentError] If the provided type is not supported.
|
63
62
|
def self.type_mapping(type)
|
64
63
|
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'
|
64
|
+
when "String" then "string"
|
65
|
+
when "Integer" then "integer"
|
66
|
+
when "Float" then "number"
|
67
|
+
when "TrueClass", "FalseClass" then "boolean"
|
68
|
+
when "Array" then "array"
|
69
|
+
when "Hash" then "object"
|
70
|
+
when "NilClass" then "null"
|
79
71
|
else
|
80
|
-
|
81
|
-
'string'
|
72
|
+
raise ArgumentError, "Unsupported type: #{type} #{type.class}"
|
82
73
|
end
|
83
74
|
end
|
84
75
|
end
|
85
76
|
|
86
77
|
class UnboundMethod
|
78
|
+
# Converts an UnboundMethod to a JSON schema.
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# class ExampleClass
|
82
|
+
# # @param name [String] The name of the person.
|
83
|
+
# # @param age [Integer] The age of the person.
|
84
|
+
# def greet(name, age)
|
85
|
+
# puts "Hello, #{name}! You are #{age} years old."
|
86
|
+
# end
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# ExampleClass.instance_method(:greet).to_json_schema
|
90
|
+
# # => {
|
91
|
+
# # "type" => "function",
|
92
|
+
# # "function" => {
|
93
|
+
# # "name" => "greet",
|
94
|
+
# # "description" => "",
|
95
|
+
# # "parameters" => {
|
96
|
+
# # "type" => "object",
|
97
|
+
# # "properties" => {
|
98
|
+
# # "name" => {
|
99
|
+
# # "type" => "string",
|
100
|
+
# # "description" => "The name of the person."
|
101
|
+
# # },
|
102
|
+
# # "age" => {
|
103
|
+
# # "type" => "integer",
|
104
|
+
# # "description" => "The age of the person."
|
105
|
+
# # }
|
106
|
+
# # },
|
107
|
+
# # "required" => ["name", "age"]
|
108
|
+
# # }
|
109
|
+
# # }
|
110
|
+
# # }
|
111
|
+
#
|
112
|
+
# @return [String] The JSON schema representation of the method.
|
113
|
+
def to_json_schema
|
114
|
+
ToolTailor.convert(self)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class Method
|
119
|
+
# Converts a Method to a JSON schema.
|
120
|
+
#
|
121
|
+
# @example
|
122
|
+
# class ExampleClass
|
123
|
+
# # @param name [String] The name of the person.
|
124
|
+
# # @param age [Integer] The age of the person.
|
125
|
+
# def greet(name, age)
|
126
|
+
# puts "Hello, #{name}! You are #{age} years old."
|
127
|
+
# end
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# ExampleClass.new.method(:greet).to_json_schema
|
131
|
+
# # => {
|
132
|
+
# # "type" => "function",
|
133
|
+
# # "function" => {
|
134
|
+
# # "name" => "greet",
|
135
|
+
# # "description" => "",
|
136
|
+
# # "parameters" => {
|
137
|
+
# # "type" => "object",
|
138
|
+
# # "properties" => {
|
139
|
+
# # "name" => {
|
140
|
+
# # "type" => "string",
|
141
|
+
# # "description" => "The name of the person."
|
142
|
+
# # },
|
143
|
+
# # "age" => {
|
144
|
+
# # "type" => "integer",
|
145
|
+
# # "description" => "The age of the person."
|
146
|
+
# # }
|
147
|
+
# # },
|
148
|
+
# # "required" => ["name", "age"]
|
149
|
+
# # }
|
150
|
+
# # }
|
151
|
+
# # }
|
152
|
+
#
|
153
|
+
# @return [String] The JSON schema representation of the method.
|
87
154
|
def to_json_schema
|
88
155
|
ToolTailor.convert(self)
|
89
156
|
end
|