omniai 3.7.1 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +24 -0
- data/lib/omniai/chat/response.rb +19 -9
- data/lib/omniai/chat/usage.rb +35 -10
- data/lib/omniai/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 444922e4ffb4b54da232b01faab2b171f39ec2547830a7c02a4dc607173acae9
|
|
4
|
+
data.tar.gz: 221d5e646b968f9cb422c64547966855518351038c9f1d7248961778785dd8fc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 30da07946fab93796f28c4d831ef00f0b609051e664a1cdc01373af8e6079d857239bf26be88d5cbeb6e9d25c7c2bcc08588979e3393da661bc73057e4c34bcb
|
|
7
|
+
data.tar.gz: '09907c95ccd31c1f672f2fcc324a11a23b63799916db570a4a4fcaee4677249f430e15071a3f5e5ca34e72d867dedfc039a52527aa2dc9d6ffe7734b4f7d7dc7'
|
data/README.md
CHANGED
|
@@ -578,6 +578,30 @@ client.chat("Solve this step by step: What is 123 * 456?", thinking: true, strea
|
|
|
578
578
|
| Google | `thinking: true` | Requires Gemini 2.0+ with thinking enabled |
|
|
579
579
|
| OpenAI | `thinking: true` or `thinking: { effort: "high" }` | Requires o1/o3 models |
|
|
580
580
|
|
|
581
|
+
#### Thinking Token Accounting
|
|
582
|
+
|
|
583
|
+
Reasoning tokens are billable. `OmniAI::Chat::Usage#thinking_tokens` reports how many of a response's output tokens were internal reasoning:
|
|
584
|
+
|
|
585
|
+
```ruby
|
|
586
|
+
response = client.chat("What is 25 * 25?", thinking: true)
|
|
587
|
+
|
|
588
|
+
response.usage.output_tokens # => 1424 — billable output, reasoning included
|
|
589
|
+
response.usage.thinking_tokens # => 840 — the reasoning subset of the above
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
`thinking_tokens` is always a **subset** of `output_tokens`, never an addition to it. Adding the two together double counts.
|
|
593
|
+
|
|
594
|
+
It is `nil` when the provider reported no breakdown, which is deliberately distinct from `0` (the provider reported that no reasoning occurred). Each provider gem reads its own vocabulary:
|
|
595
|
+
|
|
596
|
+
| Provider | Populated | Read from |
|
|
597
|
+
|----------|-----------|-----------|
|
|
598
|
+
| omniai-google >= 3.12 | yes | `thoughtsTokenCount` |
|
|
599
|
+
| omniai-anthropic >= 3.6 | yes | `usage.output_tokens_details.thinking_tokens` |
|
|
600
|
+
| omniai-openai >= 3.2 | yes | `usage.output_tokens_details.reasoning_tokens` (Responses API) |
|
|
601
|
+
| omniai-mistral | no | no breakdown reported |
|
|
602
|
+
|
|
603
|
+
Note also that `total_tokens` may exceed `input_tokens + output_tokens` — providers count buckets OmniAI does not model, such as cached input and tool-use prompts — and may be `nil` where a provider reports no total. The reported total is authoritative and is never recomputed from the parts.
|
|
604
|
+
|
|
581
605
|
### 🎤 Speech to Text
|
|
582
606
|
|
|
583
607
|
Clients that support transcribe (e.g. OpenAI w/ "Whisper") convert recordings to text via the following calls:
|
data/lib/omniai/chat/response.rb
CHANGED
|
@@ -153,20 +153,30 @@ module OmniAI
|
|
|
153
153
|
# Returns aggregated usage across all responses in the chain.
|
|
154
154
|
# Walks the parent chain and sums all token counts.
|
|
155
155
|
#
|
|
156
|
+
# `total_tokens` prefers each response's provider-reported total and only falls back to `input + output` for
|
|
157
|
+
# responses where the provider reported none. Summing the reported totals matters wherever a provider counts
|
|
158
|
+
# tokens that are neither input nor output — Google's `totalTokenCount` includes thinking tokens, so
|
|
159
|
+
# recomputing unconditionally would discard them.
|
|
160
|
+
#
|
|
161
|
+
# Known limitation: Anthropic reports no total at all, so its contribution is always the derived
|
|
162
|
+
# `input + output`, which excludes `cache_creation_input_tokens` and `cache_read_input_tokens`. An aggregate
|
|
163
|
+
# spanning Anthropic responses therefore understates cache-heavy conversations.
|
|
164
|
+
#
|
|
156
165
|
# @return [Usage, nil]
|
|
157
166
|
def total_usage
|
|
158
|
-
|
|
159
|
-
usages = chain.map(&:usage).compact
|
|
167
|
+
usages = response_chain.map(&:usage).compact
|
|
160
168
|
return nil if usages.empty?
|
|
161
169
|
|
|
162
|
-
input_tokens = usages.sum { |
|
|
163
|
-
output_tokens = usages.sum { |
|
|
170
|
+
input_tokens = usages.sum { |usage| usage.input_tokens || 0 }
|
|
171
|
+
output_tokens = usages.sum { |usage| usage.output_tokens || 0 }
|
|
172
|
+
total_tokens = usages.sum do |usage|
|
|
173
|
+
usage.total_tokens || ((usage.input_tokens || 0) + (usage.output_tokens || 0))
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
thinking = usages.filter_map(&:thinking_tokens)
|
|
177
|
+
thinking_tokens = thinking.sum unless thinking.empty?
|
|
164
178
|
|
|
165
|
-
Usage.new(
|
|
166
|
-
input_tokens:,
|
|
167
|
-
output_tokens:,
|
|
168
|
-
total_tokens: input_tokens + output_tokens
|
|
169
|
-
)
|
|
179
|
+
Usage.new(input_tokens:, output_tokens:, total_tokens:, thinking_tokens:)
|
|
170
180
|
end
|
|
171
181
|
end
|
|
172
182
|
end
|
data/lib/omniai/chat/usage.rb
CHANGED
|
@@ -3,28 +3,52 @@
|
|
|
3
3
|
module OmniAI
|
|
4
4
|
class Chat
|
|
5
5
|
# The usage of a chat in terms of tokens (input / output / total).
|
|
6
|
+
#
|
|
7
|
+
# Two invariants hold across every provider:
|
|
8
|
+
#
|
|
9
|
+
# - `thinking_tokens` is a *subset* of `output_tokens`, never an addition to it. Providers either fold reasoning
|
|
10
|
+
# into their output count already (reporting the breakdown separately) or report it separately and have it
|
|
11
|
+
# added in by their own serializer. Adding `thinking_tokens` to `output_tokens` double counts.
|
|
12
|
+
# - `total_tokens` may exceed `input_tokens + output_tokens`. Providers count buckets this class does not model
|
|
13
|
+
# — cached input, tool-use prompts — so the reported total is authoritative and is never recomputed from the
|
|
14
|
+
# parts. It may also be `nil`: some providers report no total at all.
|
|
15
|
+
#
|
|
16
|
+
# Provider-specific vocabulary is read by that provider's own `:usage` deserializer, not here. This class reads
|
|
17
|
+
# only its own keys and the flat OpenAI-compatible aliases the base client speaks.
|
|
6
18
|
class Usage
|
|
7
|
-
# @return [Integer]
|
|
19
|
+
# @return [Integer, nil]
|
|
8
20
|
attr_accessor :input_tokens
|
|
9
21
|
|
|
10
|
-
# @return [Integer]
|
|
22
|
+
# @return [Integer, nil]
|
|
11
23
|
attr_accessor :output_tokens
|
|
12
24
|
|
|
13
|
-
# @return [Integer]
|
|
25
|
+
# @return [Integer, nil]
|
|
14
26
|
attr_accessor :total_tokens
|
|
15
27
|
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
|
|
28
|
+
# The subset of `output_tokens` a provider attributes to internal reasoning ("thinking"). `nil` when the
|
|
29
|
+
# provider does not report a breakdown — which is distinct from `0`, meaning the provider reported that no
|
|
30
|
+
# reasoning occurred.
|
|
31
|
+
#
|
|
32
|
+
# @return [Integer, nil]
|
|
33
|
+
attr_accessor :thinking_tokens
|
|
34
|
+
|
|
35
|
+
# @param input_tokens [Integer, nil]
|
|
36
|
+
# @param output_tokens [Integer, nil]
|
|
37
|
+
# @param total_tokens [Integer, nil]
|
|
38
|
+
# @param thinking_tokens [Integer, nil] optional
|
|
39
|
+
def initialize(input_tokens:, output_tokens:, total_tokens:, thinking_tokens: nil)
|
|
20
40
|
@input_tokens = input_tokens
|
|
21
41
|
@output_tokens = output_tokens
|
|
22
42
|
@total_tokens = total_tokens
|
|
43
|
+
@thinking_tokens = thinking_tokens
|
|
23
44
|
end
|
|
24
45
|
|
|
25
46
|
# @return [String]
|
|
26
47
|
def inspect
|
|
27
|
-
"#<#{self.class.name} input_tokens=#{input_tokens} output_tokens=#{output_tokens}
|
|
48
|
+
text = "#<#{self.class.name} input_tokens=#{input_tokens} output_tokens=#{output_tokens} " \
|
|
49
|
+
"total_tokens=#{total_tokens}"
|
|
50
|
+
text += " thinking_tokens=#{thinking_tokens}" unless thinking_tokens.nil?
|
|
51
|
+
"#{text}>"
|
|
28
52
|
end
|
|
29
53
|
|
|
30
54
|
# @param data [Hash]
|
|
@@ -38,8 +62,9 @@ module OmniAI
|
|
|
38
62
|
input_tokens = data["input_tokens"] || data["prompt_tokens"]
|
|
39
63
|
output_tokens = data["output_tokens"] || data["completion_tokens"]
|
|
40
64
|
total_tokens = data["total_tokens"]
|
|
65
|
+
thinking_tokens = data["thinking_tokens"]
|
|
41
66
|
|
|
42
|
-
new(input_tokens:, output_tokens:, total_tokens:)
|
|
67
|
+
new(input_tokens:, output_tokens:, total_tokens:, thinking_tokens:)
|
|
43
68
|
end
|
|
44
69
|
|
|
45
70
|
# @param context [OmniAI::Context] optional
|
|
@@ -53,7 +78,7 @@ module OmniAI
|
|
|
53
78
|
input_tokens:,
|
|
54
79
|
output_tokens:,
|
|
55
80
|
total_tokens:,
|
|
56
|
-
}
|
|
81
|
+
}.tap { |data| data[:thinking_tokens] = thinking_tokens unless thinking_tokens.nil? }
|
|
57
82
|
end
|
|
58
83
|
end
|
|
59
84
|
end
|
data/lib/omniai/version.rb
CHANGED