feat: add selectable routing groups and composite billing

Support per-model provider enablement and compact model editing. Capture request-time billing factors, charge customer costs separately, and preserve historical statistics without backfills.
This commit is contained in:
elky
2026-10-07 14:49:57 +08:00
parent 310098a853
commit 911c7f8875
110 changed files with 6524 additions and 559 deletions
@@ -8,14 +8,17 @@ use serde_json::{Map, Value};
use crate::repository::candidates::sanitize_request_candidate_skip_reason;
use super::{
normalize_provider_response_model, LIVE_SESSION_METADATA_KEY,
billing_multiplier_snapshot, normalize_provider_response_model,
BILLING_MULTIPLIER_SNAPSHOT_METADATA_KEY, LIVE_SESSION_METADATA_KEY,
PLAN_USAGE_RESERVATION_DEFERRED_METADATA_KEY, PROVIDER_ACTUAL_SERVICE_TIER_METADATA_KEY,
PROVIDER_CACHE_TTL_MINUTES_METADATA_KEY, PROVIDER_REASONING_EFFORT_METADATA_KEY,
PROVIDER_RESPONSE_MODEL_METADATA_KEY, PROVIDER_SERVICE_TIER_METADATA_KEY,
REALTIME_SESSION_METADATA_KEY, REQUESTED_REASONING_EFFORT_METADATA_KEY,
ROUTING_CANDIDATE_SKIP_REASON_METADATA_KEY, ROUTING_FAILURE_DIAGNOSTIC_METADATA_KEY,
USAGE_AVAILABLE_METADATA_KEY, USAGE_PRICING_AVAILABLE_METADATA_KEY,
WEBSOCKET_MODE_METADATA_KEY, WEBSOCKET_TRANSPORT_METADATA_KEY,
ROUTING_GROUP_BILLING_MULTIPLIER_METADATA_KEY, ROUTING_GROUP_ID_METADATA_KEY,
ROUTING_GROUP_NAME_METADATA_KEY, USAGE_AVAILABLE_METADATA_KEY,
USAGE_PRICING_AVAILABLE_METADATA_KEY, WEBSOCKET_MODE_METADATA_KEY,
WEBSOCKET_TRANSPORT_METADATA_KEY,
};
const UPSTREAM_IS_STREAM_KEY: &str = "upstream_is_stream";
@@ -43,8 +46,68 @@ pub fn sanitize_usage_request_metadata_ref(value: Option<&Value>) -> Option<Valu
sanitize_usage_request_metadata_object(value?.as_object()?)
}
/// Keep the request's first captured billing snapshot and reservation owner across retries.
pub fn preserve_usage_routing_group_snapshot(
incoming: Option<Value>,
previous: Option<&Value>,
) -> Option<Value> {
let Some(previous) = previous.and_then(Value::as_object) else {
return incoming;
};
let mut snapshot = Map::from_iter(
[
BILLING_MULTIPLIER_SNAPSHOT_METADATA_KEY,
ROUTING_GROUP_BILLING_MULTIPLIER_METADATA_KEY,
ROUTING_GROUP_ID_METADATA_KEY,
ROUTING_GROUP_NAME_METADATA_KEY,
PLAN_USAGE_RESERVATION_TOKEN_KEY,
]
.into_iter()
.filter_map(|key| {
previous
.get(key)
.map(|value| (key.to_string(), value.clone()))
}),
);
if !snapshot.contains_key(BILLING_MULTIPLIER_SNAPSHOT_METADATA_KEY)
&& snapshot.contains_key(ROUTING_GROUP_BILLING_MULTIPLIER_METADATA_KEY)
{
let captured = billing_multiplier_snapshot(Some(&Value::Object(snapshot.clone())))
.ok()
.flatten()
.and_then(|snapshot| serde_json::to_value(snapshot).ok())
.unwrap_or(Value::Null);
snapshot.insert(
BILLING_MULTIPLIER_SNAPSHOT_METADATA_KEY.to_string(),
captured,
);
}
let Some(Value::Object(snapshot)) = sanitize_usage_request_metadata_object(&snapshot) else {
return incoming;
};
let mut metadata = incoming
.and_then(|value| value.as_object().cloned())
.unwrap_or_default();
metadata.extend(snapshot);
Some(Value::Object(metadata))
}
pub fn sanitize_usage_request_metadata_object(source: &Map<String, Value>) -> Option<Value> {
let mut target = Map::new();
if let Some(snapshot) = source.get(BILLING_MULTIPLIER_SNAPSHOT_METADATA_KEY) {
let metadata = serde_json::json!({BILLING_MULTIPLIER_SNAPSHOT_METADATA_KEY: snapshot});
let snapshot = match billing_multiplier_snapshot(Some(&metadata)) {
Ok(Some(snapshot)) => serde_json::to_value(snapshot)
.expect("validated billing multiplier snapshot must serialize"),
// Preserve an invalid marker so malformed financial input cannot silently
// fall back to legacy billing after metadata projection.
_ => Value::Null,
};
target.insert(
BILLING_MULTIPLIER_SNAPSHOT_METADATA_KEY.to_string(),
snapshot,
);
}
if let Some(source) = source
.get("analytics_measurement")
.and_then(|value| value.get("source"))
@@ -81,6 +144,8 @@ pub fn sanitize_usage_request_metadata_object(source: &Map<String, Value>) -> Op
}
insert_token(source, &mut target, "trace_id", 128);
insert_token(source, &mut target, ROUTING_GROUP_ID_METADATA_KEY, 128);
insert_bounded_text(source, &mut target, ROUTING_GROUP_NAME_METADATA_KEY, 256);
insert_ip_address(source, &mut target, "client_ip");
insert_client_family(source, &mut target);
for key in [
@@ -181,6 +246,7 @@ pub fn sanitize_usage_request_metadata_object(source: &Map<String, Value>) -> Op
for key in [
"rate_multiplier",
ROUTING_GROUP_BILLING_MULTIPLIER_METADATA_KEY,
"input_price_per_1m",
"output_price_per_1m",
"cache_creation_price_per_1m",
@@ -189,6 +255,20 @@ pub fn sanitize_usage_request_metadata_object(source: &Map<String, Value>) -> Op
] {
insert_nonnegative_number(source, &mut target, key);
}
// An invalid legacy routing factor must remain a financial tombstone. Dropping it
// would make a subsequent reader silently fall back to the historical provider charge.
if source
.get(ROUTING_GROUP_BILLING_MULTIPLIER_METADATA_KEY)
.is_some_and(|value| {
!value
.as_f64()
.is_some_and(|value| value.is_finite() && value >= 0.0)
})
{
target
.entry(BILLING_MULTIPLIER_SNAPSHOT_METADATA_KEY.to_string())
.or_insert(Value::Null);
}
let billing_snapshot = source
.get("billing_snapshot")
@@ -1156,6 +1236,27 @@ fn insert_token(
target.insert(key.to_string(), Value::String(value.to_string()));
}
fn insert_bounded_text(
source: &Map<String, Value>,
target: &mut Map<String, Value>,
key: &str,
max_len: usize,
) {
let Some(value) = source
.get(key)
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| {
!value.is_empty()
&& value.chars().count() <= max_len
&& !value.chars().any(char::is_control)
})
else {
return;
};
target.insert(key.to_string(), Value::String(value.to_string()));
}
fn insert_dimension_token(source: &Map<String, Value>, target: &mut Map<String, Value>, key: &str) {
let Some(value) = source
.get(key)
@@ -1263,7 +1364,111 @@ fn safe_version_value(value: &Value) -> Option<String> {
mod tests {
use serde_json::json;
use super::{sanitize_usage_request_metadata, sanitize_usage_request_metadata_ref};
use super::{
billing_multiplier_snapshot, preserve_usage_routing_group_snapshot,
sanitize_usage_request_metadata, sanitize_usage_request_metadata_ref,
};
#[test]
fn billing_multiplier_snapshot_projection_preserves_invalid_marker_and_immutable_factors() {
for snapshot in [
serde_json::Value::Null,
json!({"version": 1, "factors": {"routing_group": 2.0}, "multiplier": 1.0}),
json!({"version": 99, "factors": {}, "multiplier": 1.0}),
] {
let projected = sanitize_usage_request_metadata(Some(json!({
"billing_multiplier_snapshot": snapshot,
"routing_group_billing_multiplier": 0.5,
})))
.unwrap();
assert_eq!(
projected.get("billing_multiplier_snapshot"),
Some(&serde_json::Value::Null)
);
assert!(billing_multiplier_snapshot(Some(&projected)).is_err());
let preserved = preserve_usage_routing_group_snapshot(
Some(json!({"billing_multiplier_snapshot": {"version": 1, "factors": {}, "multiplier": 1.0}})),
Some(&projected),
).unwrap();
assert!(billing_multiplier_snapshot(Some(&preserved)).is_err());
}
let legacy =
json!({"routing_group_billing_multiplier": 0.25, "routing_group_name": "历史分组"});
let preserved = preserve_usage_routing_group_snapshot(
Some(json!({"billing_multiplier_snapshot": {"version": 1, "factors": {"routing_group": 99.0}, "multiplier": 99.0}})),
Some(&legacy),
).unwrap();
assert_eq!(
billing_multiplier_snapshot(Some(&preserved))
.unwrap()
.unwrap()
.multiplier(),
0.25
);
assert_eq!(preserved["routing_group_name"], "历史分组");
}
#[test]
fn billing_multiplier_snapshot_projection_rejects_malformed_legacy_factors() {
for factor in [serde_json::Value::Null, json!(-1), json!("2"), json!({})] {
let projected = sanitize_usage_request_metadata(Some(json!({
"routing_group_billing_multiplier": factor,
})))
.expect("invalid financial input must retain a tombstone");
assert_eq!(
projected["billing_multiplier_snapshot"],
serde_json::Value::Null
);
assert!(billing_multiplier_snapshot(Some(&projected)).is_err());
assert_eq!(
sanitize_usage_request_metadata(Some(projected.clone())),
Some(projected)
);
}
let generic = sanitize_usage_request_metadata(Some(json!({
"routing_group_billing_multiplier": -1,
"billing_multiplier_snapshot": {"version": 1, "factors": {"routing_group": 2}, "multiplier": 2},
})))
.unwrap();
assert_eq!(
billing_multiplier_snapshot(Some(&generic))
.unwrap()
.unwrap()
.multiplier(),
2.0
);
}
#[test]
fn billing_multiplier_snapshot_preserves_the_original_reservation_owner() {
let token_a = "550e8400-e29b-41d4-a716-446655440001";
let token_b = "550e8400-e29b-41d4-a716-446655440002";
let incoming = json!({
"plan_usage_reservation_token": token_b,
"billing_multiplier_snapshot": {"version": 1, "factors": {"routing_group": 3}, "multiplier": 3},
});
let previous = json!({
"plan_usage_reservation_token": token_a,
"billing_multiplier_snapshot": {"version": 1, "factors": {"routing_group": 0.5}, "multiplier": 0.5},
});
let preserved =
preserve_usage_routing_group_snapshot(Some(incoming.clone()), Some(&previous)).unwrap();
assert_eq!(preserved["plan_usage_reservation_token"], token_a);
assert_eq!(
billing_multiplier_snapshot(Some(&preserved))
.unwrap()
.unwrap()
.multiplier(),
0.5
);
for empty in [json!({}), json!({"plan_usage_reservation_token": " "})] {
let preserved =
preserve_usage_routing_group_snapshot(Some(incoming.clone()), Some(&empty))
.unwrap();
assert_eq!(preserved["plan_usage_reservation_token"], token_b);
}
}
#[test]
fn account_attribution_preserves_key_flag_without_custom_identity_or_purpose() {