ずんだもんが天気予報を読み上げる!? ChatGPTとVOICEVOXで実現した自動天気予報生成システム

# はじめに
ChatGPTとVOICEVOXを用いて、気象データから天気予報テキストを自動生成し、それをキャラクター"ずんだもん"のユニークな声で読み上げる仕組みを構築した。

## 今回の取り組みの概要
- ChatGPTに気象データ取得の機能を追加する
- 気象データを元にChatGPTに天気予報テキストを自動生成させる
- "ずんだもん"に天気予報テキストを読み上げさせる　

## ChatGPT x ずんだもん による天気予報の読み上げ結果

今回の取り組みで ずんだもん に天気予報の読み上げさせた結果を示す

### 天気を問い合わせた場合

<iframe width="560" height="315" src="https://www.youtube.com/embed/8A06mEk1Z08" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

### 天気以外を問い合わせた場合

<iframe width="560" height="315" src="https://www.youtube.com/embed/ZoC3Z3sfGZA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>


# ChatGPTに気象データを取得させる

ChatGPTは2021年までの情報に基づいてトレーニングされており、最新のデータやリアルタイムの情報にはアクセスできない。つまり最新の気象データに基づく天気予報テキストを生成したければ、その情報を何らかの手段でChatGPTに与える必要がある。

ChatGPTにはすでに天気情報にアクセス可能なプラグインが複数存在している。それらのプラグインをLangchainから利用するのが最も簡単だろう。

https://vercel.com/templates/next.js/weathergpt


https://note.com/npaka/n/nb7a5ad7a7f27

ところがlangchainからChatGPTのプラグインを利用した場合、途中で処理が終了してしまう事が発生してなかなか上手く行かなかった。まだまだこの辺りは安定していないのかもしれない。

そこで今回は、ChatGPTのAPIのアップデートで追加された関数呼び出し機能を用いる。気象に関する問い合わせがあった場合にはChatGPTが自作の関数を用いて気象データを取得する。

## 気象データ取得サイト
https://open-meteo.com/en/docs

気象データは次のような形式で取得される
```json
{
	"latitude": 52.52,
	"longitude": 13.419998,
	"generationtime_ms": 0.2809762954711914,
	"utc_offset_seconds": 32400,
	"timezone": "Asia/Tokyo",
	"timezone_abbreviation": "JST",
	"elevation": 38.0,
	"current_weather": {
		"temperature": 14.6,
		"windspeed": 9.8,
		"winddirection": 287.0,
		"weathercode": 2,
		"is_day": 1,
		"time": "2023-06-24T13:00"
	},
	"daily_units": {
		"time": "iso8601",
		"weathercode": "wmo code",
		"temperature_2m_max": "°C",
		"temperature_2m_min": "°C"
	},
	"daily": {
		"time": [
			"2023-06-24",
			"2023-06-25",
			"2023-06-26",
			"2023-06-27",
			"2023-06-28",
			"2023-06-29",
			"2023-06-30"
		],
		"weathercode": [
			61,
			3,
			80,
			80,
			61,
			61,
			61
		],
		"temperature_2m_max": [
			23.3,
			25.7,
			29.7,
			23.1,
			20.9,
			19.3,
			22.4
		],
		"temperature_2m_min": [
			14.6,
			16.6,
			17.8,
			13.2,
			13.7,
			17.8,
			13.3
		]
	}
}
```


## 実装方法

https://github.com/aRaikoFunakami/voicechat_tv/blob/main/openai_chat.py

`weather_function` の `description` にChatGPTに呼び出させる`get_weather_info` 関数を登録する。ChatGPTは説明文と入力文を突き合わせて必要に応じて `get_weather_info` を呼び出すか、ChatGPT自身で回答するかを決める。

```python
weather_function = {
    "name": "get_weather_info",
    "description": "緯度と経度の情報から現在の天気を取得します",
    "parameters": {
        "type": "object",
        "properties": {
            "latitude": {
                "type": "string",
                "description": "緯度の情報",
            },
            "longitude": {
                "type": "string",
                "description": "経度の情報",
            },
        },
        "required": ["latitude", "longitude"],
    },
}
```

```python
def get_weather_info(latitude, longitude):
    base_url = "https://api.open-meteo.com/v1/forecast"
    parameters = {
        "latitude": latitude,
        "longitude": longitude,
        #        "current_weather": "true",
        "hourly": "temperature_2m,relativehumidity_2m",
        "timezone": "Asia/Tokyo"
    }
    response = requests.get(base_url, params=parameters)
    if response.status_code == 200:
        data = response.json()
        return json.dumps(data)
    else:
        return None
```

