diff --git a/api/app/core/workflow/nodes/base_node.py b/api/app/core/workflow/nodes/base_node.py index 5d08670a..eb93c28c 100644 --- a/api/app/core/workflow/nodes/base_node.py +++ b/api/app/core/workflow/nodes/base_node.py @@ -1,5 +1,6 @@ import asyncio import logging +import re import time import uuid from abc import ABC, abstractmethod @@ -22,6 +23,9 @@ from app.services.multimodal_service import MultimodalService logger = logging.getLogger(__name__) +# 匹配模板变量 {{xxx}} 的正则 +_TEMPLATE_PATTERN = re.compile(r"\{\{.*?\}\}") + class NodeExecutionError(Exception): """节点执行失败异常。 @@ -503,10 +507,29 @@ class BaseNode(ABC): variable_pool: The variable pool used for reading and writing variables. Returns: - A dictionary containing the node's input data. + A dictionary containing the node's input data with all template + variables resolved to their actual runtime values. """ - # Default implementation returns the node configuration - return {"config": self.config} + return {"config": self._resolve_config(self.config, variable_pool)} + + @staticmethod + def _resolve_config(config: Any, variable_pool: VariablePool) -> Any: + """递归解析 config 中的模板变量,将 {{xxx}} 替换为实际值。 + + Args: + config: 节点的原始配置(可能包含模板变量)。 + variable_pool: 变量池,用于解析模板变量。 + + Returns: + 解析后的配置,所有字符串中的 {{变量}} 已被替换为真实值。 + """ + if isinstance(config, str) and _TEMPLATE_PATTERN.search(config): + return BaseNode._render_template(config, variable_pool, strict=False) + elif isinstance(config, dict): + return {k: BaseNode._resolve_config(v, variable_pool) for k, v in config.items()} + elif isinstance(config, list): + return [BaseNode._resolve_config(item, variable_pool) for item in config] + return config def _extract_output(self, business_result: Any) -> Any: """Extracts the actual output from the business result. diff --git a/api/app/core/workflow/nodes/document_extractor/node.py b/api/app/core/workflow/nodes/document_extractor/node.py index ea1070f4..737e3569 100644 --- a/api/app/core/workflow/nodes/document_extractor/node.py +++ b/api/app/core/workflow/nodes/document_extractor/node.py @@ -121,7 +121,10 @@ class DocExtractorNode(BaseNode): return business_result def _extract_input(self, state: WorkflowState, variable_pool: VariablePool) -> dict[str, Any]: - return {"file_selector": self.config.get("file_selector")} + file_selector = self.config.get("file_selector", "") + # 将变量选择器(如 sys.files)解析为实际值 + resolved = self.get_variable(file_selector, variable_pool, strict=False, default=file_selector) + return {"file_selector": resolved} async def execute(self, state: WorkflowState, variable_pool: VariablePool) -> Any: config = DocExtractorNodeConfig(**self.config)