JuliusBrussee/caveman · error · CatalogError

: pricing must be an object

Error message

{label}: pricing must be an object

What it means

A row's `pricing` value is not a mapping (dict). Pricing must be an object whose keys are the known per-million rate fields; a scalar, list, or null cannot carry the rate data the cost engine reads. Thrown from validate_row.

Solutions

  1. Make pricing a mapping with at least input_per_million and output_per_million keys
  2. Restore indentation so rate keys sit under `pricing:` (two spaces deeper)
  3. For rates that don't apply, use explicit `null` values inside the object instead of omitting or flattening
  4. Copy the pricing block shape from a neighboring valid row

Example fix

# before
pricing: 2.5

# after
pricing:
  input_per_million: 2.5
  output_per_million: 10.0
  cache_read_input_per_million: null
Defensive patterns

Strategy: type-guard

Validate before calling

def pricing_is_object(row: dict) -> bool:
    return isinstance(row.get("pricing"), dict)

Type guard

def is_pricing_object(value: object) -> bool:
    return isinstance(value, dict) and "input_per_million" in value

Try / catch

try:
    validate_catalog()
except CatalogError as e:
    if "pricing must be an object" in str(e):
        restructure_pricing_block(str(e))

Prevention

When it happens

Trigger: A row sets `pricing: 2.5` (a single rate instead of an object), `pricing: [2.5, 10]`, `pricing:` with nothing after it (YAML null), or indentation errors that collapse the pricing block into a string.

Common situations: Wrong indentation so the pricing sub-keys detach from `pricing:`; writing a flat number because only one rate was known; merge conflicts corrupting the block; a script emitting JSON arrays instead of objects.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/49179d279d111d2f. Report an issue: GitHub.

Appendix: source

Thrown at shared/provider-catalog/validate_catalog.py:135

    unknown = set(row) - TOP_LEVEL_KEYS
    missing = REQUIRED_KEYS - set(row)
    if unknown:
        raise CatalogError(f"{label}: unknown field(s): {', '.join(sorted(unknown))}")
    if missing:
        raise CatalogError(f"{label}: missing field(s): {', '.join(sorted(missing))}")

    identity: list[str] = []
    for field in ("provider", "model", "region"):
        value = row[field]
        if not isinstance(value, str) or not IDENTIFIER.fullmatch(value):
            raise CatalogError(f"{label}: {field} must be a safe non-empty identifier")
        identity.append(value)
    if row["currency"] != "USD":
        raise CatalogError(f"{label}: currency must be USD")

    pricing = row["pricing"]
    if not isinstance(pricing, dict):
        raise CatalogError(f"{label}: pricing must be an object")
    pricing_unknown = set(pricing) - PRICING_KEYS
    if pricing_unknown:
        raise CatalogError(f"{label}: unknown pricing field(s): {', '.join(sorted(pricing_unknown))}")
    for required in ("input_per_million", "output_per_million"):
        if required not in pricing:
            raise CatalogError(f"{label}: pricing.{required} is required")
    for field, value in pricing.items():
        if value is None or field == "long_context_threshold_inclusive":
            if field == "long_context_threshold_inclusive" and value is not None and not isinstance(value, bool):
                raise CatalogError(f"{label}: pricing.{field} must be boolean or null")
            continue
        if isinstance(value, bool) or not isinstance(value, (int, float)):
            raise CatalogError(f"{label}: pricing.{field} must be numeric or null")
        if not math.isfinite(value) or value < 0:
            raise CatalogError(f"{label}: pricing.{field} must be finite and nonnegative")
        if field == "batch_discount_fraction" and value > 1:
            raise CatalogError(f"{label}: pricing.batch_discount_fraction must be <= 1")

View on GitHub (pinned to 3ee70a1026)