作成したプログラムのテストとして次のような`prompt1`と`prompt2`をChatGPTに入力した結果を示す。ChatGPTからの回答を同期的に受け取る方法とストリーミングで受け取る場合の２種類を実行した。
天気に関する入力の場合には`get_weather_info`を呼び出し気象データをChatGPTに渡した上で回答を作成する。天気に関する入力でない場合には通常通りChatGPTが直接回答する。

```python
question1 = "横浜の今日の天気を詳しく教えてください"
prompt1 = f'''
あなたは天気を説明するアナウンサーです
次の条件に従って入力文に回答してください
#条件:
温度19度や湿度20%といった数字はできるだけ使わず、暑苦しい、肌寒い、など感覚的な回答を行う
雨の可能性があれば傘をもって出かけるべきだと回答する
10文字以内で答えよ
#入力文:
{question1}
'''

question2 = "織田信長について答えよ"
prompt2 = f'''
10文字以内で答えよ
#入力文:
{question2}
'''
```


```terminal
$ python3 ./openai_chat.py

2023-06-24 11:22:00,323 - INFO:root - chatstart
2023-06-24 11:22:01,696 - INFO:root - message: {
  "content": null,
  "function_call": {
    "arguments": "{\n  \"latitude\": \"35.4437\",\n  \"longitude\": \"139.6380\"\n}",
    "name": "get_weather_info"
  },
  "role": "assistant"
}
2023-06-24 11:22:01,697 - INFO:root - 選択された関数を呼び出す: get_weather_info
2023-06-24 11:22:02,506 - INFO:root - 選択された関数に最適な prompt を選ぶ
2023-06-24 11:22:02,506 - INFO:root - 関数の回答:{"latitude": 35.45, "longitude": 139.625, "generationtime_ms": 0.15997886657714844, "utc_offset_seco...
{'response': '横浜の今日の天気は、肌寒いです。気温は21度で、湿度は96%です。雨の可能性があるため、傘を持って出かけることをおすすめします。', 'finish_reason': 'stop'}
2023-06-24 11:22:06,607 - INFO:root - 
2023-06-24 11:22:06,608 - INFO:root - chatstart
2023-06-24 11:22:08,397 - INFO:root - 選択された関数を呼び出す: get_weather_info
2023-06-24 11:22:09,635 - INFO:root - 選択された関数に最適な prompt を選ぶ
2023-06-24 11:22:09,636 - INFO:root - 関数の回答:{"latitude": 35.45, "longitude": 139.625, "generationtime_ms": 0.19502639770507812, "utc_offset_seco...
2023-06-24 11:22:10,394 - INFO:root - response:{"response": "", "finish_reason": ""}
2023-06-24 11:22:10,394 - INFO:root - response:{"response": "\u6a2a", "finish_reason": ""}
... (省略)
2023-06-24 11:22:11,913 - INFO:root - response:{"response": "\u3002", "finish_reason": ""}
2023-06-24 11:22:11,936 - INFO:root - response:{"response": "", "finish_reason": "stop"}
{'response': '横浜の今日の天気は、肌寒いです。気温は21.7℃で、湿度は97%です。雨の可能性があるため、傘を持って出かけることをおすすめします。', 'finish_reason': 'stop'}
2023-06-24 11:22:11,937 - INFO:root - 
2023-06-24 11:22:11,937 - INFO:root - chatstart
2023-06-24 11:22:12,666 - INFO:root - message: {
  "content": "\u6b66\u5c06",
  "role": "assistant"
}
{'response': '武将', 'finish_reason': 'stop'}
2023-06-24 11:22:12,667 - INFO:root - 
2023-06-24 11:22:12,668 - INFO:root - chatstart
2023-06-24 11:22:13,157 - INFO:root - response:{"response": "", "finish_reason": ""}
2023-06-24 11:22:13,158 - INFO:root - response:{"response": "\u5929", "finish_reason": ""}
... (省略)
2023-06-24 11:22:13,336 - INFO:root - response:{"response": "\u5bb6", "finish_reason": ""}
2023-06-24 11:22:13,347 - INFO:root - response:{"response": "", "finish_reason": "stop"}
{'response': '天才的な軍略家', 'finish_reason': 'stop'}
```

