feat(gateway/data): 新增 usage 关键词搜索、缓存命中摘要、结算成本摘要及 dashboard 聚合查询能力

- 新增 UsageAuditKeywordSearchQuery,支持多关键词、用户名、API Key 交叉过滤
- 新增 UsageCacheHitSummaryQuery/StoredUsageCacheHitSummary,统计请求缓存命中率
- 新增 UsageSettledCostSummaryQuery/StoredUsageSettledCostSummary,汇总结算成本
- 新增 UsageDashboardSummaryQuery、UsageBreakdownSummaryQuery 等 dashboard 聚合类型
- SQL 层实现对应查询方法,含 list_by_ids、list_usage_audits_by_keyword_search、count_usage_audits_by_keyword_search
- 将上述能力通过 GatewayDataState / AdminAppState 暴露给 handler 层
- user_me_usage 及 dashboard_filters 切换为新查询接口,移除旧的内存过滤逻辑
- 同步更新 memory 层及测试
This commit is contained in:
fawney19
2026-04-17 00:58:46 +08:00
parent 6e5af5ef70
commit 1e0bc61526
48 changed files with 5241 additions and 1162 deletions

View File

@@ -3,10 +3,21 @@ mod types;
pub use types::{
parse_usage_body_ref, usage_body_ref, StoredProviderApiKeyUsageSummary,
StoredProviderUsageSummary, StoredProviderUsageWindow, StoredRequestUsageAudit,
StoredUsageAuditAggregation, StoredUsageAuditSummary, StoredUsageDailySummary,
StoredUsageLeaderboardSummary, StoredUsageTimeSeriesBucket, UpsertUsageRecord,
UsageAuditAggregationGroupBy, UsageAuditAggregationQuery, UsageAuditListQuery,
UsageAuditSummaryQuery, UsageBodyField, UsageDailyHeatmapQuery, UsageLeaderboardGroupBy,
UsageLeaderboardQuery, UsageReadRepository, UsageRepository, UsageTimeSeriesGranularity,
UsageTimeSeriesQuery, UsageWriteRepository,
StoredUsageAuditAggregation, StoredUsageAuditSummary, StoredUsageBreakdownSummaryRow,
StoredUsageCacheAffinityHitSummary, StoredUsageCacheAffinityIntervalRow,
StoredUsageCacheHitSummary, StoredUsageCostSavingsSummary, StoredUsageDailySummary,
StoredUsageDashboardDailyBreakdownRow, StoredUsageDashboardProviderCount,
StoredUsageDashboardSummary, StoredUsageErrorDistributionRow, StoredUsageLeaderboardSummary,
StoredUsagePerformancePercentilesRow, StoredUsageSettledCostSummary,
StoredUsageTimeSeriesBucket, UpsertUsageRecord, UsageAuditAggregationGroupBy,
UsageAuditAggregationQuery, UsageAuditKeywordSearchQuery, UsageAuditListQuery,
UsageAuditSummaryQuery, UsageBodyField, UsageBreakdownGroupBy, UsageBreakdownSummaryQuery,
UsageCacheAffinityHitSummaryQuery, UsageCacheAffinityIntervalGroupBy,
UsageCacheAffinityIntervalQuery, UsageCacheHitSummaryQuery, UsageCostSavingsSummaryQuery,
UsageDailyHeatmapQuery, UsageDashboardDailyBreakdownQuery, UsageDashboardProviderCountsQuery,
UsageDashboardSummaryQuery, UsageErrorDistributionQuery, UsageLeaderboardGroupBy,
UsageLeaderboardQuery, UsageMonitoringErrorCountQuery, UsageMonitoringErrorListQuery,
UsagePerformancePercentilesQuery, UsageReadRepository, UsageRepository,
UsageSettledCostSummaryQuery, UsageTimeSeriesGranularity, UsageTimeSeriesQuery,
UsageWriteRepository,
};

View File

