Skip to main content
Glama
Varma904

Agentic Travel Recommendations Service

by Varma904

Agentic Travel Recommendations Service

このプロジェクトは、マルチテナント旅行レコメンデーションサービスのためのTypeScriptおよびNode.jsによる概念実証です。共有レコメンデーション機能をREST API、Streamable HTTP MCP エンドポイント、およびコマンドラインインターフェースを通じて公開します。

主な機能

  • ヘルスチェック、メンバープロファイル、レコメンデーションのためのREST API

  • Streamable HTTP MCP エンドポイント

  • MCPツール: get_member_profile

  • MCPツール: get_recommendations

  • 権威あるメンバー由来のテナント解決

  • パートナー固有のレコメンデーション上限

  • パートナー固有のカテゴリ除外

  • 決定論的なレコメンデーション生成

  • フェイルクローズドなパートナー設定動作

  • リクエストIDと構造化JSONログ

  • 最小限のCLIデモ

  • マルチステージDockerビルド

  • 自動テスト

Related MCP server: Agentic Travel Recommendations API

アーキテクチャ概要

REST / MCP / CLI
       |
       v
RecommendationService
       |
       v
MemberDataService
       |
       | member.partnerId
       v
PartnerConfigurationService
       |
       v
CandidateGenerator
       |
       v
RecommendationPolicy
       |
       | exclusions then cap
       v
Final Recommendations

呼び出し元は memberId のみを提供し、権威ある partnerId を選択しません。パートナー設定はメンバープロファイルによって決定され、REST、MCP、CLIはすべて同じビジネスレイヤーを再利用します。

クイックスタート

npm ci
npm run dev

サービスはデフォルトで http://localhost:3000 で利用可能です。

型チェック

npm run typecheck
npm run typecheck:test
npm run typecheck:all

テスト

npm test

現在検証済みのベースラインは、6ファイルで46件の合格テストです。

本番ビルド

npm run build
npm start

REST API

GET /health
GET /api/members/:memberId
GET /api/recommendations/:memberId

リクエスト例:

curl http://localhost:3000/api/members/MEMBER-001
curl http://localhost:3000/api/recommendations/MEMBER-001

MCP

MCPサーバーは以下を通じて公開されます:

POST /mcp

次のツールを提供します:

  • get_member_profile

  • get_recommendations

両方のツールはメンバー識別子のみを受け付けます:

{
  "memberId": "MEMBER-001"
}

実装は公式の @modelcontextprotocol/sdk のStreamable HTTPトランスポートを使用します。呼び出し元は partnerId を提供せず、権威あるメンバープロファイルから解決されます。

CLI

npm run cli -- MEMBER-001

デモメンバー:

  • MEMBER-001 → BANK_A

  • MEMBER-002 → BANK_B

  • MEMBER-003 → CREDIT_UNION_C

Docker

docker build -t agentic-travel-recommendations .
docker run --rm -p 3000:3000 agentic-travel-recommendations

このイメージはマルチステージビルド、Node 24ランタイム、非rootランタイムユーザーを使用します。HTTPサーバーはグレースフルシャットダウンシグナルを処理します。

セクションA — アーキテクチャとトレードオフ

アーキテクチャ概要

このサービスは、REST、Streamable HTTP MCP、CLIを通じて同じレコメンデーションワークフローを公開するステートレスなTypeScriptおよびNode.jsアプリケーションです。各トランスポートは入力を検証し、共有の RecommendationService に委任します。トランスポートハンドラーはパートナーポリシーを自ら実装しません。

権威あるテナントフローは次のとおりです:

memberId
→ MemberDataService
→ MemberProfile.partnerId
→ PartnerConfigurationService
→ CandidateGenerator
→ RecommendationPolicy
→ final recommendations

呼び出し元は memberId を提供し、権威ある partnerId を選択することは決してありません。MemberDataService が返すメンバープロファイルによって、取得するパートナー設定が決まります。両方のアップストリームサービスは、レスポンスに埋め込まれたアイデンティティをリクエストされたアイデンティティと関連付け、RecommendationService は生成前に追加のパートナーアイデンティティチェックを実行します。

