ライブラリに戻る
アーキテクチャ

RFQエンジンアーキテクチャ:可用性ホールドとキャンセルスナップショットが重要な理由

最終更新:2026年7月13日

ほとんどのRFQ自動化が見落とす問題

チームがRFQワークフローを自動化する際、通常はフロントエンドに集中します:バイヤーのリクエストを解析し、カタログを検索し、製品をマッチし、価格設定を適用する。この作業は現実的であり、ツールが存在します — GEPがその調達自動化分析で説明するように、AIが要件を抽出し、決定論的ルールが価格、承認、SLA追跡を処理します。Ivaluaの2026年ソーシングガイドも同じパターンを確認しています:AIが抽出とマッチングを処理し、ルールがガバナンスを処理します。

しかし、2つの問題が本番環境でこのモデルを破壊し、ほとんどの自動化はそれに対処していません:

  1. 可用性ドリフト。 交渉に3日かかる見積もりは、提示価格で存在しなくなる可能性のある在庫に対する見積もりです。エージェントが$12.40で50単位を約束し、バイヤーが承諾した時にサプライヤーに12個しか残っていなければ、その見積もりは最初から履行不可能でした。エージェントは嘘をついていません — 世界がその下で変わったのです。

  2. ポリシードリフト。 30日前に引用されたキャンセルペナルティは、予約時に有効なペナルティと異なる場合があります。サプライヤーが交渉の途中で条件を厳格化し、エージェントが見積もり時にポリシーをキャプチャしていなければ、バイヤーは後でその請求を争議します。争議は正当です — 条件が動いたのです。

両方を解決するアーキテクチャは、2つのメカニズムを持つRFQエンジンです:時間枠の間制約付き在庫を予約する原子的可用性ホールドと、見積もりの瞬間に商業条件を凍結するキャンセルポリシースナップショット。これらは見積もりツールにボルトオンされた機能ではありません。データモデルの構造的コミットメントです — エンジンが強制するフィールド、状態遷移、ライフサイクル操作です。

これを機能させるエンティティモデル

本番RFQエンジンにはリクエストと見積もり以上のものが必要です。エンティティモデルはバイヤーの意図をサプライヤーのレスポンスから分離し、カタログとは独立して制約付き供給を追跡する必要があります。以下のエンティティがバックボーンを形成します:

Request — バイヤーの意図:連絡先、タイトル、ラインアイテム、請求先・送り先住所、有効期限、ステータス。リクエストは定義された状態マシンを通じて移動します:initialin_progressconfirmedcompleted、見積もりが既に存在する後にアイテムが変更された場合の再入ポイントとしてmodified

Item — バイヤーが望むカタログ製品。アイテムは汎用です;UUID、タイプ、価格設定モード、計量単位を持ちます。同じアイテムが複数のプロバイダーアイテムにマップされます。

ProviderItem — そのアイテムに対するサプライヤーの特定の提供。計量単位あたりの基本価格。プロバイダーアイテムはプロバイダー企業に属し、複数のバッチを持てます。

ProviderItemBatch — 制約付き在庫単位。各バッチにはバッチ番号、製造日、有効期限、単位コスト、在庫フラグ、および在庫の動きが遅いことを示すslow_move_itemフラグがあります。バッチは可用性がチェックされホールドされる粒度です。

Quote — 確認されたリクエストに対するサプライヤーの価格付きレスポンス。見積もりにはプロバイダー、営業担当、配送方法と金額、通貨、FXレートとFXロックタイムスタンプ、交渉ラウンド数(自動計算)、状態マシンがあります:initialin_progressconfirmedcompletedまたはdisapproved

QuoteItem — 見積もりの1行で、プロバイダーアイテムとバッチを数量、単価、小計、割引、最終小計にリンクします。重要なことに、見積もりアイテムはhold_tokenhold_expires_atguardrail_price_per_uomslow_move_itemを持ちます — 可用性ホールドと収益性ガードレールを行レベルで可視化するフィールドです。

CancellationPolicy — 予約がキャンセルされる条件、階層、期限、ペナルティルール、プロバイダーアイテムごとにスコープされます。

Installment — 確認された見積もりにリンクされた支払いスケジュール、優先度、予定日、金額、比率、状態マシン:pendingpaidまたはcancelled

これはフラットテーブルではありません。ネストされたリゾルバを持つ型付きグラフです — リクエストはその見積もりを解決し、見積もりはそのアイテムを解決し、見積もりアイテムはそのプロバイダーアイテムとバッチを解決します。エンジンはDataLoaderベースのバッチロードを使用し、20のラインアイテムを持つ見積もりが20の逐次データベースルックアップを発火しないようにします。

可用性ホールド:キャパシティの原子的予約

