omniai-anthropic 1.9.8 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee6ca2cb64add94603589ca1cfb5b8e096042a0d60bdf6a00f4ee7288f52047a
4
- data.tar.gz: eb9eddc32df781539751b92a34ab502a3c0622fb2760583bb38191a2f704b659
3
+ metadata.gz: ed3bbf0fe24852928799eeeffc76163c4d45a68c2ae5070f46b57590e1da8b60
4
+ data.tar.gz: d330deaf3dfe93d3fd1d9c64b86656668708b3936f9bdbd6445abc3f7ed2bc98
5
5
  SHA512:
6
- metadata.gz: 892593c2136b1dd2f84b4523350030543811fd32b8991cdccca7f5a00fa8289ab1f38f708093d8ea81cd14c58014331323a9420c314a0a29480755fa69e49596
7
- data.tar.gz: 8fc61070743f464d2e136b68ed608457eff12624baffa4b59aadf32674cc74b5da6967c6858a11f436480575a2a5918e392cea216616128e6f4a1bb45ebd1419
6
+ metadata.gz: 92315dd5847588f9b344dc6111d5a0e5b4176cc72dba14c275de14d7cd0d7a2ba03a60608ba0d2cc8ba341cff566bcae838f762d807c0fdb2218cac1b3068650
7
+ data.tar.gz: 1db95ee64d3a6ad40f289a05dacc0d2154a0b0de016502864c4103834cc9a69568a591641bb4f94957cdf3ef93b147f5503776e228717ad55a6ac2b1869bb56f
@@ -6,6 +6,7 @@ module OmniAI
6
6
  # Overrides function serialize / deserialize.
7
7
  module FunctionSerializer
8
8
  # @param function [OmniAI::Chat::Function]
9
+ #
9
10
  # @return [Hash]
10
11
  def self.serialize(function, *)
11
12
  {
@@ -15,10 +16,12 @@ module OmniAI
15
16
  end
16
17
 
17
18
  # @param data [Hash]
19
+ #
18
20
  # @return [OmniAI::Chat::Function]
19
21
  def self.deserialize(data, *)
20
22
  name = data["name"]
21
23
  arguments = data["input"]
24
+
22
25
  OmniAI::Chat::Function.new(name:, arguments:)
23
26
  end
24
27
  end
@@ -5,7 +5,7 @@ module OmniAI
5
5
  class Chat
6
6
  # Overrides media serialize / deserialize.
7
7
  module MediaSerializer
8
- # @param payload [OmniAI::Chat::Media]
8
+ # @param media [OmniAI::Chat::Media]
9
9
  # @return [Hash]
10
10
  def self.serialize(media, *)
11
11
  {
@@ -7,10 +7,12 @@ module OmniAI
7
7
  module MessageSerializer
8
8
  # @param message [OmniAI::Chat::Message]
9
9
  # @param context [OmniAI::Context]
10
+ #
10
11
  # @return [Hash]
11
12
  def self.serialize(message, context:)
12
13
  role = message.role
13
- parts = arrayify(message.content) + arrayify(message.tool_call_list)
14
+ parts = arrayify(message.content) + arrayify(message.tool_call_list&.entries)
15
+
14
16
  content = parts.map do |part|
15
17
  case part
16
18
  when String then { type: "text", text: part }
@@ -23,6 +25,7 @@ module OmniAI
23
25
 
24
26
  # @param data [Hash]
25
27
  # @param context [OmniAI::Context]
28
+ #
26
29
  # @return [OmniAI::Chat::Message]
27
30
  def self.deserialize(data, context:)
28
31
  role = data["role"]
@@ -30,8 +33,11 @@ module OmniAI
30
33
  ContentSerializer.deserialize(content, context:)
31
34
  end
32
35
 
33
- tool_call_list = parts.select { |part| part.is_a?(OmniAI::Chat::ToolCall) }
34
- content = parts.reject { |part| part.is_a?(OmniAI::Chat::ToolCall) }
36
+ tool_call_parts = parts.select { |part| part.is_a?(OmniAI::Chat::ToolCall) }
37
+ non_tool_call_parts = parts.reject { |part| part.is_a?(OmniAI::Chat::ToolCall) }
38
+
39
+ tool_call_list = OmniAI::Chat::ToolCallList.new(entries: tool_call_parts) if tool_call_parts.any?
40
+ content = non_tool_call_parts if non_tool_call_parts.any?
35
41
 
36
42
  OmniAI::Chat::Message.new(content:, role:, tool_call_list:)
37
43
  end
@@ -3,26 +3,28 @@
3
3
  module OmniAI
4
4
  module Anthropic
5
5
  class Chat
6
- # Overrides payload serialize / deserialize.
7
- module PayloadSerializer
8
- # @param payload [OmniAI::Chat::Payload]
6
+ # Overrides response serialize / deserialize.
7
+ module ResponseSerializer
8
+ # @param response [OmniAI::Chat::Response]
9
9
  # @param context [OmniAI::Context]
10
+ #
10
11
  # @return [Hash]
11
- def self.serialize(payload, context:)
12
- usage = payload.usage.serialize(context:)
13
- choice = payload.choice.serialize(context:)
12
+ def self.serialize(response, context:)
13
+ usage = response.usage.serialize(context:)
14
+ choice = response.choice.serialize(context:)
14
15
 
15
16
  choice.merge({ usage: })
16
17
  end
17
18
 
18
19
  # @param data [Hash]
19
20
  # @param context [OmniAI::Context]
20
- # @return [OmniAI::Chat::Payload]
21
+ #
22
+ # @return [OmniAI::Chat::Response]
21
23
  def self.deserialize(data, context:)
22
24
  usage = OmniAI::Chat::Usage.deserialize(data["usage"], context:) if data["usage"]
23
25
  choice = OmniAI::Chat::Choice.deserialize(data, context:)
24
26
 
25
- OmniAI::Chat::Payload.new(choices: [choice], usage:)
27
+ OmniAI::Chat::Response.new(data:, choices: [choice], usage:)
26
28
  end
27
29
  end
28
30
  end
@@ -3,90 +3,156 @@
3
3
  module OmniAI
4
4
  module Anthropic
5
5
  class Chat
6
- # A stream given when streaming.
6
+ # Combine chunks into a hash. For each chunk yield the text (delta) if a block is given and the chunk is text.
7
7
  class Stream < OmniAI::Chat::Stream
8
8
  module Type
9
- PING = "ping"
10
9
  MESSAGE_START = "message_start"
11
- MESSAGE_STOP = "message_stop"
12
10
  MESSAGE_DELTA = "message_delta"
11
+ MESSAGE_STOP = "message_stop"
13
12
  CONTENT_BLOCK_START = "content_block_start"
14
- CONTENT_BLOCK_STOP = "content_block_stop"
15
13
  CONTENT_BLOCK_DELTA = "content_block_delta"
14
+ CONTENT_BLOCK_STOP = "content_block_stop"
16
15
  end
17
16
 
18
- # Process the stream into chunks by event.
19
- class Builder
20
- # @return [OmniAI::Chat::Payload, nil]
21
- def payload(context:)
22
- return unless @content
17
+ module ContentBlockType
18
+ TEXT = "text"
19
+ TOOL_USE = "tool_use"
20
+ end
23
21
 
24
- OmniAI::Chat::Payload.deserialize(@message.merge({
25
- "content" => @content,
26
- }), context:)
27
- end
22
+ module ContentBlockDeltaType
23
+ TEXT_DELTA = "text_delta"
24
+ INPUT_JSON_DELTA = "input_json_delta"
25
+ end
28
26
 
29
- # Handler for Type::MESSAGE_START
30
- #
31
- # @param data [Hash]
32
- def message_start(data)
33
- @message = data["message"]
34
- end
27
+ # @yield [delta]
28
+ # @yieldparam delta [OmniAI::Chat::Delta]
29
+ #
30
+ # @return [Hash]
31
+ def stream!(&block)
32
+ @data = { "content" => [] }
35
33
 
36
- # Handler for Type::MESSAGE_STOP
37
- #
38
- # @param _data [Hash]
39
- def message_stop(_data)
40
- @message = nil
34
+ @chunks.each do |chunk|
35
+ parser.feed(chunk) do |type, data, id|
36
+ process!(type, data, id, &block)
37
+ end
41
38
  end
42
39
 
43
- # Handler for Type::CONTENT_BLOCK_START
44
- #
45
- # @param data [Hash]
46
- def content_block_start(_data)
47
- @content = nil
48
- end
40
+ @data
41
+ end
49
42
 
50
- # Handler for Type::CONTENT_BLOCK_STOP
51
- #
52
- # @param _data [Hash]
53
- def content_block_stop(_data)
54
- @content = nil
43
+ protected
44
+
45
+ # @yield delta
46
+ # @yieldparam delta [OmniAI::Chat::Delta]
47
+ #
48
+ # @param type [String]
49
+ # @param data [Hash]
50
+ # @param id [String]
51
+ def process!(type, data, id, &)
52
+ log(type, data, id)
53
+
54
+ data = JSON.parse(data)
55
+
56
+ case data["type"]
57
+ when Type::MESSAGE_START then message_start(data)
58
+ when Type::MESSAGE_DELTA then message_delta(data)
59
+ when Type::MESSAGE_STOP then message_stop(data)
60
+ when Type::CONTENT_BLOCK_START then content_block_start(data)
61
+ when Type::CONTENT_BLOCK_DELTA then content_block_delta(data, &)
62
+ when Type::CONTENT_BLOCK_STOP then content_block_stop(data)
55
63
  end
64
+ end
56
65
 
57
- # Handler for Type::CONTENT_BLOCK_DELTA
58
- #
59
- # @param data [Hash]
60
- def content_block_delta(data)
61
- @content = [{ "type" => "text", "text" => data["delta"]["text"] }]
66
+ # Handler for Type::MESSAGE_START
67
+ #
68
+ # @param data [Hash]
69
+ def message_start(data)
70
+ @data = data["message"]
71
+ end
72
+
73
+ # Handler for Type::MESSAGE_DELTA
74
+ #
75
+ # @param data [Hash]
76
+ def message_delta(data)
77
+ @data.merge!(data["delta"])
78
+
79
+ input_tokens = data.dig("usage", "input_tokens")
80
+ output_tokens = data.dig("usage", "output_tokens")
81
+
82
+ @data["usage"] ||= {}
83
+ @data["usage"]["input_tokens"] = input_tokens if input_tokens
84
+ @data["usage"]["output_tokens"] = output_tokens if output_tokens
85
+ end
86
+
87
+ # Handler for Type::MESSAGE_STOP
88
+ #
89
+ # @param _data [Hash]
90
+ def message_stop(_data)
91
+ # NOOP
92
+ end
93
+
94
+ # Handler for Type::CONTENT_BLOCK_START
95
+ #
96
+ # @param data [Hash]
97
+ def content_block_start(data)
98
+ index = data["index"]
99
+ @data["content"][index] = data["content_block"]
100
+ end
101
+
102
+ # Handler for Type::CONTENT_BLOCK_DELTA
103
+ #
104
+ # @yield [delta]
105
+ # @yieldparam delta [OmniAI::Chat::Delta]
106
+ #
107
+ # @param data [Hash]
108
+ def content_block_delta(data, &)
109
+ case data["delta"]["type"]
110
+ when ContentBlockDeltaType::TEXT_DELTA
111
+ content_block_delta_for_text_delta(data, &)
112
+ when ContentBlockDeltaType::INPUT_JSON_DELTA
113
+ content_block_delta_for_input_json_delta(data, &)
62
114
  end
63
115
  end
64
116
 
65
- protected
117
+ # Handler for Type::CONTENT_BLOCK_DELTA w/ ContentBlockDeltaType::TEXT_DELTA
118
+ #
119
+ # @yield [delta]
120
+ # @yieldparam delta [OmniAI::Chat::Delta]
121
+ #
122
+ # @param data [Hash]
123
+ def content_block_delta_for_text_delta(data, &block)
124
+ index = data["index"]
125
+ text = data["delta"]["text"]
126
+
127
+ content = @data["content"][index]
128
+ content["text"] += data["delta"]["text"]
66
129
 
67
- def builder
68
- @builder ||= Builder.new
130
+ block&.call(OmniAI::Chat::Delta.new(text: text))
69
131
  end
70
132
 
71
- # @param type [String]
133
+ # Handler for Type::CONTENT_BLOCK_DELTA w/ ContentBlockDeltaType::INPUT_JSON_DELTA
134
+ #
135
+ # @yield [delta]
136
+ # @yieldparam delta [OmniAI::Chat::Delta]
137
+ #
72
138
  # @param data [Hash]
73
- # @param builder [Builder]
74
- def process!(type, data, id, &block)
75
- log(type, data, id)
139
+ def content_block_delta_for_input_json_delta(data)
140
+ content = @data["content"][data["index"]]
141
+ content["partial_json"] ||= ""
142
+ content["partial_json"] += data["delta"]["partial_json"]
143
+ end
76
144
 
77
- data = JSON.parse(data)
145
+ # Handler for Type::CONTENT_BLOCK_STOP
146
+ #
147
+ # @param _data [Hash]
148
+ def content_block_stop(data)
149
+ index = data["index"]
150
+ content = @data["content"][index]
78
151
 
79
- case type
80
- when Type::MESSAGE_START then builder.message_start(data)
81
- when Type::CONTENT_BLOCK_START then builder.content_block_start(data)
82
- when Type::CONTENT_BLOCK_STOP then builder.content_block_stop(data)
83
- when Type::MESSAGE_STOP then builder.message_stop(data)
84
- when Type::CONTENT_BLOCK_DELTA
85
- builder.content_block_delta(data)
152
+ return unless content["partial_json"]
86
153
 
87
- payload = builder.payload(context: @context)
88
- block.call(payload) if payload
89
- end
154
+ content["input"] = JSON.parse(content["partial_json"])
155
+ content.delete("partial_json")
90
156
  end
91
157
  end
92
158
  end
@@ -61,7 +61,7 @@ module OmniAI
61
61
  context.deserializers[:message] = MessageSerializer.method(:deserialize)
62
62
 
63
63
  context.deserializers[:content] = ContentSerializer.method(:deserialize)
64
- context.deserializers[:payload] = PayloadSerializer.method(:deserialize)
64
+ context.deserializers[:response] = ResponseSerializer.method(:deserialize)
65
65
  end
66
66
 
67
67
  # @return [Hash]
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OmniAI
4
4
  module Anthropic
5
- VERSION = "1.9.8"
5
+ VERSION = "2.0.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniai-anthropic
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.8
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Sylvestre
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-02-27 00:00:00.000000000 Z
10
+ date: 2025-03-03 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: event_stream_parser
@@ -29,14 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '1.9'
32
+ version: '2.0'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '1.9'
39
+ version: '2.0'
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: zeitwerk
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -67,7 +67,7 @@ files:
67
67
  - lib/omniai/anthropic/chat/function_serializer.rb
68
68
  - lib/omniai/anthropic/chat/media_serializer.rb
69
69
  - lib/omniai/anthropic/chat/message_serializer.rb
70
- - lib/omniai/anthropic/chat/payload_serializer.rb
70
+ - lib/omniai/anthropic/chat/response_serializer.rb
71
71
  - lib/omniai/anthropic/chat/stream.rb
72
72
  - lib/omniai/anthropic/chat/text_serializer.rb
73
73
  - lib/omniai/anthropic/chat/tool_call_result_serializer.rb