@@ -489,6 +489,29 @@ pub struct UsageAuditListQuery {
pub newest_first: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct UsageAuditKeywordSearchQuery {
pub created_from_unix_secs: Option<u64>,
pub created_until_unix_secs: Option<u64>,
pub user_id: Option<String>,
pub provider_name: Option<String>,
pub model: Option<String>,
pub api_format: Option<String>,
pub statuses: Option<Vec<String>>,
pub is_stream: Option<bool>,
pub error_only: bool,
pub keywords: Vec<String>,
pub matched_user_ids_by_keyword: Vec<Vec<String>>,
pub auth_user_reader_available: bool,
pub matched_api_key_ids_by_keyword: Vec<Vec<String>>,
pub auth_api_key_reader_available: bool,
pub username_keyword: Option<String>,
pub matched_user_ids_for_username: Vec<String>,
pub limit: Option<usize>,
pub offset: Option<usize>,
pub newest_first: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UsageAuditAggregationGroupBy {
@@ -553,6 +576,244 @@ pub struct StoredUsageAuditSummary {
pub error_requests: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct UsageCacheHitSummaryQuery {
pub created_from_unix_secs: u64,
pub created_until_unix_secs: u64,
pub user_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct StoredUsageCacheHitSummary {
pub total_requests: u64,
pub cache_hit_requests: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct UsageSettledCostSummaryQuery {
pub created_from_unix_secs: u64,
pub created_until_unix_secs: u64,
pub user_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct StoredUsageSettledCostSummary {
pub total_cost_usd: f64,
pub total_requests: u64,
pub input_tokens: u64,
pub output_tokens: u64,
pub cache_creation_tokens: u64,
pub cache_read_tokens: u64,
pub first_finalized_at_unix_secs: Option<u64>,
pub last_finalized_at_unix_secs: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct UsageCacheAffinityHitSummaryQuery {
pub created_from_unix_secs: u64,
pub created_until_unix_secs: u64,
pub user_id: Option<String>,
pub api_key_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct StoredUsageCacheAffinityHitSummary {
pub total_requests: u64,
pub requests_with_cache_hit: u64,
pub input_tokens: u64,
pub cache_read_tokens: u64,
pub cache_creation_tokens: u64,
pub total_input_context: u64,
pub cache_read_cost_usd: f64,
pub cache_creation_cost_usd: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UsageCacheAffinityIntervalGroupBy {
#[default]
User,
ApiKey,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct UsageCacheAffinityIntervalQuery {
pub created_from_unix_secs: u64,
pub created_until_unix_secs: u64,
pub group_by: UsageCacheAffinityIntervalGroupBy,
pub user_id: Option<String>,
pub api_key_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct StoredUsageCacheAffinityIntervalRow {
pub group_id: String,
pub username: Option<String>,
pub model: String,
pub created_at_unix_secs: u64,
pub interval_minutes: f64,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct UsageDashboardSummaryQuery {
pub created_from_unix_secs: u64,
pub created_until_unix_secs: u64,
pub user_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct StoredUsageDashboardSummary {
pub total_requests: u64,
pub input_tokens: u64,
pub effective_input_tokens: u64,
pub output_tokens: u64,
pub total_tokens: u64,
pub cache_creation_tokens: u64,
pub cache_read_tokens: u64,
pub total_input_context: u64,
pub cache_creation_cost_usd: f64,
pub cache_read_cost_usd: f64,
pub total_cost_usd: f64,
pub actual_total_cost_usd: f64,
pub error_requests: u64,
pub response_time_sum_ms: f64,
pub response_time_samples: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct UsageDashboardDailyBreakdownQuery {
pub created_from_unix_secs: u64,
pub created_until_unix_secs: u64,
pub tz_offset_minutes: i32,
pub user_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct StoredUsageDashboardDailyBreakdownRow {
pub date: String,
pub model: String,
pub provider: String,
pub requests: u64,
pub total_tokens: u64,
pub total_cost_usd: f64,
pub response_time_sum_ms: f64,
pub response_time_samples: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct UsageDashboardProviderCountsQuery {
pub created_from_unix_secs: u64,
pub created_until_unix_secs: u64,
pub user_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct StoredUsageDashboardProviderCount {
pub provider_name: String,
pub request_count: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UsageBreakdownGroupBy {
#[default]
Model,
Provider,
ApiFormat,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct UsageBreakdownSummaryQuery {
pub created_from_unix_secs: u64,
pub created_until_unix_secs: u64,
pub user_id: Option<String>,
pub group_by: UsageBreakdownGroupBy,
}
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct StoredUsageBreakdownSummaryRow {
pub group_key: String,
pub request_count: u64,
pub input_tokens: u64,
pub total_tokens: u64,
pub output_tokens: u64,
pub effective_input_tokens: u64,
pub total_input_context: u64,
pub cache_creation_tokens: u64,
pub cache_creation_ephemeral_5m_tokens: u64,
pub cache_creation_ephemeral_1h_tokens: u64,
pub cache_read_tokens: u64,
pub total_cost_usd: f64,
pub actual_total_cost_usd: f64,
pub success_count: u64,
pub response_time_sum_ms: f64,
pub response_time_samples: u64,
pub overall_response_time_sum_ms: f64,
pub overall_response_time_samples: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct UsageMonitoringErrorCountQuery {
pub created_from_unix_secs: u64,
pub created_until_unix_secs: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct UsageMonitoringErrorListQuery {
pub created_from_unix_secs: u64,
pub created_until_unix_secs: u64,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct UsageErrorDistributionQuery {
pub created_from_unix_secs: u64,
pub created_until_unix_secs: u64,
pub tz_offset_minutes: i32,
}
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct StoredUsageErrorDistributionRow {
pub date: String,
pub error_category: String,
pub count: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct UsagePerformancePercentilesQuery {
pub created_from_unix_secs: u64,
pub created_until_unix_secs: u64,
pub tz_offset_minutes: i32,
}
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct StoredUsagePerformancePercentilesRow {
pub date: String,
pub p50_response_time_ms: Option<u64>,
pub p90_response_time_ms: Option<u64>,
pub p99_response_time_ms: Option<u64>,
pub p50_first_byte_time_ms: Option<u64>,
pub p90_first_byte_time_ms: Option<u64>,
pub p99_first_byte_time_ms: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct UsageCostSavingsSummaryQuery {
pub created_from_unix_secs: u64,
pub created_until_unix_secs: u64,
pub user_id: Option<String>,
pub provider_name: Option<String>,
pub model: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct StoredUsageCostSavingsSummary {
pub cache_read_tokens: u64,
pub cache_read_cost_usd: f64,
pub cache_creation_cost_usd: f64,
pub estimated_full_cost_usd: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UsageTimeSeriesGranularity {
@@ -694,6 +955,11 @@ pub trait UsageReadRepository: Send + Sync {
id: &str,
) -> Result<Option<StoredRequestUsageAudit>, crate::DataLayerError>;
async fn list_by_ids(
&self,
ids: &[String],
) -> Result<Vec<StoredRequestUsageAudit>, crate::DataLayerError>;
async fn find_by_request_id(
&self,
request_id: &str,
@@ -714,6 +980,16 @@ pub trait UsageReadRepository: Send + Sync {
query: &UsageAuditListQuery,
) -> Result<u64, crate::DataLayerError>;
async fn list_usage_audits_by_keyword_search(
&self,
query: &UsageAuditKeywordSearchQuery,
) -> Result<Vec<StoredRequestUsageAudit>, crate::DataLayerError>;
async fn count_usage_audits_by_keyword_search(
&self,
query: &UsageAuditKeywordSearchQuery,
) -> Result<u64, crate::DataLayerError>;
async fn aggregate_usage_audits(
&self,
query: &UsageAuditAggregationQuery,
@@ -724,6 +1000,71 @@ pub trait UsageReadRepository: Send + Sync {
query: &UsageAuditSummaryQuery,
) -> Result<StoredUsageAuditSummary, crate::DataLayerError>;
async fn summarize_usage_cache_hit_summary(
&self,
query: &UsageCacheHitSummaryQuery,
) -> Result<StoredUsageCacheHitSummary, crate::DataLayerError>;
async fn summarize_usage_settled_cost(
&self,
query: &UsageSettledCostSummaryQuery,
) -> Result<StoredUsageSettledCostSummary, crate::DataLayerError>;
async fn summarize_usage_cache_affinity_hit_summary(
&self,
query: &UsageCacheAffinityHitSummaryQuery,
) -> Result<StoredUsageCacheAffinityHitSummary, crate::DataLayerError>;
async fn list_usage_cache_affinity_intervals(
&self,
query: &UsageCacheAffinityIntervalQuery,
) -> Result<Vec<StoredUsageCacheAffinityIntervalRow>, crate::DataLayerError>;
async fn summarize_dashboard_usage(
&self,
query: &UsageDashboardSummaryQuery,
) -> Result<StoredUsageDashboardSummary, crate::DataLayerError>;
async fn list_dashboard_daily_breakdown(
&self,
query: &UsageDashboardDailyBreakdownQuery,
) -> Result<Vec<StoredUsageDashboardDailyBreakdownRow>, crate::DataLayerError>;
async fn summarize_dashboard_provider_counts(
&self,
query: &UsageDashboardProviderCountsQuery,
) -> Result<Vec<StoredUsageDashboardProviderCount>, crate::DataLayerError>;
async fn summarize_usage_breakdown(
&self,
query: &UsageBreakdownSummaryQuery,
) -> Result<Vec<StoredUsageBreakdownSummaryRow>, crate::DataLayerError>;
async fn count_monitoring_usage_errors(
&self,
query: &UsageMonitoringErrorCountQuery,
) -> Result<u64, crate::DataLayerError>;
async fn list_monitoring_usage_errors(
&self,
query: &UsageMonitoringErrorListQuery,
) -> Result<Vec<StoredRequestUsageAudit>, crate::DataLayerError>;
async fn summarize_usage_error_distribution(
&self,
query: &UsageErrorDistributionQuery,
) -> Result<Vec<StoredUsageErrorDistributionRow>, crate::DataLayerError>;
async fn summarize_usage_performance_percentiles(
&self,
query: &UsagePerformancePercentilesQuery,
) -> Result<Vec<StoredUsagePerformancePercentilesRow>, crate::DataLayerError>;
async fn summarize_usage_cost_savings(
&self,
query: &UsageCostSavingsSummaryQuery,
) -> Result<StoredUsageCostSavingsSummary, crate::DataLayerError>;
async fn summarize_usage_time_series(
&self,
query: &UsageTimeSeriesQuery,