diff --git a/assets/adk-web-dev-ui-function-call.png b/assets/adk-web-dev-ui-function-call.png index 61d3afce..ef12092d 100644 Binary files a/assets/adk-web-dev-ui-function-call.png and b/assets/adk-web-dev-ui-function-call.png differ diff --git a/contributing/samples/artifact_save_text/agent.py b/contributing/samples/artifact_save_text/agent.py index 6cf73213..1f62dcca 100755 --- a/contributing/samples/artifact_save_text/agent.py +++ b/contributing/samples/artifact_save_text/agent.py @@ -24,7 +24,7 @@ async def log_query(tool_context: ToolContext, query: str): root_agent = Agent( - model='gemini-2.0-flash-exp', + model='gemini-2.0-flash', name='log_agent', description='Log user query.', instruction="""Always log the user query and reploy "kk, I've logged." diff --git a/contributing/samples/bigquery_agent/agent.py b/contributing/samples/bigquery_agent/agent.py index adf11934..976cea17 100644 --- a/contributing/samples/bigquery_agent/agent.py +++ b/contributing/samples/bigquery_agent/agent.py @@ -16,7 +16,7 @@ import os from dotenv import load_dotenv from google.adk import Agent -from google.adk.tools.google_api_tool import bigquery_toolset +from google.adk.tools.google_api_tool import BigQueryToolset # Load environment variables from .env file load_dotenv() @@ -24,8 +24,6 @@ load_dotenv() # Access the variable oauth_client_id = os.getenv("OAUTH_CLIENT_ID") oauth_client_secret = os.getenv("OAUTH_CLIENT_SECRET") -bigquery_toolset.configure_auth(oauth_client_id, oauth_client_secret) - tools_to_expose = [ "bigquery_datasets_list", "bigquery_datasets_get", @@ -34,15 +32,17 @@ tools_to_expose = [ "bigquery_tables_get", "bigquery_tables_insert", ] -bigquery_toolset.set_tool_filter( - lambda tool, ctx=None: tool.name in tools_to_expose +bigquery_toolset = BigQueryToolset( + client_id=oauth_client_id, + client_secret=oauth_client_secret, + tool_filter=tools_to_expose, ) root_agent = Agent( model="gemini-2.0-flash", name="bigquery_agent", instruction=""" - You are a helpful Google BigQuery agent that help to manage users' data on Goolge BigQuery. + You are a helpful Google BigQuery agent that help to manage users' data on Google BigQuery. Use the provided tools to conduct various operations on users' data in Google BigQuery. Scenario 1: diff --git a/contributing/samples/callbacks/agent.py b/contributing/samples/callbacks/agent.py index b849e506..4f10f7c6 100755 --- a/contributing/samples/callbacks/agent.py +++ b/contributing/samples/callbacks/agent.py @@ -145,7 +145,7 @@ def after_tool_cb3(tool, args, tool_context, tool_response): root_agent = Agent( - model='gemini-2.0-flash-exp', + model='gemini-2.0-flash', name='data_processing_agent', description=( 'hello world agent that can roll a dice of 8 sides and check prime' diff --git a/contributing/samples/hello_world/asyncio_run.py b/contributing/samples/callbacks/main.py similarity index 52% rename from contributing/samples/hello_world/asyncio_run.py rename to contributing/samples/callbacks/main.py index 53768f5e..5cf6b52e 100755 --- a/contributing/samples/hello_world/asyncio_run.py +++ b/contributing/samples/callbacks/main.py @@ -19,7 +19,6 @@ import warnings import agent from dotenv import load_dotenv from google.adk import Runner -from google.adk.agents.run_config import RunConfig from google.adk.artifacts import InMemoryArtifactService from google.adk.cli.utils import logs from google.adk.sessions import InMemorySessionService @@ -42,7 +41,7 @@ async def main(): artifact_service=artifact_service, session_service=session_service, ) - session_11 = session_service.create_session( + session_11 = await session_service.create_session( app_name=app_name, user_id=user_id_1 ) @@ -59,25 +58,6 @@ async def main(): if event.content.parts and event.content.parts[0].text: print(f'** {event.author}: {event.content.parts[0].text}') - async def run_prompt_bytes(session: Session, new_message: str): - content = types.Content( - role='user', - parts=[ - types.Part.from_bytes( - data=str.encode(new_message), mime_type='text/plain' - ) - ], - ) - print('** User says:', content.model_dump(exclude_none=True)) - async for event in runner.run_async( - user_id=user_id_1, - session_id=session.id, - new_message=content, - run_config=RunConfig(save_input_blobs_as_artifacts=True), - ): - if event.content.parts and event.content.parts[0].text: - print(f'** {event.author}: {event.content.parts[0].text}') - start_time = time.time() print('Start time:', start_time) print('------------------------------------') @@ -85,7 +65,6 @@ async def main(): await run_prompt(session_11, 'Roll a die with 100 sides') await run_prompt(session_11, 'Roll a die again with 100 sides.') await run_prompt(session_11, 'What numbers did I got?') - await run_prompt_bytes(session_11, 'Hi bytes') print( await artifact_service.list_artifact_keys( app_name=app_name, user_id=user_id_1, session_id=session_11.id @@ -97,49 +76,5 @@ async def main(): print('Total time:', end_time - start_time) -def main_sync(): - app_name = 'my_app' - user_id_1 = 'user1' - session_service = InMemorySessionService() - artifact_service = InMemoryArtifactService() - runner = Runner( - app_name=app_name, - agent=agent.root_agent, - artifact_service=artifact_service, - session_service=session_service, - ) - session_11 = session_service.create_session( - app_name=app_name, user_id=user_id_1 - ) - - def run_prompt(session: Session, new_message: str): - content = types.Content( - role='user', parts=[types.Part.from_text(text=new_message)] - ) - print('** User says:', content.model_dump(exclude_none=True)) - for event in runner.run( - user_id=user_id_1, - session_id=session.id, - new_message=content, - ): - if event.content.parts and event.content.parts[0].text: - print(f'** {event.author}: {event.content.parts[0].text}') - - start_time = time.time() - print('Start time:', start_time) - print('------------------------------------') - run_prompt(session_11, 'Hi') - run_prompt(session_11, 'Roll a die with 100 sides.') - run_prompt(session_11, 'Roll a die again with 100 sides.') - run_prompt(session_11, 'What numbers did I got?') - end_time = time.time() - print('------------------------------------') - print('End time:', end_time) - print('Total time:', end_time - start_time) - - if __name__ == '__main__': - print('--------------ASYNC--------------------') asyncio.run(main()) - print('--------------SYNC--------------------') - main_sync() diff --git a/contributing/samples/code_execution/agent.py b/contributing/samples/code_execution/agent.py index 3e7e4c0b..b8cbd614 100644 --- a/contributing/samples/code_execution/agent.py +++ b/contributing/samples/code_execution/agent.py @@ -15,7 +15,7 @@ """Data science agent.""" from google.adk.agents.llm_agent import Agent -from google.adk.tools import built_in_code_execution +from google.adk.code_executors.built_in_code_executor import BuiltInCodeExecutor def base_system_instruction(): @@ -96,5 +96,5 @@ When plotting trends, you should make sure to sort and order the data by the x-a """, - tools=[built_in_code_execution], + code_executor=BuiltInCodeExecutor(), ) diff --git a/contributing/samples/fields_planner/asyncio_run.py b/contributing/samples/fields_planner/main.py similarity index 63% rename from contributing/samples/fields_planner/asyncio_run.py rename to contributing/samples/fields_planner/main.py index 5aa7392f..18f67f5c 100755 --- a/contributing/samples/fields_planner/asyncio_run.py +++ b/contributing/samples/fields_planner/main.py @@ -41,7 +41,7 @@ async def main(): artifact_service=artifact_service, session_service=session_service, ) - session_11 = session_service.create_session(app_name, user_id_1) + session_11 = await session_service.create_session(app_name, user_id_1) async def run_prompt(session: Session, new_message: str): content = types.Content( @@ -69,44 +69,5 @@ async def main(): print('Total time:', end_time - start_time) -def main_sync(): - app_name = 'my_app' - user_id_1 = 'user1' - session_service = InMemorySessionService() - artifact_service = InMemoryArtifactService() - runner = Runner( - app_name=app_name, - agent=agent.root_agent, - artifact_service=artifact_service, - session_service=session_service, - ) - session_11 = session_service.create_session(app_name, user_id_1) - - def run_prompt(session: Session, new_message: str): - content = types.Content( - role='user', parts=[types.Part.from_text(text=new_message)] - ) - print('** User says:', content.model_dump(exclude_none=True)) - for event in runner.run_sync( - session=session, - new_message=content, - ): - if event.content.parts and event.content.parts[0].text: - print(f'** {event.author}: {event.content.parts[0].text}') - - start_time = time.time() - print('Start time:', start_time) - print('------------------------------------') - run_prompt(session_11, 'Hi') - run_prompt(session_11, 'Roll a die.') - run_prompt(session_11, 'Roll a die again.') - run_prompt(session_11, 'What numbers did I got?') - end_time = time.time() - print('------------------------------------') - print('End time:', end_time) - print('Total time:', end_time - start_time) - - if __name__ == '__main__': asyncio.run(main()) - main_sync() diff --git a/contributing/samples/hello_world/agent.py b/contributing/samples/hello_world/agent.py index 8c1b500a..b7b8ce1a 100755 --- a/contributing/samples/hello_world/agent.py +++ b/contributing/samples/hello_world/agent.py @@ -66,7 +66,7 @@ async def check_prime(nums: list[int]) -> str: ) root_agent = Agent( - model='gemini-2.0-flash-exp', + model='gemini-2.0-flash', name='data_processing_agent', description=( 'hello world agent that can roll a dice of 8 sides and check prime' diff --git a/contributing/samples/callbacks/asyncio_run.py b/contributing/samples/hello_world/main.py similarity index 59% rename from contributing/samples/callbacks/asyncio_run.py rename to contributing/samples/hello_world/main.py index 53768f5e..92803298 100755 --- a/contributing/samples/callbacks/asyncio_run.py +++ b/contributing/samples/hello_world/main.py @@ -14,35 +14,27 @@ import asyncio import time -import warnings import agent from dotenv import load_dotenv -from google.adk import Runner from google.adk.agents.run_config import RunConfig -from google.adk.artifacts import InMemoryArtifactService from google.adk.cli.utils import logs -from google.adk.sessions import InMemorySessionService +from google.adk.runners import InMemoryRunner from google.adk.sessions import Session from google.genai import types load_dotenv(override=True) -warnings.filterwarnings('ignore', category=UserWarning) logs.log_to_tmp_folder() async def main(): app_name = 'my_app' user_id_1 = 'user1' - session_service = InMemorySessionService() - artifact_service = InMemoryArtifactService() - runner = Runner( - app_name=app_name, + runner = InMemoryRunner( agent=agent.root_agent, - artifact_service=artifact_service, - session_service=session_service, + app_name=app_name, ) - session_11 = session_service.create_session( + session_11 = await runner.session_service.create_session( app_name=app_name, user_id=user_id_1 ) @@ -87,7 +79,7 @@ async def main(): await run_prompt(session_11, 'What numbers did I got?') await run_prompt_bytes(session_11, 'Hi bytes') print( - await artifact_service.list_artifact_keys( + await runner.artifact_service.list_artifact_keys( app_name=app_name, user_id=user_id_1, session_id=session_11.id ) ) @@ -97,49 +89,5 @@ async def main(): print('Total time:', end_time - start_time) -def main_sync(): - app_name = 'my_app' - user_id_1 = 'user1' - session_service = InMemorySessionService() - artifact_service = InMemoryArtifactService() - runner = Runner( - app_name=app_name, - agent=agent.root_agent, - artifact_service=artifact_service, - session_service=session_service, - ) - session_11 = session_service.create_session( - app_name=app_name, user_id=user_id_1 - ) - - def run_prompt(session: Session, new_message: str): - content = types.Content( - role='user', parts=[types.Part.from_text(text=new_message)] - ) - print('** User says:', content.model_dump(exclude_none=True)) - for event in runner.run( - user_id=user_id_1, - session_id=session.id, - new_message=content, - ): - if event.content.parts and event.content.parts[0].text: - print(f'** {event.author}: {event.content.parts[0].text}') - - start_time = time.time() - print('Start time:', start_time) - print('------------------------------------') - run_prompt(session_11, 'Hi') - run_prompt(session_11, 'Roll a die with 100 sides.') - run_prompt(session_11, 'Roll a die again with 100 sides.') - run_prompt(session_11, 'What numbers did I got?') - end_time = time.time() - print('------------------------------------') - print('End time:', end_time) - print('Total time:', end_time - start_time) - - if __name__ == '__main__': - print('--------------ASYNC--------------------') asyncio.run(main()) - print('--------------SYNC--------------------') - main_sync() diff --git a/contributing/samples/hello_world_litellm/asyncio_run.py b/contributing/samples/hello_world_litellm/main.py similarity index 95% rename from contributing/samples/hello_world_litellm/asyncio_run.py rename to contributing/samples/hello_world_litellm/main.py index f2fd4ae3..e95353b5 100644 --- a/contributing/samples/hello_world_litellm/asyncio_run.py +++ b/contributing/samples/hello_world_litellm/main.py @@ -15,7 +15,6 @@ import asyncio import time -import warnings import agent from dotenv import load_dotenv @@ -27,7 +26,6 @@ from google.adk.sessions import Session from google.genai import types load_dotenv(override=True) -warnings.filterwarnings('ignore', category=UserWarning) logs.log_to_tmp_folder() @@ -42,7 +40,7 @@ async def main(): artifact_service=artifact_service, session_service=session_service, ) - session_11 = session_service.create_session( + session_11 = await session_service.create_session( app_name=app_name, user_id=user_id_1 ) diff --git a/contributing/samples/hello_world_ollama/asyncio_run.py b/contributing/samples/hello_world_ollama/main.py similarity index 95% rename from contributing/samples/hello_world_ollama/asyncio_run.py rename to contributing/samples/hello_world_ollama/main.py index 2f106dfb..9a679f4f 100755 --- a/contributing/samples/hello_world_ollama/asyncio_run.py +++ b/contributing/samples/hello_world_ollama/main.py @@ -41,7 +41,7 @@ async def main(): artifact_service=artifact_service, session_service=session_service, ) - session_11 = session_service.create_session( + session_11 = await session_service.create_session( app_name=app_name, user_id=user_id_1 ) @@ -66,7 +66,7 @@ async def main(): session_11, 'Roll a die with 100 sides and check if it is prime' ) await run_prompt(session_11, 'Roll it again.') - await run_prompt(session_11, 'What numbers did I got?') + await run_prompt(session_11, 'What numbers did I get?') end_time = time.time() print('------------------------------------') print('End time:', end_time) diff --git a/contributing/samples/mcp_agent/agent.py b/contributing/samples/mcp_agent/agent.py index 6f8dccae..a14ab439 100755 --- a/contributing/samples/mcp_agent/agent.py +++ b/contributing/samples/mcp_agent/agent.py @@ -19,10 +19,16 @@ from google.adk.agents.llm_agent import LlmAgent from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset from google.adk.tools.mcp_tool.mcp_toolset import StdioServerParameters +_allowed_path = os.path.dirname(os.path.abspath(__file__)) + root_agent = LlmAgent( model='gemini-2.0-flash', name='enterprise_assistant', - instruction='Help user accessing their file systems', + instruction=f"""\ +Help user accessing their file systems. + +Allowed directory: {_allowed_path} + """, tools=[ MCPToolset( connection_params=StdioServerParameters( @@ -30,15 +36,26 @@ root_agent = LlmAgent( args=[ '-y', # Arguments for the command '@modelcontextprotocol/server-filesystem', - os.path.dirname(os.path.abspath(__file__)), + _allowed_path, ], ), # don't want agent to do write operation + # you can also do below + # tool_filter=lambda tool, ctx=None: tool.name + # not in [ + # 'write_file', + # 'edit_file', + # 'create_directory', + # 'move_file', + # ], tool_filter=[ - 'write_file', - 'edit_file', - 'create_directory', - 'move_file', + 'read_file', + 'read_multiple_files', + 'list_directory', + 'directory_tree', + 'search_files', + 'get_file_info', + 'list_allowed_directories', ], ) ], diff --git a/contributing/samples/oauth_calendar_agent/agent.py b/contributing/samples/oauth_calendar_agent/agent.py index e11153b7..a1b1dea8 100644 --- a/contributing/samples/oauth_calendar_agent/agent.py +++ b/contributing/samples/oauth_calendar_agent/agent.py @@ -27,7 +27,7 @@ from google.adk.auth import AuthCredential from google.adk.auth import AuthCredentialTypes from google.adk.auth import OAuth2Auth from google.adk.tools import ToolContext -from google.adk.tools.google_api_tool import calendar_toolset +from google.adk.tools.google_api_tool import CalendarToolset from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials from googleapiclient.discovery import build @@ -42,16 +42,14 @@ oauth_client_secret = os.getenv("OAUTH_CLIENT_SECRET") SCOPES = ["https://www.googleapis.com/auth/calendar"] -calendar_toolset.configure_auth( - client_id=oauth_client_id, client_secret=oauth_client_secret +calendar_toolset = CalendarToolset( + # you can also replace below customized `list_calendar_events` with build-in + # google calendar tool by adding `calendar_events_list` in the filter list + client_id=oauth_client_id, + client_secret=oauth_client_secret, + tool_filter=["calendar_events_get"], ) -get_calendar_events = calendar_toolset.get_tool("calendar_events_get") -# list_calendar_events = calendar_toolset.get_tool("calendar_events_list") -# you can replace below customized list_calendar_events tool with above ADK -# build-in google calendar tool which is commented for now to acheive same -# effect. - def list_calendar_events( start_time: str, @@ -210,6 +208,6 @@ root_agent = Agent( Currnet time: {_time} """, - tools=[list_calendar_events, get_calendar_events], + tools=[list_calendar_events, calendar_toolset], before_agent_callback=update_time, ) diff --git a/contributing/samples/simple_sequential_agent/agent.py b/contributing/samples/simple_sequential_agent/agent.py index 74e8f58d..afd7ebea 100644 --- a/contributing/samples/simple_sequential_agent/agent.py +++ b/contributing/samples/simple_sequential_agent/agent.py @@ -28,7 +28,7 @@ def roll_die(sides: int) -> int: roll_agent = LlmAgent( name="roll_agent", description="Handles rolling dice of different sizes.", - model="gemini-2.0-flash-exp", + model="gemini-2.0-flash", instruction=""" You are responsible for rolling dice based on the user's request. When asked to roll a die, you must call the roll_die tool with the number of sides as an integer. @@ -69,7 +69,7 @@ def check_prime(nums: list[int]) -> str: prime_agent = LlmAgent( name="prime_agent", description="Handles checking if numbers are prime.", - model="gemini-2.0-flash-exp", + model="gemini-2.0-flash", instruction=""" You are responsible for checking whether numbers are prime. When asked to check primes, you must call the check_prime tool with a list of integers. diff --git a/contributing/samples/token_usage/__init__.py b/contributing/samples/token_usage/__init__.py new file mode 100755 index 00000000..c48963cd --- /dev/null +++ b/contributing/samples/token_usage/__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/token_usage/agent.py b/contributing/samples/token_usage/agent.py new file mode 100755 index 00000000..65990cee --- /dev/null +++ b/contributing/samples/token_usage/agent.py @@ -0,0 +1,97 @@ +# 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. + +import random + +from google.adk import Agent +from google.adk.agents.llm_agent import LlmAgent +from google.adk.agents.sequential_agent import SequentialAgent +from google.adk.models.anthropic_llm import Claude +from google.adk.models.lite_llm import LiteLlm +from google.adk.planners import BuiltInPlanner +from google.adk.planners import PlanReActPlanner +from google.adk.tools.tool_context import ToolContext +from google.genai import types + + +def roll_die(sides: int, tool_context: ToolContext) -> int: + """Roll a die and return the rolled result. + + Args: + sides: The integer number of sides the die has. + + Returns: + An integer of the result of rolling the die. + """ + result = random.randint(1, sides) + if 'rolls' not in tool_context.state: + tool_context.state['rolls'] = [] + + tool_context.state['rolls'] = tool_context.state['rolls'] + [result] + return result + + +roll_agent_with_openai = LlmAgent( + model=LiteLlm(model='openai/gpt-4o'), + description='Handles rolling dice of different sizes.', + name='roll_agent_with_openai', + instruction=""" + You are responsible for rolling dice based on the user's request. + When asked to roll a die, you must call the roll_die tool with the number of sides as an integer. + """, + tools=[roll_die], +) + +roll_agent_with_claude = LlmAgent( + model=Claude(model='claude-3-7-sonnet@20250219'), + description='Handles rolling dice of different sizes.', + name='roll_agent_with_claude', + instruction=""" + You are responsible for rolling dice based on the user's request. + When asked to roll a die, you must call the roll_die tool with the number of sides as an integer. + """, + tools=[roll_die], +) + +roll_agent_with_litellm_claude = LlmAgent( + model=LiteLlm(model='vertex_ai/claude-3-7-sonnet'), + description='Handles rolling dice of different sizes.', + name='roll_agent_with_litellm_claude', + instruction=""" + You are responsible for rolling dice based on the user's request. + When asked to roll a die, you must call the roll_die tool with the number of sides as an integer. + """, + tools=[roll_die], +) + +roll_agent_with_gemini = LlmAgent( + model='gemini-2.0-flash', + description='Handles rolling dice of different sizes.', + name='roll_agent_with_gemini', + instruction=""" + You are responsible for rolling dice based on the user's request. + When asked to roll a die, you must call the roll_die tool with the number of sides as an integer. + """, + tools=[roll_die], +) + +root_agent = SequentialAgent( + name='code_pipeline_agent', + sub_agents=[ + roll_agent_with_openai, + roll_agent_with_claude, + roll_agent_with_litellm_claude, + roll_agent_with_gemini, + ], +) diff --git a/contributing/samples/token_usage/main.py b/contributing/samples/token_usage/main.py new file mode 100755 index 00000000..d85669af --- /dev/null +++ b/contributing/samples/token_usage/main.py @@ -0,0 +1,102 @@ +# 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. + +import asyncio +import time +import warnings + +import agent +from dotenv import load_dotenv +from google.adk import Runner +from google.adk.agents.run_config import RunConfig +from google.adk.artifacts import InMemoryArtifactService +from google.adk.cli.utils import logs +from google.adk.sessions import InMemorySessionService +from google.adk.sessions import Session +from google.genai import types + +load_dotenv(override=True) +warnings.filterwarnings('ignore', category=UserWarning) +logs.log_to_tmp_folder() + + +async def main(): + app_name = 'my_app' + user_id_1 = 'user1' + session_service = InMemorySessionService() + artifact_service = InMemoryArtifactService() + runner = Runner( + app_name=app_name, + agent=agent.root_agent, + artifact_service=artifact_service, + session_service=session_service, + ) + session_11 = await session_service.create_session( + app_name=app_name, user_id=user_id_1 + ) + + total_prompt_tokens = 0 + total_candidate_tokens = 0 + total_tokens = 0 + + async def run_prompt(session: Session, new_message: str): + nonlocal total_prompt_tokens + nonlocal total_candidate_tokens + nonlocal total_tokens + content = types.Content( + role='user', parts=[types.Part.from_text(text=new_message)] + ) + print('** User says:', content.model_dump(exclude_none=True)) + async for event in runner.run_async( + user_id=user_id_1, + session_id=session.id, + new_message=content, + ): + if event.content.parts and event.content.parts[0].text: + print(f'** {event.author}: {event.content.parts[0].text}') + if event.usage_metadata: + total_prompt_tokens += event.usage_metadata.prompt_token_count or 0 + total_candidate_tokens += ( + event.usage_metadata.candidates_token_count or 0 + ) + total_tokens += event.usage_metadata.total_token_count or 0 + print( + 'Turn tokens:' + f' {event.usage_metadata.total_token_count} (prompt={event.usage_metadata.prompt_token_count},' + f' candidates={event.usage_metadata.candidates_token_count})' + ) + + print( + f'Session tokens: {total_tokens} (prompt={total_prompt_tokens},' + f' candidates={total_candidate_tokens})' + ) + + start_time = time.time() + print('Start time:', start_time) + print('------------------------------------') + await run_prompt(session_11, 'Hi') + await run_prompt(session_11, 'Roll a die with 100 sides') + print( + await artifact_service.list_artifact_keys( + app_name=app_name, user_id=user_id_1, session_id=session_11.id + ) + ) + end_time = time.time() + print('------------------------------------') + print('End time:', end_time) + print('Total time:', end_time - start_time) + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/contributing/samples/toolbox_agent/README.md b/contributing/samples/toolbox_agent/README.md new file mode 100644 index 00000000..98218f24 --- /dev/null +++ b/contributing/samples/toolbox_agent/README.md @@ -0,0 +1,74 @@ +# Toolbox Agent + +This agent is utilizing [mcp toolbox for database](https://googleapis.github.io/genai-toolbox/getting-started/introduction/) to assist end user based on the informaton stored in database. +Follow below steps to run this agent + +# Install toolbox + +* Run below command: + +```bash +export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64 +curl -O https://storage.googleapis.com/genai-toolbox/v0.5.0/$OS/toolbox +chmod +x toolbox +``` + +# install SQLite + +* install sqlite from https://sqlite.org/ + + +# Create DB (optional. The db instance is already attached in the folder) + +* Run below command: + +```bash +sqlite3 tool_box.db +``` + +* Run below SQL: + +```sql +CREATE TABLE hotels( + id INTEGER NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL, + location VARCHAR NOT NULL, + price_tier VARCHAR NOT NULL, + checkin_date DATE NOT NULL, + checkout_date DATE NOT NULL, + booked BIT NOT NULL +); + + +INSERT INTO hotels(id, name, location, price_tier, checkin_date, checkout_date, booked) +VALUES + (1, 'Hilton Basel', 'Basel', 'Luxury', '2024-04-22', '2024-04-20', 0), + (2, 'Marriott Zurich', 'Zurich', 'Upscale', '2024-04-14', '2024-04-21', 0), + (3, 'Hyatt Regency Basel', 'Basel', 'Upper Upscale', '2024-04-02', '2024-04-20', 0), + (4, 'Radisson Blu Lucerne', 'Lucerne', 'Midscale', '2024-04-24', '2024-04-05', 0), + (5, 'Best Western Bern', 'Bern', 'Upper Midscale', '2024-04-23', '2024-04-01', 0), + (6, 'InterContinental Geneva', 'Geneva', 'Luxury', '2024-04-23', '2024-04-28', 0), + (7, 'Sheraton Zurich', 'Zurich', 'Upper Upscale', '2024-04-27', '2024-04-02', 0), + (8, 'Holiday Inn Basel', 'Basel', 'Upper Midscale', '2024-04-24', '2024-04-09', 0), + (9, 'Courtyard Zurich', 'Zurich', 'Upscale', '2024-04-03', '2024-04-13', 0), + (10, 'Comfort Inn Bern', 'Bern', 'Midscale', '2024-04-04', '2024-04-16', 0); +``` + +# create tools configurations + +* Create a yaml file named "tools.yaml", see its contents in the agent folder. + +# start toolbox server + +* Run below commands in the agent folder + +```bash +toolbox --tools-file "tools.yaml" +``` + +# start ADK web UI + +# send user query + +* query 1: what can you do for me ? +* query 2: could you let know the information about "Hilton Basel" hotel ? diff --git a/contributing/samples/toolbox_agent/__init__.py b/contributing/samples/toolbox_agent/__init__.py new file mode 100644 index 00000000..c48963cd --- /dev/null +++ b/contributing/samples/toolbox_agent/__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/toolbox_agent/agent.py b/contributing/samples/toolbox_agent/agent.py new file mode 100644 index 00000000..37a59972 --- /dev/null +++ b/contributing/samples/toolbox_agent/agent.py @@ -0,0 +1,28 @@ +# 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 import Agent +from google.adk.tools import ToolboxToolset + +root_agent = Agent( + model="gemini-2.0-flash", + name="root_agent", + instruction="You are a helpful assistant", + # Add Toolbox tools to ADK agent + tools=[ + ToolboxToolset( + server_url="http://127.0.0.1:5000", toolset_name="my-toolset" + ) + ], +) diff --git a/contributing/samples/toolbox_agent/tool_box.db b/contributing/samples/toolbox_agent/tool_box.db new file mode 100644 index 00000000..4be746cc Binary files /dev/null and b/contributing/samples/toolbox_agent/tool_box.db differ diff --git a/contributing/samples/toolbox_agent/tools.yaml b/contributing/samples/toolbox_agent/tools.yaml new file mode 100644 index 00000000..d9050359 --- /dev/null +++ b/contributing/samples/toolbox_agent/tools.yaml @@ -0,0 +1,81 @@ +# 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. +sources: + my-sqlite-db: + kind: "sqlite" + database: "tool_box.db" +tools: + search-hotels-by-name: + kind: sqlite-sql + source: my-sqlite-db + description: Search for hotels based on name. + parameters: + - name: name + type: string + description: The name of the hotel. + statement: SELECT * FROM hotels WHERE name LIKE '%' || $1 || '%'; + search-hotels-by-location: + kind: sqlite-sql + source: my-sqlite-db + description: Search for hotels based on location. + parameters: + - name: location + type: string + description: The location of the hotel. + statement: SELECT * FROM hotels WHERE location LIKE '%' || $1 || '%'; + book-hotel: + kind: sqlite-sql + source: my-sqlite-db + description: >- + Book a hotel by its ID. If the hotel is successfully booked, returns a NULL, raises an error if not. + parameters: + - name: hotel_id + type: string + description: The ID of the hotel to book. + statement: UPDATE hotels SET booked = 1 WHERE id = $1; + update-hotel: + kind: sqlite-sql + source: my-sqlite-db + description: >- + Update a hotel's check-in and check-out dates by its ID. Returns a message + indicating whether the hotel was successfully updated or not. + parameters: + - name: hotel_id + type: string + description: The ID of the hotel to update. + - name: checkin_date + type: string + description: The new check-in date of the hotel. + - name: checkout_date + type: string + description: The new check-out date of the hotel. + statement: >- + UPDATE hotels SET checkin_date = CAST($2 as date), checkout_date = CAST($3 + as date) WHERE id = $1; + cancel-hotel: + kind: sqlite-sql + source: my-sqlite-db + description: Cancel a hotel by its ID. + parameters: + - name: hotel_id + type: string + description: The ID of the hotel to cancel. + statement: UPDATE hotels SET booked = 0 WHERE id = $1; +toolsets: + my-toolset: + - search-hotels-by-name + - search-hotels-by-location + - book-hotel + - update-hotel + - cancel-hotel diff --git a/pyproject.toml b/pyproject.toml index 988f791e..5be6c6e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,6 @@ dependencies = [ "google-cloud-aiplatform>=1.87.0", # For VertexAI integrations, e.g. example store. "google-cloud-secret-manager>=2.22.0", # Fetching secrets in RestAPI Tool "google-cloud-speech>=2.30.0", # For Audio Transcription - "google-cloud-storage>=2.18.0, <3.0.0", # For GCS Artifact service "google-genai>=1.14.0", # Google GenAI SDK "graphviz>=0.20.2", # Graphviz for graph rendering @@ -46,6 +45,7 @@ dependencies = [ "sqlalchemy>=2.0", # SQL database ORM "tzlocal>=5.3", # Time zone utilities "uvicorn>=0.34.0", # ASGI server for FastAPI + "toolbox-core>=0.1.0", # go/keep-sorted end ] dynamic = ["version"] @@ -67,6 +67,7 @@ dev = [ "isort>=6.0.0", "pyink>=24.10.0", "pylint>=2.6.0", + "mypy>=1.15.0", # go/keep-sorted end ] @@ -155,3 +156,9 @@ known_third_party = ["google.adk"] testpaths = ["tests"] asyncio_default_fixture_loop_scope = "function" asyncio_mode = "auto" + +[tool.mypy] +exclude = "tests/" +plugins = ["pydantic.mypy"] +strict = true +disable_error_code = ["import-not-found", "import-untyped", "unused-ignore"] diff --git a/src/google/adk/agents/llm_agent.py b/src/google/adk/agents/llm_agent.py index e6b782ac..6f211f49 100644 --- a/src/google/adk/agents/llm_agent.py +++ b/src/google/adk/agents/llm_agent.py @@ -53,7 +53,7 @@ from .callback_context import CallbackContext from .invocation_context import InvocationContext from .readonly_context import ReadonlyContext -logger = logging.getLogger(__name__) +logger = logging.getLogger('google_adk.' + __name__) _SingleBeforeModelCallback: TypeAlias = Callable[ [CallbackContext, LlmRequest], @@ -200,8 +200,7 @@ class LlmAgent(BaseAgent): Check out available code executions in `google.adk.code_executor` package. - NOTE: to use model's built-in code executor, don't set this field, add - `google.adk.tools.built_in_code_execution` to tools instead. + NOTE: to use model's built-in code executor, use the `BuiltInCodeExecutor`. """ # Advance features - End @@ -308,31 +307,53 @@ class LlmAgent(BaseAgent): ancestor_agent = ancestor_agent.parent_agent raise ValueError(f'No model found for {self.name}.') - async def canonical_instruction(self, ctx: ReadonlyContext) -> str: + async def canonical_instruction( + self, ctx: ReadonlyContext + ) -> tuple[str, bool]: """The resolved self.instruction field to construct instruction for this agent. This method is only for use by Agent Development Kit. + + Args: + ctx: The context to retrieve the session state. + + Returns: + A tuple of (instruction, bypass_state_injection). + instruction: The resolved self.instruction field. + bypass_state_injection: Whether the instruction is based on + InstructionProvider. """ if isinstance(self.instruction, str): - return self.instruction + return self.instruction, False else: instruction = self.instruction(ctx) if inspect.isawaitable(instruction): instruction = await instruction - return instruction + return instruction, True - async def canonical_global_instruction(self, ctx: ReadonlyContext) -> str: + async def canonical_global_instruction( + self, ctx: ReadonlyContext + ) -> tuple[str, bool]: """The resolved self.instruction field to construct global instruction. This method is only for use by Agent Development Kit. + + Args: + ctx: The context to retrieve the session state. + + Returns: + A tuple of (instruction, bypass_state_injection). + instruction: The resolved self.global_instruction field. + bypass_state_injection: Whether the instruction is based on + InstructionProvider. """ if isinstance(self.global_instruction, str): - return self.global_instruction + return self.global_instruction, False else: global_instruction = self.global_instruction(ctx) if inspect.isawaitable(global_instruction): global_instruction = await global_instruction - return global_instruction + return global_instruction, True async def canonical_tools( self, ctx: ReadonlyContext = None diff --git a/src/google/adk/agents/run_config.py b/src/google/adk/agents/run_config.py index f19ae0fc..566fe860 100644 --- a/src/google/adk/agents/run_config.py +++ b/src/google/adk/agents/run_config.py @@ -22,7 +22,7 @@ from pydantic import BaseModel from pydantic import ConfigDict from pydantic import field_validator -logger = logging.getLogger(__name__) +logger = logging.getLogger('google_adk.' + __name__) class StreamingMode(Enum): diff --git a/src/google/adk/artifacts/gcs_artifact_service.py b/src/google/adk/artifacts/gcs_artifact_service.py index 8adbfe57..e4af21e1 100644 --- a/src/google/adk/artifacts/gcs_artifact_service.py +++ b/src/google/adk/artifacts/gcs_artifact_service.py @@ -23,7 +23,7 @@ from typing_extensions import override from .base_artifact_service import BaseArtifactService -logger = logging.getLogger(__name__) +logger = logging.getLogger("google_adk." + __name__) class GcsArtifactService(BaseArtifactService): diff --git a/src/google/adk/artifacts/in_memory_artifact_service.py b/src/google/adk/artifacts/in_memory_artifact_service.py index fcfb8811..1dd724bb 100644 --- a/src/google/adk/artifacts/in_memory_artifact_service.py +++ b/src/google/adk/artifacts/in_memory_artifact_service.py @@ -24,7 +24,7 @@ from typing_extensions import override from .base_artifact_service import BaseArtifactService -logger = logging.getLogger(__name__) +logger = logging.getLogger("google_adk." + __name__) class InMemoryArtifactService(BaseArtifactService, BaseModel): diff --git a/src/google/adk/cli/agent_graph.py b/src/google/adk/cli/agent_graph.py index 58d9120a..2b6ca3c6 100644 --- a/src/google/adk/cli/agent_graph.py +++ b/src/google/adk/cli/agent_graph.py @@ -25,7 +25,7 @@ from ..tools.agent_tool import AgentTool from ..tools.base_tool import BaseTool from ..tools.function_tool import FunctionTool -logger = logging.getLogger(__name__) +logger = logging.getLogger('google_adk.' + __name__) try: from ..tools.retrieval.base_retrieval_tool import BaseRetrievalTool diff --git a/src/google/adk/cli/browser/index.html b/src/google/adk/cli/browser/index.html index 3692c56c..8573e48b 100644 --- a/src/google/adk/cli/browser/index.html +++ b/src/google/adk/cli/browser/index.html @@ -25,9 +25,9 @@ - +