候補生成は意図的にパートナーポリシーから独立しています。決定論的ジェネレーターはまずメンバープロファイルから生の候補を生成し、次にジェネリックなポリシーレイヤーが excludedCategories 内の候補を削除し、recommendationCap を適用します(この順序で)。結果として得られたレコメンデーションのみが返されます。この概念実証では、Member Data ServiceとPartner Configuration Serviceはモックされており、パートナー設定へのアクセスは読み取り専用です。

設計上のトレードオフ

可用性よりも正確性。 権威あるパートナー設定が欠落している、利用できない、スキーマが無効である、またはアイデンティティが一致しない場合、リクエストはフェイルクローズします。サービスは寛容なデフォルト値を代用したり、無制限のレコメンデーションを返したりしません。これによりアップストリーム障害時の可用性が低下する可能性がありますが、レコメンデーションが正しいテナントポリシーから逸脱することを防ぎます。

キャッシュよりも新しい設定。 最初のリリースでは、キャッシュ基盤を追加する代わりに、すべてのレコメンデーションリクエストに対してパートナー設定を取得します。これにより動作が単純になり、成功した各リクエストが最新のポリシーを使用することが保証されます。追加のアップストリームレイテンシと負荷を受け入れます。短命キャッシュは、測定されたパフォーマンスが一貫性のトレードオフを正当化する場合にのみ、後で適切になります。

外部LLMよりも決定論的生成。 候補生成は再現可能で、テスト可能で、コストがかからず、運用上の予測可能性があります。これによりパーソナライゼーションの洗練度は制限されますが、ポリシーの動作と評価結果を容易に検証できます。将来のLLMまたはランキングコンポーネントは、決定論的ポリシーの適用を変更せずに候補生成を置き換えることができます。

パートナー設定変更の処理

Partner Configuration Serviceは読み取り専用の依存関係です。パートナーがレコメンデーション上限を無制限から3に変更したり、excludedCategories に cruise を追加したりしても、レコメンデーションサービスはコード変更やテナント固有の分岐を必要としません。次に成功したリクエストが現在の設定を読み取り、ジェネリックなポリシーロジックが新しい除外値と上限値を適用します。

設定が既存のスキーマと互換性を保つ限り、アプリケーションの再デプロイは必要ありません。後で設定キャッシュを導入する場合は、意図的に短いTTLまたは信頼できる無効化戦略が必要です。古い設定が一時的にパートナーの現在のポリシーに違反する可能性があるためです。

セクションB — 本番準備とインシデント対応

インシデントランブックエントリ

シナリオ: メンバーが、自分のパートナーがクルーズを除外しているにもかかわらず、AIコンシェルジュがクルーズのレコメンデーションを表示したと報告しました。

  1. 特定と関連付け。 レポートからリクエストIDまたは相関IDを取得できる場合は取得し、対応する構造化ログを見つけます。解決後の operation、memberId、権威ある partnerId、resultCode、該当する場合はHTTPステータスを記録します。旅行履歴とレコメンデーションペイロードは意図的にログに記録されないため、相関には識別子と結果メタデータを使用します。

  2. 権威あるテナントの確認。 MemberDataService を通じて影響を受けたメンバーを取得し、リクエストされた memberId が返された member.memberId と等しいことを確認します。テナントは member.partnerId からのみ導出します。フロントエンド、MCP呼び出し元、クエリパラメータ、またはサポートレポートによって提供されたパートナーIDを信頼しないでください。

  3. パートナー設定の確認。 member.partnerId を使用して設定を取得し、configuration.partnerId === member.partnerId を確認します。excludedCategories と recommendationCap を調べ、現在の権威ある設定で cruise が除外されているかどうかを判断します。欠落、利用不可、不正な形式、またはアイデンティティ不一致の設定は、寛容なデフォルトを使用するのではなく、フェイルクローズさせなければなりません。

  4. パイプラインの再現。 同じレコメンデーションワークフローにメンバーを通します。生の CandidateGenerator 出力にクルーズが含まれていても、生成は意図的にパートナーポリシーを無視するため、それ自体は欠陥ではありません。RecommendationPolicy が raw candidates → remove excluded categories → apply recommendation cap → final recommendations を処理することを検証し、最終結果にクルーズが含まれていないことを確認します。

  5. 障害箇所の特定。 クルーズが生の候補には現れるが最終レコメンデーションには現れない場合、ポリシーは正しく機能しています。古いクライアントレスポンス、誤ったメンバーに関連付けられたレスポンス、期待されるワークフローを迂回する別のコンシューマーやエンドポイント、または報告された時間と現在の設定の違いを調査します。RecommendationPolicy をクルーズがすり抜けた場合は、カテゴリの比較または正規化、権威ある設定の内容とアイデンティティ、および最近のポリシー変更やリグレッションを調べます。

  6. 封じ込め。 正しいパートナーポリシーを確立または安全に再現できない場合は、潜在的に非準拠のレコメンデーションを返す代わりにフェイルクローズします。このアプリケーションから読み取り専用のPartner Configuration Serviceを変更しようとしないでください。

  7. 修正と検証。 責任のあるレイヤーの欠陥を修正し、正確な障害を再現するリグレッションテストを追加します。次を実行します:

    npm run typecheck:all
    npm test
    npm run build

    影響を受けたパートナー、少なくとも1つの影響を受けていないテナント、RESTの動作、および該当する場合はMCPの動作を検証します。

  8. フォローアップ。 根本原因、影響を受けたパートナーとメンバーの範囲、影響期間、是正措置、リグレッションカバレッジ、予防策を記録します。

