Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature examples #299

Merged
merged 2 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions docs/examples/features/dependent-tasks.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
title: Dependent Tasks
description: Build complex workflows by indicating relationships between tasks.
icon: link
---
In this example, we'll explore how ControlFlow enables the creation of complex, hierarchical workflows using dependent tasks. We've chosen a text analysis scenario to demonstrate several powerful features of ControlFlow:

- Organizing tasks in a logical, nested structure
- Simplifying data flow through automatic context sharing
- Ensuring correct task execution order with dependencies
- Maintaining a shared context across all tasks in a workflow

As you examine the code, pay attention to how these concepts are implemented and how they contribute to creating a clear, efficient workflow structure.

## Code

<CodeGroup>
```python Code
import controlflow as cf

@cf.flow
def analyze_text(text: str):

# Create a parent task to represent the entire analysis
with cf.Task(
"Analyze the given text",
instructions="Include each subtask result in your result",
result_type=dict,
context={"text": text}
) as parent_task:

# Child task 1: Identify key terms
key_terms = cf.Task(
"Identify up to 10 key terms in the text",
result_type=list[str]
)

# Child task 2: Summarize (depends on key_terms)
summary = cf.Task(
"Summarize the text in one sentence",
result_type=str,
depends_on=[key_terms]
)

# Run the parent task, which will automatically run all child tasks
result = parent_task.run()
return result

# Execute the flow
text = """
Agentic workflow orchestration refers to the coordination of autonomous
agents within a structured workflow, allowing them to operate independently
while achieving a common objective. Unlike traditional workflows that rigidly
define tasks and dependencies, agentic workflows empower agents—typically
AI-driven—to make decisions, prioritize tasks, and collaborate dynamically.
Each agent in this system operates with a degree of autonomy, enabling it to
adapt to changing conditions, handle uncertainties, and optimize its own
actions within the broader workflow. This approach enhances flexibility and
scalability, making it particularly effective for complex, multi-step
processes where real-time adjustments and intelligent decision-making are
crucial. By leveraging agents with defined roles and responsibilities, agentic
workflows maintain structure while enabling innovation and responsiveness in
task execution.
"""

result = analyze_text(text)
print(result)
```
```python Result
{
'key_terms': [
'Agentic workflow orchestration',
'autonomous agents',
'structured workflow',
'independently',
'common objective',
'traditional workflows',
'tasks and dependencies',
'AI-driven',
'decisions',
'prioritize tasks'
],
'summary': """
Agentic workflow orchestration involves coordinating
autonomous agents within a structured workflow to operate independently
and dynamically collaborate, enhancing flexibility and scalability for
complex, multi-step processes.
"""
}
```
</CodeGroup>

## Key points
1. Task hierarchy: The parent task encompasses the entire analysis process, with child tasks handling specific aspects. This structure allows for logical organization of complex workflows.
2. Automatic context sharing: Child tasks have access to their parent's context without explicit passing, streamlining data flow within the workflow.
3. Dependencies: The depends_on parameter ensures tasks are executed in the correct order, as demonstrated by the summary task depending on the key terms task.
4. Flow context: By wrapping tasks in a flow, ControlFlow maintains a shared context across all tasks, including visibility into prior executions and conversation history.
5. Unified execution: Running the parent task automatically executes all child tasks in the correct order, simplifying workflow management.

## Further reading

- For more details on creating tasks and context sharing, see the [task documentation](/concepts/tasks).
- To learn more about defining dependencies between tasks, check out the [dependencies guide](/patterns/dependencies).
- For information on how ControlFlow manages task execution and context, refer to the [running tasks guide](/patterns/running-tasks).

By leveraging these features, you can create complex workflows that maintain a clear structure and ensure efficient information flow between tasks. This approach helps in building more maintainable and scalable AI-powered applications with minimal boilerplate code.
68 changes: 68 additions & 0 deletions docs/examples/features/private-flows.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
title: Private flows
description: Create isolated execution environments within your workflows.
icon: lock
---

