sys/sysmod/openai/basicfuncs/
web.rs

1//! Web アクセス関連。
2
3use 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
13/// このモジュールの関数をすべて登録する。
14pub 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
19/// 気象情報地域のリストを取得する。
20async 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
45/// 気象情報を取得する。
46async 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    // 引数の都市名をコードに変換
51    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    // cargo test weather_report -- --ignored --nocapture
112    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}