라이브러리로 돌아가기
아키텍처

RFQ 엔진 아키텍처: 가용성 홀드와 취소 스냅샷이 중요한 이유

최종 업데이트: 2026年7月13日

대부분의 RFQ 자동화가 간과하는 문제

팀이 RFQ 워크플로를 자동화할 때, 일반적으로 프론트엔드에 집중합니다: 바이어 요청을 파싱하고, 카탈로그를 검색하고, 제품을 매칭하고, 가격을 적용합니다. 이 작업은 실제이며 도구가 존재합니다 — GEP가 그 구매 자동화 분석에서 설명하듯, AI가 요구사항을 추출하고 결정론적 규칙이 가격, 승인, SLA 추적을 처리합니다. Ivalua의 2026년 소싱 가이드도 같은 패턴을 확인합니다: AI가 추출과 매칭을 처리하고, 규칙이 거버넌스를 처리합니다.

하지만 두 가지 문제가 프로덕션에서 이 모델을 깨고, 대부분의 자동화는 이를 다루지 않습니다:

  1. 가용성 드리프트. 협상에 3일이 걸리는 견적은 제시된 가격으로 더 이상 존재하지 않을 수 있는 재고에 대한 견적입니다. 에이전트가 $12.40에 50단위를 약속하고 바이어가 수락할 때 공급업체에 12개만 남아 있다면, 그 견적은 처음부터 인도 불가능했습니다. 에이전트가 거짓말을 한 것이 아닙니다 — 세상이 그 아래서 바뀐 것입니다.

  2. 정책 드리프트. 30일 전에 견적된 취소 페널티는 예약 시점에 유효한 페널티와 다를 수 있습니다. 공급업체가 협상 도중에 조건을 강화하고 에이전트가 견적 시점에 정책을 캡처하지 않았다면, 바이어는 나중에 그 요금을 이의제기합니다. 이의제기는 정당합니다 — 조건이 바뀐 것입니다.

둘 다 해결하는 아키텍처는 두 가지 메커니즘을 가진 RFQ 엔진입니다: 시간 창 동안 제한된 재고를 예약하는 원자적 가용성 홀드와 견적 순간에 상업 조건을 동결하는 취소 정책 스냅샷. 이것들은 견적 도구에 볼트온된 기능이 아닙니다. 데이터 모델의 구조적 커밋먼트입니다 — 엔진이 강제하는 필드, 상태 전이, 라이프사이클 작업입니다.

이것을 작동시키는 엔티티 모델

프로덕션 RFQ 엔진은 요청과 견적 이상이 필요합니다. 엔티티 모델은 바이어 의도를 공급업체 응답에서 분리해야 하며, 카탈로그와 독립적으로 제한된 공급을 추적해야 합니다. 다음 엔티티가 백본을 형성합니다:

Request — 바이어 의도: 연락처, 제목, 라인 아이템, 청구 및 배송 주소, 만료, 상태. 요청은 정의된 상태 머신을 통해 이동합니다: initialin_progressconfirmedcompleted, 견적이 이미 존재한 후 아이템이 변경될 때 재진입점으로 modified.

Item — 바이어가 원하는 카탈로그 제품. 아이템은 제네릭입니다; UUID, 타입, 가격 책정 모드, 측정 단위를 가집니다. 같은 아이템이 여러 공급업체 아이템에 매핑됩니다.

ProviderItem — 그 아이템에 대한 공급업체의 특정 제공. 측정 단위당 기본 가격. 공급업체 아이템은 공급업체 기업에 속하며 여러 배치를 가질 수 있습니다.

ProviderItemBatch — 제한된 재고 단위. 각 배치에는 배치 번호, 생산일, 만료일, 단위당 비용, 재고 플래그, 느린 이동 아이템 플래그 slow_move_item이 있습니다. 배치는 가용성이 확인되고 홀드되는 세분성입니다.

Quote — 확인된 요청에 대한 공급업체의 가격이 지정된 응답. 견적에는 공급업체, 영업 담당자, 배송 방법과 금액, 통화, 환율과 환율 잠금 타임스탬프, 협상 라운드 수(자동 계산), 상태 머신이 있습니다: initialin_progressconfirmedcompleted 또는 disapproved.

