목적
호출 전 계산한 REQUEST 범위의 안전한 input token 상한 과 reservedOutputTokens를 immutable pricing policy에 적용해, atomic budget reservation에 사용할 보수적 금액 상한을 계산한다.
이 이슈는 token context admission(#33 )과 금액 reservation(#36 ) 사이의 명시적인 경계다. token 상한을 비용 상한으로 바꾸는 책임을 Advisor나 reservation store에 흩어놓지 않는다.
문제 정의
[Core] framework-independent TokenEstimator와 TokenCountResult API 추가 #30 /#33은 token 상한과 context 적합 여부를 정의하지만 이를 얼마로 예약할지는 정의하지 않는다.
평균 token estimate에 평균 단가를 곱한 값은 정보 제공용 추정치일 뿐 예산 초과를 막는 안전한 금액 상한이 아니다.
호출 전에는 cache read/create 여부와 reasoning output 비율을 모를 수 있다.
가격 미등록을 0원으로 처리하면 무료 모델과 catalog 누락을 구분할 수 없다.
TEXT_ONLY 계산은 request framing/tool/schema가 빠져 있으므로 전체 요청의 비용 상한이 아니다.
호출 후 actual TokenUsage와 호출 전 preflight bound를 같은 타입으로 재사용하면 정산과 예약의 의미가 섞인다.
목표 API
이름은 구현 중 조정할 수 있으나 책임은 하나의 Core 서비스로 둔다.
public interface PreflightCostEstimator {
PreflightCostResult estimate (
ModelDefinition model ,
TokenCountResult requestInput ,
long reservedOutputTokens
);
}
권장 결과:
PreflightCostResult
├─ Bounded
│ ├─ estimatedCost
│ ├─ safeUpperBoundCost
│ ├─ inputEstimatedTokens
│ ├─ inputSafeUpperBoundTokens
│ ├─ reservedOutputTokens
│ ├─ currency
│ ├─ canonicalModelId
│ ├─ pricingPolicyId
│ ├─ catalogVersion
│ └─ estimatorId / estimatorVersion
└─ Unavailable
└─ bounded reason
CostBound 또는 Bounded 결과는 immutable value object다. estimatedCost는 관찰/표시용이며 #36의 reservation은 반드시 safeUpperBoundCost를 사용한다.
계산 전제
숫자 비용 상한은 다음 조건을 모두 만족할 때만 생성한다.
input result가 정상 계산 상태다.
scope가 REQUEST다.
estimator/tokenizer compatibility가 model definition과 일치한다.
reservedOutputTokens >= 0이고 [Core] TokenBudget.check()와 reserved output token 지원 #33 context admission과 일관된다.
model이 참조하는 pricingPolicyId와 catalogVersion의 immutable snapshot을 찾을 수 있다.
pricing policy가 해당 요청에 대해 유한한 보수적 상한을 제공할 수 있다.
조건을 만족하지 못하면 0원이나 임의의 fallback 단가를 반환하지 않고 typed Unavailable/INDETERMINATE 결과를 반환한다. budget enforcement 경계의 기본 정책은 fail-closed다.
TEXT_ONLY 값에 누락된 framing/headroom을 명시적으로 더해 REQUEST 결과를 만든 경우에만 사용할 수 있다. 단순히 scope enum을 변경해 비용 상한을 생성하는 것은 금지한다.
보수적 가격 계산 규칙
input은 inputSafeUpperBoundTokens를 사용한다.
output은 최대 생성 가능량인 reservedOutputTokens를 사용한다.
cache read/create, reasoning 등 호출 전에 breakdown을 확정할 수 없는 항목은 PricingPolicy가 가능한 적용 경로 중 최대 비용이 되는 유한 단가/조합을 선택한다.
포함 관계인 cache/reasoning token을 total에 다시 더하지 않는다. 정책은 같은 token을 이중 과금하지 않아야 한다.
할인만 있는 cache read를 발생한다고 가정해 상한을 낮추지 않는다.
reasoning이 일반 output 단가에 포함되는지 별도 단가인지 여부는 pricing policy가 명시적으로 소유한다.
정책상 유한 상한을 계산할 수 없으면 Unavailable(UNBOUNDED_PRICING)로 반환한다.
safeUpperBoundCost >= estimatedCost >= 0
모든 금액은 같은 currency이며 내부 계산에서 반올림하지 않는다.
표시/청구 경계 전까지 [ADR] TokenUsage와 Money 불변식 및 정밀도 정책 정의 #24 /#26의 BigDecimal 정밀도와 반올림 정책을 따른다.
가격 식별과 재현성
Bounded 결과에는 최소한 다음 식별자를 보존한다.
canonical model id
currency
pricingPolicyId
catalogVersion
estimatorId/version
계산에 사용한 input estimate/safe bound와 reserved output
alias 입력도 먼저 canonical model로 해석한다. 계산 도중 catalog를 재조회해 다른 버전을 혼합하지 않고 하나의 immutable snapshot만 사용한다.
가격 미등록과 0원 정책
가격 미등록: Unavailable(PRICING_NOT_FOUND), budget 경계 fail-closed
필수 price component 누락: Unavailable(INCOMPLETE_PRICING)
명시적인 무료 정책: 등록된 pricing policy가 모든 적용 단가를 정확히 0으로 선언한 경우에만 0원 Bound 생성
통화 불일치: Unavailable(CURRENCY_MISMATCH), 환율 변환은 하지 않음
구현 범위
Core PreflightCostEstimator와 immutable/sealed result
보수적 upper-bound 계산을 소유하는 pricing policy API
bounded unavailable reason
ModelRegistry/PricingRegistry immutable snapshot 연결
Money/Cost 기반 exact arithmetic
#36이 소비할 safeUpperBoundCost projection
계산 근거를 로그/이벤트에 전달할 식별 metadata
Spring AI 타입 없는 public API
테스트 시나리오
Acceptance criteria
제외 범위
의존관계와 순서
선행:
이 이슈 완료 후 진행:
Source
목적
호출 전 계산한 REQUEST 범위의 안전한 input token 상한과
reservedOutputTokens를 immutable pricing policy에 적용해, atomic budget reservation에 사용할 보수적 금액 상한을 계산한다.이 이슈는 token context admission(#33)과 금액 reservation(#36) 사이의 명시적인 경계다. token 상한을 비용 상한으로 바꾸는 책임을 Advisor나 reservation store에 흩어놓지 않는다.
문제 정의
TokenUsage와 호출 전 preflight bound를 같은 타입으로 재사용하면 정산과 예약의 의미가 섞인다.목표 API
이름은 구현 중 조정할 수 있으나 책임은 하나의 Core 서비스로 둔다.
권장 결과:
CostBound또는Bounded결과는 immutable value object다.estimatedCost는 관찰/표시용이며 #36의 reservation은 반드시safeUpperBoundCost를 사용한다.계산 전제
숫자 비용 상한은 다음 조건을 모두 만족할 때만 생성한다.
reservedOutputTokens >= 0이고 [Core] TokenBudget.check()와 reserved output token 지원 #33 context admission과 일관된다.pricingPolicyId와catalogVersion의 immutable snapshot을 찾을 수 있다.조건을 만족하지 못하면 0원이나 임의의 fallback 단가를 반환하지 않고 typed
Unavailable/INDETERMINATE 결과를 반환한다. budget enforcement 경계의 기본 정책은 fail-closed다.TEXT_ONLY 값에 누락된 framing/headroom을 명시적으로 더해 REQUEST 결과를 만든 경우에만 사용할 수 있다. 단순히 scope enum을 변경해 비용 상한을 생성하는 것은 금지한다.
보수적 가격 계산 규칙
inputSafeUpperBoundTokens를 사용한다.reservedOutputTokens를 사용한다.PricingPolicy가 가능한 적용 경로 중 최대 비용이 되는 유한 단가/조합을 선택한다.Unavailable(UNBOUNDED_PRICING)로 반환한다.safeUpperBoundCost >= estimatedCost >= 0가격 식별과 재현성
Bounded 결과에는 최소한 다음 식별자를 보존한다.
alias 입력도 먼저 canonical model로 해석한다. 계산 도중 catalog를 재조회해 다른 버전을 혼합하지 않고 하나의 immutable snapshot만 사용한다.
가격 미등록과 0원 정책
Unavailable(PRICING_NOT_FOUND), budget 경계 fail-closedUnavailable(INCOMPLETE_PRICING)Unavailable(CURRENCY_MISMATCH), 환율 변환은 하지 않음구현 범위
PreflightCostEstimator와 immutable/sealed resultsafeUpperBoundCostprojection테스트 시나리오
safeUpperBoundCost >= estimatedCost >= 0불변식을 검증한다.TokenUsage타입을 재사용하지 않는다.Acceptance criteria
TokenUsage를 preflight 결과로 재사용하지 않는다.제외 범위
의존관계와 순서
선행:
이 이슈 완료 후 진행:
safeUpperBoundCost를 예약한다.Source