rohitg00/ai-engineering-from-scratch · error · ValueError

schema {name} must be a non-negative integer

Error message

schema {name} must be a non-negative integer

What it means

_integer_bound raises ValueError(f'schema {name} must be a non-negative integer') when a numeric constraint keyword (minimum or maximum) is present but its value is not an int, is a bool, or is negative. The function reads bounds for numeric comparisons; a malformed bound cannot be compared safely, so it fails fast. This is about the schema author's constraint value, not the data being validated.

Source

Thrown at certifications/claude/lessons/10-tool-use-and-agentic-loops/code/main.py:104

        "null": lambda item: item is None,
        "boolean": lambda item: isinstance(item, bool),
        "integer": lambda item: isinstance(item, int) and not isinstance(item, bool),
        "number": lambda item: isinstance(item, (int, float)) and not isinstance(item, bool),
        "string": lambda item: isinstance(item, str),
        "array": lambda item: isinstance(item, list),
        "object": lambda item: isinstance(item, dict),
    }
    if expected not in checks:
        raise ValueError(f"unsupported schema type: {expected}")
    return checks[expected](value)


def _integer_bound(schema: dict[str, Any], name: str) -> int | None:
    if name not in schema:
        return None
    value = schema[name]
    if not isinstance(value, int) or isinstance(value, bool) or value < 0:
        raise ValueError(f"schema {name} must be a non-negative integer")
    return value


def _validate_schema_value(value: Any, schema: Any, location: str) -> None:
    if not isinstance(schema, dict):
        raise ValueError(f"schema for {location} must be an object")

    declared_type = schema.get("type")
    if declared_type is not None:
        declared_types = declared_type if isinstance(declared_type, list) else [declared_type]
        if not declared_types or not all(isinstance(item, str) for item in declared_types):
            raise ValueError(f"schema type for {location} must be a string or non-empty string list")
        if not any(_matches_json_type(value, item) for item in declared_types):
            expected = " or ".join(declared_types)
            raise ValueError(f"invalid type for {location}: expected {expected}")

    if "enum" in schema:
        choices = schema["enum"]

View on GitHub (pinned to 39ea8a1c6d)

Solutions

  1. Make the bound a plain non-negative int literal: {"minimum": 1, "maximum": 5}.
  2. Coerce config-sourced bounds with int() (and range-check) before building the schema.
  3. Add a schema-lint step that verifies minimum/maximum are non-negative ints at startup.
  4. If fractional bounds are genuinely needed, note this validator subset only supports integer bounds and adjust the design.

Example fix

# before
{"type": "integer", "minimum": "1"}
# ValueError: schema minimum must be a non-negative integer

# after
{"type": "integer", "minimum": 1}
Defensive patterns

Strategy: validation

Validate before calling

def bounds_ok(schema: dict) -> bool:
    return all(
        isinstance(schema.get(k, 0), int) and not isinstance(schema.get(k, 0), bool) and schema.get(k, 0) >= 0
        for k in ("minimum", "maximum")
    )

Type guard

def is_non_negative_int(value) -> bool:
    return isinstance(value, int) and not isinstance(value, bool) and value >= 0

Try / catch

try:
    validate_tool_input(value, schema)
except ValueError as exc:
    if "must be a non-negative integer" in str(exc):
        schema = normalize_bounds(schema)  # coerce "1"->1, 1.0->1 at config load
        validate_tool_input(value, schema)
    else:
        raise

Prevention

When it happens

Trigger: A schema like {"type": "integer", "minimum": "1"} (string bound), {"minimum": true} (bool), {"minimum": -1} (negative), or {"maximum": 2.5} (float). Reached whenever _validate_schema_value processes a numeric value and iterates the ('minimum', 'maximum') keyword table.

Common situations: Env-var or YAML config flowing into schema bounds as strings; JSON configs where 1.0 parses as float; placeholder values like "TODO" left in schemas; booleans leaking in from checkbox-driven schema builders.

Related errors


AI-assisted analysis of rohitg00/ai-engineering-from-scratch@39ea8a1c6d (2026-08-26). Data as JSON: /api/errors/3cc4285a5bf4860e. Report an issue: GitHub.