QuoteItem — 견적의 한 라인, 공급업체 아이템과 배치를 수량, 단위당 가격, 소계, 할인, 최종 소계에 연결. 결정적으로, 견적 아이템은 hold_token, hold_expires_at, guardrail_price_per_uom, slow_move_item을 가집니다 — 가용성 홀드와 수익성 가드레일을 라인 레벨에서 보이게 만드는 필드입니다.

CancellationPolicy — 예약이 취소될 수 있는 조건, 티어, 마감일, 페널티 규칙, 공급업체 아이템별로 범위 지정.

Installment — 확인된 견적에 연결된 지불 일정, 우선순위, 예정일, 금액, 비율, 상태 머신: pendingpaid 또는 cancelled.

이것은 플랫 테이블이 아닙니다. 중첩된 리졸버를 가진 타입화된 그래프입니다 — 요청은 견적을 해결하고, 견적은 아이템을 해결하고, 견적 아이템은 공급업체 아이템과 배치를 해결합니다. 엔진은 DataLoader 기반 배치 로드를 사용하여 20개의 라인 아이템을 가진 견적이 20개의 순차 데이터베이스 조회를 발생시키지 않도록 합니다.

가용성 홀드: 원자적으로 용량 예약

가용성 홀드는 가용성 드리프트를 방지하는 메커니즘입니다. 없으면 에이전트는 진부해질 수 있는 재고 스냅샷에 대해 견적합니다. 있으면 에이전트는 시간 창 동안 용량을 예약하고 견적은 실제, 감소된 공급으로 뒷받침됩니다.

홀드 라이프사이클에는 4개 상태와 엄격한 전이 규칙이 있습니다:

상태 의미 허용된 전이
held 용량 감소, TTL(15분)과 함께 홀드 토큰 발행 confirmed, released, expired
confirmed 예약 커밋, 두 번째 용량 감소 없음 종단
released 홀드 취소, 용량 복원(멱등) 종단
expired TTL 경과, 용량 복원(멱등) 종단

중요한 설계 결정은 confirmed 전이에 있습니다: 두 번째 용량 감소 없음. 홀드는 이미 용량을 예약했습니다. 예약 확인은 재고 카운트에 다시 접촉하지 않고 홀드를 확인된 상태로 변환합니다. 이것은 이중 계산을 방지합니다 — 홀드가 용량을 감소시키고, 확인이 다시 감소시켜 시스템이 실제보다 적은 공급을 가지고 있다고 생각하는 버그입니다.

홀드는 원자적입니다. acquireAvailabilityHold 뮤테이션은 가용 용량을 감소시키고 expires_at 타임스탬프와 함께 hold_token을 반환합니다. 용량이 부족하면 작업은 부작용 없이 실패합니다 — 부분 홀드 없음, 더러운 상태 없음. releaseAvailabilityHoldexpireAvailabilityHold 뮤테이션은 용량을 복원하며 멱등입니다: 같은 토큰에 두 번 호출하면 같은 결과를 반환합니다. 이것은 장애 허용에 중요합니다 — 네트워크 타임아웃이 재시도를 유발하면 시스템이 용량을 과도하게 복원하지 않습니다.

이것이 견적 워크플로에 매핑되는 방법

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로 전이, 용량 잠금, 두 번째 감소 없음.
  5. 바이어가 거절하거나 TTL이 만료되면, 에이전트는 release_availability_hold를 호출하거나 만료시킵니다 — 용량이 복원, 다음 견적이 사용 가능.

hold_token은 명시적 핸들입니다. 모델은 숨겨진 세션 상태가 아닌 인수로 전달합니다. 이것은 MCP 2026-07-28 릴리스 후보가 공식화하는 명시적 핸들 패턴과 동일합니다: 에이전트가 도구 호출 간에 필요한 상태가 전송 계층 메타데이터가 아닌 보이고 감사 가능한 핸들이 됩니다.

취소 스냅샷: 조건 동결

