Compare commits

...

21 Commits

Author SHA1 Message Date
Liang Wu c6cf11cb74 chore:Rename conformance create command to record
PiperOrigin-RevId: 822708044
2025-10-22 13:03:37 -07:00
Yifan Wang 2724819622 chore: update adk web, fixing cursor color, and thought bubble
PiperOrigin-RevId: 822693836
2025-10-22 12:27:28 -07:00
Alexey Guseynov 2f4f5611bd fix:Remove unnecessary Aclosing
PiperOrigin-RevId: 822496695
2025-10-22 02:40:07 -07:00
Wei Sun (Jack) 4df926388b fix: Returns dict as result from McpTool
The `BaseTool` expects the run_async to return a json-serializable object. By model_dump the McpTool result explicitly can allow what ADK runtime sees is identical to what is persisted in the session event list.

Before the change, runtime sees CallToolResult instance and Session persists its serialized dict.

https://github.com/modelcontextprotocol/python-sdk/blob/main/src/mcp/types.py#L916-L922

PiperOrigin-RevId: 822465432
2025-10-22 00:58:04 -07:00
Wei Sun (Jack) d4dc645478 chore: Fixes MCPToolset --> McpToolset in various places
PiperOrigin-RevId: 822377517
2025-10-21 19:42:37 -07:00
Wei Sun (Jack) 7d5c6b9acf fix: Fixes the identity prompt to be one line and add ending period after description statement
From

```
You are an agent. Your internal name is "agent".

 The description about you is "test description"
```

to

```
You are an agent. Your internal name is "agent". The description about you is "test description".
```

PiperOrigin-RevId: 822358196
2025-10-21 18:31:48 -07:00
Yifan Wang aab2504ebd chore: update adk web
PiperOrigin-RevId: 822341297
2025-10-21 17:31:20 -07:00
Shangjie Chen 391628fcdc feat: Add a service registry to provide a generic way to register custom service implementations to be used in FastAPI server
To register a custom service:
- Create a factory function that takes a URI and returns an instance of your custom service. This function will parse any details it needs from the URI.
- Register your factory with the global service registry. You need to define a unique URI scheme for your service (e.g., custom).

PiperOrigin-RevId: 822310466
2025-10-21 15:58:51 -07:00
Luis Pabon 409df1378f feat: Granular Per Agent Speech Configuration
Merge https://github.com/google/adk-python/pull/3170

Addresses Feature Request: #3116

This PR adds a `speech_config` to the **LLM Agent configuration** for the **live use case**. When an **asynchronous LLM** call is made to the **Gemini Live API**, it prioritizes the most specific agent configuration's speech_config. If that is null, it then uses the run configuration's speech_config. Unit tests have been added to verify this behavior.

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/3170 from qyuo:bidi_agent_speech_config af1bd277d4f95c4a7d9aa0b16828ba3de826ce08
PiperOrigin-RevId: 822305427
2025-10-21 15:44:00 -07:00
Shangjie Chen 2a901d12f4 chore: Raise AlreadyExistsError when trying to create a resource with same ID
Move the dedupe logic into session service so that the internal error can be surfaced to client

PiperOrigin-RevId: 822294430
2025-10-21 15:11:33 -07:00
Xuan Yang c850da3a07 fix: Fix the broken langchain importing caused their 1.0.0 release
PiperOrigin-RevId: 822279014
2025-10-21 14:30:34 -07:00
Parham MohammadAlizadeh ed37e343f0 feat(tools): support additional headers for google api toolset #non-breaking
Merge https://github.com/google/adk-python/pull/3194

Allow Google API toolsets to accept optional per-request headers
#3105

## Testing Plan

### Unit Tests
-  Added `test_init_with_additional_headers` in `test_google_api_tool.py` to verify headers are passed to RestApiTool
-  Added `test_prepare_request_params_merges_default_headers` in `test_rest_api_tool.py` to verify custom headers are merged into requests
-  Added `test_prepare_request_params_preserves_existing_headers` in `test_rest_api_tool.py` to verify critical headers (Content-Type, User-Agent) are not overridden by additional_headers
-  Updated `test_init` and `test_get_tools` in `test_google_api_toolset.py` to verify the parameter is properly stored and passed through

