langgenius/dify · error · InvalidArgumentError

expected dict for file, got {type(raw_value)}

Error message

expected dict for file, got {type(raw_value)}

What it means

Returned (HTTP 400, error_code `invalid_param`) by PATCH on a draft variable whose `value_type` is `SegmentType.FILE`. The handler expects the request body `value` field to be a JSON object (a file mapping with type/transfer_method/url/upload_file_id) so it can be passed to `build_from_mapping`. If `value` is a string, number, list, or null-shaped non-dict, the isinstance check fails and InvalidArgumentError is raised before any DB write.

Source

Thrown at api/controllers/console/datasets/rag_pipeline/rag_pipeline_draft_variable.py:250

        variable_id_str = str(variable_id)
        variable = draft_var_srv.get_variable(variable_id=variable_id_str)
        if variable is None:
            raise NotFoundError(description=f"variable not found, id={variable_id_str}")
        if variable.app_id != pipeline.id:
            raise NotFoundError(description=f"variable not found, id={variable_id_str}")

        new_name = args.get(self._PATCH_NAME_FIELD, None)
        raw_value = args.get(self._PATCH_VALUE_FIELD, None)
        if new_name is None and raw_value is None:
            return variable

        new_value = None
        if raw_value is not None:
            match variable.value_type:
                case SegmentType.FILE:
                    if not isinstance(raw_value, dict):
                        raise InvalidArgumentError(description=f"expected dict for file, got {type(raw_value)}")
                    raw_value = build_from_mapping(
                        mapping=raw_value,
                        tenant_id=pipeline.tenant_id,
                        access_controller=_file_access_controller,
                    )
                case SegmentType.ARRAY_FILE:
                    if not isinstance(raw_value, list):
                        raise InvalidArgumentError(description=f"expected list for files, got {type(raw_value)}")
                    if len(raw_value) > 0 and not isinstance(raw_value[0], dict):
                        raise InvalidArgumentError(description=f"expected dict for files[0], got {type(raw_value)}")
                    raw_value = build_from_mappings(
                        mappings=raw_value,
                        tenant_id=pipeline.tenant_id,
                        access_controller=_file_access_controller,
                    )
                case _:
                    pass
            new_value = build_segment_with_type(variable.value_type, raw_value)

View on GitHub (pinned to ef8544b173)

Solutions

  1. Send `value` as an object matching the file mapping shape: `{"type":"image","transfer_method":"local_file","upload_file_id":"<id>"}`.
  2. For remote files use `{"type":"image","transfer_method":"remote_url","url":"<signed url>"}`.
  3. Inspect `variable.value_type` from the GET response and only attach a file mapping when it equals `file`.
  4. Add a client-side schema validator (Pydantic/zod) for the file mapping before PATCH.

Example fix

// before
{ "value": "daded54f-72c7-4f8e-9d18-9b0abdd9f190" }
// after
{
  "value": {
    "type": "image",
    "transfer_method": "local_file",
    "upload_file_id": "daded54f-72c7-4f8e-9d18-9b0abdd9f190"
  }
}
Defensive patterns

Strategy: type-guard

Validate before calling

function asFileMapping(v: unknown): { type: string; transfer_method: string; upload_file_id?: string; url?: string } {
  if (!v || typeof v !== 'object' || Array.isArray(v)) {
    throw new Error('file value must be a mapping object')
  }
  const o = v as Record<string, unknown>
  if (typeof o.type !== 'string' || typeof o.transfer_method !== 'string') {
    throw new Error('file mapping requires string `type` and `transfer_method`')
  }
  return o as any
}
// before PATCH:
if (variable.value_type === 'file') rawValue = asFileMapping(rawValue)

Type guard

function isFileMapping(v: unknown): v is { type: string; transfer_method: string; upload_file_id?: string; url?: string } {
  return (
    !!v &&
    typeof v === 'object' &&
    !Array.isArray(v) &&
    typeof (v as any).type === 'string' &&
    typeof (v as any).transfer_method === 'string'
  )
}

Try / catch

try {
  await patch(...)
} catch (e) {
  if (e.code === 'invalid_param' && /expected dict for file/.test(e.message)) {
    // rewrap value as a file mapping and retry once
  } else throw e
}

Prevention

When it happens

Trigger: PATCHing a FILE-typed variable with `"value": "<upload_file_id>"` (string) instead of an object; sending `"value": [...]`; sending a bare upload_file_id string; client serializing the file object to a string before sending.

Common situations: Frontend form binding the file picker to a string id instead of the mapping object. Misreading the API doc and assuming value is the upload_file_id. Migration scripts that previously stored file ids as strings.

Related errors


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