omniai 1.4.2 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/README.md +32 -1
- data/lib/omniai/chat/chunk.rb +1 -1
- data/lib/omniai/chat/completion.rb +21 -4
- data/lib/omniai/chat/delta.rb +11 -14
- data/lib/omniai/chat/delta_choice.rb +14 -12
- data/lib/omniai/chat/function.rb +28 -0
- data/lib/omniai/chat/message.rb +20 -14
- data/lib/omniai/chat/message_choice.rb +14 -12
- data/lib/omniai/chat/tool_call.rb +33 -0
- data/lib/omniai/chat/usage.rb +10 -24
- data/lib/omniai/chat.rb +50 -6
- data/lib/omniai/cli/chat_handler.rb +1 -0
- data/lib/omniai/cli.rb +3 -2
- data/lib/omniai/client.rb +3 -2
- data/lib/omniai/instrumentation.rb +0 -2
- data/lib/omniai/tool/parameters.rb +47 -0
- data/lib/omniai/tool/property.rb +90 -0
- data/lib/omniai/tool.rb +85 -0
- data/lib/omniai/version.rb +1 -1
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c05ed0777b6e88c023738b43e5188d722ca9bf372a7cabda61a800ae5a7a13a7
|
4
|
+
data.tar.gz: e2f0220221030ce99bc81dfd456a7505bce557594ba03336d5445ff4a7188b7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ec41c146e0052129894ad17fdcbd8768b96d76114f85604098153c901a842b4d59cc78121ad5e77a7a7501c17c3caf060ed2bcf4c05fea3523fc8c081f8479b
|
7
|
+
data.tar.gz: 11c641f64597f09bff8d67dd00b5599701868c7b31bbb12675191cd554e94b051c97e6f7251e355dcf849b9693f34dc0f29c8b8b0ac92a77fa0ff00ca6c2d05e
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -143,7 +143,9 @@ completion = client.chat(messages, model: '...', temperature: 0.7, format: :json
|
|
143
143
|
completion.choice.message.content # '...'
|
144
144
|
```
|
145
145
|
|
146
|
-
#### Completions using
|
146
|
+
#### Completions using Streaming via Proc
|
147
|
+
|
148
|
+
A real-time stream of messages can be generated by passing in a proc:
|
147
149
|
|
148
150
|
```ruby
|
149
151
|
stream = proc do |chunk|
|
@@ -152,6 +154,35 @@ end
|
|
152
154
|
client.chat('Tell me a joke.', stream:)
|
153
155
|
```
|
154
156
|
|
157
|
+
#### Completion using Streaming via IO
|
158
|
+
|
159
|
+
The above code can also be supplied any IO (e.g. `File`, `$stdout`, `$stdin`, etc):
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
client.chat('Tell me a story', stream: $stdout)
|
163
|
+
```
|
164
|
+
|
165
|
+
#### Completion with Tools
|
166
|
+
|
167
|
+
A chat can also be initialized with tools:
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
client.chat('What is the weather in "London, England" and "Madrid, Spain"?', tools: [
|
171
|
+
OmniAI::Tool.new(
|
172
|
+
proc { |location:, unit: 'celsius'| "It is #{rand(20..50)}° #{unit} in #{location}" },
|
173
|
+
name: 'Weather',
|
174
|
+
description: 'Lookup the weather in a location',
|
175
|
+
parameters: OmniAI::Tool::Parameters.new(
|
176
|
+
properties: {
|
177
|
+
location: OmniAI::Tool::Property.string(description: 'The city and country (e.g. Toronto, Canada).'),
|
178
|
+
unit: OmniAI::Tool::Property.string(enum: %w[celcius farenheit]),
|
179
|
+
},
|
180
|
+
required: %i[location]
|
181
|
+
)
|
182
|
+
)
|
183
|
+
])
|
184
|
+
```
|
185
|
+
|
155
186
|
### Transcribe
|
156
187
|
|
157
188
|
Clients that support transcribe (e.g. OpenAI w/ "Whisper") convert recordings to text via the following calls:
|
data/lib/omniai/chat/chunk.rb
CHANGED
@@ -11,6 +11,11 @@ module OmniAI
|
|
11
11
|
@data = data
|
12
12
|
end
|
13
13
|
|
14
|
+
# @return [String]
|
15
|
+
def inspect
|
16
|
+
"#<#{self.class.name} id=#{id.inspect} choices=#{choices.inspect}"
|
17
|
+
end
|
18
|
+
|
14
19
|
# @return [String]
|
15
20
|
def id
|
16
21
|
@data['id']
|
@@ -33,14 +38,12 @@ module OmniAI
|
|
33
38
|
|
34
39
|
# @return [OmniAI::Chat::Usage]
|
35
40
|
def usage
|
36
|
-
|
37
|
-
|
38
|
-
@usage ||= Usage.for(data: @data['usage'])
|
41
|
+
@usage ||= Usage.new(data: @data['usage']) if @data['usage']
|
39
42
|
end
|
40
43
|
|
41
44
|
# @return [Array<OmniAI::Chat::MessageChoice>]
|
42
45
|
def choices
|
43
|
-
@choices ||= @data['choices'].map { |data| MessageChoice.
|
46
|
+
@choices ||= @data['choices'].map { |data| MessageChoice.new(data:) }
|
44
47
|
end
|
45
48
|
|
46
49
|
# @param index [Integer] optional - default is 0
|
@@ -48,6 +51,20 @@ module OmniAI
|
|
48
51
|
def choice(index: 0)
|
49
52
|
choices[index]
|
50
53
|
end
|
54
|
+
|
55
|
+
# @return [Boolean]
|
56
|
+
def tool_call_required?
|
57
|
+
choices.any? { |choice| choice.message.tool_call_list.any? }
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Array<OmniAI::Chat::ToolCall>]
|
61
|
+
def tool_call_list
|
62
|
+
list = []
|
63
|
+
choices.each do |choice|
|
64
|
+
list += choice.message.tool_call_list
|
65
|
+
end
|
66
|
+
list
|
67
|
+
end
|
51
68
|
end
|
52
69
|
end
|
53
70
|
end
|
data/lib/omniai/chat/delta.rb
CHANGED
@@ -4,27 +4,24 @@ module OmniAI
|
|
4
4
|
class Chat
|
5
5
|
# A delta returned by the API.
|
6
6
|
class Delta
|
7
|
-
attr_accessor :role, :content
|
8
|
-
|
9
7
|
# @param data [Hash]
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
role = data['role'] || data[:role]
|
8
|
+
def initialize(data:)
|
9
|
+
@data = data
|
10
|
+
end
|
14
11
|
|
15
|
-
|
12
|
+
# @return [String]
|
13
|
+
def inspect
|
14
|
+
"#<#{self.class.name} role=#{role.inspect} content=#{content.inspect}>"
|
16
15
|
end
|
17
16
|
|
18
|
-
# @
|
19
|
-
|
20
|
-
|
21
|
-
@content = content
|
22
|
-
@role = role
|
17
|
+
# @return [String, nil]
|
18
|
+
def content
|
19
|
+
@data['content']
|
23
20
|
end
|
24
21
|
|
25
22
|
# @return [String]
|
26
|
-
def
|
27
|
-
|
23
|
+
def role
|
24
|
+
@data['role'] || Role::USER
|
28
25
|
end
|
29
26
|
end
|
30
27
|
end
|
@@ -4,22 +4,24 @@ module OmniAI
|
|
4
4
|
class Chat
|
5
5
|
# A delta choice returned by the API.
|
6
6
|
class DeltaChoice
|
7
|
-
attr_accessor :index, :delta
|
8
|
-
|
9
7
|
# @param data [Hash]
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
def initialize(data:)
|
9
|
+
@data = data
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Integer]
|
13
|
+
def index
|
14
|
+
@data['index']
|
15
|
+
end
|
14
16
|
|
15
|
-
|
17
|
+
# @return [OmniAI::Chat::Delta]
|
18
|
+
def delta
|
19
|
+
@delta ||= Delta.new(data: @data['delta'])
|
16
20
|
end
|
17
21
|
|
18
|
-
# @
|
19
|
-
|
20
|
-
|
21
|
-
@index = index
|
22
|
-
@delta = delta
|
22
|
+
# @return [String]
|
23
|
+
def inspect
|
24
|
+
"#<#{self.class.name} index=#{index} delta=#{delta.inspect}>"
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OmniAI
|
4
|
+
class Chat
|
5
|
+
# A function returned by the API.
|
6
|
+
class Function
|
7
|
+
# @param data [Hash]
|
8
|
+
def initialize(data:)
|
9
|
+
@data = data
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [String]
|
13
|
+
def name
|
14
|
+
@data['name']
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Hash, nil]
|
18
|
+
def arguments
|
19
|
+
JSON.parse(@data['arguments']) if @data['arguments']
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [String]
|
23
|
+
def inspect
|
24
|
+
"#<#{self.class.name} name=#{name.inspect} arguments=#{arguments.inspect}>"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/omniai/chat/message.rb
CHANGED
@@ -4,28 +4,34 @@ module OmniAI
|
|
4
4
|
class Chat
|
5
5
|
# A message returned by the API.
|
6
6
|
class Message
|
7
|
-
|
7
|
+
# @return [Hash]
|
8
|
+
attr_accessor :data
|
8
9
|
|
9
10
|
# @param data [Hash]
|
10
|
-
|
11
|
-
|
12
|
-
content = data['content'] || data[:content]
|
13
|
-
role = data['role'] || data[:role]
|
14
|
-
|
15
|
-
new(content:, role: role || Role::USER)
|
16
|
-
end
|
17
|
-
|
18
|
-
# @param content [String]
|
19
|
-
# @param role [String] optional (default to "user") e.g. "assistant" / "user" / "system"
|
20
|
-
def initialize(content:, role: OmniAI::Chat::Role::USER)
|
21
|
-
@role = role
|
22
|
-
@content = content
|
11
|
+
def initialize(data:)
|
12
|
+
@data = data
|
23
13
|
end
|
24
14
|
|
25
15
|
# @return [String]
|
26
16
|
def inspect
|
27
17
|
"#<#{self.class.name} role=#{role.inspect} content=#{content.inspect}>"
|
28
18
|
end
|
19
|
+
|
20
|
+
# @return [String]
|
21
|
+
def role
|
22
|
+
@data['role'] || Role::USER
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [String, nil]
|
26
|
+
def content
|
27
|
+
@data['content']
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Array<OmniAI::Chat::ToolCall>]
|
31
|
+
def tool_call_list
|
32
|
+
@tool_call_list ||=
|
33
|
+
@data['tool_calls'] ? @data['tool_calls'].map { |tool_call_data| ToolCall.new(data: tool_call_data) } : []
|
34
|
+
end
|
29
35
|
end
|
30
36
|
end
|
31
37
|
end
|
@@ -4,22 +4,24 @@ module OmniAI
|
|
4
4
|
class Chat
|
5
5
|
# A choice returned by the API.
|
6
6
|
class MessageChoice
|
7
|
-
attr_accessor :index, :message
|
8
|
-
|
9
7
|
# @param data [Hash]
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
def initialize(data:)
|
9
|
+
@data = data
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Integer]
|
13
|
+
def index
|
14
|
+
@data['index']
|
15
|
+
end
|
14
16
|
|
15
|
-
|
17
|
+
# @return [OmniAI::Chat::Message]
|
18
|
+
def message
|
19
|
+
@message ||= Message.new(data: @data['message'])
|
16
20
|
end
|
17
21
|
|
18
|
-
# @
|
19
|
-
|
20
|
-
|
21
|
-
@index = index
|
22
|
-
@message = message
|
22
|
+
# @return [String]
|
23
|
+
def inspect
|
24
|
+
"#<#{self.class.name} index=#{index} message=#{message.inspect}>"
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OmniAI
|
4
|
+
class Chat
|
5
|
+
# A tool-call returned by the API.
|
6
|
+
class ToolCall
|
7
|
+
# @param data [Hash]
|
8
|
+
def initialize(data:)
|
9
|
+
@data = data
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [String]
|
13
|
+
def id
|
14
|
+
@data['id']
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [String]
|
18
|
+
def type
|
19
|
+
@data['type']
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [OmniAI::Chat::Function]
|
23
|
+
def function
|
24
|
+
@function ||= Function.new(data: @data['function']) if @data['function']
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String]
|
28
|
+
def inspect
|
29
|
+
"#<#{self.class.name} id=#{id.inspect} type=#{type.inspect}>"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/omniai/chat/usage.rb
CHANGED
@@ -4,39 +4,25 @@ module OmniAI
|
|
4
4
|
class Chat
|
5
5
|
# A usage returned by the API.
|
6
6
|
class Usage
|
7
|
-
attr_accessor :
|
7
|
+
attr_accessor :data
|
8
8
|
|
9
9
|
# @param data [Hash]
|
10
|
-
|
11
|
-
|
12
|
-
input_tokens = data['input_tokens'] || data['prompt_tokens']
|
13
|
-
output_tokens = data['output_tokens'] || data['completion_tokens']
|
14
|
-
total_tokens = data['total_tokens'] || (input_tokens + output_tokens)
|
15
|
-
|
16
|
-
new(
|
17
|
-
input_tokens:,
|
18
|
-
output_tokens:,
|
19
|
-
total_tokens:
|
20
|
-
)
|
10
|
+
def initialize(data:)
|
11
|
+
@data = data
|
21
12
|
end
|
22
13
|
|
23
|
-
# @
|
24
|
-
|
25
|
-
|
26
|
-
def initialize(input_tokens:, output_tokens:, total_tokens:)
|
27
|
-
@input_tokens = input_tokens
|
28
|
-
@output_tokens = output_tokens
|
29
|
-
@total_tokens = total_tokens
|
14
|
+
# @return [Integer]
|
15
|
+
def input_tokens
|
16
|
+
@data['input_tokens'] || @data['prompt_tokens']
|
30
17
|
end
|
31
18
|
|
32
19
|
# @return [Integer]
|
33
|
-
def
|
34
|
-
@output_tokens
|
20
|
+
def output_tokens
|
21
|
+
@data['output_tokens'] || @data['completion_tokens']
|
35
22
|
end
|
36
23
|
|
37
|
-
|
38
|
-
|
39
|
-
@input_tokens
|
24
|
+
def total_tokens
|
25
|
+
@data['total_tokens'] || (input_tokens + output_tokens)
|
40
26
|
end
|
41
27
|
|
42
28
|
# @return [String]
|
data/lib/omniai/chat.rb
CHANGED
@@ -27,10 +27,19 @@ module OmniAI
|
|
27
27
|
class Chat
|
28
28
|
JSON_PROMPT = 'Respond with valid JSON. Do not include any non-JSON in the response.'
|
29
29
|
|
30
|
+
# An error raised when a chat makes a tool-call for a tool that cannot be found.
|
31
|
+
class ToolCallLookupError < Error
|
32
|
+
def initialize(tool_call)
|
33
|
+
super("missing tool for tool_call=#{tool_call.inspect}")
|
34
|
+
@tool_call = tool_call
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
30
38
|
module Role
|
31
39
|
ASSISTANT = 'assistant'
|
32
40
|
USER = 'user'
|
33
41
|
SYSTEM = 'system'
|
42
|
+
TOOL = 'tool'
|
34
43
|
end
|
35
44
|
|
36
45
|
module Format
|
@@ -46,13 +55,15 @@ module OmniAI
|
|
46
55
|
# @param model [String] required
|
47
56
|
# @param temperature [Float, nil] optional
|
48
57
|
# @param stream [Proc, IO, nil] optional
|
58
|
+
# @param tools [Array<OmniAI::Tool>] optional
|
49
59
|
# @param format [Symbol, nil] optional - :json
|
50
|
-
def initialize(messages, client:, model:, temperature: nil, stream: nil, format: nil)
|
51
|
-
@messages = messages
|
60
|
+
def initialize(messages, client:, model:, temperature: nil, stream: nil, tools: nil, format: nil)
|
61
|
+
@messages = arrayify(messages)
|
52
62
|
@client = client
|
53
63
|
@model = model
|
54
64
|
@temperature = temperature
|
55
65
|
@stream = stream
|
66
|
+
@tools = tools
|
56
67
|
@format = format
|
57
68
|
end
|
58
69
|
|
@@ -88,8 +99,20 @@ module OmniAI
|
|
88
99
|
end
|
89
100
|
|
90
101
|
# @param response [OmniAI::Chat::Completion]
|
102
|
+
# @return [OmniAI::Chat::Completion]
|
91
103
|
def complete!(response:)
|
92
|
-
self.class::Completion.new(data: response.parse)
|
104
|
+
completion = self.class::Completion.new(data: response.parse)
|
105
|
+
|
106
|
+
if @tools && completion.tool_call_required?
|
107
|
+
@messages = [
|
108
|
+
*@messages,
|
109
|
+
*completion.choices.map(&:message).map(&:data),
|
110
|
+
*(completion.tool_call_list.map { |tool_call| execute_tool_call(tool_call) }),
|
111
|
+
]
|
112
|
+
process!
|
113
|
+
else
|
114
|
+
completion
|
115
|
+
end
|
93
116
|
end
|
94
117
|
|
95
118
|
# @param response [HTTP::Response]
|
@@ -99,19 +122,19 @@ module OmniAI
|
|
99
122
|
|
100
123
|
self.class::Stream.new(response:).stream! do |chunk|
|
101
124
|
case @stream
|
102
|
-
when IO
|
125
|
+
when IO, StringIO
|
103
126
|
@stream << chunk.choice.delta.content
|
104
127
|
@stream.flush
|
105
128
|
else @stream.call(chunk)
|
106
129
|
end
|
107
130
|
end
|
108
131
|
|
109
|
-
@stream.puts if @stream.is_a?(IO)
|
132
|
+
@stream.puts if @stream.is_a?(IO) || @stream.is_a?(StringIO)
|
110
133
|
end
|
111
134
|
|
112
135
|
# @return [Array<Hash>]
|
113
136
|
def messages
|
114
|
-
|
137
|
+
@messages.map do |content|
|
115
138
|
case content
|
116
139
|
when String then { role: Role::USER, content: }
|
117
140
|
when Hash then content
|
@@ -133,5 +156,26 @@ module OmniAI
|
|
133
156
|
.accept(:json)
|
134
157
|
.post(path, json: payload)
|
135
158
|
end
|
159
|
+
|
160
|
+
# @param tool_call [OmniAI::Chat::ToolCall]
|
161
|
+
def execute_tool_call(tool_call)
|
162
|
+
function = tool_call.function
|
163
|
+
|
164
|
+
tool = @tools.find { |entry| function.name == entry.name } || raise(ToolCallLookupError, tool_call)
|
165
|
+
result = tool.call(function.arguments)
|
166
|
+
|
167
|
+
prepare_tool_call_message(tool_call:, content: result)
|
168
|
+
end
|
169
|
+
|
170
|
+
# @param tool_call [OmniAI::Chat::ToolCall]
|
171
|
+
# @param content [String]
|
172
|
+
def prepare_tool_call_message(tool_call:, content:)
|
173
|
+
{
|
174
|
+
role: Role::TOOL,
|
175
|
+
name: tool_call.function.name,
|
176
|
+
tool_call_id: tool_call.id,
|
177
|
+
content:,
|
178
|
+
}
|
179
|
+
end
|
136
180
|
end
|
137
181
|
end
|
data/lib/omniai/cli.rb
CHANGED
@@ -12,8 +12,8 @@ module OmniAI
|
|
12
12
|
class CLI
|
13
13
|
ChatArgs = Struct.new(:provider, :model, :temperature)
|
14
14
|
|
15
|
-
# @param
|
16
|
-
# @param
|
15
|
+
# @param stdin [IO] a stream
|
16
|
+
# @param stdout [IO] a stream
|
17
17
|
# @param provider [String] a provider
|
18
18
|
def initialize(stdin: $stdin, stdout: $stdout, provider: 'openai')
|
19
19
|
@stdin = stdin
|
@@ -22,6 +22,7 @@ module OmniAI
|
|
22
22
|
@args = {}
|
23
23
|
end
|
24
24
|
|
25
|
+
# @param argv [Array<String>]
|
25
26
|
def parse(argv = ARGV)
|
26
27
|
parser.order!(argv)
|
27
28
|
command = argv.shift
|
data/lib/omniai/client.rb
CHANGED
@@ -129,9 +129,10 @@ module OmniAI
|
|
129
129
|
# @param format [Symbol] optional :text or :json
|
130
130
|
# @param temperature [Float, nil] optional
|
131
131
|
# @param stream [Proc, nil] optional
|
132
|
+
# @param tools [Array<OmniAI::Tool>] optional
|
132
133
|
#
|
133
134
|
# @return [OmniAI::Chat::Completion]
|
134
|
-
def chat(messages, model:, temperature: nil, format: nil, stream: nil)
|
135
|
+
def chat(messages, model:, temperature: nil, format: nil, stream: nil, tools: nil)
|
135
136
|
raise NotImplementedError, "#{self.class.name}#chat undefined"
|
136
137
|
end
|
137
138
|
|
@@ -144,7 +145,7 @@ module OmniAI
|
|
144
145
|
# @param temperature [Float, nil] optional
|
145
146
|
# @param format [Symbol] :text, :srt, :vtt, or :json (default)
|
146
147
|
#
|
147
|
-
# @return
|
148
|
+
# @return [OmniAI::Transcribe::Transcription]
|
148
149
|
def transcribe(io, model:, language: nil, prompt: nil, temperature: nil, format: nil)
|
149
150
|
raise NotImplementedError, "#{self.class.name}#speak undefined"
|
150
151
|
end
|
@@ -18,7 +18,6 @@ module OmniAI
|
|
18
18
|
@logger.error("#{name}: #{error.message}")
|
19
19
|
end
|
20
20
|
|
21
|
-
# @param name [String]
|
22
21
|
# @param payload [Hash]
|
23
22
|
# @option payload [HTTP::Request] :request
|
24
23
|
def start(_, payload)
|
@@ -26,7 +25,6 @@ module OmniAI
|
|
26
25
|
@logger.info("#{request.verb.upcase} #{request.uri}")
|
27
26
|
end
|
28
27
|
|
29
|
-
# @param name [String]
|
30
28
|
# @param payload [Hash]
|
31
29
|
# @option payload [HTTP::Response] :response
|
32
30
|
def finish(_, payload)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OmniAI
|
4
|
+
class Tool
|
5
|
+
# Usage:
|
6
|
+
#
|
7
|
+
# parameters = OmniAI::Tool::Parameters.new(properties: {
|
8
|
+
# n: OmniAI::Tool::Parameters.integer(description: 'The nth number to calculate.')
|
9
|
+
# required: %i[n]
|
10
|
+
# })
|
11
|
+
class Parameters
|
12
|
+
module Type
|
13
|
+
OBJECT = 'object'
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param type [String]
|
17
|
+
# @param properties [Hash]
|
18
|
+
# @param required [Array<String>]
|
19
|
+
# @return [OmniAI::Tool::Parameters]
|
20
|
+
def initialize(type: Type::OBJECT, properties: {}, required: [])
|
21
|
+
@type = type
|
22
|
+
@properties = properties
|
23
|
+
@required = required
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Hash]
|
27
|
+
def prepare
|
28
|
+
{
|
29
|
+
type: @type,
|
30
|
+
properties: @properties.transform_values(&:prepare),
|
31
|
+
required: @required,
|
32
|
+
}.compact
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param args [Hash]
|
36
|
+
# @return [Hash]
|
37
|
+
def parse(args)
|
38
|
+
result = {}
|
39
|
+
@properties.each do |name, property|
|
40
|
+
value = args[String(name)]
|
41
|
+
result[name.intern] = property.parse(value) if value
|
42
|
+
end
|
43
|
+
result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OmniAI
|
4
|
+
class Tool
|
5
|
+
# Usage:
|
6
|
+
#
|
7
|
+
# property = OmniAI::Tool::Property.new(type: 'string', description: 'The nth number to calculate.')
|
8
|
+
class Property
|
9
|
+
module Type
|
10
|
+
BOOLEAN = 'boolean'
|
11
|
+
INTEGER = 'integer'
|
12
|
+
STRING = 'string'
|
13
|
+
NUMBER = 'number'
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [String]
|
17
|
+
attr_reader :type
|
18
|
+
|
19
|
+
# @return [String, nil]
|
20
|
+
attr_reader :description
|
21
|
+
|
22
|
+
# @return [Array<String>, nil]
|
23
|
+
attr_reader :enum
|
24
|
+
|
25
|
+
# @param description [String]
|
26
|
+
# @param enum [Array<String>]
|
27
|
+
# @return [OmniAI::Tool::Property]
|
28
|
+
def self.boolean(description: nil, enum: nil)
|
29
|
+
new(type: Type::BOOLEAN, description:, enum:)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param description [String]
|
33
|
+
# @param enum [Array<String>]
|
34
|
+
# @return [OmniAI::Tool::Property]
|
35
|
+
def self.integer(description: nil, enum: nil)
|
36
|
+
new(type: Type::INTEGER, description:, enum:)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param description [String]
|
40
|
+
# @param enum [Array<String>]
|
41
|
+
# @return [OmniAI::Tool::Property]
|
42
|
+
def self.string(description: nil, enum: nil)
|
43
|
+
new(type: Type::STRING, description:, enum:)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param description [String]
|
47
|
+
# @param enum [Array<String>]
|
48
|
+
# @return [OmniAI::Tool::Property]
|
49
|
+
def self.number(description: nil, enum: nil)
|
50
|
+
new(type: Type::NUMBER, description:, enum:)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @param description [String]
|
54
|
+
# @param enum [Array<String>]
|
55
|
+
# @return [OmniAI::Tool::Property]
|
56
|
+
def initialize(type:, description: nil, enum: nil)
|
57
|
+
@type = type
|
58
|
+
@description = description
|
59
|
+
@enum = enum
|
60
|
+
end
|
61
|
+
|
62
|
+
# @example
|
63
|
+
# property.prepare
|
64
|
+
# # {
|
65
|
+
# # type: 'string',
|
66
|
+
# # description: 'The unit (e.g. "fahrenheit" or "celsius").'
|
67
|
+
# # enum: %w[fahrenheit celsius]
|
68
|
+
# # }
|
69
|
+
#
|
70
|
+
# @return [Hash]
|
71
|
+
def prepare
|
72
|
+
{
|
73
|
+
type: @type,
|
74
|
+
description: @description,
|
75
|
+
enum: @enum,
|
76
|
+
}.compact
|
77
|
+
end
|
78
|
+
|
79
|
+
# @return [String, Integer, Float, Boolean, Object]
|
80
|
+
def parse(value)
|
81
|
+
case @type
|
82
|
+
when Type::INTEGER then Integer(value)
|
83
|
+
when Type::STRING then String(value)
|
84
|
+
when Type::NUMBER then Float(value)
|
85
|
+
else value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/omniai/tool.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OmniAI
|
4
|
+
# Usage:
|
5
|
+
#
|
6
|
+
# fibonacci = proc do |n:|
|
7
|
+
# next(0) if n == 0
|
8
|
+
# next(1) if n == 1
|
9
|
+
# fibonacci.call(n: n - 1) + fibonacci.call(n: n - 2)
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# tool = OmniAI::Tool.new(fibonacci,
|
13
|
+
# name: 'Fibonacci',
|
14
|
+
# description: 'Cacluate the nth Fibonacci',
|
15
|
+
# parameters: OmniAI::Tool::Parameters.new(
|
16
|
+
# properties: {
|
17
|
+
# n: OmniAI::Tool::Property.integer(description: 'The nth Fibonacci number to calculate')
|
18
|
+
# },
|
19
|
+
# required: %i[n],
|
20
|
+
# )
|
21
|
+
# )
|
22
|
+
class Tool
|
23
|
+
# @return [Proc]
|
24
|
+
attr_accessor :function
|
25
|
+
|
26
|
+
# @return [String]
|
27
|
+
attr_accessor :name
|
28
|
+
|
29
|
+
# @return [String, nil]
|
30
|
+
attr_accessor :description
|
31
|
+
|
32
|
+
# @return [Hash, nil]
|
33
|
+
attr_accessor :parameters
|
34
|
+
|
35
|
+
# @param function [Proc]
|
36
|
+
# @param name [String]
|
37
|
+
# @param description [String]
|
38
|
+
# @param parameters [Hash]
|
39
|
+
def initialize(function, name:, description: nil, parameters: nil)
|
40
|
+
@function = function
|
41
|
+
@name = name
|
42
|
+
@description = description
|
43
|
+
@parameters = parameters
|
44
|
+
end
|
45
|
+
|
46
|
+
# @example
|
47
|
+
# tool.prepare
|
48
|
+
# # {
|
49
|
+
# # type: 'function',
|
50
|
+
# # function: {
|
51
|
+
# # name: 'Fibonacci',
|
52
|
+
# # description: 'Calculate the nth Fibonacci number',
|
53
|
+
# # parameters: {
|
54
|
+
# # type: 'object',
|
55
|
+
# # properties: {
|
56
|
+
# # n: OmniAI::Tool::Property.integer(description: 'The nth Fibonacci number to calculate')
|
57
|
+
# # },
|
58
|
+
# # required: ['n']
|
59
|
+
# # }
|
60
|
+
# # }
|
61
|
+
# # }
|
62
|
+
#
|
63
|
+
# @return [Hash]
|
64
|
+
def prepare
|
65
|
+
{
|
66
|
+
type: 'function',
|
67
|
+
function: {
|
68
|
+
name: @name,
|
69
|
+
description: @description,
|
70
|
+
parameters: @parameters&.prepare,
|
71
|
+
}.compact,
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
# @example
|
76
|
+
# tool.call({ "n" => 6 })
|
77
|
+
# #=> 8
|
78
|
+
#
|
79
|
+
# @param args [Hash]
|
80
|
+
# @return [String]
|
81
|
+
def call(args = {})
|
82
|
+
@function.call(**(@parameters ? @parameters.parse(args) : args))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/omniai/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omniai
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Sylvestre
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
11
|
+
date: 2024-07-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: event_stream_parser
|
@@ -76,9 +76,11 @@ files:
|
|
76
76
|
- lib/omniai/chat/content/url.rb
|
77
77
|
- lib/omniai/chat/delta.rb
|
78
78
|
- lib/omniai/chat/delta_choice.rb
|
79
|
+
- lib/omniai/chat/function.rb
|
79
80
|
- lib/omniai/chat/message.rb
|
80
81
|
- lib/omniai/chat/message_choice.rb
|
81
82
|
- lib/omniai/chat/stream.rb
|
83
|
+
- lib/omniai/chat/tool_call.rb
|
82
84
|
- lib/omniai/chat/usage.rb
|
83
85
|
- lib/omniai/cli.rb
|
84
86
|
- lib/omniai/cli/base_handler.rb
|
@@ -87,6 +89,9 @@ files:
|
|
87
89
|
- lib/omniai/config.rb
|
88
90
|
- lib/omniai/instrumentation.rb
|
89
91
|
- lib/omniai/speak.rb
|
92
|
+
- lib/omniai/tool.rb
|
93
|
+
- lib/omniai/tool/parameters.rb
|
94
|
+
- lib/omniai/tool/property.rb
|
90
95
|
- lib/omniai/transcribe.rb
|
91
96
|
- lib/omniai/transcribe/transcription.rb
|
92
97
|
- lib/omniai/version.rb
|
@@ -112,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
117
|
- !ruby/object:Gem::Version
|
113
118
|
version: '0'
|
114
119
|
requirements: []
|
115
|
-
rubygems_version: 3.5.
|
120
|
+
rubygems_version: 3.5.14
|
116
121
|
signing_key:
|
117
122
|
specification_version: 4
|
118
123
|
summary: A generalized framework for interacting with many AI services
|