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

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

Overview

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. Unknown keys fold into metadata — never silently dropped.

Constant Summary collapse

STOP_REASONS =
%i[end_turn tool_use max_tokens stop_sequence content_filter error].freeze
BUILD_SITE =
'Canonical::Response.build'
FROM_HASH_SITE =
'Canonical::Response.from_hash'
NEW_SITE =
'Canonical::Response.new'

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



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

def 
  @metadata
end

#modelObject (readonly)

Returns the value of attribute model

Returns:

  • (Object)

    the current value of model



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

def model
  @model
end

#routingObject (readonly)

Returns the value of attribute routing

Returns:

  • (Object)

    the current value of routing



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

def routing
  @routing
end

#stop_reasonObject (readonly)

Returns the value of attribute stop_reason

Returns:

  • (Object)

    the current value of stop_reason



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

def stop_reason
  @stop_reason
end

#textObject (readonly)

Returns the value of attribute text

Returns:

  • (Object)

    the current value of text



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

def text
  @text
end

#thinkingObject (readonly)

Returns the value of attribute thinking

Returns:

  • (Object)

    the current value of thinking



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

def thinking
  @thinking
end

#tool_callsObject (readonly)

Returns the value of attribute tool_calls

Returns:

  • (Object)

    the current value of tool_calls



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

def tool_calls
  @tool_calls
end

#usageObject (readonly)

Returns the value of attribute usage

Returns:

  • (Object)

    the current value of usage



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

def usage
  @usage
end

Class Method Details

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

Build from keyword args (primary constructor).



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/legion/extensions/llm/canonical/response.rb', line 18

def self.build(
  text: '', thinking: nil, tool_calls: nil, usage: nil,
  stop_reason: nil, model: nil, routing: nil, metadata: {}
)
  new(
    text: text.to_s,
    thinking: normalize_thinking!(thinking, self::BUILD_SITE),
    tool_calls: normalize_tool_calls!(tool_calls, self::BUILD_SITE),
    usage: normalize_usage!(usage, self::BUILD_SITE),
    stop_reason: normalize_stop_reason!(stop_reason, self::BUILD_SITE),
    model: model,
    routing: routing || {},
    metadata: Strict.metadata!(, self::BUILD_SITE)
  )
end

.from_hash(source) ⇒ Object

Build from a Hash (raw provider response or deserialized wire payload). Canonical keys only (O03a): edges pass stop_reason, not finish_reason.



36
37
38
39
40
41
# File 'lib/legion/extensions/llm/canonical/response.rb', line 36

def self.from_hash(source)
  Strict.require_hash!(source, self::FROM_HASH_SITE)
  hash = Strict.symbolize_keys(source)
   = Strict.fold_unknowns!(self, self::FROM_HASH_SITE, hash)
  build(**hash, metadata:)
end

.normalize_stop_reason!(stop_reason, site) ⇒ Object

L6: stop_reason validated at construction, in both factories.



44
45
46
47
# File 'lib/legion/extensions/llm/canonical/response.rb', line 44

def self.normalize_stop_reason!(stop_reason, site)
  stop_reason_sym = stop_reason&.to_sym
  Strict.enum!(stop_reason_sym, self::STOP_REASONS, site, :stop_reason)
end

.normalize_thinking!(thinking, site) ⇒ Object

L2: one normalizer per member, shared by build and from_hash.



50
51
52
53
54
55
56
# File 'lib/legion/extensions/llm/canonical/response.rb', line 50

def self.normalize_thinking!(thinking, site)
  return nil if thinking.nil?
  return thinking if thinking.is_a?(Thinking)

  Strict.expect_type!(thinking, [::Hash], site, :thinking)
  Thinking.from_hash(thinking)
end

.normalize_tool_calls!(tool_calls, site) ⇒ Object



58
59
60
61
62
63
# File 'lib/legion/extensions/llm/canonical/response.rb', line 58

def self.normalize_tool_calls!(tool_calls, site)
  return [] if tool_calls.nil?

  Strict.expect_type!(tool_calls, [::Array], site, :tool_calls)
  tool_calls.map { |tc| tc.is_a?(ToolCall) ? tc : ToolCall.from_hash(tc) }
end

.normalize_usage!(usage, site) ⇒ Object



65
66
67
68
69
70
71
# File 'lib/legion/extensions/llm/canonical/response.rb', line 65

def self.normalize_usage!(usage, site)
  return nil if usage.nil?
  return usage if usage.is_a?(Usage)

  Strict.expect_type!(usage, [::Hash], site, :usage)
  Usage.from_hash(usage)
end

Instance Method Details

#as_jsonObject

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



79
80
81
# File 'lib/legion/extensions/llm/canonical/response.rb', line 79

def as_json(*)
  to_h
end

#error?Boolean

Whether the response ended due to an error.

Returns:

  • (Boolean)


93
94
95
# File 'lib/legion/extensions/llm/canonical/response.rb', line 93

def error?
  stop_reason == :error
end

#to_hObject

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



74
75
76
# File 'lib/legion/extensions/llm/canonical/response.rb', line 74

def to_h
  super.compact
end

#to_jsonObject



83
84
85
# File 'lib/legion/extensions/llm/canonical/response.rb', line 83

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

#tool_call?Boolean

Whether the response includes tool calls.

Returns:

  • (Boolean)


88
89
90
# File 'lib/legion/extensions/llm/canonical/response.rb', line 88

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