Nested flows in ControlFlow allow you to create isolated threads within a larger workflow. This feature is particularly useful when you need to perform operations that shouldn't affect or be visible to the main flow, or when you want to encapsulate a set of tasks for modularity or security reasons.

In this example, we'll demonstrate how to use private flows to process sensitive information without exposing it to the main workflow. In a real-world version of this, you might use a locally-hosted LLM or private API for the sensitive data processing.

## Code

<CodeGroup>
```python Code
import controlflow as cf

@cf.flow(args_as_context=False)
def process_user_data(user_name: str, sensitive_info: str):
# Main flow context
print(f"Processing data for user: {user_name}")

# Create a private flow to handle sensitive information
with cf.Flow() as private_flow:
# This task runs in an isolated context
masked_info = cf.run(
"Mask the sensitive information",
context={"sensitive_info": sensitive_info},
result_type=str
)

# Task in the main flow can be provided the masked_info as context
summary = cf.run(
"Summarize the data processing result",
context={"user_name": user_name, "masked_info": masked_info},
result_type=str
)

return summary

# Execute the flow
result = process_user_data("Alice", "SSN: 123-45-6789")
print(result)
```
```text Result
The data processing for user Alice has been successfully completed. The sensitive information has been masked appropriately. Here are the details:

- User Name: Alice
- Masked Information: SSN: XXX-XX-6789
```
</CodeGroup>

## Key points

1. **Isolation**: Private flows create an isolated execution environment. Tasks within a private flow cannot access or modify the context of the parent flow directly.

2. **Data encapsulation**: Sensitive information (`sensitive_info`) is only available within the private flow, protecting it from accidental exposure in the main workflow.

3. **Context control**: By setting `args_as_context=False`, we can pass the sensitive information to the flow function without adding it to context automatically.

4. **Result passing**: Results from the private flow (like `masked_info`) can be explicitly passed back to the main flow for further processing.

5. **Nested structure**: Private flows can be nested within larger workflows, allowing for modular and secure task organization.

## Further reading

- To learn more about flows in ControlFlow, see the [flows documentation](/concepts/flows).

By using private flows, you can create more secure and modular workflows, especially when dealing with sensitive information or when you need to isolate certain operations from the main workflow context.
148 changes: 148 additions & 0 deletions docs/examples/language-tutor.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
title: Interactive Language Tutor
description: Create an engaging AI tutor for language learning using ControlFlow
icon: graduation-cap
---

This example demonstrates how to use ControlFlow to create a simple yet interactive language learning assistant. It showcases the use of a custom agent, user interaction, and a flexible learning flow.

## Code

The following code creates a basic language learning session with an AI tutor:

```python
import controlflow as cf
from pydantic import BaseModel

class Lesson(BaseModel):
topic: str
content: str
exercises: list[str]

def language_learning_session(language: str) -> None:
tutor = cf.Agent(
name="Tutor",
instructions="""
You are a friendly and encouraging language tutor. Your goal is to create an
engaging and supportive learning environment. Always maintain a warm tone,
offer praise for efforts, and provide gentle corrections. Adapt your teaching
style to the user's needs and pace. Use casual language to keep the
conversation light and fun. When working through exercises:
- Present one exercise at a time.
- Provide hints if the user is struggling.
- Offer the correct answer if the user can't solve it after a few attempts.
- Use encouraging language throughout the process.
"""
)

@cf.flow(agent=tutor)
def learning_flow():
cf.run(
f"Greet the user, learn their name,and introduce the {language} learning session",
interactive=True
)

while True:
lesson = cf.run(
"Create a fun and engaging language lesson",
result_type=Lesson
)

cf.run(
"Present the lesson content to the user in an interactive and engaging way",
interactive=True,
context={"lesson": lesson}
)

for exercise in lesson.exercises:
cf.run(
"Work through the exercise with the user",
interactive=True,
context={"exercise": exercise}
)

continue_learning = cf.run(
"Check if the user wants to continue learning",
result_type=bool,
interactive=True
)

if not continue_learning:
break

cf.run(
"Summarize the learning session and provide encouragement",
interactive=True
)

learning_flow()

# Example usage
language_learning_session("French")
```

