How to Create Tools with Multiple Outputs#

Prerequisites

This guide assumes familiarity with:

WayFlow Tools are a powerful way to equip assistants with new, controllable capabilities. A key feature of tools is their ability to return multiple outputs, which can then be used across different steps in a :doc:Flow <../tutorials/basic_flow>. Understanding how to map these outputs to variables within a Flow is useful for creating flexible and modular assistants.

In this tutorial, you will:

  • Recap how tools output their variables

  • Learn how to output multiple variables from a tool with the tool annotation

  • Learn how to output multiple variables from a tool with ServerTool

Imports#

To get started, import the following elements:

 1from typing import Dict, Union
 2
 3from wayflowcore.flow import Flow
 4from wayflowcore.property import (
 5    BooleanProperty,
 6    DictProperty,
 7    IntegerProperty,
 8    StringProperty,
 9    UnionProperty,
10)
11from wayflowcore.steps import ToolExecutionStep
12from wayflowcore.tools import ServerTool, tool
13

Basic tool implementation#

When using a tool decorator, the tool step returns a single output by default. If the tool’s return type is a dictionary (Dict), the step returns a dictionary object. This output can be accessed using ToolExecutionStep.TOOL_OUTPUT, which is its default name. While this is useful in many cases, it does not allow the flow to access each variable individually without additional preprocessing.

 1@tool(description_mode="only_docstring")
 2def my_func() -> Dict[str, Union[str, int]]:
 3    """..."""
 4    return {"my_string": "Hello World!", "my_int": 2147483647, "my_bool": False}
 5
 6
 7flow = Flow.from_steps(
 8    [ToolExecutionStep(tool=my_func, output_mapping={ToolExecutionStep.TOOL_OUTPUT: "full_output"})]
 9)
10
11conv = flow.start_conversation()
12status = conv.execute()
13print(status.output_values)

As can be seen in the next two sections, it is possible to use either the @tool annotation or ServerTool to be able to output several outputs out of a tool.

Multiple outputs with the tool annotation#

To enable a tool to return multiple outputs that can be used directly in subsequent steps, all expected outputs must be defined using output_descriptors. This argument can be passed to the @tool annotation. The flow will then unpack the dictionary returned by the tool and assigns each value to a separate variable.

 1@tool(description_mode="only_docstring", output_descriptors=[
 2        StringProperty(name="my_string"),
 3        IntegerProperty(name="my_int"),
 4        BooleanProperty(name="my_bool"),
 5    ])
 6def my_func() -> Dict[str, Union[str, int]]:
 7    """..."""
 8    return {"my_string": "Hello World!", "my_int": 2147483647, "my_bool": False}
 9
10
11flow = Flow.from_steps(
12    [
13        ToolExecutionStep(
14            tool=my_func,
15            output_mapping={
16                "my_int": "integer_output",
17                "my_string": "string_output",
18                "my_bool": "bool_output",
19            },
20        )
21    ]
22)
23
24conv = flow.start_conversation()
25status = conv.execute()
26print(status.output_values)

Warning

If output_descriptors receives a single variable (i.g., [StringProperty(...)]), the dictionary will not be unpacked. Instead, the entire dictionary will be treated as a single output, which may result in a type error.

In this case, the unpacked output becomes {'bool_output': False, 'string_output': 'Hello World!', 'integer_output': 2147483647}, and we can use each of the variables independently inside the Flow.

Multiple outputs with ServerTool#

To enable a tool to return multiple outputs that can be used directly in subsequent steps, all expected outputs must be defined using output_descriptors. The model will then unpack the dictionary returned by the tool and assigns each value to a separate variable.

 1def my_func() -> Dict[str, Union[str, int]]:
 2    return {"my_string": "Hello World!", "my_int": 2147483647, "my_bool": False}
 3
 4
 5my_tool = ServerTool(
 6    name="my_tool",
 7    description="...",
 8    func=my_func,
 9    input_descriptors=[],
10    output_descriptors=[
11        StringProperty(name="my_string"),
12        IntegerProperty(name="my_int"),
13        BooleanProperty(name="my_bool"),
14    ],
15)
16
17flow = Flow.from_steps(
18    [
19        ToolExecutionStep(
20            tool=my_tool,
21            output_mapping={
22                "my_int": "integer_output",
23                "my_string": "string_output",
24                "my_bool": "bool_output",
25            },
26        )
27    ]
28)
29
30conv = flow.start_conversation()
31status = conv.execute()
32print(status.output_values)

Warning

If output_descriptors receives a single variable (i.g., [StringProperty(...)]), the dictionary will not be unpacked. Instead, the entire dictionary will be treated as a single output, which may result in a type error.

In this case, the unpacked output becomes {'bool_output': False, 'string_output': 'Hello World!', 'integer_output': 2147483647}, and we can use each of the variables independently inside the Flow.

Recap#

In this guide, you have learned how to extract individual variables returned by a ToolExecutionStep so they can be used independently within a Flow.

Below is the complete code referenced in this guide.
 1from typing import Dict, Union
 2
 3from wayflowcore.flow import Flow
 4from wayflowcore.property import (
 5    BooleanProperty,
 6    DictProperty,
 7    IntegerProperty,
 8    StringProperty,
 9    UnionProperty,
10)
11from wayflowcore.steps import ToolExecutionStep
12from wayflowcore.tools import ServerTool, tool
13
 1@tool(description_mode="only_docstring", output_descriptors=[
 2        StringProperty(name="my_string"),
 3        IntegerProperty(name="my_int"),
 4        BooleanProperty(name="my_bool"),
 5    ])
 6def my_func() -> Dict[str, Union[str, int]]:
 7    """..."""
 8    return {"my_string": "Hello World!", "my_int": 2147483647, "my_bool": False}
 9
10
11flow = Flow.from_steps(
12    [
13        ToolExecutionStep(
14            tool=my_func,
15            output_mapping={
16                "my_int": "integer_output",
17                "my_string": "string_output",
18                "my_bool": "bool_output",
19            },
20        )
21    ]
22)
23
24conv = flow.start_conversation()
25status = conv.execute()
26print(status.output_values)
 1def my_func() -> Dict[str, Union[str, int]]:
 2    return {"my_string": "Hello World!", "my_int": 2147483647, "my_bool": False}
 3
 4
 5my_tool = ServerTool(
 6    name="my_tool",
 7    description="...",
 8    func=my_func,
 9    input_descriptors=[],
10    output_descriptors=[
11        StringProperty(name="my_string"),
12        IntegerProperty(name="my_int"),
13        BooleanProperty(name="my_bool"),
14    ],
15)
16
17flow = Flow.from_steps(
18    [
19        ToolExecutionStep(
20            tool=my_tool,
21            output_mapping={
22                "my_int": "integer_output",
23                "my_string": "string_output",
24                "my_bool": "bool_output",
25            },
26        )
27    ]
28)
29
30conv = flow.start_conversation()
31status = conv.execute()
32print(status.output_values)

Next steps#

Now that you hve learned how to produce multiple outputs in a Tool and use them in a Flow, you may proceed to: