intelligence 1.0.0.beta05 → 1.0.0.beta06

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: c0a3f15cdc1061405db03b0b8039b4867b16e70458d1a896a3d7cec542870cf5
4
- data.tar.gz: 2914681b64f2295b7fbd9a4e4edb3425f53d23ec3a3dd3da0639df101a2fe31f
3
+ metadata.gz: 2882de87c5b4c5e0ce5c59b91ca981788b1435b4e8b68e82c592d67b5b33512e
4
+ data.tar.gz: 92072e12828beec46b8d988c0b4e6ddaa9aac1d9d4cee7fbdf1d2c02cb8ccc83
5
5
  SHA512:
6
- metadata.gz: 181c33085c1c608f8d35b39332514e0a4028124e85309f4e95fddb5913b43490333298ffa8d935f3cc447d682968e04816c7d5497967d1700daa4d575dbd6425
7
- data.tar.gz: abf03a55cf8c5f1ff5d0c97bc4d6918327f240cbc1f2137338726611c447fdd32cabf2cc15ba7f0f40b6cb122d35f9e0dec092add65c10b40044f248c2535d97
6
+ metadata.gz: 0a80eedb98958100ca842a66352bb43f68123285a3abc503b1a563e9161e237f7bf095541e22272ae4e73c7d738c9ad7aa23ee06d6ee76d4f7059d2c72b98041
7
+ data.tar.gz: b0d7a59ade65eb893e976875d42075aacf8edfe32eada98af10de1ab39a8145913f37cb830a09498345d01cd59e1fc6e8eb511efe72e3e9976fc48c0dd2bec41
@@ -0,0 +1,47 @@
1
+ require_relative '../../adapter'
2
+ require_relative 'chat_request_methods'
3
+ require_relative 'chat_response_methods'
4
+
5
+ module Intelligence
6
+ module Deepseek
7
+ class Adapter < Adapter::Base
8
+ include ChatRequestMethods
9
+ include ChatResponseMethods
10
+
11
+ chat_request_uri 'https://api.deepseek.com/v1/chat/completions'
12
+
13
+ schema do
14
+ key String
15
+ chat_options do
16
+ frequency_penalty Float
17
+ logprobs [ TrueClass, FalseClass ]
18
+ max_tokens Integer
19
+ model String
20
+ presence_penalty Float, in: [ -2..2 ]
21
+ response_format do
22
+ # 'text' and 'json_object' are the only supported types; you must also instruct
23
+ # the model to output json
24
+ type Symbol, in: [ :text, :json_object ]
25
+ end
26
+ stop String, array: true
27
+ stream [ TrueClass, FalseClass ]
28
+ stream_options do
29
+ include_usage [ TrueClass, FalseClass ]
30
+ end
31
+ temperature Float, in: [ 0..2 ]
32
+ tool array: true, as: :tools, &Tool.schema
33
+ tool_choice do
34
+ # one of 'auto', 'none' or 'function'
35
+ type Symbol, in: [ :auto, :none, :required ]
36
+ # the function parameters is required if you specify a type of 'function'
37
+ function do
38
+ name String
39
+ end
40
+ end
41
+ top_logprobs Integer
42
+ top_p Float
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,233 @@
1
+ module Intelligence
2
+ module Deepseek
3
+ module ChatRequestMethods
4
+
5
+ module ClassMethods
6
+ def chat_request_uri( uri = nil )
7
+ if uri
8
+ @chat_request_uri = uri
9
+ else
10
+ @chat_request_uri
11
+ end
12
+ end
13
+ end
14
+
15
+ CHAT_COMPLETIONS_PATH = 'chat/completions'
16
+
17
+ def self.included( base )
18
+ base.extend( ClassMethods )
19
+ end
20
+
21
+ def chat_request_uri( options = nil )
22
+ options = merge_options( @options, build_options( options ) )
23
+ self.class.chat_request_uri || begin
24
+ base_uri = options[ :base_uri ]
25
+ if base_uri
26
+ # because URI join is dumb
27
+ base_uri += '/' unless base_uri.end_with?( '/' )
28
+ URI.join( base_uri, CHAT_COMPLETIONS_PATH )
29
+ else
30
+ nil
31
+ end
32
+ end
33
+ end
34
+
35
+ def chat_request_headers( options = nil )
36
+ options = merge_options( @options, build_options( options ) )
37
+ result = { 'Content-Type': 'application/json' }
38
+
39
+ key = options[ :key ]
40
+ result[ 'Authorization' ] = "Bearer #{key}" if key
41
+
42
+ result
43
+ end
44
+
45
+ def chat_request_body( conversation, options = nil )
46
+ tools = options.delete( :tools ) || []
47
+
48
+ options = merge_options( @options, build_options( options ) )
49
+
50
+ result = options[ :chat_options ]
51
+ result[ :messages ] = []
52
+
53
+ system_message = chat_request_system_message_attributes( conversation[ :system_message ] )
54
+ result[ :messages ] << system_message if system_message
55
+
56
+ conversation[ :messages ]&.each do | message |
57
+ return nil unless message[ :contents ]&.any?
58
+
59
+ result_message = { role: message[ :role ] }
60
+ result_message_content = []
61
+
62
+ message_contents = message[ :contents ]
63
+
64
+ # tool calls in the open ai api are not content
65
+ tool_calls, message_contents = message_contents.partition do | content |
66
+ content[ :type ] == :tool_call
67
+ end
68
+
69
+ # tool results in the open ai api are not content
70
+ tool_results, message_contents = message_contents.partition do | content |
71
+ content[ :type ] == :tool_result
72
+ end
73
+
74
+ # many vendor api's, especially when hosting text only models, will only accept a single
75
+ # text content item; if the content is only text this will coalece multiple text content
76
+ # items into a single content item
77
+ unless message_contents.any? { | c | c[ :type ] != :text }
78
+ result_message_content = message_contents.map { | c | c[ :text ] || '' }.join( "\n" )
79
+ else
80
+ message_contents&.each do | content |
81
+ result_message_content << chat_request_message_content_attributes( content )
82
+ end
83
+ end
84
+
85
+ if tool_calls.any?
86
+ result_message[ :tool_calls ] = tool_calls.map { | tool_call |
87
+ {
88
+ id: tool_call[ :tool_call_id ],
89
+ type: 'function',
90
+ function: {
91
+ name: tool_call[ :tool_name ],
92
+ arguments: JSON.generate( tool_call[ :tool_parameters ] || {} )
93
+ }
94
+ }
95
+ }
96
+ end
97
+
98
+ result_message[ :content ] = result_message_content
99
+ unless result_message_content.empty? && tool_calls.empty?
100
+ result[ :messages ] << result_message
101
+ end
102
+
103
+ if tool_results.any?
104
+ result[ :messages ].concat( tool_results.map { | tool_result |
105
+ {
106
+ role: :tool,
107
+ tool_call_id: tool_result[ :tool_call_id ],
108
+ content: tool_result[ :tool_result ]
109
+ }
110
+ } )
111
+ end
112
+ end
113
+
114
+ tools_attributes = chat_request_tools_attributes(
115
+ ( result[ :tools ] || [] ).concat( tools )
116
+ )
117
+ result[ :tools ] = tools_attributes if tools_attributes && tools_attributes.length > 0
118
+
119
+ JSON.generate( result )
120
+ end
121
+
122
+ def chat_request_message_content_attributes( content )
123
+ case content[ :type ]
124
+ when :text
125
+ { type: 'text', text: content[ :text ] }
126
+ when :binary
127
+ content_type = content[ :content_type ]
128
+ bytes = content[ :bytes ]
129
+ if content_type && bytes
130
+ mime_type = MIME::Types[ content_type ].first
131
+ if mime_type&.media_type == 'image'
132
+ {
133
+ type: 'image_url',
134
+ image_url: {
135
+ url: "data:#{content_type};base64,#{Base64.strict_encode64( bytes )}".freeze
136
+ }
137
+ }
138
+ else
139
+ raise UnsupportedContentError.new(
140
+ :generic,
141
+ 'only support content of type image/*'
142
+ )
143
+ end
144
+ else
145
+ raise UnsupportedContentError.new(
146
+ :generic,
147
+ 'requires binary content to include content type and ( packed ) bytes'
148
+ )
149
+ end
150
+ when :file
151
+ content_type = content[ :content_type ]
152
+ uri = content[ :uri ]
153
+ if content_type && uri
154
+ mime_type = MIME::Types[ content_type ].first
155
+ if mime_type&.media_type == 'image'
156
+ {
157
+ type: 'image_url',
158
+ image_url: { url: uri }
159
+ }
160
+ else
161
+ raise UnsupportedContentError.new(
162
+ :generic,
163
+ 'only support content of type image/*'
164
+ )
165
+ end
166
+ else
167
+ raise UnsupportedContentError.new(
168
+ :generic,
169
+ 'requires binary content to include content type and ( packed ) bytes'
170
+ )
171
+ end
172
+ end
173
+ end
174
+
175
+ def chat_request_system_message_attributes( system_message )
176
+ return nil if system_message.nil?
177
+
178
+ result = ''
179
+ system_message[ :contents ].each do | content |
180
+ result += content[ :text ] if content[ :type ] == :text
181
+ end
182
+
183
+ result.empty? ? nil : { role: 'system', content: result } if system_message
184
+ end
185
+
186
+ def chat_request_tools_attributes( tools )
187
+ properties_array_to_object = lambda do | properties |
188
+ return nil unless properties&.any?
189
+ object = {}
190
+ required = []
191
+ properties.each do | property |
192
+ name = property.delete( :name )
193
+ required << name if property.delete( :required )
194
+ if property[ :properties ]&.any?
195
+ property_properties, property_required =
196
+ properties_array_to_object.call( property[ :properties ] )
197
+ property[ :properties ] = property_properties
198
+ property[ :required ] = property_required if property_required.any?
199
+ end
200
+ object[ name ] = property
201
+ end
202
+ [ object, required.compact ]
203
+ end
204
+
205
+ tools&.map do | tool |
206
+ function = {
207
+ type: 'function',
208
+ function: {
209
+ type: 'object',
210
+ name: tool[ :name ],
211
+ description: tool[ :description ],
212
+ }
213
+ }
214
+
215
+ if tool[ :properties ]&.any?
216
+ properties_object, properties_required =
217
+ properties_array_to_object.call( tool[ :properties ] )
218
+ function[ :function ][ :parameters ] = {
219
+ type: 'object',
220
+ properties: properties_object
221
+ }
222
+ function[ :function ][ :parameters ][ :required ] = properties_required \
223
+ if properties_required.any?
224
+ else
225
+ # function[ :function ][ :parameters ] = {}
226
+ end
227
+ function
228
+ end
229
+ end
230
+
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,248 @@
1
+ module Intelligence
2
+ module Deepseek
3
+ module ChatResponseMethods
4
+
5
+ def chat_result_attributes( response )
6
+ return nil unless response.success?
7
+ response_json = JSON.parse( response.body, symbolize_names: true ) rescue nil
8
+ return nil if response_json.nil? || response_json[ :choices ].nil?
9
+
10
+ result = {}
11
+ result[ :choices ] = []
12
+
13
+ ( response_json[ :choices ] || [] ).each do | json_choice |
14
+ if ( json_message = json_choice[ :message ] )
15
+ result_message = { role: json_message[ :role ] }
16
+ if json_message[ :content ]
17
+ result_message[ :contents ] = [ { type: :text, text: json_message[ :content ] } ]
18
+ end
19
+ if json_message[ :tool_calls ] && !json_message[ :tool_calls ].empty?
20
+ result_message[ :contents ] ||= []
21
+ json_message[ :tool_calls ].each do | json_message_tool_call |
22
+ result_message_tool_call_parameters =
23
+ JSON.parse( json_message_tool_call[ :function ][ :arguments ], symbolize_names: true ) \
24
+ rescue json_message_tool_call[ :function ][ :arguments ]
25
+ result_message[ :contents ] << {
26
+ type: :tool_call,
27
+ tool_call_id: json_message_tool_call[ :id ],
28
+ tool_name: json_message_tool_call[ :function ][ :name ],
29
+ tool_parameters: result_message_tool_call_parameters
30
+ }
31
+ end
32
+ end
33
+ end
34
+ result[ :choices ].push( {
35
+ end_reason: to_end_reason( json_choice[ :finish_reason ] ),
36
+ message: result_message
37
+ } )
38
+ end
39
+
40
+ metrics_json = response_json[ :usage ]
41
+ unless metrics_json.nil?
42
+
43
+ metrics = {}
44
+ metrics[ :input_tokens ] = metrics_json[ :prompt_tokens ]
45
+ metrics[ :output_tokens ] = metrics_json[ :completion_tokens ]
46
+ metrics = metrics.compact
47
+
48
+ result[ :metrics ] = metrics unless metrics.empty?
49
+
50
+ end
51
+
52
+ result
53
+ end
54
+
55
+ def chat_result_error_attributes( response )
56
+ error_type, error_description = to_error_response( response.status )
57
+ error = error_type
58
+
59
+ parsed_body = JSON.parse( response.body, symbolize_names: true ) rescue nil
60
+ if parsed_body && parsed_body.respond_to?( :[] )
61
+ if parsed_body[ :error ].respond_to?( :[] )
62
+ error = parsed_body[ :error ][ :code ] || error_type
63
+ error_description = parsed_body[ :error ][ :message ] || error_description
64
+ elsif parsed_body[ :object ] == 'error'
65
+ error = parsed_body[ :type ] || error_type
66
+ error_description = parsed_body[ :detail ] || parsed_body[ :message ]
67
+ end
68
+ end
69
+
70
+ { error_type: error_type.to_s, error: error.to_s, error_description: error_description }
71
+ end
72
+
73
+ def stream_result_chunk_attributes( context, chunk )
74
+ context ||= {}
75
+ buffer = context[ :buffer ] || ''
76
+ metrics = context[ :metrics ] || {
77
+ input_tokens: 0,
78
+ output_tokens: 0
79
+ }
80
+ choices = context[ :choices ] || Array.new( 1 , { message: {} } )
81
+
82
+ choices.each do | choice |
83
+ choice[ :message ][ :contents ] = choice[ :message ][ :contents ]&.map do | content |
84
+ { type: content[ :type ] }
85
+ end
86
+ end
87
+
88
+
89
+ buffer += chunk
90
+ while ( eol_index = buffer.index( "\n" ) )
91
+ line = buffer.slice!( 0..eol_index )
92
+ line = line.strip
93
+ next if line.empty? || !line.start_with?( 'data:' )
94
+ line = line[ 6..-1 ]
95
+ next if line.end_with?( '[DONE]' )
96
+
97
+ data = JSON.parse( line ) rescue nil
98
+ if data.is_a?( Hash )
99
+ data[ 'choices' ]&.each do | data_choice |
100
+
101
+ data_choice_index = data_choice[ 'index' ]
102
+ data_choice_delta = data_choice[ 'delta' ]
103
+ data_choice_finish_reason = data_choice[ 'finish_reason' ]
104
+
105
+ choices.fill( { message: {} }, choices.size, data_choice_index + 1 ) \
106
+ if choices.size <= data_choice_index
107
+ contents = choices[ data_choice_index ][ :message ][ :contents ] || []
108
+
109
+ # this deepseek response doesn't place reasoning in a separate index ( presumably
110
+ # because they are simply detecting the thought tag in the first text )
111
+
112
+ if data_choice_content = data_choice_delta[ 'reasoning_content' ]
113
+ thought_content = contents.find { | content | content[ :type ] == :thought }
114
+ if thought_content.nil?
115
+ thought_content = { type: :thought, text: data_choice_content }
116
+ contents << thought_content
117
+ else
118
+ thought_content[ :text ] = ( thought_content[ :text ] || '' ) + data_choice_content
119
+ end
120
+ end
121
+
122
+ if data_choice_content = data_choice_delta[ 'content' ]
123
+ text_content = contents.find { | content | content[ :type ] == :text }
124
+ if text_content.nil?
125
+ text_content = { type: :text, text: data_choice_content }
126
+ contents << text_content
127
+ else
128
+ text_content[ :text ] = ( text_content[ :text ] || '' ) + data_choice_content
129
+ end
130
+ end
131
+
132
+ if data_choice_tool_calls = data_choice_delta[ 'tool_calls' ]
133
+ data_choice_tool_calls.each_with_index do | data_choice_tool_call, data_choice_tool_call_index |
134
+ if data_choice_tool_call_function = data_choice_tool_call[ 'function' ]
135
+ data_choice_tool_index = data_choice_tool_call[ 'index' ] || data_choice_tool_call_index
136
+ data_choice_tool_id = data_choice_tool_call[ 'id' ]
137
+ data_choice_tool_name = data_choice_tool_call_function[ 'name' ]
138
+ data_choice_tool_parameters = data_choice_tool_call_function[ 'arguments' ]
139
+
140
+ if data_choice_tool_id
141
+ contents.push( {
142
+ type: :tool_call,
143
+ tool_call_id: data_choice_tool_id,
144
+ tool_name: data_choice_tool_name,
145
+ tool_parameters: data_choice_tool_parameters
146
+ } )
147
+ else
148
+ tool_call_content_index = contents.rindex do | content |
149
+ content[ :type ] == :tool_call
150
+ end
151
+ tool_call = contents[ tool_call_content_index ]
152
+ tool_call[ :tool_parameters ] = ( tool_call[ :tool_parameters ] || '' ) + data_choice_tool_parameters \
153
+ if data_choice_tool_parameters
154
+ end
155
+ end
156
+ end
157
+ end
158
+ choices[ data_choice_index ][ :message ][ :contents ] = contents
159
+ choices[ data_choice_index ][ :end_reason ] ||=
160
+ to_end_reason( data_choice_finish_reason )
161
+ end
162
+
163
+ if usage = data[ 'usage' ]
164
+ # note: A number of providers will resend the input tokens as part of their usage
165
+ # payload.
166
+ metrics[ :input_tokens ] = usage[ 'prompt_tokens' ] \
167
+ if usage.include?( 'prompt_tokens' )
168
+ metrics[ :output_tokens ] += usage[ 'completion_tokens' ] \
169
+ if usage.include?( 'completion_tokens' )
170
+ end
171
+
172
+ end
173
+
174
+ end
175
+
176
+ context[ :buffer ] = buffer
177
+ context[ :metrics ] = metrics
178
+ context[ :choices ] = choices
179
+
180
+ [ context, choices.empty? ? nil : { choices: choices.dup } ]
181
+ end
182
+
183
+ def stream_result_attributes( context )
184
+ choices = context[ :choices ]
185
+ metrics = context[ :metrics ]
186
+
187
+ choices = choices.map do | choice |
188
+ { end_reason: choice[ :end_reason ] }
189
+ end
190
+
191
+ { choices: choices, metrics: context[ :metrics ] }
192
+ end
193
+
194
+ alias_method :stream_result_error_attributes, :chat_result_error_attributes
195
+
196
+ def to_end_reason( finish_reason )
197
+ case finish_reason
198
+ when 'stop'
199
+ :ended
200
+ when 'length'
201
+ :token_limit_exceeded
202
+ when 'tool_calls'
203
+ :tool_called
204
+ when 'content_filter'
205
+ :filtered
206
+ else
207
+ nil
208
+ end
209
+ end
210
+
211
+ def to_error_response( status )
212
+ case status
213
+ when 400
214
+ [ :invalid_request_error,
215
+ "There was an issue with the format or content of your request." ]
216
+ when 401
217
+ [ :authentication_error,
218
+ "There's an issue with your API key." ]
219
+ when 403
220
+ [ :permission_error,
221
+ "Your API key does not have permission to use the specified resource." ]
222
+ when 404
223
+ [ :not_found_error,
224
+ "The requested resource was not found." ]
225
+ when 413
226
+ [ :request_too_large,
227
+ "Request exceeds the maximum allowed number of bytes." ]
228
+ when 422
229
+ [ :invalid_request_error,
230
+ "There was an issue with the format or content of your request." ]
231
+ when 429
232
+ [ :rate_limit_error,
233
+ "Your account has hit a rate limit." ]
234
+ when 500, 502, 503
235
+ [ :api_error,
236
+ "An unexpected error has occurred internal to the providers systems." ]
237
+ when 529
238
+ [ :overloaded_error,
239
+ "The providers server is temporarily overloaded." ]
240
+ else
241
+ [ :unknown_error, "
242
+ An unknown error occurred." ]
243
+ end
244
+ end
245
+
246
+ end
247
+ end
248
+ end
@@ -1,44 +1 @@
1
- require_relative 'generic/adapter'
2
-
3
- module Intelligence
4
- module Deepseek
5
- class Adapter < Generic::Adapter
6
-
7
- chat_request_uri 'https://api.deepseek.com/v1/chat/completions'
8
-
9
- schema do
10
- key String
11
- chat_options do
12
- frequency_penalty Float
13
- logprobs [ TrueClass, FalseClass ]
14
- max_tokens Integer
15
- model String
16
- presence_penalty Float, in: [ -2..2 ]
17
- response_format do
18
- # 'text' and 'json_object' are the only supported types; you must also instruct
19
- # the model to output json
20
- type Symbol, in: [ :text, :json_object ]
21
- end
22
- stop String, array: true
23
- stream [ TrueClass, FalseClass ]
24
- stream_options do
25
- include_usage [ TrueClass, FalseClass ]
26
- end
27
- temperature Float, in: [ 0..2 ]
28
- tool array: true, as: :tools, &Tool.schema
29
- tool_choice do
30
- # one of 'auto', 'none' or 'function'
31
- type Symbol, in: [ :auto, :none, :required ]
32
- # the function parameters is required if you specify a type of 'function'
33
- function do
34
- name String
35
- end
36
- end
37
- top_logprobs Integer
38
- top_p Float
39
- end
40
- end
41
-
42
- end
43
- end
44
- end
1
+ require_relative 'deepseek/adapter'
@@ -71,6 +71,7 @@ module Intelligence
71
71
  end
72
72
 
73
73
  def stream_result_chunk_attributes( context, chunk )
74
+
74
75
  context ||= {}
75
76
  buffer = context[ :buffer ] || ''
76
77
  metrics = context[ :metrics ] || {
@@ -99,20 +100,21 @@ module Intelligence
99
100
 
100
101
  data_choice_index = data_choice[ 'index' ]
101
102
  data_choice_delta = data_choice[ 'delta' ]
102
- data_choice_finish_reason = data_choice[ 'finish_reason' ]
103
+ end_reason = to_end_reason( data_choice[ 'finish_reason' ] )
103
104
 
104
105
  choices.fill( { message: {} }, choices.size, data_choice_index + 1 ) \
105
106
  if choices.size <= data_choice_index
106
107
  contents = choices[ data_choice_index ][ :message ][ :contents ] || []
107
108
 
108
- text_content = contents.first&.[]( :type ) == :text ? contents.first : nil
109
109
  if data_choice_content = data_choice_delta[ 'content' ]
110
+ text_content = contents.last&.[]( :type ) == :text ? contents.last : nil
110
111
  if text_content.nil?
111
- contents.unshift( text_content = { type: :text, text: data_choice_content } )
112
+ contents.push( text_content = { type: :text, text: data_choice_content } )
112
113
  else
113
114
  text_content[ :text ] = ( text_content[ :text ] || '' ) + data_choice_content
114
115
  end
115
116
  end
117
+
116
118
  if data_choice_tool_calls = data_choice_delta[ 'tool_calls' ]
117
119
  data_choice_tool_calls.each_with_index do | data_choice_tool_call, data_choice_tool_call_index |
118
120
  if data_choice_tool_call_function = data_choice_tool_call[ 'function' ]
@@ -121,29 +123,29 @@ module Intelligence
121
123
  data_choice_tool_name = data_choice_tool_call_function[ 'name' ]
122
124
  data_choice_tool_parameters = data_choice_tool_call_function[ 'arguments' ]
123
125
 
124
- tool_call_content_index = ( text_content.nil? ? 0 : 1 ) + data_choice_tool_index
125
- if tool_call_content_index >= contents.length
126
+ # if the data_choice_tool_id is present this indicates a new tool call.
127
+ if data_choice_tool_id
126
128
  contents.push( {
127
129
  type: :tool_call,
128
130
  tool_call_id: data_choice_tool_id,
129
131
  tool_name: data_choice_tool_name,
130
132
  tool_parameters: data_choice_tool_parameters
131
133
  } )
134
+ # otherwise the tool is being aggregated
132
135
  else
136
+ tool_call_content_index = contents.rindex do | content |
137
+ content[ :type ] == :tool_call
138
+ end
133
139
  tool_call = contents[ tool_call_content_index ]
134
- tool_call[ :tool_call_id ] = ( tool_call[ :tool_call_id ] || '' ) + data_choice_tool_id \
135
- if data_choice_tool_id
136
- tool_call[ :tool_name ] = ( tool_call[ :tool_name ] || '' ) + data_choice_tool_name \
137
- if data_choice_tool_name
138
140
  tool_call[ :tool_parameters ] = ( tool_call[ :tool_parameters ] || '' ) + data_choice_tool_parameters \
139
141
  if data_choice_tool_parameters
140
142
  end
141
143
  end
142
144
  end
143
145
  end
146
+
144
147
  choices[ data_choice_index ][ :message ][ :contents ] = contents
145
- choices[ data_choice_index ][ :end_reason ] ||=
146
- to_end_reason( data_choice_finish_reason )
148
+ choices[ data_choice_index ][ :end_reason ] ||= end_reason
147
149
  end
148
150
 
149
151
  if usage = data[ 'usage' ]
@@ -164,6 +166,7 @@ module Intelligence
164
166
  context[ :choices ] = choices
165
167
 
166
168
  [ context, choices.empty? ? nil : { choices: choices.dup } ]
169
+
167
170
  end
168
171
 
169
172
  def stream_result_attributes( context )
@@ -5,10 +5,13 @@ module Intelligence
5
5
 
6
6
  class Adapter < Generic::Adapter
7
7
 
8
- chat_request_uri "https://api.x.ai/v1/chat/completions"
9
-
8
+ DEFAULT_BASE_URI = "https://api.x.ai/v1"
9
+
10
10
  schema do
11
+
12
+ base_uri String, default: DEFAULT_BASE_URI
11
13
  key String
14
+
12
15
  chat_options do
13
16
  model String
14
17
  frequency_penalty Float, in: -2..2
@@ -31,6 +34,7 @@ module Intelligence
31
34
  top_p Float
32
35
 
33
36
  user String
37
+
34
38
  end
35
39
  end
36
40
 
@@ -83,102 +87,6 @@ module Intelligence
83
87
  result
84
88
  end
85
89
 
86
- def stream_result_chunk_attributes( context, chunk )
87
- context ||= {}
88
- buffer = context[ :buffer ] || ''
89
- metrics = context[ :metrics ] || {
90
- input_tokens: 0,
91
- output_tokens: 0
92
- }
93
- choices = context[ :choices ] || Array.new( 1 , { message: {} } )
94
-
95
- choices.each do | choice |
96
- choice[ :message ][ :contents ] = choice[ :message ][ :contents ]&.map do | content |
97
- { type: content[ :type ] }
98
- end
99
- end
100
-
101
- buffer += chunk
102
- while ( eol_index = buffer.index( "\n" ) )
103
- line = buffer.slice!( 0..eol_index )
104
- line = line.strip
105
- next if line.empty? || !line.start_with?( 'data:' )
106
- line = line[ 6..-1 ]
107
- next if line.end_with?( '[DONE]' )
108
-
109
- data = JSON.parse( line ) rescue nil
110
- if data.is_a?( Hash )
111
- data[ 'choices' ]&.each do | data_choice |
112
-
113
- data_choice_index = data_choice[ 'index' ]
114
- data_choice_delta = data_choice[ 'delta' ]
115
- end_reason = to_end_reason( data_choice[ 'finish_reason' ] )
116
-
117
- choices.fill( { message: {} }, choices.size, data_choice_index + 1 ) \
118
- if choices.size <= data_choice_index
119
- contents = choices[ data_choice_index ][ :message ][ :contents ] || []
120
-
121
- text_content = contents.first&.[]( :type ) == :text ? contents.first : nil
122
- if data_choice_content = data_choice_delta[ 'content' ]
123
- if text_content.nil?
124
- contents.unshift( text_content = { type: :text, text: data_choice_content } )
125
- else
126
- text_content[ :text ] = ( text_content[ :text ] || '' ) + data_choice_content
127
- end
128
- end
129
- if data_choice_tool_calls = data_choice_delta[ 'tool_calls' ]
130
- end_reason = :tool_called
131
- data_choice_tool_calls.each_with_index do | data_choice_tool_call, data_choice_tool_call_index |
132
- if data_choice_tool_call_function = data_choice_tool_call[ 'function' ]
133
- data_choice_tool_index = data_choice_tool_call[ 'index' ] || data_choice_tool_call_index
134
- data_choice_tool_id = data_choice_tool_call[ 'id' ]
135
- data_choice_tool_name = data_choice_tool_call_function[ 'name' ]
136
- data_choice_tool_parameters = data_choice_tool_call_function[ 'arguments' ]
137
-
138
- tool_call_content_index = ( text_content.nil? ? 0 : 1 ) + data_choice_tool_index
139
- if tool_call_content_index >= contents.length
140
- contents.push( {
141
- type: :tool_call,
142
- tool_call_id: data_choice_tool_id,
143
- tool_name: data_choice_tool_name,
144
- tool_parameters: data_choice_tool_parameters
145
- } )
146
- else
147
- tool_call = contents[ tool_call_content_index ]
148
- tool_call[ :tool_call_id ] = ( tool_call[ :tool_call_id ] || '' ) + data_choice_tool_id \
149
- if data_choice_tool_id
150
- tool_call[ :tool_name ] = ( tool_call[ :tool_name ] || '' ) + data_choice_tool_name \
151
- if data_choice_tool_name
152
- tool_call[ :tool_parameters ] = ( tool_call[ :tool_parameters ] || '' ) + data_choice_tool_parameters \
153
- if data_choice_tool_parameters
154
- end
155
- end
156
- end
157
- end
158
- choices[ data_choice_index ][ :message ][ :contents ] = contents
159
- choices[ data_choice_index ][ :end_reason ] ||= end_reason
160
- end
161
-
162
- if usage = data[ 'usage' ]
163
- # note: A number of providers will resend the input tokens as part of their usage
164
- # payload.
165
- metrics[ :input_tokens ] = usage[ 'prompt_tokens' ] \
166
- if usage.include?( 'prompt_tokens' )
167
- metrics[ :output_tokens ] += usage[ 'completion_tokens' ] \
168
- if usage.include?( 'completion_tokens' )
169
- end
170
-
171
- end
172
-
173
- end
174
-
175
- context[ :buffer ] = buffer
176
- context[ :metrics ] = metrics
177
- context[ :choices ] = choices
178
-
179
- [ context, choices.empty? ? nil : { choices: choices.dup } ]
180
- end
181
-
182
90
  def chat_result_error_attributes( response )
183
91
  error_type, error_description = to_error_response( response.status )
184
92
  error = error_type
@@ -0,0 +1,30 @@
1
+ module Intelligence
2
+ module MessageContent
3
+
4
+ class Thought < Base
5
+
6
+ schema do
7
+ text String, required: true
8
+ end
9
+
10
+ attr_reader :text
11
+
12
+ def valid?
13
+ true
14
+ end
15
+
16
+ def merge( other )
17
+ other_text = other.text
18
+ text = @text
19
+ text = ( @text || '' ) + other_text if other_text
20
+
21
+ self.class.new( text: text )
22
+ end
23
+
24
+ def to_h
25
+ { type: :text, text: text }
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -1,3 +1,3 @@
1
1
  module Intelligence
2
- VERSION = "1.0.0.beta05"
2
+ VERSION = "1.0.0.beta06"
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: intelligence
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta05
4
+ version: 1.0.0.beta06
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kristoph Cichocki-Romanov
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-06-07 00:00:00.000000000 Z
10
+ date: 2025-06-08 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: faraday
@@ -135,6 +135,9 @@ files:
135
135
  - lib/intelligence/adapters/anthropic/chat_response_methods.rb
136
136
  - lib/intelligence/adapters/cerebras.rb
137
137
  - lib/intelligence/adapters/deepseek.rb
138
+ - lib/intelligence/adapters/deepseek/adapter.rb
139
+ - lib/intelligence/adapters/deepseek/chat_request_methods.rb
140
+ - lib/intelligence/adapters/deepseek/chat_response_methods.rb
138
141
  - lib/intelligence/adapters/generic.rb
139
142
  - lib/intelligence/adapters/generic/adapter.rb
140
143
  - lib/intelligence/adapters/generic/chat_request_methods.rb
@@ -170,6 +173,7 @@ files:
170
173
  - lib/intelligence/message_content/binary.rb
171
174
  - lib/intelligence/message_content/file.rb
172
175
  - lib/intelligence/message_content/text.rb
176
+ - lib/intelligence/message_content/thought.rb
173
177
  - lib/intelligence/message_content/tool_call.rb
174
178
  - lib/intelligence/message_content/tool_result.rb
175
179
  - lib/intelligence/tool.rb