propogate changes

This commit is contained in:
Brandon Hancock
2024-10-21 11:10:10 -04:00
parent 698cac066f
commit ec70e76ab6
5 changed files with 76 additions and 78 deletions

View File

@@ -26,8 +26,11 @@ Let's create a simple Flow where you will use OpenAI to generate a random city i
import asyncio import asyncio
from crewai.flow.flow import Flow, listen, start from crewai.flow.flow import Flow, listen, start
from dotenv import load_dotenv
from litellm import completion from litellm import completion
load_dotenv()
class ExampleFlow(Flow): class ExampleFlow(Flow):
model = "gpt-4o-mini" model = "gpt-4o-mini"
@@ -80,6 +83,8 @@ In the above example, we have created a simple Flow that generates a random city
When you run the Flow, it will generate a random city and then generate a fun fact about that city. The output will be printed to the console. When you run the Flow, it will generate a random city and then generate a fun fact about that city. The output will be printed to the console.
**Note:** Ensure you have set up your `.env` file to store your `OPENAI_API_KEY`. This key is necessary for authenticating requests to the OpenAI API.
### @start() ### @start()
The `@start()` decorator is used to mark a method as the starting point of a Flow. When a Flow is started, all the methods decorated with `@start()` are executed in parallel. You can have multiple start methods in a Flow, and they will all be executed when the Flow is started. The `@start()` decorator is used to mark a method as the starting point of a Flow. When a Flow is started, all the methods decorated with `@start()` are executed in parallel. You can have multiple start methods in a Flow, and they will all be executed when the Flow is started.
@@ -123,34 +128,34 @@ import asyncio
from crewai.flow.flow import Flow, listen, start from crewai.flow.flow import Flow, listen, start
class OutputExampleFlow(Flow): class OutputExampleFlow(Flow):
@start() @start()
def first_method(self): def first_method(self):
return "Output from first_method" return "Output from first_method"
@listen(first_method) @listen(first_method)
def second_method(self, first_output): def second_method(self, first_output):
return f"Second method received: {first_output}" return f"Second method received: {first_output}"
async def main(): async def main():
flow = OutputExampleFlow() flow = OutputExampleFlow()
final_output = await flow.kickoff() final_output = await flow.kickoff()
print("---- Final Output ----") print("---- Final Output ----")
print(final_output) print(final_output)
asyncio.run(main()) asyncio.run(main())
```
````
``` text Output ``` text Output
---- Final Output ---- ---- Final Output ----
Second method received: Output from first_method Second method received: Output from first_method
``` ````
</CodeGroup> </CodeGroup>
In this example, the `second_method` is the last method to complete, so its output will be the final output of the Flow. In this example, the `second_method` is the last method to complete, so its output will be the final output of the Flow.
The `kickoff()` method will return the final output, which is then printed to the console. The `kickoff()` method will return the final output, which is then printed to the console.
#### Accessing and Updating State #### Accessing and Updating State
In addition to retrieving the final output, you can also access and update the state within your Flow. The state can be used to store and share data between different methods in the Flow. After the Flow has run, you can access the state to retrieve any information that was added or updated during the execution. In addition to retrieving the final output, you can also access and update the state within your Flow. The state can be used to store and share data between different methods in the Flow. After the Flow has run, you can access the state to retrieve any information that was added or updated during the execution.
@@ -191,27 +196,28 @@ async def main():
asyncio.run(main()) asyncio.run(main())
``` ```
``` text Output ```text Output
Final Output: Hello from first_method - updated by second_method Final Output: Hello from first_method - updated by second_method
Final State: Final State:
counter=2 message='Hello from first_method - updated by second_method' counter=2 message='Hello from first_method - updated by second_method'
``` ```
</CodeGroup> </CodeGroup>
In this example, the state is updated by both `first_method` and `second_method`. In this example, the state is updated by both `first_method` and `second_method`.
After the Flow has run, you can access the final state to see the updates made by these methods. After the Flow has run, you can access the final state to see the updates made by these methods.
By ensuring that the final method's output is returned and providing access to the state, CrewAI Flows make it easy to integrate the results of your AI workflows into larger applications or systems, By ensuring that the final method's output is returned and providing access to the state, CrewAI Flows make it easy to integrate the results of your AI workflows into larger applications or systems,
while also maintaining and accessing the state throughout the Flow's execution. while also maintaining and accessing the state throughout the Flow's execution.
## Flow State Management ## Flow State Management
Managing state effectively is crucial for building reliable and maintainable AI workflows. CrewAI Flows provides robust mechanisms for both unstructured and structured state management, Managing state effectively is crucial for building reliable and maintainable AI workflows. CrewAI Flows provides robust mechanisms for both unstructured and structured state management,
allowing developers to choose the approach that best fits their application's needs. allowing developers to choose the approach that best fits their application's needs.
### Unstructured State Management ### Unstructured State Management
In unstructured state management, all state is stored in the `state` attribute of the `Flow` class. In unstructured state management, all state is stored in the `state` attribute of the `Flow` class.
This approach offers flexibility, enabling developers to add or modify state attributes on the fly without defining a strict schema. This approach offers flexibility, enabling developers to add or modify state attributes on the fly without defining a strict schema.
```python Code ```python Code
@@ -254,7 +260,7 @@ asyncio.run(main())
### Structured State Management ### Structured State Management
Structured state management leverages predefined schemas to ensure consistency and type safety across the workflow. Structured state management leverages predefined schemas to ensure consistency and type safety across the workflow.
By using models like Pydantic's `BaseModel`, developers can define the exact shape of the state, enabling better validation and auto-completion in development environments. By using models like Pydantic's `BaseModel`, developers can define the exact shape of the state, enabling better validation and auto-completion in development environments.
```python Code ```python Code
@@ -352,14 +358,14 @@ async def main():
asyncio.run(main()) asyncio.run(main())
``` ```
``` text Output ```text Output
Logger: Hello from the start method Logger: Hello from the start method
Logger: Hello from the second method Logger: Hello from the second method
``` ```
</CodeGroup> </CodeGroup>
When you run this Flow, the `logger` method will be triggered by the output of either the `start_method` or the `second_method`. When you run this Flow, the `logger` method will be triggered by the output of either the `start_method` or the `second_method`.
The `or_` function is used to listen to multiple methods and trigger the listener method when any of the specified methods emit an output. The `or_` function is used to listen to multiple methods and trigger the listener method when any of the specified methods emit an output.
### Conditional Logic: `and` ### Conditional Logic: `and`
@@ -396,19 +402,19 @@ async def main():
asyncio.run(main()) asyncio.run(main())
``` ```
``` text Output ```text Output
---- Logger ---- ---- Logger ----
{'greeting': 'Hello from the start method', 'joke': 'What do computers eat? Microchips.'} {'greeting': 'Hello from the start method', 'joke': 'What do computers eat? Microchips.'}
``` ```
</CodeGroup> </CodeGroup>
When you run this Flow, the `logger` method will be triggered only when both the `start_method` and the `second_method` emit an output. When you run this Flow, the `logger` method will be triggered only when both the `start_method` and the `second_method` emit an output.
The `and_` function is used to listen to multiple methods and trigger the listener method only when all the specified methods emit an output. The `and_` function is used to listen to multiple methods and trigger the listener method only when all the specified methods emit an output.
### Router ### Router
The `@router()` decorator in Flows allows you to define conditional routing logic based on the output of a method. The `@router()` decorator in Flows allows you to define conditional routing logic based on the output of a method.
You can specify different routes based on the output of the method, allowing you to control the flow of execution dynamically. You can specify different routes based on the output of the method, allowing you to control the flow of execution dynamically.
<CodeGroup> <CodeGroup>
@@ -454,7 +460,7 @@ async def main():
asyncio.run(main()) asyncio.run(main())
``` ```
``` text Output ```text Output
Starting the structured flow Starting the structured flow
Third method running Third method running
Fourth method running Fourth method running
@@ -462,16 +468,16 @@ Fourth method running
</CodeGroup> </CodeGroup>
In the above example, the `start_method` generates a random boolean value and sets it in the state. In the above example, the `start_method` generates a random boolean value and sets it in the state.
The `second_method` uses the `@router()` decorator to define conditional routing logic based on the value of the boolean. The `second_method` uses the `@router()` decorator to define conditional routing logic based on the value of the boolean.
If the boolean is `True`, the method returns `"success"`, and if it is `False`, the method returns `"failed"`. If the boolean is `True`, the method returns `"success"`, and if it is `False`, the method returns `"failed"`.
The `third_method` and `fourth_method` listen to the output of the `second_method` and execute based on the returned value. The `third_method` and `fourth_method` listen to the output of the `second_method` and execute based on the returned value.
When you run this Flow, the output will change based on the random boolean value generated by the `start_method`. When you run this Flow, the output will change based on the random boolean value generated by the `start_method`.
## Adding Crews to Flows ## Adding Crews to Flows
Creating a flow with multiple crews in CrewAI is straightforward. Creating a flow with multiple crews in CrewAI is straightforward.
You can generate a new CrewAI project that includes all the scaffolding needed to create a flow with multiple crews by running the following command: You can generate a new CrewAI project that includes all the scaffolding needed to create a flow with multiple crews by running the following command:
@@ -485,22 +491,21 @@ This command will generate a new CrewAI project with the necessary folder struct
After running the `crewai create flow name_of_flow` command, you will see a folder structure similar to the following: After running the `crewai create flow name_of_flow` command, you will see a folder structure similar to the following:
| Directory/File | Description | | Directory/File | Description |
|:---------------------------------|:------------------------------------------------------------------| | :--------------------- | :----------------------------------------------------------------- |
| `name_of_flow/` | Root directory for the flow. | | `name_of_flow/` | Root directory for the flow. |
| ├── `crews/` | Contains directories for specific crews. | | ├── `crews/` | Contains directories for specific crews. |
| │ └── `poem_crew/` | Directory for the "poem_crew" with its configurations and scripts.| | │ └── `poem_crew/` | Directory for the "poem_crew" with its configurations and scripts. |
| │ ├── `config/` | Configuration files directory for the "poem_crew". | | │ ├── `config/` | Configuration files directory for the "poem_crew". |
| │ ├── `agents.yaml` | YAML file defining the agents for "poem_crew". | | │ ├── `agents.yaml` | YAML file defining the agents for "poem_crew". |
| │ └── `tasks.yaml` | YAML file defining the tasks for "poem_crew". | | │ └── `tasks.yaml` | YAML file defining the tasks for "poem_crew". |
| │ ├── `poem_crew.py` | Script for "poem_crew" functionality. | | │ ├── `poem_crew.py` | Script for "poem_crew" functionality. |
| ├── `tools/` | Directory for additional tools used in the flow. | | ├── `tools/` | Directory for additional tools used in the flow. |
| │ └── `custom_tool.py` | Custom tool implementation. | | │ └── `custom_tool.py` | Custom tool implementation. |
| ├── `main.py` | Main script for running the flow. | | ├── `main.py` | Main script for running the flow. |
| ├── `README.md` | Project description and instructions. | | ├── `README.md` | Project description and instructions. |
| ├── `pyproject.toml` | Configuration file for project dependencies and settings. | | ├── `pyproject.toml` | Configuration file for project dependencies and settings. |
| └── `.gitignore` | Specifies files and directories to ignore in version control. | | └── `.gitignore` | Specifies files and directories to ignore in version control. |
### Building Your Crews ### Building Your Crews
@@ -542,8 +547,7 @@ class PoemFlow(Flow[PoemState]):
@listen(generate_sentence_count) @listen(generate_sentence_count)
def generate_poem(self): def generate_poem(self):
print("Generating poem") print("Generating poem")
poem_crew = PoemCrew().crew() result = PoemCrew().crew().kickoff(inputs={"sentence_count": self.state.sentence_count})
result = poem_crew.kickoff(inputs={"sentence_count": self.state.sentence_count})
print("Poem generated", result.raw) print("Poem generated", result.raw)
self.state.poem = result.raw self.state.poem = result.raw
@@ -554,18 +558,17 @@ class PoemFlow(Flow[PoemState]):
with open("poem.txt", "w") as f: with open("poem.txt", "w") as f:
f.write(self.state.poem) f.write(self.state.poem)
async def run(): async def kickoff():
"""
Run the flow.
"""
poem_flow = PoemFlow() poem_flow = PoemFlow()
await poem_flow.kickoff() await poem_flow.kickoff()
def main(): def plot():
asyncio.run(run()) poem_flow = PoemFlow()
poem_flow.plot()
if __name__ == "__main__": if __name__ == "__main__":
main() asyncio.run(kickoff())
``` ```
In this example, the `PoemFlow` class defines a flow that generates a sentence count, uses the `PoemCrew` to generate a poem, and then saves the poem to a file. The flow is kicked off by calling the `kickoff()` method. In this example, the `PoemFlow` class defines a flow that generates a sentence count, uses the `PoemCrew` to generate a poem, and then saves the poem to a file. The flow is kicked off by calling the `kickoff()` method.
@@ -587,13 +590,13 @@ source .venv/bin/activate
After activating the virtual environment, you can run the flow by executing one of the following commands: After activating the virtual environment, you can run the flow by executing one of the following commands:
```bash ```bash
crewai flow run crewai flow kickoff
``` ```
or or
```bash ```bash
uv run run_flow uv run kickoff
``` ```
The flow will execute, and you should see the output in the console. The flow will execute, and you should see the output in the console.
@@ -657,13 +660,13 @@ By exploring these examples, you can gain insights into how to leverage CrewAI F
Also, check out our YouTube video on how to use flows in CrewAI below! Also, check out our YouTube video on how to use flows in CrewAI below!
<iframe <iframe
width="560" width="560"
height="315" height="315"
src="https://www.youtube.com/embed/MTb5my6VOT8" src="https://www.youtube.com/embed/MTb5my6VOT8"
title="YouTube video player" title="YouTube video player"
frameborder="0" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin" referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen allowfullscreen
></iframe> ></iframe>{" "}

