omniai 1.7.0 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/README.md +5 -12
  4. data/lib/omniai/chat/choice.rb +68 -0
  5. data/lib/omniai/chat/content.rb +10 -2
  6. data/lib/omniai/chat/file.rb +3 -3
  7. data/lib/omniai/chat/function.rb +57 -0
  8. data/lib/omniai/chat/message/builder.rb +67 -0
  9. data/lib/omniai/chat/message.rb +64 -45
  10. data/lib/omniai/chat/payload.rb +85 -0
  11. data/lib/omniai/chat/prompt.rb +30 -16
  12. data/lib/omniai/chat/response.rb +70 -0
  13. data/lib/omniai/chat/stream.rb +61 -0
  14. data/lib/omniai/chat/text.rb +2 -2
  15. data/lib/omniai/chat/tool_call.rb +54 -0
  16. data/lib/omniai/chat/tool_call_message.rb +61 -0
  17. data/lib/omniai/chat/tool_call_result.rb +51 -0
  18. data/lib/omniai/chat/url.rb +2 -2
  19. data/lib/omniai/chat/usage.rb +60 -0
  20. data/lib/omniai/chat.rb +61 -34
  21. data/lib/omniai/context.rb +12 -0
  22. data/lib/omniai/embed/response.rb +2 -2
  23. data/lib/omniai/tool/parameters.rb +2 -2
  24. data/lib/omniai/tool/property.rb +2 -2
  25. data/lib/omniai/tool.rb +12 -5
  26. data/lib/omniai/version.rb +1 -1
  27. metadata +12 -16
  28. data/lib/omniai/chat/response/choice.rb +0 -35
  29. data/lib/omniai/chat/response/chunk.rb +0 -15
  30. data/lib/omniai/chat/response/completion.rb +0 -15
  31. data/lib/omniai/chat/response/delta.rb +0 -11
  32. data/lib/omniai/chat/response/delta_choice.rb +0 -25
  33. data/lib/omniai/chat/response/function.rb +0 -25
  34. data/lib/omniai/chat/response/message.rb +0 -11
  35. data/lib/omniai/chat/response/message_choice.rb +0 -25
  36. data/lib/omniai/chat/response/part.rb +0 -38
  37. data/lib/omniai/chat/response/payload.rb +0 -72
  38. data/lib/omniai/chat/response/resource.rb +0 -22
  39. data/lib/omniai/chat/response/stream.rb +0 -27
  40. data/lib/omniai/chat/response/tool_call.rb +0 -30
  41. data/lib/omniai/chat/response/usage.rb +0 -35
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmniAI
4
+ class Chat
5
+ # Used when processing everything at once.
6
+ class Response
7
+ # @return [Hash]
8
+ attr_accessor :data
9
+
10
+ # @param data [Hash]
11
+ # @param context [Context, nil]
12
+ def initialize(data:, context: nil)
13
+ @data = data
14
+ @context = context
15
+ end
16
+
17
+ # @return [Payload]
18
+ def completion
19
+ @completion ||= Payload.deserialize(@data, context: @context)
20
+ end
21
+
22
+ # @return [Usage, nil]
23
+ def usage
24
+ completion.usage
25
+ end
26
+
27
+ # @return [Array<Choice>]
28
+ def choices
29
+ completion.choices
30
+ end
31
+
32
+ # @return [Array<Message>]
33
+ def messages
34
+ completion.messages
35
+ end
36
+
37
+ # @param index [Integer]
38
+ # @return [Choice]
39
+ def choice(index: 0)
40
+ completion.choice(index:)
41
+ end
42
+
43
+ # @param index [Integer]
44
+ # @return [Message]
45
+ def message(index: 0)
46
+ completion.message(index:)
47
+ end
48
+
49
+ # @return [String]
50
+ def text
51
+ message.text
52
+ end
53
+
54
+ # @return [Boolean]
55
+ def text?
56
+ message.text?
57
+ end
58
+
59
+ # @return [Array<ToolCall>]
60
+ def tool_call_list
61
+ choice.tool_call_list
62
+ end
63
+
64
+ # @return [Boolean]
65
+ def tool_call_list?
66
+ tool_call_list&.any?
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmniAI
4
+ class Chat
5
+ # Used when streaming to process chunks of data.
6
+ class Stream
7
+ # @param logger [OmniAI::Client]
8
+ # @param body [HTTP::Response::Body]
9
+ # @param context [Context, nil]
10
+ def initialize(body:, logger: nil, context: nil)
11
+ @body = body
12
+ @logger = logger
13
+ @context = context
14
+ end
15
+
16
+ # @yield [payload]
17
+ # @yieldparam payload [OmniAI::Chat::Payload]
18
+ def stream!(&)
19
+ @body.each do |chunk|
20
+ parser.feed(chunk) do |type, data, id|
21
+ process!(type, data, id, &)
22
+ end
23
+ end
24
+ end
25
+
26
+ protected
27
+
28
+ # @param type [String]
29
+ # @param data [String]
30
+ # @param id [String]
31
+ def log(type, data, id)
32
+ arguments = [
33
+ ("type=#{type.inspect}" if type),
34
+ ("data=#{data.inspect}" if data),
35
+ ("id=#{id.inspect}" if id),
36
+ ].compact
37
+
38
+ @logger&.debug("Stream#process! #{arguments.join(' ')}")
39
+ end
40
+
41
+ # @param type [String]
42
+ # @param data [String]
43
+ # @param id [String]
44
+ #
45
+ # @yield [payload]
46
+ # @yieldparam payload [OmniAI::Chat::Payload]
47
+ def process!(type, data, id, &block)
48
+ log(type, data, id)
49
+
50
+ return if data.eql?('[DONE]')
51
+
52
+ block.call(Payload.deserialize(JSON.parse(data), context: @context))
53
+ end
54
+
55
+ # @return [EventStreamParser::Parser]
56
+ def parser
57
+ @parser ||= EventStreamParser::Parser.new
58
+ end
59
+ end
60
+ end
61
+ end
@@ -25,7 +25,7 @@ module OmniAI
25
25
 
