Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion catalogue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,12 @@ Missing fields mean unrecorded facts, never unsupported features or zero prices.
records the number of decimal places in a published rounded price. A rounded
price is evidence of that price, not a basis for inventing an exact token count.
The checker reproduces any recorded token count from the formula and rejects
duplicate size/quality examples.
duplicate size/quality examples. Optional `quality_multipliers` supplies exact positive
decimal strings for every quality axis, together with `result_rounding:
"nearest_ties_up"`. Apply the multiplier after the base output-token ceiling, then
round to the nearest integer with half ties upward. This preserves the GPT Image
2.6 calculator's second step; older calculators omit both fields and keep their
original formula. The multiplier map must match the quality-axis keys exactly.
- **Vision tokenization.** `vision_tokenization` is a sourced set of the rules the
vendor actually publishes: `patch_size_px`, `token_multiplier`,
`detail_patch_budgets`, `detail_token_budgets`, `detail_maximum_edges`,
Expand Down
19 changes: 19 additions & 0 deletions catalogue/schemas/models.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,26 @@
"type": "string",
"format": "uri",
"pattern": "^https://[^/]+(?:/|$)"
},
"quality_multipliers": {
"type": "object",
"minProperties": 1,
"propertyNames": {
"pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
},
"additionalProperties": {
"type": "string",
"pattern": "^(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$",
"description": "Exact positive decimal multiplier; no exponent notation."
}
},
"result_rounding": {
"enum": ["nearest_ties_up"]
}
},
"dependentRequired": {
"quality_multipliers": ["result_rounding"],
"result_rounding": ["quality_multipliers"]
}
},
"output_token_example": {
Expand Down
17 changes: 17 additions & 0 deletions scripts/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,20 @@ def numeric_range_errors(value: dict[str, Any], where: str) -> Iterator[str]:
def calculator_errors(calculator: dict[str, Any], where: str) -> Iterator[str]:
axes = calculator.get("quality_axes", {})
axes = axes if isinstance(axes, dict) else {}
multipliers = calculator.get("quality_multipliers")
valid_multipliers: dict[str, Fraction] = {}
if isinstance(multipliers, dict):
if multipliers.keys() != axes.keys():
yield f"{where}/quality_multipliers: qualities must match quality_axes exactly"
for quality, raw in multipliers.items():
try:
number = Fraction(raw) if isinstance(raw, str) else Fraction(0)
except (ValueError, ZeroDivisionError):
number = Fraction(0)
if number <= 0:
yield f"{where}/quality_multipliers/{quality}: multiplier must be a positive decimal string"
else:
valid_multipliers[quality] = number
seen = set()
examples = calculator.get("examples", [])
if not isinstance(examples, list):
Expand Down Expand Up @@ -206,6 +220,9 @@ def calculator_errors(calculator: dict[str, Any], where: str) -> Iterator[str]:
short_axis = round(Fraction(axis * min(width, height), max(width, height)))
numerator = axis * short_axis * (offset + width * height)
expected = -(-numerator // divisor)
if quality in valid_multipliers and calculator.get("result_rounding") == "nearest_ties_up":
scaled = expected * valid_multipliers[quality]
expected = (scaled.numerator * 2 + scaled.denominator) // (scaled.denominator * 2)
if output_tokens != expected:
yield f"{example_where}: output_tokens {output_tokens} does not match calculator {expected}"

Expand Down
46 changes: 46 additions & 0 deletions tests/test_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,52 @@ def test_verified_output_token_example_must_match_calculator():
assert any("does not match calculator" in error for error in enriched(lambda card: card["output_token_calculator"]["examples"][0].update(output_tokens=197)))


def scaled_calculator(card, multiplier="4.2", tokens=823):
calc = card["output_token_calculator"]
calc["quality_multipliers"] = {"low": multiplier, "medium": "1", "high": "1"}
calc["result_rounding"] = "nearest_ties_up"
calc["examples"][0]["output_tokens"] = tokens


def test_calculator_applies_multiplier_after_base_ceiling():
assert enriched(scaled_calculator) == []


def test_calculator_multiplier_rounds_half_ties_up():
# Base low square output is 196; 196 * 0.125 is 24.5, not ties-to-even 24.
assert enriched(lambda card: scaled_calculator(card, "0.125", 25)) == []
assert any("does not match calculator" in error for error in enriched(lambda card: scaled_calculator(card, "0.125", 24)))


@pytest.mark.parametrize("multiplier", ["0", "-1", "nan", "1e3", 4.2])
def test_calculator_multiplier_requires_positive_decimal_string(multiplier):
assert enriched(lambda card: scaled_calculator(card, multiplier))


@pytest.mark.parametrize("missing", ["result_rounding", "quality_multipliers"])
def test_calculator_multiplier_and_rounding_are_a_pair(missing):
def mutate(card):
scaled_calculator(card)
card["output_token_calculator"].pop(missing)
assert enriched(mutate)


@pytest.mark.parametrize("change", ["missing", "extra"])
def test_calculator_multiplier_qualities_match_axes(change):
def mutate(card):
scaled_calculator(card)
multipliers = card["output_token_calculator"]["quality_multipliers"]
if change == "missing":
multipliers.pop("medium")
else:
multipliers["unknown"] = "1"
assert any("match quality_axes" in error for error in enriched(mutate))


def test_calculator_rejects_unscaled_example_for_scaled_formula():
assert any("does not match calculator" in error for error in enriched(lambda card: scaled_calculator(card, tokens=196)))


def test_rounded_price_example_never_requires_inferred_tokens():
def mutate(card):
example = card["output_token_calculator"]["examples"][0]
Expand Down
Loading