sys/sysmod/openai/basicfuncs/
system.rs

1//! システム情報取得。
2
3use crate::rpienv::{self, CameraInfo, RaspiEnv};
4use crate::sysmod::health::{
5    ThrottleFlags, get_cpu_cores, get_cpu_info, get_disk_info, get_mem_info, get_throttle_status,
6};
7use crate::sysmod::openai::function::{
8    BasicContext, FuncArgs, Function, FunctionTable, ParameterElement, Parameters,
9    get_arg_bool_opt, get_arg_str,
10};
11use crate::sysmod::openai::{ModelInfo, ParameterType};
12use anyhow::{Result, anyhow, bail};
13use chrono::{DateTime, Local, Utc};
14use serde::{Deserialize, Serialize};
15use serde_with::skip_serializing_none;
16use std::collections::HashMap;
17use std::sync::Arc;
18use std::sync::atomic::Ordering;
19use verinfo::VersionInfo;
20
21/// このモジュールの関数をすべて登録する。
22pub fn register_all<T: 'static>(func_table: &mut FunctionTable<T>) {
23    register_debug_mode(func_table);
24    register_get_assistant_info(func_table);
25    //register_get_rate_limit(func_table);
26    register_get_current_datetime(func_table);
27}
28
29/// デバッグモード取得/設定。
30async fn debug_mode(bctx: Arc<BasicContext>, args: &FuncArgs) -> Result<String> {
31    let enabled = get_arg_bool_opt(args, "enabled")?;
32
33    #[skip_serializing_none]
34    #[derive(Serialize)]
35    struct FuncResult {
36        current: bool,
37        previous: Option<bool>,
38    }
39
40    let result = if let Some(enabled) = enabled {
41        let old = bctx.debug_mode.swap(enabled, Ordering::SeqCst);
42
43        FuncResult {
44            current: enabled,
45            previous: Some(old),
46        }
47    } else {
48        let current = bctx.debug_mode.load(Ordering::SeqCst);
49
50        FuncResult {
51            current,
52            previous: None,
53        }
54    };
55
56    Ok(serde_json::to_string(&result).unwrap())
57}
58
59fn register_debug_mode<T: 'static>(func_table: &mut FunctionTable<T>) {
60    let mut properties = HashMap::new();
61    properties.insert(
62        "enabled".to_string(),
63        ParameterElement {
64            type_: vec![ParameterType::Boolean, ParameterType::Null],
65            description: Some(
66                "New value. If not specified, just get the current value.".to_string(),
67            ),
68            ..Default::default()
69        },
70    );
71
72    func_table.register_function(
73        Function {
74            name: "debug_mode".to_string(),
75            description: Some("Get/Set debug mode of function calls".to_string()),
76            parameters: Parameters {
77                properties,
78                required: vec!["enabled".to_string()],
79                ..Default::default()
80            },
81            ..Default::default()
82        },
83        |bctx, _ctx, args| Box::pin(debug_mode(bctx, args)),
84    );
85}
86
87/// AI アシスタント情報取得
88async fn get_assistant_info(bctx: Arc<BasicContext>, _args: &FuncArgs) -> Result<String> {
89    let model = bctx.ctrl.sysmods().openai.lock().await.model_info().await?;
90    let build_info = verinfo::version_info_struct();
91    let rpienv = rpienv::raspi_env();
92    let cpu = get_cpu_status().await?;
93    let memory = get_memory_status().await?;
94    let disk = get_disk_status().await?;
95
96    #[skip_serializing_none]
97    #[derive(Serialize)]
98    struct RpiEnv {
99        model: &'static str,
100        cameras: Option<&'static Vec<CameraInfo>>,
101    }
102    let rpienv = match rpienv {
103        RaspiEnv::RasRi { model, cameras } => {
104            let cameras = Some(cameras).filter(|v| !v.is_empty());
105            RpiEnv { model, cameras }
106        }
107        RaspiEnv::NotRasRi => RpiEnv {
108            model: "Not Raspberry Pi",
109            cameras: None,
110        },
111    };
112
113    #[derive(Serialize)]
114    struct Info {
115        ai_model: ModelInfo,
116        build: &'static VersionInfo,
117        env: RpiEnv,
118        cpu: CpuStatus,
119        memory: MemoryStatus,
120        disk: DiskStatus,
121    }
122    let info = Info {
123        ai_model: model,
124        build: build_info,
125        env: rpienv,
126        cpu,
127        memory,
128        disk,
129    };
130
131    Ok(serde_json::to_string(&info).unwrap())
132}
133
134#[skip_serializing_none]
135#[derive(Debug, Serialize, Deserialize)]
136struct CpuStatus {
137    number_of_cores: u32,
138    usage_percent: f32,
139    temperature_celsius: Option<f32>,
140    throttle_status: Option<Vec<String>>,
141}
142
143/// CPU 使用率情報取得。
144async fn get_cpu_status() -> Result<CpuStatus> {
145    let cpu_info = get_cpu_info().await?;
146
147    let number_of_cores = get_cpu_cores().await?;
148    let throttle_status = get_throttle_status().await?.map(|st| {
149        let mut v = vec![];
150        if st.contains(ThrottleFlags::UNDER_VOLTAGE) {
151            v.push("Under Voltage".to_string());
152        }
153        if st.contains(ThrottleFlags::SOFT_TEMP_LIMIT) {
154            v.push("Soft Throttled".to_string());
155        }
156        if st.contains(ThrottleFlags::THROTTLED) {
157            v.push("Hard Throttled".to_string());
158        }
159        v
160    });
161    let throttle_status = throttle_status.filter(|v| !v.is_empty());
162
163    Ok(CpuStatus {
164        number_of_cores,
165        usage_percent: cpu_info.cpu_percent_total as f32,
166        temperature_celsius: cpu_info.temp.map(|t| t as f32),
167        throttle_status,
168    })
169}
170
171#[derive(Serialize)]
172struct MemoryStatus {
173    total_gib: f32,
174    available_gib: f32,
175    usage_percent: f32,
176}
177
178/// メモリ使用量取得。
179async fn get_memory_status() -> Result<MemoryStatus> {
180    let mem_info = get_mem_info().await?;
181    let usage_percent =
182        (((mem_info.total_mib - mem_info.avail_mib) / mem_info.total_mib) * 100.0) as f32;
183
184    Ok(MemoryStatus {
185        total_gib: (mem_info.total_mib / 1024.0) as f32,
186        available_gib: (mem_info.avail_mib / 1024.0) as f32,
187        usage_percent,
188    })
189}
190
191#[derive(Serialize)]
192struct DiskStatus {
193    total_gib: f32,
194    available_gib: f32,
195    usage_percent: f32,
196}
197
198/// ディスク使用量取得。
199async fn get_disk_status() -> Result<DiskStatus> {
200    let disk_info = get_disk_info().await?;
201    let usage_percent =
202        (((disk_info.total_gib - disk_info.avail_gib) / disk_info.total_gib) * 100.0) as f32;
203
204    Ok(DiskStatus {
205        total_gib: disk_info.total_gib as f32,
206        available_gib: disk_info.avail_gib as f32,
207        usage_percent,
208    })
209}
210
211fn register_get_assistant_info<T: 'static>(func_table: &mut FunctionTable<T>) {
212    func_table.register_function(
213        Function {
214            name: "get_assistant_info".to_string(),
215            description: Some("AI model, build info, hardware env, cpu/memory/disk".to_string()),
216            parameters: Parameters {
217                properties: Default::default(),
218                required: Default::default(),
219                ..Default::default()
220            },
221            ..Default::default()
222        },
223        |bctx, _ctx, args| Box::pin(get_assistant_info(bctx, args)),
224    );
225}
226
227/// レートリミット情報取得。
228async fn get_rate_limit(bctx: Arc<BasicContext>, _args: &FuncArgs) -> Result<String> {
229    let exp = bctx
230        .ctrl
231        .sysmods()
232        .openai
233        .lock()
234        .await
235        .get_expected_rate_limit();
236
237    exp.ok_or_else(|| anyhow!("No data")).map(|exp| {
238        format!(
239            "Remaining\nRequests: {} / {}\nTokens: {} / {}",
240            exp.remaining_requests, exp.limit_requests, exp.remaining_tokens, exp.limit_tokens,
241        )
242    })
243}
244
245#[allow(dead_code)]
246fn register_get_rate_limit<T: 'static>(func_table: &mut FunctionTable<T>) {
247    func_table.register_function(
248        Function {
249            name: "get_rate_limit".to_string(),
250            description: Some("Get rate limit info of GPT usage".to_string()),
251            parameters: Parameters {
252                properties: Default::default(),
253                required: Default::default(),
254                ..Default::default()
255            },
256            ..Default::default()
257        },
258        |bctx, _ctx, args| Box::pin(get_rate_limit(bctx, args)),
259    );
260}
261
262/// 現在の日時を取得する。
263async fn get_current_datetime(args: &FuncArgs) -> Result<String> {
264    let tz = get_arg_str(args, "tz")?;
265    match tz {
266        "JST" => {
267            let dt: DateTime<Local> = Local::now();
268            Ok(dt.to_string())
269        }
270        "UTC" => {
271            let dt: DateTime<Utc> = Utc::now();
272            Ok(dt.to_string())
273        }
274        _ => {
275            bail!("Parameter tz must be JST or UTC")
276        }
277    }
278}
279
280fn register_get_current_datetime<T: 'static>(func_table: &mut FunctionTable<T>) {
281    let mut properties = HashMap::new();
282    properties.insert(
283        "tz".to_string(),
284        ParameterElement {
285            type_: vec![ParameterType::String],
286            description: Some("Time zone".to_string()),
287            enum_: Some(vec!["JST".to_string(), "UTC".to_string()]),
288        },
289    );
290
291    func_table.register_function(
292        Function {
293            name: "get_current_datetime".to_string(),
294            description: Some("Get the current date and time".to_string()),
295            parameters: Parameters {
296                properties,
297                required: vec!["tz".to_string()],
298                ..Default::default()
299            },
300            ..Default::default()
301        },
302        |_, _, args| Box::pin(get_current_datetime(args)),
303    );
304}