26
26
  # @param data [Hash]
27
27
  def self.deserialize(data, context: nil)
28
- deserialize = context&.deserializers&.[](:text)
28
+ deserialize = context&.deserializer(:text)
29
29
  return deserialize.call(data, context:) if deserialize
30
30
 
31
31
  new(data['text'])
@@ -35,7 +35,7 @@ module OmniAI
35
35
  #
36
36
  # @return [Hash]
37
37
  def serialize(context: nil)
38
- serializer = context&.serializers&.[](:text)
38
+ serializer = context&.serializer(:text)
39
39
  return serializer.call(self, context:) if serializer
40
40
 
41
41
  { type: 'text', text: @text }
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmniAI
4
+ class Chat
5
+ # A tool-call that includes an ID / function.
6
+ class ToolCall
7
+ # @return [String]
8
+ attr_accessor :id
9
+
10
+ # @return [Function]
11
+ attr_accessor :function
12
+
13
+ # @param id [String]
14
+ # @param function [Function]
15
+ def initialize(id:, function:)
16
+ @id = id
17
+ @function = function
18
+ end
19
+
20
+ # @return [String]
21
+ def inspect
22
+ "#<#{self.class.name} id=#{id.inspect} function=#{function.inspect}>"
23
+ end
24
+
25
+ # @param data [Hash]
26
+ # @param context [Context] optional
27
+ #
28
+ # @return [Function]
29
+ def self.deserialize(data, context: nil)
30
+ deserialize = context&.deserializer(:tool_call)
31
+ return deserialize.call(data, context:) if deserialize
32
+
33
+ id = data['id']
34
+ function = Function.deserialize(data['function'], context:)
35
+
36
+ new(id:, function:)
37
+ end
38
+
39
+ # @param context [Context] optional
40
+ #
41
+ # @return [Hash]
42
+ def serialize(context: nil)
43
+ serializer = context&.serializer(:tool_call)
44
+ return serializer.call(self, context:) if serializer
45
+
46
+ {
47
+ id: @id,
48
+ type: 'function',
49
+ function: @function.serialize(context:),
50
+ }
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmniAI
4
+ class Chat
5
+ # A specific message that contains the result of a tool call.
6
+ class ToolCallMessage < Message
7
+ # @return [String]
8
+ attr_accessor :tool_call_id
9
+
10
+ # @param content [String]
11
+ # @param tool_call_id [String]
12
+ def initialize(content:, tool_call_id:, role: OmniAI::Chat::Role::TOOL)
13
+ super(content:, role:)
14
+ @tool_call_id = tool_call_id
15
+ end
16
+
17
+ # @return [String]
18
+ def inspect
19
+ "#<#{self.class.name} content=#{content.inspect} tool_call_id=#{tool_call_id.inspect}>"
20
+ end
21
+
22
+ # Usage:
23
+ #
24
+ # ToolCall.deserialize({ 'role' => 'tool', content: '{ 'temperature': 0 }' }) # => #<ToolCall ...>
25
+ #
26
+ # @param data [Hash]
27
+ # @param context [Context] optional
28
+ #
29
+ # @return [ToolMessage]
30
+ def self.deserialize(data, context: nil)
31
+ deserialize = context&.deserializer(:tool_message)
32
+ return deserialize.call(data, context:) if deserialize
33
+
34
+ role = data['role']
35
+ content = JSON.parse(data['content'])
36
+ tool_call_id = data['tool_call_id']
37
+
38
+ new(role:, content:, tool_call_id:)
39
+ end
40
+
41
+ # Usage:
42
+ #
43
+ # message.serialize # => { role: :user, content: 'Hello!' }
44
+ # message.serialize # => { role: :user, content: [{ type: 'text', text: 'Hello!' }] }
45
+ #
46
+ # @param context [Context] optional
47
+ #
48
+ # @return [Hash]
49
+ def serialize(context: nil)
50
+ serializer = context&.serializer(:tool_message)
51
+ return serializer.call(self, context:) if serializer
52
+
53
+ role = @role
54
+ content = JSON.generate(@content)
55
+ tool_call_id = @tool_call_id
56
+
57
+ { role:, content:, tool_call_id: }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmniAI
4
+ class Chat
5
+ # The result of a tool call.
6
+ class ToolCallResult
7
+ # @return [Object]
8
+ attr_accessor :content
9
+
10
+ # @return [ToolCall]
11
+ attr_accessor :tool_call_id
12
+
13
+ # @param content [Object]
14
+ # @param tool_call [ToolCall]
15
+ def initialize(content:, tool_call_id:)
16
+ @content = content
17
+ @tool_call_id = tool_call_id
18
+ end
19
+
20
+ # @return [String]
21
+ def inspect
22
+ "#<#{self.class.name} content=#{content.inspect} tool_call_id=#{tool_call_id.inspect}>"
23
+ end
24
+
25
+ # @param context [Context] optional
26
+ # @return [Hash]
27
+ def serialize(context: nil)
28
+ serializer = context&.serializer(:tool_call_result)
29
+ return serializer.call(self, context:) if serializer
30
+
31
+ content = JSON.generate(@content)
32
+ tool_call_id = @tool_call_id
33
+
34
+ { content:, tool_call_id: }
35
+ end
36
+
37
+ # @param data [Hash]
38
+ # @param context [Context] optional
39
+ # @return [ToolCallResult]
40
+ def self.deserialize(data, context: nil)
41
+ deserialize = context&.deserializer(:tool_call_result)
42
+ return deserialize.call(data, context:) if deserialize
43
+
44
+ content = JSON.parse(data['content'])
45
+ tool_call_id = data['tool_call_id']
46
+
47
+ new(content:, tool_call_id:)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -28,7 +28,7 @@ module OmniAI
28
28
 
