omniai 1.4.2 → 1.5.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 +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
|