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

invalid type for {location}: expected {expected}

Error message

invalid type for {location}: expected {expected}

What it means

_validate_schema_value raises ValueError(f'invalid type for {location}: expected {expected}') when the VALUE being validated does not match any of the schema's declared type(s). Unlike the schema-shape errors, this flags bad data: the value at {location} is, say, a string where the schema declares integer, and the message joins all allowed types with ' or '. Booleans are deliberately not accepted as integers or numbers here.

Source

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

        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"]
        if not isinstance(choices, list) or not choices:
            raise ValueError(f"schema enum for {location} must be a non-empty list")
        if value not in choices:
            raise ValueError(f"invalid value for {location}: not in enum")

    if isinstance(value, (int, float)) and not isinstance(value, bool):
        for keyword, comparison, message in (
            ("minimum", lambda current, bound: current >= bound, "below minimum"),
            ("maximum", lambda current, bound: current <= bound, "above maximum"),
            ("exclusiveMinimum", lambda current, bound: current > bound, "at or below exclusive minimum"),
            ("exclusiveMaximum", lambda current, bound: current < bound, "at or above exclusive maximum"),
        ):
            if keyword not in schema:
                continue
            bound = schema[keyword]

View on GitHub (pinned to 39ea8a1c6d)

Solutions

  1. Coerce the value before validating (int('3') -> 3) or fix the producer: prompt the model to emit raw JSON numbers, not quoted ones.
  2. If multiple types are acceptable, declare a type list: {"type": ["integer", "string"]}.
  3. Inspect {location} and the expected types in the message to find exactly which field failed.
  4. In repair loops, feed this precise message back to the model as corrective feedback (the BoundedExtractor pattern).

Example fix

# before
validate_tool_input({"count": "3"}, {"type": "object", "properties": {"count": {"type": "integer"}}})
# ValueError: invalid type for $.count: expected integer

# after
validate_tool_input({"count": 3}, ...)
Defensive patterns

Strategy: retry

Validate before calling

def coerce_to_declared(value, schema):
    t = schema.get("type")
    if t == "integer" and isinstance(value, str) and value.isdigit():
        return int(value)
    if t == "number" and isinstance(value, str):
        try:
            return float(value)
        except ValueError:
            return value
    return value

Type guard

def matches_declared(value, expected: str) -> bool:
    checks = {
        "boolean": lambda v: isinstance(v, bool),
        "integer": lambda v: isinstance(v, int) and not isinstance(v, bool),
        "number": lambda v: isinstance(v, (int, float)) and not isinstance(v, bool),
        "string": lambda v: isinstance(v, str),
        "array": lambda v: isinstance(v, list),
        "object": lambda v: isinstance(v, dict),
    }
    return expected not in checks or checks[expected](value)

Try / catch

for attempt in range(max_attempts):
    try:
        return validate_tool_input(model_output, schema)
    except ValueError as exc:
        if not str(exc).startswith("invalid type for"):
            raise
        model_output = generate(f"Previous output failed: {exc}. Return correct JSON types.")
raise ContractViolation(last_error)

Prevention

When it happens

Trigger: Validating tool input like {"count": "3"} against {"type": "integer"}; passing true where a number is declared; a wrong-type value at any nested property or array item, reached recursively from validate().

Common situations: Model-produced tool calls with stringified numbers; LLM emitting true/false for 1/0; CLI/env inputs arriving as strings; frontends sending string numbers from form fields.

Related errors


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