29
29
  # @param data [Hash]
30
30
  def self.deserialize(data, context: nil)
31
- deserialize = context&.deserializers&.[](:url)
31
+ deserialize = context&.deserializer(:url)
32
32
  return deserialize.call(data, context:) if deserialize
33
33
 
34
34
  type = /(?<type>\w+)_url/.match(data['type'])[:type]
@@ -45,7 +45,7 @@ module OmniAI
45
45
  content = fetch!
46
46
  Text.new("<file>#{filename}: #{content}</file>").serialize(context:)
47
47
  else
48
- serializer = context&.serializers&.[](:url)
48
+ serializer = context&.serializer(:url)
49
49
  return serializer.call(self, context:) if serializer
50
50
 
51
51
  {
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmniAI
4
+ class Chat
5
+ # The usage of a chat in terms of tokens (input / output / total).
6
+ class Usage
7
+ # @return [Integer]
8
+ attr_accessor :input_tokens
9
+
10
+ # @return [Integer]
11
+ attr_accessor :output_tokens
12
+
13
+ # @return [Integer]
14
+ attr_accessor :total_tokens
15
+
16
+ # @param input_tokens [Integer]
17
+ # @param output_tokens [Integer]
18
+ # @param total_tokens [Integer]
19
+ def initialize(input_tokens:, output_tokens:, total_tokens:)
20
+ @input_tokens = input_tokens
21
+ @output_tokens = output_tokens
22
+ @total_tokens = total_tokens
23
+ end
24
+
25
+ # @return [String]
26
+ def inspect
27
+ "#<#{self.class.name} input_tokens=#{input_tokens} output_tokens=#{output_tokens} total_tokens=#{total_tokens}>"
28
+ end
29
+
30
+ # @param data [Hash]
31
+ # @param context [OmniAI::Context] optional
32
+ #
33
+ # @return [OmniAI::Chat::Usage]
34
+ def self.deserialize(data, context: nil)
35
+ deserialize = context&.deserializer(:usage)
36
+ return deserialize.call(data, context:) if deserialize
37
+
38
+ input_tokens = data['input_tokens']
39
+ output_tokens = data['output_tokens']
40
+ total_tokens = data['total_tokens']
41
+
42
+ new(input_tokens:, output_tokens:, total_tokens:)
43
+ end
44
+
45
+ # @param context [OmniAI::Context] optional
46
+ #
47
+ # @return [Hash]
48
+ def serialize(context: nil)
49
+ serialize = context&.serializer(:usage)
50
+ return serialize.call(self, context:) if serialize
51
+
52
+ {
53
+ input_tokens:,
54
+ output_tokens:,
55
+ total_tokens:,
56
+ }
57
+ end
58
+ end
59
+ end
60
+ end
data/lib/omniai/chat.rb CHANGED
@@ -27,14 +27,23 @@ 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}")
30
+ # An error raised for tool-call issues.
31
+ class ToolCallError < Error
32
+ # @param tool_call [OmniAI::Chat::ToolCall]
33
+ # @param message [String]
34
+ def initialize(tool_call:, message:)
35
+ super(message)
34
36
  @tool_call = tool_call
