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
- .body_hints_enabled? ⇒ Boolean
- .body_symbol_array(body:, key:) ⇒ Object
-
.build(request:, headers:) ⇒ Object
Build the routing_payload from headers, body, and classification.
- .derive_capabilities(request:) ⇒ Object
- .derive_privacy(request:) ⇒ Object
- .derive_thinking(request:) ⇒ Object
- .header_int(headers:, name:) ⇒ Object
-
.parse_and_validate_header(headers:, name:, taxonomy:) ⇒ Object
G31 / opus M7: validate header values against taxonomy.
- .parse_header_array(headers:, name:) ⇒ Object
Class Method Details
.body_hints_enabled? ⇒ 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 |