Class: OmniAI::Chat::Response

Inherits:
Object
  • Object
show all
Defined in:
lib/omniai/chat/response.rb

Overview

An ‘OmniAI::Chat::Response` encapsulates the result of generating a chat completion.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data:, choices: [], usage: nil, finish_reason: nil) ⇒ Response

Returns a new instance of Response.

Parameters:

  • data (Hash)
  • choices (Array<Choice>) (defaults to: [])
  • usage (Usage, nil) (defaults to: nil)
  • finish_reason (FinishReason, nil) (defaults to: nil)

    an optional response-level finish reason (used by providers that expose it at the response level, e.g. OpenAI’s Responses API); when omitted, ‘#finish_reason` falls back to the first choice’s finish reason.



29
30
31
32
33
34
# File 'lib/omniai/chat/response.rb', line 29

def initialize(data:, choices: [], usage: nil, finish_reason: nil)
  @data = data
  @choices = choices
  @usage = usage
  @finish_reason = finish_reason
end

Instance Attribute Details

#choicesObject

Returns the value of attribute choices.



17
18
19
# File 'lib/omniai/chat/response.rb', line 17

def choices
  @choices
end

#dataObject

Returns the value of attribute data.



9
10
11
# File 'lib/omniai/chat/response.rb', line 9

def data
  @data
end

#finish_reasonFinishReason?

The normalized FinishReason for the final turn (carrying both ‘#reason` and the verbatim provider `#value`), or `nil` when absent. Some providers (e.g. OpenAI’s Responses API) expose this at the response level; most expose it per-choice. Prefers an explicit response-level value, then falls back to the first choice. Reflects this response only (the final turn) — it is not aggregated across the parent chain, unlike #total_usage.

Returns:



42
43
44
# File 'lib/omniai/chat/response.rb', line 42

def finish_reason
  @finish_reason || @choices.first&.finish_reason
end

#usageObject

Returns the value of attribute usage.



13
14
15
# File 'lib/omniai/chat/response.rb', line 13

def usage
  @usage
end

Class Method Details

.deserialize(data, context: nil) ⇒ OmniAI::Chat::Response

Parameters:

Returns:



58
59
60
61
62
63
64
65
66
# File 'lib/omniai/chat/response.rb', line 58

def self.deserialize(data, context: nil)
  deserialize = context&.deserializer(:response)
  return deserialize.call(data, context:) if deserialize

  choices = data["choices"].map { |choice_data| Choice.deserialize(choice_data, context:) }
  usage = Usage.deserialize(data["usage"], context:) if data["usage"]

  new(data:, choices:, usage:)
end

Instance Method Details

#=( = (value)) ⇒ Array<Choice>

Returns:



9
# File 'lib/omniai/chat/response.rb', line 9

attr_accessor :data

#inspectString

Returns:

  • (String)


50
51
52
# File 'lib/omniai/chat/response.rb', line 50

def inspect
  "#<#{self.class.name} choices=#{@choices.inspect} usage=#{@usage.inspect}>"
end

Links this response chain to a parent response. Walks up to find the oldest response (no parent) and sets its parent.

Parameters:



132
133
134
135
136
# File 'lib/omniai/chat/response.rb', line 132

def link_to(parent)
  current = self
  current = current.parent while current.parent
  current.parent = parent
end

#messagesArray<Message>

Returns:



82
83
84
# File 'lib/omniai/chat/response.rb', line 82

def messages
  @choices.map(&:message).compact
end

#messages?Boolean

Returns:

  • (Boolean)


87
88
89
# File 'lib/omniai/chat/response.rb', line 87

def messages?
  messages.any?
end

#parentObject



21
22
23
# File 'lib/omniai/chat/response.rb', line 21

def parent
  @parent
end

#response_chainArray<Response>

Returns the chain of responses from oldest (first) to newest (self).

Returns:



141
142
143
144
145
146
147
148
149
150
151
# File 'lib/omniai/chat/response.rb', line 141

def response_chain
  chain = []
  current = self

  while current
    chain.unshift(current)
    current = current.parent
  end

  chain
end

#serialize(context:) ⇒ Hash

Parameters:

Returns:

  • (Hash)


71
72
73
74
75
76
77
78
79
# File 'lib/omniai/chat/response.rb', line 71

def serialize(context:)
  serialize = context&.serializer(:response)
  return serialize.call(self, context:) if serialize

  {
    choices: @choices.map { |choice| choice.serialize(context:) },
    usage: @usage&.serialize(context:),
  }
end

#textString?

Returns:

  • (String, nil)


92
93
94
95
96
# File 'lib/omniai/chat/response.rb', line 92

def text
  return unless text?

  messages.filter(&:text?).map(&:text).join("\n\n")
end

#text?Boolean

Returns:

  • (Boolean)


99
100
101
# File 'lib/omniai/chat/response.rb', line 99

def text?
  messages.any?(&:text?)
end

#thinkingString?

Returns:

  • (String, nil)


104
105
106
107
108
# File 'lib/omniai/chat/response.rb', line 104

def thinking
  return unless thinking?

  messages.filter(&:thinking?).map(&:thinking).join("\n\n")
end

#thinking?Boolean

Returns:

  • (Boolean)


111
112
113
# File 'lib/omniai/chat/response.rb', line 111

def thinking?
  messages.any?(&:thinking?)
end

#tool_call_listToolCallList?

Returns:



116
117
118
119
120
121
# File 'lib/omniai/chat/response.rb', line 116

def tool_call_list
  tool_call_lists = messages.filter(&:tool_call_list?).map(&:tool_call_list)
  return if tool_call_lists.empty?

  tool_call_lists.reduce(&:+)
end

#tool_call_list?Boolean

Returns:

  • (Boolean)


124
125
126
# File 'lib/omniai/chat/response.rb', line 124

def tool_call_list?
  !tool_call_list.nil?
end

#total_usageUsage?

Returns aggregated usage across all responses in the chain. Walks the parent chain and sums all token counts.

Returns:



157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/omniai/chat/response.rb', line 157

def total_usage
  chain = response_chain
  usages = chain.map(&:usage).compact
  return nil if usages.empty?

  input_tokens = usages.sum { |u| u.input_tokens || 0 }
  output_tokens = usages.sum { |u| u.output_tokens || 0 }

  Usage.new(
    input_tokens:,
    output_tokens:,
    total_tokens: input_tokens + output_tokens
  )
end