You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Introduces new field `models` to ModelCosts struct which will be used to
calculate cost of LLM calls by type of tokens used. Current solution was
not flexible enough, and it only supported 2 type of tokens: input and
output tokens.
Cost calculation has gotten more complex in the last year since this was
first implemented, and we need more flexible structure which will also
support future changes in cost calculation.
This PR only introduces new struct, but the cost calculation will be
changed in a separate PR to make this PR smaller and easier to review.
Part of [TET-645: Automate cost/pricing
updating](https://linear.app/getsentry/issue/TET-645/automate-costpricing-updating)
/// Test that integer versions are handled correctly in the struct format
291
367
#[test]
292
-
fntest_model_cost_config(){
293
-
let original = r#"{"version":1,"costs":[{"modelId":"babbage-002.ft-*","forCompletion":false,"costPer1kTokens":0.0016}]}"#;
368
+
fntest_model_cost_version_sent_as_number(){
369
+
// Test integer version 1
370
+
let original = r#"{"version":1,"costs":[{"modelId":"babbage-002.ft","forCompletion":false,"costPer1kTokens":0.0016}]}"#;
294
371
let deserialized:ModelCosts = serde_json::from_str(original).unwrap();
295
-
assert_debug_snapshot!(deserialized, @r###"
372
+
assert_debug_snapshot!(
373
+
deserialized,
374
+
@r#"
375
+
ModelCosts {
376
+
version: 1,
377
+
costs: [
378
+
ModelCost {
379
+
model_id: LazyGlob("babbage-002.ft"),
380
+
for_completion: false,
381
+
cost_per_1k_tokens: 0.0016,
382
+
},
383
+
],
384
+
models: {},
385
+
}
386
+
"#,
387
+
);
388
+
389
+
// Test integer version 2
390
+
let original_v2 = r#"{"version":2,"models":{"gpt-4":{"inputPerToken":0.03,"outputPerToken":0.06,"outputReasoningPerToken":0.12,"inputCachedPerToken":0.015}}}"#;
391
+
let deserialized_v2:ModelCosts = serde_json::from_str(original_v2).unwrap();
392
+
assert_debug_snapshot!(
393
+
deserialized_v2,
394
+
@r###"
395
+
ModelCosts {
396
+
version: 2,
397
+
costs: [],
398
+
models: {
399
+
"gpt-4": ModelCostV2 {
400
+
input_per_token: 0.03,
401
+
output_per_token: 0.06,
402
+
output_reasoning_per_token: 0.12,
403
+
input_cached_per_token: 0.015,
404
+
},
405
+
},
406
+
}
407
+
"###,
408
+
);
409
+
410
+
// Test unknown integer version
411
+
let original_unknown = r#"{"version":99,"costs":[]}"#;
412
+
let deserialized_unknown:ModelCosts = serde_json::from_str(original_unknown).unwrap();
413
+
assert_eq!(deserialized_unknown.version,99);
414
+
assert!(!deserialized_unknown.is_enabled());
415
+
}
416
+
417
+
#[test]
418
+
fntest_model_cost_config_v1(){
419
+
let original = r#"{"version":1,"costs":[{"modelId":"babbage-002.ft","forCompletion":false,"costPer1kTokens":0.0016}]}"#;
420
+
let deserialized:ModelCosts = serde_json::from_str(original).unwrap();
421
+
assert_debug_snapshot!(deserialized, @r#"
296
422
ModelCosts {
297
423
version: 1,
298
424
costs: [
299
425
ModelCost {
300
-
model_id: LazyGlob("babbage-002.ft-*"),
426
+
model_id: LazyGlob("babbage-002.ft"),
301
427
for_completion: false,
302
428
cost_per_1k_tokens: 0.0016,
303
429
},
304
430
],
431
+
models: {},
432
+
}
433
+
"#);
434
+
435
+
let serialized = serde_json::to_string(&deserialized).unwrap();
436
+
assert_eq!(&serialized, original);
437
+
}
438
+
439
+
#[test]
440
+
fntest_model_cost_config_v2(){
441
+
let original = r#"{"version":2,"models":{"gpt-4":{"inputPerToken":0.03,"outputPerToken":0.06,"outputReasoningPerToken":0.12,"inputCachedPerToken":0.015}}}"#;
442
+
let deserialized:ModelCosts = serde_json::from_str(original).unwrap();
443
+
assert_debug_snapshot!(deserialized, @r###"
444
+
ModelCosts {
445
+
version: 2,
446
+
costs: [],
447
+
models: {
448
+
"gpt-4": ModelCostV2 {
449
+
input_per_token: 0.03,
450
+
output_per_token: 0.06,
451
+
output_reasoning_per_token: 0.12,
452
+
input_cached_per_token: 0.015,
453
+
},
454
+
},
305
455
}
306
456
"###);
307
457
308
458
let serialized = serde_json::to_string(&deserialized).unwrap();
let costs = v1_config.cost_per_token("gpt-4-turbo").unwrap();
476
+
assert_eq!(costs.input_per_token *1000.0,0.03);// multiplying by 1000 to avoid floating point errors
477
+
assert_eq!(costs.output_per_token,0.0);// output tokens are not defined
478
+
}
479
+
480
+
#[test]
481
+
fntest_model_cost_functionality_v1(){
482
+
let v1_config = ModelCosts{
483
+
version:1,
484
+
costs:vec![
485
+
ModelCost{
486
+
model_id:LazyGlob::new("gpt-4*"),
487
+
for_completion:false,
488
+
cost_per_1k_tokens:0.03,
489
+
},
490
+
ModelCost{
491
+
model_id:LazyGlob::new("gpt-4*"),
492
+
for_completion:true,
493
+
cost_per_1k_tokens:0.06,
494
+
},
495
+
],
496
+
models:HashMap::new(),
497
+
};
498
+
assert!(v1_config.is_enabled());
499
+
let costs = v1_config.cost_per_token("gpt-4").unwrap();
500
+
assert_eq!(costs.input_per_token *1000.0,0.03);// multiplying by 1000 to avoid floating point errors
501
+
assert_eq!(costs.output_per_token *1000.0,0.06);// multiplying by 1000 to avoid floating point errors
502
+
}
503
+
504
+
#[test]
505
+
fntest_model_cost_functionality_v2(){
506
+
// Test V2 functionality
507
+
letmut models_map = HashMap::new();
508
+
models_map.insert(
509
+
"gpt-4".to_owned(),
510
+
ModelCostV2{
511
+
input_per_token:0.03,
512
+
output_per_token:0.06,
513
+
output_reasoning_per_token:0.12,
514
+
input_cached_per_token:0.015,
515
+
},
516
+
);
517
+
let v2_config = ModelCosts{
518
+
version:2,
519
+
costs:vec![],
520
+
models: models_map,
521
+
};
522
+
assert!(v2_config.is_enabled());
523
+
let cost = v2_config.cost_per_token("gpt-4").unwrap();
524
+
assert_eq!(
525
+
cost,
526
+
ModelCostV2{
527
+
input_per_token:0.03,
528
+
output_per_token:0.06,
529
+
output_reasoning_per_token:0.12,
530
+
input_cached_per_token:0.015,
531
+
}
532
+
);
533
+
}
534
+
535
+
#[test]
536
+
fntest_model_cost_unknown_version(){
537
+
// Test that unknown versions are handled properly
538
+
let unknown_version_json = r#"{"version":3,"models":{"some-model":{"inputPerToken":0.01,"outputPerToken":0.02,"outputReasoningPerToken":0.03,"inputCachedPerToken":0.005}}}"#;
539
+
let deserialized:ModelCosts = serde_json::from_str(unknown_version_json).unwrap();
0 commit comments