Structured results
ControlFlow allows you to specify the expected structure of a task’s result using theresult_type
parameter. This ensures that the result conforms to a specific data schema, making it easier to work with and reducing the risk of errors in downstream tasks.
Strings
By default, theresult_type
of a task is a string, which essentially means the agent can return any value that satisfies the task’s objective.
For example, if you ask an agent to “Say hello in three languages”, it might return a simple string like "Hello; Hola; Bonjour"
or a more complex, conversational response instead:
Numbers
If your result is a number, you can specify theresult_type
as int
or float
:
Booleans
You can usebool
for tasks whose result is a simple true/false value:
Collections
You can also use typed collections like lists and dicts to specify the structure of your task’s result. Let’s revisit the example of asking an agent to say hello in three languages, but this time specifying that the result should be a list of strings, orlist[str]
. This forces the agent to produce the result you probably expected (three separate strings, each representing a greeting in a different language):
Annotated types
Sometimes, data types are not precise enough to guide the agent to the desired result. In these cases, you can use an annotated type to provide more specific instructions. For example, if we want to ensure that the agent returns a string that is only a zip code, we can specify theresult_type
as Annotated[str, "a 5 digit zip code"]
.
Note that annotated types are not validated; the annotation is provided as part of the agent’s natural language instructions. You could additionaly provide a custom result validator to enforce the constraint.
Labeling / classification
Often, you may want an agent to choose a value from a specific set of options, in order to label or classify a response as one of potentially many choices. To do this, specify a list, tuple,Literal
, or enum of allowed values for the result type. Here, we classify the media type of “Star Wars: Return of the Jedi” from a list of options:
ControlFlow optimizes single-choice constrained selection by asking agents to choose a value by index rather than writing out the entire response. This optimization significantly improves latency while also conserving output tokens.
A list of labels
When you provide a set of constrained choices, the agent will choose one and only one as the task’s result. Often, you will want to produce a list of labels, either because you want to classify multiple items at once OR because you want to allow the agent to choose multiple values for a single input. To do so, you must indicate that your expected result type is a list of eitherLiteral
values or enum members.
In the following example, two media types are provided as context, and because the result type is a list, the agent is able to produce two responses:
Labeling multiple inputs at once relies on Python’s built-in type annotations and does not provide the same list- and tuple-aware optimizations and sugar that ControlFlow provides for single-choice constrained selection. Therefore the following syntax, which is not considered proper Python, will error:but using a
Literal
or enum will work:Pydantic models
For complex, structured results, you can use a Pydantic model as theresult_type
. Pydantic models provide a powerful way to define data schemas and validate input data.
No result
Sometimes, you may want to ask an agent to perform an action without expecting or requiring a result. In this case, you can specifyresult_type=None
. For example, you might want to ask an agent to use a tool or post a message to the workflow thread, without requiring any task output.
Validation
Pydantic
When using a Pydantic model as theresult_type
, you can use any of Pydantic’s built-in or custom validators to further constrain or modify the result after it has been produced.
Validation functions
If you supply a function as your task’sresult_validator
, it can be used to further validate or even modify the result after it has been produced by an agent.
The result validator will be called with the LLM result after it has been coerced into the result_type
, and must either return a validated result or raise an exception. ControlFlow supplies a few common validators to get you started:
between(min_value, max_value)
: Validates that the result is a float betweenmin_value
andmax_value
.has_len(min_length, max_length)
: Validates that the result is a string, list, or tuple with a length betweenmin_length
andmax_length
.has_keys(required_keys)
: Validates that the result is a dictionary with all of the specified keys.is_url()
: Validates that the result is a string that is a URL.is_email()
: Validates that the result is a string that is an email address.
controlflow.tasks.validators
module, along with a convenient chain
function that allows you to combine multiple validators into a single function.
Remember that result validators must either return the result or raise an exception. They are not true/false checks!