-
Notifications
You must be signed in to change notification settings - Fork 42
Step Listification
With the introduction of custom tools, and parameters, we now have a pretty powerful set of primitives that mimic functional programming. However, there is a set of use cases that these primitives satisfy, but do so awkwardly. Like:
Make three ideas for instagram posts based on a topic, then generate post content and a picture for each.
This is possible to build with "Plan and Execute" and custom tools, but it feels like an overkill for such a simple task.
It is very easy to create a flow for just one post though:

🆕 Now implemented.
Now, imagine if the "Generate Post Text" step had an extra "Make List" checkbox when we edit it:

So we check this checkbox and change the "Generate Post Text" prompt to:
Generate three instagram posts based on a topic:
(Topic)
Now, when we run this flow, we will suddenly get three posts with three images at the end.
What happens here?
When we check the "Make List" checkbox, it changes the output mode of the step. Instead of producing the typical LLMContent[]
, it produces a list of them.
When the next step receives a list like that, it recognizes that it's a list and instead of running just once, the step runs over each item in the list. And then, it produces the list again, creating a chain that is very similar to jQuery's selector chain. Gumloop's loop mode is spiritually similar as well.
When the lists are combined with chiclets, the step uses a list coalescing strategy (TBD) to produce a list as well.
Sometimes, the user might want to flatten the list back into LLMContent[]
, so the "Combine Outputs" step has a checkbox like this:

When checked, the list is flattened and we're back to a single-item mode. The next will receive a single LLMContent[]
, and will run in single-item mode, rather than iterating over a list.
Here's another example, using "Plan and Execute" steps:

The original intent was:
given an address and a set of interests, research local summer camps. find the 5 best camps that match the user interests. for each, do research to find out the exact hours, cost, distance from address, and available weeks. present findings in a web report.
We can also create nested lists. For instance, imagine this flow:

When we check the "Make List" checkbox on a step that already takes in a list, we create a list of lists. In the example above, it's the "Generate Chapter Outline", which creates a list of scenes for each chapter.
Each "Combine List" flattens only one level (the innermost) of the list, so it gives us an opportunity to first combine the scenes into a chapter, and then stick an illustration on top of them.
With lists, there are also opportunities for a more powerful user interaction. For instance, we could have a "Ask user to pick" step, where it takes a list and shows it as options, with configuration checkboxes to pick one or pick many. When only one item is picked, the "Ask user to pick" turns the list back into a single item.
This will be very useful in situations where flow builders want the user to choose among several choices of generated content.
To make a step list-aware, wrap the step's logic using ListExpander
. The general pattern is:
import { ListExpander } from "./a2/lists";
const results = await ListExpander(instruction, context).map(async (localInstruction, localContext) => {
// use localInstruction and localContext as if they were instruction and context.
});
if (!ok(results)) return results;
// The `results` is LLMContent[] that is ready to be returned as output.
return { context: results }