Class: Legion::Extensions::Llm::Canonical::Response

Inherits:
Data
  • Object
show all
Defined in:
lib/legion/extensions/llm/canonical/response.rb

Overview

rubocop:disable Lint/ConstantDefinitionInBlock -- required for Data.define block scope Canonical response shape — the provider-boundary contract. Per R2: does NOT replace Inference::Response (the pipeline envelope). Per Amendment A: immutable Data.define with strict factory.

Constant Summary collapse

STOP_REASONS =
%i[end_turn tool_use max_tokens stop_sequence content_filter error].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#metadataObject (readonly)

Returns the value of attribute metadata

Returns:

  • (Object)

    the current value of metadata



12
13
14
# File 'lib/legion/extensions/llm/canonical/response.rb', line 12

def 
  @metadata
end

#modelObject (readonly)

Returns the value of attribute model

Returns:

  • (Object)

    the current value of model



12
13
14
# File 'lib/legion/extensions/llm/canonical/response.rb', line 12

def model
  @model
end

#routingObject (readonly)

Returns the value of attribute routing

Returns:

  • (Object)

    the current value of routing



12
13
14
# File 'lib/legion/extensions/llm/canonical/response.rb', line 12

def routing
  @routing
end

#stop_reasonObject (readonly)

Returns the value of attribute stop_reason

Returns:

  • (Object)

    the current value of stop_reason



12
13
14
# File 'lib/legion/extensions/llm/canonical/response.rb', line 12

def stop_reason
  @stop_reason
end

#textObject (readonly)

Returns the value of attribute text

Returns:

  • (Object)

    the current value of text



12
13
14
# File 'lib/legion/extensions/llm/canonical/response.rb', line 12

def text
  @text
end

#thinkingObject (readonly)

Returns the value of attribute thinking

Returns:

  • (Object)

    the current value of thinking



12
13
14
# File 'lib/legion/extensions/llm/canonical/response.rb', line 12

def thinking
  @thinking
end

#tool_callsObject (readonly)

Returns the value of attribute tool_calls

Returns:

  • (Object)

    the current value of tool_calls



12
13
14
# File 'lib/legion/extensions/llm/canonical/response.rb', line 12

def tool_calls
  @tool_calls
end

#usageObject (readonly)

Returns the value of attribute usage

Returns:

  • (Object)

    the current value of usage



12
13
14
# File 'lib/legion/extensions/llm/canonical/response.rb', line 12

def usage
  @usage
end

Class Method Details

.build(text: '', thinking: nil, tool_calls: nil, usage: nil, stop_reason: nil, model: nil, routing: nil, metadata: nil) ⇒ Object

Build from keyword args (primary constructor).



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/legion/extensions/llm/canonical/response.rb', line 68

def self.build(
  text: '', thinking: nil, tool_calls: nil, usage: nil,
  stop_reason: nil, model: nil, routing: nil, metadata: nil
)
  stop_reason_sym = stop_reason&.to_sym
  unless stop_reason_sym.nil? || STOP_REASONS.include?(stop_reason_sym)
    raise ArgumentError,
          "Invalid stop_reason: #{stop_reason_sym.inspect}. Must be one of: #{STOP_REASONS.join(', ')}"
  end

  new(
    text: text.to_s,
    thinking: thinking,
    tool_calls: tool_calls || [],
    usage: usage,
    stop_reason: stop_reason_sym,
    model: model,
    routing: routing || {},
    metadata:  || {}
  )
end

.from_hash(source) ⇒ Object

Build from a Hash (raw provider response or deserialized wire payload). Unknown keys go to metadata, never silently dropped.



20
21
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
61
62
63
64
65
# File 'lib/legion/extensions/llm/canonical/response.rb', line 20

def self.from_hash(source)
  return nil if source.nil?

  h = source.transform_keys(&:to_sym)

  # Extract known fields
  text = h.delete(:text) || h.delete(:content) || ''
  text = text.to_s if text

  thinking_raw = h.delete(:thinking)
  thinking = thinking_raw.is_a?(Thinking) ? thinking_raw : Thinking.from_hash(thinking_raw)

  tool_calls_raw = h.delete(:tool_calls)
  tool_calls = Array(tool_calls_raw).filter_map do |tc|
    tc.is_a?(ToolCall) ? tc : ToolCall.from_hash(tc)
  end

  usage_raw = h.delete(:usage)
  usage = usage_raw.is_a?(Usage) ? usage_raw : Usage.from_hash(usage_raw)

  # Normalize stop_reason
  stop_reason_raw = h.delete(:stop_reason) || h.delete(:finish_reason)
  stop_reason = stop_reason_raw&.to_sym if stop_reason_raw
  unless stop_reason.nil? || STOP_REASONS.include?(stop_reason)
    raise ArgumentError,
          "Invalid stop_reason: #{stop_reason.inspect}. Must be one of: #{STOP_REASONS.join(', ')}"
  end

  model = h.delete(:model)
  routing = h.delete(:routing) || {}

  # Remaining keys become metadata
   = h.delete(:metadata) || {}
   = .merge(h).compact

  new(
    text: text,
    thinking: thinking,
    tool_calls: tool_calls,
    usage: usage,
    stop_reason: stop_reason,
    model: model,
    routing: routing,
    metadata: 
  )
end

Instance Method Details

#as_jsonObject

MultiJson/Oj/::JSON callback — prevents Data.define #inspect leak into JSON.



108
109
110
# File 'lib/legion/extensions/llm/canonical/response.rb', line 108

def as_json(*)
  to_h
end

#error?Boolean

Whether the response ended due to an error.

Returns:

  • (Boolean)


122
123
124
# File 'lib/legion/extensions/llm/canonical/response.rb', line 122

def error?
  stop_reason == :error
end

#to_hObject

Serialize to a Hash for AMQP/fleet/wire transport.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/legion/extensions/llm/canonical/response.rb', line 91

def to_h
  {
    text: text,
    thinking: thinking&.to_h,
    tool_calls: tool_calls&.map { |tc| tc.is_a?(ToolCall) ? tc.to_h : tc },
    usage: usage&.to_h,
    stop_reason: stop_reason,
    model: model,
    routing: routing,
    metadata: 
  }.compact.reject do |k, v|
    %i[tool_calls routing
       metadata].include?(k) && v.is_a?(Enumerable) && v.empty?
  end
end

#to_jsonObject



112
113
114
# File 'lib/legion/extensions/llm/canonical/response.rb', line 112

def to_json(*)
  to_h.to_json(*)
end

#tool_call?Boolean

Whether the response includes tool calls.

Returns:

  • (Boolean)


117
118
119
# File 'lib/legion/extensions/llm/canonical/response.rb', line 117

def tool_call?
  !tool_calls.nil? && !tool_calls.empty?
end