{"record":{"id":"943b6d8e6e8bbec4","repo":"BerriAI/litellm","slug":"unpack-defs-inlined-schema-exceeded-the-max-inli","errorCode":null,"errorMessage":"unpack_defs: inlined schema exceeded the {max_inlined_bytes:,}-byte budget. Refusing to deep-copy further to prevent schema-bomb resource exhaustion.","messagePattern":"unpack_defs: inlined schema exceeded the (.+?)-byte budget\\. Refusing to deep-copy further to prevent schema-bomb resource exhaustion\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/litellm_core_utils/prompt_templates/common_utils.py","lineNumber":943,"sourceCode":"        if isinstance(node, dict):\n            # --- Case 1: this node *is* a reference ---\n            if \"$ref\" in node:\n                ref_name = node[\"$ref\"].split(\"/\")[-1]\n\n                # Check for circular reference in the resolution chain\n                if ref_name in ref_chain:\n                    # Circular reference detected - leave as-is to prevent infinite recursion\n                    continue\n\n                target_schema = active_defs.get(ref_name)\n                # Unknown reference – leave untouched\n                if target_schema is None:\n                    continue\n\n                if max_inlined_bytes is not None:\n                    inlined_bytes += _estimate_json_bytes(target_schema)\n                    if inlined_bytes > max_inlined_bytes:\n                        raise ValueError(\n                            f\"unpack_defs: inlined schema exceeded the \"\n                            f\"{max_inlined_bytes:,}-byte budget. Refusing to \"\n                            f\"deep-copy further to prevent schema-bomb \"\n                            f\"resource exhaustion.\"\n                        )\n\n                # Merge defs from the target to capture nested definitions\n                child_defs = {\n                    **active_defs,\n                    **target_schema.get(\"$defs\", {}),\n                    **target_schema.get(\"definitions\", {}),\n                }\n\n                # Replace the reference with resolved copy\n                resolved = copy.deepcopy(target_schema)\n                if parent is not None and key is not None:\n                    if (\n                        isinstance(parent, dict)","sourceCodeStart":925,"sourceCodeEnd":961,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/litellm_core_utils/prompt_templates/common_utils.py#L925-L961","documentation":"unpack_defs inlines $ref targets from a JSON schema's $defs/definitions into the main schema (needed by providers that don't support $ref). To prevent schema-bomb resource exhaustion, it accumulates the estimated byte size of every inlined target and aborts with ValueError once a configurable budget (max_inlined_bytes) is exceeded, rather than deep-copying unbounded recursive structures.","triggerScenarios":"Passing tools/response_format with huge or self-referentially expanding $defs (e.g. a recursive schema whose $ref targets pull in the entire defs tree each time); many mutually-referencing definitions that blow past the byte budget when inlined; providers that require full inlining (no $ref support) combined with large OpenAPI-generated schemas.","commonSituations":"Auto-generated pydantic/jsonschema models with deeply nested $defs; passing an entire database schema or OpenAPI spec as a tool definition; a low max_inlined_bytes configured by the caller or by a security-conscious default.","solutions":["Simplify the schema: split the tool into smaller ones or hand-write a flattened schema without $defs.","Remove recursive/self-referential $ref patterns from the schema (they multiply inlined bytes).","Raise max_inlined_bytes if you legitimately need a large schema and accept the memory cost.","Use providers that natively support $ref so inlining is unnecessary."],"exampleFix":"// before\ntools=[{'type':'function','function':{'name':'db','parameters': giant_openapi_schema_with_defs}}]\n\n# after\ntools=[{'type':'function','function':{'name':'get_user','parameters': {'type':'object','properties':{'id':{'type':'string'}},'required':['id']}}}]","handlingStrategy":"validation","validationCode":"import json\n\ndef schema_within_budget(schema: dict, max_bytes: int = 1_000_000) -> bool:\n    def estimate(o):\n        if isinstance(o, dict): return sum(estimate(k) + estimate(v) for k, v in o.items())\n        if isinstance(o, list): return sum(estimate(i) for i in o)\n        return len(json.dumps(o))\n    # rough: inlined defs cost ~2x original schema when refs repeat\n    return estimate(schema) * 2 <= max_bytes","typeGuard":null,"tryCatchPattern":"try:\n    resp = litellm.completion(model=m, messages=msgs, tools=tools)\nexcept ValueError as e:\n    if 'unpack_defs' in str(e):\n        tools = flatten_schema_manually(tools)  # pre-inline only needed defs, drop the rest\n        resp = litellm.completion(model=m, messages=msgs, tools=tools)\n    else:\n        raise","preventionTips":["Avoid recursive $ref patterns in tool schemas passed to $ref-less providers.","Keep tool schemas minimal; don't feed entire OpenAPI specs as tool definitions.","Measure your schema size (json.dumps length) in CI when tools are generated from pydantic models."],"tags":["json-schema","tools","resource-limits","schema-bomb"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}