| 質問 | 回答1 | 回答2 |
|:-|:-|:-|
| 横浜の今日の天気を詳しく教えてください  | 横浜の今日の天気は、肌寒いです。気温は21度で、湿度は96%です。雨の可能性があるため、傘を持って出かけることをおすすめします。  |   横浜の今日の天気は、肌寒いです。気温は21.7℃で、湿度は97%です。雨の可能性があるため、傘を持って出かけることをおすすめします。|
| 織田信長について答えよ  | 武将  | 天才的な軍略家  |


# VOICEVOXを使った読み上げ
VOICEVOXは音声合成エンジン。特にその中でもずんだもんの声は、そのユーモラスで個性的な発音が人気を博している。このずんだもんの声を用いて、ChatGPTから生成される天気予報の文章を読み上げる。

https://voicevox.hiroshiba.jp/

## VOICEVOXを音声合成サーバーとして利用する
- VOICEVOXのアプリをインストール
- VOICEVOXのアプリを実行 (ローカルWebサーバーが立ち上がる)
- VOICEVOXのローカルWebサーバーに対して問い合わせを行う

### VOICEVOXのローカルWebサーバーの利用方法
https://github.com/VOICEVOX/voicevox_engine/blob/master/

```terminal: 使用法 (https://github.com/VOICEVOX/voicevox_engine/blob/master/README.md）
$ echo -n "こんにちは、音声合成の世界へようこそ" >text.txt

$ curl -s \
    -X POST \
    "127.0.0.1:50021/audio_query?speaker=1"\
    --get --data-urlencode text@text.txt \
    > query.json

$ curl -s \
    -H "Content-Type: application/json" \
    -X POST \
    -d @query.json \
    "127.0.0.1:50021/synthesis?speaker=1" \
    > audio.wav
```

## ずんだもん に読み上げさせる
`ずんだもん`にテキストデータを読み上げされるには次の２つのプロセスが必要だ。
それぞれの処理にそれなりの時間がかかるため、リアルタイム処理で音声合成を利用したい場合には音声読み上げ中に音声合成処理を裏で進めておきたい。
- テキストデータから音声合成用のクエリを作成する
- 音声合成用のクエリから音声合成する

次のプログラムではテキストデータを与えた順にクエリ作成・音声合成を非同期で進め、同期的に読み上げを行う。つまり、"ハロー”、”ワールド”を引数に`playAudioByVoiceVox()`を順に呼び出すと、"ハロー" と "ワールド" のクエリ作成・音声合成は並列に実行されつつ、その処理の終了した順ではなく、`playAudioByVoiceVox()`を呼び出した順に"ハロー"、"ワールド"と読み上げる。


https://github.com/aRaikoFunakami/voicechat_tv/blob/main/templates/playAudioByVoiceVox.js


# 利用規約
`VOICEVOX`と`ずんだもん`の利用規約について情報をまとめておく

## VOICEVOXの利用規約

https://voicevox.hiroshiba.jp/

:::note info
- ずんだもんの音声ライブラリを用いて生成した音声は、「VOICEVOX:ずんだもん」とクレジットを記載すれば、商用・非商用で利用可能です。
- 玄野武宏の音声ライブラリを用いて生成した音声は、「VOICEVOX:玄野武宏」とクレジットを記載すれば、商用・非商用で利用可能です。
:::


## 東北ずん子プロジェクト　キャラクター利用の手引き

https://zunko.jp/guideline.html

:::note info
Q : 技術紹介でWebページや書籍に使いたい
A : この場合は例外的に商用利用はOKです
プログラミングや3Dなどの利用方法を解説するといった目的で、東北ずん子をWebページや書籍で使うことは例外的に商用利用が可能です。
（東北ずん子の使い方を広げる目的に合致するからです）
:::

## クレジット

### VOICEVOX

- VOICEVOX:ずんだもん
- VOICEVOX:玄野武宏

### Big Buck Bunney
オリジナルURL：https://peach.blender.org/
新しいリリース（4k、HFR、3D）Sunflower version：
古いバージョン：https://download.blender.org/peach/bigbuckbunny_movies/

The movie is licensed under CC-BY, read more about what that means

Big Buck Bunny Copyright (C) 2008 Blender Foundation | peach.blender.org Some Rights Reserved.
Creative Commons Attribution 3.0 license. https://peach.blender.org/

Stereoscopic / 4K / 60 fps / HDRI changes Copyright (C) 2013 Janus B. Kristensen Some Rights Reserved.
Creative Commons Attribution 3.0 license.

# 参考資料

https://dev.classmethod.jp/articles/understand-openai-function-calling/

https://note.com/it_navi/n/ncc9f000967f2

https://paiza.hatenablog.com/entry/2021/11/04/130000