Skip to content

Commit 1ecc85f

Browse files
committed
fix(accounting): infer pricing for suffixed model ids
1 parent 75d5401 commit 1ecc85f

3 files changed

Lines changed: 599 additions & 13 deletions

File tree

lib/codex_pooler/accounting/pricing_resolution.ex

Lines changed: 250 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ defmodule CodexPooler.Accounting.PricingResolution do
1111
@default_price_bucket "default"
1212
@long_context_price_bucket "long_context"
1313

14+
@typep suffix_candidate :: %{
15+
from: String.t(),
16+
to: String.t(),
17+
distance: pos_integer()
18+
}
19+
1420
@spec lookup(Model.t(), String.t(), map(), map(), DateTime.t()) :: map()
1521
def lookup(%Model{} = model, requested_model, payload, opts, timestamp) do
1622
requested_tier = requested_service_tier(payload, opts)
@@ -92,7 +98,7 @@ defmodule CodexPooler.Accounting.PricingResolution do
9298
def metadata(pricing) do
9399
snapshot = pricing.snapshot
94100

95-
%{
101+
pricing_metadata = %{
96102
"status" => pricing.status,
97103
"requested_service_tier" => pricing.requested_service_tier,
98104
"actual_service_tier" => pricing.actual_service_tier,
@@ -110,13 +116,15 @@ defmodule CodexPooler.Accounting.PricingResolution do
110116
else: nil
111117
)
112118
}
119+
120+
put_serialized_alias_metadata(pricing_metadata, Map.get(pricing, :alias))
113121
end
114122

115123
@spec details(map()) :: map()
116124
def details(pricing) do
117125
snapshot = pricing.snapshot
118126

