mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-06-29 15:31:05 +08:00
fix(agent): support iteration item aliases in child nodes (#14146)
## Summary This PR fixes the iteration variable mismatch reported in #14142. Changes: - restore compatibility for `IterationItem@result` by exposing `result` alongside `item` - support bare iteration aliases like `{item}`, `{index}`, and `{result}` inside iteration child-node inputs - add focused unit/runtime tests covering both alias styles and multi-item iteration execution ## Validation ```bash pytest -q --noconftest \ test/testcases/test_web_api/test_canvas_app/test_iterationitem_unit.py \ test/testcases/test_web_api/test_canvas_app/test_iteration_runtime_unit.py \ test/testcases/test_web_api/test_canvas_app/test_invoke_component_unit.py ``` Result: `12 passed` Closes #14142
This commit is contained in:
@@ -366,6 +366,7 @@ class ComponentBase(ABC):
|
||||
component_name: str
|
||||
thread_limiter = asyncio.Semaphore(int(os.environ.get("MAX_CONCURRENT_CHATS", 10)))
|
||||
variable_ref_patt = r"\{* *\{([a-zA-Z:0-9]+@[A-Za-z0-9_.-]+|sys\.[A-Za-z0-9_.]+|env\.[A-Za-z0-9_.]+)\} *\}*"
|
||||
iteration_alias_patt = r"\{* *\{(item|index|result)\} *\}*"
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
@@ -501,6 +502,23 @@ class ComponentBase(ABC):
|
||||
|
||||
return {var: self.get_input_value(var) for var, o in self.get_input_elements().items()}
|
||||
|
||||
def _resolve_iteration_alias_ref(self, exp: str) -> str | None:
|
||||
if exp not in {"item", "index", "result"}:
|
||||
return None
|
||||
|
||||
parent = self.get_parent()
|
||||
if not parent or parent.component_name.lower() != "iteration":
|
||||
return None
|
||||
|
||||
for cid, cpn in self._canvas.components.items():
|
||||
if cpn.get("parent_id") != parent._id:
|
||||
continue
|
||||
if cpn["obj"].component_name.lower() != "iterationitem":
|
||||
continue
|
||||
return f"{cid}@{exp}"
|
||||
|
||||
return None
|
||||
|
||||
def get_input_elements_from_text(self, txt: str) -> dict[str, dict[str, str]]:
|
||||
res = {}
|
||||
for r in re.finditer(self.variable_ref_patt, txt, flags=re.IGNORECASE | re.DOTALL):
|
||||
@@ -512,6 +530,20 @@ class ComponentBase(ABC):
|
||||
"_retrieval": self._canvas.get_variable_value(f"{cpn_id}@_references") if cpn_id else None,
|
||||
"_cpn_id": cpn_id
|
||||
}
|
||||
for r in re.finditer(self.iteration_alias_patt, txt, flags=re.IGNORECASE | re.DOTALL):
|
||||
exp = r.group(1)
|
||||
if exp in res:
|
||||
continue
|
||||
ref = self._resolve_iteration_alias_ref(exp)
|
||||
if not ref:
|
||||
continue
|
||||
cpn_id, var_nm = ref.split("@", 1)
|
||||
res[exp] = {
|
||||
"name": (self._canvas.get_component_name(cpn_id) + f"@{var_nm}"),
|
||||
"value": self._canvas.get_variable_value(ref),
|
||||
"_retrieval": self._canvas.get_variable_value(f"{cpn_id}@_references"),
|
||||
"_cpn_id": cpn_id
|
||||
}
|
||||
return res
|
||||
|
||||
def get_input_elements(self) -> dict[str, Any]:
|
||||
|
||||
@@ -54,7 +54,11 @@ class IterationItem(ComponentBase, ABC):
|
||||
if self.check_if_canceled("IterationItem processing"):
|
||||
return
|
||||
|
||||
self.set_output("item", arr[self._idx])
|
||||
current_item = arr[self._idx]
|
||||
self.set_output("item", current_item)
|
||||
# Keep `result` as a compatibility alias because existing DSL examples
|
||||
# and downstream references may still consume IterationItem via `@result`.
|
||||
self.set_output("result", current_item)
|
||||
self.set_output("index", self._idx)
|
||||
|
||||
self._idx += 1
|
||||
|
||||
Reference in New Issue
Block a user