Skip to content

[Core] 보수적 PreflightCostBound 계산 계약과 구현 - #57

Closed
Leejaewang03 wants to merge 8 commits into
mainfrom
feat/issue-45-preflight-cost-bound
Closed

[Core] 보수적 PreflightCostBound 계산 계약과 구현#57
Leejaewang03 wants to merge 8 commits into
mainfrom
feat/issue-45-preflight-cost-bound

Conversation

@Leejaewang03

@Leejaewang03 Leejaewang03 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

배경

REQUEST 범위의 input token 안전 상한과 reserved output token을 호출 전 금액 상한으로 변환하는 Core 경계가 필요합니다. 평균 token estimate를 그대로 예약 금액으로 사용하거나 가격 누락을 0원으로 처리하지 않도록 계산 결과와 실패 사유를 구조화했습니다.

변경 내용

  • PreflightCostEstimator Core 계약을 추가했습니다.
  • PreflightCostResult.BoundedUnavailable을 sealed 결과로 분리했습니다.
  • estimatedCost와 reservation용 safeUpperBoundCost를 분리했습니다.
  • canonical model, pricing policy, catalog, currency와 estimator/tokenizer metadata를 결과에 보존합니다.
  • REQUEST scope와 tokenizer compatibility를 검증합니다.
  • pricing 미등록, 필수 단가 누락, unbounded pricing, currency/snapshot mismatch를 typed unavailable로 반환합니다.
  • cache read/create와 reasoning 가능한 경로에서는 입력/출력별 최대 유한 단가를 선택하고 같은 token을 이중 합산하지 않습니다.
  • 명시적으로 등록된 무료 pricing policy만 정확한 0원 bound를 반환합니다.
  • 내부 계산은 BigDecimal exact arithmetic을 사용하고 반올림하지 않습니다.

테스트

  • estimated cost와 safe upper bound 분리
  • safe upper bound 단조성
  • cache/reasoning 최대 단가 선택과 이중 과금 방지
  • TEXT_ONLY, unavailable count, tokenizer mismatch
  • pricing 미등록/불완전/unbounded typed unavailable
  • 명시적 무료 정책과 currency/snapshot mismatch
  • Long.MAX_VALUE token 정밀도와 overflow 회귀
  • 음수 reserved output 거부

검증:

./gradlew :token-pilot-core:test
./gradlew check
BUILD SUCCESSFUL

제외 범위

선행 API 미병합 범위

Refs #45

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 409fe1b2-aed3-495e-87ae-cc98421de27b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • 새 기능

    • 요청의 토큰 수와 계산 정확도·범위를 확인할 수 있습니다.
    • 호출 전 예상 비용과 보수적인 최대 비용 상한을 계산합니다.
    • 계산이 불가능한 경우 구체적인 사유와 메타데이터를 제공합니다.
    • 가격, 통화, 모델 및 토큰화 기준 정보를 검증합니다.
  • 검증 및 안정성

    • 빈 입력을 정확한 0토큰 결과로 처리합니다.
    • 잘못된 토큰 수, 가격 정보, 통화 및 스냅샷 불일치를 감지합니다.
    • 소비자 실행 환경에서 불필요한 런타임 의존성을 검사합니다.

Walkthrough

토큰 계산 결과 계약과 preflight 비용 상한 계약을 추가했다. 가격 및 토큰화 메타데이터를 검증하고, 계산 불가 사유 또는 비용 상한을 반환한다. 소비자 실행 검증과 관련 테스트 및 상태 문서를 갱신했다.

Changes

토큰 및 비용 계약