취소 정책 드리프트는 두 번째 장애 모드입니다. 공급업체는 견적과 예약 사이에 취소 페널티를 변경할 수 있습니다 — 30일 전 10% 페널티가 7일 전 50%가 됩니다. 에이전트가 이전 페널티를 견적하고 바이어가 수락했다면, 바이어는 새 요금을 이의제기합니다. 이의제기는 올바릅니다.

아키텍처는 견적 시점에 취소 정책을 스냅샷하여 이를 해결합니다. CancellationPolicy 엔티티에는 tiers(마감일과 페널티 비율을 가진 JSON 구조), label, description, status가 있습니다. 에이전트가 견적을 생성할 때, 각 공급업체 아이템의 활성 취소 정책을 검색하고 정책 조건을 견적에 첨부합니다. 정책 UUID와 그 티어는 동결됩니다 — 공급업체가 나중에 정책을 업데이트해도 견적은 발행 시점에 유효했던 조건을 가집니다.

search_cancellation_policies 도구는 공급업체 아이템과 상태로 필터링하여 에이전트가 각 라인 아이템의 활성 정책을 검색할 수 있게 합니다. 정책은 외래키로 참조되는 것이 아니라 견적 아이템과 연관됩니다 — 스냅샷이 정책이지, 가변 레코드에 대한 포인터가 아닙니다.

이 패턴은 취소 이상으로 일반화됩니다. 환율도 같은 접근 방식을 사용합니다: QuoteTypefx_ratefx_rate_locked_at을 가집니다 — 환율은 예약 시점이 아닌 견적 시점에 잠깁니다. 바이어의 표시 통화가 공급업체 견적 통화와 다르면, 잠긴 환율이 바이어가 보는 환율이며, 시장 변동과 무관합니다.

가격 티어, 세그먼트 룩업, 가드레일

가격은 플랫 필드가 아닙니다. 엔진은 고객 세그먼트, 수량, 공급업체 아이템별로 가격 티어를 해결합니다. get_item_price_tiers 도구는 고객 이메일과 견적 아이템 목록(각각 아이템 UUID, 공급업체 아이템 UUID, 수량)을 받아 세그먼트 연락처를 통해 고객의 세그먼트를 룩업하고 단일 쿼리에서 모든 매칭 가격 티어를 배치 로드합니다. 티어는 데이터베이스 레벨에서 수량 임계값으로 필터링됩니다 — quantity_greater_thenquantity_less_then이 요청 수량을 감싸는 티어만 반환됩니다.

할인 프롬프트도 같은 방식으로 작동하며, 계층적 스코프 시스템을 사용합니다: GLOBAL(모든 견적에 적용), SEGMENT(고객 세그먼트에 적용), ITEM(특정 아이템에 적용), PROVIDER_ITEM(특정 공급업체의 아이템 제공에 적용). get_discount_prompts 도구는 4개 스코프 모두에서 로드하고, 중복 제거하고, 조건과 규칙이 있는 결합된 세트를 반환합니다. 이는 하나의 견적이 볼륨 할인(세그먼트 스코프), 계절적 프로모션(아이템 스코프), 공급업체별 청산(공급업체 아이템 스코프)을 동시에 적용할 수 있음을 의미합니다 — 각각 고유한 우선순위를 가집니다.

견적 아이템의 guardrail_price_per_uom 필드는 수익성 가드레일입니다 — 공급업체 아이템 배치에서 로드된 아이템의 최소 허용 가격. 에이전트가 가격을 가드레일 아래로 떨어뜨리는 할인을 적용하면, 작업은 실패하지 않지만(에이전트에게 정당한 이유가 있을 수 있음) 플래그가 견적 아이템 응답에서 보입니다. slow_move_item 플래그는 너무 오래 재고에 있었던 아이템을 표시합니다 — 에이전트는 이것을 사용하여 더 깊은 할인을 적용할 수 있지만, 가드레일은 비수익적 가격 책정을 방지합니다.

무효한 작업을 방지하는 상태 머신

상태 강제가 없는 RFQ 엔진은 스프레드시트입니다. 엔진은 4개 상태 머신을 강제하며, 각각 명시적 전이 규칙과 작업 가드를 가집니다:

