{"record":{"id":"c519b7eb45cb419c","repo":"langflow-ai/langflow","slug":"per-file-metadata-exceeds-the-kb-metadata-max-key","errorCode":null,"errorMessage":"Per-file metadata exceeds the {KB_METADATA_MAX_KEYS} file limit.","messagePattern":"Per-file metadata exceeds the (.+?) file limit\\.","errorType":"validation","errorClass":"HTTPException","httpStatus":422,"severity":"warning","filePath":"src/backend/base/langflow/api/utils/kb_metadata.py","lineNumber":124,"sourceCode":"    \"\"\"Decode + validate the ``per_file_metadata`` form field.\n\n    Shape: ``{filename: {metadata_dict}, ...}``. Each inner dict goes through\n    the same validator as run-level metadata, so per-file overrides obey the\n    same key/value rules. Empty/None → ``{}``.\n    \"\"\"\n    if not raw:\n        return {}\n    try:\n        decoded = json.loads(raw)\n    except json.JSONDecodeError as exc:\n        msg = f\"Per-file metadata is not valid JSON: {exc.msg}\"\n        raise HTTPException(status_code=422, detail=msg) from exc\n    if not isinstance(decoded, dict):\n        msg = \"Per-file metadata must be a JSON object keyed by filename.\"\n        raise HTTPException(status_code=422, detail=msg)\n    if len(decoded) > KB_METADATA_MAX_KEYS:\n        msg = f\"Per-file metadata exceeds the {KB_METADATA_MAX_KEYS} file limit.\"\n        raise HTTPException(status_code=422, detail=msg)\n    out: dict[str, dict[str, Any]] = {}\n    for filename, file_metadata in decoded.items():\n        if not isinstance(filename, str) or not filename:\n            msg = \"Per-file metadata keys must be non-empty filename strings.\"\n            raise HTTPException(status_code=422, detail=msg)\n        if not isinstance(file_metadata, dict):\n            msg = f\"Per-file metadata for {filename!r} must be a JSON object.\"\n            raise HTTPException(status_code=422, detail=msg)\n        out[filename] = validate_user_metadata(file_metadata)\n    return out\n","sourceCodeStart":106,"sourceCodeEnd":135,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/utils/kb_metadata.py#L106-L135","documentation":"parse_per_file_metadata caps the number of per-file override entries at KB_METADATA_MAX_KEYS = 16 files. A valid-JSON object keyed by filename with more than 16 entries is rejected with 422 before any inner validation runs — mirroring the run-level 16-key cap since each entry expands into per-chunk metadata.","triggerScenarios":"per_file_metadata JSON object with 17+ filename keys. Fires before the per-entry checks, so even if every inner dict is perfect, an oversize map is rejected whole. Note the run-level metadata object has the same numeric cap (error 152) but a different message text.","commonSituations":"Batch-uploading a directory of 50 files with one ingest request and per-file overrides for each; auto-generating overrides for every file in a folder; pipelines that grew incrementally past the cap without noticing.","solutions":["Limit overrides to the files that actually need them (<=16) and put the rest in run-level metadata.","Split the upload into multiple ingest requests, each with <=16 per-file overrides.","Compute the cap from the same constant the server uses (langflow.utils.kb_constants.KB_METADATA_MAX_KEYS) rather than hardcoding, so a client/server mismatch cannot surprise you.","If every file needs distinct metadata, per-request batching by metadata group is usually the cleanest model."],"exampleFix":"# before\nper_file = {f.name: meta_for(f) for f in all_files}  # 50 files -> 422\n\n# after\nBATCH = 16\nfor i in range(0, len(all_files), BATCH):\n    chunk = all_files[i:i+BATCH]\n    submit(files=chunk, per_file_metadata={f.name: meta_for(f) for f in chunk})","handlingStrategy":"validation","validationCode":"from langflow.utils.kb_constants import KB_METADATA_MAX_KEYS as MAXK\n\ndef batch_per_file(per_file: dict, files: list):\n    for i in range(0, len(files), MAXK):\n        batch = files[i:i+MAXK]\n        yield batch, {f: per_file[f] for f in batch if f in per_file}","typeGuard":"def within_file_limit(per_file) -> bool:\n    return isinstance(per_file, dict) and len(per_file) <= 16","tryCatchPattern":"try:\n    parse_per_file_metadata(raw)\nexcept HTTPException as e:\n    if e.status_code == 422 and 'file limit' in e.detail:\n        trimmed = dict(list(per_file.items())[:16])\n        raw = json.dumps(trimmed)\n        parse_per_file_metadata(raw)\n    else:\n        raise","preventionTips":["Batch uploads so each request carries <=16 per-file overrides.","Import KB_METADATA_MAX_KEYS rather than hardcoding 16.","Put common metadata at run level; keep per-file entries for true exceptions."],"tags":["knowledge-base","metadata","validation","limits","http-422"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}