35
37
  end
36
38
  end
37
39
 
40
+ # An error raised when a tool-call is missing.
41
+ class ToolCallMissingError < ToolCallError
42
+ def initialize(tool_call:)
43
+ super(tool_call:, message: "missing tool for tool_call=#{tool_call.inspect}")
44
+ end
45
+ end
46
+
38
47
  module Role
39
48
  ASSISTANT = 'assistant'
40
49
  USER = 'user'
@@ -87,10 +96,22 @@ module OmniAI
87
96
 
88
97
  protected
89
98
 
99
+ # Override to provide an context for serializers / deserializes for a provider.
100
+ #
101
+ # @return [Context, nil]
102
+ def context
103
+ nil
104
+ end
105
+
106
+ # @return [Logger, nil]
107
+ def logger
108
+ @client.logger
109
+ end
110
+
90
111
  # Used to spawn another chat with the same configuration using different messages.
91
112
  #
92
113
  # @param prompt [OmniAI::Chat::Prompt]
93
- # @return [OmniAI::Chat::Prompt]
114
+ # @return [OmniAI::Chat]
94
115
  def spawn!(prompt)
95
116
  self.class.new(
96
117
  prompt,
@@ -100,7 +121,7 @@ module OmniAI
100
121
  stream: @stream,
101
122
  tools: @tools,
102
123
  format: @format
103
- ).process!
124
+ )
104
125
  end
105
126
 
106
127
  # @return [Hash]
@@ -114,7 +135,7 @@ module OmniAI
114
135
  end
115
136
 
116
137
  # @param response [HTTP::Response]
