[Core] 보수적 PreflightCostBound 계산 계약과 구현 - #57
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughSummary by CodeRabbit
Walkthrough토큰 계산 결과 계약과 preflight 비용 상한 계약을 추가했다. 가격 및 토큰화 메타데이터를 검증하고, 계산 불가 사유 또는 비용 상한을 반환한다. 소비자 실행 검증과 관련 테스트 및 상태 문서를 갱신했다. Changes토큰 및 비용 계약
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
재검토 결과입니다.
병합 전에 우선 해결할 핵심 문제는 다음입니다.
- P1: 계산 시점에 확정한 가격 스냅샷을 버리고 mutable registry를 다시 조회해, 예약과 실제 정산에 서로 다른 가격이 사용될 수 있습니다.
- P2: UpperBoundCapability를 호출자가 임의로 FINITE라고 선언할 수 있어, 검증된 정책 스냅샷의 결과라는 보장이 없습니다.
- P3: 극단적인 BigDecimal 소수 자릿수에서 Bounded/Unavailable 계약 밖으로 ArithmeticException이 빠질 수 있습니다.
컨텍스트 허용 판정(#33)은 PR 설명의 제외 범위이므로 이번 PR의 병합 blocker로 보지는 않습니다. 다만 이후 연결 시 REQUEST 범위와 모델 컨텍스트 검증 결과를 함께 전달해야 합니다.
| ); | ||
| } | ||
|
|
||
| Optional<PricingSnapshot> resolvedSnapshot = pricingRegistry.resolveSnapshot( |
There was a problem hiding this comment.
[P1] 이 위치에서는 이미 확정한 가격 스냅샷을 다시 조회하고 있습니다. 현재 InMemoryPricingRegistry는 같은 model/policy를 덮어써도 catalogVersion을 default로 유지하므로, 문맥을 만든 뒤 가격이 10에서 1로 바뀌면 기존 검사만으로는 변경을 알아채지 못합니다. 그 결과 예약에는 낮은 가격을 쓰고 실제 정산에는 이전 스냅샷을 쓰는 경쟁 조건이 생겨 예산을 덜 예약할 수 있습니다. 호출 전에 만든 불변 PricingSnapshot 또는 그 식별자를 이 계산에 직접 전달하고, 이후 예약과 정산까지 같은 값을 사용하도록 바꿔 주세요.
| PreflightCostResult estimate( | ||
| PreflightPricingContext pricingContext, | ||
| TokenCountResult requestInput, | ||
| long reservedOutputTokens |
There was a problem hiding this comment.
[P2] 이 PR 범위에서 컨텍스트 허용 판정 자체를 구현하라는 뜻은 아닙니다. 다만 #33과 연결할 때는 모델의 컨텍스트 한도와 요청 허용 판정 결과를 이 계약에 전달해 주세요. 현재처럼 출력 토큰이 음수가 아닌지만 확인하면, 입력 상한과 예약 출력량의 합이 한도를 넘어도 Bounded가 반환될 수 있습니다. PR 설명의 제외 범위와 맞으므로 이번 병합 blocker는 아니지만, 다음 경계에서 실행 가능한 요청인지 반드시 검증해야 합니다.
| String catalogVersion, | ||
| TokenizationBasis tokenizationBasis, | ||
| Currency currency, | ||
| UpperBoundCapability upperBoundCapability |
There was a problem hiding this comment.
[P2] 현재 문맥은 모델·정책·카탈로그 식별자와 호출자가 전달한 UpperBoundCapability만 보관합니다. #45가 요구하는 별칭에서 표준 모델로의 해석, 불변 ModelRegistry/PricingPolicy 스냅샷, 유한·무한 상한 판정은 호출자가 임의로 선언할 값이 아니라 검증된 정책과 스냅샷에서 나와야 합니다. 지금은 실제 정책이 달라도 FINITE를 전달하면 계산이 진행됩니다. 검증된 불변 스냅샷을 이 경계에 전달하도록 책임을 한 곳으로 모아 주세요.
| } | ||
|
|
||
| private BigDecimal costFor(long tokens, BigDecimal ratePerK) { | ||
| return ratePerK.multiply(BigDecimal.valueOf(tokens)).movePointLeft(TOKENS_PER_K_SHIFT); |
There was a problem hiding this comment.
[P3] PricingSnapshot은 음수가 아닌 BigDecimal을 허용하지만, 소수 자릿수가 극단적으로 크면 movePointLeft(3)에서 ArithmeticException: Underflow가 발생할 수 있습니다. 그러면 Bounded 또는 Unavailable이라는 정형 결과 계약 밖으로 예외가 빠져나갑니다. 가격 입력 경계에서 소수 자릿수 범위를 검증해 명시적으로 거부하거나, 정형 unavailable 결과로 바꾸고 극단적인 자릿수 회귀 테스트를 추가해 주세요.
|
수정 사항을 통합 PR #59에 반영해 main에 병합했습니다. 이 PR은 중복이므로 닫습니다. |
배경
REQUEST 범위의 input token 안전 상한과 reserved output token을 호출 전 금액 상한으로 변환하는 Core 경계가 필요합니다. 평균 token estimate를 그대로 예약 금액으로 사용하거나 가격 누락을 0원으로 처리하지 않도록 계산 결과와 실패 사유를 구조화했습니다.
변경 내용
PreflightCostEstimatorCore 계약을 추가했습니다.PreflightCostResult.Bounded와Unavailable을 sealed 결과로 분리했습니다.estimatedCost와 reservation용safeUpperBoundCost를 분리했습니다.BigDecimalexact arithmetic을 사용하고 반올림하지 않습니다.테스트
Long.MAX_VALUEtoken 정밀도와 overflow 회귀검증:
제외 범위
선행 API 미병합 범위
TokenCountResult는 선행 PR #56의 구현을 사용합니다.main에 병합되지 않아 alias/canonical model 해석과 context admission API는 이 PR에서 대신 구현하지 않았습니다.PreflightPricingContext와 REQUEST 범위의TokenCountResult를 입력받으며, [Core] versioned ModelRegistry와 최소 모델 정책 등록 #32/[Core] TokenBudget.check()와 reserved output token 지원 #33 병합 후 해당 경계와 연결합니다.Refs #45