diff --git a/docs/agents/custom-agents.md b/docs/agents/custom-agents.md index 84cd2d401..90cf3f283 100644 --- a/docs/agents/custom-agents.md +++ b/docs/agents/custom-agents.md @@ -80,72 +80,7 @@ Let's illustrate the power of custom agents with an example pattern: a multi-sta We define the `StoryFlowAgent` inheriting from `BaseAgent`. In `__init__`, we store the necessary sub-agents (passed in) as instance attributes and tell the `BaseAgent` framework about the top-level agents this custom agent will directly orchestrate. ```python -# agent.py (Initialization part) -import logging -from typing import AsyncGenerator -from typing_extensions import override - -from google.adk.agents import Agent, LlmAgent, BaseAgent, LoopAgent, SequentialAgent -from google.adk.agents.invocation_context import InvocationContext -from google.adk.events import Event - -logger = logging.getLogger(__name__) - -class StoryFlowAgent(BaseAgent): - """ - custom agent demonstrating a conditional story generation workflow. - Illustrates using custom logic to orchestrate LLM, Loop, and Sequential agents. - """ - # --- Field Declarations for Clarity (Optional but good practice) --- - story_generator: LlmAgent - # loop_agent and sequential_agent are created internally but used directly - loop_agent: LoopAgent - sequential_agent: SequentialAgent - - model_config = {"arbitrary_types_allowed": True} # Allow agent types - - def __init__( - self, - name: str, - story_generator: LlmAgent, # Generates initial/regenerated story - critic: LlmAgent, # Critiques story (used by loop) - reviser: LlmAgent, # Revises story (used by loop) - grammar_check: LlmAgent, # Checks grammar (used by sequence) - tone_check: LlmAgent, # Checks tone (used by sequence) - ): - # Store agents needed by _run_async_impl - self.story_generator = story_generator - - # Create internal workflow agents used by our custom logic - self.loop_agent = LoopAgent( - name="CriticReviserLoop", sub_agents=[critic, reviser], max_iterations=2 - ) - self.sequential_agent = SequentialAgent( - name="PostProcessing", sub_agents=[grammar_check, tone_check] - ) - - # Define the list of agents directly orchestrated by _run_async_impl - # This list informs the ADK framework. - framework_sub_agents = [ - self.story_generator, - self.loop_agent, - self.sequential_agent, - # critic, reviser, grammar_check, tone_check are managed *within* - # the loop_agent and sequential_agent, so not listed here directly. - ] - - # Initialize BaseAgent, telling it about the direct sub-agents - super().__init__( - name=name, - sub_agents=framework_sub_agents, - # Pydantic automatically assigns the passed-in agents to the declared fields - story_generator=story_generator, - critic=critic, # Still pass these if needed for Pydantic validation/typing - reviser=reviser, - grammar_check=grammar_check, - tone_check=tone_check, - ) - +--8<-- "examples/python/snippets/agents/custom-agent/storyflow_agent.py:init" ``` --- @@ -155,53 +90,7 @@ class StoryFlowAgent(BaseAgent): This method orchestrates the sub-agents using standard Python async/await and control flow. ```python -# agent.py (_run_async_impl part, inside StoryFlowAgent class) - - @override - async def _run_async_impl( - self, ctx: InvocationContext - ) -> AsyncGenerator[Event, None]: - """Implements the custom story workflow logic.""" - logger.info(f"[{self.name}] Starting story generation workflow.") - - # --- Step 1: Initial Story Generation --- - logger.info(f"[{self.name}] Running StoryGenerator...") - async for event in self.story_generator.run_async(ctx): - yield event # Pass events up - - # Check state: Ensure story was generated before proceeding - if "current_story" not in ctx.session.state or not ctx.session.state["current_story"]: - logger.error(f"[{self.name}] Failed to generate initial story. Aborting.") - return - - # --- Step 2: Critic-Reviser Loop --- - # Use the pre-configured LoopAgent instance - logger.info(f"[{self.name}] Running CriticReviserLoop...") - async for event in self.loop_agent.run_async(ctx): - yield event - - # --- Step 3: Sequential Post-Processing --- - # Use the pre-configured SequentialAgent instance - logger.info(f"[{self.name}] Running PostProcessing (Grammar & Tone)...") - async for event in self.sequential_agent.run_async(ctx): - yield event - - # --- Step 4: Conditional Logic based on Tone Check --- - # Read the result stored in state by the 'tone_check' agent - tone_check_result = ctx.session.state.get("tone_check_result") - logger.info(f"[{self.name}] Tone check result: {tone_check_result}") - - if str(tone_check_result).strip().lower() == "negative": # Check tone - logger.warning(f"[{self.name}] Tone is negative. Regenerating story...") - # Re-run the story generator if tone is negative - async for event in self.story_generator.run_async(ctx): - yield event - else: - logger.info(f"[{self.name}] Tone is acceptable. Keeping current story.") - # Optionally yield a final status event here if needed - - logger.info(f"[{self.name}] Workflow finished.") - +--8<-- "examples/python/snippets/agents/custom-agent/storyflow_agent.py:executionlogic" ``` **Explanation of Logic:** @@ -218,39 +107,8 @@ This method orchestrates the sub-agents using standard Python async/await and co These are standard `LlmAgent` definitions, responsible for specific tasks. Their `output_key` parameter is crucial for placing results into the `session.state` where other agents or the custom orchestrator can access them. ```python -# agent.py (LLM Agent Definitions part) - GEMINI_FLASH = "gemini-2.0-flash-exp" # Define model constant - -story_generator = LlmAgent( - name="StoryGenerator", model=GEMINI_FLASH, - instruction="Write a short story (~100 words) about the topic in state['topic'].", - output_key="current_story", -) - -critic = LlmAgent( - name="Critic", model=GEMINI_FLASH, - instruction="Review the story in state['current_story']. Provide 1-2 sentences of constructive criticism.", - output_key="criticism", -) - -reviser = LlmAgent( - name="Reviser", model=GEMINI_FLASH, - instruction="Revise the story in state['current_story'] based on criticism in state['criticism']. Output only the revised story.", - output_key="current_story", # Overwrites previous story -) - -grammar_check = LlmAgent( - name="GrammarCheck", model=GEMINI_FLASH, - instruction="Check grammar of story in state['current_story']. Output corrections or 'Grammar is good!'.", - output_key="grammar_suggestions", -) - -tone_check = LlmAgent( - name="ToneCheck", model=GEMINI_FLASH, - instruction="Analyze tone of story in state['current_story']. Output one word: 'positive', 'negative', or 'neutral'.", - output_key="tone_check_result", # Crucial for conditional logic -) +--8<-- "examples/python/snippets/agents/custom-agent/storyflow_agent.py:llmagents" ``` --- @@ -260,59 +118,7 @@ tone_check = LlmAgent( Finally, you instantiate your `StoryFlowAgent` and use the `Runner` as usual. ```python -# agent.py (Instantiation and Runner Setup part) -from google.adk.sessions import InMemorySessionService -from google.adk.runners import Runner -from google.genai import types -import asyncio # Import asyncio - -# Create the custom agent instance, passing in the LLM agents -story_flow_agent = StoryFlowAgent( - name="StoryFlowOrchestrator", # Give the orchestrator a distinct name - story_generator=story_generator, - critic=critic, - reviser=reviser, - grammar_check=grammar_check, - tone_check=tone_check, -) - -# --- Setup Runner and Session (Example) --- -async def run_story_agent(): - session_service = InMemorySessionService() - initial_state = {"topic": "a curious squirrel discovering sunglasses"} - session_id = "story_session_001" - user_id = "user_abc" - app_name = "story_creator_app" - - session = session_service.create_session( - app_name=app_name, user_id=user_id, session_id=session_id, state=initial_state - ) - logger.info(f"Initial session state: {session.state}") - - runner = Runner( - agent=story_flow_agent, # Use the custom orchestrator agent - app_name=app_name, - session_service=session_service - ) - - # Trigger the agent (content here is just a trigger, actual topic is in state) - start_content = types.Content(role='user', parts=[types.Part(text="Start story flow.")]) - events = [] - async for event in runner.run_async(user_id=user_id, session_id=session_id, new_message=start_content): - events.append(event) - # You can inspect events here as they are yielded - - print("\n--- Agent Workflow Complete ---") - final_session = session_service.get_session(app_name, user_id, session_id) - print("Final Session State:") - import json - print(json.dumps(final_session.state, indent=2)) - print("-----------------------------\n") - -if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) # Ensure logging is configured - asyncio.run(run_story_agent()) - +--8<-- "examples/python/snippets/agents/custom-agent/storyflow_agent.py:story_flow_agent" ``` *(Note: The full runnable code, including imports and execution logic, can be found linked below.)* diff --git a/docs/tools/google-cloud-tools.md b/docs/tools/google-cloud-tools.md index e2bc01666..ea50b6741 100644 --- a/docs/tools/google-cloud-tools.md +++ b/docs/tools/google-cloud-tools.md @@ -242,7 +242,7 @@ When running the agent, make sure to run adk web in project\_root\_folder --- -## 3. **GenAI Toolbox** +## 3. **Toolbox Tools for Databases** [MCP Toolbox for Databases](https://github.com/googleapis/genai-toolbox) is an open source MCP server for databases. It was designed with enterprise-grade and production-quality in mind. It enables you to develop tools easier, faster, and more securely by handling the complexities such as connection pooling, authentication, and more. @@ -272,14 +272,18 @@ pip install toolbox-langchain Once you’ve Toolbox server is configured and up and running, you can load tools from your server using the ADK: ```py -from google.adk.tools import ToolboxTool toolbox = ToolboxTool("https://127.0.0.1:5000") +from google.adk.tools import ToolboxTool + +toolbox = ToolboxTool("https://127.0.0.1:5000") # Load all tools tools = toolbox.get_toolset(), # Load a specific set of tools tools = toolbox.get_toolset(toolset_name='my-toolset-name'), # Load single tool -tools = toolbox.get_toolset(tool_name='my-tool-name'), root_agent = Agent( +tools = toolbox.get_toolset(tool_name='my-tool-name'), + +root_agent = Agent( ..., tools=tools # Provide the list of tools to the Agent diff --git a/docs/tools/mcp-tools.md b/docs/tools/mcp-tools.md index e5c9acc48..9c7626a42 100644 --- a/docs/tools/mcp-tools.md +++ b/docs/tools/mcp-tools.md @@ -327,7 +327,7 @@ You will create a standard Python MCP server application using the model-context Install the MCP server library in the same environment as ADK: ```shell -pip install model-context-protocol +pip install mcp ``` ### Step 1: Create the MCP Server Script @@ -453,15 +453,36 @@ if __name__ == "__main__": ``` -### Step 3: Run the MCP Server +### Step 3: Test your MCP Server with ADK -Execute the script from your terminal (ensure necessary libraries like model-context-protocol and google-adk are installed in your environment): +Follow the same instructions in “Example 1: File System MCP Server” and create a MCP client. This time use your MCP Server file created above as input command: + +```py +# ./adk_agent_samples/mcp_agent/agent.py + +# ... + +async def get_tools_async(): + """Gets tools from the File System MCP Server.""" + print("Attempting to connect to MCP Filesystem server...") + tools, exit_stack = await MCPToolset.from_server( + # Use StdioServerParameters for local process communication + connection_params=StdioServerParameters( + command='python3', # Command to run the server + args=[ + "/absolute/path/to/adk_mcp_server.py"], + ) + ) +``` + +Execute the agent script from your terminal similar to above (ensure necessary libraries like model-context-protocol and google-adk are installed in your environment): ```shell -python adk_mcp_server.py +cd ./adk_agent_samples +python3 ./mcp_agent/agent.py ``` -The script will print startup messages and then wait for an MCP client to connect via its standard input/output. Any MCP-compliant client (like Claude Desktop, or a custom client using the MCP libraries) can now connect to this process, discover the load\_web\_page tool, and invoke it. The server will print log messages indicating received requests and ADK tool execution. Refer to the [documentation](https://modelcontextprotocol.io/quickstart/server#core-mcp-concepts), to try it out with Claude Desktop. +The script will print startup messages and then wait for an MCP client to connect via its standard input/output to your MCP Server in adk\_mcp\_server.py. Any MCP-compliant client (like Claude Desktop, or a custom client using the MCP libraries) can now connect to this process, discover the load\_web\_page tool, and invoke it. The server will print log messages indicating received requests and ADK tool execution. Refer to the [documentation](https://modelcontextprotocol.io/quickstart/server#core-mcp-concepts), to try it out with Claude Desktop. ## Key considerations diff --git a/examples/python/snippets/agents/custom-agent/storyflow_agent.py b/examples/python/snippets/agents/custom-agent/storyflow_agent.py index 0cbceddad..321a7d74e 100644 --- a/examples/python/snippets/agents/custom-agent/storyflow_agent.py +++ b/examples/python/snippets/agents/custom-agent/storyflow_agent.py @@ -93,7 +93,7 @@ def __init__( sequential_agent=sequential_agent, sub_agents=sub_agents_list, # Pass the sub_agents list directly ) - # --8<-- [end:init] +# --8<-- [end:init] # --8<-- [start:executionlogic] @override @@ -215,7 +215,6 @@ async def _run_async_impl( grammar_check=grammar_check, tone_check=tone_check, ) -# --8<-- [end:story_flow_agent] # --- Setup Runner and Session --- session_service = InMemorySessionService() @@ -271,4 +270,5 @@ def call_agent(user_input_topic: str): print("-------------------------------\n") # --- Run the Agent --- -call_agent("a lonely robot finding a friend in a junkyard") \ No newline at end of file +call_agent("a lonely robot finding a friend in a junkyard") +# --8<-- [end:story_flow_agent] \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 59037a00e..cde8f004f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -141,7 +141,6 @@ nav: - Local testing: deploy/local-testing.md - Agent Engine: deploy/agent-engine.md - Cloud Run: deploy/cloud-run.md - - Docker: deploy/docker.md - Guides: - Evaluate agents: guides/evaluate-agents.md - Responsible Agents: guides/responsible-agents.md