ruby_llm-agents 3.14.0 → 3.14.1
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4734a9b91b3dc228b78661cdbe4e336b9053beb6440601482709f99745c6e918
|
|
4
|
+
data.tar.gz: de29b3452e781a363c3330137323364edca7e80afd55680f200161c464d3f027
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 180db730cb98f426ce7e774cad42ad146b33cbb09b74340b772a2306cc0249152adbee79da9de71d372eb5f925ebf894db8305ef9df2ca58c60104280fec03e3
|
|
7
|
+
data.tar.gz: 2b2183165d6e2271ffda0b673ed173abde8cd77d8adc6e85b168de24b9744bc6c51ebe2194fcc45ad7d3e3fb35b82603f7830260927b2fabe763c552b93691cd
|
|
@@ -934,11 +934,18 @@ module RubyLLM
|
|
|
934
934
|
context.output_tokens = metadata.output_tokens if metadata.respond_to?(:output_tokens)
|
|
935
935
|
context.model_used = (metadata.respond_to?(:model_id) && metadata.model_id) || model
|
|
936
936
|
|
|
937
|
-
#
|
|
937
|
+
# Prompt-cache metrics. Every provider that reports them lands here —
|
|
938
|
+
# not just Anthropic: OpenAI caches automatically with no opt-in, so
|
|
939
|
+
# these are populated on ordinary calls with a long stable prefix.
|
|
940
|
+
# Kept on the context's own accessors (and mirrored into metadata for
|
|
941
|
+
# backward compatibility) because #build_result and #calculate_costs
|
|
942
|
+
# both read them back.
|
|
938
943
|
if metadata.respond_to?(:cached_tokens) && metadata.cached_tokens&.positive?
|
|
944
|
+
context.cached_tokens = metadata.cached_tokens
|
|
939
945
|
context[:cached_tokens] = metadata.cached_tokens
|
|
940
946
|
end
|
|
941
947
|
if metadata.respond_to?(:cache_creation_tokens) && metadata.cache_creation_tokens&.positive?
|
|
948
|
+
context.cache_creation_tokens = metadata.cache_creation_tokens
|
|
942
949
|
context[:cache_creation_tokens] = metadata.cache_creation_tokens
|
|
943
950
|
end
|
|
944
951
|
else
|
|
@@ -1001,7 +1008,7 @@ module RubyLLM
|
|
|
1001
1008
|
input_price = model_info.pricing&.text_tokens&.input || 0
|
|
1002
1009
|
output_price = model_info.pricing&.text_tokens&.output || 0
|
|
1003
1010
|
|
|
1004
|
-
context.input_cost = (input_tokens
|
|
1011
|
+
context.input_cost = input_cost_for(input_tokens, input_price, model_info, context)
|
|
1005
1012
|
|
|
1006
1013
|
# Price cache/reasoning extras first so we know whether reasoning was
|
|
1007
1014
|
# actually billed at the reasoning rate. Only then exclude those tokens
|
|
@@ -1014,6 +1021,75 @@ module RubyLLM
|
|
|
1014
1021
|
context.total_cost = (context.input_cost + context.output_cost + extra).round(6)
|
|
1015
1022
|
end
|
|
1016
1023
|
|
|
1024
|
+
# Input charge, with cache reads billed at the cached rate rather than the
|
|
1025
|
+
# full one.
|
|
1026
|
+
#
|
|
1027
|
+
# Providers disagree on whether the reported input_tokens ALREADY contains
|
|
1028
|
+
# the cache reads, and the difference decides who has a bug:
|
|
1029
|
+
#
|
|
1030
|
+
# OpenAI prompt_tokens INCLUDES prompt_tokens_details.cached_tokens
|
|
1031
|
+
# Gemini promptTokenCount INCLUDES cachedContentTokenCount
|
|
1032
|
+
# Anthropic input_tokens EXCLUDES cache_read_input_tokens
|
|
1033
|
+
#
|
|
1034
|
+
# On Anthropic the cache read is genuinely additive, so charging the full
|
|
1035
|
+
# input_tokens at full rate and letting #extra_token_costs add cache_read
|
|
1036
|
+
# on top is already correct — that path is left untouched.
|
|
1037
|
+
#
|
|
1038
|
+
# On OpenAI and Gemini the cached tokens are sitting inside input_tokens,
|
|
1039
|
+
# so charging them at the full rate overstates spend by 2-3x on exactly
|
|
1040
|
+
# the workload caching exists for (a long stable system prompt replayed
|
|
1041
|
+
# every turn) — and would double-charge outright once the provider also
|
|
1042
|
+
# reports a cache_read cost component. Those get split here instead.
|
|
1043
|
+
#
|
|
1044
|
+
# Falls back to the full input rate whenever the split can't be made
|
|
1045
|
+
# safely (no cache reported, or no cached price in the registry), so an
|
|
1046
|
+
# unknown model is never under-billed.
|
|
1047
|
+
#
|
|
1048
|
+
# @return [Float] The input cost in USD
|
|
1049
|
+
def input_cost_for(input_tokens, input_price, model_info, context)
|
|
1050
|
+
full_rate_charge = (input_tokens / 1_000_000.0) * input_price
|
|
1051
|
+
|
|
1052
|
+
cached_tokens = context.cached_tokens.to_i
|
|
1053
|
+
return full_rate_charge unless cached_tokens.positive?
|
|
1054
|
+
return full_rate_charge unless cached_tokens_included_in_input?(model_info)
|
|
1055
|
+
|
|
1056
|
+
cached_price = cached_input_price(model_info)
|
|
1057
|
+
return full_rate_charge unless cached_price
|
|
1058
|
+
|
|
1059
|
+
# Tells extra_token_costs not to charge these same reads a second time.
|
|
1060
|
+
context[:cache_read_priced] = true
|
|
1061
|
+
|
|
1062
|
+
uncached_tokens = [input_tokens - cached_tokens, 0].max
|
|
1063
|
+
(uncached_tokens / 1_000_000.0) * input_price +
|
|
1064
|
+
(cached_tokens / 1_000_000.0) * cached_price
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
# Per-million price for a cache read, or nil when the model registry
|
|
1068
|
+
# doesn't publish one. Pricing shapes vary by source (and are stubbed with
|
|
1069
|
+
# partial doubles in tests), so an absent field degrades to "unknown" —
|
|
1070
|
+
# callers then charge the full input rate rather than guessing a discount.
|
|
1071
|
+
#
|
|
1072
|
+
# @return [Float, nil]
|
|
1073
|
+
def cached_input_price(model_info)
|
|
1074
|
+
tier = model_info.pricing&.text_tokens
|
|
1075
|
+
return nil unless tier.respond_to?(:cached_input)
|
|
1076
|
+
|
|
1077
|
+
tier.cached_input
|
|
1078
|
+
rescue
|
|
1079
|
+
nil
|
|
1080
|
+
end
|
|
1081
|
+
|
|
1082
|
+
# Whether this provider's reported input_tokens already contains the cache
|
|
1083
|
+
# reads. See #input_cost_for for the per-provider breakdown. Anthropic is
|
|
1084
|
+
# the exception; defaulting the unknown case to "included" matches every
|
|
1085
|
+
# other provider RubyLLM supports.
|
|
1086
|
+
#
|
|
1087
|
+
# @return [Boolean]
|
|
1088
|
+
def cached_tokens_included_in_input?(model_info)
|
|
1089
|
+
provider = model_info.respond_to?(:provider) ? model_info.provider.to_s : ""
|
|
1090
|
+
!provider.include?("anthropic")
|
|
1091
|
+
end
|
|
1092
|
+
|
|
1017
1093
|
# Number of reasoning (thinking) tokens that were actually charged at the
|
|
1018
1094
|
# reasoning rate, recorded in the cost breakdown by +extra_token_costs+.
|
|
1019
1095
|
#
|
|
@@ -1050,8 +1126,12 @@ module RubyLLM
|
|
|
1050
1126
|
cost = response_cost(response, model_info)
|
|
1051
1127
|
return 0.0 unless cost
|
|
1052
1128
|
|
|
1129
|
+
# Skip cache_read when #input_cost_for already billed the cache at the
|
|
1130
|
+
# cached rate — otherwise the same tokens are charged twice (once inside
|
|
1131
|
+
# input_tokens at full rate, once here). Cache WRITES are always extra:
|
|
1132
|
+
# no provider folds them into input_tokens.
|
|
1053
1133
|
components = {
|
|
1054
|
-
cache_read: cost.cache_read,
|
|
1134
|
+
cache_read: (cost.cache_read unless context[:cache_read_priced]),
|
|
1055
1135
|
cache_write: cost.cache_write,
|
|
1056
1136
|
thinking: cost.thinking
|
|
1057
1137
|
}.compact.reject { |_, value| value.zero? }
|
|
@@ -1156,6 +1236,8 @@ module RubyLLM
|
|
|
1156
1236
|
agent_class_name: self.class.name,
|
|
1157
1237
|
input_tokens: context.input_tokens,
|
|
1158
1238
|
output_tokens: context.output_tokens,
|
|
1239
|
+
cached_tokens: context.cached_tokens,
|
|
1240
|
+
cache_creation_tokens: context.cache_creation_tokens,
|
|
1159
1241
|
input_cost: context.input_cost,
|
|
1160
1242
|
output_cost: context.output_cost,
|
|
1161
1243
|
total_cost: context.total_cost,
|
|
@@ -43,6 +43,11 @@ module RubyLLM
|
|
|
43
43
|
# Cost tracking
|
|
44
44
|
attr_accessor :input_tokens, :output_tokens, :input_cost, :output_cost, :total_cost
|
|
45
45
|
|
|
46
|
+
# Prompt-cache usage. First-class accessors, not metadata keys: these
|
|
47
|
+
# feed both the persisted execution record and the input-cost split, so
|
|
48
|
+
# a generic bag entry would be silently droppable (it was).
|
|
49
|
+
attr_accessor :cached_tokens, :cache_creation_tokens
|
|
50
|
+
|
|
46
51
|
# Response metadata
|
|
47
52
|
attr_accessor :model_used, :finish_reason, :time_to_first_token_ms
|
|
48
53
|
|