Skip to main content

systemprompt_cli/commands/analytics/requests/
mod.rs

1//! AI request analytics: aggregate stats, individual listings, trends, and
2//! model usage.
3//!
4//! Defines the [`RequestsCommands`] subcommand tree and the typed output shapes
5//! ([`RequestStatsOutput`], [`RequestTrendsOutput`], [`ModelsOutput`]) rendered
6//! by the `analytics requests` commands.
7
8mod list;
9mod models;
10mod stats;
11mod trends;
12
13use anyhow::Result;
14use clap::Subcommand;
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17use systemprompt_runtime::DatabaseContext;
18
19use crate::CliConfig;
20use crate::shared::render_result;
21
22#[derive(Debug, Subcommand)]
23pub enum RequestsCommands {
24    #[command(about = "Aggregate AI request statistics")]
25    Stats(stats::StatsArgs),
26
27    #[command(about = "List individual AI requests")]
28    List(list::ListArgs),
29
30    #[command(about = "AI request trends over time")]
31    Trends(trends::TrendsArgs),
32
33    #[command(about = "Model usage breakdown")]
34    Models(models::ModelsArgs),
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
38pub struct RequestStatsOutput {
39    pub period: String,
40    pub total_requests: i64,
41    pub total_tokens: i64,
42    pub input_tokens: i64,
43    pub output_tokens: i64,
44    pub total_cost_microdollars: i64,
45    pub avg_latency_ms: i64,
46    pub cache_hit_rate: f64,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
50pub struct RequestTrendPoint {
51    pub timestamp: String,
52    pub request_count: i64,
53    pub total_tokens: i64,
54    pub cost_microdollars: i64,
55    pub avg_latency_ms: i64,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
59pub struct RequestTrendsOutput {
60    pub period: String,
61    pub group_by: String,
62    pub points: Vec<RequestTrendPoint>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
66pub struct ModelUsageRow {
67    pub provider: String,
68    pub model: String,
69    pub request_count: i64,
70    pub total_tokens: i64,
71    pub total_cost_microdollars: i64,
72    pub avg_latency_ms: i64,
73    pub percentage: f64,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
77pub struct ModelsOutput {
78    pub period: String,
79    pub models: Vec<ModelUsageRow>,
80    pub total_requests: i64,
81}
82
83pub async fn execute(command: RequestsCommands, config: &CliConfig) -> Result<()> {
84    match command {
85        RequestsCommands::Stats(args) => {
86            let result = stats::execute(args, config).await?;
87            render_result(&result);
88            Ok(())
89        },
90        RequestsCommands::List(args) => {
91            let result = list::execute(args, config).await?;
92            render_result(&result);
93            Ok(())
94        },
95        RequestsCommands::Trends(args) => {
96            let result = trends::execute(args, config).await?;
97            render_result(&result);
98            Ok(())
99        },
100        RequestsCommands::Models(args) => {
101            let result = models::execute(args, config).await?;
102            render_result(&result);
103            Ok(())
104        },
105    }
106}
107
108pub async fn execute_with_pool(
109    command: RequestsCommands,
110    db_ctx: &DatabaseContext,
111    config: &CliConfig,
112) -> Result<()> {
113    match command {
114        RequestsCommands::Stats(args) => {
115            let result = stats::execute_with_pool(args, db_ctx, config).await?;
116            render_result(&result);
117            Ok(())
118        },
119        RequestsCommands::List(args) => {
120            let result = list::execute_with_pool(args, db_ctx, config).await?;
121            render_result(&result);
122            Ok(())
123        },
124        RequestsCommands::Trends(args) => {
125            let result = trends::execute_with_pool(args, db_ctx, config).await?;
126            render_result(&result);
127            Ok(())
128        },
129        RequestsCommands::Models(args) => {
130            let result = models::execute_with_pool(args, db_ctx, config).await?;
131            render_result(&result);
132            Ok(())
133        },
134    }
135}