omniai-anthropic 3.6.0 → 3.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 70d172e3b288451b57742351a6db872672b84a7a9f0b139e2466299b17ef3a2e
4
- data.tar.gz: b83fb955581badb868e95b99caee30d1b3e2aa9203a01bcd137b7b0517d14cf3
3
+ metadata.gz: 20cd61e7e110c59273cb86062c34630d07d66b14a5da7be33cfb11f3a3f29514
4
+ data.tar.gz: 44dfedfa87d0cb07ca36fc155efd5ddb8507eef146202a41e98be1c1bbd0010c
5
5
  SHA512:
6
- metadata.gz: 26ef3bb07ec0aae3544ec8008a745d754a0a2044645836b26507b1be5c148c341877763281826a8745e5a93df6534032454d38b95d4b062a926875fb32b6e263
7
- data.tar.gz: 10efb98acf0f9ee712568d4c1a4cf7087c32bbaae0ec82b28dec5d046937803595483df3a6039dd042c28653a346a8cdfb1866329ef149eb6feec831cfde8c63
6
+ metadata.gz: 815f8ed94a51cdbe222754218ab865a17227971e6d58d0c39aa6a87425919127af507871bd25263f355d22c5e46c5849c2bc34d6f3a955b6cebc6a06ce944c49
7
+ data.tar.gz: c4303f54ed395240b18c054f1776ac680323c89ba609c9c0c8dba57f5ad0c641d71eaa34a4713d9d6cc3356fdda59d4f020a186bc7b0d6aca5ec66f9bf2fe34d
data/README.md CHANGED
@@ -170,3 +170,21 @@ client.chat("What are the prime factors of 1234567?", model: "claude-sonnet-4-20
170
170
  The thinking content will stream first, followed by the response.
171
171
 
172
172
  [Anthropic API Reference `thinking`](https://docs.anthropic.com/en/docs/build-with-claude/thinking)
173
+
174
+ ### Prompt Caching
175
+
176
+ Prompt caching is opt-in. When enabled, the system prompt (or the last tool, when there is no system prompt) and the last block of the last message are marked with `cache_control`, so each round of a tool-call loop reads the history the previous round wrote.
177
+
178
+ ```ruby
179
+ client.chat(prompt, tools:, cache: true) # 5-minute TTL
180
+ client.chat(prompt, tools:, cache: { ttl: "1h" }) # 1-hour TTL
181
+ ```
182
+
183
+ `usage.input_tokens` reports the whole prompt, including cached tokens. The cache breakdown is on each response's raw usage:
184
+
185
+ ```ruby
186
+ response.response_chain.sum { |r| r.data.dig("usage", "cache_read_input_tokens").to_i }
187
+ response.response_chain.sum { |r| r.data.dig("usage", "cache_creation_input_tokens").to_i }
188
+ ```
189
+
190
+ [Anthropic API Reference `prompt caching`](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching)
@@ -24,6 +24,11 @@ module OmniAI
24
24
  thinking_tokens = data.dig("output_tokens_details", "thinking_tokens")
25
25
  usage.thinking_tokens = thinking_tokens unless thinking_tokens.nil?
26
26
 
27
+ # Anthropic's `input_tokens` excludes cache reads and writes; adding them back keeps it the whole prompt, as
28
+ # for every other provider. A base-serialized payload has neither key and is already whole.
29
+ cache_tokens = data.values_at("cache_creation_input_tokens", "cache_read_input_tokens").compact
30
+ usage.input_tokens = (usage.input_tokens || 0) + cache_tokens.sum if cache_tokens.any?
31
+
27
32
  usage
28
33
  end
29
34
  end
@@ -165,19 +165,40 @@ module OmniAI
165
165
  end
166
166
  end
167
167
 
168
+ # When caching, the last block of the last message is marked so each tool-loop round reads the history the
169
+ # previous round wrote.
170
+ #
168
171
  # @return [Array<Hash>]
169
172
  def messages
170
- messages = @prompt.messages.reject(&:system?)
171
- messages.map { |message| message.serialize(context:) }
173
+ messages = @prompt.messages.reject(&:system?).map { |message| message.serialize(context:) }
174
+ return messages unless cache_control && messages.any?
175
+
176
+ *history, last = messages
177
+ history + [last.merge(content: with_cache_control(last[:content]))]
172
178
  end
173
179
 
174
- # @return [String, nil]
180
+ # When caching, a single marked text block. Tools render before system, so this breakpoint covers both.
181
+ #
182
+ # @return [String, Array<Hash>, nil]
175
183
  def system
176
184
  parts = @prompt.messages.filter(&:system?).filter(&:text?).map(&:text)
177
185
  parts << formatting if formatting?
178
186
  return if parts.empty?
179
187
 
180
- parts.join("\n\n")
188
+ text = parts.join("\n\n")
189
+ cache_control ? [{ type: "text", text:, cache_control: }] : text
190
+ end
191
+
192
+ # Translates the opt-in `cache` option to Anthropic's `cache_control`.
193
+ # Example: `cache: true` becomes `{ type: "ephemeral" }` (5-minute TTL)
194
+ # Example: `cache: { ttl: "1h" }` becomes `{ type: "ephemeral", ttl: "1h" }`
195
+ #
196
+ # @return [Hash, nil]
197
+ def cache_control
198
+ case @options[:cache]
199
+ when true then { type: "ephemeral" }
200
+ when Hash then { type: "ephemeral" }.merge(@options[:cache])
201
+ end
181
202
  end
182
203
 
183
204
  # @return [String]
@@ -217,9 +238,25 @@ module OmniAI
217
238
  end
218
239
  end
219
240
 
241
+ # When caching without a system prompt, the last tool carries the breakpoint instead.
242
+ #
220
243
  # @return [Array<Hash>, nil]
221
244
  def tools_payload
222
- @tools.map { |tool| tool.serialize(context:) } if @tools&.any?
245
+ return unless @tools&.any?
246
+
247
+ tools = @tools.map { |tool| tool.serialize(context:) }
248
+ cache_control && system.nil? ? with_cache_control(tools) : tools
249
+ end
250
+
251
+ # Relies on `MessageSerializer` normalizing every content part into a block Hash; a bare String would raise.
252
+ #
253
+ # @param blocks [Array<Hash>]
254
+ # @return [Array<Hash>]
255
+ def with_cache_control(blocks)
256
+ return blocks if blocks.empty?
257
+
258
+ *head, last = blocks
259
+ head + [last.merge(cache_control:)]
223
260
  end
224
261
 
225
262
  # @return [Boolean]
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OmniAI
4
4
  module Anthropic
5
- VERSION = "3.6.0"
5
+ VERSION = "3.7.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniai-anthropic
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.0
4
+ version: 3.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Sylvestre