Viewing files at commit e9b767a51662Commit detailsView latest

scripts/oakbench/calibration.py 95 lines · 3.8 KB · python
1
"""Versioned token-calibration factors for the reporting layer.
2
3
Rows stay RAW: they carry char-based token estimates exactly as measured.
4
Converting raw char counts into calibrated token estimates is a reporting
5
concern, and every converted number must cite the calibration version and
6
method it came from so two reports can never silently disagree about what
7
"tokens" means.
8
9
Factors live in config/token_calibration.json, written by
10
scripts/token_calibration_campaign.py. The committed file is an honest
11
uncalibrated placeholder (n=0, null CIs) using documented heuristic
12
constants: ~4.0 chars/token for prose-like output, ~3.2 for diff-like
13
output, which BPE-family tokenizers split more finely.
14
15
Null means unmeasured (ADR-0002): an unknown style or a style whose factor
16
is null yields None — never a silent default factor. Optional enhancers
17
(tiktoken, the Anthropic count-tokens API) are feature-detected and never
18
required (ADR-0001).
19
"""
20
21
from __future__ import annotations
22
23
import importlib.util
24
import json
25
import os
26
from pathlib import Path
27
from typing import Any, Optional
28
29
SCHEMA_VERSION = 1
30
31
DEFAULT_CALIBRATION_PATH = Path(__file__).resolve().parents[2] / "config" / "token_calibration.json"
32
33
34
def load_calibration(path: Path | str = DEFAULT_CALIBRATION_PATH) -> dict[str, Any]:
35
    """Load and validate a calibration factors file.
36
37
    Raises ValueError with a clear message on a missing/foreign
38
    schema_version so a stale or future-format file fails loudly instead of
39
    mispricing every report.
40
    """
41
    data = json.loads(Path(path).read_text())
42
    if not isinstance(data, dict):
43
        raise ValueError(f"calibration file {path}: expected a JSON object, got {type(data).__name__}")
44
    schema_version = data.get("schema_version")
45
    if schema_version != SCHEMA_VERSION:
46
        raise ValueError(
47
            f"calibration file {path}: unsupported schema_version {schema_version!r}; "
48
            f"this harness understands schema_version {SCHEMA_VERSION}"
49
        )
50
    if not isinstance(data.get("factors"), dict):
51
        raise ValueError(f"calibration file {path}: missing 'factors' object")
52
    if not isinstance(data.get("calibration_version"), str) or not data["calibration_version"]:
53
        raise ValueError(f"calibration file {path}: missing non-empty 'calibration_version'")
54
    return data
55
56
57
def calibrated_tokens(
58
    char_count: Optional[float], style: str, calibration: dict[str, Any]
59
) -> Optional[dict[str, Any]]:
60
    """Convert a raw char count to a calibrated token estimate, citing its source.
61
62
    Returns None when char_count is None, the style has no factor entry, or
63
    the style's factor is null — unmeasured stays unmeasured (ADR-0002);
64
    there is no silent default factor.
65
    """
66
    if char_count is None:
67
        return None
68
    factors = calibration.get("factors")
69
    if not isinstance(factors, dict):
70
        return None
71
    entry = factors.get(style)
72
    if not isinstance(entry, dict):
73
        return None
74
    chars_per_token = entry.get("chars_per_token")
75
    if not isinstance(chars_per_token, (int, float)) or isinstance(chars_per_token, bool) or chars_per_token <= 0:
76
        return None
77
    return {
78
        "tokens_estimated": float(char_count) / float(chars_per_token),
79
        "calibration_version": str(calibration.get("calibration_version")),
80
        "method": str(calibration.get("method")),
81
        "style": style,
82
    }
83
84
85
def available_token_counters() -> dict[str, bool]:
86
    """Feature-detect optional token counters without importing or calling them.
87
88
    tiktoken: importable module (never required, ADR-0001).
89
    anthropic_api: ANTHROPIC_API_KEY present in the environment — presence
90
    only; nothing here performs a network call.
91
    """
92
    return {
93
        "tiktoken": importlib.util.find_spec("tiktoken") is not None,
94
        "anthropic_api": bool(os.environ.get("ANTHROPIC_API_KEY")),
95
    }