Request: initialin_progressconfirmedcompleted, modified를 재진입점으로. RequestOperationGuard는 견적이 confirmed 요청에서만 생성될 수 있고, 아이템 수정은 initial, in_progress, modified 상태에서만 허용됨을 강제합니다.

Quote: initialin_progressconfirmedcompleted 또는 disapproved. QuoteOperationGuard는 아이템 수정이 initial 또는 in_progress에서만 허용되고, 분할 지불이 confirmed 견적에서만 생성될 수 있음을 강제합니다.

가용성 홀드: heldconfirmed / released / expired. 세 개의 대상 상태 모두 종단입니다.

Installment: pendingpaid 또는 cancelled. 두 대상 상태 모두 종단입니다.

확인된 요청의 아이템이 변경되고 견적이 이미 존재할 때, 요청은 modified로 전이되고 모든 기존 견적은 자동으로 거부됩니다 — 바이어는 업데이트된 아이템에 대해 새 견적을 받아야 합니다. 이는 견적을 생성한 요청이 변경된 후 진부한 견적이 수락되는 것을 방지합니다.

견적의 모든 미취소 분할 지불이 지불되면, 견적은 자동으로 completed로 전이됩니다. 요청의 최소 하나의 견적이 완료되면, 요청은 자동으로 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은 GraphQL 클라이언트, 로거, 설정을 제공하는 GraphQLBackedProcessor 기반 클래스를 공유합니다. 각 mixin은 자체 도구 메서드를 기여하고 self.logger, self.setting, self._execute_graphql_query에만 접근합니다.

도구는 각 도구의 이름, 설명, 입력 스키마(JSON Schema), 모듈 링크(클래스명, 함수명, 반환 타입)를 선언하는 설정 블록(MCP_CONFIGURATION)을 통해 등록됩니다. MCP 런타임은 시작 시 이 설정을 로드하고 도구 호출을 해당 mixin 메서드로 디스패치합니다. @handle_errors 데코레이터는 모든 도구 호출을 구조화된 에러 처리로 래핑하여 GRAPHQL_QUERY_FAILED, VALIDATION_FAILED, HOLD_NOT_FOUND, HOLD_ALREADY_EXPIRED, AVAILABILITY_INSUFFICIENT, PRICING_MODE_UNSUPPORTED와 같은 코드가 있는 타입화된 에러 응답을 생성합니다.

38개 도구는 전체 RFQ 라이프사이클을 포괄합니다:

도메인 도구 핵심 작업
요청 관리 8 제출, 업데이트, 가져오기, 검색, 아이템 추가/제거, 공급업체 아이템 할당/제거
아이템 관리 3 아이템 검색, 아이템 가져오기, 배치 정보와 함께 공급업체 아이템 가져오기
견적 관리 4 견적 업데이트, 견적 가져오기, 견적 검색, 견적 아이템 업데이트
가격 책정 3 가격 티어 가져오기, 할인 프롬프트 가져오기, 견적 가격 계산
분할 지불 4 생성, 업데이트, 일정 생성, 분할 지불 가져오기
가용성 5 확인, 홀드 획득, 해제, 확인, 만료
번들 3 번들 검색, 번들 가져오기, 컴포넌트 검색
취소 2 정책 가져오기, 정책 검색
카탈로그 1 카탈로그 조회(지식 그래프 검색)
세그먼트 1 세그먼트 연락처 가져오기
파일 2 업로드, 파일 가져오기
워크플로 2 요청 확인+견적 생성, 견적 확인+분할 지불 생성

두 워크플로 도구 — confirm_request_and_create_quotesconfirm_quote_and_create_installments — 는 여러 원자적 도구를 단일 호출로 구성하는 편의 작업입니다. 전자는 1작업으로 요청을 확인하고 선택한 공급업체의 견적을 생성합니다. 후자는 1작업으로 견적을 확인하고 분할 지불 계획을 생성합니다. 이것들은 가장 일반적인 다단계 워크플로에서 에이전트가 필요한 왕복 횟수를 줄입니다.

