openai/openai-python · error · ValueError

Expected `$ref: {ref}` to resolved to a dictionary but got {

Error message

Expected `$ref: {ref}` to resolved to a dictionary but got {resolved}

What it means

While inlining $ref entries for strict mode, the resolved target looked up in the root schema was not a dictionary. This indicates a structurally malformed or self-inconsistent JSON schema, not user input error per se.

Source

Thrown at src/openai/lib/_pydantic.py:106

    # strip `None` defaults as there's no meaningful distinction here
    # the schema will still be `nullable` and the model will default
    # to using `None` anyway
    if json_schema.get("default", NOT_GIVEN) is None:
        json_schema.pop("default")

    # we can't use `$ref`s if there are also other properties defined, e.g.
    # `{"$ref": "...", "description": "my description"}`
    #
    # so we unravel the ref
    # `{"type": "string", "description": "my description"}`
    ref = json_schema.get("$ref")
    if ref and has_more_than_n_keys(json_schema, 1):
        assert isinstance(ref, str), f"Received non-string $ref - {ref}"

        resolved = resolve_ref(root=root, ref=ref)
        if not is_dict(resolved):
            raise ValueError(f"Expected `$ref: {ref}` to resolved to a dictionary but got {resolved}")

        # properties from the json schema take priority over the ones on the `$ref`
        json_schema.update({**resolved, **json_schema})
        json_schema.pop("$ref")
        # Since the schema expanded from `$ref` might not have `additionalProperties: false` applied,
        # we call `_ensure_strict_json_schema` again to fix the inlined schema and ensure it's valid.
        return _ensure_strict_json_schema(json_schema, path=path, root=root)

    return json_schema


def resolve_ref(*, root: dict[str, object], ref: str) -> object:
    if not ref.startswith("#/"):
        raise ValueError(f"Unexpected $ref format {ref!r}; Does not start with #/")

    path = ref[2:].split("/")
    resolved = root
    for key in path:

View on GitHub (pinned to 9917c6e28e)

Solutions

  1. Upgrade pydantic to the latest v2 patch
  2. Simplify nested generic models that trigger unusual $ref layouts
  3. Print model_json_schema() and verify every $ref resolves to an object node under #/definitions or #/$defs

Example fix

# before
class Inner(BaseModel): ...
class Outer(BaseModel):
    inner: Inner | list[Inner] | int  # complex union producing odd refs
# after
class Outer(BaseModel):
    inners: list[Inner]
Defensive patterns

Strategy: validation

Validate before calling

schema = Output.model_json_schema()
def refs_resolve(node, root):
    if isinstance(node, dict):
        if "$ref" in node and not isinstance(node.get("$ref"), str):
            return False
        return all(refs_resolve(v, root) for v in node.values())
    return not isinstance(node, (list,)) or all(refs_resolve(v, root) for v in node)
assert refs_resolve(schema, schema)

Prevention

When it happens

Trigger: A model whose schema has a $ref pointing at a non-object location (e.g. refs into arrays or scalar positions), or schemas mutated/corrupted between generation and strictification.

Common situations: Custom CoreSchema manipulation; pydantic version quirks generating unusual refs; nested generics with older pydantic 2.x versions.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of openai/openai-python@9917c6e28e (2026-08-28). Data as JSON: /api/errors/a0e15352b22c5136. Report an issue: GitHub.