パートB2 — 必須推論問題

AIコーディングアシスタントは、アップストリームのメンバーおよびパートナーレコードをZodで検証するが、返されたアイデンティティとリクエストされたアイデンティティを決して関連付けない実装を生成する可能性があります。そのコードは型安全で、スキーマ検証とハッピーパステストは合格し、表面的なレビューでは妥当な防御的検証が見られるでしょう。しかし、欠落したクロステナント不変条件は依然として深刻なポリシーリスクを生み出します。

例えば、MEMBER-001 は BANK_A に属します。RecommendationService は BANK_A の設定を要求しますが、バグがある、またはルーティングを誤ったアップストリームサービスが、無制限の上限とカテゴリ除外のない、完全にスキーマ妥当な BANK_B 設定を返します。Zodはその形状を正しく受け入れますが、そのポリシーを MEMBER-001 に適用すると、BANK_A の制限を迂回する可能性があります。

私はこれを敵対的リグレッションテストで捕捉します。意図的に障害を起こす設定サービスのダブルが妥当な BANK_B 設定を返す間に BANK_A をリクエストします。InvalidUpstreamDataError を期待し、レコメンデーション結果が生成されないことをアサートし、CandidateGenerator.generate が呼び出されなかったことを明示的に検証します。また、リクエストされた memberId が返された member.memberId と等しくなければならないことを証明する、対応するメンバーデータテストを追加します。

AIが生成したコードに取り組む前に、私は型だけに頼るのではなく、権威と実行順序を追跡します。member.partnerId(呼び出し元の入力ではなく)がテナントを選択すること、返された両方のアイデンティティが権威あるリクエストと一致すること、そして欠落、利用不可、または不一致の設定が寛容なフォールバックなしでフェイルクローズすることを検証します。また、ポリシーアイデンティティが安全に確立される前に生成が開始できないこと、および否定的で敵対的なテストが通常のハッピーパスと並んでこれらのケースをカバーしていることも確認します。

セクションC — AI使用ログ

インタラクション1 — アーキテクチャレビュー

私が尋ねたこと

私はAIコーディングアシスタントに、課題をレビューし、1人のエンジニアが現実的に実装できる最小限のアーキテクチャの設計を手伝うよう依頼しました。要求した範囲には、ドメインモデル、モックされたアップストリームサービス、レコメンデーションロジック、REST、MCP、CLI、自動テスト、コンテナ化が含まれ、概念実証に不要なインフラストラクチャは避けました。

AIが提供したもの

AIは、ドメインモデル、サービス契約、モックされたアップストリームサービス、候補生成、パートナーポリシー、オーケストレーション、トランスポートアダプターを分離することを提案しました。当初は最も単純なMCPトランスポートとしてstdioを提案しました。

私が維持、変更、または却下したもの

