How to Create Tools with Multiple Outputs#
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:
Using a BranchingStep to decide how to handle those variables.