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(
25        about = "Dashboard request metrics: time range, model filter, cache-hit rate, CSV export. For a quick operational aggregate, use `infra logs request stats`"
26    )]
27    Stats(stats::StatsArgs),
28
29    #[command(
30        about = "Dashboard list of AI requests with time range, model filter, and CSV export. For a quick operational list, use `infra logs request list`"
31    )]
32    List(list::ListArgs),
33
34    #[command(about = "AI request trends over time")]
35    Trends(trends::TrendsArgs),
36
37    #[command(about = "Model usage breakdown")]
38    Models(models::ModelsArgs),
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
42pub struct RequestStatsOutput {
43    pub period: String,
44    pub total_requests: i64,
45    pub total_tokens: i64,
46    pub input_tokens: i64,
47    pub output_tokens: i64,
48    pub total_cost_microdollars: i64,
49    pub avg_latency_ms: i64,
50    pub cache_hit_rate: f64,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
54pub struct RequestTrendPoint {
55    pub timestamp: String,
56    pub request_count: i64,
57    pub total_tokens: i64,
58    pub cost_microdollars: i64,
59    pub avg_latency_ms: i64,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
63pub struct RequestTrendsOutput {
64    pub period: String,
65    pub group_by: String,
66    pub points: Vec<RequestTrendPoint>,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
70pub struct ModelUsageRow {
71    pub provider: String,
72    pub model: String,
73    pub request_count: i64,
74    pub total_tokens: i64,
75    pub total_cost_microdollars: i64,
76    pub avg_latency_ms: i64,
77    pub percentage: f64,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
81pub struct ModelsOutput {
82    pub period: String,
83    pub models: Vec<ModelUsageRow>,
84    pub total_requests: i64,
85}
86
87pub async fn execute(command: RequestsCommands, config: &CliConfig) -> Result<()> {
88    match command {
89        RequestsCommands::Stats(args) => {
90            let result = stats::execute(args, config).await?;
91            render_result(&result);
92            Ok(())
93        },
94        RequestsCommands::List(args) => {
95            let result = list::execute(args, config).await?;
96            render_result(&result);
97            Ok(())
98        },
99        RequestsCommands::Trends(args) => {
100            let result = trends::execute(args, config).await?;
101            render_result(&result);
102            Ok(())
103        },
104        RequestsCommands::Models(args) => {
105            let result = models::execute(args, config).await?;
106            render_result(&result);
107            Ok(())
108        },
109    }
110}
111
112pub async fn execute_with_pool(
113    command: RequestsCommands,
114    db_ctx: &DatabaseContext,
115    config: &CliConfig,
116) -> Result<()> {
117    match command {
118        RequestsCommands::Stats(args) => {
119            let result = stats::execute_with_pool(args, db_ctx, config).await?;
120            render_result(&result);
121            Ok(())
122        },
123        RequestsCommands::List(args) => {
124            let result = list::execute_with_pool(args, db_ctx, config).await?;
125            render_result(&result);
126            Ok(())
127        },
128        RequestsCommands::Trends(args) => {
129            let result = trends::execute_with_pool(args, db_ctx, config).await?;
130            render_result(&result);
131            Ok(())
132        },
133        RequestsCommands::Models(args) => {
134            let result = models::execute_with_pool(args, db_ctx, config).await?;
135            render_result(&result);
136            Ok(())
137        },
138    }
139}