## Key Concepts

This implementation showcases several important ControlFlow features and concepts:

1. **Custom Agent**: We define a tutor agent with specific instructions on how to interact with the user. This allows for a consistent and engaging teaching style throughout the session.

```python
tutor = cf.Agent(
name="Tutor",
instructions="""
You are a friendly and encouraging language tutor...
"""
)
```

2. **Flow-level Agent Assignment**: We assign the tutor agent to the entire flow, eliminating the need to specify it for each task.

```python
@cf.flow(agent=tutor)
def learning_flow():
...
```

3. **Interactive Tasks**: We use the `interactive=True` parameter for tasks that require user interaction. This allows the AI tutor to engage directly with the user.

```python
cf.run(
"Work through the exercise with the user",
interactive=True,
context={"exercise": exercise}
)
```

4. **Flexible Flow Control**: The learning session uses a while loop with a condition checked after each lesson. This allows the session to continue as long as the user wants to keep learning.

```python
while True:
# ... lesson content ...
continue_learning = cf.run(
"Check if the user wants to continue learning",
result_type=bool,
interactive=True
)
if not continue_learning:
break
```

5. **Context Passing**: We pass the `lesson` and `exercise` objects as context to relevant tasks. This allows the AI tutor to have access to the current lesson content.

```python
context={"lesson": lesson}
```

6. **Structured Data Models**: We use a Pydantic model (`Lesson`) to define the structure of our lesson data. This ensures that the data passed between tasks is well-structured and type-safe.

```python
class Lesson(BaseModel):
topic: str
content: str
exercises: list[str]
```

By leveraging these ControlFlow features, we create a simple yet engaging language learning assistant. This example demonstrates how to build interactive AI workflows that can respond to user input and adapt their behavior based on the user's choices.

The simplicity of this implementation allows for easy expansion. Users could extend this example by adding more complex lesson structures, implementing progress tracking, or incorporating additional language learning features like vocabulary reviews or grammar explanations.
10 changes: 9 additions & 1 deletion docs/mint.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,16 @@
]
},
{
"group": "Agentic",
"group": "ControlFlow Features",
"pages": [
"examples/features/dependent-tasks",
"examples/features/private-flows"
]
},
{
"group": "Agentic Flows",
"pages": [
"examples/language-tutor",
"examples/rock-paper-scissors",
"examples/agent-engineer",
"examples/call-routing"
Expand Down
8 changes: 6 additions & 2 deletions src/controlflow/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def flow(
retry_delay_seconds: Optional[Union[float, int]] = None,
timeout_seconds: Optional[Union[float, int]] = None,
prefect_kwargs: Optional[dict[str, Any]] = None,
args_as_context: Optional[bool] = True,
**kwargs: Optional[dict[str, Any]],
):
"""
Expand All @@ -44,7 +45,7 @@ def flow(
instructions (str, optional): Instructions for the flow. Defaults to None.
tools (list[Callable], optional): List of tools to be used in the flow. Defaults to None.
agents (list[Agent], optional): List of agents to be used in the flow. Defaults to None.

args_as_context (bool, optional): Whether to pass the arguments as context to the flow. Defaults to True.
Returns:
callable: The wrapped function or a new flow decorator if `fn` is not provided.
"""
Expand All @@ -60,6 +61,7 @@ def flow(
retries=retries,
retry_delay_seconds=retry_delay_seconds,
timeout_seconds=timeout_seconds,
args_as_context=args_as_context,
**kwargs,
)

Expand Down Expand Up @@ -91,11 +93,13 @@ def wrapper(
if agents is not None:
flow_kwargs.setdefault("agents", agents)

context = bound.arguments if args_as_context else {}

with (
Flow(
name=fn.__name__,
description=fn.__doc__,
context=bound.arguments,
context=context,
**flow_kwargs,
),
controlflow.instructions(instructions),
Expand Down