Class: Legion::Extensions::Llm::Openai::Translator

Inherits:
Object
  • Object
show all
Includes:
Logging::Helper
Defined in:
lib/legion/extensions/llm/openai/translator.rb

Overview

Canonical <-> OpenAI wire-format translator.

Implements the provider-boundary contract (Amendment A) so that canonical requests/responses/chunks cross exactly one translation layer per provider. Extracted from OpenAICompatible mixin methods; semantics preserved, not rewritten.

Capabilities (declarative, per the design doc):

- reasoning_effort: true            (gpt-5.x / o-series)
- responses_api: true               (chat/completions wire format)
- thinking_metadata_keys: [...]     (metadata keys for thinking)
- stop_reason_map: { openai -> canonical }

-- translator complexity is inherent to multi-field mapping (B1a pattern) rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity

Constant Summary collapse

STOP_REASON_MAP =

OpenAI finish_reason -> canonical stop_reason (G18 stop-reason matrix)

{
  'stop' => :end_turn,
  'tool_calls' => :tool_use,
  'function_call' => :tool_use,
  'length' => :max_tokens,
  'content_filter' => :content_filter
}.freeze
THINKING_METADATA_KEYS =

Metadata keys carrying thinking/reasoning in OpenAI responses

%i[
  reasoning_content reasoning thinking thinking_text
  thinking_signature reasoning_signature
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(api_base: nil, headers: nil) ⇒ Translator

Returns a new instance of Translator.



39
40
41
42
# File 'lib/legion/extensions/llm/openai/translator.rb', line 39

def initialize(api_base: nil, headers: nil)
  @api_base = api_base
  @headers = headers || {}
end

Instance Method Details

#capabilitiesHash

Returns declarative capabilities consumed by routing/dispatch.

Returns:

  • (Hash)

    declarative capabilities consumed by routing/dispatch



45
46
47
48
49
50
51
52
53
# File 'lib/legion/extensions/llm/openai/translator.rb', line 45

def capabilities
  {
    provider: 'openai',
    reasoning_effort: true,
    responses_api: true,
    thinking_metadata_keys: THINKING_METADATA_KEYS,
    stop_reason_map: STOP_REASON_MAP
  }
end

#parse_chunk(raw) ⇒ Canonical::Chunk?

Parameters:

  • raw (Hash)

    single SSE data payload or canonical chunk

Returns:

  • (Canonical::Chunk, nil)


109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/legion/extensions/llm/openai/translator.rb', line 109

def parse_chunk(raw)
  return nil if raw.nil? || raw.to_h.empty?
  return Canonical::Chunk.from_hash(raw) if canonical_chunk_form?(raw)

  data = raw.to_h
  choice = Array(data['choices']).first || {}
  delta = choice['delta'] || {}
  finish_reason = choice['finish_reason']
  usage_raw = data['usage']

  chunk_stop_reason = finish_reason ? map_stop_reason(finish_reason) : nil
  chunk_usage = finish_reason && usage_raw ? parse_usage(usage_raw) : nil

  return parse_error_chunk(data) if data['error']

  reasoning = delta['reasoning_content'] || delta['reasoning']
  content = delta['content']

  if reasoning
    return build_thinking_chunk(reasoning, data['id'], stop_reason: chunk_stop_reason,
                                                       usage: chunk_usage)
  end
  return build_text_chunk(content, data['id'], stop_reason: chunk_stop_reason, usage: chunk_usage) if content

  tc_chunk = parse_tool_call_delta(delta, data, stop_reason: chunk_stop_reason, usage: chunk_usage)
  return tc_chunk if tc_chunk

  return build_done_chunk(data, finish_reason, usage_raw) if finish_reason

  nil
rescue StandardError => e
  handle_exception(e, level: :error, handled: true, operation: 'openai.translator.parse_chunk')
  Canonical::Chunk.error_chunk(
    error: e.message,
    request_id: raw.to_h['id']
  )
end

#parse_response(wire) ⇒ Canonical::Response

Parameters:

  • wire (Hash)

    OpenAI API response body (string-keyed)

Returns:

  • (Canonical::Response)


75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/legion/extensions/llm/openai/translator.rb', line 75

def parse_response(wire)
  return Canonical::Response.from_hash(wire) if canonical_form?(wire)

  body = wire.to_h
  choice = Array(body['choices']).first || {}
  message = choice['message'] || {}
  usage_raw = body['usage'] || {}

  text, thinking = extract_thinking_from_message(message)
  tool_calls = parse_tool_calls(message['tool_calls'])
  usage = parse_usage(usage_raw)
  stop_reason = map_stop_reason(choice['finish_reason'])
   = (message)

  Canonical::Response.build(
    text: text,
    thinking: thinking,
    tool_calls: tool_calls,
    usage: usage,
    stop_reason: stop_reason,
    model: body['model'],
    metadata: 
  )
rescue StandardError => e
  handle_exception(e, level: :error, handled: true, operation: 'openai.translator.parse_response')
  Canonical::Response.build(
    text: '',
    stop_reason: :error,
    metadata: { error: e.message }
  )
end

#render_request(canonical_request) ⇒ Hash

Returns OpenAI wire-format payload for /v1/chat/completions.

Parameters:

  • canonical_request (Canonical::Request)

Returns:

  • (Hash)

    OpenAI wire-format payload for /v1/chat/completions



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/legion/extensions/llm/openai/translator.rb', line 57

def render_request(canonical_request)
  wire = {
    model: resolve_model(canonical_request),
    messages: render_messages(canonical_request),
    stream: canonical_request.stream
  }.compact

  apply_params(wire, canonical_request.params) if canonical_request.params
  apply_tools(wire, canonical_request) if canonical_request.tools&.any?
  apply_tool_choice(wire, canonical_request.tool_choice) if canonical_request.tool_choice
  apply_thinking(wire, canonical_request)
  use_stream_usage(wire) if canonical_request.stream

  wire
end