Class: Legion::LLM::API::ClientTranslators::OpenAIChat::Events

Inherits:
Object
  • Object
show all
Includes:
Legion::Logging::Helper
Defined in:
lib/legion/llm/api/client_translators/openai_chat.rb

Overview

Events emitter for /v1/chat/completions streaming. Emits ‘data: <chunk>nn` lines with a final `data: [DONE]nn`.

Instance Method Summary collapse

Constructor Details

#initialize(out:, request_id:, model:, conv_id: nil, include_reasoning: true) ⇒ Events

Returns a new instance of Events.



221
222
223
224
225
226
227
228
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 221

def initialize(out:, request_id:, model:, conv_id: nil, include_reasoning: true)
  @out = out
  @request_id = request_id
  @model = model
  @conv_id = conv_id
  @include_reasoning = include_reasoning
  @done_emitted = false
end

Instance Method Details

#on_done(stop_reason:, usage:, model:) ⇒ Object



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 327

def on_done(stop_reason:, usage:, model:)
  return if @done_emitted

  finish_reason = FINISH_REASON_MAP[stop_reason] || 'stop'
  done_chunk = {
    id:      "chatcmpl-#{@request_id.to_s.delete('-')}",
    object:  'chat.completion.chunk',
    created: Time.now.to_i,
    model:   model.to_s,
    choices: [{ index: 0, delta: {}, finish_reason: finish_reason }],
    usage:   {
      prompt_tokens:     usage[:input_tokens].to_i,
      completion_tokens: usage[:output_tokens].to_i,
      total_tokens:      usage[:input_tokens].to_i + usage[:output_tokens].to_i
    }
  }
  emit_chunk(done_chunk)
  @out << "data: [DONE]\n\n"
  @done_emitted = true
end

#on_error(message:, type:, status_code:) ⇒ Object



348
349
350
351
352
353
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 348

def on_error(message:, type:, status_code:)
  _ = status_code
  emit_chunk({ error: { message: message, type: type } })
  @out << "data: [DONE]\n\n" unless @done_emitted
  @done_emitted = true
end

#on_keep_aliveObject



317
318
319
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 317

def on_keep_alive
  @out << ": keep-alive\n\n"
end

#on_message_delta(stop_reason:, output_tokens:) ⇒ Object



321
322
323
324
325
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 321

def on_message_delta(stop_reason:, output_tokens:)
  _ = stop_reason
  _ = output_tokens
  # Trailing fields fold into on_done.
end

#on_server_tool_result(block_index:, tool_call_id:, result_text:) ⇒ Object



309
310
311
312
313
314
315
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 309

def on_server_tool_result(block_index:, tool_call_id:, result_text:)
  _ = block_index
  _ = tool_call_id
  _ = result_text
  # Chat completions has no inline server tool result event. The
  # follow-up turn carries the tool message.
end

#on_start(model:, request_id:, input_tokens:) ⇒ Object



230
231
232
233
234
235
236
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 230

def on_start(model:, request_id:, input_tokens:)
  _ = model
  _ = request_id
  _ = input_tokens
  # Chat completions stream has no equivalent of "message_start";
  # initial role marker piggybacks on the first text delta.
end

#on_text_close(block_index:) ⇒ Object



248
249
250
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 248

def on_text_close(block_index:)
  _ = block_index
end

#on_text_delta(block_index:, text:) ⇒ Object



243
244
245
246
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 243

def on_text_delta(block_index:, text:)
  _ = block_index
  emit_chunk(delta_envelope({ content: text }))
end

#on_text_open(block_index:) ⇒ Object



238
239
240
241
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 238

def on_text_open(block_index:)
  _ = block_index
  # No explicit open in chat.completion.chunk format.
end

#on_thinking_close(block_index:, signature:) ⇒ Object



265
266
267
268
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 265

def on_thinking_close(block_index:, signature:)
  _ = block_index
  _ = signature
end

#on_thinking_delta(block_index:, text:, signature:) ⇒ Object



256
257
258
259
260
261
262
263
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 256

def on_thinking_delta(block_index:, text:, signature:)
  _ = block_index
  _ = signature
  return unless @include_reasoning
  return if text.to_s.empty?

  emit_chunk(delta_envelope({ reasoning_content: text }))
end

#on_thinking_open(block_index:) ⇒ Object



252
253
254
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 252

def on_thinking_open(block_index:)
  _ = block_index
end

#on_tool_call_abort(block_index:, reason:) ⇒ Object

rubocop:disable Lint/UnusedMethodArgument



305
306
307
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 305

def on_tool_call_abort(block_index:, reason:) # rubocop:disable Lint/UnusedMethodArgument
  nil
end

#on_tool_call_close(block_index:) ⇒ Object



299
300
301
302
303
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 299

def on_tool_call_close(block_index:)
  _ = block_index
  # No close event in chat.completion.chunk; finish_reason
  # 'tool_calls' on the final done covers it.
end

#on_tool_call_delta(block_index:, partial_arguments_json:) ⇒ Object



288
289
290
291
292
293
294
295
296
297
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 288

def on_tool_call_delta(block_index:, partial_arguments_json:)
  return if partial_arguments_json.to_s.empty?

  emit_chunk(delta_envelope({
                              tool_calls: [{
                                index:    block_index,
                                function: { arguments: partial_arguments_json }
                              }]
                            }))
end

#on_tool_call_open(block_index:, tool_call:, server_tool:) ⇒ Object



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 270

def on_tool_call_open(block_index:, tool_call:, server_tool:)
  _ = server_tool
  # Send the initial tool_call with name. Index is 0-based across
  # this streaming response; chat.completion.chunk references it
  # by index in the delta.
  emit_chunk(delta_envelope({
                              tool_calls: [{
                                index:    block_index,
                                id:       tool_call[:id],
                                type:     'function',
                                function: {
                                  name:      tool_call[:name].to_s,
                                  arguments: ''
                                }
                              }]
                            }))
end