inquire_catalog 도구는 지식 그래프로의 브리지입니다. 자연어 쿼리, 선택적 네임스페이스(hotel, flight, activity), 한계를 받아 지식 그래프 엔진에서 순위가 지정된 결과를 반환합니다. 이것이 에이전트가 견적 전에 제품을 발견하는 방식입니다 — 제품 테이블에 대한 SQL 쿼리가 아니라, 대체품, 호환성, 가용성을 아는 카탈로그 그래프를 통한 시맨틱 검색입니다.

이 아키텍처가 일반화되는 이유

참조 구현은 여행과 호스피탈리티를 위해 구축되었습니다 — 날짜가 한정된 재고, 점유 규칙, 1인당 가격, 번들, 보증금, 취소 조건. 이것이 스트레스 테스트입니다. 엔진이 특정 날짜의 호텔 객실을 견적하고 예약하고, TTL로 가용성을 홀드하고, 취소 정책을 스냅샷하고, 환율을 잠그고, 세그먼트별 가격 티어를 적용하고, 분할 지불 계획으로 인도할 수 있다면, 공급업체, 바이어, 제한된 재고, 상업 조건을 가진 모든 B2B RFQ 워크플로를 지원할 수 있습니다.

일반화는 구조적입니다:

  • 유통과 도매: 호텔 객실을 창고 재고로, 공급업체 아이템을 공급업체 SKU로, 배치를 로트 번호와 만료일로 교체. 가용성 홀드는 재고를 예약하고; 취소 스냅샷은 반품 조건을 보존합니다.
  • 구매: 여행 패키지를 부품과 자재로 교체. 세그먼트 기반 가격 티어는 협상된 계약 요율을 적용하고; 가드레일 가격은 비수익적 구매를 방지합니다.
  • 제조: 서비스 창을 생산 슬롯으로 교체. 가용성 홀드는 생산 라인의 용량을 예약하고; 취소 정책은 재일정 페널티를 보존합니다.
  • 현장 서비스: 호텔 날짜를 예약 창으로 교체. 가용성 홀드는 기술자의 시간 슬롯을 예약하고; 분할 지불 계획은 지불 일정을 구조화합니다.

엔티티 모델 — Request, Item, ProviderItem, ProviderItemBatch, Quote, QuoteItem, CancellationPolicy, Installment — 는 산업 비종속적입니다. 도메인 mixin은 조합 가능합니다: 구매 배포는 Bundle mixin을 제거하고 나머지를 유지할 수 있습니다. 제조 배포는 Availability 옆에 ProductionSlot mixin을 추가할 수 있습니다. 모듈 패턴, 상태 머신, 명시적 핸들 접근 방식은 동일하게 유지됩니다.


NetSuite, BigCommerce, 3개 공급업체 카탈로그를 운영하는 유통업체는 이메일 또는 포털로 RFQ를 수신하고, 카탈로그 그래프에 대해 제품과 대체품을 해결하고, 고객 세그먼트별로 가격을 책정하고, 만료일과 함께 재고를 홀드하고, 취소 조건을 스냅샷하고, 환율을 잠그고, 수락된 견적을 NetSuite에 다시 기록하는 에이전트를 얻습니다 — 모든 도구 호출이 로깅되고, 모든 상태 전이가 검증됩니다. 그 빌드는 4단계 메서드의 페이즈 2-3이며, 일반적으로 5-8주 내에 라이브됩니다.

스코프가 정의된 빌드를 요청. 1주일 Discovery. 시스템 목록, 워크플로 맵, 고정 스코프를 받습니다 — 당사와 빌드하든 안 하든.

귀하의 시스템을 위해 이것을 구축하고 싶으신가요?

여기의 각 문서는 실제 프로덕션 작업에서 나왔습니다. 대상 시스템과 워크플로가 있다면, 1주 내에 빌드를 범위 정의할 수 있습니다.

범위 정의 빌드 요청

1주 발견. 시스템 인벤토리, 워크플로 맵, 고정 범위를 받습니다 — 우리와 빌드할지 여부와 관계없이.