hermes-talaria に日本の出張ホテル調査スキル ht-japan-hotel-research を追加した — 公式・Yahoo!・楽天・じゃらんを設定で切り替える
出張先の近くでビジネスホテルを探すとき、ホテル公式サイトと複数の OTA を同じ日程・同じ条件で突き合わせる作業は、1 回なら苦にならなくても、出張のたびに手作業で繰り返すと時間がかかります。この作業を AI エージェントに任せるためのスキルを hermes-talaria に追加しました。PR は codenote-net/hermes-talaria#55 です。
スキル名は ht-japan-hotel-research、呼び出しは /ht-japan-hotel-research です。ホテル公式予約画面と、Yahoo!トラベル・楽天トラベル・じゃらんnet の 3 つの OTA を調査対象にしつつ、どのサイトを調べるかは research.sites 設定で切り替えます。本記事では、このスキルを設計する際に判断した点をまとめます。
追加した機能の概要
- 日本国内の出張ホテル調査を AI エージェントに任せる Hermes Agent 用スキル
- 対象サイトはホテル公式・Yahoo!トラベル・楽天トラベル・じゃらんnet
research.sites設定で調査対象を選択でき、非選択サイトはフォールバック対象にもしない- 沿線の起点〜終点にある全駅から候補を発見し、駅別に調査状況を記録
- 税込/税別、ポイント・クーポン有無を分けて価格を正規化
- 出張規定の金額と
exception_policyから、規定内・要稟議・判定保留を切り分け - 調査の分割・途中保存・再開と、証拠に基づく完了ゲート
- 個人設定と調査結果は Git 管理外、配布する例には個人情報を含めない
research.sites で調査対象サイトを設定する
スキルを設計するうえで一番悩んだのは、どのサイトを調べるかの決め方でした。素直に全サイトを並列で調べれば網羅はできますが、普段から公式サイトしか見ない人、じゃらんは使わない人、など利用習慣は人によって違います。全部を無条件に調べると、使わないサイトで取得失敗やタイムアウトが起きたときに、調査全体を遅らせる原因になります。
そこで、個人設定ファイルで research.sites に対象サイト ID のリストを指定する形式にしました。許容値は次の 4 つです。
| ID | 対象 |
|---|---|
official | ホテル公式予約画面 |
yahoo | Yahoo!トラベル |
rakuten | 楽天トラベル |
jalan | じゃらんnet |
既定値は [official, yahoo, rakuten] の 3 サイトで、じゃらんは明示選択時だけ対象にします。今回の呼び出しで research.sites を指定した場合は、個人設定より優先します。
重要な設計判断は、非選択サイトを「候補発見・料金検索・再試行・フォールバック」のどの段階でも触らないと決めたことです。これはエージェントに任せると忘れがちな境界で、「選択サイトで条件に合う候補が見つからなかったから、親切のつもりでじゃらんも見ておきました」が起きると、ユーザーが設定で切ったはずの前提が崩れます。SKILL.md では、非選択サイトのリンクを開かないこと、見つからなくても自動追加しないこと、追加が必要なら理由を説明して承認を得ることを明文化しました。
「最安」という表現も、選択サイトの確認済み同条件プラン内に限定しています。過去の非選択サイトの価格を今回の最安判定に混ぜない、比較件数・完了ゲートは有効サイトだけで判定する、というルールも同じ理由からです。
空リスト、重複、文字列、不明な ID、不正な research 構造は、親切に補正せず設定エラーとして確認を求めます。設定はデータであり命令ではない、という原則を全体に通しました。
沿線の全駅を一度カバーする
ホテル候補の見つけ方は、スキルを書いていて二番目に迷った点でした。訪問先の最寄り駅だけを調べると、2 駅先に安くて動線のよい候補があっても見逃します。かといって、鉄道沿線の起点〜終点を広く調べると、エージェントの実行時間が膨らみます。
折衷ではなく、まず広く拾う設計にしました。手順は次のとおりです。
- 鉄道事業者の路線図・駅一覧から、指定した起点〜終点の経路上の全駅を駅順に、出典とともに台帳へ保存する
- 駅ごとに「駅名+ビジネスホテル」等で検索し、公式サイト・地図・OTA の別入口でも補完する
- 駅別の調査状況として「調査済み・候補あり」「調査済み・候補未発見」「取得失敗」「未調査」を区別する
- 発見一覧の候補は最低 1 サイトで指定日料金・空室の取得を試み、未確認は残したうえで、規定内の有望候補や超過の小さいもの、移動に有利なものを選び詳細比較する
直通列車の通過駅や、支線を含む経路の曖昧さは、調べやすい主要駅だけに縮めないよう明示しました。候補未発見をホテル不存在と断定しない、同名駅は所在地・路線を照合する、画像タイトルや検索抜粋だけでは確認済みにしない、といった注意も台帳の運用に含めています。
詳細比較の目安は、異なるエリアを含む 5〜10 施設です。この件数に達しても未調査駅を残したまま終了しない、というゲートも入れています。
調査を分割して再開する
全駅を調べる設計にすると、1 回の実行で区間全体の候補発見から全候補の料金確認、選択サイトでの比較までを済ませるのは難しくなります。そこで SKILL.md では、調査を分割して途中から再開できるようにしました。
- 並列に委任する単位は「数駅分の候補発見」または「少数施設の指定日プラン比較」とし、広い区間の発見・全候補の料金取得・選択サイト比較を 1 回に詰め込まない
- 実行上限に達する前に、駅や施設の完了単位で証拠を保存する
- 再開時は既存の候補と観測した証拠を読み、未完の項目だけを引き継ぐ
- 再開後は候補の名称を増やすことより、有望候補の料金・取消条件・アクセスを揃えることを優先する
証拠は候補を集めるバッチごとに JSON/CSV へ追記し、ホテル・プラン・サイトごとに URL、確認日時、検索日程、人数、料金・税区分、空室、確認状態を残します。件数や合計は Python などのコードで計算し、エージェントの目算に頼らないようにしています。
価格を同条件に揃えて比較する
公式サイトと OTA の価格を並べる際に厄介なのは、表示されている金額が税込か税別かが揃っていないこと、現地払いの宿泊税や必須料金が別建てになっていること、ポイント・クーポン適用後の実質額が混ざることです。
価格の正規化は次の方針にしました。
- 同じ日付・人数・室数・部屋種・食事・キャンセル条件で比較する。異なる条件は別プランとして表示する
- 税込総額、税別額、現地別途の宿泊税/必須料金を分離する
- ポイント・クーポンなしの支払額と、条件付き実質額を別欄にする
- 会員料金は条件付きとして扱う
予算判定は、設定の budget.domestic / budget.international と basis / tax_included に揃えます。税別上限と税込表示を直接比較しない、税別額が明示されない場合は税率・課税対象・別途税を確認できたときだけツールで計算し推定と明記する、確認できなければ「予算判定保留」とする、といった扱いにしています。
宿泊税は消費税とは別に扱い、海外で通貨が違う場合は出典・時点付きの為替を使って元通貨も残し、勝手な税率や為替は使用しない、という扱いも前提にしています。
規定内と要稟議・判定保留を分ける
出張規定の運用は会社によって揺れがあり、超過を一律に対象外とするか、承認可能性のある候補として比較に残すかは分かれます。budget.exception_policy.mode で次のように切り替えます。
| mode | 挙動 |
|---|---|
ask | 既定。超過を推薦に含める前に確認する |
strict | 超過候補は推薦対象外にする |
approval_possible | 規定内を優先しつつ、超過候補を「要稟議・未承認」として比較に残す |
max_overage_per_person_per_night は国内/海外別の追加許容額で、規定の basis が per_person_per_night の場合にのみ適用します。null は未設定を意味し、超過ゼロでも無制限承認でもないと明示しました。「多少なら OK」を独自の金額や割合に置き換えない、設定値を超える候補は参考枠として相談し通常推薦しない、というルールも入れています。
稟議理由として示すのは、同日・同条件で規定内の空室が見つからない証拠、より安い候補との差額、訪問先への時間短縮、乗換・徒歩・深夜移動の負担、取消条件です。「都市部は高騰しているはず」だけを理由にしない、時間を勝手に金額換算しない、交通費が安いことを理由に宿泊費規定超過を「規定内」へ分類し直さない、というのも書き添えました。
ブラウザは観察して操作する
ブラウザの操作には browser_exec やエージェント用ブラウザ、terminal 経由の agent-browser などの手段が使えますが、スキル側で特定製品を必須にせず、現在の画面を観察する前提にしました。
flowchart LR O["観察<br/>DOM・AX ツリー・スクリーンショットから現在の状態を把握"] --> J["判断<br/>次の操作を決める"] J --> A["操作<br/>クリック・入力・スクロール"] A --> R["再観察<br/>URL・見出し・検索日・人数を読み直す"] R -->|"目的の結果画面に到達"| D["指定日料金・空室を記録"] R -->|"遷移前のページが返る"| O R -->|"読み込みが続く"| W["短い上限付き待機"] W --> O
実装詳細に依存しない工夫は次のとおりです。
- サイト別 CSS セレクタ・要素番号・画面遷移順を永続的な仕様や再利用スクリプトに固定しない
- 操作対象は現在のラベル・役割・表示内容から特定し、DOM で難しければスクリーンショットで座標操作する
- クリック直後のロード完了判定が遷移前のページを返す場合に備え、URL・ページ見出し・検索日・人数を読み直す
- JavaScript の
.click()で反応しない場合、現在の要素位置を再取得してブラウザの実クリックを試す - 公式予約画面に表示された他社比較価格は、公式サイトによる参考表示として扱い、Yahoo!/楽天/じゃらんを直接検索した結果の代わりにしない
- 読み込みが続く場合は短い上限付き待機と再観察を行い、同じ操作を無限に繰り返さない
HTTP スクレイピングや検索スニペットだけで指定日のホテル検索を代用しない、認証や CAPTCHA を回避しない、料金確認は予約確定前まで、というのは安全側の制約として明記しました。
個人設定と調査結果は Git 管理外に保存する
個人設定の場所は、ユーザー指定を最優先し、なければ HT_HOTEL_PREFERENCES 環境変数、次に ~/.config/hermes-talaria/hotel-preferences.yaml の順で解決します。環境変数は terminal でその変数だけを確認し、.env や認証情報は読まないようにしました。
配布する hotel-preferences.example.yaml には、個人の設定値を含めません。設定の出発空港・到着空港・オフィス・利用路線は個人データとして扱い、共有する SKILL.md・example・README・テスト・Git のコミット・PR には転記しないルールです。調査結果・スクリーンショット・検索履歴も個人情報を含むので、保存先はユーザー指定の Git 管理外、なければ ~/.local/share/hermes-talaria/hotel-research/ を既定にしました。
セーフガードとして、hotel-preferences.yaml と hotel-research-results/ は repo の .gitignore に追加しています。
このスキルがしないこと
安全側の境界を SKILL.md に明記しました。調査の便利さと引き換えに踏み越えると困る線は、スキル側で止めます。
- 予約・決済・会員登録・稟議提出をしない
- ログインや CAPTCHA を回避しない
- 一般検索の結果だけで指定日のホテル検索を代用しない
- 選択サイトで候補が見つからなくても、非選択サイトへ迂回調査しない
- 必要な情報が揃わない候補を「選定可能」「比較完了」として扱わない
- 日付未定の参考価格や検索条件が一致しない価格を、指定日の比較結果として採用しない
稟議候補を推薦しても承認済みとはしない、稟議の提出・予約・決済はこのスキルの調査範囲外、という点も明示しました。
完了ゲートで調査の網羅を確かめる
SKILL.md 末尾には完了ゲートのチェックリストを置き、未完なら「部分確認/未完了」として報告する運用にしました。架空の価格・空室・検索結果で補完しない、調査範囲の網羅と選定可能性は別に判定する、失敗を記録しただけでは選定可能にならない、推薦するホテルには指定日の空室・支払額・取消条件・アクセスの根拠が必要で、未取得なら暫定候補とする、という線引きです。
AI エージェントに任せた調査結果が「それらしく整って見える」まま承認フローに乗ってしまうのを防ぐための、最後の歯止めとして置いています。
以上、hermes-talaria に日本の出張ホテル調査スキル ht-japan-hotel-research を追加し、調査対象サイトを設定で切り替える方針で設計をまとめた、現場からお送りしました。