mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-15 04:30:55 +08:00
input_tokens includes the cached subset under Anthropic and OpenAI usage semantics, so pricing input and cache read at their full prices double-counted the cache. The fresh share is now input minus cache reads, clamped at zero for gateways reporting disjoint buckets.
62 lines
1.8 KiB
Go
62 lines
1.8 KiB
Go
package server
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/Autumn-27/artex/db"
|
|
)
|
|
|
|
// The cost ledger: profile prices (EUR per 1M tokens) turn token buckets into
|
|
// EUR on the usage aggregation; unpriced profiles stay nil.
|
|
func TestCostLedgerEurOnUsage(t *testing.T) {
|
|
m, err := NewManager(t.TempDir(), "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { m.Close() })
|
|
if m.pg == nil {
|
|
t.Skip("no postgres for this run")
|
|
}
|
|
|
|
pid, err := m.pg.SaveProfile(&db.LLMProfile{
|
|
Name: "cost-test", Format: "openai", Model: "m", APIKey: "k",
|
|
PriceInputPerM: 1.5, PriceOutputPerM: 4.0, PriceCacheReadPerM: 0.15,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _, _ = m.pg.Exec("DELETE FROM llm_profiles WHERE id=$1", pid) })
|
|
|
|
stored, err := m.pg.ProfileByID(pid)
|
|
if err != nil || stored == nil {
|
|
t.Fatalf("ProfileByID: %v %v", stored, err)
|
|
}
|
|
if stored.PriceInputPerM != 1.5 || stored.PriceOutputPerM != 4.0 || stored.PriceCacheReadPerM != 0.15 || stored.PriceCacheWritePerM != 0 {
|
|
t.Fatalf("prices did not round-trip: %+v", *stored)
|
|
}
|
|
|
|
if _, err := m.pg.Exec(`INSERT INTO llm_usage(task_id, profile_name, model, input_tokens, output_tokens, cache_read, cache_write, ts)
|
|
VALUES ('', 'cost-test', 'm', 2000000, 500000, 1000000, 0, now())`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _, _ = m.pg.Exec(`DELETE FROM llm_usage WHERE profile_name='cost-test'`) })
|
|
|
|
profiles, err := m.pg.UsageByProfile()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var got *float64
|
|
for _, p := range profiles {
|
|
if p.ProfileName == "cost-test" {
|
|
got = p.CostEur
|
|
}
|
|
}
|
|
if got == nil {
|
|
t.Fatal("priced profile has no cost_eur")
|
|
}
|
|
want := 1.0*1.5 + 0.5*4.0 + 1.0*0.15 // fresh input 1M (2M incl. 1M cached) + 0.5M out + 1M cache-read = 3.65
|
|
if d := *got - want; d > 0.001 || d < -0.001 {
|
|
t.Fatalf("cost_eur = %v, want %v", *got, want)
|
|
}
|
|
}
|