From c81d2f1c2415102ca7fa1d94ad43dfb59516c463 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:36:38 -0400 Subject: [PATCH] Add feature examples --- docs/examples/features/dependent-tasks.mdx | 106 +++++++++++++++ docs/examples/features/private-flows.mdx | 68 ++++++++++ docs/examples/language-tutor.mdx | 148 +++++++++++++++++++++ docs/mint.json | 8 ++ src/controlflow/decorators.py | 8 +- 5 files changed, 336 insertions(+), 2 deletions(-) create mode 100644 docs/examples/features/dependent-tasks.mdx create mode 100644 docs/examples/features/private-flows.mdx create mode 100644 docs/examples/language-tutor.mdx diff --git a/docs/examples/features/dependent-tasks.mdx b/docs/examples/features/dependent-tasks.mdx new file mode 100644 index 0000000..7507869 --- /dev/null +++ b/docs/examples/features/dependent-tasks.mdx @@ -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 + + +```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. + """ +} +``` + + +## 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. \ No newline at end of file diff --git a/docs/examples/features/private-flows.mdx b/docs/examples/features/private-flows.mdx new file mode 100644 index 0000000..e0c0db4 --- /dev/null +++ b/docs/examples/features/private-flows.mdx @@ -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 + + +```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 +``` + + +## 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. \ No newline at end of file diff --git a/docs/examples/language-tutor.mdx b/docs/examples/language-tutor.mdx new file mode 100644 index 0000000..2b9d581 --- /dev/null +++ b/docs/examples/language-tutor.mdx @@ -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. \ No newline at end of file diff --git a/docs/mint.json b/docs/mint.json index 5d41ca9..5bbb458 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -84,9 +84,17 @@ "examples/code-explanation" ] }, + { + "group": "ControlFlow Features", + "pages": [ + "examples/features/dependent-tasks", + "examples/features/private-flows" + ] + }, { "group": "Agentic", "pages": [ + "examples/language-tutor", "examples/rock-paper-scissors", "examples/agent-engineer", "examples/call-routing" diff --git a/src/controlflow/decorators.py b/src/controlflow/decorators.py index 2338c37..42b9a99 100644 --- a/src/controlflow/decorators.py +++ b/src/controlflow/decorators.py @@ -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]], ): """ @@ -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. """ @@ -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, ) @@ -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),