[LINE WORKS] GPTとFunction Callingを使ってカレンダーに予定を入れてくれるアシスタントbotを作ってみる

# はじめに
OpenAIのAPIを使って、LINE WORKSのカレンダーに予定を登録してくれるアシスタントbotを作ってみました。

OpenAIのGPTとFunction Callingの機能で、会話の中で予定作成が必要な場面が出たら、自動でLINE WORKSカレンダーの方に予定を追加してくれるアシスタントです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241369/b7c23333-d609-2798-cab7-64b2b2dd75a7.png)

# ソースコード
https://github.com/mmclsntr/lineworks-assistant-bot

# 動くところ
![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241369/e03873bd-d26d-e631-5c84-602f852a41e7.gif)


# 仕組み
GPTによって自然な会話を生成しながらも、スケジュールの話をするとあらかじめ登録しておいた `add_schedule` Functionを呼び出して、LINE WORKS APIの「Calendar API」を使って、その予定を自動で登録。

[基本カレンダーの予定の登録 - LINE WORKS API](https://developers.worksmobile.com/jp/docs/calendar-default-event-user-create　)


```python:lineworks.py
def create_event_to_user_default_calendar(summary: str, start_time: str, end_time: str, user_id: str, access_token: str):
    """Create an event to user default calendar
    """

    url = "{}/users/{}/calendar/events".format(BASE_API_URL, user_id)

    headers = {
          'Content-Type' : 'application/json',
          'Authorization' : "Bearer {}".format(access_token)
        }

    params = {
      "eventComponents": [
          {
              "summary": summary,
              "start": {
                "dateTime": start_time,
                "timeZone": "Asia/Tokyo"
              },
              "end": {
                "dateTime": end_time,
                "timeZone": "Asia/Tokyo"
              }
          }
      ]
    }
    form_data = json.dumps(params)

    r = requests.post(url=url, data=form_data, headers=headers)

    r.raise_for_status()

```

```python:add_schedule
def add_schedule(title, start_time, end_time, user_id, access_token) -> str:
    lineworks.create_event_to_user_default_calendar(title, start_time, end_time, user_id, access_token)
    return "予定をカレンダーへ登録しました。\n\nタイトル: {}\n開始日時: {}\n終了日時: {}".format(title, start_time, end_time)
```

実際のOpenAIのChat APIの呼び出し部はこちら。
ポイントとしては、あらかじめシステムスクリプトとして現在時刻を渡しておくことで、相対的な時間指定 (「2時間後」「明日」「来週火曜」など) が可能。

```python:会話部
def chatWithGPT(text: str, api_type: str, model: str, user_id, access_token) -> str:
    dt_now = datetime.now(timezone(timedelta(hours=9)))
    system_script = """現在の時刻は{}です。予定の登録を依頼されたらカレンダーに登録します。
    現在の時刻から依頼された時間が午前なのか午後なのかを判断して過去の時間に予定が入らないよう適切に処理してください。
    その他の返答は2000文字以内にしてください。""".format(dt_now)

    functions = [
        {
            "name": "add_schedule",
            "description": "予定をカレンダーへ登録する。",
            "parameters": {
                "type": "object",
                "properties": {
                    "title": {
                        "type": "string",
                        "description": "予定のタイトル。これは空欄とすることはできない。"
                    },
                    "start_time": {
                        "type": "string",
                        "description": "予定の開始日時。形式は \"YYYY-MM-DDTHH:mm:ss\" とする。これは空欄とすることはできない。既定値は現在の日時から1時間後の正時間とする。"
                    },
                    "end_time": {
                        "type": "string",
                        "description": "予定の終了日時。形式は \"YYYY-MM-DDTHH:mm:ss\" とする。これは空欄とすることはできない。既定値はstart_timeから1時間後の日時とする。"
                    }
                },
                "required": ["title", "start_time", "end_time"]
            },
        }
    ]

    if api_type == "azure":
        response = openai.ChatCompletion.create(
            deployment_id=model,
            messages=[
                {"role": "system", "content": system_script},
                {"role": "user", "content": text},
            ],
            functions=functions,
            function_call="auto"
        )
    else:
        response = openai.ChatCompletion.create(
            model=model,
            messages=[
                {"role": "system", "content": system_script},
                {"role": "user", "content": text},
            ],
            functions=functions,
            function_call="auto"
        )
    logger.info(response)
    res_msg = response.choices[0]["message"]

    reply_text = ""
    if res_msg.get("function_call"):
        function_name = res_msg["function_call"]["name"]
        function_args = json.loads(res_msg["function_call"]["arguments"])

        if function_name == "add_schedule":
            reply_text = add_schedule(function_args["title"],
                                      function_args["start_time"],
                                      function_args["end_time"],
                                      user_id,
                                      access_token)
    else:
        reply_text = res_msg["content"].strip()

    logger.info(reply_text)
    return reply_text

```

# まとめ
Function Callingは、こういった機能間の連携でうまく使える機能だと感じました。

こういうのを作ってみると、AIが仕事を支援するのが当たり前になるような未来が具体的に見えてきます。