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 +4 -4
- data/lib/intelligence/adapters/deepseek/adapter.rb +47 -0
- data/lib/intelligence/adapters/deepseek/chat_request_methods.rb +233 -0
- data/lib/intelligence/adapters/deepseek/chat_response_methods.rb +248 -0
- data/lib/intelligence/adapters/deepseek.rb +1 -44
- data/lib/intelligence/adapters/generic/chat_response_methods.rb +14 -11
- data/lib/intelligence/adapters/x_ai.rb +6 -98
- data/lib/intelligence/message_content/thought.rb +30 -0
- data/lib/intelligence/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2882de87c5b4c5e0ce5c59b91ca981788b1435b4e8b68e82c592d67b5b33512e
|
4
|
+
data.tar.gz: 92072e12828beec46b8d988c0b4e6ddaa9aac1d9d4cee7fbdf1d2c02cb8ccc83
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 '
|
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
|
-
|
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.
|
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
|
-
|
125
|
-
if
|
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
|
-
|
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
|
data/lib/intelligence/version.rb
CHANGED
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.
|
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-
|
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
|