Module: Legion::LLM::Inference::Executor::PayloadBuilder

Extended by:
Legion::Logging::Helper
Defined in:
lib/legion/llm/inference/executor/payload_builder.rb

Overview

Single ingress site for routing_payload construction (C2). Parses x-legion-* headers, validates against Taxonomies, gates body hints, and assembles the dumb hash threaded through Router.request_lane. C2 replaces build_routing_payload_from_resolved from C1.

Class Method Summary collapse

Class Method Details

.body_hints_enabled?Boolean

Returns:

  • (Boolean)


92
93
94
# File 'lib/legion/llm/inference/executor/payload_builder.rb', line 92

def body_hints_enabled?
  Legion::Settings[:llm][:routing][:allow_body_routing_hints] == true
end

.body_symbol_array(body:, key:) ⇒ Object



96
97
98
# File 'lib/legion/llm/inference/executor/payload_builder.rb', line 96

def body_symbol_array(body:, key:, **)
  Array(body[key] || body[key.to_s]).map(&:to_sym).reject { |s| s.to_s.empty? }
end

.build(request:, headers:) ⇒ Object

Build the routing_payload from headers, body, and classification. Called once per request at the executor ingress. H-H / sonnet G4: body model always honored; body tier/provider/instance gated by allow_body_routing_hints setting.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/legion/llm/inference/executor/payload_builder.rb', line 22

def build(request:, headers:, **)
  body = request.respond_to?(:body) ? (request.body || {}) : {}
  # When called from the executor context, body may be empty; fall back to
  # routing[:model] which was parsed from the HTTP body by the client translator.
  body_model = body[:model] || body['model'] ||
               (request.respond_to?(:routing) ? (request.routing[:model] || request.routing['model']) : nil)

  tiers     = parse_and_validate_header(headers: headers, name: 'x-legion-tiers',
                                        taxonomy: Legion::Extensions::Llm::Taxonomies::TIERS)
  providers = parse_header_array(headers: headers, name: 'x-legion-providers')
  instances = parse_header_array(headers: headers, name: 'x-legion-instances')
  models    = body_model ? [body_model.to_s] : parse_header_array(headers: headers, name: 'x-legion-models')

  if body_hints_enabled?
    tiers     |= body_symbol_array(body: body, key: :tier)
    providers |= body_symbol_array(body: body, key: :provider)
    instances |= body_symbol_array(body: body, key: :instance)
  end

  capabilities = Legion::Extensions::Llm::Capabilities.normalize(
    derive_capabilities(request: request)
  )

  {
    type:              :inference,
    tiers:             tiers,
    providers:         providers,
    instances:         instances,
    models:            models,
    capabilities:      capabilities,
    thinking:          derive_thinking(request: request),
    privacy:           derive_privacy(request: request),
    estimated_context: nil,
    tried_lanes:       [],
    max_attempts:      header_int(headers: headers, name: 'x-legion-max-attempts') ||
      Legion::Settings[:llm][:routing][:max_attempts],
    rng:               nil
  }
end

.derive_capabilities(request:) ⇒ Object



100
101
102
103
104
105
# File 'lib/legion/llm/inference/executor/payload_builder.rb', line 100

def derive_capabilities(request:, **)
  caps = []
  tools = request.respond_to?(:tools) ? request.tools : nil
  caps << :tools if tools.is_a?(Array) && tools.any?
  caps
end

.derive_privacy(request:) ⇒ Object



114
115
116
117
118
119
120
121
# File 'lib/legion/llm/inference/executor/payload_builder.rb', line 114

def derive_privacy(request:, **)
  classification = request.respond_to?(:classification) ? request.classification : nil
  return :normal unless classification.is_a?(Hash)

  classification[:privacy]&.to_sym == :strict ? :strict : :normal
rescue StandardError
  :normal
end

.derive_thinking(request:) ⇒ Object



107
108
109
110
111
112
# File 'lib/legion/llm/inference/executor/payload_builder.rb', line 107

def derive_thinking(request:, **)
  thinking = request.respond_to?(:thinking) ? request.thinking : nil
  return :any unless thinking.is_a?(Hash) && thinking.any?

  thinking[:type]&.to_sym == :enabled ? :require : :any
end

.header_int(headers:, name:) ⇒ Object



83
84
85
86
87
88
89
90
# File 'lib/legion/llm/inference/executor/payload_builder.rb', line 83

def header_int(headers:, name:, **)
  raw = headers[name] || headers[name.upcase] || headers[name.tr('-', '_').upcase]
  return nil if raw.nil? || raw.to_s.empty?

  Integer(raw.to_s, 10)
rescue ArgumentError
  nil
end

.parse_and_validate_header(headers:, name:, taxonomy:) ⇒ Object

G31 / opus M7: validate header values against taxonomy. Raises Errors::InvalidHeader (mapped to 400) on unknown values. Returns symbol array of valid values.



64
65
66
67
68
69
70
71
72
73
74
# File 'lib/legion/llm/inference/executor/payload_builder.rb', line 64

def parse_and_validate_header(headers:, name:, taxonomy:, **)
  values = parse_header_array(headers: headers, name: name)
  return values if values.empty?

  bad = values - taxonomy
  return values if bad.empty?

  raise Legion::LLM::Errors::InvalidHeader.new(
    header: name, got: bad, valid: taxonomy
  )
end

.parse_header_array(headers:, name:) ⇒ Object



76
77
78
79
80
81
# File 'lib/legion/llm/inference/executor/payload_builder.rb', line 76

def parse_header_array(headers:, name:, **)
  raw = headers[name] || headers[name.upcase] || headers[name.tr('-', '_').upcase]
  return [] if raw.nil? || raw.to_s.empty?

  raw.to_s.split(',').map(&:strip).reject(&:empty?).map(&:to_sym)
end