Layer / File(s) Summary
토큰 계산 계약
token-pilot-core/src/main/java/io/tokenpilot/core/TokenEstimator.java, token-pilot-core/src/main/java/io/tokenpilot/core/domain/*, token-pilot-core/src/test/java/io/tokenpilot/core/domain/*
TokenEstimatorTokenCountResult를 추가했다. counted 및 unavailable 상태, 정확도, 범위, estimator와 tokenization 메타데이터를 표현하고 입력값을 검증한다.
Preflight 비용 결과 계약
token-pilot-core/src/main/java/io/tokenpilot/core/PreflightCostEstimator.java, token-pilot-core/src/main/java/io/tokenpilot/core/domain/Preflight*
가격 컨텍스트, 비용 결과, unavailable 사유를 추가했다. 비용 통화, 토큰 수, 가격 스냅샷, 식별자와 안전 상한 관계를 검증한다.
비용 상한 계산과 연결
token-pilot-core/src/main/java/io/tokenpilot/core/internal/*, token-pilot-core/src/test/java/io/tokenpilot/core/internal/*
DefaultPreflightCostEstimator가 토큰 범위, tokenizer, 가격 상태, 통화 및 snapshot을 순차적으로 검증한다. 검증 성공 시 예상 비용과 보수적인 안전 상한을 계산하고, 실패 시 typed Unavailable 결과를 반환한다.
소비자 검증과 상태 문서
build.gradle, AGENTS.md
생성된 소비자 프로젝트에서 금지된 런타임 의존성을 검사하고 run 전에 검증한다. 빈 문자열의 정확한 0토큰 결과 검증과 새 기능의 상태 및 업데이트 기록을 추가했다.

Suggested reviewers: huitaepark

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.97% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed 설명은 PreflightCostEstimator 계약, 보수적 비용 상한 계산, 검증 범위와 테스트 내용을 변경 사항에 맞게 설명합니다.
Title check ✅ Passed 제목은 PR의 핵심 변경인 보수적 PreflightCostBound 계산 계약과 구현을 명확하게 요약합니다.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai
coderabbitai Bot requested a review from HuitaePark August 8, 2026 07:51

@HuitaePark HuitaePark left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

재검토 결과입니다.

병합 전에 우선 해결할 핵심 문제는 다음입니다.

  • P1: 계산 시점에 확정한 가격 스냅샷을 버리고 mutable registry를 다시 조회해, 예약과 실제 정산에 서로 다른 가격이 사용될 수 있습니다.
  • P2: UpperBoundCapability를 호출자가 임의로 FINITE라고 선언할 수 있어, 검증된 정책 스냅샷의 결과라는 보장이 없습니다.
  • P3: 극단적인 BigDecimal 소수 자릿수에서 Bounded/Unavailable 계약 밖으로 ArithmeticException이 빠질 수 있습니다.

컨텍스트 허용 판정(#33)은 PR 설명의 제외 범위이므로 이번 PR의 병합 blocker로 보지는 않습니다. 다만 이후 연결 시 REQUEST 범위와 모델 컨텍스트 검증 결과를 함께 전달해야 합니다.

);
}

Optional<PricingSnapshot> resolvedSnapshot = pricingRegistry.resolveSnapshot(

@HuitaePark HuitaePark Aug 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] 이 위치에서는 이미 확정한 가격 스냅샷을 다시 조회하고 있습니다. 현재 InMemoryPricingRegistry는 같은 model/policy를 덮어써도 catalogVersion을 default로 유지하므로, 문맥을 만든 뒤 가격이 10에서 1로 바뀌면 기존 검사만으로는 변경을 알아채지 못합니다. 그 결과 예약에는 낮은 가격을 쓰고 실제 정산에는 이전 스냅샷을 쓰는 경쟁 조건이 생겨 예산을 덜 예약할 수 있습니다. 호출 전에 만든 불변 PricingSnapshot 또는 그 식별자를 이 계산에 직접 전달하고, 이후 예약과 정산까지 같은 값을 사용하도록 바꿔 주세요.

PreflightCostResult estimate(
PreflightPricingContext pricingContext,
TokenCountResult requestInput,
long reservedOutputTokens

@HuitaePark HuitaePark Aug 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 이 PR 범위에서 컨텍스트 허용 판정 자체를 구현하라는 뜻은 아닙니다. 다만 #33과 연결할 때는 모델의 컨텍스트 한도와 요청 허용 판정 결과를 이 계약에 전달해 주세요. 현재처럼 출력 토큰이 음수가 아닌지만 확인하면, 입력 상한과 예약 출력량의 합이 한도를 넘어도 Bounded가 반환될 수 있습니다. PR 설명의 제외 범위와 맞으므로 이번 병합 blocker는 아니지만, 다음 경계에서 실행 가능한 요청인지 반드시 검증해야 합니다.

String catalogVersion,
TokenizationBasis tokenizationBasis,
Currency currency,
UpperBoundCapability upperBoundCapability

@HuitaePark HuitaePark Aug 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 현재 문맥은 모델·정책·카탈로그 식별자와 호출자가 전달한 UpperBoundCapability만 보관합니다. #45가 요구하는 별칭에서 표준 모델로의 해석, 불변 ModelRegistry/PricingPolicy 스냅샷, 유한·무한 상한 판정은 호출자가 임의로 선언할 값이 아니라 검증된 정책과 스냅샷에서 나와야 합니다. 지금은 실제 정책이 달라도 FINITE를 전달하면 계산이 진행됩니다. 검증된 불변 스냅샷을 이 경계에 전달하도록 책임을 한 곳으로 모아 주세요.

}

private BigDecimal costFor(long tokens, BigDecimal ratePerK) {
return ratePerK.multiply(BigDecimal.valueOf(tokens)).movePointLeft(TOKENS_PER_K_SHIFT);

@HuitaePark HuitaePark Aug 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] PricingSnapshot은 음수가 아닌 BigDecimal을 허용하지만, 소수 자릿수가 극단적으로 크면 movePointLeft(3)에서 ArithmeticException: Underflow가 발생할 수 있습니다. 그러면 Bounded 또는 Unavailable이라는 정형 결과 계약 밖으로 예외가 빠져나갑니다. 가격 입력 경계에서 소수 자릿수 범위를 검증해 명시적으로 거부하거나, 정형 unavailable 결과로 바꾸고 극단적인 자릿수 회귀 테스트를 추가해 주세요.

@HuitaePark

Copy link
Copy Markdown
Member

수정 사항을 통합 PR #59에 반영해 main에 병합했습니다. 이 PR은 중복이므로 닫습니다.

@HuitaePark HuitaePark closed this Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants