diff --git a/contributing/samples/workflow_triage/README.md b/contributing/samples/workflow_triage/README.md new file mode 100644 index 00000000..ead5e479 --- /dev/null +++ b/contributing/samples/workflow_triage/README.md @@ -0,0 +1,108 @@ +# Workflow Triage Sample + +This sample demonstrates how to build a multi-agent workflow that intelligently triages incoming requests and delegates them to appropriate specialized agents. + +## Overview + +The workflow consists of three main components: + +1. **Execution Manager Agent** (`agent.py`) - Analyzes user input and determines which execution agents are relevant +2. **Plan Execution Agent** - Sequential agent that coordinates execution and summarization +3. **Worker Execution Agents** (`execution_agent.py`) - Specialized agents that execute specific tasks in parallel + +## Architecture + +### Execution Manager Agent (`root_agent`) +- **Model**: gemini-2.5-flash +- **Name**: `execution_manager_agent` +- **Role**: Analyzes user requests and updates the execution plan +- **Tools**: `update_execution_plan` - Updates which execution agents should be activated +- **Sub-agents**: Delegates to `plan_execution_agent` for actual task execution +- **Clarification**: Asks for clarification if user intent is unclear before proceeding + +### Plan Execution Agent +- **Type**: SequentialAgent +- **Name**: `plan_execution_agent` +- **Components**: + - `worker_parallel_agent` (ParallelAgent) - Runs relevant agents in parallel + - `execution_summary_agent` - Summarizes the execution results + +### Worker Agents +The system includes two specialized execution agents that run in parallel: + +- **Code Agent** (`code_agent`): Handles code generation tasks + - Uses `before_agent_callback_check_relevance` to skip if not relevant + - Output stored in `code_agent_output` state key +- **Math Agent** (`math_agent`): Performs mathematical calculations + - Uses `before_agent_callback_check_relevance` to skip if not relevant + - Output stored in `math_agent_output` state key + +### Execution Summary Agent +- **Model**: gemini-2.5-flash +- **Name**: `execution_summary_agent` +- **Role**: Summarizes outputs from all activated agents +- **Dynamic Instructions**: Generated based on which agents were activated +- **Content Inclusion**: Set to "none" to focus on summarization + +## Key Features + +- **Dynamic Agent Selection**: Automatically determines which agents are needed based on user input +- **Parallel Execution**: Multiple relevant agents can work simultaneously via `ParallelAgent` +- **Relevance Filtering**: Agents skip execution if they're not relevant to the current state using callback mechanism +- **Stateful Workflow**: Maintains execution state through `ToolContext` +- **Execution Summarization**: Automatically summarizes results from all activated agents +- **Sequential Coordination**: Uses `SequentialAgent` to ensure proper execution flow + +## Usage + +The workflow follows this pattern: + +1. User provides input to the root agent (`execution_manager_agent`) +2. Manager analyzes the request and identifies relevant agents (`code_agent`, `math_agent`) +3. If user intent is unclear, manager asks for clarification before proceeding +4. Manager updates the execution plan using `update_execution_plan` +5. Control transfers to `plan_execution_agent` +6. `worker_parallel_agent` (ParallelAgent) runs only relevant agents based on the updated plan +7. `execution_summary_agent` summarizes the results from all activated agents + +### Example Queries + +**Vague requests requiring clarification:** + +``` +> hi +> Help me do this. +``` + +The root agent (`execution_manager_agent`) will greet the user and ask for clarification about their specific task. + +**Math-only requests:** + +``` +> What's 1+1? +``` + +Only the `math_agent` executes while `code_agent` is skipped. + +**Multi-domain requests:** + +``` +> What's 1+11? Write a python function to verify it. +``` + +Both `code_agent` and `math_agent` execute in parallel, followed by summarization. + +## Available Execution Agents + +- `code_agent` - For code generation and programming tasks +- `math_agent` - For mathematical computations and analysis + +## Implementation Details + +- Uses Google ADK agents framework +- Implements callback-based relevance checking via `before_agent_callback_check_relevance` +- Maintains state through `ToolContext` and state keys +- Supports parallel agent execution with `ParallelAgent` +- Uses `SequentialAgent` for coordinated execution flow +- Dynamic instruction generation for summary agent based on activated agents +- Agent outputs stored in state with `{agent_name}_output` keys diff --git a/contributing/samples/workflow_triage/__init__.py b/contributing/samples/workflow_triage/__init__.py new file mode 100755 index 00000000..c48963cd --- /dev/null +++ b/contributing/samples/workflow_triage/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import agent diff --git a/contributing/samples/workflow_triage/agent.py b/contributing/samples/workflow_triage/agent.py new file mode 100755 index 00000000..88a863d9 --- /dev/null +++ b/contributing/samples/workflow_triage/agent.py @@ -0,0 +1,57 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from google.adk.agents.llm_agent import Agent +from google.adk.tools.tool_context import ToolContext + +from . import execution_agent + + +def update_execution_plan( + execution_agents: list[str], tool_context: ToolContext +) -> str: + """Updates the execution plan for the agents to run.""" + + tool_context.state["execution_agents"] = execution_agents + return "execution_agents updated." + + +root_agent = Agent( + model="gemini-2.5-flash", + name="execution_manager_agent", + instruction="""\ +You are the Execution Manager Agent, responsible for setting up execution plan and delegate to plan_execution_agent for the actual plan execution. + +You ONLY have the following worker agents: `code_agent`, `math_agent`. + +You should do the following: + +1. Analyze the user input and decide any worker agents that are relevant; +2. If none of the worker agents are relevant, you should explain to user that no relevant agents are available and ask for something else; +2. Update the execution plan with the relevant worker agents using `update_execution_plan` tool. +3. Transfer control to the plan_execution_agent for the actual plan execution. + +When calling the `update_execution_plan` tool, you should pass the list of worker agents that are relevant to user's input. + +NOTE: + +* If you are not clear about user's intent, you should ask for clarification first; +* Only after you're clear about user's intent, you can proceed to step #2. +""", + sub_agents=[ + execution_agent.plan_execution_agent, + ], + tools=[update_execution_plan], +) diff --git a/contributing/samples/workflow_triage/execution_agent.py b/contributing/samples/workflow_triage/execution_agent.py new file mode 100644 index 00000000..2f3f1140 --- /dev/null +++ b/contributing/samples/workflow_triage/execution_agent.py @@ -0,0 +1,119 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from typing import Optional + +from google.adk.agents import Agent +from google.adk.agents import ParallelAgent +from google.adk.agents.base_agent import BeforeAgentCallback +from google.adk.agents.callback_context import CallbackContext +from google.adk.agents.readonly_context import ReadonlyContext +from google.adk.agents.sequential_agent import SequentialAgent +from google.genai import types + + +def before_agent_callback_check_relevance( + agent_name: str, +) -> BeforeAgentCallback: + """Callback to check if the state is relevant before executing the agent.""" + + def callback(callback_context: CallbackContext) -> Optional[types.Content]: + """Check if the state is relevant.""" + if agent_name not in callback_context.state["execution_agents"]: + return types.Content( + parts=[ + types.Part( + text=( + f"Skipping execution agent {agent_name} as it is not" + " relevant to the current state." + ) + ) + ] + ) + + return callback + + +code_agent = Agent( + model="gemini-2.5-flash", + name="code_agent", + instruction="""\ +You are the Code Agent, responsible for generating code. + +NOTE: You should only generate code and ignore other askings from the user. +""", + before_agent_callback=before_agent_callback_check_relevance("code_agent"), + output_key="code_agent_output", +) + +math_agent = Agent( + model="gemini-2.5-flash", + name="math_agent", + instruction="""\ +You are the Math Agent, responsible for performing mathematical calculations. + +NOTE: You should only perform mathematical calculations and ignore other askings from the user. +""", + before_agent_callback=before_agent_callback_check_relevance("math_agent"), + output_key="math_agent_output", +) + + +worker_parallel_agent = ParallelAgent( + name="worker_parallel_agent", + sub_agents=[ + code_agent, + math_agent, + ], +) + + +def instruction_provider_for_execution_summary_agent( + readonly_context: ReadonlyContext, +) -> str: + """Provides the instruction for the execution agent.""" + activated_agents = readonly_context.state["execution_agents"] + prompt = f"""\ +You are the Execution Summary Agent, responsible for summarizing the execution of the plan in the current invocation. + +In this invocation, the following agents were involved: {', '.join(activated_agents)}. + +Below are their outputs: +""" + for agent_name in activated_agents: + output = readonly_context.state.get(f"{agent_name}_output", "") + prompt += f"\n\n{agent_name} output:\n{output}" + + prompt += ( + "\n\nPlease summarize the execution of the plan based on the above" + " outputs." + ) + return prompt.strip() + + +execution_summary_agent = Agent( + model="gemini-2.5-flash", + name="execution_summary_agent", + instruction=instruction_provider_for_execution_summary_agent, + include_contents="none", +) + +plan_execution_agent = SequentialAgent( + name="plan_execution_agent", + sub_agents=[ + worker_parallel_agent, + execution_summary_agent, + ], +)