レイヤー分離は、REST、MCP、CLIがルールを個別に実装するのではなく、1つのビジネスレイヤーを呼び出せるようにするため、私はそれを維持しました。主要なMCPトランスポートとしてのstdioは却下し、POST /mcp における公式MCP SDKのStreamable HTTPトランスポートに設計を向け直しました。課題は、エージェントが発見して呼び出すべき内部APIを説明しており、HTTPはコンテナ化されたサービスアーキテクチャに適合します。私は、最も単純なオプションを自動的に受け入れるのではなく、提案を課題の統合要件と比較した上でこの選択を行いました。

インタラクション2 — 段階的実装

私が尋ねたこと

私はアプリケーション全体を1つのプロンプトで依頼しませんでした。実装を境界のあるステップに分割しました: ドメインモデル、スキーマとエラー、アップストリーム契約とモック、候補生成、レコメンデーションポリシー、オーケストレーション、REST、MCP、CLI、可観測性、Dockerです。各ステップの後、報告された動作をレビューし、先に進む前に型チェックとテストを要求しました。

AIが提供したもの

アシスタントは、焦点を絞ったテストとともに各境界コンポーネントを実装し、変更されたファイルと検証結果を報告しました。これにより、個々の設計上の選択が大きな生成パッチの中に隠れるのではなく、可視化されレビュー可能になりました。

私が維持、変更、または却下したもの

私は共有の RecommendationService、決定論的な CandidateGenerator、独立した RecommendationPolicy、メンバー由来のテナント解決、読み取り専用の設定コントラクト、共有の REST/MCP/CLI ビジネスロジックを維持しました。この構造により、テナントポリシーは独立してテスト可能となり、トランスポート固有のルール実装を防ぐことができます。また、外部の LLM 依存を追加する代わりに、意図的に決定論的生成を保持しました。この評価はサービス設計とポリシー適用に焦点を当てており、再現可能な出力はテスト・デバッグ・デモが容易です。各インクリメントは、その動作がアーキテクチャの不変条件に一致し、チェックがパスした場合にのみ受け入れられました。

インタラクション3 — 本番運用とセキュリティ監査

私が依頼したこと

アプリケーションが動作した後、私は AI に対して機能追加をやめ、シニアエンジニア、マルチテナントセキュリティレビュアー、オンコール本番運用担当者、REST/MCP API レビュアーの視点からリポジトリを監査するよう依頼しました。

AI が提供したもの

監査では、スキーマ検証済みのアップストリーム応答が、要求されたメンバーまたはパートナー ID と元々関連付けられていないことが判明しました。また、不正な JSON は、リクエスト ID ミドルウェアがリクエストコンテキストを確立する前に失敗する可能性があることも判明しました。追加の優先度が低い改善も提案されました。

私が保持・変更・却下したもの

私は、テナントの正確性と安全な運用に影響するため、価値の高い2つの指摘を両方受け入れました。ID 関連付けについては、実装は現在、返された memberId が要求されたメンバーと一致すること、configuration.partnerId が権威ある member.partnerId と一致すること、そして RecommendationService が防御的に設定 ID チェックを繰り返すことを検証します。不一致はフェイルクローズされ、敵対的な回帰テストは、権威ある設定を確立できない場合に候補生成が決して開始されないことを検証します。

不正な JSON については、リクエストコンテキストとリクエスト ID が解析前に確立されるようになりました。無効なボディは、パーサーの詳細、スタックトレース、ファイルシステムパス、生のリクエスト内容を含まない安全な構造化 400 応答を受け取ります。

私は、永続的な MCP セッション、追加の分散インフラストラクチャ、より高度な可観測性などの優先度の低いアイデアを延期しました。これらは4週間の概念実証には不要であり、運用範囲を拡大するためです。各推奨事項を、課題の要件、テナントの正確性、テスト容易性、運用リスク、提供範囲に照らして評価しました。アシスタントはオプションと実装支援を提供しましたが、私は推論をレビューし、変更を選択し、焦点を絞ったテストとエンドツーエンドのチェックを通じて検証しました。

4週間の最初のステップ

最初にリリースされるもの

4週間の目標は、最初の出荷可能な内部概念実証です。これは、安全なテナント強制と運用の基本を備えた必要なワークフローを示します。広範な本番ロールアウトに必要なすべての機能が完了しているという主張ではありません。

