diff --git a/AGENTS.md b/AGENTS.md
index 11addf9..2b29ab9 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -79,7 +79,7 @@ Token Pilot의 제품 포지션은 framework-independent Java LLM control and ac
| `token-pilot-notification` | Atomic accounting integration implemented | Commit, reconciliation-required, late reconciliation, and reservation BLOCK results produce process-local atomically deduplicated threshold events with isolated handlers, lifecycle observation, and a sanitized error hook; durable delivery remains |
| `token-pilot-autoconfigure` | Basic implementation complete | Owner-specific optional bean graphs wire pricing, atomic budget/accounting, Spring AI, notification, and metrics while preserving budget-disabled ledger-only compatibility and `ChatClientBuilderCustomizer` |
| `token-pilot-starter` | Basic implementation complete | Thin final user entrypoint that brings runtime modules together |
-| `token-pilot-sample-app` | Basic E2E complete | Direct ledger metrics and fake Spring AI lifecycle E2E cover preflight BLOCK, reservation, reconciliation, request-scope rejection, Advisor ordering, listener isolation, and enforcement streaming rejection without an API key |
+| `token-pilot-sample-app` | Basic E2E complete | Direct ledger metrics, deterministic fake-provider demo E2E, and an opt-in OpenAI provider smoke path cover the sample integration; live provider compatibility still requires an explicit API-key run |
## Current Work Focus
@@ -306,6 +306,13 @@ sample app's `DEMO_RUNBOOK.md` documents the Gradle, Prometheus/Grafana, and sce
verification flow. The provisioned dashboard uses Token Pilot-owned `tokenpilot_*`
meters and deliberately does not use legacy `ai_token_*` meters.
+OpenAI smoke profile endpoints (run with `--spring.profiles.active=openai-smoke`):
+
+- `GET /test/token-pilot/openai-smoke`: performs one real OpenAI Chat Completions call,
+ returns normalized usage and accounting evidence, and requires `OPENAI_API_KEY`.
+- `OpenAiSmokeE2ETest`: is skipped by default and only runs when both
+ `RUN_OPENAI_SMOKE=true` and `OPENAI_API_KEY` are present.
+
Test-only E2E endpoint:
- `GET /test/token-pilot/chat`: exercises the Spring AI `ChatClient` advisor path with a fake/mock provider or documented real provider setup.
@@ -369,7 +376,7 @@ The active checklist is in `docs/30_DAY_MVP_REPORT.md`; detailed long-term works
- A response model that differs from the reservation pricing snapshot is not committed at the request model price; it remains pending with `PRICING_RECONCILIATION_REQUIRED` for later settlement.
- Pricing-mismatch pending events preserve provider actual usage and response model metadata. Consumers may call the explicit `reconcileLateActual(command, responsePricingSnapshot)` API with an immutable response-model pricing snapshot; model, currency, rate, state, and duplicate-callback validation remain fail-closed.
- The repository, README, JReleaser configuration, and every published module POM use the MIT License. `verifyPublicationMetadata` guards this release contract and ensures the sample app is not published.
-- Sample app E2E uses a fake Spring AI `ChatModel`; real provider API behavior is not yet verified.
+- Sample app's default/demo E2E uses a fake Spring AI `ChatModel`; the OpenAI smoke path is opt-in and is not a CI/live compatibility guarantee until the guarded test is run with a real key.
- `token-pilot-spring-ai-starter` does not exist in the current build; never use it as an install instruction until implemented and published.
- Maven Central release consumption must be re-verified for both core and starter paths before announcing `0.1.0`.
- Preflight cost bounds must use one immutable pricing snapshot from calculation through reservation and reconciliation; resolving a mutable registry again by model/policy identifiers can mix prices from different requests.
@@ -471,6 +478,11 @@ Stage and deploy a Central release:
## Update History
+### 2026-08-27
+
+- Added the sample app's application-owned OpenAI starter dependency, isolated `openai-smoke` profile, usage/accounting evidence endpoint, and key-gated live JUnit smoke test; default and demo profiles explicitly disable provider auto-configuration so deterministic tests remain unchanged.
+- Documented the sequential demo → OpenAI smoke procedure, environment-only API key handling, model/pricing requirements, and Prometheus evidence.
+
### 2026-08-26
- Added root publication aggregation and a release-candidate verification gate
diff --git a/README.md b/README.md
index 15ad431..c1f60ed 100644
--- a/README.md
+++ b/README.md
@@ -64,6 +64,8 @@ against the reservation-time pricing snapshot.
- [Metrics reference](docs/METRICS.md) — Token Pilot-owned meters and legacy compatibility policy.
- [Sample app runbook](docs/SAMPLE_RUNBOOK.md) — local app, Prometheus, Grafana, and troubleshooting commands.
- [Deterministic demo runbook](token-pilot-sample-app/DEMO_RUNBOOK.md) — eight admission, reservation, idempotency, release, and reconciliation scenarios.
+- [English demo runbook](token-pilot-sample-app/DEMO_RUNBOOK_EN.md) — the same demo and OpenAI smoke procedure in English.
+- The same [sample app runbook](docs/SAMPLE_RUNBOOK.md) includes the opt-in OpenAI provider smoke path.
- [Release procedure](docs/RELEASE.md) — staging, signing, external consumer, and Central Portal gates.
- [30-day MVP cutline](docs/30_DAY_MVP_REPORT.md) and [post-MVP evolution plan](docs/EVOLUTION_PLAN.md).
@@ -92,6 +94,12 @@ See [QUICKSTART.md](docs/QUICKSTART.md) for the ten-minute path and
[SAMPLE_RUNBOOK.md](docs/SAMPLE_RUNBOOK.md) for endpoint and troubleshooting
details.
+The sample app's `openai-smoke` profile uses the application-selected OpenAI
+starter and exposes `/test/token-pilot/openai-smoke`. It requires
+`OPENAI_API_KEY`, makes a real provider call, and reports normalized usage plus
+the resulting Token Pilot accounting state. The guarded live test is skipped
+unless `RUN_OPENAI_SMOKE=true` is set.
+
## Micrometer metrics
When a `MeterRegistry` is available, the Spring Boot starter publishes
diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 112051f..bdd1a78 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -52,6 +52,30 @@ is `TEXT_ONLY`; media, tool messages or schemas, structured-output
augmentation, and enforcement-enabled streaming are rejected before
reservation when they cannot be represented by the supported contract.
+## Sample OpenAI smoke profile
+
+The repository sample app selects an actual provider only when the
+`openai-smoke` profile is active. The provider dependency is application-owned:
+
+```gradle
+implementation 'cloud.token-pilot:token-pilot-starter:0.0.1-SNAPSHOT'
+implementation 'org.springframework.ai:spring-ai-starter-model-openai:2.0.0'
+```
+
+The profile reads `OPENAI_API_KEY` and `OPENAI_MODEL` from the environment and
+uses a versioned catalog entry by default. Its Token Pilot pricing values are
+example rates per 1,000 tokens and should be replaced with the rates for the
+selected provider model before using the result as a financial record.
+
+The sample endpoint is:
+
+```text
+GET /test/token-pilot/openai-smoke
+```
+
+See [`docs/SAMPLE_RUNBOOK.md`](SAMPLE_RUNBOOK.md) for the demo-to-smoke
+sequence and the guarded live test command.
+
## Metrics
| Property | Default | Meaning |
diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md
index 96228dd..a1b05e3 100644
--- a/docs/QUICKSTART.md
+++ b/docs/QUICKSTART.md
@@ -132,7 +132,45 @@ curl -s http://localhost:8080/test/token-pilot/demo/reconciliation-failure | jq
The complete sample procedure is in
[`token-pilot-sample-app/DEMO_RUNBOOK.md`](../token-pilot-sample-app/DEMO_RUNBOOK.md).
-## 3. Prometheus and Grafana
+## 3. OpenAI provider smoke
+
+The sample app also includes an opt-in path that uses the real OpenAI provider.
+Stop the `demo` profile first, then set the key in the environment. The call
+can incur provider charges, so keep the prompt short and never commit the key.
+
+```bash
+export OPENAI_API_KEY='your OpenAI API key'
+export OPENAI_MODEL='gpt-4o-mini-2024-07-18'
+
+./gradlew --no-daemon :token-pilot-sample-app:bootRun \
+ --args='--spring.profiles.active=openai-smoke'
+```
+
+In another terminal:
+
+```bash
+curl -sS --get http://localhost:8080/test/token-pilot/openai-smoke \
+ --data-urlencode 'prompt=Reply with one short sentence confirming the provider is reachable.' \
+ | jq
+```
+
+The expected evidence is `status=PASS`, a reported usage source, and
+`accountingState=COMMITTED`. The default model is the versioned model already
+known by the sample catalog. If `OPENAI_MODEL` is changed, its catalog entry
+and pricing plan must also match the response model.
+
+The live test is guarded and skipped by the normal test suite:
+
+```bash
+RUN_OPENAI_SMOKE=true \
+ ./gradlew --no-daemon :token-pilot-sample-app:test \
+ --tests io.tokenpilot.sample.OpenAiSmokeE2ETest
+```
+
+Run the demo first, stop it, then start `openai-smoke`; both profiles use port
+`8080` and must not run concurrently.
+
+## 4. Prometheus and Grafana
Keep the sample app running and start the monitoring services:
diff --git a/docs/SAMPLE_RUNBOOK.md b/docs/SAMPLE_RUNBOOK.md
index b757034..56ecd80 100644
--- a/docs/SAMPLE_RUNBOOK.md
+++ b/docs/SAMPLE_RUNBOOK.md
@@ -114,7 +114,59 @@ curl -s http://localhost:8080/test/token-pilot/demo/reconciliation-unknown | jq
```
The endpoint list and the effective profile configuration are also maintained
-in [`token-pilot-sample-app/DEMO_RUNBOOK.md`](../token-pilot-sample-app/DEMO_RUNBOOK.md).
+in the [Korean demo runbook](../token-pilot-sample-app/DEMO_RUNBOOK.md) and the
+[English demo runbook](../token-pilot-sample-app/DEMO_RUNBOOK_EN.md).
+
+## OpenAI provider smoke profile
+
+Stop the demo app before starting this profile. The sample app selects the
+OpenAI provider only for `openai-smoke`; the default and `demo` profiles keep
+provider auto-configuration disabled so fake-provider tests remain deterministic.
+
+Set the API key in the environment. Do not put it in a source file or commit it.
+The default model is the versioned catalog entry
+`gpt-4o-mini-2024-07-18`; each request is a real provider call and may incur
+provider charges.
+
+```bash
+export OPENAI_API_KEY='your OpenAI API key'
+export OPENAI_MODEL='gpt-4o-mini-2024-07-18'
+
+./gradlew --no-daemon :token-pilot-sample-app:bootRun \
+ --args='--spring.profiles.active=openai-smoke'
+```
+
+In another terminal, run one short request:
+
+```bash
+curl -sS --get http://localhost:8080/test/token-pilot/openai-smoke \
+ --data-urlencode 'prompt=Reply with one short sentence confirming the provider is reachable.' \
+ | jq
+```
+
+The response is a pass when it reports `status=PASS`, a non-`UNAVAILABLE`
+`usageSource`, and `accountingState=COMMITTED`. It also returns normalized
+input/output/total tokens, the newly applied cost metric delta, and the budget
+snapshot. Inspect the rendered meters with:
+
+```bash
+curl -sS http://localhost:8080/actuator/prometheus \
+ | rg '^tokenpilot_(preflight|budget|cost|reconciliation)'
+```
+
+The opt-in JUnit smoke test uses the same endpoint and is guarded by both
+`RUN_OPENAI_SMOKE=true` and `OPENAI_API_KEY`:
+
+```bash
+RUN_OPENAI_SMOKE=true \
+ ./gradlew --no-daemon :token-pilot-sample-app:test \
+ --tests io.tokenpilot.sample.OpenAiSmokeE2ETest
+```
+
+To run both paths, execute `/test/token-pilot/demo/run` while the `demo` profile
+is active, stop that process, then restart with `openai-smoke` and call the
+smoke endpoint. They intentionally share port `8080` and should not run at the
+same time.
## Prometheus and Grafana
diff --git a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/LedgerChatClientBuilderCustomizer.java b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/LedgerChatClientBuilderCustomizer.java
index 5b17ffa..35ed942 100644
--- a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/LedgerChatClientBuilderCustomizer.java
+++ b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/LedgerChatClientBuilderCustomizer.java
@@ -5,7 +5,7 @@
import org.springframework.ai.chat.client.ChatClientBuilderCustomizer;
/**
- * ChatClient.Builder에 LedgerAdvisor를 자동으로 주입하는 커스터마이저.
+ * Customizer that automatically injects LedgerAdvisor into ChatClient.Builder.
*/
public class LedgerChatClientBuilderCustomizer implements ChatClientBuilderCustomizer {
diff --git a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/PricingPlanProperties.java b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/PricingPlanProperties.java
index a7cee32..58e5ee5 100644
--- a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/PricingPlanProperties.java
+++ b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/PricingPlanProperties.java
@@ -9,11 +9,11 @@
import java.util.Objects;
/**
- * 개별 AI 모델에 대한 가격 설정 정보.
+ * Pricing configuration for an individual AI model.
*
- * @param modelId AI 모델 식별자 (예: gpt-4o, Claude-3-5-sonnet). 이 값은 ChatResponse의 model_id와 매칭되어야 합니다.
- * @param rates 토큰 타입별 1,000(1K) 토큰당 가격 설정. (PROMPT, COMPLETION 등)
- * @param currency 가격 정산에 사용할 통화 단위 (기본값: USD).
+ * @param modelId AI model identifier, such as gpt-4o or Claude-3-5-sonnet. This value must match the ChatResponse model_id.
+ * @param rates per-1,000 (1K) token price by token type, such as PROMPT or COMPLETION
+ * @param currency currency used for cost settlement, defaulting to USD
*/
public record PricingPlanProperties(
String modelId,
diff --git a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotAutoConfiguration.java b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotAutoConfiguration.java
index 6225c79..be03728 100644
--- a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotAutoConfiguration.java
+++ b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotAutoConfiguration.java
@@ -20,7 +20,7 @@
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
-/** Token Pilot의 framework-independent core 자동 설정입니다. */
+/** Auto-configuration for the Token Pilot framework-independent core. */
@AutoConfiguration
@ConditionalOnProperty(
prefix = "token-pilot",
diff --git a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotBudgetAutoConfiguration.java b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotBudgetAutoConfiguration.java
index 3b914d7..80ff896 100644
--- a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotBudgetAutoConfiguration.java
+++ b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotBudgetAutoConfiguration.java
@@ -15,7 +15,7 @@
import java.time.Clock;
-/** Optional budget module의 bean graph를 격리하는 자동 설정입니다. */
+/** Auto-configuration isolating the optional budget module bean graph. */
@AutoConfiguration(after = {
TokenPilotAutoConfiguration.class,
TokenPilotBudgetMetricsAutoConfiguration.class
diff --git a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotBudgetMetricsAutoConfiguration.java b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotBudgetMetricsAutoConfiguration.java
index 28a5726..3ceaecd 100644
--- a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotBudgetMetricsAutoConfiguration.java
+++ b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotBudgetMetricsAutoConfiguration.java
@@ -11,7 +11,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
-/** Atomic reservation/accounting metrics의 조건부 자동 설정입니다. */
+/** Conditional auto-configuration for atomic reservation/accounting metrics. */
@AutoConfiguration(after = TokenPilotCoreMetricsAutoConfiguration.class)
@ConditionalOnClass({
MeterRegistry.class,
diff --git a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotBudgetPolicyFactory.java b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotBudgetPolicyFactory.java
index 0e63186..09445fa 100644
--- a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotBudgetPolicyFactory.java
+++ b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotBudgetPolicyFactory.java
@@ -8,10 +8,10 @@
import java.util.Objects;
/**
- * Token Pilot 설정을 optional budget 정책으로 변환하는 공개 진입점입니다.
+ * Public entry point for converting Token Pilot settings into an optional budget policy.
*
- *
budget 타입을 {@link TokenPilotProperties}에서 분리해 budget 모듈이 없는
- * core-only 자동 설정도 class loading에 실패하지 않도록 합니다.
+ * Budget types are kept separate from {@link TokenPilotProperties} so
+ * core-only auto-configuration can load when the budget module is absent.
*/
public final class TokenPilotBudgetPolicyFactory {
diff --git a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotCoreMetricsAutoConfiguration.java b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotCoreMetricsAutoConfiguration.java
index 5d09d94..69933d4 100644
--- a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotCoreMetricsAutoConfiguration.java
+++ b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotCoreMetricsAutoConfiguration.java
@@ -13,7 +13,7 @@
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
-/** Core control events와 legacy ledger metrics의 조건부 자동 설정입니다. */
+/** Conditional auto-configuration for core control events and legacy ledger metrics. */
@AutoConfiguration(after = TokenPilotAutoConfiguration.class, afterName = {
"org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration",
"org.springframework.boot.micrometer.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration"
diff --git a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotNotificationAutoConfiguration.java b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotNotificationAutoConfiguration.java
index 826ceaf..85a7f1b 100644
--- a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotNotificationAutoConfiguration.java
+++ b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotNotificationAutoConfiguration.java
@@ -16,7 +16,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
-/** Optional notification module의 bean graph를 격리하는 자동 설정입니다. */
+/** Auto-configuration isolating the optional notification module bean graph. */
@AutoConfiguration(after = {
TokenPilotAutoConfiguration.class,
TokenPilotNotificationMetricsAutoConfiguration.class
diff --git a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotNotificationMetricsAutoConfiguration.java b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotNotificationMetricsAutoConfiguration.java
index 0b39766..b0011b9 100644
--- a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotNotificationMetricsAutoConfiguration.java
+++ b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotNotificationMetricsAutoConfiguration.java
@@ -11,7 +11,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
-/** Budget notification lifecycle metrics의 조건부 자동 설정입니다. */
+/** Conditional auto-configuration for budget notification lifecycle metrics. */
@AutoConfiguration(after = TokenPilotCoreMetricsAutoConfiguration.class)
@ConditionalOnClass({
MeterRegistry.class,
diff --git a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotProperties.java b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotProperties.java
index 0d512f9..a29d9cd 100644
--- a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotProperties.java
+++ b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotProperties.java
@@ -10,7 +10,7 @@
import java.util.Set;
/**
- * Token Pilot의 설정을 담당하는 프로퍼티 클래스.
+ * Configuration properties for Token Pilot.
*/
@ConfigurationProperties(prefix = "token-pilot")
public class TokenPilotProperties {
@@ -186,7 +186,7 @@ public void setTargetTagKey(String targetTagKey) {
}
/**
- * 대상 tag가 없을 때 사용할 명시적 fallback입니다. 미설정 시 평가는 fail-closed 됩니다.
+ * Explicit fallback used when the target tag is absent. Evaluation fails closed when it is unset.
*/
public String getFallbackTargetId() {
return fallbackTargetId;
@@ -205,7 +205,7 @@ public void setCurrency(String currency) {
}
/**
- * 월별 budget window 경계를 계산하는 IANA ZoneId입니다. 기본값은 UTC입니다.
+ * IANA ZoneId used to calculate monthly budget-window boundaries. The default is UTC.
*/
public String getZoneId() {
return zoneId;
diff --git a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotSpringAiAutoConfiguration.java b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotSpringAiAutoConfiguration.java
index 2f49df1..f077b3a 100644
--- a/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotSpringAiAutoConfiguration.java
+++ b/token-pilot-autoconfigure/src/main/java/io/tokenpilot/autoconfigure/TokenPilotSpringAiAutoConfiguration.java
@@ -25,7 +25,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
-/** Optional Spring AI adapter의 bean graph를 격리하는 자동 설정입니다. */
+/** Auto-configuration isolating the optional Spring AI adapter bean graph. */
@AutoConfiguration(after = {
TokenPilotAutoConfiguration.class,
TokenPilotBudgetAutoConfiguration.class,
diff --git a/token-pilot-autoconfigure/src/test/java/io/tokenpilot/autoconfigure/TokenPilotAutoConfigurationTest.java b/token-pilot-autoconfigure/src/test/java/io/tokenpilot/autoconfigure/TokenPilotAutoConfigurationTest.java
index c3eb8f6..8a85f86 100644
--- a/token-pilot-autoconfigure/src/test/java/io/tokenpilot/autoconfigure/TokenPilotAutoConfigurationTest.java
+++ b/token-pilot-autoconfigure/src/test/java/io/tokenpilot/autoconfigure/TokenPilotAutoConfigurationTest.java
@@ -1104,7 +1104,7 @@ int evaluations() {
}
}
- // 테스트용 no-op handler - 실제 알림 전송 없이 빈 등록 여부만 검증
+ // Test-only no-op handler; verifies bean registration without sending notifications.
@Configuration(proxyBeanMethods = false)
static class FakeBudgetNotificationHandlerConfiguration {
@Bean
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/AccountingTransitionStatus.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/AccountingTransitionStatus.java
index 00df53c..8a0de70 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/AccountingTransitionStatus.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/AccountingTransitionStatus.java
@@ -1,35 +1,36 @@
package io.tokenpilot.budget;
/**
- * 예약 생성 이후 회계 상태 변경 명령의 결과입니다.
+ * Result of an accounting state-change command after reservation creation.
*
- * {@link #APPLIED}만 새로운 회계 변경이 적용되었음을 뜻합니다. 나머지 결과는 기존 상태와
- * 금액을 바꾸지 않았음을 뜻하므로 호출자는 예외 메시지나 저장소 내부 구현에 의존하지 않고
- * 재시도, 충돌 처리, 입력 보정을 결정할 수 있습니다.
+ * Only {@link #APPLIED} means that a new accounting change was applied. All
+ * other results leave the existing state and amounts unchanged, allowing callers
+ * to decide on retries, conflict handling, or input correction without depending
+ * on exception messages or store internals.
*/
public enum AccountingTransitionStatus {
- /** 요청한 회계 변경이 새로 적용되었습니다. */
+ /** The requested accounting change was newly applied. */
APPLIED,
- /** 같은 회계 명령과 값이 이미 적용되어 기존 결과가 재사용되었습니다. */
+ /** The same accounting command and values were already applied, so the existing result was reused. */
REUSED,
- /** 기존에 적용된 종료 명령 또는 값이 새 요청과 충돌합니다. */
+ /** An already applied terminal command or value conflicts with the new request. */
CONFLICT,
- /** 대상 예약을 찾을 수 없습니다. */
+ /** The target reservation was not found. */
NOT_FOUND,
- /** 명령 금액의 통화가 예약 통화와 다릅니다. */
+ /** The command amount uses a different currency from the reservation. */
CURRENCY_MISMATCH,
- /** 명령 인자가 계약을 만족하지 않습니다. */
+ /** The command arguments do not satisfy the contract. */
INVALID_ARGUMENT,
- /** 현재 예약 상태에서는 요청한 전이가 허용되지 않습니다. */
+ /** The requested transition is not allowed from the current reservation state. */
NOT_ALLOWED;
- /** 새로운 회계 변경이 적용되었는지 반환합니다. */
+ /** Returns whether a new accounting change was applied. */
public boolean isApplied() {
return this == APPLIED;
}
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ActualUsageCommand.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ActualUsageCommand.java
index c7bb159..804fe30 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ActualUsageCommand.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ActualUsageCommand.java
@@ -6,7 +6,7 @@
import java.util.Objects;
/**
- * provider가 보고한 actual usage를 하나의 예약에 정산하기 위한 명령입니다.
+ * Command for settling provider-reported actual usage against one reservation.
*/
public record ActualUsageCommand(
String requestId,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/AtomicBudgetStateStore.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/AtomicBudgetStateStore.java
index f8edc5a..286c80e 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/AtomicBudgetStateStore.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/AtomicBudgetStateStore.java
@@ -1,11 +1,12 @@
package io.tokenpilot.budget;
/**
- * 예약 승인과 후속 회계 전이를 같은 원자적 상태에서 처리하는 budget store입니다.
+ * Budget store that handles reservation admission and subsequent accounting
+ * transitions in the same atomic state.
*
- * Spring 같은 DI container는 이 합성 계약을 단일 bean으로 노출해
- * {@link BudgetStateStore}와 {@link ReservationAccounting}이 서로 다른 저장소로
- * 연결되는 것을 방지할 수 있습니다.
+ * A dependency-injection container such as Spring can expose this composite
+ * contract as one bean to prevent {@link BudgetStateStore} and
+ * {@link ReservationAccounting} from being connected to different stores.
*/
public interface AtomicBudgetStateStore
extends BudgetStateStore, ReservationAccounting {
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetDecision.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetDecision.java
index ab113a4..a583f6c 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetDecision.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetDecision.java
@@ -5,21 +5,22 @@
import java.util.Objects;
/**
- * 부수 효과 없는 예산 평가 결과입니다.
+ * Side-effect-free budget evaluation result.
*
- * @param key 평가 시점에 확정된 예산 bucket 식별자
- * @param evaluationType 후보 비용 포함 여부
- * @param state ALLOW / WARN / BLOCK / CURRENCY_MISMATCH 상태
- * @param threshold 예상 사용량이 도달한 예산 임계치
- * @param reason 상태 설명
- * @param committedUsage 현재 확정된 누적 사용량
- * @param projectedUsage 후보 비용을 포함한 예상 사용량. 상태 조회이거나 통화가 일치하지 않으면
- * {@code committedUsage}와 같습니다.
- * @param limit 통화를 포함한 총 예산
+ * @param key budget bucket identifier fixed at evaluation time
+ * @param evaluationType whether candidate cost is included
+ * @param state ALLOW / WARN / BLOCK / CURRENCY_MISMATCH state
+ * @param threshold budget threshold reached by projected usage
+ * @param reason state explanation
+ * @param committedUsage currently committed cumulative usage
+ * @param projectedUsage projected usage including candidate cost. It equals
+ * {@code committedUsage} for status queries or currency mismatches.
+ * @param limit total budget including its currency
*
- * Migration note: {@link EvaluationType#STATUS} 결과는 현재 상태 조회용이며
- * provider 호출 허가의 근거가 아닙니다. 기존의 모호한 {@code currentUsage} 대신 확정
- * 사용량은 {@code committedUsage}, 후보 포함 사용량은 {@code projectedUsage}를 사용합니다.
+ *
Migration note: {@link EvaluationType#STATUS} results are
+ * for current-state queries and are not grounds for authorizing provider calls.
+ * Use {@code committedUsage} for committed usage and {@code projectedUsage} for
+ * usage including a candidate instead of the former ambiguous {@code currentUsage}.
*/
public record BudgetDecision(
BudgetKey key,
@@ -57,7 +58,7 @@ public record BudgetDecision(
}
/**
- * 후보 비용을 포함해 provider admission을 판단한 결과인지 반환합니다.
+ * Returns whether this result evaluates provider admission with candidate cost included.
*/
public boolean isAdmissionDecision() {
return evaluationType == EvaluationType.ADMISSION;
@@ -65,12 +66,12 @@ public boolean isAdmissionDecision() {
public enum EvaluationType {
/**
- * 후보 비용 없는 조회 전용 결과입니다. Provider 호출 허가로 사용할 수 없습니다.
+ * Query-only result without candidate cost. It cannot authorize provider invocation.
*/
STATUS,
/**
- * 후보 비용을 포함한 admission 판단 결과입니다.
+ * Admission decision that includes candidate cost.
*/
ADMISSION
}
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetEvaluator.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetEvaluator.java
index 76300b5..72dda36 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetEvaluator.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetEvaluator.java
@@ -6,29 +6,30 @@
/**
- * 부수 효과 없이 예산 상태를 판단하는 인터페이스입니다.
+ * Interface for determining budget state without side effects.
*
- * 구현체는 판단 결과를 구조화된 {@link BudgetDecision}으로 반환하며 provider 호출을 직접
- * 차단하거나 알림/metric listener를 호출하지 않습니다. Provider 경계는 반환된 decision을
- * 별도로 집행해야 합니다.
+ * Implementations return a structured {@link BudgetDecision}; they do not block
+ * provider invocation or call notification/metric listeners directly. The
+ * provider boundary must enforce the returned decision separately.
*/
public interface BudgetEvaluator {
/**
- * 현재 확정 사용량만 조회합니다.
+ * Reads only the currently committed usage.
*
- * @return {@link BudgetDecision.EvaluationType#STATUS}인 조회 전용 결과. 후보 비용이 없으므로
- * provider 호출 허가의 근거로 사용할 수 없습니다.
+ * @return query-only {@link BudgetDecision.EvaluationType#STATUS} result. It
+ * cannot authorize provider invocation because it has no candidate cost.
*/
BudgetDecision evaluate(Map tags);
/**
- * 후보 요청의 통화가 포함된 안전 상한 비용을 더해 admission 상태를 판단합니다.
+ * Determines admission state after adding the candidate request's
+ * currency-aware safe upper-bound cost.
*
- * {@code projectedUsage >= limit}이면 BLOCK입니다. BLOCK과 CURRENCY_MISMATCH도 예외를
- * 던지지 않고 decision으로 반환합니다.
+ * {@code projectedUsage >= limit} produces BLOCK. BLOCK and CURRENCY_MISMATCH
+ * are returned as decisions rather than thrown as exceptions.
*
- * @return {@link BudgetDecision.EvaluationType#ADMISSION}인 판단 결과
+ * @return {@link BudgetDecision.EvaluationType#ADMISSION} decision result
*/
BudgetDecision evaluate(
Map tags,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetKey.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetKey.java
index 4c1ecd5..e2aa522 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetKey.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetKey.java
@@ -3,12 +3,12 @@
import java.util.Objects;
/**
- * 월별 예산 bucket을 식별하는 불변 key입니다.
+ * Immutable key identifying a monthly budget bucket.
*
- * @param policyId 예산 정책 식별자
- * @param targetType 예산 대상 종류
- * @param targetId 예산 대상 식별자
- * @param window 월별 예산 기간
+ * @param policyId budget policy identifier
+ * @param targetType budget target type
+ * @param targetId budget target identifier
+ * @param window monthly budget period
*/
public record BudgetKey(
String policyId,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetPolicy.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetPolicy.java
index cb72e76..cf855a6 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetPolicy.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetPolicy.java
@@ -6,15 +6,15 @@
import java.util.Objects;
/**
- * 월별 예산 key 생성과 금액 검증에 사용하는 정책 snapshot입니다.
- * 대상 tag가 없고 {@code fallbackTargetId}도 설정되지 않으면 평가는 fail-closed 됩니다.
+ * Policy snapshot used to create monthly budget keys and validate amounts.
+ * Evaluation fails closed when the target tag and {@code fallbackTargetId} are both absent.
*
- * @param id 정책 식별자
- * @param targetType 예산 대상 종류
- * @param targetTagKey 대상 식별자를 읽을 tag key
- * @param fallbackTargetId 누락된 대상에 사용할 명시적 fallback, 미설정 시 {@code null}
- * @param monthlyLimit 통화를 포함한 월별 한도
- * @param zoneId 월 경계를 계산할 시간대
+ * @param id policy identifier
+ * @param targetType budget target type
+ * @param targetTagKey tag key from which to read the target identifier
+ * @param fallbackTargetId explicit fallback for a missing target, or {@code null} when unset
+ * @param monthlyLimit monthly limit including its currency
+ * @param zoneId time zone used to calculate month boundaries
*/
public record BudgetPolicy(
String id,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetReservation.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetReservation.java
index 1657acc..3d552b9 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetReservation.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetReservation.java
@@ -8,7 +8,7 @@
import java.util.Optional;
/**
- * 예산 bucket에 생성된 immutable 예약 snapshot입니다.
+ * Immutable reservation snapshot created in a budget bucket.
*/
public record BudgetReservation(
ReservationId id,
@@ -74,8 +74,8 @@ public record BudgetReservation(
}
/**
- * @deprecated request ID와 idempotency key를 같은 값으로 사용하는 호환 생성자입니다.
- * 신규 예약은 {@link #reserved(ReservationId, BudgetReservationRequest, Instant)}로 생성하세요.
+ * @deprecated Compatibility constructor that uses the same value for request ID and idempotency key.
+ * Create new reservations with {@link #reserved(ReservationId, BudgetReservationRequest, Instant)}.
*/
@Deprecated(since = "0.1.0", forRemoval = false)
public BudgetReservation(
@@ -131,11 +131,13 @@ public static BudgetReservation reserved(
}
/**
- * 같은 idempotency key에 전달된 요청이 기존 예약과 같은 비용 책임을 뜻하는지 비교합니다.
+ * Compares whether a request using the same idempotency key represents the
+ * same billing liability as the existing reservation.
*
- * {@link PricingSnapshot#checkedAt()}은 가격 조회 시점의 관측 metadata이므로 비교에서
- * 제외합니다. 반면 model, pricing policy, catalog version, currency, rate와 token estimate를
- * 포함해 실제 가격 또는 예약 책임을 바꾸는 값은 모두 일치해야 합니다.
+ * {@link PricingSnapshot#checkedAt()} is observational metadata from the
+ * pricing lookup time and is excluded from comparison. Values that could
+ * change the actual price or reservation liability—including model, pricing
+ * policy, catalog version, currency, rate, and token estimate—must all match.
*/
public boolean matches(BudgetReservationRequest request) {
return key.equals(request.key())
@@ -169,7 +171,7 @@ private static boolean hasSamePricingTerms(
);
}
- /** 이 예약이 지정한 provider 요청에 속하는지 확인합니다. */
+ /** Returns whether this reservation belongs to the specified provider request. */
public boolean belongsTo(String candidateRequestId) {
return requestId.equals(candidateRequestId);
}
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetReservationRequest.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetReservationRequest.java
index bd7a433..3cd6aa5 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetReservationRequest.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetReservationRequest.java
@@ -7,11 +7,12 @@
import java.util.Optional;
/**
- * 호출 전 안전 상한 비용을 예산 bucket에 예약하기 위한 immutable 요청입니다.
+ * Immutable request for reserving a pre-call safe upper-bound cost in a budget bucket.
*
- * {@code limit}은 bucket 생성 시 고정되는 정책 snapshot이고,
- * {@code safeUpperBoundCost}는 예약할 실제 금액입니다. 모델과 가격 식별자는
- * 같은 idempotency key의 요청 payload가 바뀌었는지 검증하는 fingerprint로 사용됩니다.
+ * {@code limit} is the policy snapshot fixed when the bucket is created, and
+ * {@code safeUpperBoundCost} is the amount to reserve. Model and pricing
+ * identifiers form part of the fingerprint used to detect a changed request
+ * payload under the same idempotency key.
*/
public record BudgetReservationRequest(
BudgetKey key,
@@ -68,8 +69,8 @@ public record BudgetReservationRequest(
}
/**
- * @deprecated request ID와 idempotency key를 같은 값으로 사용하는 호환 생성자입니다.
- * 신규 호출은 두 값을 명시하는 canonical 생성자를 사용하세요.
+ * @deprecated Compatibility constructor that uses the same value for request ID and idempotency key.
+ * New calls should use the canonical constructor with both values explicit.
*/
@Deprecated(since = "0.1.0", forRemoval = false)
public BudgetReservationRequest(
@@ -96,8 +97,8 @@ public BudgetReservationRequest(
}
/**
- * @deprecated request ID와 idempotency key를 같은 값으로 사용하는 호환 생성자입니다.
- * 신규 호출은 두 값을 명시하는 canonical 생성자를 사용하세요.
+ * @deprecated Compatibility constructor that uses the same value for request ID and idempotency key.
+ * New calls should use the canonical constructor with both values explicit.
*/
@Deprecated(since = "0.1.0", forRemoval = false)
public BudgetReservationRequest(
@@ -125,7 +126,7 @@ public BudgetReservationRequest(
}
/**
- * @deprecated request ID와 idempotency key를 같은 값으로 사용하는 호환 생성자입니다.
+ * @deprecated Compatibility constructor that uses the same value for request ID and idempotency key.
*/
@Deprecated(since = "0.1.0", forRemoval = false)
public BudgetReservationRequest(
@@ -149,7 +150,7 @@ public BudgetReservationRequest(
}
/**
- * @deprecated request ID와 idempotency key를 같은 값으로 사용하는 호환 생성자입니다.
+ * @deprecated Compatibility constructor that uses the same value for request ID and idempotency key.
*/
@Deprecated(since = "0.1.0", forRemoval = false)
public BudgetReservationRequest(
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetReservationResult.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetReservationResult.java
index 2d4f485..15ddc60 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetReservationResult.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetReservationResult.java
@@ -3,7 +3,7 @@
import java.util.Objects;
/**
- * 원자적 예산 예약 시도의 결과입니다.
+ * Result of an atomic budget reservation attempt.
*/
public record BudgetReservationResult(
ReservationStatus status,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetSnapshot.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetSnapshot.java
index 9897c01..efbb157 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetSnapshot.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetSnapshot.java
@@ -7,7 +7,7 @@
import java.util.Set;
/**
- * 예산 bucket의 framework-independent 읽기 snapshot입니다.
+ * Framework-independent read snapshot of a budget bucket.
*/
public record BudgetSnapshot(
BudgetKey key,
@@ -45,7 +45,7 @@ public static BudgetSnapshot empty(BudgetKey key, Cost limit) {
}
/**
- * 예약과 미해결 정산 부채를 포함한 admission 기준 사용량입니다.
+ * Usage used for admission, including reservations and unresolved reconciliation liability.
*/
public Cost effectiveUsage() {
return committedCost
@@ -54,7 +54,7 @@ public Cost effectiveUsage() {
}
/**
- * 사용량을 반영한 남은 예산입니다. 초과 상태에서는 0입니다.
+ * Remaining budget after usage. Returns zero when the budget is exceeded.
*/
public Cost remaining() {
Cost effectiveUsage = effectiveUsage();
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetState.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetState.java
index 21368c9..784e97c 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetState.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetState.java
@@ -1,12 +1,12 @@
package io.tokenpilot.budget;
/**
- * 예산 평가 결과 상태를 나타냅니다.
+ * Represents the result state of a budget evaluation.
*
- * ALLOW : 호출 허용
- * WARN : 예산 경고
- * BLOCK : 호출 차단
- * CURRENCY_MISMATCH : 예산과 비용 통화 불일치
+ * ALLOW : call allowed
+ * WARN : budget warning
+ * BLOCK : call blocked
+ * CURRENCY_MISMATCH : budget and cost currencies differ
*/
public enum BudgetState {
ALLOW,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetStateStore.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetStateStore.java
index cbc545e..4ffb101 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetStateStore.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetStateStore.java
@@ -3,35 +3,39 @@
import io.tokenpilot.core.domain.Cost;
/**
- * resolved {@link BudgetKey}별 누적 비용과 limit/currency snapshot을 관리합니다.
+ * Manages accumulated cost and limit/currency snapshots for each resolved
+ * {@link BudgetKey}.
*/
public interface BudgetStateStore {
/**
- * 기존 확정 비용 조회 API입니다. 예약 금액은 포함하지 않습니다.
+ * Legacy API for reading committed cost. Reserved amounts are not included.
*
- *
새 provider admission 경계는 {@link #snapshot(BudgetKey, Cost)} 또는
- * {@link #checkAndReserve(BudgetReservationRequest)}를 사용해야 합니다.
+ * New provider admission boundaries must use
+ * {@link #snapshot(BudgetKey, Cost)} or
+ * {@link #checkAndReserve(BudgetReservationRequest)}.
*/
Cost getAccumulatedCost(BudgetKey key, Cost limit);
/**
- * 기존 확정 비용 누적 API입니다. 예약 lifecycle을 변경하지 않습니다.
+ * Legacy API for adding committed cost. It does not change the reservation lifecycle.
*/
void addCost(BudgetKey key, Cost limit, Cost amount);
/**
- * 조회와 안전 상한 예약을 하나의 원자적 연산으로 수행합니다.
+ * Performs the read and safe upper-bound reservation as one atomic operation.
*
- * 기존 구현체와의 source 호환성을 위해 기본 구현은 지원하지 않음을 명시적으로
- * 반환합니다. 예약을 지원하는 구현체는 이 메서드 또는 요청 overload를 구현해야 합니다.
+ * For source compatibility with existing implementations, the default
+ * implementation explicitly returns that the operation is unsupported.
+ * Implementations that support reservations must implement this method or the
+ * request overload.
*
- * @param key 예산 bucket 식별자
- * @param limit bucket에 고정할 예산 limit snapshot
- * @param safeUpperBoundCost 예약할 보수적 비용 상한
- * @param idempotencyKey 중복 요청 식별자
- * @return 생성·재사용·차단·충돌·통화 불일치 결과
- * @deprecated request ID와 idempotency key를 분리하는 overload 또는 요청 객체를 사용하세요.
+ * @param key budget bucket identifier
+ * @param limit budget limit snapshot fixed for the bucket
+ * @param safeUpperBoundCost conservative cost bound to reserve
+ * @param idempotencyKey duplicate-request identifier
+ * @return created, reused, blocked, conflict, or currency-mismatch result
+ * @deprecated Use an overload or request object that separates request ID and idempotency key.
*/
@Deprecated(since = "0.1.0", forRemoval = false)
default BudgetReservationResult checkAndReserve(
@@ -49,9 +53,9 @@ default BudgetReservationResult checkAndReserve(
}
/**
- * typed idempotency key를 사용하는 원자적 예약 overload입니다.
+ * Atomic reservation overload using a typed idempotency key.
*
- * @deprecated request ID와 idempotency key를 분리하는 overload 또는 요청 객체를 사용하세요.
+ * @deprecated Use an overload or request object that separates request ID and idempotency key.
*/
@Deprecated(since = "0.1.0", forRemoval = false)
default BudgetReservationResult checkAndReserve(
@@ -72,7 +76,8 @@ default BudgetReservationResult checkAndReserve(
}
/**
- * 요청 상관관계와 중복 방지 식별자를 분리하는 원자적 예약 overload입니다.
+ * Atomic reservation overload separating request correlation from the
+ * duplicate-prevention identifier.
*/
default BudgetReservationResult checkAndReserve(
BudgetKey key,
@@ -95,7 +100,7 @@ default BudgetReservationResult checkAndReserve(
}
/**
- * 모델·가격 snapshot metadata를 포함한 원자적 예약 요청입니다.
+ * Atomic reservation request including model and pricing snapshot metadata.
*/
default BudgetReservationResult checkAndReserve(BudgetReservationRequest request) {
throw new UnsupportedOperationException(
@@ -104,8 +109,8 @@ default BudgetReservationResult checkAndReserve(BudgetReservationRequest request
}
/**
- * 예약과 미해결 정산 부채를 포함한 읽기 snapshot입니다.
- * 기존 저장소는 확정 비용만 포함한 호환 snapshot을 반환할 수 있습니다.
+ * Read snapshot including reservations and unresolved reconciliation liability.
+ * Legacy stores may return a compatible snapshot containing committed cost only.
*/
default BudgetSnapshot snapshot(BudgetKey key, Cost limit) {
Cost committedCost = getAccumulatedCost(key, limit);
@@ -120,7 +125,7 @@ default BudgetSnapshot snapshot(BudgetKey key, Cost limit) {
}
/**
- * {@link #snapshot(BudgetKey, Cost)}의 명시적인 query alias입니다.
+ * Explicit query alias for {@link #snapshot(BudgetKey, Cost)}.
*/
default BudgetSnapshot getSnapshot(BudgetKey key, Cost limit) {
return snapshot(key, limit);
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetThreshold.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetThreshold.java
index 82976d6..f274ce7 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetThreshold.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetThreshold.java
@@ -1,19 +1,19 @@
package io.tokenpilot.budget;
/**
- * 예산 사용률에 따른 임계치 정의
+ * Threshold definitions based on budget utilization.
*
- * NONE : 임계치 미도달
- * HALF : 50% 도달
- * WARNING : 80% 도달
- * EXCEEDED : 100% 초과
+ * NONE : threshold not reached
+ * HALF : 50% reached
+ * WARNING : 80% reached
+ * EXCEEDED : 100% exceeded
*
- * notification 모듈에서 알림 판단 기준으로 사용됨
+ * Used by the notification module as the alerting criterion.
*/
public enum BudgetThreshold {
- NONE, // 아직 알림 필요 없음
+ NONE, // No notification is needed yet.
HALF, // 50%
WARNING, // 80%
EXCEEDED // 100%
-}
\ No newline at end of file
+}
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetWindow.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetWindow.java
index a5d13b3..0cb6853 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetWindow.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/BudgetWindow.java
@@ -6,9 +6,9 @@
import java.util.Objects;
/**
- * 설정된 시간대 기준의 월별 예산 기간입니다.
+ * Monthly budget period in the configured time zone.
*
- * @param value 예산이 적용되는 연월
+ * @param value year and month to which the budget applies
*/
public record BudgetWindow(YearMonth value) {
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/IdempotencyKey.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/IdempotencyKey.java
index 9640c1b..5154e5b 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/IdempotencyKey.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/IdempotencyKey.java
@@ -1,7 +1,7 @@
package io.tokenpilot.budget;
/**
- * 같은 요청의 중복 예약을 식별하는 불변 키입니다.
+ * Immutable key identifying duplicate reservations for the same request.
*/
public record IdempotencyKey(String value) {
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccounting.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccounting.java
index 9840e52..8076ba1 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccounting.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccounting.java
@@ -6,16 +6,16 @@
import java.util.Objects;
/**
- * 예약의 회계 상태와 금액을 변경하는 단일 진입점입니다.
+ * Single entry point for changing a reservation's accounting state and amounts.
*
*
- * 예약 회계 명령의 허용 전이와 금액 이동
+ * Allowed reservation accounting transitions and amount movements
*
*
- * | 명령 |
- * 허용 상태 |
- * 결과 상태 |
- * 금액 이동 |
+ * Command |
+ * Allowed state |
+ * Resulting state |
+ * Amount movement |
*
*
*
@@ -23,7 +23,7 @@
* {@link #markInFlight(ReservationId)} |
* {@link ReservationState#RESERVED} |
* {@link ReservationState#IN_FLIGHT} |
- * 없음 |
+ * None |
*
*
* | {@code release(CANCELLED_BEFORE_DISPATCH)} |
@@ -64,22 +64,26 @@
*
*
*
- * 표의 전이가 새로 적용되면 {@link AccountingTransitionStatus#APPLIED}입니다.
- * 동일한 종료 명령과 값 또는 정산 대기 명령의 재호출은 상태와 금액을 유지하고
- * {@link AccountingTransitionStatus#REUSED}, 다른 actual 또는 상충하는 종료 명령은
- * {@link AccountingTransitionStatus#CONFLICT}입니다. 아직 종료 명령이 적용되지 않았지만
- * 현재 상태가 표의 허용 상태가 아니면 {@link AccountingTransitionStatus#NOT_ALLOWED}입니다.
+ * A transition newly applied from the table returns
+ * {@link AccountingTransitionStatus#APPLIED}. Repeating the same terminal
+ * command and values, or a reconciliation-pending command, preserves state and
+ * amounts and returns {@link AccountingTransitionStatus#REUSED}. Different
+ * actual usage or a conflicting terminal command returns
+ * {@link AccountingTransitionStatus#CONFLICT}. If no terminal command has been
+ * applied but the current state is not allowed by the table,
+ * {@link AccountingTransitionStatus#NOT_ALLOWED} is returned.
*
- * 존재하지 않는 예약과 명령에 허용되지 않은 reason은 상태를 변경하기 전에
- * {@link IllegalArgumentException}으로 거부합니다. 모든 상태와 금액 변경은 같은 budget
- * bucket의 임계 구역 안에서 함께 적용됩니다.
+ * Missing reservations and reasons not allowed for a command are rejected
+ * with {@link IllegalArgumentException} before state changes. Every state and
+ * amount change is applied together inside the critical section for the same
+ * budget bucket.
*/
public interface ReservationAccounting {
- /** 예약을 사용한 provider 호출 시작을 기록합니다. */
+ /** Records the start of provider invocation using the reservation. */
ReservationTransition markInFlight(ReservationId reservationId);
- /** provider 호출 전에 사용하지 않은 예약을 해제합니다. */
+ /** Releases an unused reservation before provider invocation. */
default ReservationTransition releaseBeforeDispatch(ReservationId reservationId) {
return release(
reservationId,
@@ -87,7 +91,7 @@ default ReservationTransition releaseBeforeDispatch(ReservationId reservationId)
);
}
- /** provider가 미과금을 확인한 진행 중 예약을 해제합니다. */
+ /** Releases an in-flight reservation after the provider confirms no charge. */
default ReservationTransition releaseConfirmedUnbilled(ReservationId reservationId) {
return release(
reservationId,
@@ -101,22 +105,23 @@ ReservationTransition release(
);
/**
- * pricing snapshot과 token estimate가 없던 호환 예약을 caller가 계산한 actual 비용으로 확정합니다.
+ * Commits a compatible reservation without a pricing snapshot or token
+ * estimate using actual cost calculated by the caller.
*
- * 이 호환 경로는 token/model correlation을 포함한
- * {@link #commit(ActualUsageCommand)} 결과와 회계 이벤트를 만들 수 없으므로,
- * 신규 예약에는 usage 기반 API를 사용해야 합니다.
+ * This compatibility path cannot create the token/model correlation and
+ * accounting event produced by {@link #commit(ActualUsageCommand)}, so new
+ * reservations must use the usage-based API.
*/
ReservationTransition commitCost(ReservationId reservationId, Cost actualCost);
/**
- * provider actual usage를 예약 시점 가격으로 계산하여 확정합니다.
- * 응답 모델이 예약 pricing snapshot과 다르면 다른 모델 가격을 request 가격으로
- * 확정하지 않도록 거부합니다.
+ * Calculates provider actual usage at the reservation-time price and commits it.
+ * Rejects a different response model so a different model's price is not
+ * committed under the request price.
*/
ReservationReconciliation commit(ActualUsageCommand command);
- /** actual을 확보하지 못한 예약을 정산 대기로 전환합니다. */
+ /** Moves a reservation without actual usage into reconciliation pending. */
default ReservationTransition markReconciliationRequired(
ReservationId reservationId
) {
@@ -132,8 +137,9 @@ ReservationTransition markReconciliationRequired(
);
/**
- * response model과 actual usage를 보존하면서 pricing reconciliation 대기로 이동합니다.
- * 기존 구현은 command metadata를 보존하지 않는 호환 동작으로 위임할 수 있습니다.
+ * Moves a reservation into pricing reconciliation pending while preserving
+ * the response model and actual usage. Existing implementations may delegate
+ * to compatibility behavior that does not retain command metadata.
*/
default ReservationTransition markReconciliationRequired(
ActualUsageCommand command,
@@ -144,14 +150,17 @@ default ReservationTransition markReconciliationRequired(
}
/**
- * 늦게 도착한 provider actual usage를 예약 시점 가격으로 계산하여 확정합니다.
- * 응답 모델은 예약 pricing snapshot의 모델과 같아야 합니다.
+ * Calculates and commits late-arriving provider actual usage at the
+ * reservation-time price. The response model must match the model in the
+ * reservation pricing snapshot.
*/
ReservationReconciliation reconcileLateActual(ActualUsageCommand command);
/**
- * pending actual에 response model의 명시적 immutable pricing snapshot을 적용합니다.
- * 기본 구현은 기존 예약 snapshot만 지원하는 구현과의 호환을 위해 fail-closed합니다.
+ * Applies an explicit immutable pricing snapshot for the response model to
+ * pending actual usage. The default implementation fails closed for
+ * compatibility with implementations that support only the existing
+ * reservation snapshot.
*/
default ReservationReconciliation reconcileLateActual(
ActualUsageCommand command,
@@ -168,16 +177,17 @@ default ReservationReconciliation reconcileLateActual(
}
/**
- * pricing snapshot과 token estimate가 없던 pending 호환 예약을 caller가 계산한 actual 비용으로 확정합니다.
+ * Commits a pending compatible reservation without a pricing snapshot or
+ * token estimate using actual cost calculated by the caller.
*
- * 신규 예약에는 {@link #reconcileLateActual(ActualUsageCommand)}를 사용해야 합니다.
+ * New reservations must use {@link #reconcileLateActual(ActualUsageCommand)}.
*/
ReservationTransition reconcileLateActualCost(
ReservationId reservationId,
Cost actualCost
);
- /** 후속 정산할 수 없는 pending 예약을 명시적으로 상각합니다. */
+ /** Explicitly writes off a pending reservation that cannot be reconciled later. */
default ReservationTransition writeOff(ReservationId reservationId) {
return writeOff(
reservationId,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingEvent.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingEvent.java
index 00438d9..b4d04aa 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingEvent.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingEvent.java
@@ -2,7 +2,7 @@
import java.util.Objects;
-/** 새롭게 적용된 예약 정산을 전달하는 회계 이벤트입니다. */
+/** Accounting event for a newly applied reservation settlement. */
public record ReservationAccountingEvent(
ReservationReconciliation reconciliation
) {
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListener.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListener.java
index e0d509e..c95fb2d 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListener.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListener.java
@@ -1,12 +1,14 @@
package io.tokenpilot.budget;
/**
- * 예약 admission과 정산 결과를 수신하는 framework-independent 계약입니다.
+ * Framework-independent contract for receiving reservation admission and
+ * settlement results.
*
- * 기존 lambda listener와의 source 호환성을 위해 {@link #onCommitted}만 추상
- * 메서드로 유지합니다. 나머지 callback은 예약 저장소가 확정한 결과를 관찰하기 위한
- * 선택적 확장점입니다. 회계 변경 callback은 새로 적용된 전이에만 전달되고, 예약
- * callback은 모든 평가 결과와 BLOCK 결과를 각각 관찰할 수 있습니다.
+ * Only {@link #onCommitted} remains abstract for source compatibility with
+ * existing lambda listeners. The other callbacks are optional extension points
+ * for observing results confirmed by the reservation store. Accounting-change
+ * callbacks are delivered only for newly applied transitions; reservation
+ * callbacks can observe every evaluation and BLOCK result separately.
*/
@FunctionalInterface
public interface ReservationAccountingListener {
@@ -14,8 +16,9 @@ public interface ReservationAccountingListener {
void onCommitted(ReservationAccountingEvent event);
/**
- * 적용된 accounting transition과 같은 linearization point의 bucket snapshot을 전달합니다.
- * 기존 listener는 {@link #onCommitted(ReservationAccountingEvent)}로 위임됩니다.
+ * Delivers the bucket snapshot at the same linearization point as the applied
+ * accounting transition. Existing listeners delegate to
+ * {@link #onCommitted(ReservationAccountingEvent)}.
*/
default void onAccountingApplied(
ReservationAccountingEvent event,
@@ -24,26 +27,26 @@ default void onAccountingApplied(
onCommitted(event);
}
- /** actual 미확정 estimate가 pending liability로 이동한 결과를 전달합니다. */
+ /** Delivers a result where an estimate with unconfirmed actual usage moved to pending liability. */
default void onReconciliationRequired(
ReservationReconciliationRequiredEvent event
) {
}
- /** 원자적 예약 시도의 모든 결과를 전달합니다. */
+ /** Delivers every result of an atomic reservation attempt. */
default void onReservationEvaluated(
BudgetReservationRequest request,
BudgetReservationResult result
) {
}
- /** 원자적 admission에서 상태 변경 없이 차단된 결과를 전달합니다. */
+ /** Delivers a result blocked during atomic admission without changing state. */
default void onReservationBlocked(
BudgetReservationRequest request,
BudgetReservationResult result
) {
}
- /** metric tag에 안전한 제한된 listener 종류를 반환합니다. */
+ /** Returns a bounded listener type safe to use as a metric tag. */
default ReservationAccountingListenerType listenerType() {
return ReservationAccountingListenerType.CUSTOM;
}
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerErrorHandler.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerErrorHandler.java
index a97c4a4..ce6ff2c 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerErrorHandler.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerErrorHandler.java
@@ -1,6 +1,6 @@
package io.tokenpilot.budget;
-/** bounded 회계 listener 실패를 관찰하는 best-effort hook입니다. */
+/** Best-effort hook for observing bounded accounting listener failures. */
@FunctionalInterface
public interface ReservationAccountingListenerErrorHandler {
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerFailureEvent.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerFailureEvent.java
index 89684b0..918b885 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerFailureEvent.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerFailureEvent.java
@@ -3,10 +3,10 @@
import java.util.Objects;
/**
- * 회계 listener callback 실패를 bounded 정보만으로 전달하는 이벤트입니다.
+ * Event carrying an accounting listener callback failure using only bounded information.
*
- * 원본 예외와 메시지를 포함하지 않아 prompt, provider 응답 또는 자격 증명이
- * 관측 경로로 전달되지 않습니다.
+ * The original exception and message are excluded so prompts, provider
+ * responses, and credentials do not reach the observation path.
*/
public record ReservationAccountingListenerFailureEvent(
ReservationAccountingListenerType listenerType,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerPhase.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerPhase.java
index 93642ce..08d0471 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerPhase.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerPhase.java
@@ -1,6 +1,6 @@
package io.tokenpilot.budget;
-/** 회계 listener 실패 관측에 사용하는 제한된 callback 단계입니다. */
+/** Bounded callback phases used to observe accounting listener failures. */
public enum ReservationAccountingListenerPhase {
ACCOUNTING_APPLIED,
RECONCILIATION_REQUIRED,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerType.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerType.java
index 8fac5d8..7542254 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerType.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingListenerType.java
@@ -1,6 +1,6 @@
package io.tokenpilot.budget;
-/** 회계 listener 실패 관측에 사용하는 제한된 listener 종류입니다. */
+/** Bounded listener types used to observe accounting listener failures. */
public enum ReservationAccountingListenerType {
METRICS,
NOTIFICATION,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingReason.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingReason.java
index 6e85725..f5b7400 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingReason.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationAccountingReason.java
@@ -1,6 +1,6 @@
package io.tokenpilot.budget;
-/** 회계 명령과 정산 결과에 사용하는 제한된 사유입니다. */
+/** Bounded reasons used by accounting commands and settlement results. */
public enum ReservationAccountingReason {
ACTUAL_USAGE_REPORTED,
LATE_ACTUAL_USAGE_REPORTED,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationActualTokens.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationActualTokens.java
index d5e377b..c618bf4 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationActualTokens.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationActualTokens.java
@@ -7,7 +7,7 @@
import java.util.Map;
import java.util.Objects;
-/** Provider actual usage에서 무제한 metadata를 제외한 회계용 token 결과입니다. */
+/** Accounting token result derived from provider actual usage without unbounded metadata. */
public record ReservationActualTokens(
long inputTokens,
long outputTokens,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationId.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationId.java
index e22dcf6..276f8cf 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationId.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationId.java
@@ -3,7 +3,7 @@
import java.util.UUID;
/**
- * 예산 예약을 식별하는 불변 ID입니다.
+ * Immutable ID identifying a budget reservation.
*/
public record ReservationId(String value) {
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationReconciliation.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationReconciliation.java
index e8cb910..1885541 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationReconciliation.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationReconciliation.java
@@ -8,7 +8,7 @@
import java.util.Objects;
/**
- * 한 예약의 estimate와 actual 비용을 연결한 회계 정산 결과입니다.
+ * Accounting settlement result linking one reservation's estimate and actual cost.
*/
public record ReservationReconciliation(
String requestId,
@@ -16,9 +16,9 @@ public record ReservationReconciliation(
ReservationId reservationId,
BudgetKey budgetKey,
String responseModelId,
- /** 예약 시점에 estimate를 계산한 request pricing snapshot입니다. */
+ /** Request pricing snapshot used to calculate the estimate at reservation time. */
PricingSnapshot pricingSnapshot,
- /** provider response model의 actual 비용 계산에 사용한 pricing snapshot입니다. */
+ /** Pricing snapshot used to calculate actual cost for the provider response model. */
PricingSnapshot actualPricingSnapshot,
ReservationTokenEstimate tokenEstimate,
ReservationActualTokens actualTokens,
@@ -65,8 +65,9 @@ public record ReservationReconciliation(
}
/**
- * 기존 request pricing snapshot만 사용한 정산 결과를 만드는 호환 생성자입니다.
- * request와 response model이 같은 기존 경로에서는 두 snapshot이 동일합니다.
+ * Compatibility constructor that creates a settlement result using only the
+ * existing request pricing snapshot. In the existing path where request and
+ * response models are the same, both snapshots are identical.
*/
public ReservationReconciliation(
String requestId,
@@ -118,32 +119,32 @@ private static PricingSnapshot actualSnapshotForResponseModel(
);
}
- /** 예약 시점 pricing snapshot의 request model입니다. */
+ /** Request model in the reservation-time pricing snapshot. */
public String requestModelId() {
return pricingSnapshot.modelId();
}
- /** actual provider usage에 적용한 response model pricing policy입니다. */
+ /** Pricing policy applied to actual provider usage for the response model. */
public String actualPricingPolicyId() {
return actualPricingSnapshot.pricingPolicyId();
}
- /** actual provider usage에 적용한 response model catalog version입니다. */
+ /** Catalog version applied to actual provider usage for the response model. */
public String actualCatalogVersion() {
return actualPricingSnapshot.catalogVersion();
}
- /** 예약 시점 pricing policy 식별자입니다. */
+ /** Pricing policy identifier at reservation time. */
public String pricingPolicyId() {
return pricingSnapshot.pricingPolicyId();
}
- /** 예약 시점 model catalog version입니다. */
+ /** Model catalog version at reservation time. */
public String catalogVersion() {
return pricingSnapshot.catalogVersion();
}
- /** actual에서 estimate를 뺀 signed 비용 차이입니다. */
+ /** Signed cost difference obtained by subtracting the estimate from actual cost. */
public BigDecimal delta() {
return actual.value().subtract(estimate.value());
}
@@ -170,7 +171,7 @@ public long totalTokenDelta() {
return Math.subtractExact(actualTokens.totalTokens(), estimatedTotal);
}
- /** estimate와 actual이 사용하는 통화입니다. */
+ /** Currency used by the estimate and actual cost. */
public Currency currency() {
return actual.currency();
}
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationReconciliationRequiredEvent.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationReconciliationRequiredEvent.java
index 120e4ee..bdfd81d 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationReconciliationRequiredEvent.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationReconciliationRequiredEvent.java
@@ -3,7 +3,7 @@
import java.util.Objects;
import java.util.Optional;
-/** actual usage를 확보하지 못해 pending liability로 이동한 원자적 회계 결과입니다. */
+/** Atomic accounting result moved to pending liability because actual usage was unavailable. */
public record ReservationReconciliationRequiredEvent(
ReservationId reservationId,
BudgetKey budgetKey,
@@ -57,7 +57,7 @@ public record ReservationReconciliationRequiredEvent(
}
}
- /** 기존 actual-unavailable 이벤트를 만드는 호환 생성자입니다. */
+ /** Compatibility constructor for the former actual-unavailable event. */
public ReservationReconciliationRequiredEvent(
ReservationId reservationId,
BudgetKey budgetKey,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationState.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationState.java
index 4a1cd07..5c376e5 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationState.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationState.java
@@ -1,31 +1,31 @@
package io.tokenpilot.budget;
/**
- * 예산 예약의 현재 회계 상태입니다.
+ * Current accounting state of a budget reservation.
*/
public enum ReservationState {
- /** 안전 상한액이 예약되었지만 공급자 호출은 시작되지 않은 상태입니다. */
+ /** The safe upper-bound amount is reserved, but provider invocation has not started. */
RESERVED,
- /** 예약을 사용해 공급자 호출이 진행 중인 상태입니다. */
+ /** Provider invocation is in progress using the reservation. */
IN_FLIGHT,
- /** 실제 사용량을 알 수 없어 후속 정산이 필요한 상태입니다. */
+ /** Actual usage is unavailable and later reconciliation is required. */
RECONCILIATION_REQUIRED,
- /** 실제 사용 금액이 확정된 종료 상태입니다. */
+ /** Terminal state with actual cost committed. */
COMMITTED,
- /** 사용되지 않은 예약 금액이 해제된 종료 상태입니다. */
+ /** Terminal state with unused reserved cost released. */
RELEASED,
- /** 실제 사용량을 끝내 확정할 수 없어 정책에 따라 상각된 종료 상태입니다. */
+ /** Terminal state written off by policy because actual usage could not be confirmed. */
WRITTEN_OFF;
/**
- * 더 이상 정상적인 회계 전이를 허용하지 않는 종료 상태인지 반환합니다.
+ * Returns whether this is a terminal state that allows no further normal accounting transitions.
*
- * {@link #RECONCILIATION_REQUIRED}는 후속 정산을 기다리는 상태이므로 종료 상태가 아닙니다.
+ * {@link #RECONCILIATION_REQUIRED} is not terminal because it is waiting for later reconciliation.
*/
public boolean isClosed() {
return this == COMMITTED || this == RELEASED || this == WRITTEN_OFF;
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationStateMachine.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationStateMachine.java
index 812b575..3f65d74 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationStateMachine.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationStateMachine.java
@@ -3,14 +3,14 @@
import java.util.Objects;
/**
- * 예약의 회계 상태 전이 가능 여부를 판단합니다.
+ * Determines whether an accounting state transition is allowed for a reservation.
*/
public final class ReservationStateMachine {
private ReservationStateMachine() {
}
- /** 예약된 요청의 공급자 호출을 시작합니다. */
+ /** Starts provider invocation for a reserved request. */
public static ReservationTransition onDispatch(ReservationState currentState) {
return transitionFrom(
currentState,
@@ -19,7 +19,7 @@ public static ReservationTransition onDispatch(ReservationState currentState) {
);
}
- /** 공급자 호출 전에 예약을 해제합니다. */
+ /** Releases a reservation before provider invocation. */
public static ReservationTransition release(ReservationState currentState) {
return transitionFrom(
currentState,
@@ -28,7 +28,7 @@ public static ReservationTransition release(ReservationState currentState) {
);
}
- /** 공급자가 미과금을 확인한 진행 중 예약을 해제합니다. */
+ /** Releases an in-flight reservation after the provider confirms no charge. */
public static ReservationTransition releaseConfirmedUnbilled(
ReservationState currentState
) {
@@ -39,7 +39,7 @@ public static ReservationTransition releaseConfirmedUnbilled(
);
}
- /** 전달받은 actual을 확정하기 위한 상태 전이를 판단합니다. */
+ /** Determines the state transition needed to commit the received actual usage. */
public static ReservationTransition commit(ReservationState currentState) {
return transitionFrom(
currentState,
@@ -48,7 +48,7 @@ public static ReservationTransition commit(ReservationState currentState) {
);
}
- /** actual을 전달받지 못한 예약을 정산 대기로 전환할 수 있는지 판단합니다. */
+ /** Determines whether a reservation without actual usage can enter reconciliation pending. */
public static ReservationTransition markReconciliationRequired(ReservationState currentState) {
return transitionFrom(
currentState,
@@ -57,7 +57,7 @@ public static ReservationTransition markReconciliationRequired(ReservationState
);
}
- /** 정산 대기 중 전달받은 late actual을 확정할 수 있는지 판단합니다. */
+ /** Determines whether late actual usage can be committed while reconciliation is pending. */
public static ReservationTransition reconcileLateActual(ReservationState currentState) {
return transitionFrom(
currentState,
@@ -66,7 +66,7 @@ public static ReservationTransition reconcileLateActual(ReservationState current
);
}
- /** 정산 대기 중인 예약을 명시적으로 상각합니다. */
+ /** Explicitly writes off a reservation pending reconciliation. */
public static ReservationTransition writeOff(ReservationState currentState) {
return transitionFrom(
currentState,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationStatus.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationStatus.java
index 08c54de..546021b 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationStatus.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationStatus.java
@@ -1,7 +1,7 @@
package io.tokenpilot.budget;
/**
- * 예산 예약 시도 결과입니다.
+ * Result of a budget reservation attempt.
*/
public enum ReservationStatus {
CREATED,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationTokenEstimate.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationTokenEstimate.java
index 3e59e05..ce5c4f7 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationTokenEstimate.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationTokenEstimate.java
@@ -1,6 +1,6 @@
package io.tokenpilot.budget;
-/** 예약 비용의 근거가 된 호출 전 token estimate입니다. */
+/** Pre-call token estimate supporting the reserved cost. */
public record ReservationTokenEstimate(
long inputEstimatedTokens,
long inputSafeUpperBoundTokens,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationTransition.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationTransition.java
index ed66bc0..1fc58d9 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationTransition.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/ReservationTransition.java
@@ -3,7 +3,7 @@
import java.util.Objects;
/**
- * 예약의 상태 전이 판단입니다.
+ * Reservation state transition decision.
*/
public record ReservationTransition(
ReservationState previousState,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/exception/BudgetExceededException.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/exception/BudgetExceededException.java
index 4ca05f2..80cb8a4 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/exception/BudgetExceededException.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/exception/BudgetExceededException.java
@@ -5,10 +5,11 @@
import java.util.Objects;
/**
- * Provider 경계에서 BLOCK decision을 집행할 때 던지는 예외입니다.
+ * Exception thrown when a BLOCK decision is enforced at the provider boundary.
*
- * {@code BudgetEvaluator}는 이 예외를 직접 던지지 않습니다. Provider invocation 직전의
- * 경계가 원래 decision을 보존한 이 예외를 던져 호출 체인을 중단합니다.
+ * {@code BudgetEvaluator} does not throw this exception directly. The boundary
+ * immediately before provider invocation throws it while preserving the original
+ * decision to stop the call chain.
*/
public class BudgetExceededException extends RuntimeException {
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/ActualUsageFingerprint.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/ActualUsageFingerprint.java
index ac017cf..7191a37 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/ActualUsageFingerprint.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/ActualUsageFingerprint.java
@@ -7,7 +7,7 @@
import java.util.Objects;
-/** 중복 actual callback을 민감하거나 무제한인 metadata 없이 식별합니다. */
+/** Identifies duplicate actual callbacks without sensitive or unbounded metadata. */
record ActualUsageFingerprint(
String requestId,
String attemptId,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/AppliedCommit.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/AppliedCommit.java
index 5e4c937..935d647 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/AppliedCommit.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/AppliedCommit.java
@@ -12,7 +12,8 @@
import static io.tokenpilot.budget.AccountingTransitionStatus.REUSED;
/**
- * 이미 적용된 commit의 종류, actual과 재호출 판단을 보관합니다.
+ * Stores the type of an applied commit and the values used to evaluate actual
+ * usage and repeated calls.
*/
record AppliedCommit(
CommitType type,
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/AppliedRelease.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/AppliedRelease.java
index fdc6c8d..092f48e 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/AppliedRelease.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/AppliedRelease.java
@@ -10,7 +10,7 @@
import static io.tokenpilot.budget.AccountingTransitionStatus.REUSED;
/**
- * 이미 적용된 release의 종류와 재호출 판단을 보관합니다.
+ * Stores the type of an applied release and the values used to evaluate repeated calls.
*/
record AppliedRelease(ReleaseType type) {
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/CommitType.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/CommitType.java
index e1f780a..25205ff 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/CommitType.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/CommitType.java
@@ -1,6 +1,6 @@
package io.tokenpilot.budget.internal;
-/** Actual 비용이 확정된 경로입니다. */
+/** Path on which actual cost was committed. */
enum CommitType {
DIRECT,
LATE_ACTUAL
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/DefaultBudgetEvaluator.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/DefaultBudgetEvaluator.java
index 19c6497..280c149 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/DefaultBudgetEvaluator.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/DefaultBudgetEvaluator.java
@@ -17,10 +17,10 @@
import java.util.Objects;
/**
- * 이 evaluator는 부수 효과 없는 policy projection만 담당합니다.
- * {@code projectedUsage = committedUsage + candidateCost}이며 active reservation과
- * reconciliation liability는 {@link BudgetStateStore#snapshot}과 원자적 reservation
- * store가 별도로 반영합니다.
+ * This evaluator is responsible only for side-effect-free policy projection.
+ * {@code projectedUsage = committedUsage + candidateCost}; active reservations
+ * and reconciliation liability are reflected separately by
+ * {@link BudgetStateStore#snapshot} and the atomic reservation store.
*/
public class DefaultBudgetEvaluator implements BudgetEvaluator {
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/InMemoryBudgetStateStore.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/InMemoryBudgetStateStore.java
index 4a7fdb4..c65f39b 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/InMemoryBudgetStateStore.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/InMemoryBudgetStateStore.java
@@ -44,10 +44,12 @@
import java.util.function.Supplier;
/**
- * resolved {@link BudgetKey}별 확정 비용과 원자적 예약을 관리하는 인메모리 저장소입니다.
+ * In-memory store managing committed cost and atomic reservations for each
+ * resolved {@link BudgetKey}.
*
- *
bucket별 monitor가 조회·통화 검증·한도 검증·예약 갱신을 함께 보호하고,
- * 별도의 idempotency index가 같은 요청의 중복 예약을 차단합니다.
+ * A per-bucket monitor protects reads, currency validation, limit validation,
+ * and reservation updates together. A separate idempotency index prevents
+ * duplicate reservations for the same request.
*/
public class InMemoryBudgetStateStore implements AtomicBudgetStateStore {
@@ -734,7 +736,7 @@ private void publishListenerFailure(
try {
errorHandler.onFailure(event);
} catch (RuntimeException ignored) {
- // Error handler도 best-effort이며 회계/admission 결과에 영향을 주지 않습니다.
+ // The error handler is also best-effort and does not affect accounting or admission results.
}
}
}
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/LedgerBudgetComponents.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/LedgerBudgetComponents.java
index 71db80e..fb51a5d 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/LedgerBudgetComponents.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/LedgerBudgetComponents.java
@@ -17,7 +17,7 @@
import java.util.function.Supplier;
/**
- * 예산 제어 컴포넌트 생성을 위한 팩토리 클래스입니다.
+ * Factory for creating budget-control components.
*/
public final class LedgerBudgetComponents {
@@ -28,12 +28,12 @@ public static BudgetStateStore inMemoryBudgetStateStore() {
return new InMemoryBudgetStateStore();
}
- /** 예약 admission과 accounting을 한 객체로 노출하는 기본 합성 store입니다. */
+ /** Default composite store exposing reservation admission and accounting as one object. */
public static AtomicBudgetStateStore inMemoryAtomicBudgetStateStore() {
return new InMemoryBudgetStateStore();
}
- /** 예약과 회계를 같은 원자적 상태에 연결하는 합성 store factory입니다. */
+ /** Composite store factory connecting reservation and accounting to the same atomic state. */
public static AtomicBudgetStateStore inMemoryAtomicBudgetStateStore(
Supplier> accountingListenerSupplier,
Supplier>
@@ -48,7 +48,7 @@ public static AtomicBudgetStateStore inMemoryAtomicBudgetStateStore(
);
}
- /** PR #68 accounting/notification listener 연결과 호환되는 convenience factory입니다. */
+ /** Convenience factory compatible with the PR #68 accounting/notification listener wiring. */
public static BudgetStateStore inMemoryBudgetStateStore(
List accountingListeners
) {
@@ -60,7 +60,7 @@ public static BudgetStateStore inMemoryBudgetStateStore(
);
}
- /** listener 실패 관측 hook까지 연결하는 convenience factory입니다. */
+ /** Convenience factory that also connects a listener-failure observation hook. */
public static BudgetStateStore inMemoryBudgetStateStore(
List accountingListeners,
List listenerErrorHandlers
@@ -74,7 +74,7 @@ public static BudgetStateStore inMemoryBudgetStateStore(
);
}
- /** Spring 등 DI container가 listener를 store 생성 뒤 지연 해석할 수 있는 factory입니다. */
+ /** Factory allowing a DI container such as Spring to resolve listeners after store creation. */
public static BudgetStateStore inMemoryBudgetStateStore(
Supplier> accountingListenerSupplier,
Supplier>
@@ -139,7 +139,7 @@ public static BudgetStateStore inMemoryBudgetStateStore(
}
/**
- * 예약을 생성한 store와 동일한 객체의 회계 명령 진입점을 반환합니다.
+ * Returns the accounting command entry point on the same object that created the reservation.
*/
public static ReservationAccounting reservationAccounting(
BudgetStateStore stateStore
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/ReleaseType.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/ReleaseType.java
index 79f5fe1..2a778b9 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/ReleaseType.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/ReleaseType.java
@@ -1,6 +1,6 @@
package io.tokenpilot.budget.internal;
-/** 예약 금액이 해제된 경로입니다. */
+/** Path on which the reserved amount was released. */
enum ReleaseType {
BEFORE_DISPATCH,
CONFIRMED_UNBILLED
diff --git a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/ReservationAccountingState.java b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/ReservationAccountingState.java
index bb06898..717c6c6 100644
--- a/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/ReservationAccountingState.java
+++ b/token-pilot-budget/src/main/java/io/tokenpilot/budget/internal/ReservationAccountingState.java
@@ -15,7 +15,7 @@
import static io.tokenpilot.budget.ReservationState.WRITTEN_OFF;
/**
- * 한 예약에 적용된 회계 명령을 판단하기 위한 내부 상태입니다.
+ * Internal state used to evaluate accounting commands applied to one reservation.
*/
final class ReservationAccountingState {
diff --git a/token-pilot-budget/src/test/java/io/tokenpilot/budget/internal/BudgetReservationConcurrencyTest.java b/token-pilot-budget/src/test/java/io/tokenpilot/budget/internal/BudgetReservationConcurrencyTest.java
index 870fce1..1edda1b 100644
--- a/token-pilot-budget/src/test/java/io/tokenpilot/budget/internal/BudgetReservationConcurrencyTest.java
+++ b/token-pilot-budget/src/test/java/io/tokenpilot/budget/internal/BudgetReservationConcurrencyTest.java
@@ -35,11 +35,13 @@
import static org.assertj.core.api.Assertions.assertThat;
/**
- * #36 예약 linearization point의 결정적 동시성 회귀 테스트입니다.
+ * Deterministic concurrency regression test for the #36 reservation linearization point.
*
- * 같은 시작선에서 경쟁 명령을 실행하고 공개 snapshot만 검증합니다. 이 테스트는
- * 동시성 정확성의 수학적 증명이나 선형성 검증기를 대체하지 않습니다. #37의
- * commit/release/reconciliation 및 #46의 listener 정책은 해당 계약이 구현된 뒤 확장합니다.
+ * Runs competing commands from the same starting line and verifies only the
+ * public snapshot. This test is not a mathematical proof of concurrency
+ * correctness or a replacement for a linearizability checker. The #37
+ * commit/release/reconciliation and #46 listener policies are extended after
+ * their contracts are implemented.
*/
class BudgetReservationConcurrencyTest {
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/CoreComponents.java b/token-pilot-core/src/main/java/io/tokenpilot/core/CoreComponents.java
index b3750c3..20f3739 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/CoreComponents.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/CoreComponents.java
@@ -7,7 +7,7 @@
import java.util.List;
/**
- * framework-independent core component entrypoint입니다.
+ * Framework-independent core component entry point.
*/
public final class CoreComponents {
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/CostCalculator.java b/token-pilot-core/src/main/java/io/tokenpilot/core/CostCalculator.java
index ca5e8da..de295bb 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/CostCalculator.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/CostCalculator.java
@@ -8,19 +8,19 @@
import java.util.Objects;
/**
- * 사용량과 가격 정책을 바탕으로 비용을 계산하는 인터페이스.
+ * Interface for calculating cost from usage and a pricing policy.
*/
public interface CostCalculator {
/**
- * 토큰 사용량과 가격 정책을 기반으로 비용을 산출합니다.
- * @param usage 토큰 사용량
- * @param plan 가격 정책
- * @return 산출된 비용
+ * Calculates cost from token usage and a pricing policy.
+ * @param usage token usage
+ * @param plan pricing policy
+ * @return calculated cost
*/
Cost calculate(TokenUsage usage, PricingPlan plan);
/**
- * 예약 시점에 고정한 가격 snapshot으로 비용을 계산합니다.
+ * Calculates cost using the pricing snapshot fixed at reservation time.
*/
default Cost calculate(TokenUsage usage, PricingSnapshot snapshot) {
Objects.requireNonNull(snapshot, "snapshot must not be null");
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/LedgerListener.java b/token-pilot-core/src/main/java/io/tokenpilot/core/LedgerListener.java
index ddb5288..d7a9ed0 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/LedgerListener.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/LedgerListener.java
@@ -3,14 +3,14 @@
import io.tokenpilot.core.domain.CostRecordedEvent;
/**
- * 비용 기록 이벤트를 수신하는 리스너 인터페이스.
- * 옵저버 패턴의 Observer 역할을 수행하며, 순수 자바 기반으로 동작합니다.
+ * Listener interface for cost-recording events.
+ * It serves as the Observer in the observer pattern and runs on plain Java.
*/
@FunctionalInterface
public interface LedgerListener {
/**
- * 비용 기록 이벤트가 발생했을 때 호출됩니다.
- * @param event 발생한 비용 기록 이벤트
+ * Called when a cost-recording event occurs.
+ * @param event emitted cost-recording event
*/
void onRecord(CostRecordedEvent event);
}
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/LedgerManager.java b/token-pilot-core/src/main/java/io/tokenpilot/core/LedgerManager.java
index e4a96dd..b13d99e 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/LedgerManager.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/LedgerManager.java
@@ -8,33 +8,33 @@
import java.util.Map;
/**
- * AI 호출에 대한 지출을 기록하고 관리하는 통합 매니저 인터페이스.
+ * Unified manager interface for recording and managing AI call spending.
*/
public interface LedgerManager {
/**
- * 특정 모델의 호출 정보를 기록하고 최종 비용을 계산합니다.
- * @param modelId 모델 식별자
- * @param usage 토큰 사용량
- * @param tags 추가 메타데이터 (tenant_id, user_id 등)
- * @return 산출된 비용
+ * Records a call for a model and calculates the final cost.
+ * @param modelId model identifier
+ * @param usage token usage
+ * @param tags additional metadata, such as tenant_id or user_id
+ * @return calculated cost
*/
Cost record(String modelId, TokenUsage usage, Map tags);
/**
- * 이미 resolve된 가격 정책으로 호출 정보를 기록하고 최종 비용을 계산합니다.
- * @param plan provider 호출 전에 resolve된 가격 정책
- * @param usage 토큰 사용량
- * @param tags 추가 메타데이터 (tenant_id, user_id 등)
- * @return 산출된 비용
+ * Records a call and calculates the final cost using an already resolved pricing policy.
+ * @param plan pricing policy resolved before provider invocation
+ * @param usage token usage
+ * @param tags additional metadata, such as tenant_id or user_id
+ * @return calculated cost
*/
Cost record(PricingPlan plan, TokenUsage usage, Map tags);
/**
- * 요청 단위 pricing snapshot으로 호출 정보를 기록하고 최종 비용을 계산합니다.
- * @param snapshot provider 호출 전에 보존된 pricing snapshot
- * @param usage 토큰 사용량
- * @param tags 추가 메타데이터 (tenant_id, user_id 등)
- * @return 산출된 비용
+ * Records a call and calculates the final cost using a request-scoped pricing snapshot.
+ * @param snapshot pricing snapshot retained before provider invocation
+ * @param usage token usage
+ * @param tags additional metadata, such as tenant_id or user_id
+ * @return calculated cost
*/
Cost record(PricingSnapshot snapshot, TokenUsage usage, Map tags);
}
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/ModelRegistry.java b/token-pilot-core/src/main/java/io/tokenpilot/core/ModelRegistry.java
index 7558726..f2cea59 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/ModelRegistry.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/ModelRegistry.java
@@ -5,21 +5,20 @@
import java.util.Optional;
/**
- * versioned model definition을 canonical id 또는 exact alias로 조회합니다.
+ * Looks up versioned model definitions by canonical ID or exact alias.
*
- * 조회 결과의 {@link ModelDefinition#canonicalModelId()}와
- * {@link ModelDefinition#pricingPolicyId()}를 함께 사용해
- * {@link PricingRegistry}에서 가격 정책을 조회해야 합니다. alias 문자열을
- * 가격 조회에 직접 전달하면 context와 pricing이 서로 다른 모델을 가리킬 수
- * 있습니다.
+ * Use {@link ModelDefinition#canonicalModelId()} and
+ * {@link ModelDefinition#pricingPolicyId()} from the result together when
+ * looking up pricing in {@link PricingRegistry}. Passing an alias directly to
+ * pricing lookup could cause context and pricing to refer to different models.
*/
public interface ModelRegistry {
/**
- * canonical model id 또는 exact alias를 canonical definition으로 해석합니다.
+ * Resolves a canonical model ID or exact alias to its canonical definition.
*
- * @param modelIdOrAlias canonical id 또는 alias
- * @return 등록된 모델 definition, 미등록이면 empty
+ * @param modelIdOrAlias canonical ID or alias
+ * @return registered model definition, or empty when not registered
*/
Optional find(String modelIdOrAlias);
}
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/PreflightCostEstimator.java b/token-pilot-core/src/main/java/io/tokenpilot/core/PreflightCostEstimator.java
index cd5d4c0..84ae3aa 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/PreflightCostEstimator.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/PreflightCostEstimator.java
@@ -5,32 +5,33 @@
import io.tokenpilot.core.domain.TokenCountResult;
/**
- * 호출 전에 계산한 REQUEST 범위의 토큰 상한을 금액 상한으로 변환하는 Core 계약입니다.
+ * Core contract for converting a pre-call REQUEST-scope token bound into a cost bound.
*
- * 이 계약은 비용을 계산할 뿐 context window 적합성이나 provider 호출 허가를
- * 판정하지 않습니다. 호출자는 요청 토큰 결과가 모델의 context admission을 통과했고,
- * {@link PreflightPricingContext}가 계산에 사용할 하나의 불변 pricing snapshot을
- * 보관하는지 먼저 보장해야 합니다. 계산기는 다른 snapshot을 registry에서 다시
- * 조회하지 않습니다.
+ * This contract calculates cost only; it does not determine context-window
+ * admission or authorize provider invocation. Callers must first ensure that
+ * the request token result passes model context admission and that
+ * {@link PreflightPricingContext} retains one immutable pricing snapshot for
+ * the calculation. The calculator does not look up another snapshot from a registry.
*
- * 계산 가능한 경우에도 예약 근거로 사용할 값은
- * {@link PreflightCostResult.Bounded#safeUpperBoundCost()}뿐입니다.
- * {@code estimatedCost}는 관찰과 표시를 위한 값입니다.
+ * Even when calculation succeeds, only
+ * {@link PreflightCostResult.Bounded#safeUpperBoundCost()} may support a reservation.
+ * {@code estimatedCost} is for observation and display.
*/
public interface PreflightCostEstimator {
/**
- * atomic reservation에 사용할 호출 전 비용 상한을 계산합니다.
- * 입력이 REQUEST 범위가 아니거나 tokenizer 기준이 맞지 않거나 가격 snapshot이
- * 없거나 문맥에 snapshot이 없으면 숫자 비용 대신 제한된 unavailable 결과를 반환해야 합니다.
+ * Calculates the pre-call cost bound used for atomic reservation.
+ * When the input is not REQUEST scope, the tokenizer basis is incompatible,
+ * pricing is missing, or the context has no snapshot, returns a bounded
+ * unavailable result instead of a numeric cost.
*
- * @param pricingContext canonical model, pricing policy, catalog version과
- * 검증된 pricing 조건을 담은 계산 문맥
- * @param requestInput 실제 전송 요청 전체를 계산한 REQUEST 범위의 token 결과
- * @param reservedOutputTokens 호출 전에 확보할 최대 출력 token 수
- * @return 계산 가능한 비용 상한 또는 그 사유를 담은 unavailable 결과
- * @throws NullPointerException 필수 인자가 {@code null}인 경우
- * @throws IllegalArgumentException reservedOutputTokens가 음수인 경우
+ * @param pricingContext calculation context containing the canonical model,
+ * pricing policy, catalog version, and validated pricing conditions
+ * @param requestInput REQUEST-scope token result covering the complete request to be sent
+ * @param reservedOutputTokens maximum output tokens secured before the call
+ * @return calculable cost bound or an unavailable result containing the reason
+ * @throws NullPointerException when a required argument is {@code null}
+ * @throws IllegalArgumentException when reservedOutputTokens is negative
*/
PreflightCostResult estimate(
PreflightPricingContext pricingContext,
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/PreflightDecisionListener.java b/token-pilot-core/src/main/java/io/tokenpilot/core/PreflightDecisionListener.java
index 4621305..e040cfc 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/PreflightDecisionListener.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/PreflightDecisionListener.java
@@ -2,7 +2,7 @@
import io.tokenpilot.core.domain.PreflightDecisionEvent;
-/** context admission 판정을 best-effort로 관찰하는 framework-independent 계약입니다. */
+/** Framework-independent contract for best-effort observation of context admission decisions. */
@FunctionalInterface
public interface PreflightDecisionListener {
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/PricingEvaluator.java b/token-pilot-core/src/main/java/io/tokenpilot/core/PricingEvaluator.java
index 16c6976..b6a61a4 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/PricingEvaluator.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/PricingEvaluator.java
@@ -7,24 +7,24 @@
import java.util.Optional;
/**
- * Pricing snapshot의 사용 가능 여부와 actual model 정합성을 판단하는 정책 계약.
+ * Policy contract for determining pricing snapshot availability and actual-model consistency.
*/
public interface PricingEvaluator {
/**
- * Snapshot에 요청 처리에 필요한 rate가 있는지 검증합니다.
+ * Validates that the snapshot contains rates required to process the request.
*
- * @param snapshot 검증할 pricing snapshot, 조회되지 않은 경우 empty
- * @return snapshot 및 필수 rate의 resolution
+ * @param snapshot pricing snapshot to validate, or empty when it was not resolved
+ * @return resolution of the snapshot and required rates
*/
PricingResolution validateSnapshotRates(Optional snapshot);
/**
- * 호출 전 snapshot을 actual 응답 모델에 적용할 수 있는지 판단합니다.
+ * Determines whether the pre-call snapshot can be applied to the actual response model.
*
- * @param snapshot 호출 전에 확정한 pricing snapshot, 확정되지 않은 경우 empty
- * @param actualModelId provider가 반환한 actual model id
- * @return pricing reconciliation 판단 결과
+ * @param snapshot pricing snapshot fixed before the call, or empty when it was not fixed
+ * @param actualModelId actual model ID returned by the provider
+ * @return pricing reconciliation result
*/
PricingReconciliationResult determineReconciliation(
Optional snapshot,
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/PricingMissingListener.java b/token-pilot-core/src/main/java/io/tokenpilot/core/PricingMissingListener.java
index 6d23f32..8ded36c 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/PricingMissingListener.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/PricingMissingListener.java
@@ -2,7 +2,7 @@
import io.tokenpilot.core.domain.PricingMissingEvent;
-/** missing-pricing 판정을 best-effort로 관찰하는 framework-independent 계약입니다. */
+/** Framework-independent contract for best-effort observation of missing-pricing decisions. */
@FunctionalInterface
public interface PricingMissingListener {
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/PricingProvider.java b/token-pilot-core/src/main/java/io/tokenpilot/core/PricingProvider.java
index 4edeeae..bf20b17 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/PricingProvider.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/PricingProvider.java
@@ -4,13 +4,13 @@
import java.util.Collection;
/**
- * AI 모델의 가격 정책 정보를 제공하는 공급자 인터페이스.
- * 로컬 파일(YAML), 데이터베이스, 또는 원격 API로부터 가격 정보를 가져올 수 있습니다.
+ * Provider interface for AI model pricing policies.
+ * Pricing data can be loaded from local files (YAML), databases, or remote APIs.
*/
public interface PricingProvider {
/**
- * 사용 가능한 모든 가격 정책 목록을 반환합니다.
- * @return 가격 정책 컬렉션
+ * Returns all available pricing policies.
+ * @return collection of pricing policies
*/
Collection getAllPlans();
}
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/PricingRegistry.java b/token-pilot-core/src/main/java/io/tokenpilot/core/PricingRegistry.java
index b905888..a965cc9 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/PricingRegistry.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/PricingRegistry.java
@@ -12,38 +12,39 @@
import java.util.Optional;
/**
- * AI 모델별 가격 정책을 관리하는 저장소 인터페이스.
+ * Repository interface for managing pricing policies by AI model.
*/
public interface PricingRegistry {
/**
- * 모델 식별자로 등록된 가격 정책을 조회합니다.
- * @param modelId 모델 식별자
- * @return 가격 정책 (존재하지 않을 경우 empty)
+ * Looks up a pricing policy registered for a model identifier.
+ * @param modelId model identifier
+ * @return pricing policy, or empty when none exists
*/
Optional getPlan(String modelId);
/**
- * 모델 식별자와 pricing policy id로 등록된 가격 정책을 조회합니다.
- * @param modelId 모델 식별자
- * @param pricingPolicyId pricing policy 식별자
- * @return 가격 정책 (존재하지 않을 경우 empty)
+ * Looks up a pricing policy registered for a model identifier and pricing policy ID.
+ * @param modelId model identifier
+ * @param pricingPolicyId pricing policy identifier
+ * @return pricing policy, or empty when none exists
*/
Optional getPlan(String modelId, String pricingPolicyId);
/**
- * 모델 식별자와 pricing policy id로 요청 단위 pricing snapshot을 resolve합니다.
- * @param modelId 모델 식별자
- * @param pricingPolicyId pricing policy 식별자
- * @return pricing snapshot (존재하지 않을 경우 empty)
+ * Resolves a request-scoped pricing snapshot by model identifier and pricing policy ID.
+ * @param modelId model identifier
+ * @param pricingPolicyId pricing policy identifier
+ * @return pricing snapshot, or empty when none exists
*/
Optional resolveSnapshot(String modelId, String pricingPolicyId);
/**
- * canonical model definition과 연결된 catalog version으로 pricing snapshot을 resolve합니다.
- * alias는 이 메서드에서 canonical id로 변환된 뒤에만 가격 조회에 사용됩니다.
+ * Resolves a pricing snapshot using the catalog version associated with a
+ * canonical model definition. An alias is used for pricing lookup only after
+ * it has been converted to a canonical ID.
*
* @param modelDefinition canonical model metadata
- * @return model definition과 연결된 pricing snapshot
+ * @return pricing snapshot associated with the model definition
*/
default Optional resolveSnapshot(ModelDefinition modelDefinition) {
Objects.requireNonNull(modelDefinition, "modelDefinition must not be null");
@@ -59,11 +60,12 @@ default Optional resolveSnapshot(ModelDefinition modelDefinitio
}
/**
- * canonical model registry를 거쳐 alias 가격 조회를 canonical 정책으로 고정합니다.
+ * Routes alias pricing lookup through the canonical model registry so the
+ * lookup is fixed to the canonical policy.
*
* @param modelRegistry canonical model lookup
- * @param modelIdOrAlias canonical id 또는 exact alias
- * @return canonical model definition과 일치하는 pricing snapshot
+ * @param modelIdOrAlias canonical ID or exact alias
+ * @return pricing snapshot matching the canonical model definition
*/
default Optional resolveSnapshot(
ModelRegistry modelRegistry,
@@ -75,25 +77,25 @@ default Optional resolveSnapshot(
}
/**
- * 모델과 토큰 타입에 대한 가격 결정 결과를 조회합니다.
- * @param modelId 모델 식별자
- * @param tokenType 토큰 타입
- * @return 가격 결정 결과
+ * Resolves pricing for a model and token type.
+ * @param modelId model identifier
+ * @param tokenType token type
+ * @return pricing resolution
*/
PricingResolution resolveRate(String modelId, TokenType tokenType);
/**
- * 모델과 토큰 타입에 대한 가격 결정 결과를 기대 통화 기준으로 조회합니다.
- * @param modelId 모델 식별자
- * @param tokenType 토큰 타입
- * @param expectedCurrency 기대 통화
- * @return 가격 결정 결과
+ * Resolves pricing for a model and token type against an expected currency.
+ * @param modelId model identifier
+ * @param tokenType token type
+ * @param expectedCurrency expected currency
+ * @return pricing resolution
*/
PricingResolution resolveRate(String modelId, TokenType tokenType, Currency expectedCurrency);
/**
- * 새로운 가격 정책을 등록하거나 업데이트합니다.
- * @param plan 가격 정책
+ * Registers or updates a pricing policy.
+ * @param plan pricing policy
*/
void registerPlan(PricingPlan plan);
}
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/RoundingPolicy.java b/token-pilot-core/src/main/java/io/tokenpilot/core/RoundingPolicy.java
index 74ae983..549832f 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/RoundingPolicy.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/RoundingPolicy.java
@@ -6,7 +6,8 @@
import java.util.Objects;
/**
- * 내부 정밀도를 보존한 비용을 외부 표시/청구 경계에서 반올림하는 정책입니다.
+ * Policy for rounding costs that preserve internal precision at an external
+ * display or billing boundary.
*/
public final class RoundingPolicy {
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/TokenBudget.java b/token-pilot-core/src/main/java/io/tokenpilot/core/TokenBudget.java
index 4479e8e..1ed9508 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/TokenBudget.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/TokenBudget.java
@@ -4,18 +4,18 @@
import io.tokenpilot.core.domain.TokenCountResult;
/**
- * provider 호출 전에 입력 token과 예약 출력량의 context admission을 판단합니다.
+ * Determines context admission for input tokens and reserved output before provider invocation.
*/
public interface TokenBudget {
/**
- * model context window 안에 요청이 들어갈 수 있는지 fail-closed로 확인합니다.
+ * Fail-closed check that the request can fit within the model context window.
*
- * @param modelId canonical model id 또는 exact alias
- * @param input 입력 token 계산 결과
- * @param reservedOutputTokens 호출 전에 확보할 최대 출력 token 수
- * @return FITS, EXCEEDS 또는 INDETERMINATE를 구분한 결과
- * @throws IllegalArgumentException reservedOutputTokens가 음수인 경우
+ * @param modelId canonical model ID or exact alias
+ * @param input input token count result
+ * @param reservedOutputTokens maximum output tokens secured before the call
+ * @return result distinguishing FITS, EXCEEDS, and INDETERMINATE
+ * @throws IllegalArgumentException when reservedOutputTokens is negative
*/
BudgetResult check(
String modelId,
@@ -24,10 +24,10 @@ BudgetResult check(
);
/**
- * FITS가 아니면 provider 호출을 진행하지 않도록 예외를 던집니다.
+ * Throws an exception to prevent provider invocation when the result is not FITS.
*
- * @return FITS 결과
- * @throws IllegalStateException admission 결과가 FITS가 아닌 경우
+ * @return FITS result
+ * @throws IllegalStateException when the admission result is not FITS
*/
default BudgetResult requireFits(
String modelId,
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/TokenEstimator.java b/token-pilot-core/src/main/java/io/tokenpilot/core/TokenEstimator.java
index 1500c44..407142b 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/TokenEstimator.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/TokenEstimator.java
@@ -3,18 +3,19 @@
import io.tokenpilot.core.domain.TokenCountResult;
/**
- * LLM 호출 전에 입력의 토큰 수를 계산하는 framework-independent 계약입니다.
- * 계산 결과에는 토큰 수뿐 아니라 계산 범위와 estimator/tokenization 기준이 포함됩니다.
+ * Framework-independent contract for counting input tokens before an LLM call.
+ * Results include not only the token count but also the count scope and
+ * estimator/tokenization basis.
*/
public interface TokenEstimator {
/**
- * 전달된 문자열의 토큰 수를 계산합니다.
- * 빈 문자열은 unavailable이 아닌 0-token counted 결과로 계산해야 합니다.
+ * Counts tokens in the supplied string.
+ * An empty string must produce a counted result with zero tokens, not an unavailable result.
*
- * @param text 계산할 문자열
- * @return 계산값과 계산 기준을 보존한 결과
- * @throws NullPointerException text가 null인 경우
+ * @param text string to count
+ * @return result preserving the count and counting basis
+ * @throws NullPointerException when text is null
*/
TokenCountResult estimate(String text);
}
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/AdmissionReason.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/AdmissionReason.java
index 4d89c7e..62c451d 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/AdmissionReason.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/AdmissionReason.java
@@ -1,19 +1,19 @@
package io.tokenpilot.core.domain;
/**
- * context admission 판정의 제한된 사유입니다.
+ * Enumerates the bounded reasons for a context admission decision.
*/
public enum AdmissionReason {
- /** 입력과 예약 출력량이 context window 안에 있습니다. */
+ /** The input and reserved output fit within the context window. */
WITHIN_CONTEXT,
- /** 보수적 입력 상한 또는 예약 출력량이 context window를 넘습니다. */
+ /** The conservative input bound or reserved output exceeds the context window. */
CONTEXT_EXCEEDED,
- /** TEXT_ONLY 등 전체 요청을 포함하지 않은 결과입니다. */
+ /** The result does not include the complete request, such as a TEXT_ONLY result. */
INCOMPLETE_SCOPE,
- /** estimator 결과와 모델의 tokenizer 기준이 호환되지 않습니다. */
+ /** The estimator result is incompatible with the model tokenizer basis. */
INCOMPATIBLE_TOKENIZER,
- /** registry에 모델이 등록되어 있지 않습니다. */
+ /** The model is not registered. */
UNKNOWN_MODEL,
- /** token 계산 결과를 사용할 수 없습니다. */
+ /** The token count result is unavailable. */
COUNT_UNAVAILABLE
}
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/AdmissionStatus.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/AdmissionStatus.java
index a263d1a..782ae1c 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/AdmissionStatus.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/AdmissionStatus.java
@@ -1,13 +1,13 @@
package io.tokenpilot.core.domain;
/**
- * 입력 token과 예약 출력량의 context admission 상태입니다.
+ * Context admission status for input tokens and reserved output tokens.
*/
public enum AdmissionStatus {
- /** 요청 전체가 안전한 상한 안에 있습니다. */
+ /** The complete request fits within the safe upper bound. */
FITS,
- /** 선언된 보수적 상한만으로 context window를 넘습니다. */
+ /** The declared conservative upper bound exceeds the context window. */
EXCEEDS,
- /** 안전하게 허용 또는 초과를 확정할 정보가 부족합니다. */
+ /** There is not enough information to safely confirm admission or overflow. */
INDETERMINATE
}
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/BudgetResult.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/BudgetResult.java
index fd1f995..79a701e 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/BudgetResult.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/BudgetResult.java
@@ -5,10 +5,11 @@
import java.util.OptionalLong;
/**
- * context admission 결과와 판정에 사용한 immutable metadata입니다.
+ * Context admission result and immutable metadata used for the decision.
*
- * {@code remainingTokens}는 FITS 결과에서만 제공됩니다. INDETERMINATE와
- * EXCEEDS에서 이를 빈 값으로 유지해 가짜 여유량을 provider 경계에 전달하지 않습니다.
+ * {@code remainingTokens} is provided only for FITS results. It remains empty
+ * for INDETERMINATE and EXCEEDS so a false remaining capacity is not passed to
+ * the provider boundary.
*/
public record BudgetResult(
AdmissionStatus status,
@@ -87,9 +88,9 @@ public record BudgetResult(
}
/**
- * FITS 상태인지 반환합니다.
+ * Returns whether the result has FITS status.
*
- * @return status가 FITS이면 true
+ * @return {@code true} when status is FITS
*/
public boolean fits() {
return status == AdmissionStatus.FITS;
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/Cost.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/Cost.java
index b3be52a..4520a66 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/Cost.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/Cost.java
@@ -5,12 +5,13 @@
import java.util.Objects;
/**
- * 계산된 AI 호출 비용 정보.
+ * Calculated AI call cost.
*
- * 내부 계산 정밀도를 그대로 보존하며 표시/청구를 위한 반올림은 외부 경계에서 적용합니다.
+ * Preserves internal calculation precision; rounding for display or billing is
+ * applied at an external boundary.
*
- * @param value 0 이상의 비용
- * @param currency 비용 통화
+ * @param value non-negative cost
+ * @param currency cost currency
*/
public record Cost(
BigDecimal value,
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/CostRecordedEvent.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/CostRecordedEvent.java
index e0e7aec..efb36a6 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/CostRecordedEvent.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/CostRecordedEvent.java
@@ -3,8 +3,8 @@
import java.util.Map;
/**
- * 토큰 사용량과 비용이 기록되었을 때 발생하는 이벤트 데이터.
- * 불변 객체(Record)로 정의하여 이벤트 전달의 안정성을 보장합니다.
+ * Event data emitted when token usage and cost are recorded.
+ * Defined as an immutable record to keep event delivery stable.
*/
public record CostRecordedEvent(
String modelId,
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/ModelDefinition.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/ModelDefinition.java
index 84d8470..9af9b6a 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/ModelDefinition.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/ModelDefinition.java
@@ -9,17 +9,18 @@
import java.util.Set;
/**
- * context admission과 pricing policy 조회에 함께 사용하는 immutable model catalog 정의입니다.
+ * Immutable model catalog definition used for both context admission and
+ * pricing policy lookup.
*
- * @param canonicalModelId versioned canonical model id
- * @param aliases canonical id로 해석할 exact aliases
- * @param encodingName 모델 encoding asset 이름
- * @param acceptedCompatibilityBasis heuristic/exact estimator가 사용할 수 있는 호환성 기준
- * @param maxContextTokens 모델 context window 크기
- * @param pricingPolicyId canonical pricing policy 식별자
- * @param catalogVersion model catalog 버전
- * @param sourceUri context/encoding metadata의 공식 출처
- * @param sourceAsOf source 확인 시점
+ * @param canonicalModelId versioned canonical model ID
+ * @param aliases exact aliases resolved to the canonical ID
+ * @param encodingName model encoding asset name
+ * @param acceptedCompatibilityBasis compatibility basis accepted by heuristic/exact estimators
+ * @param maxContextTokens model context window size
+ * @param pricingPolicyId canonical pricing policy identifier
+ * @param catalogVersion model catalog version
+ * @param sourceUri authoritative source for context/encoding metadata
+ * @param sourceAsOf time at which the source was checked
*/
public record ModelDefinition(
String canonicalModelId,
@@ -59,7 +60,8 @@ public record ModelDefinition(
}
/**
- * 기존 모델 정의 생성 코드와의 호환성을 위해 USD를 기본 통화로 사용합니다.
+ * Uses USD as the default currency for compatibility with existing model
+ * definition construction code.
*/
public ModelDefinition(
String canonicalModelId,
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightCostResult.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightCostResult.java
index d6fcf78..0db4fb1 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightCostResult.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightCostResult.java
@@ -4,28 +4,28 @@
import java.util.Objects;
/**
- * 호출 전 비용 계산 결과입니다. 숫자 상한이 있는 결과와 계산 불가 결과를
- * sealed hierarchy로 분리해 0원과 pricing 부재를 혼동하지 않습니다.
+ * Result of preflight cost calculation. A sealed hierarchy separates numeric
+ * bounds from unavailable results so zero cost is not confused with missing pricing.
*/
public sealed interface PreflightCostResult
permits PreflightCostResult.Bounded, PreflightCostResult.Unavailable {
/**
- * 하나의 불변 pricing snapshot으로 계산한 유한 비용 상한입니다.
- * 내부 계산에서는 반올림하지 않으며, context admission을 통과했다는 의미는 아닙니다.
- * 예약 계층은 반드시 {@code safeUpperBoundCost}를 사용해야 합니다.
+ * Finite cost bound calculated from one immutable pricing snapshot.
+ * Internal calculations are not rounded, and this does not imply that context admission passed.
+ * The reservation layer must use {@code safeUpperBoundCost}.
*
- * @param estimatedCost 관찰과 표시를 위한 예상 비용
- * @param safeUpperBoundCost 예약에 사용할 보수적인 비용 상한
- * @param inputEstimatedTokens 정보 제공용 입력 token 계산값
- * @param inputSafeUpperBoundTokens 보수적인 입력 token 안전 상한
- * @param reservedOutputTokens 예약한 최대 출력 token 수
- * @param canonicalModelId canonical model 식별자
- * @param pricingPolicyId 불변 pricing policy 식별자
- * @param catalogVersion 계산에 사용한 catalog 버전
- * @param pricingSnapshot 계산·예약·정산에 전달할 정확한 가격 snapshot
- * @param estimatorDescriptor token estimator 식별 정보
- * @param tokenizationBasis 검증된 tokenizer 호환성 기준
+ * @param estimatedCost estimated cost for observation and display
+ * @param safeUpperBoundCost conservative cost bound used for reservation
+ * @param inputEstimatedTokens informational input token count
+ * @param inputSafeUpperBoundTokens safe input token bound for conservative admission
+ * @param reservedOutputTokens maximum output tokens reserved
+ * @param canonicalModelId canonical model identifier
+ * @param pricingPolicyId immutable pricing policy identifier
+ * @param catalogVersion catalog version used for calculation
+ * @param pricingSnapshot exact pricing snapshot passed to calculation, reservation, and settlement
+ * @param estimatorDescriptor token estimator identity
+ * @param tokenizationBasis validated tokenizer compatibility basis
*/
record Bounded(
Cost estimatedCost,
@@ -42,7 +42,7 @@ record Bounded(
) implements PreflightCostResult {
/**
- * 비용, token 관계와 재현성 metadata를 검증합니다.
+ * Validates cost/token relationships and reproducibility metadata.
*/
public Bounded {
estimatedCost = Objects.requireNonNull(estimatedCost, "estimatedCost must not be null");
@@ -78,9 +78,9 @@ record Bounded(
}
/**
- * 계산된 비용의 통화를 반환합니다.
+ * Returns the currency of the calculated costs.
*
- * @return estimated cost와 safe upper bound cost가 공유하는 통화
+ * @return the currency shared by the estimated and safe upper-bound costs
*/
public Currency currency() {
return safeUpperBoundCost.currency();
@@ -88,18 +88,19 @@ public Currency currency() {
}
/**
- * fail-closed 경계에서 숫자 비용으로 취급할 수 없는 결과입니다.
- * 이 결과는 0원이나 임의의 기본 단가를 의미하지 않으며, provider 호출과 예약을
- * 계속할지 여부는 각 control 계층이 사유를 확인해 결정해야 합니다.
+ * Result that cannot be treated as a numeric cost at a fail-closed boundary.
+ * It does not mean zero cost or an arbitrary default rate. Each control layer
+ * must inspect the reason before deciding whether to continue with provider
+ * invocation and reservation.
*
- * @param reason 숫자 비용을 생성하지 못한 제한된 사유
- * @param canonicalModelId canonical model 식별자
- * @param pricingPolicyId 참조한 pricing policy 식별자
- * @param catalogVersion 참조한 catalog 버전
- * @param currency 모델이 요구한 비용 통화
- * @param reservedOutputTokens 요청한 최대 출력 token 수
- * @param estimatorDescriptor token estimator 식별 정보
- * @param tokenizationBasis token 결과의 tokenizer 호환성 기준
+ * @param reason bounded reason a numeric cost could not be produced
+ * @param canonicalModelId canonical model identifier
+ * @param pricingPolicyId referenced pricing policy identifier
+ * @param catalogVersion referenced catalog version
+ * @param currency cost currency required by the model
+ * @param reservedOutputTokens requested maximum output token count
+ * @param estimatorDescriptor token estimator identity
+ * @param tokenizationBasis tokenizer compatibility basis of the token result
*/
record Unavailable(
PreflightCostUnavailableReason reason,
@@ -113,7 +114,7 @@ record Unavailable(
) implements PreflightCostResult {
/**
- * Unavailable 사유와 진단 metadata를 검증합니다.
+ * Validates the unavailable reason and diagnostic metadata.
*/
public Unavailable {
reason = Objects.requireNonNull(reason, "reason must not be null");
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightCostUnavailableReason.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightCostUnavailableReason.java
index 7a08569..fd13804 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightCostUnavailableReason.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightCostUnavailableReason.java
@@ -1,25 +1,26 @@
package io.tokenpilot.core.domain;
/**
- * 호출 전 보수적 비용 상한을 숫자로 확정할 수 없는 제한된 사유입니다.
+ * Enumerates the bounded reasons a conservative preflight cost bound cannot be
+ * established as a number.
*/
public enum PreflightCostUnavailableReason {
- /** token 계산 결과를 사용할 수 없는 상태입니다. */
+ /** The token count result is unavailable. */
COUNT_UNAVAILABLE,
- /** 전체 REQUEST 범위를 포함하지 않은 token 결과입니다. */
+ /** The token result does not cover the complete REQUEST scope. */
INCOMPLETE_SCOPE,
- /** estimator와 model의 tokenizer 기준이 호환되지 않습니다. */
+ /** The estimator and model tokenizer bases are incompatible. */
INCOMPATIBLE_TOKENIZER,
- /** 참조한 pricing policy snapshot을 찾지 못했습니다. */
+ /** The referenced pricing policy snapshot was not found. */
PRICING_NOT_FOUND,
- /** 비용 계산에 필요한 기본 입력/출력 단가가 누락됐습니다. */
+ /** A required base input or output rate is missing. */
INCOMPLETE_PRICING,
- /** pricing policy가 호출 전에 유한한 상한을 제공할 수 없습니다. */
+ /** The pricing policy cannot provide a finite bound before the call. */
UNBOUNDED_PRICING,
- /** model과 pricing snapshot의 통화가 다릅니다. */
+ /** The model and pricing snapshot use different currencies. */
CURRENCY_MISMATCH,
- /** canonical model, policy 또는 catalog 식별자가 snapshot과 다릅니다. */
+ /** The canonical model, policy, or catalog identifier differs from the snapshot. */
PRICING_SNAPSHOT_MISMATCH,
- /** 가격 정밀도가 계산 범위를 벗어나 정형 비용 결과를 만들 수 없습니다. */
+ /** Price precision exceeded the calculation range, so a structured cost result could not be created. */
ARITHMETIC_FAILURE
}
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightDecisionEvent.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightDecisionEvent.java
index efd3eea..cd1313d 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightDecisionEvent.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightDecisionEvent.java
@@ -3,9 +3,9 @@
import java.util.Objects;
/**
- * provider 호출 전 context admission 판정을 전달하는 framework-independent 이벤트입니다.
+ * Framework-independent event carrying a context admission decision before provider invocation.
*
- * @param result bounded status/reason과 판정 metadata를 포함한 immutable 결과
+ * @param result immutable result containing bounded status/reason and decision metadata
*/
public record PreflightDecisionEvent(BudgetResult result) {
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightPricingContext.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightPricingContext.java
index 42a6541..e84d7e9 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightPricingContext.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PreflightPricingContext.java
@@ -5,27 +5,29 @@
import java.util.Optional;
/**
- * canonical model 해석 이후 preflight 비용 계산에 전달하는 불변 pricing projection입니다.
+ * Immutable pricing projection passed to preflight cost calculation after the
+ * canonical model has been resolved.
*
- * alias는 ModelRegistry에서 먼저 canonical model로 해석해야 하며, 이 문맥을
- * 임의의 문자열 조합으로 만들면 안 됩니다. {@code catalogVersion},
- * {@code pricingPolicyId}, 통화와 tokenizer 기준은 같은 모델 정의와 pricing
- * snapshot에서 함께 파생되어야 합니다.
+ * An alias must first be resolved to a canonical model by ModelRegistry; this
+ * context must not be assembled from arbitrary strings. {@code catalogVersion},
+ * {@code pricingPolicyId}, currency, and tokenizer basis must be derived
+ * together from the same model definition and pricing snapshot.
*
- * 계산 가능한 문맥은 registry에서 한 번 확정한 동일한 불변 snapshot을 함께
- * 보관해야 합니다. 계산·예약·정산 과정에서 mutable registry를 다시 조회하면
- * 서로 다른 가격을 섞을 수 있습니다. 가격을 찾지 못한 문맥은 빈 snapshot으로
- * 만들 수 있으며, 계산기는 이를 {@code PRICING_NOT_FOUND}로 반환합니다.
- * {@link UpperBoundCapability} 역시 호출자가 임의로 선언하는 값이 아니라 검증된
- * pricing policy의 결과여야 합니다.
+ * A calculable context must retain the same immutable snapshot resolved once
+ * from the registry. Looking up a mutable registry again during calculation,
+ * reservation, or settlement could mix prices from different observations. A
+ * context with no pricing can use an empty snapshot, which the calculator
+ * returns as {@code PRICING_NOT_FOUND}. {@link UpperBoundCapability} must also
+ * be the result of a validated pricing policy rather than an arbitrary caller
+ * declaration.
*
- * @param canonicalModelId canonical model 식별자
- * @param pricingPolicyId 불변 pricing policy 식별자
- * @param catalogVersion model과 pricing catalog의 버전
- * @param tokenizationBasis 모델이 허용하는 tokenizer 호환성 기준
- * @param currency 모델이 요구하는 비용 통화
- * @param upperBoundCapability pricing policy가 유한한 비용 상한을 제공할 수 있는지 나타내는 결과
- * @param pricingSnapshot 이 계산·예약·정산에 사용할 확정 가격 snapshot
+ * @param canonicalModelId canonical model identifier
+ * @param pricingPolicyId immutable pricing policy identifier
+ * @param catalogVersion model and pricing catalog version
+ * @param tokenizationBasis tokenizer compatibility basis accepted by the model
+ * @param currency cost currency required by the model
+ * @param upperBoundCapability result indicating whether the pricing policy can provide a finite cost bound
+ * @param pricingSnapshot resolved pricing snapshot used for calculation, reservation, and settlement
*/
public record PreflightPricingContext(
String canonicalModelId,
@@ -37,7 +39,7 @@ public record PreflightPricingContext(
Optional pricingSnapshot
) {
- /** 모든 식별자와 호환성 metadata가 비어 있지 않은지 검증합니다. */
+ /** Validates that identifiers and compatibility metadata are not blank. */
public PreflightPricingContext {
canonicalModelId = requireText(canonicalModelId, "canonicalModelId");
pricingPolicyId = requireText(pricingPolicyId, "pricingPolicyId");
@@ -52,15 +54,15 @@ public record PreflightPricingContext(
}
/**
- * 아직 가격 snapshot을 찾지 못한 문맥을 생성합니다.
- * 계산기는 이 문맥을 숫자 비용이 아닌 {@code PRICING_NOT_FOUND}로 처리합니다.
+ * Creates a context for which a pricing snapshot has not yet been found.
+ * The calculator handles this context as {@code PRICING_NOT_FOUND}, not as a numeric cost.
*
- * @param canonicalModelId canonical model 식별자
- * @param pricingPolicyId pricing policy 식별자
- * @param catalogVersion model과 pricing catalog의 버전
- * @param tokenizationBasis tokenizer 호환성 기준
- * @param currency 비용 통화
- * @param upperBoundCapability 가격 정책의 상한 제공 가능 여부
+ * @param canonicalModelId canonical model identifier
+ * @param pricingPolicyId pricing policy identifier
+ * @param catalogVersion model and pricing catalog version
+ * @param tokenizationBasis tokenizer compatibility basis
+ * @param currency cost currency
+ * @param upperBoundCapability whether the pricing policy can provide a cost bound
*/
public PreflightPricingContext(
String canonicalModelId,
@@ -88,11 +90,11 @@ private static String requireText(String value, String name) {
return value;
}
- /** pricing policy가 호출 전에 유한한 보수적 상한을 제공할 수 있는지 나타냅니다. */
+ /** Describes whether the pricing policy can provide a finite conservative bound before a call. */
public enum UpperBoundCapability {
- /** 모든 적용 경로에 유한한 최대 단가가 있습니다. */
+ /** Every applicable path has a finite maximum rate. */
FINITE,
- /** 하나 이상의 적용 경로에 유한한 최대 단가가 없습니다. */
+ /** At least one applicable path has no finite maximum rate. */
UNBOUNDED
}
}
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingMissingEvent.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingMissingEvent.java
index 94580f1..dc78e55 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingMissingEvent.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingMissingEvent.java
@@ -3,10 +3,10 @@
import java.util.Objects;
/**
- * 명시적인 missing-pricing 판정을 전달하는 framework-independent 이벤트입니다.
+ * Framework-independent event carrying an explicit missing-pricing decision.
*
- * @param policy missing pricing 처리 정책
- * @param resolution missing plan/rate 판정
+ * @param policy policy for handling missing pricing
+ * @param resolution missing-plan or missing-rate decision
*/
public record PricingMissingEvent(
MissingPricingPolicy policy,
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingPlan.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingPlan.java
index 9de0d72..d14e1f0 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingPlan.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingPlan.java
@@ -8,12 +8,12 @@
import java.util.Objects;
/**
- * 특정 모델의 가격 정책 정보.
- * {@link TokenType} 별로 1,000(1K) 토큰당 가격을 관리합니다.
+ * Pricing policy information for a specific model.
+ * Maintains the price per 1,000 (1K) tokens for each {@link TokenType}.
*
- * @param modelId 모델 식별자 (예: gpt-4o, claude-3-5-sonnet)
- * @param rates 1K 토큰 타입별 단가 (Map)
- * @param currency 통화 (기본값: USD)
+ * @param modelId model identifier, such as gpt-4o or claude-3-5-sonnet
+ * @param rates per-1K-token rate by token type
+ * @param currency currency, defaulting to USD
*/
public record PricingPlan(
String modelId,
@@ -48,21 +48,21 @@ public PricingPlan(String modelId, Map rates, Currency cu
}
/**
- * 기본 입력/출력 단가와 통화를 사용하는 {@link PricingPlan}을 생성합니다.
+ * Creates a {@link PricingPlan} with basic input/output rates and a currency.
*/
public PricingPlan(String modelId, BigDecimal promptPricePerK, BigDecimal completionPricePerK, Currency currency) {
this(modelId, DEFAULT_PRICING_POLICY_ID, createRates(promptPricePerK, completionPricePerK), currency);
}
/**
- * 기본 입력/출력 단가와 pricing policy id, 통화를 사용하는 {@link PricingPlan}을 생성합니다.
+ * Creates a {@link PricingPlan} with basic input/output rates, a pricing policy ID, and a currency.
*/
public PricingPlan(String modelId, String pricingPolicyId, BigDecimal promptPricePerK, BigDecimal completionPricePerK, Currency currency) {
this(modelId, pricingPolicyId, createRates(promptPricePerK, completionPricePerK), currency);
}
/**
- * 기본 입력/출력 단가만 사용하는 {@link PricingPlan}을 생성합니다. 기본 통화는 USD입니다.
+ * Creates a {@link PricingPlan} with basic input/output rates and the default USD currency.
*/
public PricingPlan(String modelId, BigDecimal promptPricePerK, BigDecimal completionPricePerK) {
this(modelId, promptPricePerK, completionPricePerK, Currency.getInstance("USD"));
@@ -76,21 +76,22 @@ private static Map createRates(BigDecimal prompt, BigDeci
}
/**
- * 기본 입력 단가를 반환합니다. (하위 호환성)
+ * Returns the basic input rate for compatibility.
*/
public BigDecimal promptPricePerK() {
return getRate(TokenType.PROMPT);
}
/**
- * 기본 출력 단가를 반환합니다. (하위 호환성)
+ * Returns the basic output rate for compatibility.
*/
public BigDecimal completionPricePerK() {
return getRate(TokenType.COMPLETION);
}
/**
- * 특정 토큰 타입의 단가를 가져옵니다. 없을 시 계층 구조에 따라 대체값을 반환합니다.
+ * Returns the rate for a token type, falling back through the rate hierarchy
+ * when a direct rate is absent.
* REASONING -> COMPLETION
* CACHE_READ_PROMPT, CACHE_CREATION_PROMPT -> PROMPT
*/
@@ -105,9 +106,10 @@ public BigDecimal getRate(TokenType type) {
}
/**
- * 특정 토큰 타입의 가격 결정 결과를 반환합니다.
- * 명시적으로 등록된 0 rate는 {@link PricingResolution#RESOLVED}로,
- * 누락된 rate는 {@link PricingResolution#MISSING_RATE}로 표현합니다.
+ * Returns the pricing resolution for a token type.
+ * An explicitly registered zero rate is represented as
+ * {@link PricingResolution#RESOLVED}; a missing rate is represented as
+ * {@link PricingResolution#MISSING_RATE}.
*/
public PricingResolution resolveRate(TokenType type) {
if (rates.containsKey(type)) {
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingReconciliationResult.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingReconciliationResult.java
index 8964bd1..5df9e75 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingReconciliationResult.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingReconciliationResult.java
@@ -1,7 +1,7 @@
package io.tokenpilot.core.domain;
/**
- * Actual reconciliation 결과.
+ * Actual reconciliation result.
*/
public enum PricingReconciliationResult {
RECONCILED,
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingSnapshot.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingSnapshot.java
index a945d1b..5cc20b1 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingSnapshot.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/PricingSnapshot.java
@@ -9,7 +9,7 @@
import java.util.Objects;
/**
- * provider 호출 전에 확정된 요청 단위 pricing snapshot.
+ * Request-scoped pricing snapshot resolved before provider invocation.
*/
public record PricingSnapshot(
String modelId,
@@ -57,17 +57,17 @@ public static PricingSnapshot from(PricingPlan plan, String catalogVersion, Inst
}
/**
- * 가격 조회 시각을 제외하고 실제 비용 책임을 결정하는 terms가 같은지 반환합니다.
- * {@link #checkedAt()}은 같은 요청의 재시도에서 달라질 수 있는 관측 metadata이므로
- * 멱등성 fingerprint에는 포함하지 않습니다.
+ * Returns whether the billing terms are equal, excluding the pricing lookup time.
+ * {@link #checkedAt()} is observational metadata that may differ on retries of
+ * the same request, so it is excluded from the idempotency fingerprint.
*/
public boolean hasSameBillingTerms(PricingSnapshot other) {
return haveSameBillingTerms(this, other);
}
/**
- * 두 snapshot의 비용 책임 terms를 비교합니다. 테스트 double처럼 rate map이 없는
- * 호환 snapshot은 양쪽 모두 없을 때 같은 것으로 취급합니다.
+ * Compares the billing terms of two snapshots. Compatible snapshots without a
+ * rate map, such as test doubles, are considered equal when both maps are absent.
*/
public static boolean haveSameBillingTerms(
PricingSnapshot first,
@@ -95,7 +95,7 @@ public static boolean haveSameBillingTerms(
}
/**
- * {@link #hasSameBillingTerms(PricingSnapshot)}와 일치하는 bounded hash code입니다.
+ * Bounded hash code consistent with {@link #hasSameBillingTerms(PricingSnapshot)}.
*/
public int billingTermsHashCode() {
int result = Objects.hash(modelId, pricingPolicyId, catalogVersion, currency);
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountAccuracy.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountAccuracy.java
index 827996c..fe625d7 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountAccuracy.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountAccuracy.java
@@ -1,16 +1,16 @@
package io.tokenpilot.core.domain;
/**
- * counted token 결과의 계산 정확도를 나타냅니다.
+ * Describes the accuracy of a counted token result.
*/
public enum TokenCountAccuracy {
/**
- * 계산값이 정확하며 tokens와 safeUpperBoundTokens가 동일한 상태입니다.
+ * The count is exact, so tokens and safeUpperBoundTokens are equal.
*/
EXACT,
/**
- * 계산값이 추정치이며 safeUpperBoundTokens가 tokens 이상인 상태입니다.
+ * The count is estimated, so safeUpperBoundTokens is greater than or equal to tokens.
*/
HEURISTIC
}
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountResult.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountResult.java
index 40479bc..b5bf454 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountResult.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountResult.java
@@ -5,8 +5,8 @@
import java.util.OptionalLong;
/**
- * LLM 호출 전에 수행한 token 계산의 immutable snapshot입니다.
- * 계산된 결과와 계산할 수 없는 결과를 상호 배타적으로 표현합니다.
+ * Immutable snapshot of a token count performed before an LLM call.
+ * Represents counted and unavailable results as mutually exclusive states.
*/
public final class TokenCountResult {
private final ResultState state;
@@ -27,17 +27,17 @@ private TokenCountResult(
}
/**
- * token 계산값이 존재하는 counted 결과를 생성합니다.
+ * Creates a counted result containing a token count.
*
- * @param tokens 정보 제공용 token 계산값
- * @param safeUpperBoundTokens 보수적 admission 판단에 사용할 안전 상한
- * @param accuracy 계산 정확도
- * @param scope 계산에 포함된 입력 범위
- * @param estimatorDescriptor 계산에 사용된 estimator 식별 정보
- * @param tokenizationBasis 모델 encoding과 비교할 tokenization 기준
- * @return 검증된 counted 결과
- * @throws IllegalArgumentException token 값이나 accuracy별 상한 관계가 유효하지 않은 경우
- * @throws NullPointerException accuracy, scope, estimatorDescriptor 또는 tokenizationBasis가 null인 경우
+ * @param tokens informational token count
+ * @param safeUpperBoundTokens safe upper bound for conservative admission decisions
+ * @param accuracy counting accuracy
+ * @param scope input scope included in the count
+ * @param estimatorDescriptor estimator identity used for counting
+ * @param tokenizationBasis tokenization basis compared with the model encoding
+ * @return a validated counted result
+ * @throws IllegalArgumentException when token values or the accuracy-specific bound relationship is invalid
+ * @throws NullPointerException when accuracy, scope, estimatorDescriptor, or tokenizationBasis is null
*/
public static TokenCountResult counted(
long tokens,
@@ -96,14 +96,14 @@ private static void validateCounted(
}
/**
- * token 계산 불가 결과를 생성합니다.
+ * Creates an unavailable token count result.
*
- * @param reason 계산 결과를 제공하지 못한 제한된 사유
- * @param scope 계산하려고 한 입력 범위
- * @param estimatorDescriptor 계산을 시도한 estimator 식별 정보
- * @param tokenizationBasis estimator가 선언한 tokenization 기준
- * @return unavailable 결과
- * @throws NullPointerException 인자가 null인 경우
+ * @param reason bounded reason the count could not be provided
+ * @param scope input scope that was requested
+ * @param estimatorDescriptor estimator identity used for the attempt
+ * @param tokenizationBasis tokenization basis declared by the estimator
+ * @return an unavailable result
+ * @throws NullPointerException when an argument is null
*/
public static TokenCountResult unavailable(
TokenCountUnavailableReason reason,
@@ -125,18 +125,18 @@ public static TokenCountResult unavailable(
}
/**
- * token 계산값이 존재하는 counted 상태인지 확인합니다.
+ * Returns whether this result is a counted state containing a token value.
*
- * @return counted 상태이면 true
+ * @return {@code true} when this is a counted state
*/
public boolean isCounted() {
return state instanceof Counted;
}
/**
- * counted 결과가 정확한 계산값인지 확인합니다.
+ * Returns whether the counted result is exact.
*
- * @return accuracy가 EXACT이면 true, HEURISTIC 또는 unavailable이면 false
+ * @return {@code true} when accuracy is EXACT; {@code false} for HEURISTIC or unavailable
*/
public boolean isExact() {
if (state instanceof Counted counted) {
@@ -146,18 +146,18 @@ public boolean isExact() {
}
/**
- * token 계산값을 제공하지 못한 unavailable 상태인지 확인합니다.
+ * Returns whether the token count is unavailable.
*
- * @return unavailable 상태이면 true
+ * @return {@code true} when this is an unavailable state
*/
public boolean isUnavailable() {
return state instanceof Unavailable;
}
/**
- * 정보 제공용 token 계산값을 반환합니다.
+ * Returns the informational token count.
*
- * @return counted 상태의 계산값, unavailable 상태이면 empty
+ * @return the counted value, or empty for an unavailable state
*/
public OptionalLong tokens() {
if (state instanceof Counted counted) {
@@ -167,9 +167,9 @@ public OptionalLong tokens() {
}
/**
- * 보수적 admission 판단에 사용할 안전 상한을 반환합니다.
+ * Returns the safe upper bound used for conservative admission decisions.
*
- * @return counted 상태의 안전 상한, unavailable 상태이면 empty
+ * @return the counted safe upper bound, or empty for an unavailable state
*/
public OptionalLong safeUpperBoundTokens() {
if (state instanceof Counted counted) {
@@ -179,9 +179,9 @@ public OptionalLong safeUpperBoundTokens() {
}
/**
- * counted 결과의 계산 정확도를 반환합니다.
+ * Returns the counting accuracy of a counted result.
*
- * @return counted 상태의 정확도, unavailable 상태이면 empty
+ * @return the counted accuracy, or empty for an unavailable state
*/
public Optional accuracy() {
if (state instanceof Counted counted) {
@@ -191,9 +191,9 @@ public Optional accuracy() {
}
/**
- * token 계산 결과를 제공하지 못한 사유를 반환합니다.
+ * Returns the reason the token count could not be provided.
*
- * @return unavailable 상태의 사유, counted 상태이면 empty
+ * @return the unavailable reason, or empty for a counted state
*/
public Optional unavailableReason() {
if (state instanceof Unavailable(TokenCountUnavailableReason reason)) {
@@ -203,27 +203,27 @@ public Optional unavailableReason() {
}
/**
- * token 계산에 포함된 입력 범위를 반환합니다.
+ * Returns the input scope included in the token count.
*
- * @return 생성 시점의 계산 범위
+ * @return the scope at creation time
*/
public TokenCountScope scope() {
return scope;
}
/**
- * token 계산에 사용된 estimator 식별 정보를 반환합니다.
+ * Returns the estimator identity used for token counting.
*
- * @return 생성 시점의 estimator 식별 정보
+ * @return the estimator identity at creation time
*/
public TokenEstimatorDescriptor estimatorDescriptor() {
return estimatorDescriptor;
}
/**
- * 모델 encoding과 비교할 tokenization 기준을 반환합니다.
+ * Returns the tokenization basis to compare with the model encoding.
*
- * @return 생성 시점의 tokenization 기준
+ * @return the tokenization basis at creation time
*/
public TokenizationBasis tokenizationBasis() {
return tokenizationBasis;
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountScope.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountScope.java
index 600ed30..f18523e 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountScope.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountScope.java
@@ -1,19 +1,22 @@
package io.tokenpilot.core.domain;
/**
- * token 계산에 포함된 입력 범위를 나타냅니다.
- * 기존 결과의 scope만 변경할 수 없으며, 더 넓은 범위를 계산했다면 새로운 결과를 생성해야 합니다.
+ * Describes the input scope included in a token count.
+ * An existing result cannot be relabeled with a different scope; a new result
+ * must be created when a broader scope has been counted.
*/
public enum TokenCountScope {
/**
- * 전달된 문자열의 UTF-8 내용만 계산한 범위입니다.
- * role, tool schema, media metadata, structured-output schema와 provider framing은 포함하지 않으므로
- * 전체 요청이 context window에 들어간다는 근거로 사용할 수 없습니다.
+ * Counts only the UTF-8 content of the supplied strings.
+ * It excludes roles, tool schemas, media metadata, structured-output schemas,
+ * and provider framing, so it cannot prove that the complete request fits in
+ * the context window.
*/
TEXT_ONLY,
/**
- * 실제 전송 요청의 tokenizable content와 필요한 framing/headroom을 모두 포함한 범위입니다.
+ * Includes all tokenizable content and required framing/headroom for the
+ * request that will actually be sent.
*/
REQUEST
}
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountUnavailableReason.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountUnavailableReason.java
index 59e71d3..32c44de 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountUnavailableReason.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenCountUnavailableReason.java
@@ -1,11 +1,11 @@
package io.tokenpilot.core.domain;
/**
- * token estimator가 계산 결과를 제공하지 못한 이유를 제한된 값으로 나타냅니다.
+ * Enumerates the bounded reasons a token estimator cannot provide a result.
*/
public enum TokenCountUnavailableReason {
/**
- * 설정된 estimator가 token 계산 결과를 제공할 수 없는 상태입니다.
+ * The configured estimator cannot provide a token count result.
*/
ESTIMATOR_UNAVAILABLE
}
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenEstimatorDescriptor.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenEstimatorDescriptor.java
index 996c549..1f61d75 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenEstimatorDescriptor.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenEstimatorDescriptor.java
@@ -1,10 +1,10 @@
package io.tokenpilot.core.domain;
/**
- * token 계산에 사용된 estimator 구현과 버전을 식별합니다.
+ * Identifies the estimator implementation and version used for token counting.
*
- * @param estimatorId 안정적인 estimator 구현 식별자
- * @param estimatorVersion 계산식이나 asset 변경을 구분하는 버전
+ * @param estimatorId stable estimator implementation identifier
+ * @param estimatorVersion version distinguishing formula or asset changes
*/
public record TokenEstimatorDescriptor(
String estimatorId,
@@ -12,9 +12,9 @@ public record TokenEstimatorDescriptor(
) {
/**
- * estimator 식별자와 버전이 null 또는 blank가 아닌지 검증합니다.
+ * Validates that the estimator identifier and version are not null or blank.
*
- * @throws IllegalArgumentException estimatorId 또는 estimatorVersion이 null이거나 blank인 경우
+ * @throws IllegalArgumentException when estimatorId or estimatorVersion is null or blank
*/
public TokenEstimatorDescriptor {
estimatorId = requireText(estimatorId, "estimatorId");
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenType.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenType.java
index 94ddd9b..d79e52d 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenType.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenType.java
@@ -1,33 +1,33 @@
package io.tokenpilot.core.domain;
/**
- * AI 모델 호출 시 발생하는 토큰의 세부 유형.
+ * Detailed token types produced by an AI model call.
*/
public enum TokenType {
- /** 일반 입력 (Prompt) */
+ /** Regular input (prompt). */
PROMPT,
- /** 일반 출력 (Completion) */
+ /** Regular output (completion). */
COMPLETION,
- /** 추론 (Reasoning) - 주로 출력 계열 */
+ /** Reasoning tokens, usually part of the output category. */
REASONING,
- /** 캐시에서 읽은 입력 (Cache Read Prompt) */
+ /** Input read from the cache (cache-read prompt). */
CACHE_READ_PROMPT,
- /** 캐시에 새로 저장한 입력 (Cache Creation Prompt) */
+ /** Input newly stored in the cache (cache-creation prompt). */
CACHE_CREATION_PROMPT;
/**
- * 해당 토큰 타입이 입력(Prompt) 계열인지 확인합니다.
+ * Returns whether this token type belongs to the input (prompt) category.
*
- * @return 입력 계열이면 {@code true}
+ * @return {@code true} when this is an input category
*/
public boolean isPrompt() {
return this == PROMPT || this == CACHE_READ_PROMPT || this == CACHE_CREATION_PROMPT;
}
/**
- * 해당 토큰 타입이 출력(Completion) 계열인지 확인합니다.
+ * Returns whether this token type belongs to the output (completion) category.
*
- * @return 출력 계열이면 {@code true}
+ * @return {@code true} when this is an output category
*/
public boolean isCompletion() {
return this == COMPLETION || this == REASONING;
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenUsage.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenUsage.java
index 6c82eaf..1fa03ed 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenUsage.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenUsage.java
@@ -4,14 +4,14 @@
import java.util.Objects;
/**
- * AI 모델 호출 시 발생하는 토큰 사용량 정보.
- * 전체 입력/출력 토큰과 세부 사용량을 관리합니다.
+ * Token usage information produced by an AI model call.
+ * Manages inclusive input/output totals and usage details.
*
- * @param inputTokens 전체 입력 토큰
- * @param outputTokens 전체 출력 토큰
- * @param details 세부 토큰 사용량
- * @param source 사용량 값의 출처
- * @param metadata 추가 메타데이터 (예: 모델 정보 등)
+ * @param inputTokens total input tokens
+ * @param outputTokens total output tokens
+ * @param details detailed token usage
+ * @param source source of the usage values
+ * @param metadata additional metadata, such as model information
*/
public record TokenUsage(
long inputTokens,
@@ -21,10 +21,11 @@ public record TokenUsage(
Map metadata
) {
/**
- * 토큰 총량과 세부량의 포함 관계를 검증하고 metadata를 불변 복사합니다.
+ * Validates containment between token totals and details, then makes an
+ * immutable copy of metadata.
*
- * @throws IllegalArgumentException 토큰 수가 음수이거나 세부량이 총량을 초과한 경우
- * @throws NullPointerException details/source가 null이거나 metadata에 null key/value가 있는 경우
+ * @throws IllegalArgumentException when token counts are negative or details exceed their totals
+ * @throws NullPointerException when details/source is null or metadata contains a null key/value
*/
public TokenUsage {
if (inputTokens < 0) {
@@ -68,11 +69,11 @@ public record TokenUsage(
}
/**
- * 기본 입력/출력 토큰을 사용하는 {@link TokenUsage}를 생성합니다.
+ * Creates {@link TokenUsage} with basic input and output token totals.
*
- * @param prompt 전체 입력 토큰
- * @param completion 전체 출력 토큰
- * @return 세부량과 metadata가 비어 있는 사용량
+ * @param prompt total input tokens
+ * @param completion total output tokens
+ * @return usage with no details or metadata
*/
public static TokenUsage from(long prompt, long completion) {
return new TokenUsage(
@@ -85,12 +86,12 @@ public static TokenUsage from(long prompt, long completion) {
}
/**
- * 입력/출력/추론 토큰을 포함하는 {@link TokenUsage}를 생성합니다.
+ * Creates {@link TokenUsage} with input, output, and reasoning tokens.
*
- * @param prompt 전체 입력 토큰
- * @param completion 전체 출력 토큰
- * @param reasoning 전체 출력에 포함된 reasoning 토큰
- * @return reasoning 세부량을 포함하는 사용량
+ * @param prompt total input tokens
+ * @param completion total output tokens
+ * @param reasoning reasoning tokens included in total output
+ * @return usage containing reasoning details
*/
public static TokenUsage from(long prompt, long completion, long reasoning) {
return new TokenUsage(
@@ -103,10 +104,10 @@ public static TokenUsage from(long prompt, long completion, long reasoning) {
}
/**
- * provider 응답에서 사용량 정보를 얻지 못한 상태를 생성합니다.
+ * Creates a state in which usage information was unavailable in the provider response.
*
- * @param metadata 보존할 응답 메타데이터
- * @return 출처가 {@link UsageSource#UNAVAILABLE}인 0 토큰 사용량
+ * @param metadata response metadata to preserve
+ * @return zero-token usage with source {@link UsageSource#UNAVAILABLE}
*/
public static TokenUsage unavailable(Map metadata) {
return new TokenUsage(
@@ -119,40 +120,41 @@ public static TokenUsage unavailable(Map metadata) {
}
/**
- * 모든 종류의 입력/출력 토큰 수의 합계를 반환합니다.
+ * Returns the total number of input tokens across all input categories.
*
- * @return 전체 입력 토큰
+ * @return total input tokens
*/
public long promptTokens() {
return inputTokens;
}
/**
- * 모든 종류의 출력(추론 포함) 토큰 수의 합계를 반환합니다.
+ * Returns the total number of output tokens, including reasoning tokens.
*
- * @return 전체 출력 토큰
+ * @return total output tokens
*/
public long completionTokens() {
return outputTokens;
}
/**
- * 전체 사용 토큰 수의 합계를 반환합니다.
+ * Returns the total number of used tokens.
*
- * @return 전체 입력과 출력 토큰의 합
- * @throws ArithmeticException 합계가 {@code long} 범위를 초과한 경우
+ * @return the sum of total input and output tokens
+ * @throws ArithmeticException when the sum exceeds the {@code long} range
*/
public long totalTokens() {
return Math.addExact(inputTokens, outputTokens);
}
/**
- * 특정 토큰 타입의 전체 또는 세부 사용량을 반환합니다.
- * 보고되지 않은 세부량은 이 호환 projection에서 {@code 0}으로 반환되며,
- * 미보고 여부는 {@link #details()}의 nullable 필드로 확인해야 합니다.
+ * Returns the total or detailed usage for a token type.
+ * Unreported details are returned as {@code 0} in this compatibility
+ * projection; use the nullable fields in {@link #details()} to distinguish
+ * unreported values.
*
- * @param type 조회할 토큰 타입
- * @return 해당 토큰 타입의 사용량
+ * @param type token type to retrieve
+ * @return usage for the requested token type
*/
public long getCount(TokenType type) {
return switch (type) {
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenUsageDetails.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenUsageDetails.java
index 8c467ae..ed30320 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenUsageDetails.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenUsageDetails.java
@@ -1,13 +1,13 @@
package io.tokenpilot.core.domain;
/**
- * 정규화된 전체 입력/출력 토큰에 포함되는 선택적 세부 사용량.
- * {@code null}은 provider가 값을 보고하지 않았음을, {@code 0}은 값을
- * 보고했으나 실제 사용량이 없었음을 뜻합니다.
+ * Optional usage details included in normalized inclusive input/output totals.
+ * {@code null} means the provider did not report a value, while {@code 0}
+ * means it reported the value but no tokens were used.
*
- * @param cacheReadInputTokens 전체 입력에 포함된 cache read 토큰 또는 미보고 시 {@code null}
- * @param cacheCreationInputTokens 전체 입력에 포함된 cache creation 토큰 또는 미보고 시 {@code null}
- * @param reasoningOutputTokens 전체 출력에 포함된 reasoning 토큰 또는 미보고 시 {@code null}
+ * @param cacheReadInputTokens cache-read tokens included in total input, or {@code null} when unreported
+ * @param cacheCreationInputTokens cache-creation tokens included in total input, or {@code null} when unreported
+ * @param reasoningOutputTokens reasoning tokens included in total output, or {@code null} when unreported
*/
public record TokenUsageDetails(
Long cacheReadInputTokens,
@@ -15,9 +15,9 @@ public record TokenUsageDetails(
Long reasoningOutputTokens
) {
/**
- * 모든 세부 토큰 수가 0 이상인지 검증합니다.
+ * Validates that every detail token count is non-negative.
*
- * @throws IllegalArgumentException 세부 토큰 수가 음수인 경우
+ * @throws IllegalArgumentException when a detail token count is negative
*/
public TokenUsageDetails {
isEmptyToken(cacheReadInputTokens);
@@ -26,9 +26,9 @@ public record TokenUsageDetails(
}
/**
- * provider가 세부 사용량을 보고하지 않은 상태를 생성합니다.
+ * Creates a state in which the provider reported no usage details.
*
- * @return 모든 세부량이 미보고 상태인 객체
+ * @return an object with all details marked as unreported
*/
public static TokenUsageDetails unreported() {
return new TokenUsageDetails(null, null, null);
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenizationBasis.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenizationBasis.java
index 8418508..f7d6a0f 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenizationBasis.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/TokenizationBasis.java
@@ -1,16 +1,17 @@
package io.tokenpilot.core.domain;
/**
- * token 계산 결과와 모델 encoding의 호환성을 판단할 때 사용하는 계산 기준입니다.
+ * Basis used to determine compatibility between a token count result and a
+ * model encoding.
*
- * @param id tokenization 기준을 식별하는 안정적인 값
+ * @param id stable value identifying the tokenization basis
*/
public record TokenizationBasis(String id) {
/**
- * tokenization 기준 식별자가 null 또는 blank가 아닌지 검증합니다.
+ * Validates that the tokenization basis identifier is not null or blank.
*
- * @throws IllegalArgumentException id가 null이거나 blank인 경우
+ * @throws IllegalArgumentException when id is null or blank
*/
public TokenizationBasis {
if (id == null || id.isBlank()) {
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/UsageSource.java b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/UsageSource.java
index b958e7b..810f921 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/domain/UsageSource.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/domain/UsageSource.java
@@ -1,17 +1,17 @@
package io.tokenpilot.core.domain;
/**
- * 토큰 사용량 값의 출처와 생성 방식을 나타냅니다.
+ * Describes the source and derivation method of token usage values.
*/
public enum UsageSource {
- /** provider가 포괄 총량을 직접 보고한 사용량 */
+ /** Usage directly reported by the provider as inclusive totals. */
PROVIDER_REPORTED,
- /** provider가 보고한 여러 필드를 어댑터가 정규화해 만든 사용량 */
+ /** Usage normalized by an adapter from multiple provider-reported fields. */
PROVIDER_DERIVED,
- /** 로컬 tokenizer가 계산한 사용량 */
+ /** Usage counted by a local tokenizer. */
LOCAL_TOKENIZER,
- /** 휴리스틱으로 근사 추정한 사용량 */
+ /** Usage approximately estimated by a heuristic. */
HEURISTIC_ESTIMATE,
- /** 사용량 정보를 얻을 수 없음 */
+ /** Usage information was unavailable. */
UNAVAILABLE
}
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultCostCalculator.java b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultCostCalculator.java
index 1f8e1fc..2c682ad 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultCostCalculator.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultCostCalculator.java
@@ -11,10 +11,10 @@
import java.math.BigDecimal;
/**
- * 기본 비용 계산기 구현체.
- * 포괄 총량에서 cache/reasoning 세부량을 분리한 배타적 구간별로
- * {@link TokenType} 단가를 적용하여 중복 없이 계산합니다.
- * 1K 토큰당 가격을 정확한 decimal shift로 계산하며 내부에서 반올림하지 않습니다.
+ * Default cost calculator implementation.
+ * Separates cache/reasoning details from inclusive totals into disjoint segments
+ * and applies the {@link TokenType} rates without double counting.
+ * Calculates per-1K-token prices with an exact decimal shift and does not round internally.
*/
class DefaultCostCalculator implements CostCalculator {
@Override
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultLedgerManager.java b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultLedgerManager.java
index 3cba97f..0dd27df 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultLedgerManager.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultLedgerManager.java
@@ -10,8 +10,9 @@
import java.util.concurrent.CopyOnWriteArrayList;
/**
- * 기본 LedgerManager 구현체.
- * 가격 저장소와 계산기를 조율하여 비용을 기록하고, 등록된 리스너들에게 이벤트를 전파합니다.
+ * Default {@link LedgerManager} implementation.
+ * Coordinates the pricing registry and calculator to record costs and publish
+ * events to registered listeners.
*/
class DefaultLedgerManager implements LedgerManager {
private static final Currency UNPRICED_COST_CURRENCY = Currency.getInstance("USD");
@@ -33,7 +34,7 @@ public DefaultLedgerManager(PricingRegistry pricingRegistry, CostCalculator cost
}
/**
- * 새로운 리스너를 런타임에 등록합니다.
+ * Registers a new listener at runtime.
*/
public void addListener(LedgerListener listener) {
if (listener != null) {
@@ -77,7 +78,7 @@ private Cost recordResolvedPlan(PricingPlan plan, TokenUsage usage, Map tags) {
- // 이벤트 발행 (리스너들에게 전파)
+ // Publish the event to listeners.
if (!listeners.isEmpty()) {
CostRecordedEvent event = new CostRecordedEvent(modelId, usage, cost, tags);
for (LedgerListener listener : listeners) {
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultPreflightCostEstimator.java b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultPreflightCostEstimator.java
index 67cd8c1..326d957 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultPreflightCostEstimator.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultPreflightCostEstimator.java
@@ -15,12 +15,14 @@
import java.util.Objects;
/**
- * 불변 pricing snapshot의 배타적인 입력/출력 가격 경로에서 각각 최대 단가를 선택해
- * 같은 token을 중복 합산하지 않고 보수적인 비용 상한을 계산하는 기본 구현입니다.
+ * Default implementation that selects the maximum rate from the disjoint input
+ * and output pricing paths in an immutable pricing snapshot and calculates a
+ * conservative cost bound without double counting tokens.
*
- * 이 구현은 비용만 계산하며 context admission이나 provider 호출을 허가하지 않습니다.
- * 입력 문맥이 보관한 동일한 snapshot만 사용하며, 계산 중 registry를 다시 조회하지
- * 않습니다. 호출 계층은 그 snapshot을 예약·정산에도 그대로 전달해야 합니다.
+ * This implementation calculates cost only; it does not authorize context
+ * admission or provider invocation. It uses only the snapshot retained by the
+ * input context and does not query a registry again during calculation. The
+ * calling layer must pass the same snapshot to reservation and settlement.
*/
class DefaultPreflightCostEstimator implements PreflightCostEstimator {
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultTokenBudget.java b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultTokenBudget.java
index da34bed..c70b96a 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultTokenBudget.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/DefaultTokenBudget.java
@@ -17,7 +17,7 @@
import java.util.OptionalLong;
/**
- * versioned model registry를 사용해 overflow-safe context admission을 수행합니다.
+ * Performs overflow-safe context admission using a versioned model registry.
*/
final class DefaultTokenBudget implements TokenBudget {
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/HeuristicTokenEstimator.java b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/HeuristicTokenEstimator.java
index 9f48af7..ce55e05 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/HeuristicTokenEstimator.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/HeuristicTokenEstimator.java
@@ -15,11 +15,11 @@
import java.util.Objects;
/**
- * UTF-8 byte 길이를 기반으로 문자열의 text-only token 수를 추정합니다.
+ * Estimates text-only token counts from UTF-8 byte length.
*
- * {@code BYTE_LEVEL_BPE_UTF8}는 byte-level tokenizer의 안전성 가정을
- * 나타내는 compatibility basis일 뿐, 이 구현이 exact BPE merge를 수행한다는
- * 뜻은 아닙니다. 결과는 전체 요청의 context admission 근거로 사용할 수 없습니다.
+ * {@code BYTE_LEVEL_BPE_UTF8} is only a compatibility basis representing a
+ * byte-level tokenizer safety assumption; this implementation does not perform
+ * exact BPE merges. The result cannot prove context admission for a complete request.
*/
final class HeuristicTokenEstimator implements TokenEstimator {
@@ -38,12 +38,12 @@ final class HeuristicTokenEstimator implements TokenEstimator {
new TokenizationBasis(TOKENIZATION_BASIS_ID);
/**
- * 원문을 정규화하지 않고 UTF-8 byte 길이로 계산합니다.
+ * Counts UTF-8 byte length without normalizing the original text.
*
- * @param text 계산할 원문
- * @return text-only 휴리스틱 token 계산 결과
- * @throws NullPointerException text가 null인 경우
- * @throws IllegalArgumentException text에 malformed UTF-16이 포함된 경우
+ * @param text original text to count
+ * @return text-only heuristic token count result
+ * @throws NullPointerException when text is null
+ * @throws IllegalArgumentException when text contains malformed UTF-16
*/
@Override
public TokenCountResult estimate(String text) {
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/InMemoryModelRegistry.java b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/InMemoryModelRegistry.java
index 558573f..da3970b 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/InMemoryModelRegistry.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/InMemoryModelRegistry.java
@@ -10,7 +10,7 @@
import java.util.Optional;
/**
- * canonical id와 exact alias를 하나의 lookup index로 관리하는 in-memory registry입니다.
+ * In-memory registry that manages canonical IDs and exact aliases in one lookup index.
*/
final class InMemoryModelRegistry implements ModelRegistry {
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/InMemoryPricingRegistry.java b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/InMemoryPricingRegistry.java
index 926c7c8..fe0293f 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/InMemoryPricingRegistry.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/InMemoryPricingRegistry.java
@@ -18,7 +18,7 @@
/**
- * 메모리 기반 가격 정책 저장소 구현체.
+ * In-memory pricing policy repository implementation.
*/
class InMemoryPricingRegistry implements PricingRegistry {
private final Map plans = new ConcurrentHashMap<>();
diff --git a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/LedgerComponents.java b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/LedgerComponents.java
index 6267212..34753ff 100644
--- a/token-pilot-core/src/main/java/io/tokenpilot/core/internal/LedgerComponents.java
+++ b/token-pilot-core/src/main/java/io/tokenpilot/core/internal/LedgerComponents.java
@@ -17,7 +17,7 @@
import java.util.List;
/**
- * Token Pilot 코어 컴포넌트 생성을 위한 팩토리 클래스입니다.
+ * Factory for creating Token Pilot core components.
*/
public final class LedgerComponents {
@@ -33,29 +33,29 @@ public static PricingEvaluator defaultPricingEvaluator() {
}
/**
- * 기본 preflight 비용 상한 계산기를 생성합니다.
+ * Creates the default preflight cost-bound estimator.
*
- * 계산기는 REQUEST 범위의 token 결과와 하나의 불변 pricing snapshot을
- * 보관한 문맥을 요구합니다. 이 factory는 context admission이나 atomic reservation을
- * 대신 수행하지 않습니다.
+ * The estimator requires a context retaining a REQUEST-scope token result
+ * and one immutable pricing snapshot. This factory does not perform context
+ * admission or atomic reservation.
*
- * @return 기본 preflight 비용 상한 계산기
+ * @return default preflight cost-bound estimator
*/
public static PreflightCostEstimator defaultPreflightCostEstimator() {
return new DefaultPreflightCostEstimator();
}
/**
- * UTF-8 byte 길이 기반의 text-only 휴리스틱 token estimator를 생성합니다.
+ * Creates a text-only heuristic token estimator based on UTF-8 byte length.
*
- * @return UTF-8 byte 휴리스틱 estimator
+ * @return UTF-8 byte heuristic estimator
*/
public static TokenEstimator utf8ByteHeuristicTokenEstimator() {
return new HeuristicTokenEstimator();
}
/**
- * model registry를 사용하는 context admission evaluator를 생성합니다.
+ * Creates a context admission evaluator backed by a model registry.
*
* @param modelRegistry versioned model catalog
* @return token context budget evaluator
@@ -65,7 +65,8 @@ public static TokenBudget tokenBudget(ModelRegistry modelRegistry) {
}
/**
- * model registry와 best-effort decision listener를 사용하는 context admission evaluator를 생성합니다.
+ * Creates a context admission evaluator backed by a model registry and
+ * best-effort decision listeners.
*/
public static TokenBudget tokenBudget(
ModelRegistry modelRegistry,
@@ -75,9 +76,9 @@ public static TokenBudget tokenBudget(
}
/**
- * 등록할 model definition으로 in-memory model registry를 생성합니다.
+ * Creates an in-memory model registry from model definitions to register.
*
- * @param definitions immutable model catalog 정의
+ * @param definitions immutable model catalog definitions
* @return in-memory model registry
*/
public static ModelRegistry inMemoryModelRegistry(Collection definitions) {
@@ -85,9 +86,10 @@ public static ModelRegistry inMemoryModelRegistry(Collection de
}
/**
- * 공개 문서의 확인 시점과 encoding metadata를 고정한 최소 기본 catalog를 생성합니다.
+ * Creates the minimal default catalog with public-document verification times
+ * and encoding metadata fixed.
*
- * @return gpt-4o와 gpt-4o-mini versioned catalog
+ * @return versioned catalog for gpt-4o and gpt-4o-mini
*/
public static ModelRegistry defaultModelRegistry() {
return inMemoryModelRegistry(DefaultModelCatalog.definitions());
diff --git a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/MetricsOptions.java b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/MetricsOptions.java
index 343e470..fc0fd32 100644
--- a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/MetricsOptions.java
+++ b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/MetricsOptions.java
@@ -22,11 +22,11 @@ public record MetricsOptions(
}
/**
- * 기존 직접 생성 코드의 source/binary 호환성을 유지하는 생성자입니다.
+ * Constructor preserving source and binary compatibility with existing direct construction code.
*
- * 이 생성자를 직접 사용하는 것은 legacy {@code ai.token.*} meter에 대한 명시적
- * opt-in으로 취급됩니다. Starter 자동 설정 기본값은 {@link #defaults()}를 사용하여
- * legacy meter를 비활성화합니다.
+ * Using this constructor directly is treated as an explicit opt-in to
+ * legacy {@code ai.token.*} meters. Starter auto-configuration uses
+ * {@link #defaults()} by default to disable legacy meters.
*/
public MetricsOptions(Set allowedTagKeys) {
this(
diff --git a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/BudgetMetricsPublisher.java b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/BudgetMetricsPublisher.java
index 8b46648..3480124 100644
--- a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/BudgetMetricsPublisher.java
+++ b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/BudgetMetricsPublisher.java
@@ -20,7 +20,7 @@
import java.math.BigInteger;
import java.util.Objects;
-/** Atomic budget/accounting 결과를 Token Pilot 고유 Micrometer meter로 투영합니다. */
+/** Projects atomic budget/accounting results into Token Pilot-owned Micrometer meters. */
public final class BudgetMetricsPublisher
implements ReservationAccountingListener, ReservationAccountingListenerErrorHandler {
diff --git a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/BudgetMicrometerComponents.java b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/BudgetMicrometerComponents.java
index 754185a..d2a288a 100644
--- a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/BudgetMicrometerComponents.java
+++ b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/BudgetMicrometerComponents.java
@@ -2,7 +2,7 @@
import io.micrometer.core.instrument.MeterRegistry;
-/** Atomic budget owner의 선택 Micrometer publisher 팩토리입니다. */
+/** Factory for the optional Micrometer publisher owned by the atomic budget component. */
public final class BudgetMicrometerComponents {
private BudgetMicrometerComponents() {
diff --git a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/CoreMetricsPublisher.java b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/CoreMetricsPublisher.java
index 7672c76..ee6cdfe 100644
--- a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/CoreMetricsPublisher.java
+++ b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/CoreMetricsPublisher.java
@@ -9,7 +9,7 @@
import java.util.Objects;
-/** Token Pilot core의 preflight와 pricing-miss 이벤트를 Micrometer meter로 투영합니다. */
+/** Projects Token Pilot core preflight and pricing-miss events into Micrometer meters. */
public final class CoreMetricsPublisher
implements PreflightDecisionListener, PricingMissingListener {
diff --git a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/LedgerMicrometerComponents.java b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/LedgerMicrometerComponents.java
index 404d760..0c664bf 100644
--- a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/LedgerMicrometerComponents.java
+++ b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/LedgerMicrometerComponents.java
@@ -7,7 +7,7 @@
import java.util.Set;
/**
- * Micrometer 메트릭 퍼블리셔 생성을 위한 팩토리 클래스입니다.
+ * Factory for creating Micrometer metric publishers.
*/
public final class LedgerMicrometerComponents {
diff --git a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/MicroCostMetricsPublisher.java b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/MicroCostMetricsPublisher.java
index 8c533e6..dd729ee 100644
--- a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/MicroCostMetricsPublisher.java
+++ b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/MicroCostMetricsPublisher.java
@@ -16,9 +16,9 @@
import java.util.Set;
/**
- * Micrometer 기반의 비용 메트릭 리스너.
- * {@link LedgerListener}를 구현하여 비용 기록 이벤트를 가로채고,
- * 이를 Prometheus 등 모니터링 시스템으로 전송합니다.
+ * Micrometer-based cost metric listener.
+ * Implements {@link LedgerListener} to intercept cost-recording events and
+ * publish them to monitoring systems such as Prometheus.
*/
public class MicroCostMetricsPublisher implements LedgerListener {
diff --git a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/NotificationMetricsPublisher.java b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/NotificationMetricsPublisher.java
index 04e35b3..77a35e7 100644
--- a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/NotificationMetricsPublisher.java
+++ b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/NotificationMetricsPublisher.java
@@ -7,7 +7,7 @@
import java.util.Objects;
-/** Budget notification lifecycle 이벤트를 Micrometer meter로 투영합니다. */
+/** Projects budget notification lifecycle events into Micrometer meters. */
public final class NotificationMetricsPublisher
implements BudgetNotificationLifecycleListener {
diff --git a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/NotificationMicrometerComponents.java b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/NotificationMicrometerComponents.java
index 023e2ee..9d1fe5d 100644
--- a/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/NotificationMicrometerComponents.java
+++ b/token-pilot-micrometer/src/main/java/io/tokenpilot/micrometer/internal/NotificationMicrometerComponents.java
@@ -2,7 +2,7 @@
import io.micrometer.core.instrument.MeterRegistry;
-/** Budget notification owner의 선택 Micrometer publisher 팩토리입니다. */
+/** Factory for the optional Micrometer publisher owned by budget notifications. */
public final class NotificationMicrometerComponents {
private NotificationMicrometerComponents() {
diff --git a/token-pilot-micrometer/src/test/java/io/tokenpilot/micrometer/internal/MicroCostMetricsPublisherTest.java b/token-pilot-micrometer/src/test/java/io/tokenpilot/micrometer/internal/MicroCostMetricsPublisherTest.java
index 314bd9a..0c8ba94 100644
--- a/token-pilot-micrometer/src/test/java/io/tokenpilot/micrometer/internal/MicroCostMetricsPublisherTest.java
+++ b/token-pilot-micrometer/src/test/java/io/tokenpilot/micrometer/internal/MicroCostMetricsPublisherTest.java
@@ -39,7 +39,7 @@ void shouldPublishMetricsWhenEventRecorded() {
// When
publisher.onRecord(event);
- // Then: 토큰 사용량 카운터 확인
+ // Then: verify the token usage counter
assertThat(meterRegistry.find("ai.token.usage.total")
.tag("model", "gpt-4o")
.tag("token_type", "prompt")
@@ -50,13 +50,13 @@ void shouldPublishMetricsWhenEventRecorded() {
.tag("token_type", "completion")
.counter().count()).isEqualTo(200.0);
- // Then: 토큰 사용량 분포(Summary) 확인
+ // Then: verify the token usage distribution summary
assertThat(meterRegistry.find("ai.token.usage.distribution")
.tag("model", "gpt-4o")
.tag("token_type", "prompt")
.summary().max()).isEqualTo(100.0);
- // Then: 비용 카운터 확인
+ // Then: verify the cost counter
var costCounter = meterRegistry.find("ai.token.cost.total")
.tag("model", "gpt-4o")
.tag("currency", "USD")
diff --git a/token-pilot-notification/src/main/java/io/tokenpilot/notification/AtomicNotificationStateStore.java b/token-pilot-notification/src/main/java/io/tokenpilot/notification/AtomicNotificationStateStore.java
index 35b791e..2fc8e81 100644
--- a/token-pilot-notification/src/main/java/io/tokenpilot/notification/AtomicNotificationStateStore.java
+++ b/token-pilot-notification/src/main/java/io/tokenpilot/notification/AtomicNotificationStateStore.java
@@ -6,7 +6,7 @@
import io.tokenpilot.budget.ReservationAccountingEvent;
import io.tokenpilot.budget.ReservationReconciliationRequiredEvent;
-/** atomic budget/accounting 결과의 threshold를 원자적으로 claim하는 저장소 계약입니다. */
+/** Store contract for atomically claiming thresholds from atomic budget/accounting results. */
public interface AtomicNotificationStateStore extends NotificationStateStore {
NotificationClaim recordAppliedTransition(
diff --git a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationError.java b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationError.java
index ddc6dd2..5be0453 100644
--- a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationError.java
+++ b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationError.java
@@ -3,10 +3,11 @@
import java.util.Objects;
/**
- * 알림 실패 hook에 전달하는 제한된 오류 정보입니다.
+ * Bounded error information passed to the notification failure hook.
*
- * 원본 예외 메시지와 notification event를 보존하지 않아 prompt, provider response,
- * API key가 오류 경로로 전달되지 않습니다. 각 문자열은 최대 128자로 제한됩니다.
+ * The original exception message and notification event are not retained, so
+ * prompts, provider responses, and API keys do not enter the error path. Each
+ * string is limited to 128 characters.
*/
public record BudgetNotificationError(
String stage,
diff --git a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationErrorHook.java b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationErrorHook.java
index 6a17978..3da7df9 100644
--- a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationErrorHook.java
+++ b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationErrorHook.java
@@ -1,6 +1,6 @@
package io.tokenpilot.notification;
-/** bounded/sanitized notification 오류를 관찰하는 best-effort hook입니다. */
+/** Best-effort hook for observing bounded and sanitized notification errors. */
@FunctionalInterface
public interface BudgetNotificationErrorHook {
diff --git a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationEvent.java b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationEvent.java
index 2625449..1ed18c5 100644
--- a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationEvent.java
+++ b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationEvent.java
@@ -9,21 +9,22 @@
import java.util.Objects;
/**
- * 예산 임계치 도달 시 발생하는 알림 이벤트입니다.
+ * Notification event emitted when a budget threshold is reached.
*
- * @param key 예산 bucket 식별자
- * @param threshold 도달한 임계치
- * @param state 예산 상태
- * @param reason 상태 설명
- * @param usage 알림을 만든 accounting 또는 admission 결과의 사용량
- * @param limit 예산 한도
- * @param source 알림을 만든 원자적 결과 종류
- * @param tags legacy decision 알림에서만 보존하는 호환 tag
+ * @param key budget bucket identifier
+ * @param threshold reached threshold
+ * @param state budget state
+ * @param reason state description
+ * @param usage usage from the accounting or admission result that produced the notification
+ * @param limit budget limit
+ * @param source atomic result type that produced the notification
+ * @param tags compatibility tags retained only for legacy decision notifications
*
- * 원자적 accounting 이벤트는 prompt, raw provider response, API key, 임의 tag map을
- * 포함하지 않습니다. legacy decision 생성자만 기존 handler 호환을 위해 불변 tag 복사본을
- * 보존합니다. {@link #projectedUsage()}와 {@link #currentUsage()} 호환 accessor는 0.1.x 동안
- * 유지하며 0.2.0에서 제거할 예정입니다.
+ * Atomic accounting events do not contain prompts, raw provider responses,
+ * API keys, or arbitrary tag maps. Only the legacy decision constructor retains
+ * an immutable tag copy for handler compatibility. The compatibility accessors
+ * {@link #projectedUsage()} and {@link #currentUsage()} remain through 0.1.x
+ * and are scheduled for removal in 0.2.0.
*/
public record BudgetNotificationEvent(
BudgetKey key,
@@ -70,7 +71,8 @@ public BudgetNotificationEvent(
}
/**
- * @deprecated legacy decision 기반 이벤트 생성 호환용입니다. tags는 불변 복사본으로 보존됩니다.
+ * @deprecated Compatibility constructor for legacy decision-based event creation.
+ * Tags are retained as an immutable copy.
*/
@Deprecated(since = "0.1.0", forRemoval = false)
public BudgetNotificationEvent(
@@ -95,8 +97,8 @@ public BudgetNotificationEvent(
}
/**
- * @return {@link #usage()}와 동일한 accounting/admission 사용량
- * @deprecated source별 의미가 명확한 {@link #usage()}를 사용하세요.
+ * @return accounting/admission usage identical to {@link #usage()}
+ * @deprecated Use {@link #usage()}, whose meaning is clear for each source.
*/
@Deprecated(since = "0.1.0", forRemoval = true)
public Cost projectedUsage() {
@@ -104,8 +106,8 @@ public Cost projectedUsage() {
}
/**
- * @return {@link #usage()}와 동일한 accounting/admission 사용량
- * @deprecated source별 의미가 명확한 {@link #usage()}를 사용하세요.
+ * @return accounting/admission usage identical to {@link #usage()}
+ * @deprecated Use {@link #usage()}, whose meaning is clear for each source.
*/
@Deprecated(since = "0.1.0", forRemoval = true)
public Cost currentUsage() {
diff --git a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationHandler.java b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationHandler.java
index 5bce627..c66264a 100644
--- a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationHandler.java
+++ b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationHandler.java
@@ -1,9 +1,9 @@
package io.tokenpilot.notification;
/**
- * 알림 이벤트를 처리하는 인터페이스
+ * Interface for handling notification events.
*/
public interface BudgetNotificationHandler {
void handle(BudgetNotificationEvent event);
-}
\ No newline at end of file
+}
diff --git a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationLifecycleEvent.java b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationLifecycleEvent.java
index 3ebabea..5436efb 100644
--- a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationLifecycleEvent.java
+++ b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationLifecycleEvent.java
@@ -5,10 +5,11 @@
import java.util.Objects;
/**
- * notification handler 처리 또는 dedup 결과를 전달하는 framework-independent 이벤트입니다.
+ * Framework-independent event carrying the result of notification handler
+ * processing or deduplication.
*
- * @param outcome success, failure 또는 process-local dedup 결과
- * @param threshold 처리 대상인 bounded budget threshold
+ * @param outcome success, failure, or process-local deduplication result
+ * @param threshold bounded budget threshold being processed
*/
public record BudgetNotificationLifecycleEvent(
BudgetNotificationOutcome outcome,
diff --git a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationLifecycleListener.java b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationLifecycleListener.java
index d1d6e0b..4f7f573 100644
--- a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationLifecycleListener.java
+++ b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationLifecycleListener.java
@@ -1,6 +1,6 @@
package io.tokenpilot.notification;
-/** Budget notification lifecycle을 best-effort로 관찰하는 framework-independent 계약입니다. */
+/** Framework-independent contract for best-effort observation of the budget notification lifecycle. */
@FunctionalInterface
public interface BudgetNotificationLifecycleListener {
diff --git a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationOutcome.java b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationOutcome.java
index 0056630..64731a2 100644
--- a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationOutcome.java
+++ b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationOutcome.java
@@ -1,6 +1,6 @@
package io.tokenpilot.notification;
-/** Budget notification 처리의 bounded 운영 결과입니다. */
+/** Bounded operational result of budget notification processing. */
public enum BudgetNotificationOutcome {
SUCCESS,
FAILURE,
diff --git a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationService.java b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationService.java
index 562df54..8b8bbb1 100644
--- a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationService.java
+++ b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationService.java
@@ -21,14 +21,15 @@
import java.util.function.Supplier;
/**
- * 원자적 budget/accounting 결과를 threshold 알림으로 변환하는 best-effort listener입니다.
+ * Best-effort listener that converts atomic budget/accounting results into threshold notifications.
*
- * 원자적 callback의 threshold claim은 handler 호출 전에 완료됩니다. handler 실패는 다음
- * handler 전달을 막지 않고 이미 적용된 회계 또는 admission 결과를 변경하지 않으며, 실패한
- * 전달은 durable outbox가 없는 MVP에서 재시도되지 않습니다. Legacy decision 경로는 호환성을
- * 위해 전달 성공 후에만 상태를 갱신하므로 실패한 호출을 다음 호출에서 다시 시도합니다.
- * 두 경로의 claim은 같은 store monitor에서 직렬화되어 혼용 중에도 같은 threshold를 중복
- * 전달하지 않습니다.
+ * Threshold claims for atomic callbacks complete before handlers are called.
+ * A handler failure does not block the next handler or change an already applied
+ * accounting/admission result, and failed delivery is not retried in the MVP
+ * because there is no durable outbox. For compatibility, the legacy decision
+ * path updates state only after successful delivery, so a failed call is retried
+ * on the next call. Claims from both paths are serialized on the same store
+ * monitor and do not deliver the same threshold twice when mixed.
*/
public class BudgetNotificationService implements ReservationAccountingListener {
@@ -40,8 +41,9 @@ public class BudgetNotificationService implements ReservationAccountingListener
private final List lifecycleListeners;
/**
- * @deprecated legacy {@link BudgetDecision} 알림 호환용 생성자입니다. 신규 연결은 복수
- * handler와 error hook을 받는 생성자를 사용하세요.
+ * @deprecated Compatibility constructor for legacy {@link BudgetDecision}
+ * notifications. New wiring should use the constructor accepting
+ * multiple handlers and an error hook.
*/
@Deprecated(since = "0.1.0", forRemoval = false)
public BudgetNotificationService(
@@ -51,7 +53,7 @@ public BudgetNotificationService(
this(handler, store, List.of());
}
- /** legacy decision 알림에 lifecycle 관측을 추가하는 호환 생성자입니다. */
+ /** Compatibility constructor that adds lifecycle observation to legacy decision notifications. */
public BudgetNotificationService(
BudgetNotificationHandler handler,
NotificationStateStore store,
@@ -134,9 +136,10 @@ private BudgetNotificationService(
}
/**
- * 기존 #37 callback은 주입된 resolver로 현재 bucket snapshot을 조회합니다. 현재 budget store는
- * 정확한 전이 시점 snapshot을 포함하는
- * {@link #onAccountingApplied(ReservationAccountingEvent, BudgetSnapshot)}를 호출합니다.
+ * The former #37 callback reads the current bucket snapshot through the
+ * injected resolver. The current budget store calls
+ * {@link #onAccountingApplied(ReservationAccountingEvent, BudgetSnapshot)}
+ * with the snapshot from the exact transition point.
*/
@Override
public void onCommitted(ReservationAccountingEvent event) {
@@ -244,9 +247,10 @@ public void onReservationBlocked(
}
/**
- * @deprecated legacy evaluator 결과는 admission/accounting 알림 근거가 아닙니다. 신규 코드는
- * {@link ReservationAccountingListener} 연결을 사용하세요. tags는 legacy 이벤트에서만
- * 불변 복사본으로 보존됩니다.
+ * @deprecated Legacy evaluator results are not grounds for admission/accounting
+ * notifications. New code should wire a
+ * {@link ReservationAccountingListener}. Tags are retained as an
+ * immutable copy only for legacy events.
*/
@Deprecated(since = "0.1.0", forRemoval = false)
public void notifyIfNeeded(
@@ -351,7 +355,7 @@ private void report(BudgetNotificationError error) {
try {
errorHook.onError(error);
} catch (RuntimeException ignored) {
- // Error hook도 best-effort이며 accounting/provider 결과에 영향을 주지 않습니다.
+ // The error hook is also best-effort and does not affect accounting or provider results.
}
}
diff --git a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationSource.java b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationSource.java
index 16f70a0..c44a210 100644
--- a/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationSource.java
+++ b/token-pilot-notification/src/main/java/io/tokenpilot/notification/BudgetNotificationSource.java
@@ -1,6 +1,6 @@
package io.tokenpilot.notification;
-/** 알림을 만든 원자적 budget/accounting 결과의 종류입니다. */
+/** Type of atomic budget/accounting result that produced a notification. */
public enum BudgetNotificationSource {
ACCOUNTING_COMMIT,
RECONCILIATION_REQUIRED,
diff --git a/token-pilot-notification/src/main/java/io/tokenpilot/notification/InMemoryNotificationStateStore.java b/token-pilot-notification/src/main/java/io/tokenpilot/notification/InMemoryNotificationStateStore.java
index da7873a..f731fe1 100644
--- a/token-pilot-notification/src/main/java/io/tokenpilot/notification/InMemoryNotificationStateStore.java
+++ b/token-pilot-notification/src/main/java/io/tokenpilot/notification/InMemoryNotificationStateStore.java
@@ -22,12 +22,14 @@
import java.util.concurrent.atomic.AtomicReference;
/**
- * 메모리 기반 accounting threshold dedup 저장소입니다.
+ * In-memory accounting threshold deduplication store.
*
- * dedup key는 {@code BudgetKey(policy/target/window) + BudgetThreshold}입니다.
- * 처리한 accounting transition은 같은 bucket의 {@link ReservationId}로 중복 claim을 막습니다.
- * 상태는 이 store 인스턴스의 생명주기 동안 보존되며 재시작 후 replay나 TTL 정리는 제공하지
- * 않습니다. 따라서 MVP 전달 보장은 process-local best-effort이며 durable exactly-once가 아닙니다.
+ * The deduplication key is {@code BudgetKey(policy/target/window) + BudgetThreshold}.
+ * Processed accounting transitions are protected from duplicate claims by
+ * {@link ReservationId} within the same bucket. State is retained for the
+ * lifetime of this store instance; restart replay and TTL cleanup are not
+ * provided. Therefore MVP delivery is process-local best-effort, not durable
+ * exactly-once.
*/
@SuppressWarnings("deprecation")
public class InMemoryNotificationStateStore
diff --git a/token-pilot-notification/src/main/java/io/tokenpilot/notification/NotificationStateStore.java b/token-pilot-notification/src/main/java/io/tokenpilot/notification/NotificationStateStore.java
index da73370..255fb51 100644
--- a/token-pilot-notification/src/main/java/io/tokenpilot/notification/NotificationStateStore.java
+++ b/token-pilot-notification/src/main/java/io/tokenpilot/notification/NotificationStateStore.java
@@ -8,23 +8,24 @@
import java.util.Objects;
/**
- * legacy decision 기반 알림 dedup 저장소입니다.
- * Atomic lifecycle 연결은 {@link AtomicNotificationStateStore}를 구현해야 합니다.
+ * Notification deduplication store for legacy decision-based notifications.
+ * Atomic lifecycle integration requires implementing
+ * {@link AtomicNotificationStateStore}.
*/
public interface NotificationStateStore {
- /** @deprecated legacy decision 기반 조회 API입니다. */
+ /** @deprecated Legacy decision-based read API. */
@Deprecated(since = "0.1.0", forRemoval = false)
BudgetThreshold getLastNotifiedThreshold(BudgetKey key);
- /** @deprecated legacy decision 기반 갱신 API입니다. */
+ /** @deprecated Legacy decision-based update API. */
@Deprecated(since = "0.1.0", forRemoval = false)
void updateLastNotifiedThreshold(
BudgetKey key,
BudgetThreshold threshold
);
- /** 원자적으로 claim된 threshold와 해당 판단의 사용량입니다. */
+ /** Atomically claimed thresholds and the usage from their decision. */
record NotificationClaim(
Cost usage,
List thresholds,
diff --git a/token-pilot-sample-app/DEMO_RUNBOOK.md b/token-pilot-sample-app/DEMO_RUNBOOK.md
index 2376c1f..bd76e09 100644
--- a/token-pilot-sample-app/DEMO_RUNBOOK.md
+++ b/token-pilot-sample-app/DEMO_RUNBOOK.md
@@ -1,4 +1,6 @@
-# Token Pilot demo runbook
+# Token Pilot demo runbook (Korean)
+
+영문 버전은 [`DEMO_RUNBOOK_EN.md`](DEMO_RUNBOOK_EN.md)에서 확인할 수 있습니다.
이 데모는 `demo` Spring profile에서만 활성화되는 네트워크 없는 가짜
`ChatModel`을 사용합니다. 별도 자격 증명이나 외부 호출 없이도 context admission,
@@ -89,7 +91,55 @@ curl -s http://localhost:8080/actuator/prometheus \
docker compose -f token-pilot-sample-app/docker-compose.yml down
```
-## 4. 상태 초기화
+## 4. OpenAI provider smoke 시나리오
+
+`demo` 앱을 먼저 종료한 뒤(`Ctrl+C`) 실제 OpenAI 호출을 수행합니다. 이 경로는
+외부 네트워크와 API key를 사용하며, 호출 비용은 OpenAI 계정에 청구됩니다. API key를
+소스 파일이나 커밋에 넣지 말고 환경 변수로만 전달합니다.
+
+기본 smoke 모델은 Token Pilot catalog에 등록된
+`gpt-4o-mini-2024-07-18`입니다. 다른 모델을 사용하려면 Token Pilot catalog와
+가격 설정도 함께 맞춰야 합니다.
+
+```bash
+export OPENAI_API_KEY='발급받은 OpenAI API key'
+export OPENAI_MODEL='gpt-4o-mini-2024-07-18'
+
+./gradlew --no-daemon :token-pilot-sample-app:bootRun \
+ --args='--spring.profiles.active=openai-smoke'
+```
+
+다른 터미널에서 한 번 호출합니다.
+
+```bash
+curl -sS --get http://localhost:8080/test/token-pilot/openai-smoke \
+ --data-urlencode 'prompt=Reply with one short sentence confirming the provider is reachable.' \
+ | jq
+```
+
+정상 결과는 `status=PASS`, `usageSource`가 `UNAVAILABLE`이 아닌 값, 그리고
+`accountingState=COMMITTED`입니다. 응답에는 provider 응답 토큰 수와 실제 정산 비용도
+함께 포함됩니다.
+
+```bash
+curl -sS http://localhost:8080/actuator/prometheus \
+ | rg '^tokenpilot_(preflight|budget|cost|reconciliation)'
+```
+
+자동화된 smoke test를 직접 실행할 때는 반드시 두 조건을 명시합니다. 일반적인
+`./gradlew test`에서는 실제 호출이 발생하지 않습니다.
+
+```bash
+RUN_OPENAI_SMOKE=true \
+ ./gradlew --no-daemon :token-pilot-sample-app:test \
+ --tests io.tokenpilot.sample.OpenAiSmokeE2ETest
+```
+
+demo와 smoke를 연속으로 확인하려면 `demo` 프로필에서 `/demo/run`을 실행하고 앱을
+종료한 다음, 위의 `openai-smoke` 프로필을 다시 시작해 smoke endpoint를 호출합니다.
+두 프로필은 같은 `8080` 포트를 사용하므로 동시에 실행하지 않습니다.
+
+## 5. 상태 초기화
앱을 종료한 뒤 다시 `bootRun`하면 in-memory budget과 Micrometer counter가
초기화됩니다.
diff --git a/token-pilot-sample-app/DEMO_RUNBOOK_EN.md b/token-pilot-sample-app/DEMO_RUNBOOK_EN.md
new file mode 100644
index 0000000..e8991af
--- /dev/null
+++ b/token-pilot-sample-app/DEMO_RUNBOOK_EN.md
@@ -0,0 +1,150 @@
+# Token Pilot demo runbook (English)
+
+This demo uses a network-free fake `ChatModel` that is enabled only in the
+`demo` Spring profile. It repeatedly exercises context admission, atomic
+reservation, idempotency, release, and actual reconciliation without separate
+credentials or external calls.
+
+## 1. Start the application
+
+Run the following from the repository root:
+
+```bash
+./gradlew --no-daemon :token-pilot-sample-app:bootRun \
+ --args='--spring.profiles.active=demo'
+```
+
+The application listens on `http://localhost:8080`. No API key is required.
+
+## 2. Run the scenarios
+
+Show the demo status and scenario index first:
+
+```bash
+curl -s http://localhost:8080/test/token-pilot/demo | jq
+```
+
+Run all eight scenarios and print the key verification fields:
+
+```bash
+curl -s http://localhost:8080/test/token-pilot/demo/run | jq '{
+ runId,
+ scenarios: [.scenarios[] | {
+ scenario,
+ status,
+ providerInvocations: .evidence.providerInvocations,
+ admittedRequests: .evidence.admittedRequests,
+ blockedRequests: .evidence.blockedRequests,
+ accountingState: .evidence.accountingState,
+ budget: .evidence.budget
+ }],
+ metrics
+}'
+```
+
+Expected results:
+
+| Scenario | Expected result |
+| --- | --- |
+| `context-fit` | `PASS`, admission `FITS`, provider invocations `0` |
+| `context-block` | `PASS`, admission `EXCEEDS`, provider invocations `0` |
+| `budget-concurrency` | `PASS`, one admitted request, seven blocked requests, one provider invocation |
+| `idempotency` | `PASS`, the repeated key is reused and the provider is called once |
+| `release` | `PASS`, the reservation is released before dispatch |
+| `reconciliation-success` | `PASS`, accounting state `COMMITTED` |
+| `reconciliation-failure` | `PASS`, accounting state `RECONCILIATION_REQUIRED` |
+| `reconciliation-unknown` | `PASS`, accounting state `RECONCILIATION_REQUIRED` |
+
+Run an individual scenario when recording a focused result:
+
+```bash
+curl -s http://localhost:8080/test/token-pilot/demo/context-block | jq
+curl -s http://localhost:8080/test/token-pilot/demo/budget-concurrency | jq
+curl -s http://localhost:8080/test/token-pilot/demo/idempotency | jq
+curl -s http://localhost:8080/test/token-pilot/demo/reconciliation-failure | jq
+```
+
+## 3. Start Prometheus and Grafana
+
+Keep the application running on the host and start only the monitoring
+containers:
+
+```bash
+docker compose -f token-pilot-sample-app/docker-compose.yml up --build -d
+```
+
+Open these addresses:
+
+- Prometheus targets: `http://localhost:9090/targets` — `token-pilot-backend` should be `UP`
+- Prometheus query UI: `http://localhost:9090/graph`
+- Grafana: `http://localhost:3000` — select the `Token Pilot Demo` dashboard
+
+Prometheus scrapes `host.docker.internal:8080/actuator/prometheus`. Run the demo
+endpoint, wait about ten seconds, and then inspect the `tokenpilot_*` values in
+Grafana or directly from the endpoint:
+
+```bash
+curl -s http://localhost:8080/actuator/prometheus \
+ | rg '^tokenpilot_(preflight|budget|cost|reconciliation)'
+```
+
+Stop the monitoring containers when finished:
+
+```bash
+docker compose -f token-pilot-sample-app/docker-compose.yml down
+```
+
+## 4. Run the OpenAI provider smoke scenario
+
+Stop the `demo` application first with `Ctrl+C`, then start the real OpenAI
+provider path. This path uses external network access and an API key; provider
+charges may apply. Never put the key in a source file or commit it.
+
+The default smoke model is the Token Pilot catalog entry
+`gpt-4o-mini-2024-07-18`. If you choose another model, update the Token Pilot
+catalog and pricing configuration to match it.
+
+```bash
+export OPENAI_API_KEY='your OpenAI API key'
+export OPENAI_MODEL='gpt-4o-mini-2024-07-18'
+
+./gradlew --no-daemon :token-pilot-sample-app:bootRun \
+ --args='--spring.profiles.active=openai-smoke'
+```
+
+In a second terminal, send one short request:
+
+```bash
+curl -sS --get http://localhost:8080/test/token-pilot/openai-smoke \
+ --data-urlencode 'prompt=Reply with one short sentence confirming the provider is reachable.' \
+ | jq
+```
+
+A successful response reports `status=PASS`, a `usageSource` other than
+`UNAVAILABLE`, and `accountingState=COMMITTED`. It also includes normalized
+provider usage and the resulting accounting cost.
+
+Inspect the resulting meters:
+
+```bash
+curl -sS http://localhost:8080/actuator/prometheus \
+ | rg '^tokenpilot_(preflight|budget|cost|reconciliation)'
+```
+
+The automated smoke test is opt-in and requires both conditions below. A normal
+`./gradlew test` run does not make a provider call.
+
+```bash
+RUN_OPENAI_SMOKE=true \
+ ./gradlew --no-daemon :token-pilot-sample-app:test \
+ --tests io.tokenpilot.sample.OpenAiSmokeE2ETest
+```
+
+To run both paths for a recording, execute `/test/token-pilot/demo/run` with the
+`demo` profile, stop the application, then restart it with `openai-smoke` and
+call the smoke endpoint. Both profiles use port `8080`, so do not run them at
+the same time.
+
+## 5. Reset the demo state
+
+Restarting `bootRun` resets the in-memory budget store and Micrometer counters.
diff --git a/token-pilot-sample-app/Dockerfile b/token-pilot-sample-app/Dockerfile
index b6cda0c..a8fb958 100644
--- a/token-pilot-sample-app/Dockerfile
+++ b/token-pilot-sample-app/Dockerfile
@@ -1,13 +1,13 @@
# token-pilot-sample-app/Dockerfile
# ==========================================
-# 실행 환경 (Java 25 규격 적용) - 빌드는 GitHub Actions가 담당
+# Runtime environment (Java 25); GitHub Actions performs the build.
# ==========================================
FROM eclipse-temurin:25-jre
WORKDIR /app
-# 깃허브 액션이 미리 빌드해둔 jar 파일을 컨테이너 내부로 복사
+# Copy the JAR prebuilt by GitHub Actions into the container.
COPY token-pilot-sample-app/build/libs/*SNAPSHOT.jar ./app.jar
EXPOSE 8080
-ENTRYPOINT ["java", "-jar", "app.jar"]
\ No newline at end of file
+ENTRYPOINT ["java", "-jar", "app.jar"]
diff --git a/token-pilot-sample-app/build.gradle b/token-pilot-sample-app/build.gradle
index 6d316e2..1b9f705 100644
--- a/token-pilot-sample-app/build.gradle
+++ b/token-pilot-sample-app/build.gradle
@@ -13,11 +13,12 @@ repositories {
dependencies {
implementation project(':token-pilot-starter')
+ implementation 'org.springframework.ai:spring-ai-starter-model-openai'
- // 웹 서버 구동을 위한 필수품
+ // Required for running the web server.
implementation 'org.springframework.boot:spring-boot-starter-web'
- // 모니터링 (프로메테우스가 데이터를 긁어갈 수 있게 해줌)
+ // Monitoring support (allows Prometheus to scrape data).
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
@@ -26,12 +27,12 @@ dependencies {
testImplementation 'org.springframework.ai:spring-ai-client-chat'
}
-// 스프링 부트 실행 파일(밀키트) 포장을 강제로 켭니다!
+// Force packaging of the Spring Boot executable.
bootJar {
enabled = true
}
-// 일반 부품용 jar 포장은 필요 없으니 끕니다.
+// Disable the regular library JAR because the executable is the intended artifact.
jar {
enabled = false
}
diff --git a/token-pilot-sample-app/docker-compose.yml b/token-pilot-sample-app/docker-compose.yml
index b658184..c871802 100644
--- a/token-pilot-sample-app/docker-compose.yml
+++ b/token-pilot-sample-app/docker-compose.yml
@@ -1,10 +1,10 @@
services:
prometheus:
- build: ./prometheus # 👈 그라파나처럼 직접 굽기로 통일!
+ build: ./prometheus # 👈 Use a local build consistently, like Grafana.
container_name: prometheus
ports:
- "9090:9090"
- extra_hosts: # 👈 로컬 호스트(스프링 부트)와 통신하기 위한 뚫기
+ extra_hosts: # 👈 Allow communication with the local Spring Boot host.
- "host.docker.internal:host-gateway"
grafana:
@@ -19,4 +19,4 @@ services:
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_NAME=Main Org.
- GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer
- - GF_AUTH_DISABLE_LOGIN_FORM=true # 별도 로그인 없이 대시보드 조회
+ - GF_AUTH_DISABLE_LOGIN_FORM=true # View the dashboard without a separate login form.
diff --git a/token-pilot-sample-app/grafana/provisioning/datasources/datasource.yml b/token-pilot-sample-app/grafana/provisioning/datasources/datasource.yml
index 43735de..63600e9 100644
--- a/token-pilot-sample-app/grafana/provisioning/datasources/datasource.yml
+++ b/token-pilot-sample-app/grafana/provisioning/datasources/datasource.yml
@@ -4,6 +4,6 @@ datasources:
- name: Prometheus
type: prometheus
access: proxy
- url: ${PROMETHEUS_URL} # 고정된 주소 대신, 환경 변수로 구멍을 뚫어둡니다! 로컬과 aws 둘 다 사용하기 위함
+ url: ${PROMETHEUS_URL} # Use an environment variable instead of a fixed address for local and AWS use.
isDefault: true
- uid: prometheus-uid
\ No newline at end of file
+ uid: prometheus-uid
diff --git a/token-pilot-sample-app/prometheus/Dockerfile b/token-pilot-sample-app/prometheus/Dockerfile
index 32512fc..ef9b120 100644
--- a/token-pilot-sample-app/prometheus/Dockerfile
+++ b/token-pilot-sample-app/prometheus/Dockerfile
@@ -1,4 +1,4 @@
FROM prom/prometheus:v2.45.0
-# 우리가 작성한 커스텀 설정 파일을 프로메테우스 설정 경로로 덮어씌웁니다.
-COPY prometheus.yml /etc/prometheus/prometheus.yml
\ No newline at end of file
+# Replace the Prometheus configuration path with our custom configuration file.
+COPY prometheus.yml /etc/prometheus/prometheus.yml
diff --git a/token-pilot-sample-app/prometheus/prometheus.yml b/token-pilot-sample-app/prometheus/prometheus.yml
index e3cddd0..84cf7fa 100644
--- a/token-pilot-sample-app/prometheus/prometheus.yml
+++ b/token-pilot-sample-app/prometheus/prometheus.yml
@@ -5,6 +5,6 @@ scrape_configs:
- job_name: 'token-pilot-backend'
metrics_path: '/actuator/prometheus'
static_configs:
- # demo profile의 Spring Boot 앱은 호스트에서 실행하고,
- # Prometheus는 host.docker.internal을 통해 scrape합니다.
+ # The Spring Boot app in the demo profile runs on the host,
+ # and Prometheus scrapes it through host.docker.internal.
- targets: ['host.docker.internal:8080']
diff --git a/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoChatModel.java b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoChatModel.java
index 9561e42..e6b8641 100644
--- a/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoChatModel.java
+++ b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoChatModel.java
@@ -17,11 +17,12 @@
import java.util.concurrent.atomic.AtomicInteger;
/**
- * 녹화와 로컬 검증을 위한 네트워크 없는 Spring AI provider입니다.
+ * Network-free Spring AI provider for recording and local verification.
*
- * 기본 프로필에는 등록되지 않으며 {@code demo} 프로필에서만 사용됩니다.
- * provider usage, 실패, usage 누락, 동시 dispatch 대기를 모두 결정적으로 재현할 수
- * 있어 API key 없이도 adapter와 accounting lifecycle을 확인할 수 있습니다.
+ * It is not registered in the default profile and is used only by the
+ * {@code demo} profile. Provider usage, failures, missing usage, and concurrent
+ * dispatch waiting can all be reproduced deterministically, allowing the
+ * adapter and accounting lifecycle to be verified without an API key.
*/
public final class DemoChatModel implements ChatModel {
diff --git a/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoConfiguration.java b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoConfiguration.java
index bfc4b71..bd4e35b 100644
--- a/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoConfiguration.java
+++ b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoConfiguration.java
@@ -8,7 +8,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
-/** API key 없이 sample app을 실행하는 demo profile의 Spring AI 구성입니다. */
+/** Spring AI configuration for running the sample app in the demo profile without an API key. */
@Configuration(proxyBeanMethods = false)
@Profile("demo")
public class DemoConfiguration {
diff --git a/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoController.java b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoController.java
index a452354..d557e32 100644
--- a/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoController.java
+++ b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoController.java
@@ -7,7 +7,7 @@
import java.util.List;
import java.util.Map;
-/** {@code demo} 프로필에서만 노출되는 검증용 HTTP API입니다. */
+/** Verification HTTP API exposed only in the {@code demo} profile. */
@RestController
@Profile("demo")
public class DemoController {
diff --git a/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoRunReport.java b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoRunReport.java
index 10bcc7e..e28b97e 100644
--- a/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoRunReport.java
+++ b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoRunReport.java
@@ -5,7 +5,7 @@
import java.util.Map;
import java.util.Collections;
-/** 모든 핵심 control/accounting 시나리오를 한 번에 실행한 demo 결과입니다. */
+/** Demo result containing all core control/accounting scenarios run together. */
public record DemoRunReport(
String runId,
String profile,
diff --git a/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoScenarioResult.java b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoScenarioResult.java
index a54dbdf..46d3b32 100644
--- a/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoScenarioResult.java
+++ b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoScenarioResult.java
@@ -4,7 +4,7 @@
import java.util.Map;
import java.util.Collections;
-/** 화면에서 바로 읽을 수 있는 단일 demo 시나리오 결과입니다. */
+/** Single demo scenario result that can be read directly on screen. */
public record DemoScenarioResult(
String scenario,
String status,
diff --git a/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoScenarioService.java b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoScenarioService.java
index 305309f..ad3d56d 100644
--- a/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoScenarioService.java
+++ b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/DemoScenarioService.java
@@ -45,8 +45,9 @@
import java.util.concurrent.atomic.AtomicInteger;
/**
- * 반복 실행할 수 있는 framework-independent control/accounting 시나리오입니다.
- * 모든 provider 호출은 {@link DemoChatModel}로만 전달되며 외부 네트워크를 사용하지 않습니다.
+ * Repeatable framework-independent control/accounting scenarios.
+ * All provider calls are routed only to {@link DemoChatModel}, and no external
+ * network is used.
*/
@Service
@Profile("demo")
@@ -226,7 +227,7 @@ private DemoScenarioResult budgetConcurrency(String runId) {
));
}
- // 경쟁 요청들은 첫 예약이 IN_FLIGHT인 동안 예약 단계에서 종료되어야 합니다.
+ // Competing requests must finish at the reservation stage while the first reservation is IN_FLIGHT.
for (Future competitor : competitors) {
competitorOutcomes.add(await(competitor));
}
diff --git a/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/OpenAiSmokeController.java b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/OpenAiSmokeController.java
new file mode 100644
index 0000000..145eb6b
--- /dev/null
+++ b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/OpenAiSmokeController.java
@@ -0,0 +1,268 @@
+package io.tokenpilot.sample;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.tokenpilot.autoconfigure.TokenPilotProperties;
+import io.tokenpilot.budget.BudgetDecision;
+import io.tokenpilot.budget.BudgetEvaluator;
+import io.tokenpilot.budget.BudgetSnapshot;
+import io.tokenpilot.budget.BudgetStateStore;
+import io.tokenpilot.core.domain.TokenUsage;
+import io.tokenpilot.core.domain.UsageSource;
+import io.tokenpilot.springai.UsageExtractor;
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.chat.client.ChatClientResponse;
+import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.chat.model.Generation;
+import org.springframework.ai.chat.prompt.ChatOptions;
+import org.springframework.context.annotation.Profile;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+import java.util.UUID;
+
+/** Verification API that performs real provider calls only in the {@code openai-smoke} profile. */
+@RestController
+@Profile("openai-smoke")
+public final class OpenAiSmokeController {
+
+ private static final String PROFILE = "openai-smoke";
+ private static final String TENANT_ID = "openai-smoke";
+ private static final String DEFAULT_PROMPT =
+ "Reply with exactly one short sentence confirming that the provider is reachable.";
+
+ private final ChatClient.Builder chatClientBuilder;
+ private final UsageExtractor usageExtractor;
+ private final BudgetEvaluator budgetEvaluator;
+ private final BudgetStateStore budgetStateStore;
+ private final MeterRegistry meterRegistry;
+ private final TokenPilotProperties properties;
+
+ public OpenAiSmokeController(
+ ChatClient.Builder chatClientBuilder,
+ UsageExtractor usageExtractor,
+ BudgetEvaluator budgetEvaluator,
+ BudgetStateStore budgetStateStore,
+ MeterRegistry meterRegistry,
+ TokenPilotProperties properties
+ ) {
+ this.chatClientBuilder = chatClientBuilder;
+ this.usageExtractor = usageExtractor;
+ this.budgetEvaluator = budgetEvaluator;
+ this.budgetStateStore = budgetStateStore;
+ this.meterRegistry = meterRegistry;
+ this.properties = properties;
+ }
+
+ @GetMapping("/test/token-pilot/openai-smoke")
+ public OpenAiSmokeReport smoke(
+ @RequestParam(defaultValue = DEFAULT_PROMPT) String prompt
+ ) {
+ String normalizedPrompt = requirePrompt(prompt);
+ String modelId = requireModelId();
+ int reservedOutputTokens = reservedOutputTokens();
+ String requestId = "openai-smoke-" + UUID.randomUUID();
+
+ Map tags = Map.of("tenant_id", TENANT_ID);
+ BudgetDecision beforeDecision = budgetEvaluator.evaluate(tags);
+ BudgetSnapshot beforeBudget = budgetStateStore.snapshot(
+ beforeDecision.key(),
+ beforeDecision.limit()
+ );
+ double beforeCommittedCost = counter(
+ "tokenpilot.cost.total",
+ "currency", "USD"
+ );
+ double beforeCommitted = committedReconciliationCounter();
+ double beforeReconciliationRequired = reconciliationRequiredCounter();
+ double beforeReservationsCreated = counter(
+ "tokenpilot.budget.reservations",
+ "state", "created"
+ );
+
+ ChatClientResponse response = chatClientBuilder.clone()
+ .build()
+ .prompt()
+ .user(normalizedPrompt)
+ .options(ChatOptions.builder()
+ .model(modelId)
+ .maxTokens(reservedOutputTokens)
+ .temperature(0.0))
+ .advisors(advisors -> advisors
+ .param("tenant_id", TENANT_ID)
+ .param("tokenpilot.request.id", requestId)
+ .param("tokenpilot.attempt.id", requestId + "-attempt"))
+ .call()
+ .chatClientResponse();
+
+ TokenUsage usage = usageExtractor.extract(response);
+ String responseModel = responseModel(response);
+ String responseText = responseText(response);
+
+ BudgetDecision afterDecision = budgetEvaluator.evaluate(tags);
+ BudgetSnapshot afterBudget = budgetStateStore.snapshot(
+ afterDecision.key(),
+ afterDecision.limit()
+ );
+ double committedCostDelta = counter(
+ "tokenpilot.cost.total",
+ "currency", "USD"
+ ) - beforeCommittedCost;
+ double committedDelta = committedReconciliationCounter() - beforeCommitted;
+ double reconciliationRequiredDelta = reconciliationRequiredCounter()
+ - beforeReconciliationRequired;
+ String accountingState = accountingState(
+ committedDelta,
+ reconciliationRequiredDelta
+ );
+ boolean passed = hasText(responseText)
+ && usage.source() != UsageSource.UNAVAILABLE
+ && "COMMITTED".equals(accountingState);
+
+ return new OpenAiSmokeReport(
+ passed ? "PASS" : "FAIL",
+ PROFILE,
+ "OpenAI Chat Completions",
+ requestId,
+ modelId,
+ responseModel,
+ responseText,
+ usage.source().name(),
+ usage.inputTokens(),
+ usage.outputTokens(),
+ usage.totalTokens(),
+ accountingState,
+ Map.of(
+ "committedCostUsd", committedCostDelta,
+ "reconciliationCommitted", committedDelta,
+ "reconciliationRequired", reconciliationRequiredDelta,
+ "reservationsCreated",
+ counter("tokenpilot.budget.reservations", "state", "created")
+ - beforeReservationsCreated
+ ),
+ budget(afterBudget),
+ note(passed, responseModel, usage)
+ );
+ }
+
+ private String requirePrompt(String prompt) {
+ if (!hasText(prompt)) {
+ throw new IllegalArgumentException("prompt must not be blank");
+ }
+ return prompt.trim();
+ }
+
+ private String requireModelId() {
+ String modelId = properties.getSpringAi().getDefaultModelId();
+ if (!hasText(modelId)) {
+ throw new IllegalStateException(
+ "token-pilot.spring-ai.default-model-id must be configured"
+ );
+ }
+ return modelId.trim();
+ }
+
+ private int reservedOutputTokens() {
+ Long configured = properties.getSpringAi().getDefaultReservedOutputTokens();
+ long value = configured == null ? 64L : configured;
+ if (value <= 0 || value > Integer.MAX_VALUE) {
+ throw new IllegalStateException(
+ "token-pilot.spring-ai.default-reserved-output-tokens must be between 1 and "
+ + Integer.MAX_VALUE
+ );
+ }
+ return (int) value;
+ }
+
+ private String responseModel(ChatClientResponse response) {
+ ChatResponse chatResponse = response == null ? null : response.chatResponse();
+ return chatResponse == null || chatResponse.getMetadata() == null
+ ? null
+ : chatResponse.getMetadata().getModel();
+ }
+
+ private String responseText(ChatClientResponse response) {
+ ChatResponse chatResponse = response == null ? null : response.chatResponse();
+ if (chatResponse == null) {
+ return null;
+ }
+ Generation result = chatResponse.getResult();
+ if (result == null || result.getOutput() == null) {
+ return null;
+ }
+ return result.getOutput().getText();
+ }
+
+ private Map budget(BudgetSnapshot snapshot) {
+ return Map.of(
+ "committedCost", CostBoundaryFormatter.format(snapshot.committedCost()),
+ "activeReservedCost", CostBoundaryFormatter.format(snapshot.activeReservedCost()),
+ "pendingReconciliationLiability",
+ CostBoundaryFormatter.format(snapshot.pendingReconciliationLiability()),
+ "effectiveUsage", CostBoundaryFormatter.format(snapshot.effectiveUsage()),
+ "remaining", CostBoundaryFormatter.format(snapshot.remaining()),
+ "limit", CostBoundaryFormatter.format(snapshot.limit())
+ );
+ }
+
+ private double committedReconciliationCounter() {
+ return counter(
+ "tokenpilot.reconciliation.outcomes",
+ "outcome", "committed",
+ "reason", "actual_usage_reported"
+ );
+ }
+
+ private double reconciliationRequiredCounter() {
+ return counter(
+ "tokenpilot.reconciliation.outcomes",
+ "outcome", "reconciliation_required",
+ "reason", "actual_usage_unavailable"
+ ) + counter(
+ "tokenpilot.reconciliation.outcomes",
+ "outcome", "reconciliation_required",
+ "reason", "pricing_reconciliation_required"
+ );
+ }
+
+ private double counter(String name, String... tags) {
+ Counter counter = meterRegistry.find(name).tags(tags).counter();
+ return counter == null ? 0.0 : counter.count();
+ }
+
+ private String accountingState(
+ double committedDelta,
+ double reconciliationRequiredDelta
+ ) {
+ if (committedDelta > 0.0) {
+ return "COMMITTED";
+ }
+ if (reconciliationRequiredDelta > 0.0) {
+ return "RECONCILIATION_REQUIRED";
+ }
+ return "UNKNOWN";
+ }
+
+ private String note(
+ boolean passed,
+ String responseModel,
+ TokenUsage usage
+ ) {
+ if (passed) {
+ return "실제 OpenAI 응답의 usage를 정규화하고 예약 비용을 COMMITTED로 정산했습니다.";
+ }
+ if (usage.source() == UsageSource.UNAVAILABLE) {
+ return "provider 응답은 받았지만 usage를 확인하지 못해 정산 대기로 남았습니다.";
+ }
+ if (!hasText(responseModel)) {
+ return "provider 응답의 model metadata가 없어 정산을 확정하지 못했습니다.";
+ }
+ return "provider 응답은 받았지만 예약 snapshot과 actual 정산 결과를 확인해야 합니다.";
+ }
+
+ private boolean hasText(String value) {
+ return value != null && !value.isBlank();
+ }
+}
diff --git a/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/OpenAiSmokeReport.java b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/OpenAiSmokeReport.java
new file mode 100644
index 0000000..d4aaf18
--- /dev/null
+++ b/token-pilot-sample-app/src/main/java/io/tokenpilot/sample/OpenAiSmokeReport.java
@@ -0,0 +1,30 @@
+package io.tokenpilot.sample;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/** Smoke result showing the real OpenAI call and the corresponding Token Pilot settlement. */
+public record OpenAiSmokeReport(
+ String status,
+ String profile,
+ String provider,
+ String requestId,
+ String configuredModel,
+ String responseModel,
+ String response,
+ String usageSource,
+ long inputTokens,
+ long outputTokens,
+ long totalTokens,
+ String accountingState,
+ Map metrics,
+ Map budget,
+ String note
+) {
+
+ public OpenAiSmokeReport {
+ metrics = Collections.unmodifiableMap(new LinkedHashMap<>(metrics));
+ budget = Collections.unmodifiableMap(new LinkedHashMap<>(budget));
+ }
+}
diff --git a/token-pilot-sample-app/src/main/resources/application-openai-smoke.yml b/token-pilot-sample-app/src/main/resources/application-openai-smoke.yml
new file mode 100644
index 0000000..e11c63e
--- /dev/null
+++ b/token-pilot-sample-app/src/main/resources/application-openai-smoke.yml
@@ -0,0 +1,56 @@
+server:
+ port: 8080
+
+management:
+ endpoints:
+ web:
+ exposure:
+ include: prometheus, health
+ metrics:
+ tags:
+ application: token-pilot-sample-app
+
+spring:
+ ai:
+ model:
+ chat: openai
+ embedding: none
+ image: none
+ audio:
+ transcription: none
+ speech: none
+ moderation: none
+ openai:
+ api-key: ${OPENAI_API_KEY}
+ timeout: 30s
+ max-retries: 0
+ chat:
+ model: ${OPENAI_MODEL:gpt-4o-mini-2024-07-18}
+ temperature: 0.0
+
+token-pilot:
+ enabled: true
+ pricing:
+ plans:
+ - model-id: gpt-4o-mini-2024-07-18
+ currency: USD
+ rates:
+ PROMPT: ${OPENAI_PROMPT_PRICE_PER_1K:0.00015}
+ COMPLETION: ${OPENAI_COMPLETION_PRICE_PER_1K:0.00060}
+ spring-ai:
+ default-model-id: ${OPENAI_MODEL:gpt-4o-mini-2024-07-18}
+ default-reserved-output-tokens: 64
+ framing-headroom-tokens: 0
+ budget:
+ enabled: true
+ policy-id: openai-smoke-monthly
+ target-type: tenant
+ target-tag-key: tenant_id
+ fallback-target-id: openai-smoke
+ monthly-limit: ${OPENAI_SMOKE_MONTHLY_LIMIT:1.00}
+ currency: USD
+ zone-id: UTC
+ metrics:
+ enabled: true
+ legacy-ai-token-metrics-enabled: false
+ tag-whitelist: []
diff --git a/token-pilot-sample-app/src/main/resources/application.yml b/token-pilot-sample-app/src/main/resources/application.yml
index 53589fd..0e0d18d 100644
--- a/token-pilot-sample-app/src/main/resources/application.yml
+++ b/token-pilot-sample-app/src/main/resources/application.yml
@@ -1,6 +1,17 @@
server:
port: 8080
+spring:
+ ai:
+ model:
+ chat: none
+ embedding: none
+ image: none
+ audio:
+ transcription: none
+ speech: none
+ moderation: none
+
management:
endpoints:
web:
diff --git a/token-pilot-sample-app/src/test/java/io/tokenpilot/sample/DemoScenarioE2ETest.java b/token-pilot-sample-app/src/test/java/io/tokenpilot/sample/DemoScenarioE2ETest.java
index 5e880d6..63431c6 100644
--- a/token-pilot-sample-app/src/test/java/io/tokenpilot/sample/DemoScenarioE2ETest.java
+++ b/token-pilot-sample-app/src/test/java/io/tokenpilot/sample/DemoScenarioE2ETest.java
@@ -13,7 +13,7 @@
import static org.assertj.core.api.Assertions.assertThat;
-/** API key 없이 실행되는 제출용 demo profile의 HTTP 계약을 검증합니다. */
+/** Verifies the HTTP contract of the submission demo profile without an API key. */
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("demo")
class DemoScenarioE2ETest {
diff --git a/token-pilot-sample-app/src/test/java/io/tokenpilot/sample/OpenAiSmokeE2ETest.java b/token-pilot-sample-app/src/test/java/io/tokenpilot/sample/OpenAiSmokeE2ETest.java
new file mode 100644
index 0000000..a937ef1
--- /dev/null
+++ b/token-pilot-sample-app/src/test/java/io/tokenpilot/sample/OpenAiSmokeE2ETest.java
@@ -0,0 +1,62 @@
+package io.tokenpilot.sample;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariables;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.test.context.ActiveProfiles;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Real OpenAI smoke test that can incur cost only when explicitly enabled. */
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@ActiveProfiles("openai-smoke")
+@EnabledIfEnvironmentVariables({
+ @EnabledIfEnvironmentVariable(
+ named = "RUN_OPENAI_SMOKE",
+ matches = "(?i)true",
+ disabledReason = "RUN_OPENAI_SMOKE=true일 때만 실제 provider를 호출합니다."
+ ),
+ @EnabledIfEnvironmentVariable(
+ named = "OPENAI_API_KEY",
+ matches = ".+",
+ disabledReason = "OPENAI_API_KEY가 없으면 실제 provider smoke를 건너뜁니다."
+ )
+})
+class OpenAiSmokeE2ETest {
+
+ private final HttpClient httpClient = HttpClient.newHttpClient();
+
+ @LocalServerPort
+ private int port;
+
+ @Test
+ void openAiResponseIsReconciledByTokenPilot() throws Exception {
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(
+ "http://localhost:" + port
+ + "/test/token-pilot/openai-smoke?prompt="
+ + "Reply%20with%20one%20short%20sentence."
+ ))
+ .GET()
+ .build();
+
+ HttpResponse response = httpClient.send(
+ request,
+ HttpResponse.BodyHandlers.ofString()
+ );
+
+ assertThat(response.statusCode()).isEqualTo(200);
+ assertThat(response.body())
+ .contains("\"status\":\"PASS\"")
+ .contains("\"provider\":\"OpenAI Chat Completions\"")
+ .contains("\"accountingState\":\"COMMITTED\"")
+ .doesNotContain("\"usageSource\":\"UNAVAILABLE\"");
+ }
+}
diff --git a/token-pilot-spring-ai/build.gradle b/token-pilot-spring-ai/build.gradle
index 0e4601d..ff48264 100644
--- a/token-pilot-spring-ai/build.gradle
+++ b/token-pilot-spring-ai/build.gradle
@@ -8,7 +8,7 @@ dependencies {
api 'org.springframework.ai:spring-ai-model'
api 'org.springframework.ai:spring-ai-client-chat'
- // Reactor (스트리밍 처리를 위한 Flux 등)
+ // Reactor (for Flux and other streaming types)
api 'io.projectreactor:reactor-core'
testImplementation 'org.junit.jupiter:junit-jupiter-api'
diff --git a/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/LedgerAdvisor.java b/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/LedgerAdvisor.java
index 27421f6..6fb6e40 100644
--- a/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/LedgerAdvisor.java
+++ b/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/LedgerAdvisor.java
@@ -7,8 +7,9 @@
import org.springframework.core.Ordered;
/**
- * ChatClient 호출 시 토큰 사용량을 가로채서 기록하는 어드바이저 인터페이스.
- * Spring AI의 {@link BaseAdvisor}를 상속하여 AI 호출 전후 처리 로직을 표준 방식으로 정의합니다.
+ * Advisor interface that intercepts and records token usage during ChatClient calls.
+ * Extends Spring AI's {@link BaseAdvisor} to define standard processing before
+ * and after an AI call.
*/
public interface LedgerAdvisor extends BaseAdvisor {
diff --git a/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/ReservedOutputTokensResolver.java b/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/ReservedOutputTokensResolver.java
index 5c0910d..dad9d51 100644
--- a/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/ReservedOutputTokensResolver.java
+++ b/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/ReservedOutputTokensResolver.java
@@ -4,12 +4,13 @@
import java.util.OptionalLong;
-/** Provider 전용 request option에서 reserved output token 수를 해석합니다. */
+/** Resolves reserved output token counts from provider-specific request options. */
@FunctionalInterface
public interface ReservedOutputTokensResolver {
/**
- * 지원하는 provider request이면 양수 token 수를, 지원하지 않으면 빈 값을 반환합니다.
+ * Returns a positive token count for a supported provider request, or empty
+ * when the request is not supported.
*/
OptionalLong resolve(ChatClientRequest request);
}
diff --git a/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/UsageExtractor.java b/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/UsageExtractor.java
index 4aaabdf..078206a 100644
--- a/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/UsageExtractor.java
+++ b/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/UsageExtractor.java
@@ -4,7 +4,8 @@
import org.springframework.ai.chat.client.ChatClientResponse;
/**
- * AI 응답({@link ChatClientResponse})에서 토큰 사용량 정보를 추출하는 인터페이스.
+ * Interface for extracting token usage information from an AI response
+ * ({@link ChatClientResponse}).
*/
public interface UsageExtractor {
TokenUsage extract(ChatClientResponse response);
diff --git a/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/internal/DefaultLedgerAdvisor.java b/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/internal/DefaultLedgerAdvisor.java
index e305d2c..326b9e2 100644
--- a/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/internal/DefaultLedgerAdvisor.java
+++ b/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/internal/DefaultLedgerAdvisor.java
@@ -37,16 +37,16 @@
import org.springframework.ai.chat.prompt.ChatOptions;
/**
- * 기본 {@link LedgerAdvisor} 구현체.
- * {@link UsageExtractor}를 사용하여 토큰 사용량을 추출하고,
- * 그 결과를 {@link LedgerManager}에 기록하는 핵심 비즈니스 로직을 수행합니다.
- * 또한 {@link BudgetEvaluator}를 통해 예산 초과 여부를 사전에 차단하고,
- * 호출 성공 시 {@link BudgetStateStore}에 비용을 누적합니다.
+ * Default {@link LedgerAdvisor} implementation.
+ * Uses {@link UsageExtractor} to extract token usage and records the result in
+ * {@link LedgerManager} as its core business logic. It also blocks budget
+ * overages in advance through {@link BudgetEvaluator} and accumulates cost in
+ * {@link BudgetStateStore} when a call succeeds.
*
- * {@link #before(ChatClientRequest, AdvisorChain)}는 budget-disabled ledger-only
- * compatibility 경로의 상태 guard입니다. budget-enabled 호출은
- * {@link #adviseCall(ChatClientRequest, CallAdvisorChain)}에서 후보 비용을 포함한
- * preflight와 원자적 reservation lifecycle을 수행합니다.
+ * {@link #before(ChatClientRequest, AdvisorChain)} is the state guard for the
+ * budget-disabled ledger-only compatibility path. Budget-enabled calls perform
+ * preflight with candidate cost and the atomic reservation lifecycle in
+ * {@link #adviseCall(ChatClientRequest, CallAdvisorChain)}.
*/
public class DefaultLedgerAdvisor implements LedgerAdvisor {
diff --git a/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/internal/DefaultUsageExtractor.java b/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/internal/DefaultUsageExtractor.java
index f3d1c3b..3502a55 100644
--- a/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/internal/DefaultUsageExtractor.java
+++ b/token-pilot-spring-ai/src/main/java/io/tokenpilot/springai/internal/DefaultUsageExtractor.java
@@ -16,10 +16,11 @@
import java.util.Map;
/**
- * 기본 {@link UsageExtractor} 구현체.
- * Spring AI의 {@link Usage} 정보를 {@link TokenUsage}로 변환하며,
- * provider native usage와 메타데이터에서 cache/reasoning breakdown을 식별합니다.
- * Provider가 비포괄 총량을 반환하면 Token Pilot의 포괄 총량 계약에 맞게 정규화합니다.
+ * Default {@link UsageExtractor} implementation.
+ * Converts Spring AI {@link Usage} information to {@link TokenUsage} and
+ * identifies cache/reasoning breakdowns in provider-native usage and metadata.
+ * When a provider returns non-inclusive totals, normalizes them to Token Pilot's
+ * inclusive-total contract.
*/
public class DefaultUsageExtractor implements UsageExtractor {
private static final TypeReference