"""Provider가 비용을 반환하지 않을 때 쓰는 공식 참조단가 계산. ``cost_usd``를 직접 반환하는 Claude CLI는 SDK가 계산한 호출별 추정값을 그대로 기록한다. Codex/Agy처럼 토큰만 반환하는 구독형 CLI와 Anthropic Messages API는 호출 시점의 공식 공개 단가로 USD 상당액을 계산한다. 어느 쪽도 청구서 실비가 아니며 비교·예산 관측용 추정 비용이다. """ from __future__ import annotations from dataclasses import dataclass MILLION = 1_000_000 RATE_CARD_VERSION = "2026-07-31" GOOGLE_PRICING_URL = "https://ai.google.dev/gemini-api/docs/pricing" OPENAI_CODEX_RATE_URL = "https://help.openai.com/en/articles/20001106-codex-rate-card" OPENAI_CREDIT_VALUE_URL = ( "https://help.openai.com/en/articles/20001147-codex-credits-for-students" ) ANTHROPIC_PRICING_URL = "https://platform.claude.com/docs/en/about-claude/pricing" @dataclass(frozen=True, slots=True) class ModelRate: input_usd_per_million: float output_usd_per_million: float cached_input_usd_per_million: float rate_id: str label: str source_url: str @dataclass(frozen=True, slots=True) class CostEstimate: cost_usd: float rate_id: str rate_label: str source_url: str def _rate( *, provider: str, model: str, tokens_in: int, ) -> ModelRate | None: provider_key = provider.strip().lower() model_key = model.strip().lower() # Agy reasoning suffix는 같은 기반 모델의 추론 강도 선택값이다. for suffix in ("-low", "-medium", "-high", "-thinking"): if model_key.endswith(suffix): model_key = model_key[: -len(suffix)] break if provider_key == "agy_cli": if model_key == "gemini-3.6-flash": return ModelRate( 1.50, 7.50, 0.15, f"google-gemini-3.6-flash-standard@{RATE_CARD_VERSION}", "Google Gemini 3.6 Flash 표준 단가 · 입력 $1.50/M · 캐시 $0.15/M · 출력 $7.50/M", GOOGLE_PRICING_URL, ) if model_key == "gemini-3.5-flash": return ModelRate( 1.50, 9.00, 0.15, f"google-gemini-3.5-flash-standard@{RATE_CARD_VERSION}", "Google Gemini 3.5 Flash 표준 단가 · 입력 $1.50/M · 캐시 $0.15/M · 출력 $9.00/M", GOOGLE_PRICING_URL, ) if model_key == "gemini-3.1-pro": long_context = tokens_in > 200_000 return ModelRate( 4.00 if long_context else 2.00, 18.00 if long_context else 12.00, 0.40 if long_context else 0.20, ( f"google-gemini-3.1-pro-standard-" f"{'long' if long_context else 'short'}@{RATE_CARD_VERSION}" ), ( "Google Gemini 3.1 Pro 표준 단가" f" ({'>200K' if long_context else '≤200K'} 입력)" ), GOOGLE_PRICING_URL, ) if model_key == "claude-sonnet-4-6": return ModelRate( 3.00, 15.00, 0.30, f"anthropic-claude-sonnet-4.6-standard@{RATE_CARD_VERSION}", "Anthropic Claude Sonnet 4.6 표준 단가 · 입력 $3/M · 캐시 $0.30/M · 출력 $15/M", ANTHROPIC_PRICING_URL, ) if model_key == "claude-opus-4-6": return ModelRate( 5.00, 25.00, 0.50, f"anthropic-claude-opus-4.6-standard@{RATE_CARD_VERSION}", "Anthropic Claude Opus 4.6 표준 단가 · 입력 $5/M · 캐시 $0.50/M · 출력 $25/M", ANTHROPIC_PRICING_URL, ) if provider_key == "claude_api": if model_key.startswith(("claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6")): return ModelRate( 5.00, 25.00, 0.50, f"anthropic-claude-opus-standard@{RATE_CARD_VERSION}", "Anthropic Claude Opus 표준 단가 · 입력 $5/M · 캐시 $0.50/M · 출력 $25/M", ANTHROPIC_PRICING_URL, ) if model_key.startswith(("claude-sonnet-4-6", "claude-sonnet-4-5")): return ModelRate( 3.00, 15.00, 0.30, f"anthropic-claude-sonnet-standard@{RATE_CARD_VERSION}", "Anthropic Claude Sonnet 표준 단가 · 입력 $3/M · 캐시 $0.30/M · 출력 $15/M", ANTHROPIC_PRICING_URL, ) if model_key.startswith("claude-haiku-4-5"): return ModelRate( 1.00, 5.00, 0.10, f"anthropic-claude-haiku-4.5-standard@{RATE_CARD_VERSION}", "Anthropic Claude Haiku 4.5 표준 단가 · 입력 $1/M · 캐시 $0.10/M · 출력 $5/M", ANTHROPIC_PRICING_URL, ) if provider_key == "codex_cli": # Codex rate card의 credit/MTok에 공식 환산값 2,500 credits=$100 # (1 credit=$0.04)을 적용한다. codex_rates = { "gpt-5.6-sol": (5.00, 30.00, 0.50), "gpt-5.6-terra": (2.50, 15.00, 0.25), "gpt-5.6-luna": (1.00, 6.00, 0.10), "gpt-5.5": (5.00, 30.00, 0.50), "gpt-5.4": (2.50, 15.00, 0.25), "gpt-5.4-mini": (0.75, 4.52, 0.075), "gpt-5.3-codex": (1.75, 14.00, 0.175), "gpt-5.2": (1.75, 14.00, 0.175), } matched = next( ( (name, values) for name, values in codex_rates.items() if model_key == name or model_key.startswith(f"{name}-") ), None, ) if matched is not None: name, (input_rate, output_rate, cached_rate) = matched return ModelRate( input_rate, output_rate, cached_rate, f"openai-codex-{name}-credits@{RATE_CARD_VERSION}", ( f"OpenAI Codex {name} 크레딧 환산 · 입력 ${input_rate:g}/M" f" · 캐시 ${cached_rate:g}/M · 출력 ${output_rate:g}/M" ), OPENAI_CODEX_RATE_URL, ) return None def estimate_reference_cost( *, provider: str, model: str, tokens_in: int, tokens_out: int, cached_input_tokens: int = 0, ) -> CostEstimate | None: """공식 공개 단가로 USD 상당액을 계산한다. ``tokens_in``은 CLI가 반환한 전체 입력 토큰으로 보고 캐시 읽기 토큰을 차감한다. 잘못된 음수·과대 캐시 값은 0..tokens_in 범위로 제한한다. """ safe_input = max(0, int(tokens_in or 0)) safe_output = max(0, int(tokens_out or 0)) if safe_input == 0 and safe_output == 0: return None cached = min(safe_input, max(0, int(cached_input_tokens or 0))) rate = _rate(provider=provider, model=model, tokens_in=safe_input) if rate is None: return None uncached = safe_input - cached cost = ( uncached * rate.input_usd_per_million + cached * rate.cached_input_usd_per_million + safe_output * rate.output_usd_per_million ) / MILLION return CostEstimate( cost_usd=round(cost, 8), rate_id=rate.rate_id, rate_label=rate.label, source_url=rate.source_url, ) def provider_uses_reference_cost(provider: str) -> bool: """청구 실비 대신 토큰 참조단가로 비용을 정규화하는 provider인지 반환한다.""" return provider.strip().lower() in {"agy_cli", "codex_cli", "claude_api"} __all__ = [ "ANTHROPIC_PRICING_URL", "CostEstimate", "GOOGLE_PRICING_URL", "OPENAI_CODEX_RATE_URL", "OPENAI_CREDIT_VALUE_URL", "RATE_CARD_VERSION", "estimate_reference_cost", "provider_uses_reference_cost", ]