Module: Legion::LLM::API::Translators::OpenAIRequest

Extended by:
Legion::Logging::Helper
Defined in:
lib/legion/llm/api/translators/openai_request.rb

Class Method Summary collapse

Class Method Details

.extract_content(content) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/legion/llm/api/translators/openai_request.rb', line 83

def extract_content(content)
  return content if content.is_a?(String)
  return content unless content.is_a?(Array)

  # Normalize keys once up front to avoid repeated transform_keys calls.
  normalized = content.map do |block|
    block.respond_to?(:transform_keys) ? block.transform_keys(&:to_sym) : block
  end

  has_non_text = normalized.any? { |b| b[:type].to_s != 'text' }
  return normalized if has_non_text

  normalized.filter_map do |b|
    b[:text] if b[:type].to_s == 'text'
  end.join("\n\n")
end

.extract_messages_and_system(raw_messages) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/legion/llm/api/translators/openai_request.rb', line 38

def extract_messages_and_system(raw_messages)
  system_content = nil
  messages = []

  raw_messages.each do |msg|
    m = msg.respond_to?(:transform_keys) ? msg.transform_keys(&:to_sym) : msg
    role = (m[:role] || m['role']).to_sym

    case role
    when :system
      system_content = extract_content(m[:content])
    when :assistant
      entry = { role: role, content: extract_content(m[:content]) }
      entry[:tool_calls] = normalize_assistant_tool_calls(m[:tool_calls]) if m[:tool_calls]
      messages << entry
    when :tool
      messages << { role: role, content: extract_content(m[:content]), tool_call_id: m[:tool_call_id] }.compact
    else
      messages << { role: role, content: extract_content(m[:content]) }
    end
  end

  [messages, system_content]
end

.normalize(body) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/legion/llm/api/translators/openai_request.rb', line 15

def normalize(body)
  log.debug('[llm][translator][openai_request] action=normalize')
  messages, system = extract_messages_and_system(body[:messages] || body['messages'] || [])
  tools = normalize_tools(body[:tools] || body['tools'])
  model = body[:model] || body['model']
  stream = body[:stream] || body['stream']
  max_tokens = body[:max_tokens] || body['max_tokens']
  temperature = body[:temperature] || body['temperature']

  result = {
    messages:    messages,
    model:       model,
    stream:      stream == true,
    max_tokens:  max_tokens,
    temperature: temperature,
    tools:       tools
  }
  result[:system] = system if system

  log.debug("[llm][translator][openai_request] action=normalized messages=#{messages.size} has_system=#{!system.nil?} tools=#{tools&.size || 0}")
  result.compact
end

.normalize_assistant_tool_calls(tool_calls) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/legion/llm/api/translators/openai_request.rb', line 63

def normalize_assistant_tool_calls(tool_calls)
  return nil unless tool_calls.is_a?(Array) && tool_calls.any?

  tool_calls.filter_map do |tc|
    tc = tc.transform_keys(&:to_sym) if tc.respond_to?(:transform_keys)
    fn = tc[:function]
    fn = fn.transform_keys(&:to_sym) if fn.respond_to?(:transform_keys)
    next unless fn.is_a?(Hash)

    {
      id:        tc[:id] || "call_#{fn[:name]}_#{SecureRandom.hex(8)}",
      name:      fn[:name].to_s,
      arguments: fn[:arguments].is_a?(String) ? Legion::JSON.parse(fn[:arguments]) : (fn[:arguments] || {})
    }
  rescue StandardError => e
    log.warn("[llm][translator][openai_request] action=normalize_tool_call error=#{e.message}")
    nil
  end
end

.normalize_tools(raw_tools) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/legion/llm/api/translators/openai_request.rb', line 100

def normalize_tools(raw_tools)
  return nil if raw_tools.nil? || !raw_tools.is_a?(Array) || raw_tools.empty?

  raw_tools.filter_map do |tool|
    t = tool.respond_to?(:transform_keys) ? tool.transform_keys(&:to_sym) : tool
    unless t[:type].to_s == 'function'
      log.debug("[llm][translator][openai_request] action=skip_unsupported_tool type=#{t[:type]}")
      next
    end

    fn = t[:function]
    fn = fn.transform_keys(&:to_sym) if fn.respond_to?(:transform_keys)
    next unless fn.is_a?(Hash)

    {
      name:        fn[:name].to_s,
      description: fn[:description].to_s,
      parameters:  fn[:parameters] || {}
    }
  end
end