Class: OmniAI::Chat::Response
- Inherits:
-
Object
- Object
- OmniAI::Chat::Response
- Defined in:
- lib/omniai/chat/response.rb
Overview
An OmniAI::Chat::Response encapsulates the result of generating a chat completion.
Instance Attribute Summary collapse
-
#choices ⇒ Object
Returns the value of attribute choices.
-
#data ⇒ Object
Returns the value of attribute data.
-
#finish_reason ⇒ FinishReason?
The normalized FinishReason for the final turn (carrying both
#reasonand the verbatim provider#value), ornilwhen absent. -
#usage ⇒ Object
Returns the value of attribute usage.
Class Method Summary collapse
Instance Method Summary collapse
- #=( = (value)) ⇒ Array<Choice>
-
#initialize(data:, choices: [], usage: nil, finish_reason: nil) ⇒ Response
constructor
A new instance of Response.
- #inspect ⇒ String
-
#link_to(parent) ⇒ Object
Links this response chain to a parent response.
- #messages ⇒ Array<Message>
- #messages? ⇒ Boolean
- #parent ⇒ Object
-
#response_chain ⇒ Array<Response>
Returns the chain of responses from oldest (first) to newest (self).
- #serialize(context:) ⇒ Hash
- #text ⇒ String?
- #text? ⇒ Boolean
- #thinking ⇒ String?
- #thinking? ⇒ Boolean
- #tool_call_list ⇒ ToolCallList?
- #tool_call_list? ⇒ Boolean
-
#total_usage ⇒ Usage?
Returns aggregated usage across all responses in the chain.
Constructor Details
#initialize(data:, choices: [], usage: nil, finish_reason: nil) ⇒ Response
Returns a new instance of Response.
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
#choices ⇒ Object
Returns the value of attribute choices.
17 18 19 |
# File 'lib/omniai/chat/response.rb', line 17 def choices @choices end |
#data ⇒ Object
Returns the value of attribute data.
9 10 11 |
# File 'lib/omniai/chat/response.rb', line 9 def data @data end |
#finish_reason ⇒ FinishReason?
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.
42 43 44 |
# File 'lib/omniai/chat/response.rb', line 42 def finish_reason @finish_reason || @choices.first&.finish_reason end |
#usage ⇒ Object
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
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
#inspect ⇒ String
50 51 52 |
# File 'lib/omniai/chat/response.rb', line 50 def inspect "#<#{self.class.name} choices=#{@choices.inspect} usage=#{@usage.inspect}>" end |
#link_to(parent) ⇒ Object
Links this response chain to a parent response. Walks up to find the oldest response (no parent) and sets its parent.
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 |
#messages ⇒ Array<Message>
82 83 84 |
# File 'lib/omniai/chat/response.rb', line 82 def @choices.map(&:message).compact end |
#messages? ⇒ Boolean
87 88 89 |
# File 'lib/omniai/chat/response.rb', line 87 def .any? end |
#parent ⇒ Object
21 22 23 |
# File 'lib/omniai/chat/response.rb', line 21 def parent @parent end |
#response_chain ⇒ Array<Response>
Returns the chain of responses from oldest (first) to newest (self).
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
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 |
#text ⇒ String?
92 93 94 95 96 |
# File 'lib/omniai/chat/response.rb', line 92 def text return unless text? .filter(&:text?).map(&:text).join("\n\n") end |
#text? ⇒ Boolean
99 100 101 |
# File 'lib/omniai/chat/response.rb', line 99 def text? .any?(&:text?) end |
#thinking ⇒ String?
104 105 106 107 108 |
# File 'lib/omniai/chat/response.rb', line 104 def thinking return unless thinking? .filter(&:thinking?).map(&:thinking).join("\n\n") end |
#thinking? ⇒ Boolean
111 112 113 |
# File 'lib/omniai/chat/response.rb', line 111 def thinking? .any?(&:thinking?) end |
#tool_call_list ⇒ ToolCallList?
116 117 118 119 120 121 |
# File 'lib/omniai/chat/response.rb', line 116 def tool_call_list tool_call_lists = .filter(&:tool_call_list?).map(&:tool_call_list) return if tool_call_lists.empty? tool_call_lists.reduce(&:+) end |
#tool_call_list? ⇒ Boolean
124 125 126 |
# File 'lib/omniai/chat/response.rb', line 124 def tool_call_list? !tool_call_list.nil? end |
#total_usage ⇒ Usage?
Returns aggregated usage across all responses in the chain. Walks the parent chain and sums all token counts.
total_tokens prefers each response's provider-reported total and only falls back to input + output for
responses where the provider reported none. Summing the reported totals matters wherever a provider counts
tokens that are neither input nor output — Google's totalTokenCount includes thinking tokens, so
recomputing unconditionally would discard them.
Known limitation: Anthropic reports no total at all, so its contribution is always the derived
input + output, which excludes cache_creation_input_tokens and cache_read_input_tokens. An aggregate
spanning Anthropic responses therefore understates cache-heavy conversations.
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/omniai/chat/response.rb', line 166 def total_usage usages = response_chain.map(&:usage).compact return nil if usages.empty? input_tokens = usages.sum { |usage| usage.input_tokens || 0 } output_tokens = usages.sum { |usage| usage.output_tokens || 0 } total_tokens = usages.sum do |usage| usage.total_tokens || ((usage.input_tokens || 0) + (usage.output_tokens || 0)) end thinking = usages.filter_map(&:thinking_tokens) thinking_tokens = thinking.sum unless thinking.empty? Usage.new(input_tokens:, output_tokens:, total_tokens:, thinking_tokens:) end |