可用性ホールドは可用性ドリフトを防ぐメカニズムです。それがなければ、エージェントは古くなる可能性のある在庫スナップショットに対して見積もります。それがあれば、エージェントは時間枠のキャパシティを予約し、見積もりは実際の、減額された供給によって裏付けられます。

ホールドライフサイクルには4つの状態と厳格な遷移ルールがあります:

状態 意味 許可された遷移
held キャパシティ減額、TTL(15分)付きホールドトークン発行 confirmed, released, expired
confirmed 予約コミット、2回目のキャパシティ減額なし 終端
released ホールドキャンセル、キャパシティ復元(冪等) 終端
expired TTL経過、キャパシティ復元(冪等) 終端

重要な設計決定はconfirmed遷移にあります:2回目のキャパシティ減額なし。ホールドは既にキャパシティを予約しています。予約の確認は、在庫カウントに再び触れることなくホールドを確認済状態に変換します。これは二重カウントを防ぎます — ホールドがキャパシティを減額し、確認がそれを再度減額し、システムが実際より少ない供給しか持たないと考えるバグです。

ホールドは原子的です。acquireAvailabilityHoldミューテーションは利用可能キャパシティを減額し、expires_atタイムスタンプ付きのhold_tokenを返します。キャパシティが不十分な場合、操作は副作用なしで失敗します — 部分的ホールドなし、ダーティ状態なし。releaseAvailabilityHoldexpireAvailabilityHoldミューテーションはキャパシティを復元し、冪等です:同じトークンで2回呼び出しても同じ結果を返します。これはフォールトトレランスに重要です — ネットワークタイムアウトがリトライを引き起こした場合、システムはキャパシティを過剰復元しません。

これが見積もりワークフローにどうマップされるか

3つの異なる有効期限とコストを持つバッチを持つサプライヤーに対して200台の産業用ポンプを見積もるディストリビューターを考えてみましょう。エージェントは:

  1. プロバイダーアイテムUUID、サービスウィンドウ、数量でcheck_availabilityを呼び出し — 一致するバッチと利用可能数量を取得。
  2. acquire_availability_holdを呼び出し — キャパシティが原子的に減額、15分TTLのhold_tokenが返される。
  3. 見積もりアイテムがhold_tokenhold_expires_atを携える。バイヤーはカタログの推測ではなく、実際の在庫に裏付けられた見積もりを見る。
  4. バイヤーが15分以内に承諾すれば、エージェントはconfirm_availability_holdを呼び出し — ホールドがconfirmedに遷移、キャパシティがロック、2回目の減額なし。
  5. バイヤーが拒否またはTTLが切れれば、エージェントはrelease_availability_holdを呼び出すか期限切れにさせる — キャパシティが復元、次の見積もりが使用可能。

hold_tokenは明示的ハンドルです。モデルはそれを隠しセッション状態ではなく引数として渡します。これはMCP 2026-07-28リリース候補が正式化する明示的ハンドルパターンと同じです:エージェントがツール呼び出し間で必要とする状態が、トランスポート層のメタデータではなく、可視的で監査可能なハンドルになります。

キャンセルスナップショット:条件の凍結

キャンセルポリシードリフトは2番目の障害モードです。サプライヤーは見積もりと予約の間でキャンセルペナルティを変更する可能性があります — 30日前の10%ペナルティが7日前の50%になります。エージェントが古いペナルティを引用しバイヤーが承諾した場合、バイヤーは新しい請求を争議します。争議は正しいです。

アーキテクチャは見積もり時にキャンセルポリシーをスナップショットすることでこれを解決します。CancellationPolicyエンティティにはtiers(期限とペナルティ率を持つJSON構造)、labeldescriptionstatusがあります。エージェントが見積もりを生成する際、各プロバイダーアイテムのアクティブなキャンセルポリシーを取得し、ポリシー条件を見積もりに添付します。ポリシーUUIDとその階層は凍結されます — サプライヤーが後でポリシーを更新しても、見積もりは発行時に有効だった条件を携えます。

search_cancellation_policiesツールはプロバイダーアイテムとステータスでフィルタし、エージェントが各ラインアイテムのアクティブポリシーを取得できるようにします。ポリシーは外部キーで参照されるだけでなく、見積もりアイテムに関連付けられます — スナップショットはポリシーそのものであり、可変レコードへのポインタではありません。

このパターンはキャンセル以外にも一般化されます。FXレートも同じアプローチを使用します:QuoteTypefx_ratefx_rate_locked_atを持ちます — 為替レートは予約時ではなく見積もり時にロックされます。バイヤーの表示通貨がサプライヤーの見積もり通貨と異なる場合、ロックされたレートがバイヤーが見るレートであり、市場変動に関係ありません。

価格階層、セグメントルックアップ、ガードレール

価格はフラットフィールドではありません。エンジンは顧客セグメント、数量、プロバイダーアイテムごとに価格階層を解決します。get_item_price_tiersツールは顧客メールと見積もりアイテムのリスト(それぞれアイテムUUID、プロバイダーアイテムUUID、数量)を受け取り、セグメント連絡先を通じて顧客のセグメントをルックアップし、1回のクエリですべての一致する価格階層をバッチロードします。階層はデータベースレベルで数量閾値によりフィルタされます — quantity_greater_thenquantity_less_thenが要求数量を囲む階層のみが返されます。

割引プロンプトも同じように動作し、階層的スコープシステムを使用します:GLOBAL(すべての見積もりに適用)、SEGMENT(顧客セグメントに適用)、ITEM(特定アイテムに適用)、PROVIDER_ITEM(特定プロバイダーのアイテム提供に適用)。get_discount_promptsツールは4つのスコープすべてからロードし、重複排除し、条件とルール付きの結合セットを返します。これは、1つの見積もりがボリューム割引(セグメントスコープ)、季節プロモーション(アイテムスコープ)、サプライヤー固有の清算(プロバイダーアイテムスコープ)を同時に適用できることを意味します — それぞれ独自の優先度を持ちます。

見積もりアイテムのguardrail_price_per_uomフィールドは収益性ガードレールです — プロバイダーアイテムバッチからロードされた、アイテムの最低許容価格。エージェントがガードレールを下回る割引を適用した場合、操作は失敗しません(エージェントには正当な理由があるかもしれません)が、フラグは見積もりアイテムレスポンスで可視化されます。slow_move_itemフラグは在庫期間が長すぎるアイテムをマークします — エージェントはこれを使用してより深い割引を適用できますが、ガードレールは非収益的な価格設定を防ぎます。

無効な操作を防ぐ状態マシン

状態強制のないRFQエンジンはスプレッドシートです。エンジンは4つの状態マシンを強制し、それぞれに明示的な遷移ルールと操作ガードがあります:

Request: initialin_progressconfirmedcompletedmodifiedを再入ポイントとして。RequestOperationGuardは見積もりがconfirmedリクエストからのみ作成でき、アイテム変更はinitialin_progressmodified状態でのみ許可されることを強制します。

Quote: initialin_progressconfirmedcompletedまたはdisapprovedQuoteOperationGuardはアイテム変更がinitialまたはin_progressでのみ許可され、分割払いがconfirmed見積もりからのみ作成できることを強制します。

可用性ホールド: heldconfirmed / released / expired。3つのターゲット状態すべてが終端です。

Installment: pendingpaidまたはcancelled。両方のターゲット状態が終端です。

確認されたリクエストのアイテムが変更され、見積もりが既に存在する場合、リクエストはmodifiedに遷移し、既存のすべての見積もりは自動的に否認されます — バイヤーは更新されたアイテムに対して新しい見積もりを取得する必要があります。これは、見積もりを生成したリクエストが変更された後に、古い見積もりが承諾されるのを防ぎます。

見積もりのすべての未キャンセル分割払いが支払われると、見積もりは自動的にcompletedに遷移します。リクエストの少なくとも1つの見積もりが完了すると、リクエストは自動的にcompletedに遷移します。状態マシンは助言ではありません — エンジンはすべての遷移を検証し、操作が無効な場合は現在のステータスと許可された遷移を含むVALIDATION_FAILEDエラーを発生させます。

モジュールパターン:38ツール、11ドメインmixin

このアーキテクチャのリファレンス実装は、単一のMCPモジュールです — mcp_hospirfq_processor — 11のドメインmixin(Request、Item、Availability、Quote、Pricing、Installment、Bundle、Cancellation、File、Segment、Catalog)から構成される38の登録ツール。mixinはGraphQLBackedProcessorベースクラスを共有し、GraphQLクライアント、ロガー、設定を提供します。各mixinは独自のツールメソッドを提供し、self.loggerself.settingself._execute_graphql_queryのみにアクセスします。

ツールは設定ブロック(MCP_CONFIGURATION)を通じて登録され、各ツールの名前、説明、入力スキーマ(JSON Schema)、モジュールリンク(クラス名、関数名、戻り値型)を宣言します。MCPランタイムは起動時にこの設定をロードし、ツール呼び出しを対応するmixinメソッドにディスパッチします。@handle_errorsデコレータはすべてのツール呼び出しを構造化エラーハンドリングでラップし、GRAPHQL_QUERY_FAILEDVALIDATION_FAILEDHOLD_NOT_FOUNDHOLD_ALREADY_EXPIREDAVAILABILITY_INSUFFICIENTPRICING_MODE_UNSUPPORTEDなどのコード付き型付きエラーレスポンスを生成します。

38のツールは完全なRFQライフサイクルを網羅します:

