Class: Legion::LLM::Call::NativeResponseAdapter

Inherits:
Object
  • Object
show all
Defined in:
lib/legion/llm/call/dispatch.rb

Overview

Wraps a native dispatch result hash so Pipeline::Executor and ConversationStore can consume a stable provider response object.

Constant Summary collapse

HASH_KEY_MAP =
{
  result: :content, content: :content,
  input_tokens: :input_tokens, output_tokens: :output_tokens,
  cache_read_tokens: :cache_read_tokens, cache_write_tokens: :cache_write_tokens,
  usage: :usage, metadata: :metadata,
  tool_calls: :tool_calls, stop_reason: :stop_reason, thinking: :thinking,
  data: :content, model: :model
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(result_hash) ⇒ NativeResponseAdapter

Returns a new instance of NativeResponseAdapter.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/legion/llm/call/dispatch.rb', line 30

def initialize(result_hash)
  result_hash = self.class.coerce_result(result_hash)
  extracted = self.class.extract_result(
    result_hash[:result],
    metadata: result_hash[:metadata] || {},
    thinking: result_hash[:thinking]
  )
  @content             = extracted[:result].to_s
  @model               = result_hash[:model]
  @metadata            = extracted[:metadata] || {}
  @tool_calls          = result_hash[:tool_calls] || []
  @stop_reason         = result_hash[:stop_reason]
  @thinking            = extracted[:thinking]
  usage                = self.class.coerce_usage(result_hash[:usage])
  @usage               = usage
  @input_tokens        = usage.input_tokens
  @output_tokens       = usage.output_tokens
  @cache_read_tokens   = usage.cache_read_tokens
  @cache_write_tokens  = usage.cache_write_tokens
end

Instance Attribute Details

#cache_read_tokensObject (readonly)

Returns the value of attribute cache_read_tokens.



17
18
19
# File 'lib/legion/llm/call/dispatch.rb', line 17

def cache_read_tokens
  @cache_read_tokens
end

#cache_write_tokensObject (readonly)

Returns the value of attribute cache_write_tokens.



17
18
19
# File 'lib/legion/llm/call/dispatch.rb', line 17

def cache_write_tokens
  @cache_write_tokens
end

#contentObject (readonly)

Returns the value of attribute content.



17
18
19
# File 'lib/legion/llm/call/dispatch.rb', line 17

def content
  @content
end

#input_tokensObject (readonly)

Returns the value of attribute input_tokens.



17
18
19
# File 'lib/legion/llm/call/dispatch.rb', line 17

def input_tokens
  @input_tokens
end

#metadataObject (readonly)

Returns the value of attribute metadata.



17
18
19
# File 'lib/legion/llm/call/dispatch.rb', line 17

def 
  @metadata
end

#modelObject (readonly)

Returns the value of attribute model.



17
18
19
# File 'lib/legion/llm/call/dispatch.rb', line 17

def model
  @model
end

#output_tokensObject (readonly)

Returns the value of attribute output_tokens.



17
18
19
# File 'lib/legion/llm/call/dispatch.rb', line 17

def output_tokens
  @output_tokens
end

#stop_reasonObject (readonly)

Returns the value of attribute stop_reason.



17
18
19
# File 'lib/legion/llm/call/dispatch.rb', line 17

def stop_reason
  @stop_reason
end

#thinkingObject (readonly)

Returns the value of attribute thinking.



17
18
19
# File 'lib/legion/llm/call/dispatch.rb', line 17

def thinking
  @thinking
end

#tool_callsObject (readonly)

Returns the value of attribute tool_calls.



17
18
19
# File 'lib/legion/llm/call/dispatch.rb', line 17

def tool_calls
  @tool_calls
end

#usageObject (readonly)

Returns the value of attribute usage.



17
18
19
# File 'lib/legion/llm/call/dispatch.rb', line 17

def usage
  @usage
end

Class Method Details

.coerce_result(raw) ⇒ Object



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

def self.coerce_result(raw)
  return raw if raw.is_a?(Hash)

  {
    result:      raw.respond_to?(:content) ? raw.content : raw,
    usage:       Usage.new(
      input_tokens:       raw.respond_to?(:input_tokens) ? raw.input_tokens.to_i : 0,
      output_tokens:      raw.respond_to?(:output_tokens) ? raw.output_tokens.to_i : 0,
      cache_read_tokens:  raw.respond_to?(:cached_tokens) ? raw.cached_tokens.to_i : 0,
      cache_write_tokens: raw.respond_to?(:cache_creation_tokens) ? raw.cache_creation_tokens.to_i : 0
    ),
    metadata:    raw.respond_to?(:metadata) && raw..is_a?(Hash) ? raw. : {},
    tool_calls:  raw.respond_to?(:tool_calls) ? raw.tool_calls : [],
    stop_reason: raw.respond_to?(:stop_reason) ? raw.stop_reason : nil,
    thinking:    raw.respond_to?(:thinking) ? raw.thinking : nil
  }.compact
end

.coerce_usage(raw_usage) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
# File 'lib/legion/llm/call/dispatch.rb', line 98

def self.coerce_usage(raw_usage)
  return raw_usage if raw_usage.is_a?(Usage)
  return Usage.new unless raw_usage.is_a?(Hash)

  Usage.new(
    input_tokens:       (raw_usage[:input_tokens] || raw_usage['input_tokens']).to_i,
    output_tokens:      (raw_usage[:output_tokens] || raw_usage['output_tokens']).to_i,
    cache_read_tokens:  (raw_usage[:cache_read_tokens] || raw_usage['cache_read_tokens']).to_i,
    cache_write_tokens: (raw_usage[:cache_write_tokens] || raw_usage['cache_write_tokens']).to_i
  )
end

.extract_result(result, metadata: {}, thinking: nil) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/legion/llm/call/dispatch.rb', line 82

def self.extract_result(result, metadata: {}, thinking: nil)
  extractor = defined?(::Legion::Extensions::Llm::Responses::ThinkingExtractor) &&
              ::Legion::Extensions::Llm::Responses::ThinkingExtractor
  return { result: result, metadata:  || {}, thinking: normalize_thinking_payload(thinking) } unless extractor

  extraction = extractor.extract(result, metadata:  || {})
  {
    result:   extraction.content,
    metadata: extraction.,
    thinking: merge_thinking_payloads(
      normalize_thinking_payload(thinking),
      normalize_thinking_payload(content: extraction.thinking, signature: extraction.signature)
    )
  }
end

.merge_thinking_payloads(existing, extracted) ⇒ Object



110
111
112
113
114
115
116
117
118
119
# File 'lib/legion/llm/call/dispatch.rb', line 110

def self.merge_thinking_payloads(existing, extracted)
  return existing || extracted unless existing && extracted

  content = [existing[:content], extracted[:content]].compact.map(&:to_s).reject(&:empty?).join
  existing.merge(
    content:   content.empty? ? nil : content,
    signature: existing[:signature] || extracted[:signature],
    enabled:   true
  ).compact
end

.normalize_thinking_payload(value = nil, content: nil, signature: nil) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/legion/llm/call/dispatch.rb', line 121

def self.normalize_thinking_payload(value = nil, content: nil, signature: nil)
  value = { content: content, signature: signature } if value.nil? && (content || signature)
  return nil if value.nil?

  if value.is_a?(Hash)
    normalized = value.transform_keys { |key| key.respond_to?(:to_sym) ? key.to_sym : key }
    content = normalized[:content] || normalized[:text]
    signature = normalized[:signature]
  elsif value.respond_to?(:text)
    content = value.text
    signature = value.respond_to?(:signature) ? value.signature : nil
  else
    content = value
    signature = nil
  end

  content = content.to_s.strip unless content.nil?
  return nil if content.to_s.empty? && signature.to_s.empty?

  { content: content, signature: signature, enabled: true }.compact
end

Instance Method Details

#[](key) ⇒ Object



51
52
53
54
# File 'lib/legion/llm/call/dispatch.rb', line 51

def [](key)
  attr = HASH_KEY_MAP[key.to_sym]
  attr ? public_send(attr) : nil
end

#dig(*keys) ⇒ Object



56
57
58
59
60
61
62
# File 'lib/legion/llm/call/dispatch.rb', line 56

def dig(*keys)
  value = self[keys.first]
  return value if keys.length == 1
  return nil unless value.respond_to?(:dig)

  value.dig(*keys[1..])
end