sys/sysmod/openai/basicfuncs/
web.rs1use crate::sysmod::openai::ParameterType;
4use crate::sysmod::openai::function::{
5 FuncArgs, Function, FunctionTable, ParameterElement, Parameters, get_arg_str,
6};
7use anyhow::{Context, Result, anyhow};
8use reqwest::Client;
9use std::{collections::HashMap, time::Duration};
10use utils::netutil;
11use utils::weather::{self, ForecastRoot, OverviewForecast};
12
13pub fn register_all<T: 'static>(func_table: &mut FunctionTable<T>) {
15 register_get_weather_areas(func_table);
16 register_get_weather_report(func_table);
17}
18
19async fn get_weather_areas(_args: &FuncArgs) -> Result<String> {
21 let area_list: Vec<_> = weather::offices()
22 .iter()
23 .map(|info| info.name.clone())
24 .collect();
25
26 Ok(serde_json::to_string(&area_list).unwrap())
27}
28
29fn register_get_weather_areas<T: 'static>(func_table: &mut FunctionTable<T>) {
30 func_table.register_function(
31 Function {
32 name: "get_weather_areas".to_string(),
33 description: Some("Get area list for get_weather_report".to_string()),
34 parameters: Parameters {
35 properties: Default::default(),
36 required: Default::default(),
37 ..Default::default()
38 },
39 ..Default::default()
40 },
41 |_, _, args| Box::pin(get_weather_areas(args)),
42 );
43}
44
45async fn get_weather_report(args: &FuncArgs) -> Result<String> {
47 const TIMEOUT: Duration = Duration::from_secs(10);
48 let area = get_arg_str(args, "area")?;
49
50 let code = weather::office_name_to_code(area).ok_or_else(|| {
52 anyhow!(
53 "Invalid area: {} - You should call get_weather_areas to get valid name list.",
54 area
55 )
56 })?;
57
58 let url1 = weather::url_overview_forecast(&code);
59 let url2 = weather::url_forecast(&code);
60 let client = Client::builder().timeout(TIMEOUT).build()?;
61
62 let fut1 = netutil::checked_get_url(&client, &url1);
63 let fut2 = netutil::checked_get_url(&client, &url2);
64 let (resp1, resp2) = tokio::join!(fut1, fut2);
65 let (s1, s2) = (resp1?, resp2?);
66
67 let ov: OverviewForecast =
68 serde_json::from_str(&s1).with_context(|| format!("OverviewForecast parse error: {s1}"))?;
69 let fc: ForecastRoot =
70 serde_json::from_str(&s2).with_context(|| format!("ForecastRoot parse error: {s2}"))?;
71 let obj = weather::weather_to_ai_readable(&code, &ov, &fc)?;
72
73 Ok(serde_json::to_string(&obj).unwrap())
74}
75
76fn register_get_weather_report<T: 'static>(func_table: &mut FunctionTable<T>) {
77 let mut properties = HashMap::new();
78 properties.insert(
79 "area".to_string(),
80 ParameterElement {
81 type_: vec![ParameterType::String],
82 description: Some(
83 "Area name that list can be obtained by get_weather_areas".to_string(),
84 ),
85 ..Default::default()
86 },
87 );
88
89 func_table.register_function(
90 Function {
91 name: "get_weather_report".to_string(),
92 description: Some("Get whether report data".to_string()),
93 parameters: Parameters {
94 properties,
95 required: vec!["area".to_string()],
96 ..Default::default()
97 },
98 ..Default::default()
99 },
100 |_, _, args| Box::pin(get_weather_report(args)),
101 );
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use serde_json::Value;
108
109 #[tokio::test]
110 #[ignore]
111 async fn weather_report() -> Result<()> {
113 let mut args = FuncArgs::new();
114 args.insert("area".into(), Value::String("広島県".into()));
115
116 let text = get_weather_report(&args).await?;
117 println!("{text}");
118
119 Ok(())
120 }
121}