|
| 1 | +--- |
| 2 | +title: "Tutorial: Tasks" |
| 3 | +--- |
| 4 | + |
| 5 | +Welcome to ControlFlow! In this tutorial, we'll explore the fundamental building block of ControlFlow workflows: `Tasks`. |
| 6 | + |
| 7 | +We'll learn how to create tasks, define dependencies between them, and control their execution within a flow. By the end of this tutorial, you'll have a solid understanding of how to break down complex problems into manageable tasks and orchestrate them effectively. |
| 8 | + |
| 9 | +## Creating a Simple Task |
| 10 | + |
| 11 | +At the core of ControlFlow are `Task` objects, which represent discrete units of work to be performed by AI agents. Let's start by creating a simple task that generates a list of top attractions in Washington D.C.: |
| 12 | + |
| 13 | +```python |
| 14 | +from controlflow import flow, Task |
| 15 | + |
| 16 | + |
| 17 | +@flow |
| 18 | +def generate_attractions(destination: str): |
| 19 | + attractions = Task( |
| 20 | + objective="Generate a list of 10 must-see attractions at the destination", |
| 21 | + result_type=list[str], |
| 22 | + ) |
| 23 | + return attractions |
| 24 | + |
| 25 | + |
| 26 | +attractions = generate_attractions("Washington D.C.") |
| 27 | +print(attractions) |
| 28 | +# ['Lincoln Memorial', 'National Mall', 'United States Capitol', ...] |
| 29 | +``` |
| 30 | + |
| 31 | +In this example, we: |
| 32 | + |
| 33 | +- Define a function `generate_attractions()` and decorate it with `@flow`, turning it into a ControlFlow workflow |
| 34 | +- Create a `Task` object named `attractions` with an objective and result type of `list[str]`. Tasks have access to their flow's context, which is why the task knew about the `destination`. |
| 35 | +- Return the `attractions` task from the flow function |
| 36 | + |
| 37 | +When we call `generate_attractions()`, ControlFlow automatically detects and runs the task. When a task is returned from a flow, it is automatically resolved into its result, which is why we received a list of attractions as the output. |
| 38 | + |
| 39 | +## Defining Task Dependencies |
| 40 | + |
| 41 | +Complex workflows often involve multiple tasks with dependencies between them. ControlFlow makes it easy to define these dependencies and ensure tasks are executed in the correct order. Let's expand our example to generate a personalized travel itinerary for Washington D.C.: |
| 42 | + |
| 43 | +```python |
| 44 | +@flow |
| 45 | +def generate_itinerary(destination: str): |
| 46 | + attractions = Task( |
| 47 | + objective="Generate a list of 10 must-see attractions at the destination", |
| 48 | + result_type=list[str], |
| 49 | + ) |
| 50 | + |
| 51 | + itinerary = [ |
| 52 | + Task( |
| 53 | + objective=f"Make an itinerary for day {day} in the destination, focusing on attractions in the provided `area`", |
| 54 | + context=dict(attractions=attractions, destination=destination, area=area), |
| 55 | + result_type=str, |
| 56 | + ) |
| 57 | + for day, area in enumerate(['National Mall', 'Tidal Basin', 'Downtown'], start=1) |
| 58 | + ] |
| 59 | + |
| 60 | + return itinerary |
| 61 | + |
| 62 | + |
| 63 | +itinerary = generate_itinerary('Washington D.C.') |
| 64 | +for day, activity in enumerate(itinerary, start=1): |
| 65 | + print(f"Day {day}:\n\n{activity}\n\n") |
| 66 | + |
| 67 | +# Day 1: |
| 68 | +# |
| 69 | +# 8:00 AM - 9:00 AM: Lincoln Memorial |
| 70 | +# 9:00 AM - 10:00 AM: Washington Monument |
| 71 | +# 10:00 AM - 12:00 PM: National Museum of American History |
| 72 | +# ... |
| 73 | +``` |
| 74 | + |
| 75 | +In this example, we: |
| 76 | + |
| 77 | +- Create an `attractions` task that generates a list of top attractions in Washington D.C. |
| 78 | +- Create an `itinerary` task for each day of the trip, focusing on attractions in specific areas of the city and using the `attractions` task as context |
| 79 | + |
| 80 | +By passing the `attractions` task as context to the `itinerary` tasks, we define a dependency between them. ControlFlow ensures that the `attractions` task is executed before the `itinerary` tasks, and the `itinerary` tasks can access the result of the `attractions` task. |
| 81 | + |
| 82 | +## Controlling Task Execution |
| 83 | + |
| 84 | +Sometimes, you might want to control the execution of tasks within a flow based on the results of previous tasks. You can do this by calling `task.run()` to manually run a task and get its result: |
| 85 | + |
| 86 | +```python |
| 87 | +@flow |
| 88 | +def generate_dc_itinerary_with_recommendations(): |
| 89 | + trip = generate_dc_itinerary() |
| 90 | + |
| 91 | + budget = Task( |
| 92 | + objective="Ask the user for their daily budget for meals and activities in Washington D.C.", |
| 93 | + result_type=float, |
| 94 | + user_access=True, |
| 95 | + ) |
| 96 | + budget.run() |
| 97 | + |
| 98 | + cuisine = Task( |
| 99 | + objective="Ask the user for their preferred cuisine type for dining in Washington D.C.", |
| 100 | + result_type=str, |
| 101 | + user_access=True, |
| 102 | + ) |
| 103 | + cuisine.run() |
| 104 | + |
| 105 | + recommendations = [ |
| 106 | + Task( |
| 107 | + objective="Generate a list of restaurant recommendations for `cuisine` cuisine in Washington D.C. for a budget of `budget` per day", |
| 108 | + context=dict(cuisine=cuisine.result, budget=budget.result), |
| 109 | + result_type=list[str], |
| 110 | + ) |
| 111 | + for _ in trip['itinerary'] |
| 112 | + ] |
| 113 | + |
| 114 | + return dict(trip=trip, recommendations=recommendations) |
| 115 | + |
| 116 | +trip_with_recs = generate_dc_itinerary_with_recommendations() |
| 117 | +print(f"Top attractions: {', '.join(trip_with_recs['trip']['attractions'])}") |
| 118 | +for day, (activity, recs) in enumerate(zip(trip_with_recs['trip']['itinerary'], trip_with_recs['recommendations']), start=1): |
| 119 | + print(f"Day {day}: {activity}") |
| 120 | + print(f"Restaurant recommendations: {', '.join(recs)}") |
| 121 | + |
| 122 | +# Output: |
| 123 | +# Top attractions: Lincoln Memorial, National Mall, United States Capitol, White House, Smithsonian National Air and Space Museum, Washington Monument, Smithsonian National Museum of Natural History, Tidal Basin, Vietnam Veterans Memorial, Library of Congress |
| 124 | +# Day 1: Start your day at the Lincoln Memorial, then walk along the National Mall, taking in the sights of the Washington Monument and the Reflecting Pool. Visit the Smithsonian National Museum of American History and the Smithsonian National Museum of Natural History. |
| 125 | +# Restaurant recommendations: Ben's Chili Bowl, Founding Farmers, Busboys and Poets |
| 126 | +# Day 2: Explore the Tidal Basin area, starting with the Jefferson Memorial. Take a stroll around the Tidal Basin to enjoy the cherry blossoms (if in season). Visit the Martin Luther King Jr. Memorial and the Franklin Delano Roosevelt Memorial. |
| 127 | +# Restaurant recommendations: Old Ebbitt Grill, The Hamilton, Jaleo |
| 128 | +# Day 3: Spend the day in Downtown Washington D.C. Start at the White House Visitor Center, then take a guided tour of the United States Capitol. Visit the Library of Congress and the Supreme Court Building. End your day with a visit to the Smithsonian National Portrait Gallery. |
| 129 | +# Restaurant recommendations: Rasika, Zaytinya, Oyamel |
| 130 | +``` |
| 131 | + |
| 132 | +In this example, we: |
| 133 | + |
| 134 | +- Call the `generate_dc_itinerary()` flow to get the top attractions and daily itinerary |
| 135 | +- Create a `budget` task to ask the user for their daily budget, using `user_access=True` to allow user interaction |
| 136 | +- Create a `cuisine` task to ask the user for their preferred cuisine type |
| 137 | +- Manually run the `budget` and `cuisine` tasks using `task.run()` to get their results |
| 138 | +- Create a `recommendations` list comprehension that generates a task for each day of the trip, providing restaurant recommendations based on the user's budget and preferred cuisine |
| 139 | +- Return a dictionary with the original `trip` and the `recommendations` |
| 140 | + |
| 141 | +By calling `task.run()`, we can control the execution flow based on task results, allowing for more dynamic and responsive workflows. |
| 142 | + |
| 143 | +## Next Steps |
| 144 | + |
| 145 | +Congratulations on completing this introduction to tasks in ControlFlow! You've learned how to: |
| 146 | + |
| 147 | +- Create simple tasks |
| 148 | +- Define dependencies between tasks using context |
| 149 | +- Control task execution within a flow using `task.run()` |
| 150 | + |
| 151 | +In the next tutorial, we'll dive deeper into the world of AI agents and explore how they can be used to bring your workflows to life. Stay tuned! |
| 152 | + |
| 153 | +If you can't wait to learn more, check out the [ControlFlow Concepts](/concepts) guide and [API Reference](/api-reference) for additional information and examples. Happy engineering! |
0 commit comments