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//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11mod list;
12mod models;
13mod stats;
14mod trends;
15
16use anyhow::Result;
17use clap::Subcommand;
18use schemars::JsonSchema;
19use serde::{Deserialize, Serialize};
20
21use crate::context::CommandContext;
22use crate::shared::render_result;
23
24#[derive(Debug, Subcommand)]
25pub enum RequestsCommands {
26    #[command(
27        about = "Dashboard request metrics: time range, model filter, cache-hit rate, CSV export. For a quick operational aggregate, use `infra logs request stats`"
28    )]
29    Stats(stats::StatsArgs),
30
31    #[command(
32        about = "Dashboard list of AI requests with time range, model filter, and CSV export. For a quick operational list, use `infra logs request list`"
33    )]
34    List(list::ListArgs),
35
36    #[command(about = "AI request trends over time")]
37    Trends(trends::TrendsArgs),
38
39    #[command(about = "Model usage breakdown")]
40    Models(models::ModelsArgs),
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
44pub struct RequestStatsOutput {
45    pub period: String,
46    pub total_requests: i64,
47    pub total_tokens: i64,
48    pub input_tokens: i64,
49    pub output_tokens: i64,
50    pub total_cost_microdollars: i64,
51    pub avg_latency_ms: i64,
52    pub cache_hit_rate: f64,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
56pub struct RequestTrendPoint {
57    pub timestamp: String,
58    pub request_count: i64,
59    pub total_tokens: i64,
60    pub cost_microdollars: i64,
61    pub avg_latency_ms: i64,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
65pub struct RequestTrendsOutput {
66    pub period: String,
67    pub group_by: String,
68    pub points: Vec<RequestTrendPoint>,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
72pub struct ModelUsageRow {
73    pub provider: String,
74    pub model: String,
75    pub request_count: i64,
76    pub total_tokens: i64,
77    pub total_cost_microdollars: i64,
78    pub avg_latency_ms: i64,
79    pub percentage: f64,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
83pub struct ModelsOutput {
84    pub period: String,
85    pub models: Vec<ModelUsageRow>,
86    pub total_requests: i64,
87}
88
89pub async fn execute(command: RequestsCommands, ctx: &CommandContext) -> Result<()> {
90    let db_ctx = ctx.database().await?;
91    match command {
92        RequestsCommands::Stats(args) => {
93            let result = stats::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
94            render_result(&result, &ctx.cli);
95            Ok(())
96        },
97        RequestsCommands::List(args) => {
98            let result = list::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
99            render_result(&result, &ctx.cli);
100            Ok(())
101        },
102        RequestsCommands::Trends(args) => {
103            let result = trends::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
104            render_result(&result, &ctx.cli);
105            Ok(())
106        },
107        RequestsCommands::Models(args) => {
108            let result = models::execute_with_pool(args, &db_ctx, &ctx.cli).await?;
109            render_result(&result, &ctx.cli);
110            Ok(())
111        },
112    }
113}