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
# 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']

  return build_done_chunk(data, finish_reason, usage_raw) if finish_reason
  return parse_error_chunk(data) if data['error']

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

  return build_thinking_chunk(reasoning, data['id']) if reasoning
  return build_text_chunk(content, data['id']) if content

  parse_tool_call_delta(delta, data)
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