ドメイン ツール 主要操作
リクエスト管理 8 送信、更新、取得、検索、アイテム追加/削除、プロバイダーアイテム割当/削除
アイテム管理 3 アイテム検索、アイテム取得、バッチ情報付きプロバイダーアイテム取得
見積もり管理 4 見積もり更新、見積もり取得、見積もり検索、見積もりアイテム更新
価格設定 3 価格階層取得、割引プロンプト取得、見積もり価格計算
分割払い 4 作成、更新、スケジュール作成、分割払い取得
可用性 5 チェック、ホールド取得、解放、確認、期限切れ
バンドル 3 バンドル検索、バンドル取得、コンポーネント検索
キャンセル 2 ポリシー取得、ポリシー検索
カタログ 1 カタログ照会(ナレッジグラフ検索)
セグメント 1 セグメント連絡先取得
ファイル 2 アップロード、ファイル取得
ワークフロー 2 リクエスト確認+見積もり作成、見積もり確認+分割払い作成

2つのワークフローツール — confirm_request_and_create_quotesconfirm_quote_and_create_installments — は複数の原子的ツールを単一呼び出しに構成する便利操作です。前者は1操作でリクエストを確認し、選択したプロバイダーの見積もりを作成します。後者は1操作で見積もりを確認し、分割払い計画を作成します。これらは最も一般的なマルチステップワークフローでエージェントが必要な往復回数を減らします。

inquire_catalogツールはナレッジグラフへのブリッジです。自然言語クエリ、オプションの名前空間(hotel、flight、activity)、制限を受け取り、ナレッジグラフエンジンからランク付けされた結果を返します。これがエージェントが見積もる前に製品を発見する方法です — 製品テーブルに対するSQLクエリではなく、代替品、互換性、可用性を知るカタロググラフを通じたセマンティック検索です。

このアーキテクチャが一般化する理由

リファレンス実装は旅行とホスピタリティ向けに構築されました — 日付限定在庫、占有ルール、パーpax価格、バンドル、デポジット、キャンセル条件。それがストレステストです。エンジンが特定日付のホテル部屋を見積もり予約し、TTLで可用性をホールドし、キャンセルポリシーをスナップショットし、FXレートをロックし、セグメント固有の価格階層を適用し、分割払い計画に引き渡すことができれば、サプライヤー、バイヤー、制約付き在庫、商業条件を持つあらゆるB2B RFQワークフローをサポートできます。

一般化は構造的です:

  • 配送と卸売: ホテル部屋を倉庫在庫に、プロバイダーアイテムをサプライヤーSKUに、バッチをロット番号と有効期限に置き換える。可用性ホールドは在庫を予約し、キャンセルスナップショットは返品条件を保存します。
  • 調達: 旅行パッケージを部品と材料に置き換える。セグメントベースの価格階層は交渉契約レートを適用し、ガードレール価格は非収益調達を防ぎます。
  • 製造: サービスウィンドウを生産スロットに置き換える。可用性ホールドは生産ラインのキャパシティを予約し、キャンセルポリシーは再予定ペナルティを保存します。
  • フィールドサービス: ホテル日付を予約ウィンドウに置き換える。可用性ホールドは技術者の時間枠を予約し、分割払い計画は支払いスケジュールを構造化します。

エンティティモデル — Request、Item、ProviderItem、ProviderItemBatch、Quote、QuoteItem、CancellationPolicy、Installment — は業界非依存です。ドメインmixinは組成可能です:調達デプロイメントはBundle mixinを削除し、残りを保持できます。製造デプロイメントはAvailabilityに加えてProductionSlot mixinを追加できます。モジュールパターン、状態マシン、明示的ハンドルアプローチは同じままです。


NetSuite、BigCommerce、3つのサプライヤーカタログを運用するディストリビューターは、メールまたはポータルでRFQを受信し、カタロググラフに対して製品と代替品を解決し、顧客セグメントごとに価格設定し、有効期限付きで在庫をホールドし、キャンセル条件をスナップショットし、FXレートをロックし、承諾された見積もりをNetSuiteに書き戻すエージェントを取得します — すべてのツール呼び出しがログに記録され、すべての状態遷移が検証されます。そのビルドは4ステップメソッドのフェーズ2-3であり、通常5〜8週間で稼働します。

スコープ定義されたビルドをリクエスト。 1週間のDiscovery。システム目録、ワークフローマップ、固定スコープを取得 — 当社と構築するかどうかにかかわらず。

あなたのシステムのためにこれを構築したいですか?

ここの各ドキュメントは実際の本番作業から来ています。ターゲットシステムとワークフローがあれば、1週間でスコープを定義できます。

スコープ付き構築を依頼

1週間のディスカバリ。システムインベントリ、ワークフローマップ、固定スコープを提供します — 私たちと構築するかどうかにかかわらず。