feat: provider api_formats 可空继承、OpenAI 图片 edit/variation 与用量配额多项补强

- 鉴权: provider_api_keys.api_formats 改为可空,OAuth 托管 key 自动继承 provider endpoints 激活格式,相关 handler/测试同步更新
- 图片 planner: OpenAI 图片路由新增 edit/variation 操作并完善参数校验、响应合并与流式处理
- 用量: user me usage 返回区分 client_requested_stream/upstream_is_stream,前端 usage 列表筛选与展示增强
- 统计: stats_daily_model 新增 cache_creation_ephemeral_5m/1h tokens 字段与回填链路
- 配额/observability: quota repository 新增内存与 SQL 扩展,admin observability usage 字段扩充
- 其它: OAuth 导入/轮询收敛、provider 汇总与 pool admin 读写链路小修、新增 system_config 缓存与 provider template handler

Closes #318

Co-authored-by: Entropy.Xu <53283266+Entropy-Xu@users.noreply.github.com>
This commit is contained in:
fawney19
2026-04-23 14:42:51 +08:00
parent f55f22d2e8
commit fa328e18a1
128 changed files with 5583 additions and 690 deletions

View File

@@ -444,6 +444,94 @@ pub fn admin_usage_provider_key_name(
.or_else(|| item.routing_key_name().map(ToOwned::to_owned))
}
fn admin_usage_request_body_stream_flag(item: &StoredRequestUsageAudit) -> Option<bool> {
item.request_body
.as_ref()
.and_then(Value::as_object)
.and_then(|body| body.get("stream"))
.and_then(Value::as_bool)
}
fn admin_usage_api_format_defaults_to_non_stream(item: &StoredRequestUsageAudit) -> bool {
let api_format = item
.api_format
.as_deref()
.or(item.endpoint_api_format.as_deref())
.map(str::trim)
.filter(|value| !value.is_empty());
matches!(
api_format,
Some(value)
if value.eq_ignore_ascii_case("openai:chat")
|| value.eq_ignore_ascii_case("openai:cli")
|| value.eq_ignore_ascii_case("openai:compact")
|| value.eq_ignore_ascii_case("openai:image")
|| value.eq_ignore_ascii_case("claude:chat")
|| value.eq_ignore_ascii_case("claude:cli")
)
}
fn admin_usage_request_body_implies_default_non_stream(item: &StoredRequestUsageAudit) -> bool {
let Some(body) = item.request_body.as_ref().and_then(Value::as_object) else {
return false;
};
!body.contains_key("stream") && admin_usage_api_format_defaults_to_non_stream(item)
}
pub fn admin_usage_client_is_stream(item: &StoredRequestUsageAudit) -> bool {
item.request_metadata
.as_ref()
.and_then(Value::as_object)
.and_then(|metadata| metadata.get("client_requested_stream"))
.and_then(Value::as_bool)
.or_else(|| admin_usage_request_body_stream_flag(item))
.or_else(|| admin_usage_request_body_implies_default_non_stream(item).then_some(false))
.unwrap_or(item.is_stream)
}
fn admin_usage_active_request_json(
item: &StoredRequestUsageAudit,
api_key_name: Option<String>,
provider_key_name: Option<String>,
) -> Value {
let cache_creation_input_tokens = admin_usage_cache_creation_tokens(item);
let client_is_stream = admin_usage_client_is_stream(item);
let mut value = json!({
"id": item.id,
"status": item.status,
"input_tokens": item.input_tokens,
"effective_input_tokens": admin_usage_effective_input_tokens(item),
"output_tokens": item.output_tokens,
"cache_creation_input_tokens": cache_creation_input_tokens,
"cache_creation_ephemeral_5m_input_tokens": item.cache_creation_ephemeral_5m_input_tokens,
"cache_creation_ephemeral_1h_input_tokens": item.cache_creation_ephemeral_1h_input_tokens,
"cache_read_input_tokens": item.cache_read_input_tokens,
"cost": round_to(item.total_cost_usd, 6),
"actual_cost": round_to(item.actual_total_cost_usd, 6),
"response_time_ms": item.response_time_ms,
"first_byte_time_ms": item.first_byte_time_ms,
"provider": item.provider_name,
"api_key_name": api_key_name,
"provider_key_name": provider_key_name,
"is_stream": item.is_stream,
"upstream_is_stream": item.is_stream,
"client_requested_stream": client_is_stream,
"client_is_stream": client_is_stream,
"has_fallback": admin_usage_has_fallback(item),
});
if let Some(api_format) = item.api_format.as_ref() {
value["api_format"] = json!(api_format);
}
if let Some(endpoint_api_format) = item.endpoint_api_format.as_ref() {
value["endpoint_api_format"] = json!(endpoint_api_format);
}
value["has_format_conversion"] = json!(item.has_format_conversion);
if let Some(target_model) = item.target_model.as_ref() {
value["target_model"] = json!(target_model);
}
value
}
pub fn admin_usage_record_json(
item: &StoredRequestUsageAudit,
users_by_id: &BTreeMap<String, StoredUserSummary>,
@@ -469,8 +557,9 @@ pub fn admin_usage_record_json(
let user_email = user
.and_then(|value| value.email.clone())
.unwrap_or_else(|| "已删除用户".to_string());
let client_is_stream = admin_usage_client_is_stream(item);
json!({
let mut payload = json!({
"id": item.id,
"user_id": item.user_id,
"user_email": user_email,
@@ -497,7 +586,6 @@ pub fn admin_usage_record_json(
"response_time_ms": item.response_time_ms,
"first_byte_time_ms": item.first_byte_time_ms,
"created_at": unix_secs_to_rfc3339(item.created_at_unix_ms),
"is_stream": item.is_stream,
"input_price_per_1m": input_price_per_1m,
"output_price_per_1m": output_price_per_1m,
"cache_creation_price_per_1m": cache_creation_price_per_1m,
@@ -515,7 +603,18 @@ pub fn admin_usage_record_json(
"api_key_name": api_key_name,
"provider_key_name": provider_key_name,
"model_version": Value::Null,
})
});
let object = payload
.as_object_mut()
.expect("admin usage record payload should be an object");
object.insert("is_stream".to_string(), json!(item.is_stream));
object.insert("upstream_is_stream".to_string(), json!(item.is_stream));
object.insert(
"client_requested_stream".to_string(),
json!(client_is_stream),
);
object.insert("client_is_stream".to_string(), json!(client_is_stream));
payload
}
pub fn admin_usage_total_tokens(item: &StoredRequestUsageAudit) -> u64 {
@@ -1250,12 +1349,13 @@ pub fn admin_usage_resolve_request_capture_body(
body_override: Option<Value>,
) -> Option<Value> {
let resolved_model = item.model.clone();
let client_is_stream = admin_usage_client_is_stream(item);
let mut request_body = body_override.or_else(|| item.request_body.clone())?;
if let Some(body) = request_body.as_object_mut() {
body.entry("model".to_string())
.or_insert_with(|| json!(resolved_model));
if !body.contains_key("stream") {
body.insert("stream".to_string(), json!(item.is_stream));
body.insert("stream".to_string(), json!(client_is_stream));
}
if let Some(target_model) = item
.target_model
@@ -1419,37 +1519,7 @@ pub fn build_admin_usage_active_requests_response(
let provider_key_name = admin_usage_provider_key_name(item, provider_key_names);
let api_key_name =
admin_usage_api_key_name(item, api_key_names, auth_api_key_reader_available);
let cache_creation_input_tokens = admin_usage_cache_creation_tokens(item);
let mut value = json!({
"id": item.id,
"status": item.status,
"input_tokens": item.input_tokens,
"effective_input_tokens": admin_usage_effective_input_tokens(item),
"output_tokens": item.output_tokens,
"cache_creation_input_tokens": cache_creation_input_tokens,
"cache_creation_ephemeral_5m_input_tokens": item.cache_creation_ephemeral_5m_input_tokens,
"cache_creation_ephemeral_1h_input_tokens": item.cache_creation_ephemeral_1h_input_tokens,
"cache_read_input_tokens": item.cache_read_input_tokens,
"cost": round_to(item.total_cost_usd, 6),
"actual_cost": round_to(item.actual_total_cost_usd, 6),
"response_time_ms": item.response_time_ms,
"first_byte_time_ms": item.first_byte_time_ms,
"provider": item.provider_name,
"api_key_name": api_key_name,
"provider_key_name": provider_key_name,
"has_fallback": admin_usage_has_fallback(item),
});
if let Some(api_format) = item.api_format.as_ref() {
value["api_format"] = json!(api_format);
}
if let Some(endpoint_api_format) = item.endpoint_api_format.as_ref() {
value["endpoint_api_format"] = json!(endpoint_api_format);
}
value["has_format_conversion"] = json!(item.has_format_conversion);
if let Some(target_model) = item.target_model.as_ref() {
value["target_model"] = json!(target_model);
}
value
admin_usage_active_request_json(item, api_key_name, provider_key_name)
})
.collect();
@@ -1674,9 +1744,10 @@ mod tests {
use serde_json::json;
use super::{
admin_usage_has_body_value, admin_usage_has_fallback, admin_usage_is_failed,
admin_usage_matches_search, admin_usage_matches_status, admin_usage_matches_username,
admin_usage_record_json, build_admin_usage_detail_payload,
admin_usage_active_request_json, admin_usage_client_is_stream, admin_usage_has_body_value,
admin_usage_has_fallback, admin_usage_is_failed, admin_usage_matches_search,
admin_usage_matches_status, admin_usage_matches_username, admin_usage_record_json,
admin_usage_resolve_request_capture_body, build_admin_usage_detail_payload,
};
use aether_data_contracts::repository::usage::{StoredRequestUsageAudit, UsageBodyField};
@@ -1738,6 +1809,100 @@ mod tests {
assert!(admin_usage_matches_status(&item, Some("completed")));
}
#[test]
fn client_requested_stream_prefers_request_metadata_flag() {
let item = StoredRequestUsageAudit {
is_stream: true,
request_metadata: Some(json!({
"client_requested_stream": false
})),
..sample_usage("completed", Some(200), None)
};
assert!(!admin_usage_client_is_stream(&item));
let record = admin_usage_record_json(
&item,
&BTreeMap::new(),
&BTreeMap::new(),
false,
false,
None,
);
assert_eq!(record["is_stream"], true);
assert_eq!(record["upstream_is_stream"], true);
assert_eq!(record["client_requested_stream"], false);
assert_eq!(record["client_is_stream"], false);
}
#[test]
fn client_requested_stream_falls_back_to_request_body_stream_flag() {
let item = StoredRequestUsageAudit {
is_stream: true,
request_body: Some(json!({
"model": "gpt-5.4",
"stream": false
})),
..sample_usage("completed", Some(200), None)
};
assert!(!admin_usage_client_is_stream(&item));
let active = admin_usage_active_request_json(&item, None, None);
assert_eq!(active["is_stream"], true);
assert_eq!(active["upstream_is_stream"], true);
assert_eq!(active["client_requested_stream"], false);
assert_eq!(active["client_is_stream"], false);
}
#[test]
fn client_requested_stream_defaults_to_non_stream_for_openai_cli_request_body_without_flag() {
let item = StoredRequestUsageAudit {
is_stream: true,
api_format: Some("openai:cli".to_string()),
request_body: Some(json!({
"model": "gpt-5.4",
"input": [{"role": "user", "content": "hi"}],
"store": false
})),
..sample_usage("completed", Some(200), None)
};
assert!(!admin_usage_client_is_stream(&item));
let record = admin_usage_record_json(
&item,
&BTreeMap::new(),
&BTreeMap::new(),
false,
false,
None,
);
assert_eq!(record["is_stream"], true);
assert_eq!(record["upstream_is_stream"], true);
assert_eq!(record["client_requested_stream"], false);
assert_eq!(record["client_is_stream"], false);
}
#[test]
fn replay_body_defaults_stream_to_client_requested_mode() {
let item = StoredRequestUsageAudit {
is_stream: true,
request_body: Some(json!({
"model": "gpt-5.4",
"input": "hello"
})),
request_metadata: Some(json!({
"client_requested_stream": false
})),
..sample_usage("completed", Some(200), None)
};
let body = admin_usage_resolve_request_capture_body(&item, None)
.expect("replay body should resolve");
assert_eq!(body["stream"], false);
}
#[test]
fn legacy_failure_signals_still_work_when_status_is_missing() {
let item = StoredRequestUsageAudit {