fix(api): honor array-element limit and byte budget for list[File] in VariableTruncator (#39218)#39220
Conversation
… VariableTruncator (langgenius#39218) `VariableTruncator._truncate_array` had a "Dirty fix" branch added in langgenius#26133 that unconditionally appended every `File` element to `truncated_value` *before* the count cap and the byte-budget check, and *before* `used_size` was ever incremented. As a result a `list[File]` was: 1. never capped at `array_element_limit` (default 20 items), 2. never counted against `max_size_bytes` (default 1000 KB), and 3. always reported `truncated=False`. This directly contradicts the class's own stated guarantee ("ensuring the final size doesn't exceed specified limits", `VariableTruncator` docstring) and silently inflated the per-key byte budget reported back to `truncate_variable_mapping`, which uses `used_size` to compute the remaining budget for sibling keys in the same `inputs`/`outputs`/`process_data` mapping. `_truncate_json_primitives` already has a correct, dedicated `File` branch that returns the file untouched but reports its real computed size. The fix removes the early `isinstance(item, File): ... continue` bypass and adds `File` to the tuple of element types handled by the normal count-and-size accounting loop. `File` items still aren't truncated (the dedicated `File` branch keeps that), but they now flow through the same count cap and byte budget as every other element type — which is what langgenius#26133 originally needed. Regression tests added in `tests/unit_tests/services/test_variable_truncator.py::TestFileArrayTruncationRegression39218` cover: - count cap honored (500 files -> 3 with cap=3), - `used_size` reflects real serialized size (not 2), - byte budget honored (50 files with small budget -> 1 file returned), - mixed arrays count Files toward the cap, - a single File in an array stays intact. Verified each new test fails against the un-fixed source (via `git stash` of the implementation file) and passes with the fix. Full file: 52 passed. Refs langgenius#39218 Co-authored-by: Cursor <cursoragent@cursor.com>
ErenAta16
left a comment
There was a problem hiding this comment.
Verified against current main: _truncate_json_primitives already has a dedicated File branch (case File(): returns the file as-is with its real calculate_json_size), it just never got reached for array elements because the old Dirty fix branch short-circuited before the type dispatch. Routing File through the same isinstance check as the other primitives is the right fix, not a new code path, just removing the thing that was bypassing the existing one.
Test coverage is thorough, count cap, byte budget, real used_size reporting, mixed File/primitive arrays, and the single-file no-truncation case are all exercised separately rather than folded into one big assertion. Confirms the fix without over-fitting to one scenario.
Summary
VariableTruncator._truncate_arrayhad a "Dirty fix" branch added in#26133 that
unconditionally appended every
Fileelement totruncated_valuebefore the count cap and the byte-budget check, and before
used_sizewas ever incremented. As a result alist[File]was:array_element_limit(default 20 items),max_size_bytes(default 1000 KB), andtruncated=False.This directly contradicts the class's own stated guarantee
("ensuring the final size doesn't exceed specified limits",
VariableTruncatordocstring) and silently inflated the per-key bytebudget reported back to
truncate_variable_mapping, which usesused_sizeto compute the remaining budget for sibling keys in thesame
inputs/outputs/process_datamapping._truncate_json_primitivesalready has a correct, dedicatedFilebranch that returns the file untouched but reports its real computed
size (
case File(): return _PartResult(val, self.calculate_json_size(val), False)).The fix removes the early
isinstance(item, File): ... continuebypass and adds
Fileto the tuple of element types handled by thenormal count-and-size accounting loop.
Fileitems still aren'ttruncated (the dedicated
Filebranch keeps that), but they nowflow through the same count cap and byte budget as every other
element type — which is what #26133 originally needed.
Reproduction (per the issue)
Regression tests
Added
TestFileArrayTruncationRegression39218intests/unit_tests/services/test_variable_truncator.pywith 5 cases:used_sizereflects real serialized size (not 2),Each new test was verified to fail against the un-fixed source (via
git stashof the implementation file) and pass with the fix. Fullfile: 52 passed.
Verification
uv run pytest tests/unit_tests/services/test_variable_truncator.py→ 52 passed.ruff checkon changed files → clean.ruff format --checkon changed files → clean.Fixes #39218