@@ -93,99 +93,85 @@ def prepare_request(payload)
9393 end
9494
9595 def final_log_update ( log )
96- log . request_tokens = @ prompt_tokens if @ prompt_tokens
97- log . response_tokens = @ completion_tokens if @ completion_tokens
96+ log . request_tokens = processor . prompt_tokens if processor . prompt_tokens
97+ log . response_tokens = processor . completion_tokens if processor . completion_tokens
9898 end
9999
100- def extract_completion_from ( response_raw )
101- json = JSON . parse ( response_raw , symbolize_names : true )
100+ class OpenAiMessageProcessor
101+ attr_reader :prompt_tokens , :completion_tokens
102102
103- if @streaming_mode
104- @prompt_tokens ||= json . dig ( :usage , :prompt_tokens )
105- @completion_tokens ||= json . dig ( :usage , :completion_tokens )
103+ def initialize
104+ @tool = nil
105+ @tool_arguments = +""
106+ @prompt_tokens = nil
107+ @completion_tokens = nil
106108 end
107109
108- parsed = json . dig ( :choices , 0 )
109- return if ! parsed
110+ def process_streamed_message ( json )
111+ rval = nil
110112
111- response_h = @streaming_mode ? parsed . dig ( :delta ) : parsed . dig ( :message )
112- @has_function_call ||= response_h . dig ( :tool_calls ) . present?
113- @has_function_call ? response_h . dig ( :tool_calls , 0 ) : response_h . dig ( :content )
114- end
113+ tool_calls = json . dig ( :choices , 0 , :delta , :tool_calls )
114+ content = json . dig ( :choices , 0 , :delta , :content )
115+
116+ finished_tools = json . dig ( :choices , 0 , :finish_reason ) || tool_calls == [ ]
115117
116- def partials_from ( decoded_chunk )
117- decoded_chunk
118- . split ( "\n " )
119- . map do |line |
120- data = line . split ( "data: " , 2 ) [ 1 ]
121- data == "[DONE]" ? nil : data
118+ if tool_calls . present?
119+ id = tool_calls . dig ( 0 , :id )
120+ name = tool_calls . dig ( 0 , :function , :name )
121+ arguments = tool_calls . dig ( 0 , :function , :arguments )
122+ #index = tool_calls[0].dig(:index)
123+ if id . present? && @tool
124+ if @tool_arguments . present?
125+ parsed_args = JSON . parse ( @tool_arguments , symbolize_names : true )
126+ @tool . parameters = parsed_args
127+ end
128+ rval = @tool
129+ @tool = nil
130+ end
131+
132+ if id . present?
133+ @tool_arguments = +""
134+ @tool = ToolCall . new ( id : id , name : name )
135+ end
136+
137+ @tool_arguments << arguments . to_s
138+ elsif finished_tools && @tool
139+ parsed_args = JSON . parse ( @tool_arguments , symbolize_names : true )
140+ @tool . parameters = parsed_args
141+ rval = @tool
142+ @tool = nil
143+ elsif content . present?
144+ rval = content
122145 end
123- . compact
146+
147+ @prompt_tokens ||= json . dig ( :usage , :prompt_tokens )
148+ @completion_tokens ||= json . dig ( :usage , :completion_tokens )
149+
150+ rval
151+ end
152+ end
153+
154+ def decode_chunk ( chunk )
155+ @decoder ||= JsonStreamDecoder . new
156+ ( @decoder << chunk ) . map do |parsed_json |
157+ processor . process_streamed_message ( parsed_json )
158+ end . flatten . compact
124159 end
125160
126161 def has_tool? ( _response_data )
127162 @has_function_call
128163 end
129164
130- def native_tool_support ?
131- true
165+ def xml_tools_enabled ?
166+ false
132167 end
133168
134- def add_to_function_buffer ( function_buffer , partial : nil , payload : nil )
135- if @streaming_mode
136- return function_buffer if !partial
137- else
138- partial = payload
139- end
140-
141- @args_buffer ||= +""
142-
143- f_name = partial . dig ( :function , :name )
144-
145- @current_function ||= function_buffer . at ( "invoke" )
146-
147- if f_name
148- current_name = function_buffer . at ( "tool_name" ) . content
149-
150- if current_name . blank?
151- # first call
152- else
153- # we have a previous function, so we need to add a noop
154- @args_buffer = +""
155- @current_function =
156- function_buffer . at ( "function_calls" ) . add_child (
157- Nokogiri ::HTML5 ::DocumentFragment . parse ( noop_function_call_text + "\n " ) ,
158- )
159- end
160- end
161-
162- @current_function . at ( "tool_name" ) . content = f_name if f_name
163- @current_function . at ( "tool_id" ) . content = partial [ :id ] if partial [ :id ]
164-
165- args = partial . dig ( :function , :arguments )
166-
167- # allow for SPACE within arguments
168- if args && args != ""
169- @args_buffer << args
170-
171- begin
172- json_args = JSON . parse ( @args_buffer , symbolize_names : true )
173-
174- argument_fragments =
175- json_args . reduce ( +"" ) do |memo , ( arg_name , value ) |
176- memo << "\n <#{ arg_name } >#{ CGI . escapeHTML ( value . to_s ) } </#{ arg_name } >"
177- end
178- argument_fragments << "\n "
179-
180- @current_function . at ( "parameters" ) . children =
181- Nokogiri ::HTML5 ::DocumentFragment . parse ( argument_fragments )
182- rescue JSON ::ParserError
183- return function_buffer
184- end
185- end
169+ private
186170
187- function_buffer
171+ def processor
172+ @processor ||= OpenAiMessageProcessor . new
188173 end
174+
189175 end
190176 end
191177 end
0 commit comments