117
- # @return [OmniAI::Chat::Response::Completion]
138
+ # @return [OmniAI::Chat::Response]
118
139
  def parse!(response:)
119
140
  if @stream
120
141
  stream!(response:)
@@ -124,31 +145,32 @@ module OmniAI
124
145
  end
125
146
 
126
147
  # @param response [HTTP::Response]
127
- # @return [OmniAI::Chat::Response::Completion]
148
+ # @return [OmniAI::Chat::Response]
128
149
  def complete!(response:)
129
- completion = self.class::Response::Completion.new(data: response.parse)
130
-
131
- if @tools && completion.tool_call_list.any?
132
- spawn!([
133
- *@prompt.serialize,
134
- *completion.choices.map(&:message).map(&:data),
135
- *(completion.tool_call_list.map { |tool_call| execute_tool_call(tool_call) }),
136
- ])
150
+ completion = self.class::Response.new(data: response.parse, context:)
151
+
152
+ if @tools && completion.tool_call_list?
153
+ spawn!(
154
+ @prompt.dup.tap do |prompt|
155
+ prompt.messages += completion.messages
156
+ prompt.messages += build_tool_call_messages(completion.tool_call_list)
157
+ end
158
+ ).process!
137
159
  else
138
160
  completion
139
161
  end
140
162
  end
141
163
 
142
164
  # @param response [HTTP::Response]
143
- # @return [OmniAI::Chat::Response::Stream]
165
+ # @return [OmniAI::Chat::Stream]
144
166
  def stream!(response:)
145
167
  raise Error, "#{self.class.name}#stream! unstreamable" unless @stream
146
168
 
147
- self.class::Response::Stream.new(response:).stream! do |chunk|
169
+ self.class::Stream.new(body: response.body, logger:, context:).stream! do |chunk|
148
170
  case @stream
149
171
  when IO, StringIO
150
- if chunk.content?
151
- @stream << chunk.content
172
+ if chunk.text
173
+ @stream << chunk.text
152
174
  @stream.flush
153
175
  end
154
176
  else @stream.call(chunk)
@@ -160,31 +182,36 @@ module OmniAI
160
182
 
161
183
  # @return [HTTP::Response]
162
184
  def request!
185
+ logger&.debug("Chat#request! payload=#{payload.inspect}")
186
+
163
187
  @client
164
188
  .connection
165
189
  .accept(:json)
166
190
  .post(path, json: payload)
167
191
  end
168
192
 
193
+ # @param tool_call_list [Array<OmniAI::Chat::ToolCall>]
194
+ # @return [Array<Message>]
195
+ def build_tool_call_messages(tool_call_list)
196
+ tool_call_list.map do |tool_call|
197
+ content = execute_tool_call(tool_call)
198
+ ToolCallMessage.new(content:, tool_call_id: tool_call.id)
199
+ end
200
+ end
201
+
202
+ # @raise [ToolCallError]
169
203
  # @param tool_call [OmniAI::Chat::ToolCall]
204
+ # @return [ToolCallResult]
170
205
  def execute_tool_call(tool_call)
171
- function = tool_call.function
206
+ logger&.debug("Chat#execute_tool_call tool_call=#{tool_call.inspect}")
172
207
 
173
- tool = @tools.find { |entry| function.name == entry.name } || raise(ToolCallLookupError, tool_call)
174
- result = tool.call(function.arguments)
208
+ function = tool_call.function
209
+ tool = @tools.find { |entry| function.name == entry.name } || raise(ToolCallMissingError, tool_call)
210
+ content = tool.call(function.arguments)
175
211
 
176
- prepare_tool_call_message(tool_call:, content: result)
177
- end
212
+ logger&.debug("Chat#execute_tool_call content=#{content.inspect}")
178
213
 
179
- # @param tool_call [OmniAI::Chat::ToolCall]
180
- # @param content [String]
181
- def prepare_tool_call_message(tool_call:, content:)
182
- {
183
- role: Role::TOOL,
184
- name: tool_call.function.name,
185
- tool_call_id: tool_call.id,
186
- content:,
187
- }
214
+ content
188
215
  end
189
216
  end
190
217
  end
