Repository navigation
fix(workflow): give each retry of a dynamic function node fresh arguments - #7556
Open
arjunprabhulal wants to merge 2 commits into
Open
arjunprabhulal wants to merge 2 commits into
arjunprabhulal wants to merge 2 commits into
Conversation
…ents When a FunctionNode run through ctx.run_node or a NodeTool was retried, every attempt received the same argument objects. If an attempt changed a list or dict argument and then failed, the retry started from the changed value. The arguments are resolved once before the node runs and that resolved input was reused as is by each attempt. The resolved input now remembers what it was resolved from. The first attempt still uses it directly, so arguments are coerced only once on the normal path, and any later attempt resolves again from the original input. A new test changes a list argument, fails once, and checks that the retry sees the original list.
Contributor
There was a problem hiding this comment.
Code Review
This pull request ensures that retried dynamic function nodes receive fresh arguments instead of mutated ones from previous attempts by storing the original source input and a bound flag in _ResolvedParameters. A unit test has been added to verify this behavior. The feedback suggests adding class-level type annotations for source and bound in _ResolvedParameters to improve type safety and static analysis.
Declare source and bound at class level so type checkers and IDEs see them on every _ResolvedParameters instance.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Link to Issue or Description of Change
1. Link to an existing issue (if applicable):
No existing issue. The bug is described below.
2. Or, if no issue exists, describe the change:
Problem:
When a
FunctionNodethat is run throughctx.run_nodeor aNodeToolis retried, the retry receives the same argument objects as the attempt that failed. If the function changed a list or dict argument before raising, the retry starts from the changed value.Steps to reproduce:
FunctionNodewithparameter_binding='node_input'andretry_configwrapsadd_item(items: list[int]), which appends99toitemsand raises on its first call.ctx.run_node(add_item_node, node_input={'items': [1, 2]}).Expected: both attempts receive
[1, 2].Observed: the second attempt receives
[1, 2, 99].This is a regression from 156f007. The scheduler and
NodeToolresolve the arguments before the node runs and pass the resolved_ResolvedParametersin asnode_input.NodeRunnerhands that same object to every attempt, and_bind_parametersnow uses it as is with a shallowdict()copy, so every attempt shares the coerced lists and dicts. Before that change each attempt coerced again and got new containers. Arguments that come in over a static graph edge are not affected, because they arrive as a plain dict and are resolved on every attempt.Solution:
_ResolvedParametersnow remembers the input it was resolved from. The first bind uses the resolved values directly, so the normal path still coerces only once, whichtest_node_input_binding_coerces_parameters_oncepins. Any later bind of the same object, which only happens on a retry, resolves again from the original input and gets its own argument objects.Testing Plan
Unit Tests:
New test
test_dynamic_function_node_retry_gets_fresh_argumentsintests/unittests/workflow/test_node_runner_failure.py. The existing coerce once tests intest_function_node.pystill pass.Without the fix:
With the fix:
The same subset passes on Python 3.11 and 3.13. The full unit suite (
pytest tests/unittests) shows the same single failure as an unmodified checkout of main,test_reading_directory_raisesintests/unittests/integrations/vmaas, which fails there without this change.I also ran the same function as a
NodeToolcalled by anLlmAgent. Before the fix the retry received[1, 2, 99], and after it the retry receives[1, 2].pre-commit run --filespasses on both changed files.Manual End-to-End (E2E) Tests:
A script runs the steps above through
RunnerwithInMemorySessionService.Before:
After:
Checklist