langgenius/dify · error · ValueError

doc_metadata must be a dictionary.

Error message

doc_metadata must be a dictionary.

What it means

Raised in the metadata PUT handler when doc_metadata is not a dict (datasets_document.py:1314). DocumentMetadataUpdatePayload types doc_metadata as Any, so the route accepts any JSON value and the isinstance(doc_metadata, dict) check fails post-parse. Same controller defect: ValueError becomes HTTP 500, not 400.

Source

Thrown at api/controllers/console/datasets/datasets_document.py:1315

        dataset_id_str = str(dataset_id)
        document_id_str = str(document_id)
        document = self.get_document(session, dataset_id_str, document_id_str, current_user, current_tenant_id)

        doc_type = req_data.doc_type
        doc_metadata = req_data.doc_metadata

        # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
        if not current_user.is_dataset_editor:
            raise Forbidden()

        if doc_type is None or doc_metadata is None:
            raise ValueError("Both doc_type and doc_metadata must be provided.")

        if doc_type not in DocumentService.DOCUMENT_METADATA_SCHEMA:
            raise ValueError("Invalid doc_type.")

        if not isinstance(doc_metadata, dict):
            raise ValueError("doc_metadata must be a dictionary.")
        metadata_schema: dict[str, Any] = cast(dict[str, Any], DocumentService.DOCUMENT_METADATA_SCHEMA[doc_type])

        document.doc_metadata = {}
        if doc_type == "others":
            document.doc_metadata = doc_metadata
        else:
            for key, value_type in metadata_schema.items():
                value = doc_metadata.get(key)
                if value is not None and isinstance(value, value_type):
                    document.doc_metadata[key] = value

        document.doc_type = doc_type
        document.updated_at = naive_utc_now()

        return SimpleResultMessageResponse(result="success", message="Document metadata updated.").model_dump(
            mode="json"
        ), 200

View on GitHub (pinned to ef8544b173)

Solutions

  1. Send doc_metadata as a JSON object (dict) whose keys/values match the doc_type schema.
  2. Server-side fix: type the payload field as dict[str, Any] so Pydantic rejects non-objects with a 422.

Example fix

# before
class DocumentMetadataUpdatePayload(BaseModel):
    doc_type: str | None = None
    doc_metadata: Any = None
# after
class DocumentMetadataUpdatePayload(BaseModel):
    doc_type: str
    doc_metadata: dict[str, Any]
Defensive patterns

Strategy: type-guard

Validate before calling

if not isinstance(doc_metadata, dict):
    raise TypeError("doc_metadata must be a JSON object (dict), got " + type(doc_metadata).__name__)

Type guard

def is_metadata_dict(doc_metadata) -> bool:
    return isinstance(doc_metadata, dict)

Try / catch

try:
    console.update_document_metadata(dataset_id, document_id, payload)
except HTTPError as e:
    if e.response.status_code >= 500 and "must be a dictionary" in e.response.json().get("message", ""):
        payload["doc_metadata"] = dict(payload["doc_metadata"])  # coerce and retry
        console.update_document_metadata(dataset_id, document_id, payload)
    else:
        raise

Prevention

When it happens

Trigger: PUT .../metadata with doc_metadata as a JSON string ("title=foo"), a list, a number, or null; the isinstance check at line 1314 rejects it.

Common situations: Client serializing the metadata object to a string before sending; sending an array of key-value pairs instead of an object; SDK generated with the wrong field type.

Related errors


AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12). Data as JSON: /api/errors/b573d9c3039b2b9b. Report an issue: GitHub.