miiboのカスタムアクションのURLクロールで運行状況取得

miiboのカスタムアクションにはテンプレートがありますが、ここにURLクロール（Webページ情報取得）が追加されてました。

これ便利そうですね。テンプレというよりプラグインみたいな感じでインストールする機能にしちゃってもよいような機能。

ということで、リアルタイムにWebコンテンツを取得できているかが分かりやすいようにヤフーの路線情報から電車の運行状況を取得してみます。

https://x.com/n0bisuke/status/1921581184456302862

## 手順

### 1. カスタムアクション追加

- テンプレートからURLクロールを選択してインポート => OKで進みます。 

> ![CleanShot 2025-05-11 at 23.26.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/1a417081-98d5-4777-b985-fce13e53a6d4.png)

> ![CleanShot 2025-05-11 at 23.27.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/383ef493-2d93-4a85-a8b1-195610f944fc.png)

- 特に中身はいじらずに保存

> ![CleanShot 2025-05-11 at 23.28.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/3ae08346-c511-4c4a-8958-a9e52352762c.png)

これでOKです。

> ![CleanShot 2025-05-11 at 23.28.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/222c47dd-b3a5-479c-b996-d8739288246a.png)

### 2. Webhook設定

次はWebhook設定です。

連携タイプは`Custm Action`を指定して、先ほどの`URLクロール`のアクションを選択します。

- `AIにタイミングを判断させる`にチェック
- Function Name: `get_webcontent` などの適したタイトル
- Function Descriptioon: `ユーザーから指定されたURLの情報を取得する` など挙動の説明

> ![CleanShot 2025-05-11 at 23.30.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/9d32c2ec-f67f-48a4-96ad-c27f5ef7e235.png)

これで保存して、Webhookの公開設定を公開にすればOKです。

### 3. 試す

`https://transit.yahoo.co.jp/diainfo/area/4`などURLを送りつつ情報を教えてということでAIがカスタムアクションを実行して情報を取得してくれます。

ヤフーのサイトをみたら横浜線が遅れてたので横浜線の状況を聞いてみるとしっかりと**運転見合わせ**を教えてくれました。

> ![CleanShot 2025-05-12 at 00.10.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/c67601ab-bbaa-43dd-877f-df2c1b6eecdc.png)

## 補足: 挙動をながめてみる

現状のテンプレコードがこちらでした。

```js

/**
 * 単一のURLをクローリングして、タイトルや概要等の情報を取得するサンプルコードです。
 * env.API_ENDPOINT, env.API_KEY, env.API_ENGINE を指定し、
 * input.url に対象となるWebページのURLを渡します。
 *
 * 例:
 *   input: {
 *     url: "https://example.com/about"
 *   }
 */

const result = {
  success: false,
  messages: [],
  error: null
};

try {
  if (!env.API_ENDPOINT) {
    throw new Error('Missing API_ENDPOINT in env');
  }
  if (!input.url) {
    throw new Error('URL is required in input');
  }

  // ペイロード定義
  const payload = {
    apiKey: env.API_KEY || '',
    engine: env.API_ENGINE, 
    urls: [ input.url ], // 単一URLを配列に入れて送る想定
    getCount: 0 // 深掘り (本文クロール) しない例
  };

  const response = fetch(env.API_ENDPOINT, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(payload)
  });

  if (response.status !== 200) {
    throw new Error('Web Search API request failed: HTTP ' + response.status);
  }

  // クロール結果をレスポンスとして格納
  result.success = true;
  result.messages = [
    'API request succeeded (URLクロール)',
    JSON.stringify(response.body)
  ];
} catch (err) {
  result.error = err.message;
}

result;
    
```

`urls: [ input.url ]`の箇所がURLの指定箇所ですね。設定との連動はこのような形式

> ![CleanShot 2025-05-12 at 00.13.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/46c25377-c879-4ed2-a58f-0a25b4865c6d.png)

`@{url}`の書き方ですが、Function Callingで呼ばれるときのパラメータを代入してそうです。

Webhook設定側の`Function Parameters`は以下のような形。

> ![CleanShot 2025-05-12 at 00.17.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/1c6bfff8-fc5b-43b1-b939-1f6d4824c048.png)

Fuction CallingでAIが判断して`"クローリングしたいWebページのURL"`を特定してくれて、それを`@{url}`の変数に代入してプログラムに送信してそうですね。

```json
{
  "type": "object",
  "properties": {
    "url": {
      "type": "string",
      "description": "クローリングしたいWebページのURL"
    }
  },
  "required": [
    "url"
  ]
}
```

```jsonc
{
  "message": "@{utterance}",
  "userId": "#{user_id}",
  "url": "@{url}" //←ここに代入
}
```

`#{}`と`@{}`の書き方がどっちがどっちだったっけ...となるときあるので気をつけたい

`#{}`