View File

@@ -14,11 +14,11 @@ from .authentication.main import AuthenticationCommand
from .deploy.main import DeployCommand from .deploy.main import DeployCommand
from .evaluate_crew import evaluate_crew from .evaluate_crew import evaluate_crew
from .install_crew import install_crew from .install_crew import install_crew
from .kickoff_flow import kickoff_flow
from .plot_flow import plot_flow from .plot_flow import plot_flow
from .replay_from_task import replay_task_command from .replay_from_task import replay_task_command
from .reset_memories_command import reset_memories_command from .reset_memories_command import reset_memories_command
from .run_crew import run_crew from .run_crew import run_crew
from .run_flow import run_flow
from .tools.main import ToolCommand from .tools.main import ToolCommand
from .train_crew import train_crew from .train_crew import train_crew
from .update_crew import update_crew from .update_crew import update_crew
@@ -304,11 +304,11 @@ def flow():
pass pass
@flow.command(name="run") @flow.command(name="kickoff")
def flow_run(): def flow_run():
"""Run the Flow.""" """Kickoff the Flow."""
click.echo("Running the Flow") click.echo("Running the Flow")
run_flow() kickoff_flow()
@flow.command(name="plot") @flow.command(name="plot")

View File

@@ -3,11 +3,11 @@ import subprocess
import click import click
def run_flow() -> None: def kickoff_flow() -> None:
""" """
Run the flow by running a command in the UV environment. Kickoff the flow by running a command in the UV environment.
""" """
command = ["uv", "run", "run_flow"] command = ["uv", "run", "kickoff"]
try: try:
result = subprocess.run(command, capture_output=False, text=True, check=True) result = subprocess.run(command, capture_output=False, text=True, check=True)

