diff --git a/contributing/samples/local_environment_skill/README.md b/contributing/samples/local_environment_skill/README.md new file mode 100644 index 0000000000..22ba2fa016 --- /dev/null +++ b/contributing/samples/local_environment_skill/README.md @@ -0,0 +1,24 @@ +# Local Environment Skill Sample + +This sample demonstrates how to use the `LocalEnvironment` with the `EnvironmentToolset` to allow an agent to manually discover and load skills from the environment, rather than using the pre-configured `SkillToolset`. + +## Description + +The agent is configured with the `EnvironmentToolset` and is initialized with a `LocalEnvironment` pointing to the agent's directory. +Instead of having skills pre-loaded, the agent uses system instructions that guide it to search for skills in the `skills/` folder and load them by reading their `SKILL.md` files using the `ReadFile` tool. + +This demonstrates a "manual skill loading" pattern where the agent can acquire new capabilities dynamically by reading instructions from the environment. + +## Sample Usage + +You can interact with the agent by providing prompts that require a specific skill (like weather). + +### Example Prompt + +> "Can you check the weather in Sunnyvale?" + +### Expected Behavior + +1. **Find Skill**: The agent uses the `Execute` tool to search for all available skills by running `find skills -name SKILL.md`. +2. **Load Skill**: The agent identifies the relevant skill and uses the `ReadFile` tool to read its `SKILL.md` file. +3. **Execute Skill**: The agent follows the instructions in the skill file (e.g., reading references or running scripts) to answer the user's request. diff --git a/contributing/samples/local_environment_skill/__init__.py b/contributing/samples/local_environment_skill/__init__.py new file mode 100644 index 0000000000..4015e47d6e --- /dev/null +++ b/contributing/samples/local_environment_skill/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2026 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/local_environment_skill/agent.py b/contributing/samples/local_environment_skill/agent.py new file mode 100644 index 0000000000..aa07cc6a1e --- /dev/null +++ b/contributing/samples/local_environment_skill/agent.py @@ -0,0 +1,95 @@ +# Copyright 2026 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. +import pathlib + +from google.adk import Agent +from google.adk.environment import LocalEnvironment +from google.adk.tools.base_tool import BaseTool +from google.adk.tools.environment import EnvironmentToolset +from google.genai import types + + +class GetTimezoneTool(BaseTool): + """A tool to get the timezone for a given location.""" + + def __init__(self): + super().__init__( + name="get_timezone", + description="Returns the timezone for a given location.", + ) + + def _get_declaration(self) -> types.FunctionDeclaration | None: + return types.FunctionDeclaration( + name=self.name, + description=self.description, + parameters_json_schema={ + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The location to get the timezone for.", + }, + }, + "required": ["location"], + }, + ) + + async def run_async(self, *, args: dict, tool_context) -> str: + return f"The timezone for {args['location']} is UTC+00:00." + + +def get_wind_speed(location: str) -> str: + """Returns the current wind speed for a given location.""" + return f"The wind speed in {location} is 10 mph." + + +BASE_INSTRUCTION = ( + "You are a helpful AI assistant that can use the local environment to" + " execute commands and file I/O." +) + +SKILL_USAGE_INSTRUCTION = """\ +[SKILLS ACCESS] +You have access to specialized skills stored in the environment's `skills/` folder. +Each skill is a folder containing a `SKILL.md` file with instructions. + +[MANDATORY PROCEDURE] +Before declaring that you cannot perform a task or answer a question (especially for domain-specific queries like weather), you MUST: +1. Use the `Execute` tool to search for all available skills by running: `find skills -name SKILL.md` +2. Review the list of found skills to see if any are relevant to the user's request. +3. If a relevant skill is found, use the `ReadFile` tool to read its `SKILL.md` file. +4. Follow the instructions in that file to complete the request. + *CRITICAL NOTE ON PATHS:* All file and script paths mentioned inside a `SKILL.md` file (e.g., `references/...` or `scripts/...`) are RELATIVE to that specific skill's folder. You MUST resolve them by prepending the skill's folder path (e.g., if the skill is at `skills/weather-skill/`, you must read `skills/weather-skill/references/weather_info.md`). + +Failure to check the `skills/` directory before stating you cannot help is unacceptable.\ +""" + + +root_agent = Agent( + model="gemini-2.5-pro", + name="local_environment_skill_agent", + description=( + "An agent that uses local environment tools to load and use skills." + ), + instruction=f"{BASE_INSTRUCTION}\n\n{SKILL_USAGE_INSTRUCTION}", + tools=[ + EnvironmentToolset( + environment=LocalEnvironment( + working_dir=pathlib.Path(__file__).parent + ), + ), + GetTimezoneTool(), + get_wind_speed, + ], +) diff --git a/contributing/samples/local_environment_skill/skills/weather-skill/SKILL.md b/contributing/samples/local_environment_skill/skills/weather-skill/SKILL.md new file mode 100644 index 0000000000..0323c18bfb --- /dev/null +++ b/contributing/samples/local_environment_skill/skills/weather-skill/SKILL.md @@ -0,0 +1,12 @@ +--- +name: weather-skill +description: A skill that provides weather information based on reference data. +metadata: + adk_additional_tools: + - get_wind_speed +--- + +Step 1: Check 'references/weather_info.md' for the current weather. +Step 2: If humidity is requested, use run 'scripts/get_humidity.py' with the `location` argument. +Step 3: If wind speed is requested, use the `get_wind_speed` tool. +Step 4: Provide the update to the user. diff --git a/contributing/samples/local_environment_skill/skills/weather-skill/references/weather_info.md b/contributing/samples/local_environment_skill/skills/weather-skill/references/weather_info.md new file mode 100644 index 0000000000..dc163470bc --- /dev/null +++ b/contributing/samples/local_environment_skill/skills/weather-skill/references/weather_info.md @@ -0,0 +1,11 @@ +# Weather Information + +- **Location:** San Francisco, CA +- **Condition:** Sunny ☀️ +- **Temperature:** 72°F (22°C) +- **Forecast:** Clear skies all day. + +- **Location:** Sunnyvale, CA +- **Condition:** Sunny ☀️ +- **Temperature:** 75°F (24°C) +- **Forecast:** Warm and sunny. diff --git a/contributing/samples/local_environment_skill/skills/weather-skill/scripts/get_humidity.py b/contributing/samples/local_environment_skill/skills/weather-skill/scripts/get_humidity.py new file mode 100644 index 0000000000..a2e1dc4701 --- /dev/null +++ b/contributing/samples/local_environment_skill/skills/weather-skill/scripts/get_humidity.py @@ -0,0 +1,29 @@ +# Copyright 2026 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. + +import argparse + + +def get_humidity(location: str) -> str: + """Fetch live humidity for a given location. (Simulated)""" + print(f"Fetching live humidity for {location}...") + return "45% (Simulated)" + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--location", type=str, default="Mountain View") + args = parser.parse_args() + + print(get_humidity(args.location))