119-
%{
127+
details = %{
120128
"pricing_status" => pricing.status,
121129
"requested_service_tier" => pricing.requested_service_tier,
122130
"actual_service_tier" => pricing.actual_service_tier,
@@ -126,6 +134,8 @@ defmodule CodexPooler.Accounting.PricingResolution do
126134
"batch_usage" => pricing.batch_usage,
127135
"price_version" => snapshot && snapshot.price_version
128136
}
137+
138+
put_serialized_alias_metadata(details, Map.get(pricing, :alias))
129139
end
130140

131141
@spec update_request_metadata(map() | nil, map()) :: map()
@@ -216,15 +226,39 @@ defmodule CodexPooler.Accounting.PricingResolution do
216226
priced_snapshot(fallback, requested_tier, actual_tier, service_tier, batch_usage?)
217227

218228
nil ->
219-
missing_pricing_snapshot(
220-
model,
221-
identifiers,
222-
requested_tier,
223-
actual_tier,
224-
service_tier,
225-
timestamp,
226-
batch_usage?
227-
)
229+
case suffix_inference_pricing_snapshot(
230+
identifiers,
231+
service_tier,
232+
price_bucket,
233+
timestamp
234+
) ||
235+
fallback_suffix_inference_pricing_snapshot(
236+
identifiers,
237+
service_tier,
238+
price_bucket,
239+
timestamp
240+
) do
241+
{%PricingSnapshot{} = inferred, alias_metadata} ->
242+
priced_snapshot(
243+
inferred,
244+
requested_tier,
245+
actual_tier,
246+
service_tier,
247+
batch_usage?,
248+
alias_metadata
249+
)
250+
251+
nil ->
252+
missing_pricing_snapshot(
253+
model,
254+
identifiers,
255+
requested_tier,
256+
actual_tier,
257+
service_tier,
258+
timestamp,
259+
batch_usage?
260+
)
261+
end
228262
end
229263
end
230264
end
@@ -252,6 +286,190 @@ defmodule CodexPooler.Accounting.PricingResolution do
252286

253287
defp fallback_pricing_snapshot(_identifiers, _service_tier, _price_bucket, _timestamp), do: nil
254288

289+
@spec fallback_suffix_inference_pricing_snapshot(
290+
[String.t()],
291+
String.t(),
292+
String.t(),
293+
DateTime.t()
294+
) :: {%PricingSnapshot{}, map()} | nil
295+
defp fallback_suffix_inference_pricing_snapshot(
296+
identifiers,
297+
service_tier,
298+
@long_context_price_bucket,
299+
timestamp
300+
),
301+
do:
302+
suffix_inference_pricing_snapshot(
303+
identifiers,
304+
service_tier,
305+
@default_price_bucket,
306+
timestamp
307+
)
308+
309+
defp fallback_suffix_inference_pricing_snapshot(
310+
_identifiers,
311+
_service_tier,
312+
_price_bucket,
313+
_timestamp
314+
),
315+
do: nil
316+
317+
@spec suffix_inference_pricing_snapshot([String.t()], String.t(), String.t(), DateTime.t()) ::
318+
{%PricingSnapshot{}, map()} | nil
319+
defp suffix_inference_pricing_snapshot(identifiers, service_tier, price_bucket, timestamp) do
320+
candidates = suffix_inference_candidates(identifiers)
321+
candidate_identifiers = candidates |> Enum.map(& &1.to) |> Enum.uniq()
322+
323+
snapshots_by_identifier =
324+
pricing_snapshots_by_identifier(
325+
candidate_identifiers,
326+
service_tier,
327+
price_bucket,
328+
timestamp
329+
)
330+
331+
candidates
332+
|> Enum.flat_map(&candidate_snapshot_match(&1, snapshots_by_identifier))
333+
|> nearest_suffix_inference_match()
334+
end
335+
336+
@spec pricing_snapshots_by_identifier([String.t()], String.t(), String.t(), DateTime.t()) ::
337+
map()
338+
defp pricing_snapshots_by_identifier([], _service_tier, _price_bucket, _timestamp), do: %{}
339+
340+
defp pricing_snapshots_by_identifier(identifiers, service_tier, price_bucket, timestamp) do
341+
PricingSnapshot
342+
|> where(
343+
[ps],
344+
ps.model_identifier in ^identifiers and ps.effective_at <= ^timestamp and
345+
fragment("?->>'service_tier'", ps.config) == ^service_tier and
346+
fragment("?->>'price_bucket'", ps.config) == ^price_bucket and
347+
fragment("?->>'pricing_type'", ps.config) == "per_1m_tokens"
348+
)
349+
|> order_by([ps],
350+
asc: ps.model_identifier,
351+
desc: ps.effective_at,
352+
desc: ps.captured_at,
353+
desc: ps.id
354+
)
355+
|> Repo.all()
356+
|> Enum.reduce(%{}, fn snapshot, snapshots ->
357+
Map.put_new(snapshots, snapshot.model_identifier, snapshot)
358+
end)
359+
end
360+
361+
@spec suffix_inference_candidates([String.t()]) :: [suffix_candidate()]
362+
defp suffix_inference_candidates(identifiers) do
363+
exact_identifiers = MapSet.new(identifiers)
364+
365+
identifiers
366+
|> Enum.flat_map(&suffix_inference_candidates_for_identifier/1)
367+
|> Enum.reject(&MapSet.member?(exact_identifiers, &1.to))
368+
|> Enum.uniq_by(fn candidate -> {candidate.from, candidate.to, candidate.distance} end)
369+
end
370+
371+
@spec suffix_inference_candidates_for_identifier(term()) :: [suffix_candidate()]
372+
defp suffix_inference_candidates_for_identifier(identifier) when is_binary(identifier) do
373+
normalized_identifier = String.trim(identifier)
374+
segments = String.split(normalized_identifier, "-", trim: true)
375+
376+
case segments do
377+
[_single] ->
378+
[]
379+
380+
[] ->
381+
[]
382+
383+
_segments ->
384+
do_suffix_inference_candidates(segments, normalized_identifier)
385+
end
386+
end
387+
388+
defp suffix_inference_candidates_for_identifier(_identifier), do: []
389+
390+
@spec do_suffix_inference_candidates([String.t()], String.t()) :: [suffix_candidate()]
391+
defp do_suffix_inference_candidates(segments, original_identifier) do
392+
segment_count = length(segments)
393+
394+
1..(segment_count - 1)
395+
|> Enum.reduce_while({[], 0}, fn distance, {candidates, arbitrary_trim_count} ->
396+
trimmed_segment = Enum.at(segments, -distance)
397+
398+
arbitrary_trim_count =
399+
if date_or_version_suffix?(trimmed_segment),
400+
do: arbitrary_trim_count,
401+
else: arbitrary_trim_count + 1
402+
403+
if arbitrary_trim_count > 1 do
404+
{:halt, {candidates, arbitrary_trim_count}}
405+
else
406+
candidate_identifier =
407+
segments
408+
|> Enum.take(segment_count - distance)
409+
|> Enum.join("-")
410+
411+
candidate =
412+
if blank?(candidate_identifier) or candidate_identifier == original_identifier do
413+
nil
414+
else
415+
%{
416+
from: original_identifier,
417+
to: candidate_identifier,
418+
distance: distance
419+
}
420+
end
421+
422+
candidates = if candidate, do: [candidate | candidates], else: candidates
423+
424+
{:cont, {candidates, arbitrary_trim_count}}
425+
end
426+
end)
427+
|> elem(0)
428+
|> Enum.reverse()
429+
end
430+
431+
@spec date_or_version_suffix?(term()) :: boolean()
432+
defp date_or_version_suffix?(segment) when is_binary(segment) do
433+
Regex.match?(~r/\A(?:v)?\d+(?:[._]\d+)*\z/i, String.trim(segment))
434+
end
435+
436+
defp date_or_version_suffix?(_segment), do: false
437+
438+
@spec candidate_snapshot_match(suffix_candidate(), map()) :: [map()]
439+
defp candidate_snapshot_match(candidate, snapshots_by_identifier) do
440+
case Map.fetch(snapshots_by_identifier, candidate.to) do
441+
{:ok, snapshot} -> [Map.put(candidate, :snapshot, snapshot)]
442+
:error -> []
443+
end
444+
end
445+
446+
@spec nearest_suffix_inference_match([map()]) :: {%PricingSnapshot{}, map()} | nil
447+
defp nearest_suffix_inference_match([]), do: nil
448+
449+
defp nearest_suffix_inference_match(matches) do
450+
nearest_distance = matches |> Enum.map(& &1.distance) |> Enum.min()
451+
nearest_matches = Enum.filter(matches, &(&1.distance == nearest_distance))
452+
453+
case nearest_matches |> Enum.map(& &1.snapshot.model_identifier) |> Enum.uniq() do
454+
[model_identifier] ->
455+
match = Enum.find(nearest_matches, &(&1.snapshot.model_identifier == model_identifier))
456+
457+
{match.snapshot, suffix_alias_metadata(match.from, model_identifier)}
458+
459+
_ambiguous_or_missing ->
460+
nil
461+
end
462+
end
463+
464+
@spec suffix_alias_metadata(String.t(), String.t()) :: map()
465+
defp suffix_alias_metadata(from_identifier, to_identifier) do
466+
%{
467+
"source" => "suffix_inference",
468+
"from" => from_identifier,
469+
"to" => to_identifier
470+
}
471+
end
472+
255473
defp pricing_identifiers(model, requested_model) do
256474
Enum.uniq(
257475
Enum.reject(
@@ -285,8 +503,15 @@ defmodule CodexPooler.Accounting.PricingResolution do
285503
unpriced_snapshot(status, requested_tier, actual_tier, service_tier, batch_usage?)
286504
end
287505

288-
defp priced_snapshot(snapshot, requested_tier, actual_tier, service_tier, batch_usage?) do
289-
%{
506+
defp priced_snapshot(
507+
snapshot,
508+
requested_tier,
509+
actual_tier,
510+
service_tier,
511+
batch_usage?,
512+
alias_metadata \\ nil
513+
) do
514+
pricing = %{
290515
snapshot: snapshot,
291516
status: "priced",
292517
requested_service_tier: requested_tier,
@@ -296,6 +521,8 @@ defmodule CodexPooler.Accounting.PricingResolution do
296521
pricing_type: snapshot.config["pricing_type"],
297522
batch_usage: batch_usage?
298523
}
524+
525+
put_pricing_alias_metadata(pricing, alias_metadata)
299526
end
300527

301528
defp unpriced_snapshot(status, requested_tier, actual_tier, service_tier, batch_usage?) do
@@ -466,5 +693,15 @@ defmodule CodexPooler.Accounting.PricingResolution do
466693
defp blank_to_nil(value), do: if(blank?(value), do: nil, else: value)
467694
defp truthy?(value), do: value in [true, "true", "1", 1, "yes", "batch"]
468695

696+
defp put_pricing_alias_metadata(pricing, nil), do: pricing
697+
698+
defp put_pricing_alias_metadata(pricing, alias_metadata),
699+
do: Map.put(pricing, :alias, alias_metadata)
700+
701+
defp put_serialized_alias_metadata(serialized, nil), do: serialized
702+
703+
defp put_serialized_alias_metadata(serialized, alias_metadata),
704+
do: Map.put(serialized, "alias", alias_metadata)
705+
469706
defp now, do: DateTime.utc_now() |> DateTime.truncate(:microsecond)
470707
end

0 commit comments

Comments
 (0)