feat(openai): unify tier authorization and settlement

This commit is contained in:
MMEXA
2026-07-11 12:27:05 +08:00
parent b2f596b8f0
commit 0b30cc6b0f
32 changed files with 3170 additions and 670 deletions
+11
View File
@@ -185,6 +185,9 @@ mod tests {
"reasoning": { "effort": "max" },
"service_tier": "priority"
})),
request_metadata: Some(serde_json::json!({
"provider_actual_service_tier": "default"
})),
..UsageEventData::default()
},
})
@@ -210,6 +213,14 @@ mod tests {
.and_then(serde_json::Value::as_str),
Some("priority")
);
assert_eq!(
record
.request_metadata
.as_ref()
.and_then(|value| value.get("provider_actual_service_tier"))
.and_then(serde_json::Value::as_str),
Some("default")
);
assert_eq!(record.finalized_at_unix_secs, Some(1_700_000_000));
}
@@ -4,7 +4,9 @@ use aether_ai_formats::api::{
use aether_ai_formats::UPSTREAM_IS_STREAM_KEY;
use aether_contracts::ExecutionPlan;
use aether_data_contracts::repository::usage::{
extract_provider_actual_service_tier_from_response,
extract_provider_reasoning_effort_from_body, extract_provider_service_tier_from_body,
normalize_provider_service_tier, PROVIDER_ACTUAL_SERVICE_TIER_METADATA_KEY,
PROVIDER_REASONING_EFFORT_METADATA_KEY, PROVIDER_SERVICE_TIER_METADATA_KEY,
};
use serde_json::{json, Map, Value};
@@ -106,6 +108,44 @@ pub(crate) fn attach_provider_request_body_metadata(
(!object.is_empty()).then_some(Value::Object(object))
}
pub(crate) fn attach_provider_response_body_metadata(
metadata: Option<Value>,
provider_response_body: Option<&Value>,
) -> Option<Value> {
if metadata
.as_ref()
.and_then(Value::as_object)
.and_then(|object| object.get(PROVIDER_ACTUAL_SERVICE_TIER_METADATA_KEY))
.and_then(Value::as_str)
.and_then(normalize_provider_service_tier)
.is_some()
{
return metadata;
}
let actual_service_tier =
extract_provider_actual_service_tier_from_response(provider_response_body);
attach_provider_actual_service_tier_metadata(metadata, actual_service_tier.as_deref())
}
pub(crate) fn attach_provider_actual_service_tier_metadata(
metadata: Option<Value>,
actual_service_tier: Option<&str>,
) -> Option<Value> {
let Some(actual_service_tier) = actual_service_tier.and_then(normalize_provider_service_tier)
else {
return metadata;
};
let mut object = match metadata {
Some(Value::Object(object)) => object,
_ => Map::new(),
};
object.insert(
PROVIDER_ACTUAL_SERVICE_TIER_METADATA_KEY.to_string(),
Value::String(actual_service_tier),
);
(!object.is_empty()).then_some(Value::Object(object))
}
fn copy_allowed_metadata_fields(source: &Map<String, Value>, target: &mut Map<String, Value>) {
copy_non_empty_string(source, target, "trace_id");
copy_non_empty_string(source, target, "client_ip");
@@ -120,6 +160,7 @@ fn copy_allowed_metadata_fields(source: &Map<String, Value>, target: &mut Map<St
copy_non_empty_string(source, target, "request_path_and_query");
copy_non_empty_string(source, target, PROVIDER_REASONING_EFFORT_METADATA_KEY);
copy_non_empty_string(source, target, PROVIDER_SERVICE_TIER_METADATA_KEY);
copy_non_empty_string(source, target, PROVIDER_ACTUAL_SERVICE_TIER_METADATA_KEY);
copy_number(source, target, "provider_request_body_base64_bytes");
copy_number(source, target, "provider_response_body_base64_bytes");
copy_number(source, target, "client_response_body_base64_bytes");
@@ -165,6 +206,11 @@ fn move_allowed_metadata_fields(mut source: Map<String, Value>, target: &mut Map
remove_non_empty_string(&mut source, target, "request_path_and_query");
remove_non_empty_string(&mut source, target, PROVIDER_REASONING_EFFORT_METADATA_KEY);
remove_non_empty_string(&mut source, target, PROVIDER_SERVICE_TIER_METADATA_KEY);
remove_non_empty_string(
&mut source,
target,
PROVIDER_ACTUAL_SERVICE_TIER_METADATA_KEY,
);
remove_number(&mut source, target, "provider_request_body_base64_bytes");
remove_number(&mut source, target, "provider_response_body_base64_bytes");
remove_number(&mut source, target, "client_response_body_base64_bytes");
@@ -449,7 +495,8 @@ mod tests {
use std::collections::BTreeMap;
use super::{
attach_provider_request_body_metadata, build_usage_request_metadata_seed,
attach_provider_actual_service_tier_metadata, attach_provider_request_body_metadata,
attach_provider_response_body_metadata, build_usage_request_metadata_seed,
merge_usage_request_metadata, merge_usage_request_metadata_owned,
sanitize_usage_request_metadata, sanitize_usage_request_metadata_ref,
MAX_USAGE_REQUEST_METADATA_BYTES, MAX_USAGE_REQUEST_METADATA_DEPTH,
@@ -803,6 +850,59 @@ mod tests {
);
}
#[test]
fn provider_response_metadata_preserves_terminal_actual_service_tier() {
let metadata = attach_provider_response_body_metadata(
Some(json!({"provider_service_tier": "priority"})),
Some(&json!({
"chunks": [
{"service_tier": "priority"},
{"service_tier": "Default", "usage": {"total_tokens": 12}}
]
})),
)
.expect("requested and actual provider tiers should be preserved");
assert_eq!(
metadata,
json!({
"provider_service_tier": "priority",
"provider_actual_service_tier": "default"
})
);
}
#[test]
fn terminal_summary_tier_uses_the_same_normalized_metadata_field() {
let metadata = attach_provider_actual_service_tier_metadata(
Some(json!({"trace_id": "trace-1"})),
Some(" Flex "),
)
.expect("terminal summary tier should be retained");
assert_eq!(
metadata,
json!({
"trace_id": "trace-1",
"provider_actual_service_tier": "flex"
})
);
}
#[test]
fn terminal_summary_tier_precedes_truncated_response_capture() {
let metadata = attach_provider_response_body_metadata(
Some(json!({"provider_actual_service_tier": "default"})),
Some(&json!({"chunks": [{"service_tier": "priority"}]})),
)
.expect("terminal summary tier should remain");
assert_eq!(
metadata.get("provider_actual_service_tier"),
Some(&Value::String("default".to_string()))
);
}
#[test]
fn owned_merge_matches_filtered_merge_for_trusted_objects() {
let base = Some(json!({
@@ -811,7 +911,8 @@ mod tests {
}));
let override_value = Some(json!({
"billing_snapshot_status": "complete",
"trace_id": "trace-2"
"trace_id": "trace-2",
"provider_actual_service_tier": "default"
}));
assert_eq!(
+26 -3
View File
@@ -13,6 +13,7 @@ use tokio::sync::mpsc;
use tracing::{info, warn};
use crate::executor::spawn_on_usage_background_runtime;
use crate::request_metadata::attach_provider_response_body_metadata;
use crate::worker::{
build_usage_queue_worker_with_record_gate, UsageWorkerControl, UsageWorkerObservation,
};
@@ -921,6 +922,7 @@ impl UsageRuntime {
where
T: UsageRuntimeAccess,
{
preserve_provider_response_facts(event);
match self.cached_body_capture_policy(data).await {
Ok(policy) => apply_usage_body_capture_policy_to_event(policy, event),
Err(err) => {
@@ -1200,6 +1202,12 @@ impl UsageRuntime {
}
}
fn preserve_provider_response_facts(event: &mut UsageEvent) {
let metadata = event.data.request_metadata.take();
event.data.request_metadata =
attach_provider_response_body_metadata(metadata, event.data.response_body.as_ref());
}
impl UsageQueueHealthSnapshot {
fn apply_stream_stats(&mut self, stats: RuntimeQueueStats) {
self.stream_length = stats.stream_length;
@@ -2318,8 +2326,9 @@ mod tests {
use tokio::time::{sleep, Duration};
use super::{
UsageBillingEventEnricher, UsageBodyCapturePolicy, UsageRequestRecordLevel,
UsageRuntimeAccess, UsageWorkerObservation, UsageWorkerSupervisorState,
preserve_provider_response_facts, UsageBillingEventEnricher, UsageBodyCapturePolicy,
UsageRequestRecordLevel, UsageRuntimeAccess, UsageWorkerObservation,
UsageWorkerSupervisorState,
};
use crate::worker::ManualProxyNodeCounter;
use crate::{
@@ -4010,16 +4019,21 @@ mod tests {
provider_request_body_ref: Some(
"usage://request/req-basic-1/provider_request_body".to_string(),
),
response_body: Some(json!({"error":{"message":"bad gateway"}})),
response_body: Some(json!({
"error":{"message":"bad gateway"},
"service_tier": "Default"
})),
response_body_ref: Some("usage://request/req-basic-1/response_body".to_string()),
client_response_body: Some(json!({"detail":"bad gateway"})),
client_response_body_ref: Some(
"usage://request/req-basic-1/client_response_body".to_string(),
),
request_metadata: Some(json!({"provider_service_tier": "priority"})),
..UsageEventData::default()
},
);
preserve_provider_response_facts(&mut event);
apply_usage_body_capture_policy_to_event(
UsageBodyCapturePolicy {
record_level: UsageRequestRecordLevel::Basic,
@@ -4038,5 +4052,14 @@ mod tests {
assert!(event.data.response_body_ref.is_none());
assert!(event.data.client_response_body.is_none());
assert!(event.data.client_response_body_ref.is_none());
assert_eq!(
event
.data
.request_metadata
.as_ref()
.and_then(|metadata| metadata.get("provider_actual_service_tier"))
.and_then(serde_json::Value::as_str),
Some("default")
);
}
}
+30 -4
View File
@@ -13,7 +13,8 @@ use crate::body_capture::{
RuntimeBodyCaptureMetadataInput,
};
use crate::request_metadata::{
attach_provider_request_body_metadata, build_usage_request_metadata_seed,
attach_provider_actual_service_tier_metadata, attach_provider_request_body_metadata,
attach_provider_response_body_metadata, build_usage_request_metadata_seed,
merge_usage_request_metadata, merge_usage_request_metadata_owned,
sanitize_usage_request_metadata, sanitize_usage_request_metadata_ref,
};
@@ -163,6 +164,7 @@ pub struct StreamTerminalUsagePayloadSeed {
pub client_response: Option<Value>,
pub client_response_body_state: Option<UsageBodyCaptureState>,
pub standardized_usage: Option<StandardizedUsage>,
pub provider_actual_service_tier: Option<String>,
pub observed_stream_finish: Option<bool>,
pub terminal_error_message: Option<String>,
pub capture_metadata: Option<Value>,
@@ -965,6 +967,10 @@ pub fn build_stream_terminal_usage_payload_seed(
.terminal_summary
.as_ref()
.and_then(|summary| summary.standardized_usage.clone()),
provider_actual_service_tier: payload
.terminal_summary
.as_ref()
.and_then(|summary| summary.provider_actual_service_tier.clone()),
observed_stream_finish,
terminal_error_message,
capture_metadata: build_payload_body_capture_metadata(
@@ -1004,6 +1010,10 @@ pub fn build_sync_terminal_usage_seed(
status_code,
provider_response_full.as_ref(),
);
let request_metadata = attach_provider_response_body_metadata(
context_seed.request_metadata,
provider_response_full.as_ref(),
);
TerminalUsageSeed {
terminal_state,
@@ -1046,7 +1056,7 @@ pub fn build_sync_terminal_usage_seed(
provider_response: provider_response_full,
client_response_headers,
client_response,
request_metadata: context_seed.request_metadata,
request_metadata,
audit_payload: capture_metadata,
standardized_usage,
}
@@ -1088,6 +1098,7 @@ pub fn build_stream_terminal_usage_seed(
mut client_response,
mut client_response_body_state,
standardized_usage,
provider_actual_service_tier,
observed_stream_finish,
terminal_error_message,
capture_metadata,
@@ -1177,6 +1188,12 @@ pub fn build_stream_terminal_usage_seed(
missing_observed_finish,
terminal_error_message.is_some(),
);
let request_metadata = attach_provider_actual_service_tier_metadata(
context_seed.request_metadata,
provider_actual_service_tier.as_deref(),
);
let request_metadata =
attach_provider_response_body_metadata(request_metadata, provider_response_full.as_ref());
TerminalUsageSeed {
terminal_state,
@@ -1219,7 +1236,7 @@ pub fn build_stream_terminal_usage_seed(
provider_response: provider_response_full,
client_response_headers,
client_response,
request_metadata: context_seed.request_metadata,
request_metadata,
audit_payload: capture_metadata,
standardized_usage,
}
@@ -4588,7 +4605,7 @@ mod tests {
finish_reason: Some("stop".to_string()),
response_id: Some("resp_summary_1".to_string()),
model: Some("gpt-5.4".to_string()),
provider_actual_service_tier: None,
provider_actual_service_tier: Some("Default".to_string()),
observed_finish: true,
unknown_event_count: 0,
parser_error: None,
@@ -4607,6 +4624,15 @@ mod tests {
assert_eq!(event.data.cache_read_input_tokens, Some(3));
assert!(event.data.response_body.is_none());
assert!(event.data.client_response_body.is_none());
assert_eq!(
event
.data
.request_metadata
.as_ref()
.and_then(|metadata| metadata.get("provider_actual_service_tier"))
.and_then(Value::as_str),
Some("default")
);
}
#[test]