第1週 — サービス基盤

  • TypeScript と Node.js のサービス基盤を確立する。

  • ドメインモデル、厳密な Zod バウンダリ検証、型付きエラーを定義する。

  • MemberDataService コントラクトとモック実装を追加する。

  • 読み取り専用の PartnerConfigurationService コントラクトとモック実装を追加する。

  • 権威あるメンバー由来のテナント解決と初期の単体テスト基盤を確立する。

目標: レコメンデーションロジックを実装する前に、安全なサービス境界とテナント権限を確立する。

第2週 — レコメンデーションワークフロー

  • パートナールールから独立して、決定論的な CandidateGenerator を実装する。

  • カテゴリ除外とレコメンデーション上限を含む RecommendationPolicy を実装する。

  • 必要な順序を強制する: 最初に除外、次に上限。

  • RecommendationService のオーケストレーションとフェイルクローズ設定動作を追加する。

  • ポリシーとオーケストレーションを焦点を絞った単体テストでカバーする。

目標: 契約上のパートナールールが決定論的であり、候補生成から独立していることを証明する。

第3週 — インターフェースとエンドツーエンドフロー

  • REST エンドポイントと Streamable HTTP MCP エンドポイントを公開する。

  • MCP ツール get_member_profile と get_recommendations を提供する。

  • CLI デモを追加する。

  • REST、MCP、CLI を共有ビジネスレイヤー経由でルーティングする。

  • REST/MCP 統合テストとテナントオーバーライドテストを追加する。

目標: 課題で必要とされるインターフェースを通じて、完全なレコメンデーションワークフローを示す。

第4週 — 本番運用準備と提供

  • リクエスト ID、相関フィールド、構造化 JSON ロギング、安全なエラーハンドリングを追加する。

  • 不正な JSON を安全に処理し、グレースフルシャットダウンを実装する。

  • Docker マルチステージビルドと非 root ランタイムを追加する。

  • 本番ソースとテストを別々に型チェックする。

  • 本番/セキュリティ監査を実施し、敵対的な ID 関連付けテストを追加する。

  • 最終的なエンドツーエンドとコンテナ検証を完了する。

  • README、インシデントランブック、デモビデオを準備する。

目標: 概念実証を、オンコールで所有するチームがサポートできるようにする。

今後の予定

以下の作業は、最初の4週間のリリース後に意図的に延期されます:

  1. 実際のアップストリーム統合。 モックされた MemberDataService と PartnerConfigurationService の実装を、既存のサービスコントラクトと ID 関連付けの不変条件を維持しながら、実際の arrivia REST クライアントに置き換えます。

  2. 既存の認証・認可統合。 新しい ID プラットフォームを導入するのではなく、arrivia の既存の ID およびゲートウェイメカニズムと統合します。認可はメンバー由来のテナント権限を維持する必要があります。

  3. ネットワーク回復力。 実際のアップストリーム HTTP 依存関係について、リクエストおよび接続タイムアウト、再試行しても安全な操作における上限付き再試行、明示的な失敗動作を検証して構成します。設定の不確実性は引き続きフェイルクローズする必要があります。

  4. パフォーマンス検証。 最適化する前に、現実的な負荷テストとパフォーマンステストを実行します。測定値が正当化する場合にのみ、短期間のパートナー設定キャッシュを検討します。古いポリシーは正確性のリスクであるため、キャッシュには明確な鮮度と無効化戦略が必要です。

  5. レコメンデーションインテリジェンス。 決定論的な CandidateGenerator を LLM、ランキングモデル、またはよりリッチなパーソナライゼーションで置き換えるか拡張する可能性があります。RecommendationPolicy は決定論的であり、モデルの外部に維持され、生成された出力がパートナールールを上書きできないようにする必要があります。

  6. 本番運用の可観測性。 既存の構造化イベントと相関 ID を、arrivia が承認したメトリクス、トレーシング、アラート、運用ツールに接続します。

  7. MCP の進化。 具体的な製品要件がクロスリクエスト状態を必要とする場合にのみ、ステートフルまたは再開可能な MCP 動作を検討します。現在のステートレスな Streamable HTTP 実装は、このサービスにとって意図的なものです。

Related MCP Connectors

Related MCP Servers