@@ -39,5 +39,17 @@ module OmniAI
39
39
  @serializers = {}
40
40
  @deserializers = {}
41
41
  end
42
+
43
+ # @param name [Symbol]
44
+ # @return [Proc, nil]
45
+ def serializer(name)
46
+ @serializers[name]
47
+ end
48
+
49
+ # @param name [Symbol]
50
+ # @return [Proc, nil]
51
+ def deserializer(name)
52
+ @deserializers[name]
53
+ end
42
54
  end
43
55
  end
@@ -22,7 +22,7 @@ module OmniAI
22
22
  # @return [Usage]
23
23
  def usage
24
24
  @usage ||= begin
25
- deserializer = @context&.deserializers&.[](:usage)
25
+ deserializer = @context&.deserializer(:usage)
26
26
 
27
27
  if deserializer
28
28
  deserializer.call(@data, context: @context)
@@ -45,7 +45,7 @@ module OmniAI
45
45
  # @return [Array<Array<Float>>]
46
46
  def embeddings
47
47
  @embeddings ||= begin
48
- deserializer = @context&.deserializers&.[](:embeddings)
48
+ deserializer = @context&.deserializer(:embeddings)
49
49
 
50
50
  if deserializer
51
51
  deserializer.call(@data, context: @context)
@@ -24,10 +24,10 @@ module OmniAI
24
24
  end
25
25
 
26
26
  # @return [Hash]
27
- def prepare
27
+ def serialize
28
28
  {
29
29
  type: @type,
30
- properties: @properties.transform_values(&:prepare),
30
+ properties: @properties.transform_values(&:serialize),
31
31
  required: @required,
32
32
  }.compact
33
33
  end
@@ -60,7 +60,7 @@ module OmniAI
60
60
  end
61
61
 
62
62
  # @example
63
- # property.prepare
63
+ # property.serialize
64
64
  # # {
65
65
  # # type: 'string',
66
66
  # # description: 'The unit (e.g. "fahrenheit" or "celsius").'
@@ -68,7 +68,7 @@ module OmniAI
68
68
  # # }
69
69
  #
70
70
  # @return [Hash]
71
- def prepare
71
+ def serialize
72
72
  {
73
73
  type: @type,
74
74
  description: @description,
data/lib/omniai/tool.rb CHANGED
@@ -44,7 +44,7 @@ module OmniAI
44
44
  end
45
45
 
46
46
  # @example
47
- # tool.prepare
47
+ # tool.serialize
48
48
  # # {
49
49
  # # type: 'function',
50
50
  # # function: {
@@ -53,21 +53,28 @@ module OmniAI
53
53
  # # parameters: {
54
54
  # # type: 'object',
55
55
  # # properties: {
56
- # # n: OmniAI::Tool::Property.integer(description: 'The nth Fibonacci number to calculate')
56
+ # # n: {
57
+ # # description: 'The nth Fibonacci number to calculate')
58
+ # # type: 'integer'
59
+ # # }
57
60
  # # },
58
61
  # # required: ['n']
59
62
  # # }
60
63
  # # }
61
64
  # # }
62
65
  #
66
+ # @param context [Context] optional
63
67
  # @return [Hash]
64
- def prepare
68
+ def serialize(context: nil)
69
+ serialize = context&.serializer(:tool)
70
+ return serialize.call(self, context:) if serialize
71
+
65
72
  {
66
73
  type: 'function',
67
74
  function: {
68
75
  name: @name,
69
76
  description: @description,
70
- parameters: @parameters&.prepare,
77
+ parameters: @parameters.is_a?(Tool::Parameters) ? @parameters.serialize : @parameters,
71
78
  }.compact,
72
79
  }
73
80
  end
@@ -79,7 +86,7 @@ module OmniAI
79
86
  # @param args [Hash]
80
87
  # @return [String]
81
88
  def call(args = {})
82
- @function.call(**(@parameters ? @parameters.parse(args) : args))
89
+ @function.call(**(@parameters.is_a?(Tool::Parameters) ? @parameters.parse(args) : args))
83
90
  end
84
91
  end
85
92
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OmniAI
4
- VERSION = '1.7.0'
4
+ VERSION = '1.8.1'
5
5
  end