diff --git a/velvetflow/bindings.py b/velvetflow/bindings.py index ee0319b4..2af099f6 100644 --- a/velvetflow/bindings.py +++ b/velvetflow/bindings.py @@ -597,8 +597,6 @@ def _validate_result_reference(self, src_path: str) -> None: if not loop_schema: raise ValueError(f"__from__ 引用的 loop 节点 '{node_id}' 未定义 exports") field_path = parts[2:] - if field_path and field_path[0] == "exports": - field_path = field_path[1:] if ( field_path and field_path[-1] == "count" @@ -711,7 +709,14 @@ def _append_token(token: Any) -> None: resolved_parts.extend(["result_of", str(first_key)]) rest = parts[2:] node = self._nodes.get(first_key) - if node and node.type == "loop" and rest and rest[0] == "exports": + if ( + node + and node.type == "loop" + and rest + and rest[0] == "exports" + and isinstance(cur, dict) + and "exports" not in cur + ): rest = rest[1:] else: context = {f"result_of.{nid}": value for nid, value in self.results.items()} @@ -981,10 +986,26 @@ def _resolve(value: Any, path: str = "") -> Any: stripped_template = normalized_templates.strip() if stripped_template.startswith("{{") and stripped_template.endswith("}}"): inner = stripped_template[2:-2].strip() + normalized_inner = normalize_reference_path(inner) + qualified_inner = ctx._qualify_context_path(normalized_inner) + head = qualified_inner.split(".", 1)[0] + if re.match(r"^[A-Za-z_][\w.\[\]*]*$", normalized_inner) and ( + head in ctx.loop_ctx + or head == ctx.loop_id + or qualified_inner.startswith("loop.") + or qualified_inner.startswith("result_of.") + ): + try: + return ctx.get_value(qualified_inner) + except Exception: + pass try: rendered = eval_jinja_expression(inner, ctx.build_jinja_context()) if isinstance(rendered, Undefined): - return None + try: + return ctx.get_value(qualified_inner) + except Exception: + return None return rendered except Exception: pass diff --git a/velvetflow/models.py b/velvetflow/models.py index 5a0fdef9..ba6ba96c 100644 --- a/velvetflow/models.py +++ b/velvetflow/models.py @@ -752,27 +752,31 @@ def validate_loop_body_subgraphs(self) -> "Workflow": normalize_reference_path(source) if isinstance(source, str) else None ) if isinstance(normalized_source, str): - from velvetflow.verification.binding_checks import ( - _get_output_schema_at_path, - ) + if not ( + normalized_source == "loop" + or normalized_source.startswith(("loop.", "loop_ctx.")) + ): + from velvetflow.verification.binding_checks import ( + _get_output_schema_at_path, + ) - schema = _get_output_schema_at_path( - normalized_source, - nodes_by_id, - actions_by_id, - loop_body_parents, - ) - actual_type = schema.get("type") if isinstance(schema, Mapping) else None - if actual_type not in {"array"}: - errors.append( - { - "loc": ("nodes", idx, "params", "source"), - "msg": ( - "loop 节点的 source 应该引用数组/序列" - f",但解析到的类型为 {actual_type or '未知'},路径: {normalized_source}" - ), - } + schema = _get_output_schema_at_path( + normalized_source, + nodes_by_id, + actions_by_id, + loop_body_parents, ) + actual_type = schema.get("type") if isinstance(schema, Mapping) else None + if actual_type not in {"array"}: + errors.append( + { + "loc": ("nodes", idx, "params", "source"), + "msg": ( + "loop 节点的 source 应该引用数组/序列" + f",但解析到的类型为 {actual_type or '未知'},路径: {normalized_source}" + ), + } + ) try: Workflow.model_validate(