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.



224
225
226
227
228
229
230
231
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 224

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



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

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



351
352
353
354
355
356
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 351

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



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

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

#on_message_delta(stop_reason:, output_tokens:) ⇒ Object



324
325
326
327
328
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 324

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



312
313
314
315
316
317
318
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 312

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



233
234
235
236
237
238
239
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 233

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



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

def on_text_close(block_index:)
  _ = block_index
end

#on_text_delta(block_index:, text:) ⇒ Object



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

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

#on_text_open(block_index:) ⇒ Object



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

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

#on_thinking_close(block_index:, signature:) ⇒ Object



268
269
270
271
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 268

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

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



259
260
261
262
263
264
265
266
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 259

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



255
256
257
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 255

def on_thinking_open(block_index:)
  _ = block_index
end

#on_tool_call_abort(block_index:, reason:) ⇒ Object

rubocop:disable Lint/UnusedMethodArgument



308
309
310
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 308

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

#on_tool_call_close(block_index:) ⇒ Object



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

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



291
292
293
294
295
296
297
298
299
300
# File 'lib/legion/llm/api/client_translators/openai_chat.rb', line 291

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



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

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