fix: align inspector panel API exposure with what the backend accepts - #14304
fix: align inspector panel API exposure with what the backend accepts#14304tarciorodrigues wants to merge 7 commits into
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughChangesAPI exposure rule alignment
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested labels: 🚥 Pre-merge checks | ✅ 9✅ Passed checks (9 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Test Coverage AdvisorNo source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉
|
|
Build successful! ✅ |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## release-1.11.2 #14304 +/- ##
=================================================
Coverage ? 61.60%
=================================================
Files ? 2444
Lines ? 239846
Branches ? 37307
=================================================
Hits ? 147768
Misses ? 90308
Partials ? 1770
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…al utils apply_tweaks silently drops tweaks aimed at code fields and protected sinks, logging a warning the API caller never sees. The parameters panel has no knowledge of those rules, so it offers an API toggle on fields the backend will refuse and the generated snippet advertises them. Add a frontend mirror of CODE_EXECUTION_COMPONENT_TYPES, CODE_EXECUTION_FIELD_NAMES and PROTECTED_TWEAK_FIELDS_BY_COMPONENT plus an isBackendRefusedField helper. No consumer yet. The mirror is UX only: the backend remains the enforcement point.
A mirrored list that drifts silently brings the bug back: the UI would again offer an API toggle on a field apply_tweaks refuses, or hide one that is legitimately tweakable. Parse the TypeScript mirror and compare it to the Python frozensets, skipping when the frontend package is absent (standalone lfx checkout). Verified to fail on an injected drift.
…able The tweaks derivation filtered template keys with an inline expression built on LANGFLOW_SUPPORTED_TYPES, a set that means 'renders an inline widget on the node' and had been borrowed as if it meant 'is tweakable'. Extract the gate into isFieldTweakable so the rule has a name and a single home, ready to be shared with the panel row and the exposure predicate. Pure refactor: same fields in, same fields out.
A Language Model parameter with the API toggle on never reached the tweaks object: the derivation gated on LANGFLOW_SUPPORTED_TYPES, which does not list the model type, so the field was dropped before the exposure predicate ran. The toggle turned green, api_editable was persisted, and the snippet stayed silent. The backend has no such restriction — apply_tweaks refuses only code fields and protected sinks — and ModelInput normalizes the assigned value through its validator (validate_assignment is on), so a tweaked model name is accepted. Treat the model type as tweakable.
The panel listed rows with no type gate at all, so a handle-only input such as Tools offered a live API toggle while the tweaks derivation dropped it — the user could turn the toggle green and nothing would ever reach the snippet. Make the row and the exposure predicate consume the same isFieldTweakable gate the derivation uses: one rule, three call sites. A non-tweakable field now shows the API control blocked, with the cursor block and the tooltip, and that case is checked before the off-node one so the tooltip does not promise a way out that does not exist. The row keeps its Add/Remove action. Test fixtures gained the template type they always have in a real flow.
apply_tweaks refuses a tweak aimed at a code field, at a code-execution input on a code-execution component, or at a protected sink, and only logs a warning the caller never sees. The panel offered those fields anyway and the snippet advertised them, so SQLComponent database_url, PythonREPLComponent python_code and friends read as configurable inputs that silently do nothing. Feed the mirrored refusal rules into isFieldTweakable, so the same gate that drives the derivation and the panel row now covers them: the API control is blocked and the snippet never lists them. The backend check is untouched and remains the enforcement point.
Pin the rules that are easy to get wrong on the next change: a code-execution input is refused only on a component that executes code, a protected sink only on the component that owns it (query is refused on SQLComponent and tweakable on NewsSearch), the model type is tweakable and a handle-only input never is.
|
Force-pushed to rebuild this branch directly on |
a240081 to
4835bda
Compare
Why
The parameters panel offers an "API" toggle on rows whose exposure never materializes, and in the worst case advertises in the generated snippet fields that the backend silently refuses at run time. Three layers hold three different opinions about "can this field be an API input" and none of them is authoritative: the panel lists rows with no type gate at all (
isManageableParameter), the tweaks derivation filters onLANGFLOW_SUPPORTED_TYPESbefore the exposure predicate ever runs (getNodesWithDefaultValue), andapply_tweaksrefuses code fields and protected sinks at run time with alogger.warningthe API caller never sees.The root cause is a borrowed list.
LANGFLOW_SUPPORTED_TYPESanswers "does this render an inline widget on the node" — that is how its other two consumers use it (RenderInputParameters/utils.ts,reactflowUtils.ts) — and it dates from #1847 (2024). The tweaks derivation reused it as a proxy for "is tweakable". WhenModelInputwas created the render path was special-cased (if (isModelInput) return true) but the set was never updated, so the derivation silently inherited the gap. LE-1810 introduced a single exposure predicate (isFieldExposable) but did not make it the only word, which is what turned a silent limitation into a visible broken promise.What
One predicate, expressed by field property — does it carry an editable literal value, and does the backend refuse it — instead of a static allowlist of type names, since the static list is precisely what aged into the bug. Every layer consumes that one predicate.
Class A — the panel promised and the snippet did not deliver. A
modelfield (18 across the component index: Agent, LanguageModelComponent, StructuredOutput, SmartRouter, SQLAgent, ToolCallingAgent, XMLAgent, OpenAPIAgent, OpenAIToolsAgent, EmbeddingModel, GuardrailValidator, Smart Transform, CSVAgent, BatchRunComponent, AMapComponent, AgenerateComponent, AreduceComponent, policies) turned the toggle green, persistedapi_editable: true, and never reached the tweaks object. The model type is now tweakable:apply_tweakshas no type allowlist, andModelInputnormalizes the assigned value through its own validator (validate_assignmentis on, so_validate_inputstriggersnormalize_value), which accepts a plain model name, a list of names, or the dict form.Class B — the panel offered what cannot mean anything. Handle-only inputs (
other, 94 fields — Tools, retrievers) are driven by an edge and carry no literal to send. The derivation always dropped them; now the panel stops offering them.Class C — the snippet promised and the backend refused in silence. Plain
str/bool/codefields that passed every frontend gate and are dropped byapply_tweaks:SQLComponent.database_urlandquery(protected sinks),PythonREPLComponent.python_codeandglobal_imports,PythonREPLTool.global_imports,Smart Transform.filter_instruction,CSVAgent.allow_dangerous_code(code-execution inputs),PythonFunction.function_code(code-typed — the frontend blocked the field NAMEcode, never the TYPE). All areshow: true, advanced: falsetoday, so they were genuinely offered. The refusal rules are now mirrored in the frontend for UX only — the backend remains the enforcement point, since any caller can post a tweak without going through this UI — and a cross-language parity test fails if the two sides ever diverge.Uniform treatment, nothing hidden. A field that cannot be tweaked keeps its row and its Add/Remove action (the panel's other job — hiding
Toolsordatabase_urlfrom the node must keep working); only the API control is blocked, with the cursor block and the existing tooltip. The non-tweakable case is checked before the off-node one, otherwise the tooltip would tell the user to add the parameter to the node — a way out that does not exist. Fields blocked temporarily (edge-connected, tool mode, off-node) are unchanged from LE-1810: they are tweakable, just not right now, and their reversible state stays visible. No new i18n key, no churn across the 7 locales.How to validate
Run the numbered script below. The evidence screenshots are numbered 1:1 with these steps — compare step Sn with image Sn. Before captures were taken on
release-1.11.1and after captures on this branch using the SAME saved flow with the same persistedapi_editable, so the only variable between the two columns is the code.tweaksblock at all despite the toggle being on. After:"tweaks": {"Agent-XXXXX": {"model": [{"name": "...", "provider": "..."}]}}.Toolsrow. Before: the API button is live and clickable. After: it is blocked, andRemoveon the same row still works.Database URLandSQL Query. Before: both offer a live API toggle. After: both are blocked.pointer-events-nonewhile disabled, so the hover is carried by the wrapper span — unchanged from LE-1810.)Plus: devtools console shows zero new errors throughout.
Evidence (S1–S4, before | after)
Tests
api-exposure-rules.test.ts(21 cases) pins the rules that are easy to get wrong on the next change: a code-execution input is refused only on a component that executes code, a protected sink only on the component that owns it (queryis refused onSQLComponentand tweakable onNewsSearch), the model type is tweakable, and a handle-only input never is.test_frontend_mirrors_tweak_refusal_rulesinsrc/lfx/tests/unit/utils/test_flow_validation.pyparses the TypeScript mirror and compares it to the Python frozensets, skipping when the frontend package is absent (standalone lfx checkout). Verified to fail on an injected drift.test_flow_validation75/75. Biome clean on the diff.tscunchanged against the base (the 6 pre-existing errors in the touched test file are identical before and after, verified by restoring the base version of the file and recounting).tweaksStore.test.tsfixtures gained the templatetypethat every real template carries — no assertion weakened.Note
The frontend mirror of the refusal rules is UX only and must never be treated as the enforcement: any caller can post a tweak without going through this UI, and
apply_tweaksis what refuses it. The module says so at the top, and the parity test exists so the mirror cannot drift into a lie.Exposing
modelis the only change here that widens what reaches the backend — those fields previously could not be advertised through this path at all. It stays under the flow author's control:api_editabledefaults to false and the toggle is theirs.Fields that already carry
api_editable: trueon a now non-tweakable field are not migrated: per LE-1810 the flag is never mutated, it simply goes inert, and the snippet stops listing it.Follow-ups deliberately left out:
apply_tweaksreports a refused tweak only through a log line, so an API caller cannot tell the override was dropped — making the run response surface that is a behavior change with security framing and deserves its own ticket. Same for renamingLANGFLOW_SUPPORTED_TYPESto what it actually means, and for splitting the catch-all "Disabled fields can't be called via API" tooltip into one message per cause.Summary by CodeRabbit
New Features
Bug Fixes
Tests