### Manual Testing
Tested with Google Ads API scenario (the original use case from issue #3105):

```python
import os
from google.adk.tools.google_api_tool import GoogleApiToolset

# Create toolset with developer-token header required by Google Ads API
google_ads_toolset = GoogleApiToolset(
    client_id=os.environ["CLIENT_ID"],
    client_secret=os.environ["CLIENT_SECRET"],
    api_name="googleads",
    api_version="v21",
    additional_headers={"developer-token": os.environ["GOOGLE_ADS_DEV_TOKEN"]}
)

# Verify headers are included in API requests
tools = await google_ads_toolset.get_tools()
# Successfully made requests with the developer-token header
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/3194 from Prhmma:feature/google-api-toolset-additional-headers-3105 e10489e82bfde5cf2bfd3f1bced3e1f5cea1f8b2
PiperOrigin-RevId: 822273582
2025-10-21 14:16:37 -07:00
Kathy Wu ce3418a69d fix: Fix BuiltInCodeExecutor so that it can support visualizations
Previously BuiltInCodeExecutor was missing the logic to save output files from executed code as artifacts, so images/visualizations wouldn't show up in the UI. This fix will iterate through all parts of the LlmResponse, and if any of them are images, it will save the image data using artifact_service (similar to what is done in VertexAICodeExecutor).

This fixes the backend, but there are still UI bugs that should be fixed -- events without content are currently ignored, so the image doesn't appear even though it is saved. We will add the UI fix in a separate change.

PiperOrigin-RevId: 822245140
2025-10-21 13:06:43 -07:00
George Weale fe1fc75c15 chore: Improve hint message in agent loader
PiperOrigin-RevId: 822216833
2025-10-21 11:54:32 -07:00
George Weale dc4975dea9 fix: relax runner app-name enforcement
- let _enforce_app_name_alignment warn instead of raising while caching the hint that now augments the existing “Session not found …” error
- tighten _infer_agent_origin so it ignores hidden folders (like .venv)
- make AgentTool reuse the parent runner’s app_name, stopping internal runners from conflicting in multi-agent setups

PiperOrigin-RevId: 822205860
2025-10-21 11:30:21 -07:00
Google Team Member aeaec859bf feat: Adds Static User Simulator and User Simulator Provider
Details:
- Adds the `StaticUserSimulator` which implements the current functionality of supplying a fixed set of user prompts for an EvalCase.
- Adds the `UserSimulatorProvider` which determines the type of user simulator required for an EvalCase (StaticUserSimulator or LlmBackedUserSimulator).
- Integrates the UserSimulatorProvider and UserSimulator into the CLI and evaluation infrastructure.
- Updates and adds unit tests for the new functionality.
- Miscellaneous updates to lay groundwork for a full implementation of the LlmBackedUserSimulator in the future.
PiperOrigin-RevId: 822198401
2025-10-21 11:15:11 -07:00
Jaroslav Pantsjoha 4a842c5a13 fix(cli): Improve error message when adk web is run in wrong directory
Merge https://github.com/google/adk-python/pull/3196

## Summary
Enhances the `AgentLoader` error message to provide clear guidance when users run `adk web` from incorrect directories.

## Motivation
During internal workshops, multiple teams encountered confusion when starting `adk web` from the wrong directory. This often happened when:
- Running `adk web my_agent/` instead of `adk web .`
- Being inside an agent directory when executing the command
- Configuring incorrect start paths during development

## Changes
- **Smart detection**: Checks if `agent.py` or `root_agent.yaml` exists in the current directory
- **Visual diagram**: Shows expected directory structure with actual agent name
- **Explicit command**: Includes `adk web <agents_dir>` usage example
- **Conditional hint**: Provides targeted guidance when user is detected to be inside an agent directory

## Example Error Message

### Before
```
ValueError: No root_agent found for 'my_agent'. Searched in 'my_agent.agent.root_agent', 'my_agent.root_agent' and 'my_agent/root_agent.yaml'. Ensure 'path/my_agent' is structured correctly, an .env file can be loaded if present, and a root_agent is exposed.
```

### After
```
ValueError: No root_agent found for 'my_agent'. Searched in 'my_agent.agent.root_agent', 'my_agent.root_agent' and 'my_agent/root_agent.yaml'.

Expected directory structure:
  <agents_dir>/
    my_agent/
      agent.py (with root_agent) OR
      root_agent.yaml

Then run: adk web <agents_dir>

Ensure 'path/my_agent' is structured correctly, an .env file can be loaded if present, and a root_agent is exposed.

HINT: It looks like you might be running 'adk web' from inside an agent directory. Try running 'adk web .' from the parent directory that contains your agent folder, not from within the agent folder itself.
```

## Testing
-  Existing unit tests pass (17/22, with 5 pre-existing failures unrelated to this change)
-  `test_agent_not_found_error` passes, confirming error message enhancement works correctly
-  Code follows ADK contribution guidelines

## Type
- [x] Bug fix (improved error messaging)
- [ ] Feature
- [ ] Breaking change
- [ ] Documentation

## Related
Fixes #3195

---

**Tags**: #non-breaking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/3196 from jpantsjoha:fix/improve-adk-web-error-message a73b190f5b021dbe0afa8426172696ee9eeae8da
PiperOrigin-RevId: 822186700
2025-10-21 10:47:30 -07:00
Wei Sun (Jack) 0bdba30263 docs: format README.md for samples
PiperOrigin-RevId: 822180731
2025-10-21 10:35:43 -07:00
Shangjie Chen 6a5eac0bdc feat: Allow passing extra kwargs to create_session of VertexAiSessionService
This can be used to set ttl and other configs.

PiperOrigin-RevId: 821782343
2025-10-20 13:34:07 -07:00
ejfn 0b73a6937b fix: Handle App objects in eval and graph endpoints
Merge https://github.com/google/adk-python/pull/3060

## Description

Fixes #3059

This PR fixes two endpoints in `adk web` that fail when using App objects instead of bare agents.

## Changes

- **Eval execution endpoint** (line ~969): Extract root_agent from App objects before passing to LocalEvalService
- **Graph visualization endpoint** (line ~1308): Extract root_agent from App objects before graph operations

Both endpoints now properly handle both BaseAgent and App objects by checking the type and extracting `.root_agent` when needed.

## Testing Plan

### Manual E2E Testing with ADK Web

Tested with an App object that includes context caching:

```python
from google.adk.apps import App
from google.adk.agents import LlmAgent

root_agent = LlmAgent(name="MyAgent", model="gemini-1.5-pro-002")
app = App(
    name="my_agent",
    root_agent=root_agent,
    context_cache_config=ContextCacheConfig(...)
)
```

**Before fix:**
- Graph visualization failed (tried to call agent methods on App object)
- Eval execution failed (LocalEvalService received App instead of agent)

**After fix:**
- Graph visualization works correctly
- Eval execution works correctly
- Both endpoints properly extract root_agent from App objects

## Checklist

- [x] Code follows project style (autoformat.sh passed)
- [x] Changes are focused and minimal
- [x] Issue #3059 created and referenced
- [x] Manual E2E testing completed

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/3060 from ejfn:ejfn/bugfix-app-object-endpoints 01c30191bfd9487a8c8463ccf24b297cb9a4ce37
PiperOrigin-RevId: 821746910
2025-10-20 12:11:26 -07:00
Google Team Member ee39a89110 feat: introduces a new AgentEngineSandboxCodeExecutor class that supports executes agent generated code
The AgentEngineSandboxCodeExecutor uses the Vertex AI Code Execution Sandbox API to execute code

PiperOrigin-RevId: 821699641
2025-10-20 10:14:34 -07:00
74 changed files with 6698 additions and 4400 deletions
@@ -0,0 +1,18 @@
# OAuth Sample
## Introduction
This sample data science agent uses Agent Engine Code Execution Sandbox to execute LLM generated code.
## How to use
* 1. Follow https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/code-execution/overview to create a code execution sandbox environment.
* 2. Replace the SANDBOX_RESOURCE_NAME with the one you just created. If you dont want to create a new sandbox environment directly, the Agent Engine Code Execution Sandbox will create one for you by default using the AGENT_ENGINE_RESOURCE_NAME you specified, however, please ensure to clean up sandboxes after use, otherwise, it will consume quotas.
## Sample prompt
* Can you write a function that calculates the sum from 1 to 100.
* The dataset is given as below. Store,Date,Weekly_Sales,Holiday_Flag,Temperature,Fuel_Price,CPI,Unemployment Store 1,2023-06-01,1000,0,70,3.0,200,5 Store 2,2023-06-02,1200,1,80,3.5,210,6 Store 3,2023-06-03,1400,0,90,4.0,220,7 Store 4,2023-06-04,1600,1,70,4.5,230,8 Store 5,2023-06-05,1800,0,80,5.0,240,9 Store 6,2023-06-06,2000,1,90,5.5,250,10 Store 7,2023-06-07,2200,0,90,6.0,260,11 Plot a scatter plot showcasing the relationship between Weekly Sales and Temperature for each store, distinguishing stores with a Holiday Flag.
@@ -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
@@ -0,0 +1,95 @@
# 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.
"""Data science agent."""
from google.adk.agents.llm_agent import Agent
from google.adk.code_executors.agent_engine_sandbox_code_executor import AgentEngineSandboxCodeExecutor
def base_system_instruction():
"""Returns: data science agent system instruction."""
return """
# Guidelines
**Objective:** Assist the user in achieving their data analysis goals within the context of a Python Colab notebook, **with emphasis on avoiding assumptions and ensuring accuracy.** Reaching that goal can involve multiple steps. When you need to generate code, you **don't** need to solve the goal in one go. Only generate the next step at a time.
**Code Execution:** All code snippets provided will be executed within the Colab environment.
**Statefulness:** All code snippets are executed and the variables stays in the environment. You NEVER need to re-initialize variables. You NEVER need to reload files. You NEVER need to re-import libraries.
**Output Visibility:** Always print the output of code execution to visualize results, especially for data exploration and analysis. For example:
- To look a the shape of a pandas.DataFrame do:
```tool_code
print(df.shape)
```
The output will be presented to you as:
```tool_outputs
(49, 7)
```
- To display the result of a numerical computation:
```tool_code
x = 10 ** 9 - 12 ** 5
print(f'{{x=}}')
```
The output will be presented to you as:
```tool_outputs
x=999751168
```
- You **never** generate ```tool_outputs yourself.
- You can then use this output to decide on next steps.
- Print just variables (e.g., `print(f'{{variable=}}')`.
**No Assumptions:** **Crucially, avoid making assumptions about the nature of the data or column names.** Base findings solely on the data itself. Always use the information obtained from `explore_df` to guide your analysis.
**Available files:** Only use the files that are available as specified in the list of available files.
**Data in prompt:** Some queries contain the input data directly in the prompt. You have to parse that data into a pandas DataFrame. ALWAYS parse all the data. NEVER edit the data that are given to you.
**Answerability:** Some queries may not be answerable with the available data. In those cases, inform the user why you cannot process their query and suggest what type of data would be needed to fulfill their request.
"""
root_agent = Agent(
model="gemini-2.0-flash-001",
name="agent_engine_code_execution_agent",
instruction=base_system_instruction() + """
You need to assist the user with their queries by looking at the data and the context in the conversation.
You final answer should summarize the code and code execution relevant to the user query.
You should include all pieces of data to answer the user query, such as the table from code execution results.
If you cannot answer the question directly, you should follow the guidelines above to generate the next step.
If the question can be answered directly with writing any code, you should do that.
If you doesn't have enough data to answer the question, you should ask for clarification from the user.
You should NEVER install any package on your own like `pip install ...`.
When plotting trends, you should make sure to sort and order the data by the x-axis.
""",
code_executor=AgentEngineSandboxCodeExecutor(
# Replace with your sandbox resource name if you already have one.
sandbox_resource_name="SANDBOX_RESOURCE_NAME",
# "projects/vertex-agent-loadtest/locations/us-central1/reasoningEngines/6842889780301135872/sandboxEnvironments/6545148628569161728",
# Replace with agent engine resource name used for creating sandbox if
# sandbox_resource_name is not set.
agent_engine_resource_name="AGENT_ENGINE_RESOURCE_NAME",
),
)
@@ -17,17 +17,19 @@ This agent aims to test the Langchain tool with Langchain's StructuredTool
"""
from google.adk.agents.llm_agent import Agent
from google.adk.tools.langchain_tool import LangchainTool
from langchain.tools import tool
from langchain_core.tools import tool
from langchain_core.tools.structured import StructuredTool
from pydantic import BaseModel
async def add(x, y) -> int:
"""Adds two numbers."""
return x + y
@tool
def minus(x, y) -> int:
"""Subtracts two numbers."""
return x - y
@@ -16,6 +16,7 @@ import random
from google.adk.agents.llm_agent import Agent
from google.adk.examples.example import Example
from google.adk.models.google_llm import Gemini
from google.adk.tools.example_tool import ExampleTool
from google.genai import types
@@ -28,6 +29,17 @@ def roll_die(sides: int) -> int:
roll_agent = Agent(
name="roll_agent",
model=Gemini(
# model="gemini-2.0-flash-live-preview-04-09", # for Vertex project
model="gemini-live-2.5-flash-preview", # for AI studio key
speech_config=types.SpeechConfig(
voice_config=types.VoiceConfig(
prebuilt_voice_config=types.PrebuiltVoiceConfig(
voice_name="Kore",
)
)
),
),
description="Handles rolling dice of different sizes.",
instruction="""
You are responsible for rolling dice based on the user's request.
@@ -69,6 +81,17 @@ def check_prime(nums: list[int]) -> str:
prime_agent = Agent(
name="prime_agent",
model=Gemini(
# model="gemini-2.0-flash-live-preview-04-09", # for Vertex project
model="gemini-live-2.5-flash-preview", # for AI studio key
speech_config=types.SpeechConfig(
voice_config=types.VoiceConfig(
prebuilt_voice_config=types.PrebuiltVoiceConfig(
voice_name="Puck",
)
)
),
),
description="Handles checking if numbers are prime.",
instruction="""
You are responsible for checking whether numbers are prime.
@@ -100,8 +123,17 @@ def get_current_weather(location: str):
root_agent = Agent(
# find supported models here: https://google.github.io/adk-docs/get-started/streaming/quickstart-streaming/
model="gemini-2.0-flash-live-preview-04-09", # for Vertex project
# model="gemini-live-2.5-flash-preview", # for AI studio key
model=Gemini(
# model="gemini-2.0-flash-live-preview-04-09", # for Vertex project
model="gemini-live-2.5-flash-preview", # for AI studio key
speech_config=types.SpeechConfig(
voice_config=types.VoiceConfig(
prebuilt_voice_config=types.PrebuiltVoiceConfig(
voice_name="Zephyr",
)
)
),
),
name="root_agent",
instruction="""
You are a helpful assistant that can check time, roll dice and check if numbers are prime.
@@ -4,37 +4,45 @@
This sample tests and demos the OAuth support in ADK via two tools:
* 1. list_calendar_events
* 1. list_calendar_events
This is a customized tool that calls Google Calendar API to list calendar events.
It pass in the client id and client secrete to ADK and then get back the access token from ADK.
And then it uses the access token to call calendar api.
This is a customized tool that calls Google Calendar API to list calendar
events. It pass in the client id and client secrete to ADK and then get back
the access token from ADK. And then it uses the access token to call
calendar api.
* 2. get_calendar_events
* 2. get_calendar_events
This is an google calendar tool that calls Google Calendar API to get the details of a specific calendar.
This tool is from the ADK built-in Google Calendar ToolSet.
Everything is wrapped and the tool user just needs to pass in the client id and client secret.
This is an google calendar tool that calls Google Calendar API to get the
details of a specific calendar. This tool is from the ADK built-in Google
Calendar ToolSet. Everything is wrapped and the tool user just needs to pass
in the client id and client secret.
## How to use
* 1. Follow https://developers.google.com/identity/protocols/oauth2#1.-obtain-oauth-2.0-credentials-from-the-dynamic_data.setvar.console_name. to get your client id and client secret.
Be sure to choose "web" as your client type.
* 1. Follow
https://developers.google.com/identity/protocols/oauth2#1.-obtain-oauth-2.0-credentials-from-the-dynamic_data.setvar.console_name.
to get your client id and client secret. Be sure to choose "web" as your
client type.
* 2. Configure your `.env` file to add two variables:
* 2. Configure your `.env` file to add two variables:
* OAUTH_CLIENT_ID={your client id}
* OAUTH_CLIENT_SECRET={your client secret}
* OAUTH_CLIENT_ID={your client id}
* OAUTH_CLIENT_SECRET={your client secret}
Note: don't create a separate `.env` file , instead put it to the same `.env` file that stores your Vertex AI or Dev ML credentials
Note: don't create a separate `.env` file , instead put it to the same
`.env` file that stores your Vertex AI or Dev ML credentials
* 3. Follow https://developers.google.com/identity/protocols/oauth2/web-server#creatingcred to add http://localhost/dev-ui/ to "Authorized redirect URIs".
* 3. Follow
https://developers.google.com/identity/protocols/oauth2/web-server#creatingcred
to add http://localhost/dev-ui/ to "Authorized redirect URIs".
Note: localhost here is just a hostname that you use to access the dev ui, replace it with the actual hostname you use to access the dev ui.
Note: localhost here is just a hostname that you use to access the dev ui,
replace it with the actual hostname you use to access the dev ui.
* 4. For 1st run, allow popup for localhost in Chrome.
* 4. For 1st run, allow popup for localhost in Chrome.
## Sample prompt
* `List all my today's meeting from 7am to 7pm.`
* `Get the details of the first event.`
* `List all my today's meeting from 7am to 7pm.`
* `Get the details of the first event.`
@@ -1,22 +1,32 @@
# Output Schema with Tools Sample Agent
This sample demonstrates how to use structured output (`output_schema`) alongside other tools in an ADK agent. Previously, this combination was not allowed, but now it's supported through a special processor that handles the interaction.
This sample demonstrates how to use structured output (`output_schema`)
alongside other tools in an ADK agent. Previously, this combination was not
allowed, but now it's supported through a special processor that handles the
interaction.
## How it Works
The agent combines:
- **Tools**: `search_wikipedia` and `get_current_year` for gathering information
- **Structured Output**: `PersonInfo` schema to ensure consistent response format
- **Tools**: `search_wikipedia` and `get_current_year` for gathering
information
- **Structured Output**: `PersonInfo` schema to ensure consistent response
format
When both `output_schema` and `tools` are specified:
1. ADK automatically adds a special `set_model_response` tool
2. The model can use the regular tools for information gathering
3. For the final response, the model uses `set_model_response` with structured data
4. ADK extracts and validates the structured response
1. ADK automatically adds a special `set_model_response` tool
2. The model can use the regular tools for information gathering
3. For the final response, the model uses `set_model_response` with structured
data
4. ADK extracts and validates the structured response
## Expected Response Format
The agent will return information in this structured format for user query "Tell me about Albert Einstein":
The agent will return information in this structured format for user query
> Tell me about Albert Einstein.
```json
{
@@ -30,7 +40,7 @@ The agent will return information in this structured format for user query "Tell
## Key Features Demonstrated
1. **Tool Usage**: Agent can search Wikipedia and get current year
2. **Structured Output**: Response follows strict PersonInfo schema
3. **Validation**: ADK validates the response matches the schema
4. **Flexibility**: Works with any combination of tools and output schemas
1. **Tool Usage**: Agent can search Wikipedia and get current year
2. **Structured Output**: Response follows strict PersonInfo schema
3. **Validation**: ADK validates the response matches the schema
4. **Flexibility**: Works with any combination of tools and output schemas
+3 -3
View File
@@ -32,7 +32,7 @@ dependencies = [
"click>=8.1.8, <9.0.0", # For CLI tools
"fastapi>=0.115.0, <1.0.0", # FastAPI framework
"google-api-python-client>=2.157.0, <3.0.0", # Google API client discovery
"google-cloud-aiplatform[agent_engines]>=1.112.0, <2.0.0",# For VertexAI integrations, e.g. example store.
"google-cloud-aiplatform[agent_engines]>=1.121.0, <2.0.0",# For VertexAI integrations, e.g. example store.
"google-cloud-bigtable>=2.32.0", # For Bigtable database
"google-cloud-discoveryengine>=0.13.12, <0.14.0", # For Discovery Engine Search Tool
"google-cloud-secret-manager>=2.22.0, <3.0.0", # Fetching secrets in RestAPI Tool
@@ -114,7 +114,7 @@ test = [
"anthropic>=0.43.0", # For anthropic model tests
"kubernetes>=29.0.0", # For GkeCodeExecutor
"langchain-community>=0.3.17",
"langgraph>=0.2.60, <= 0.4.10", # For LangGraphAgent
"langgraph>=0.2.60, <0.4.8", # For LangGraphAgent
"litellm>=1.75.5, <2.0.0", # For LiteLLM tests
"llama-index-readers-file>=0.4.0", # For retrieval tests
"openai>=1.100.2", # For LiteLLM
@@ -144,7 +144,7 @@ extensions = [
"crewai[tools];python_version>='3.10'", # For CrewaiTool
"docker>=7.0.0", # For ContainerCodeExecutor
"kubernetes>=29.0.0", # For GkeCodeExecutor
"langgraph>=0.2.60", # For LangGraphAgent
"langgraph>=0.2.60, <0.4.8", # For LangGraphAgent
"litellm>=1.75.5", # For LiteLlm class. Currently has OpenAI limitations. TODO: once LiteLlm fix it
"llama-index-readers-file>=0.4.0", # For retrieval using LlamaIndex.
"llama-index-embeddings-google-genai>=0.3.0",# For files retrieval using LlamaIndex.
+23 -33
View File
@@ -282,27 +282,22 @@ class BaseAgent(BaseModel):
Event: the events generated by the agent.
"""
async def _run_with_trace() -> AsyncGenerator[Event, None]:
with tracer.start_as_current_span(f'invoke_agent {self.name}') as span:
ctx = self._create_invocation_context(parent_context)
tracing.trace_agent_invocation(span, self, ctx)
if event := await self._handle_before_agent_callback(ctx):
yield event
if ctx.end_invocation:
return
with tracer.start_as_current_span(f'invoke_agent {self.name}') as span:
ctx = self._create_invocation_context(parent_context)
tracing.trace_agent_invocation(span, self, ctx)
if event := await self._handle_before_agent_callback(ctx):
yield event
if ctx.end_invocation:
return
async with Aclosing(self._run_async_impl(ctx)) as agen:
async for event in agen:
yield event
if ctx.end_invocation:
return
if event := await self._handle_after_agent_callback(ctx):
async with Aclosing(self._run_async_impl(ctx)) as agen:
async for event in agen:
yield event
async with Aclosing(_run_with_trace()) as agen:
async for event in agen:
if ctx.end_invocation:
return
if event := await self._handle_after_agent_callback(ctx):
yield event
@final
@@ -320,24 +315,19 @@ class BaseAgent(BaseModel):
Event: the events generated by the agent.
"""
async def _run_with_trace() -> AsyncGenerator[Event, None]:
with tracer.start_as_current_span(f'invoke_agent {self.name}') as span:
ctx = self._create_invocation_context(parent_context)
tracing.trace_agent_invocation(span, self, ctx)
if event := await self._handle_before_agent_callback(ctx):
yield event
if ctx.end_invocation:
return
with tracer.start_as_current_span(f'invoke_agent {self.name}') as span:
ctx = self._create_invocation_context(parent_context)
tracing.trace_agent_invocation(span, self, ctx)
if event := await self._handle_before_agent_callback(ctx):
yield event
if ctx.end_invocation:
return
async with Aclosing(self._run_live_impl(ctx)) as agen:
async for event in agen:
yield event
if event := await self._handle_after_agent_callback(ctx):
async with Aclosing(self._run_live_impl(ctx)) as agen:
async for event in agen:
yield event
async with Aclosing(_run_with_trace()) as agen:
async for event in agen:
if event := await self._handle_after_agent_callback(ctx):
yield event
async def _run_async_impl(
+1 -1
View File
@@ -143,7 +143,7 @@ Examples:
```
# tools.py
my_mcp_toolset = MCPToolset(
my_mcp_toolset = McpToolset(
connection_params=StdioServerParameters(
command="npx",
args=["-y", "@notionhq/notion-mcp-server"],
+4 -1
View File
@@ -35,7 +35,10 @@ class StreamingMode(Enum):
class RunConfig(BaseModel):
"""Configs for runtime behavior of agents."""
"""Configs for runtime behavior of agents.
The configs here will be overriden by agent-specific configurations.
"""
model_config = ConfigDict(
extra='forbid',
+46 -35
View File
@@ -63,6 +63,7 @@ from ..agents.run_config import StreamingMode
from ..apps.app import App
from ..artifacts.base_artifact_service import BaseArtifactService
from ..auth.credential_service.base_credential_service import BaseCredentialService
from ..errors.already_exists_error import AlreadyExistsError
from ..errors.not_found_error import NotFoundError
from ..evaluation.base_eval_service import InferenceConfig
from ..evaluation.base_eval_service import InferenceRequest
@@ -486,6 +487,12 @@ class AdkWebServer:
self.runner_dict[app_name] = runner
return runner
def _get_root_agent(self, agent_or_app: BaseAgent | App) -> BaseAgent:
"""Extract root agent from either a BaseAgent or App object."""
if isinstance(agent_or_app, App):
return agent_or_app.root_agent
return agent_or_app
def _create_runner(self, agentic_app: App) -> Runner:
"""Create a runner with common services."""
return Runner(
@@ -577,6 +584,33 @@ class AdkWebServer:
"Failed to write runtime config file %s: %s", runtime_config_path, e
)
async def _create_session(
self,
*,
app_name: str,
user_id: str,
session_id: Optional[str] = None,
state: Optional[dict[str, Any]] = None,
) -> Session:
try:
session = await self.session_service.create_session(
app_name=app_name,
user_id=user_id,
state=state,
session_id=session_id,
)
logger.info("New session created: %s", session.id)
return session
except AlreadyExistsError as e:
raise HTTPException(
status_code=409, detail=f"Session already exists: {session_id}"
) from e
except Exception as e:
logger.error(
"Internal server error during session creation: %s", e, exc_info=True
)
raise HTTPException(status_code=500, detail=str(e)) from e
def get_fast_api_app(
self,
lifespan: Optional[Lifespan[FastAPI]] = None,
@@ -734,20 +768,12 @@ class AdkWebServer:
session_id: str,
state: Optional[dict[str, Any]] = None,
) -> Session:
if (
await self.session_service.get_session(
app_name=app_name, user_id=user_id, session_id=session_id
)
is not None
):
raise HTTPException(
status_code=409, detail=f"Session already exists: {session_id}"
)
session = await self.session_service.create_session(
app_name=app_name, user_id=user_id, state=state, session_id=session_id
return await self._create_session(
app_name=app_name,
user_id=user_id,
state=state,
session_id=session_id,
)
logger.info("New session created: %s", session_id)
return session
@app.post(
"/apps/{app_name}/users/{user_id}/sessions",
@@ -759,18 +785,9 @@ class AdkWebServer:
req: Optional[CreateSessionRequest] = None,
) -> Session:
if not req:
return await self.session_service.create_session(
app_name=app_name, user_id=user_id
)
return await self._create_session(app_name=app_name, user_id=user_id)
if req.session_id and await self.session_service.get_session(
app_name=app_name, user_id=user_id, session_id=req.session_id
):
raise HTTPException(
status_code=409, detail=f"Session already exists: {req.session_id}"
)
session = await self.session_service.create_session(
session = await self._create_session(
app_name=app_name,
user_id=user_id,
state=req.state,
@@ -933,9 +950,8 @@ class AdkWebServer:
# Populate the session with initial session state.
agent_or_app = self.agent_loader.load_agent(app_name)
if isinstance(agent_or_app, App):
agent_or_app = agent_or_app.root_agent
initial_session_state = create_empty_state(agent_or_app)
root_agent = self._get_root_agent(agent_or_app)
initial_session_state = create_empty_state(root_agent)
new_eval_case = EvalCase(
eval_id=req.eval_id,
@@ -1096,7 +1112,8 @@ class AdkWebServer:
status_code=400, detail=f"Eval set `{eval_set_id}` not found."
)
root_agent = self.agent_loader.load_agent(app_name)
agent_or_app = self.agent_loader.load_agent(app_name)
root_agent = self._get_root_agent(agent_or_app)
eval_case_results = []
@@ -1437,13 +1454,7 @@ class AdkWebServer:
function_calls = event.get_function_calls()
function_responses = event.get_function_responses()
agent_or_app = self.agent_loader.load_agent(app_name)
# The loader may return an App; unwrap to its root agent so the graph builder
# receives a BaseAgent instance.
root_agent = (
agent_or_app.root_agent
if isinstance(agent_or_app, App)
else agent_or_app
)
root_agent = self._get_root_agent(agent_or_app)
dot_graph = None
if function_calls:
function_call_highlights = []
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+10 -4
View File
@@ -127,7 +127,7 @@ def conformance():
pass
@conformance.command("create", cls=HelpfulCommand)
@conformance.command("record", cls=HelpfulCommand)
@click.argument(
"paths",
nargs=-1,
@@ -136,7 +136,7 @@ def conformance():
),
)
@click.pass_context
def cli_conformance_create(
def cli_conformance_record(
ctx,
paths: tuple[str, ...],
):
@@ -162,7 +162,7 @@ def cli_conformance_create(
"""
try:
from .conformance.cli_create import run_conformance_create
from .conformance.cli_record import run_conformance_record
except ImportError as e:
click.secho(
f"Error: Missing conformance testing dependencies: {e}",
@@ -178,7 +178,7 @@ def cli_conformance_create(
# Default to tests/ directory if no paths provided
test_paths = [Path(p) for p in paths] if paths else [Path("tests").resolve()]
asyncio.run(run_conformance_create(test_paths))
asyncio.run(run_conformance_record(test_paths))
@conformance.command("test", cls=HelpfulCommand)
@@ -549,6 +549,7 @@ def cli_eval(
from ..evaluation.local_eval_set_results_manager import LocalEvalSetResultsManager
from ..evaluation.local_eval_sets_manager import load_eval_set_from_file
from ..evaluation.local_eval_sets_manager import LocalEvalSetsManager
from ..evaluation.user_simulator_provider import UserSimulatorProvider
from .cli_eval import _collect_eval_results
from .cli_eval import _collect_inferences
from .cli_eval import get_root_agent
@@ -638,11 +639,16 @@ def cli_eval(
)
)
user_simulator_provider = UserSimulatorProvider(
user_simulator_config=eval_config.user_simulator_config
)
try:
eval_service = LocalEvalService(
root_agent=root_agent,
eval_sets_manager=eval_sets_manager,
eval_set_results_manager=eval_set_results_manager,
user_simulator_provider=user_simulator_provider,
)
inference_results = asyncio.run(
@@ -105,7 +105,7 @@ async def _create_conformance_test_files(
return generated_session_file
async def run_conformance_create(paths: list[Path]) -> None:
async def run_conformance_record(paths: list[Path]) -> None:
"""Generate conformance tests from TestCaseInput files.
Args:

Some files were not shown because too many files have changed in this diff Show More