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

schema for {location} must be an object

Error message

schema for {location} must be an object

What it means

_validate_schema_value raises ValueError(f'schema for {location} must be an object') when a subschema at the given location is not a dict - e.g. a property schema that is a string, list, or None. JSON Schema subschemas must be objects, and this recursive validator asserts that shape before reading 'type', 'enum', or bounds. The location string pinpoints exactly which property or items subschema is malformed.

Source

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

        "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"]
        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):

View on GitHub (pinned to 39ea8a1c6d)

Solutions

  1. Wrap every subschema in an object: {"q": {"type": "string"}} not {"q": "string"}.
  2. Read the {location} in the message to find the exact offending property or items node.
  3. When assembling schemas in code, assert isinstance(subschema, dict) at build time.
  4. Validate the schema itself once at startup before using it in tool definitions or model calls.

Example fix

# before
{"type": "object", "properties": {"q": "string"}}
# ValueError: schema for $.q must be an object

# after
{"type": "object", "properties": {"q": {"type": "string"}}}
Defensive patterns

Strategy: type-guard

Validate before calling

def subschemas_are_objects(schema, location="$") -> bool:
    if not isinstance(schema, dict):
        return False
    for name, sub in schema.get("properties", {}).items():
        if not subschemas_are_objects(sub, f"{location}.{name}"):
            return False
    if "items" in schema and not subschemas_are_objects(schema["items"], location + "[0]"):
        return False
    return True

Type guard

from typing import Any
def is_object_schema(schema: Any) -> bool:
    """A schema node must itself be a dict."""
    return isinstance(schema, dict)

Try / catch

try:
    validate(value, schema)
except ValueError as exc:
    if "schema for" in str(exc) and "must be an object" in str(exc):
        raise SchemaError(f"malformed schema: {exc}") from exc  # author bug, not data bug
    raise

Prevention

When it happens

Trigger: A tool input_schema like {"properties": {"q": "string"}} (bare type string instead of {"type": ...}); {"items": ["string"]} (list of schemas where one schema object is expected); a property set to None. Hit from validate() at the root if the top schema is not a dict, or recursively for any child.

Common situations: Writing shorthand schemas from memory ('name': 'string'); mixing JSON Schema and OpenAPI styles; programmatic schema assembly where a branch returns None.

Related errors


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