In UR-2.0, pipelines bind data through variable names: each tool registers its input parameter names and output variable names, and each step in the pipeline execution passes parameters and data flow through these variable names. This mechanism is simple and straightforward, but in multi-round calls and complex control structures (such as loops or branches), it can cause variable name conflicts or duplicate bindings. To solve this problem, UltraRAG provides a parameter rewriting mechanism (parameter renaming), allowing you to flexibly control data flow within the pipeline.

How does data flow?

Each tool specifies its input and output variable names when registered on the Server. For example:
def __init__(self, mcp_inst):
    mcp_inst.tool(
        self.retriever_search,
        output="q_ls,top_k->ret_psg",
    )

def retriever_search(self, q_ls, top_k) -> ...
    ...
    return {"ret_psg": ...}
This means:
  • The tool receives two input variables q_ls and top_k
  • The tool’s output variable name is ret_psg
In the simplest serial pipeline, this default binding works fine. But if you call retriever_search multiple times in loops or branches and want to pass different data (for example, q_ls the first time and subq_ls the second time), you need a way to tell the pipeline that these variables are actually “synonyms”.

Parameter Renaming Mechanism

To solve the above variable name binding conflicts, UR-2.0 provides the following mechanism: UR-2.0 supports explicit renaming of input and output parameters in pipeline.yaml using input: and output: mappings, without modifying the Server code.

Basic Syntax

- module.tool:
    input:
      function_parameter_name: pipeline_variable_name
    output:
      tool_output_key: pipeline_variable_name
This mechanism follows these principles:

Example 1: Input Variable Renaming

Suppose the tool function is declared as follows:
async def retriever_search(
        self,
        query_list: List[str],
        top_k: Optional[int] | None = None,
        query_instruction: str = "",
        use_openai: bool = False,
    ) -> Dict[str, List[List[str]]]:
You can rename the input variable in the pipeline like this:
- retriever.retriever_search:
    input:
      query_list: sub_q_ls
The retriever_search originally expects an input parameter named query_list, but here you actually use the variable name sub_q_ls. By explicitly binding it with input:, the mapping is completed without modifying the tool’s internal implementation.
This mapping is based on the parameter names declared in the function signature.

Example 2: Output Variable Renaming

Suppose the tool is registered as follows:
mcp_inst.tool(
    self.retriever_search,
    output="q_ls,top_k,query_instruction,use_openai->ret_psg",
)
You can rewrite the output name in the pipeline like this:
- retriever.retriever_search:
    output:
      ret_psg: round1_result
No matter what the internal return variable name is, as long as the output is registered as ret_psg, it will now be mapped to round1_result for subsequent steps to use.
This mapping is based on the output name specified during tool registration.
If there is a downstream module depending on this result:
@app.prompt(output="q_ls,ret_psg,template->prompt_ls")
def qa_rag_boxed(
    q_ls: List[str], ret_psg: List[str | Any], template: str | Path
) -> list[PromptMessage]:
- prompt.qa_rag_boxed:
    input:
      ret_psg: round1_result
The tool qa_rag_boxed originally expects the input ret_psg. Here, we explicitly map the previous step’s round1_result as its input to achieve data binding.

Example 3: Renaming Both Input and Output

- retriever.retriever_search:
    input:
      q_ls: round1_query
    output:
      ret_psg: round1_result
This pattern is very common in loops, where each retrieval uses new variable names. With the parameter renaming mechanism, you can flexibly combine and reuse any Server-provided tools without modifying their source code, making UltraRAG’s pipeline more extensible and controllable.