View File

@@ -21,21 +21,16 @@ class PoemFlow(Flow[PoemState]):
@listen(generate_sentence_count) @listen(generate_sentence_count)
def generate_poem(self): def generate_poem(self):
print("Generating poem") print("Generating poem")
print(f"State before poem: {self.state}")
result = PoemCrew().crew().kickoff(inputs={"sentence_count": self.state.sentence_count}) result = PoemCrew().crew().kickoff(inputs={"sentence_count": self.state.sentence_count})
print("Poem generated", result.raw) print("Poem generated", result.raw)
self.state.poem = result.raw self.state.poem = result.raw
print(f"State after generate_poem: {self.state}")
@listen(generate_poem) @listen(generate_poem)
def save_poem(self): def save_poem(self):
print("Saving poem") print("Saving poem")
print(f"State before save_poem: {self.state}")
with open("poem.txt", "w") as f: with open("poem.txt", "w") as f:
f.write(self.state.poem) f.write(self.state.poem)
print(f"State after save_poem: {self.state}")
async def kickoff(): async def kickoff():
poem_flow = PoemFlow() poem_flow = PoemFlow()

View File

@@ -11,8 +11,8 @@ dependencies = [
[project.scripts] [project.scripts]
{{folder_name}} = "{{folder_name}}.main:kickoff" {{folder_name}} = "{{folder_name}}.main:kickoff"
run_flow = "{{folder_name}}.main:kickoff" kickoff = "{{folder_name}}.main:kickoff"
plot_flow = "{{folder_name}}.main:plot" plot = "{{folder_name}}.main:plot"
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]