sys/sysmod/openai/basicfuncs/
system.rs1use 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
21pub 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_current_datetime(func_table);
27}
28
29async 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
87async 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
143async 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
178async 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
198async 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
227async 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
262async 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}