LLMで時間表現の解析

# 概要
「2021年4月1日の1日前の午後10時」を「2021-03-31 22:00:00」にする、みたいな時間表現の解析をLLMで試してみました。
今回のテストケースでは、gpt-4o-miniは高精度だが少し不安定（19問中16問正解）、gpt-4oは全問正解、という感じでした。

# 背景
文中の時間表現を文脈を考慮して正規化したいです。例えば「2021年4月1日の1日前の午後10時」を「2021-03-31 22:00:00」にする、みたいなことです。応用先としては、対話形式でのスケジュール設定などがあります。

これまでルールベースでやる方法を試してきましたがエッジケースを考えるとルールの実装が大変です。

- [文脈を考慮した時間表現の正規化（ja-timex）](https://qiita.com/shimajiroxyz/items/035f5a7edec5e73add7a)
- [曜日の時間表現の解析](https://qiita.com/shimajiroxyz/items/7441aa4a7772c7596f7b)

そこで、今更ですが、LLMでどのくらいできるのか試してみました。
試みとしては、ちゃんと調べていませんが、ありふれた課題設定なので、たくさんやられていると思いますが、少し探した中では見つからなかったので、自分で試したほうが早いと思い、やってみました。

# テストデータと評価システム実装
過去の取り組みで指標にしていた以下のテストデータをLLMで解析します。

| query                          | expected              | reference                        |
|--------------------------------|-----------------------|----------------------------------|
| 次の水曜日の同じ時間            | 2024-11-06T11:00:00   | 2024-10-30T11:00:00（水曜日）   |
| 再来週の水曜日の同じ時間        | 2024-11-13T11:00:00   | 2024-10-30T11:00:00（水曜日）   |
| 次の次の水曜日の同じ時間        | 2024-11-13T11:00:00   | 2024-10-30T11:00:00（水曜日）   |
| 今週の水曜日の同じ時間          | 2024-10-30T11:00:00   | 2024-10-30T11:00:00（水曜日）   |
| 今週の火曜日の同じ時間          | 2024-10-29T11:00:00   | 2024-10-30T11:00:00（水曜日）   |
| 次の月曜日の同じ時間            | 2024-11-04T11:00:00   | 2024-10-30T11:00:00（水曜日）   |
| 次の金曜日の同じ時間            | 2024-11-01T11:00:00   | 2024-10-30T11:00:00（水曜日）   |
| 次の次の月曜日の同じ時間        | 2024-11-11T11:00:00   | 2024-10-30T11:00:00（水曜日）   |
| 次の日曜日の同じ時間            | 2024-11-03T11:00:00   | 2024-10-30T11:00:00（水曜日）   |
| 再来週の日曜日の同じ時間        | 2024-11-17T11:00:00   | 2024-10-30T11:00:00（水曜日）   |
| 明日の7時                      | 2024-12-25T07:00:00   | 2024-12-24T15:15:15（火曜日）   |
| 明日の午後7時                  | 2024-12-25T19:00:00   | 2024-12-24T15:15:15（火曜日）   |
| 朝9時                          | 2024-12-25T09:00:00   | 2024-12-24T15:15:15（火曜日）   |
| 夕方6時                        | 2024-12-24T18:00:00   | 2024-12-24T15:15:15（火曜日）   |
| 1ヶ月後の同じ時間              | 2025-01-24T15:15:15   | 2024-12-24T15:15:15（火曜日）   |
| 1年3ヶ月後の同じ時間            | 2026-03-24T15:15:15   | 2024-12-24T15:15:15（火曜日）   |
| 26日の7時                      | 2024-12-26T07:00:00   | 2024-12-24T15:15:15（火曜日）   |
| 20分後                         | 2024-12-24T15:35:15   | 2024-12-24T15:15:15（火曜日）   |
| 50分後                         | 2024-12-24T16:05:15   | 2024-12-24T15:15:15（火曜日）   |

タスクとしては、referenceを現在時刻として、queryが示す時刻をISO8601拡張形式で出力します。これをexpectedを比較します。
LLMへのプロンプトは以下のとおりです。Chain of Thoughtさせたほうが性能が上がると期待されるので、そのように指示をしています。systemとuserメッセージをopenai api形式でリストにしてLLMに渡しています。modelはgpt-4oまたはgpt-4o-miniです。

```
/system
与えられた文章が示す日時をiso8601拡張形式（YYYY-MM-DDTHH:MM:SS）で出力してください。現在時刻は{{ reference }}とします。週の始まりは月曜日とします。候補が複数ある場合は最も近い未来の日時としてください。ステップバイステップで考えてください。

/user
{{ query }}
```

上記のプロンプトで得られた出力をgpt-4o-miniで構造化してdatetimeを取得し、expectedと完全一致で比較しています。
[dify](https://github.com/langgenius/dify)のワークフローで上記処理を実行し、バッチで結果を得ました。

# 結果
正答率はgpt-4oが19/19（全問正解）、gpt-4o-miniが16/19でした。
gpt-4o-miniが間違えた3問を以下に示します。

## 今週の火曜日の同じ時間
- 基準日時: 2024-10-30T11:00:00（水曜日）
- 正解: 2024/10/29 11:00:00
- gpt-4o-mini回答: 2024/11/05 11:00:00
- gpt-4o-miniのthought:
  ```
  現在の日時は2024-10-30T11:00:00です。

  1. **現在の曜日を確認**: 2024-10-30は水曜日です。
  2. **今週の火曜日を特定**: 今週の火曜日は2024-10-29T11:00:00です。
  3. **火曜日の日時を確認**: 今週の火曜日の日時は一度過ぎているため、次の火曜日を考えます。
  4. **次の火曜日を計算**: 次の火曜日は2024-11-05T11:00:00です。
  5. **最も近い未来の日時を確定**: 次の火曜日の日時が必要なため、2024-11-05T11:00:00になります。

  したがって、今週の火曜日の同じ時間は次のようになります：

  **2024-11-05T11:00:00**
  gpt-4o-miniのthoughts
  ```

### 考察
基準日時が水曜日なので「今週の火曜日」は前日なのですが、翌週の火曜日を出力しています。「候補が複数ある場合は最も近い未来の日時としてください。」という指示の「候補が複数ある場合は」という条件を読み落としてしまったようです。

ちなみにgpt-4oだと以下のように正解しています。賢いですね。

```
現在の日付は2024年10月30日（水曜日）です。「今週の火曜日の同じ時間」とは、今週の火曜日に相当する日付と時刻を求めることになります。

1. まず、現在の日付2024年10月30日（水曜日）から週の始まりを確認すると、週の始まりは月曜日です。

2. 今週の月曜日の日付を求めます。2024年10月30日（水曜日）から2日前が月曜日です。したがって、今週の月曜日は2024年10月28日です。

3. 次に、今週の火曜日を求めます。今週の月曜日2024年10月28日から1日後が火曜日です。したがって、今週の火曜日は2024年10月29日です。

4. 現在の時刻は11:00:00ですので、同じ時刻の火曜日の日付と時刻は2024年10月29日T11:00:00です。

したがって、今週の火曜日の同じ時間は2024-10-29T11:00:00です。
```
## 再来週の日曜日の同じ時間
- 基準日時: 2024-10-30T11:00:00（水曜日）
- 正解: 2024/11/17T11:00:00
- gpt-4o-mini回答: 2024/11/12T11:00:00
- gpt-4o-miniのthoughts:
  ```
  現在の日時は2024年10月30日（水曜日）11:00:00です。この時点から再来週の日曜日の同じ時間を計算します。以下のステップで進めます。

  1. **現在の日付を確認**: 2024年10月30日（水曜日）です。
  2. **再来週の日曜日を特定**: 現在の日付から2週間後の日曜日を求めます。
    - 現在の日付から1週間後は2024年11月6日（水曜日）。
    - 1週間後の日曜日は2024年11月5日（前の週の土曜日から数えて日曜日）。
    - さらに1週間後、すなわち再来週の日曜日は2024年11月12日（日曜日）です。
  3. **同じ時間を設定**: 現在の時間は11:00:00なので、再来週の日曜日の11:00:00を設定します。

  以上の計算から、再来週の日曜日の同じ時間は以下のようになります。

  2024年11月12日（日曜日）の11:00:00。

  したがって、ISO 8601拡張形式での出力は次の通りです。

  **2024-11-12T11:00:00**
  ```
### 考察
現在の日付の1週間後は「11/6」とわかっているのに、その1週間後の日曜日は「11/5」と謎の計算をしており、ミスに繋がっています。

gpt-4oは以下の通り正解しています。先に今週の日曜日を求めてから2週間足しています。賢いです。

```
現在時刻は2024年10月30日（水曜日）です。再来週の日曜日を求めるために、次のステップで計算を行います。

1. **現在の週を確認**:
   - 現在は2024年10月30日水曜日です。

2. **今週の日曜日を求める**:
   - 2024年10月30日（水）は週の3日目（週の始まりは月曜日）です。
   - 今週の日曜日は、2024年11月3日です。

3. **再来週の日曜日を求める**:
   - 今週の日曜日（2024年11月3日）からさらに1週間後が来週の日曜日、つまり2024年11月10日です。
   - さらにもう1週間後が再来週の日曜日、つまり2024年11月17日です。

4. **時間を同じに保つ**:
   - 現在時刻は2024年10月30日11:00:00ですので、再来週の日曜日の同じ時間は2024年11月17日11:00:00になります。

したがって、再来週の日曜日の同じ時間は、2024-11-17T11:00:00です。
```
## 1年3ヶ月後の同じ時間

- 基準日時: 2024-12-24T15:15:15（火曜日）
- 正解: 2026/03/24T15:15:15
- gpt-4o-mini回答: 2025/03/24T15:15:15
- gpt-4o-miniのthoughts:
  ```
  現在時刻は2024年12月24日15時15分15秒です。この日時から1年3ヶ月後の同じ時間を計算します。

  1. 現在の年は2024年です。
    - 1年後: 2025年
  2. 現在の月は12月です。
    - 3ヶ月後: 12月 + 3ヶ月 = 3月（翌年）
  3. 現在の時間は15時15分15秒です。

  したがって、1年3ヶ月後の日時は以下のように計算されます。

  - 年: 2024 + 1 = 2025
  - 月: 12 + 3 = 3（翌年の3月）
  - 日: 24日（そのまま）
  - 時間: 15時15分15秒（そのまま）

  これをISO 8601形式で表すと、以下のようになります。

  **出力**: 2025-03-24T15:15:15
  ```

### 考察
基準日が12月なので、1年3ヶ月後は西暦が繰り上がって合計+2になるのですが、繰り上げを忘れて+1の2025年としており不正解です。
コーディングで求めさせたら正解できた可能性もあります。

gpt-4oの場合は以下の通り正解しています。きちんと3ヶ月足すと翌年になることを考慮できています。

```
現在時刻は2024-12-24T15:15:15です。ここから1年3ヶ月後の同じ時刻を計算します。

1. 1年後：2024年に1年を足すと2025年になります。
   - 2024-12-24T15:15:15 + 1年 = 2025-12-24T15:15:15

2. 3ヶ月後：2025年12月から3ヶ月を足します。
   - 12月に3ヶ月を足すと、翌年の3月になります。
   - したがって、2025年12月から3ヶ月後は2026年3月です。

したがって、1年3ヶ月後の同じ時間は2026年3月24日15:15:15です。

答えは2026-03-24T15:15:15です。
```

# おわりに
LLMで自然言語の時間表現の正規化を試してみました。
全体的に精度が高くLLMさすがでした。
速度、コスト面で、入力フォーマットがある程度制限されている状態ならルールベースを使う旨味はまだまだあると思いますが、広範なフォーマットに柔軟に対応しようと思うと、LLMでいいじゃん、かもしれません。
今回のテストケースが簡単すぎる可能性もあり、思いついていませんが、コーパスなど眺めるとひょっとしたらLLMで太刀打ちできないようなケースもあるかもしれません。