Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions contributing/samples/data_agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Data Agent Sample

This sample agent demonstrates ADK's first-party tools for interacting with
Data Agents powered by [Conversational Analytics API](https://docs.cloud.google.com/gemini/docs/conversational-analytics-api/overview).
These tools are distributed via
the `google.adk.tools.data_agent` module and allow you to list,
inspect, and
chat with Data Agents using natural language.

These tools leverage stateful conversations, meaning you can ask follow-up
questions in the same session, and the agent will maintain context.

## Prerequisites

1. An active Google Cloud project with BigQuery and Gemini APIs enabled.
2. Google Cloud authentication configured for Application Default Credentials:
```bash
gcloud auth application-default login
```
3. At least one Data Agent created. You could create data agents via
[Conversational API](https://docs.cloud.google.com/gemini/docs/conversational-analytics-api/overview),
its
[Python SDK](https://docs.cloud.google.com/gemini/docs/conversational-analytics-api/build-agent-sdk),
or for BigQuery data
[BigQuery Studio](https://docs.cloud.google.com/bigquery/docs/create-data-agents#create_a_data_agent).
These agents are created and configured in the Google Cloud console and
point to your BigQuery tables or other data sources.
4. Follow the official
[Setup and prerequisites](https://docs.cloud.google.com/gemini/docs/conversational-analytics-api/overview#setup)
guide to enable the API and configure IAM permissions and authentication for
your data sources.

## Tools Used

* `list_accessible_data_agents`: Lists Data Agents you have permission to
access in the configured GCP project.
* `get_data_agent_info`: Retrieves details about a specific Data Agent given
its full resource name.
* `ask_data_agent`: Chats with a specific Data Agent using natural language.
This tool maintains conversation state: if you ask multiple
questions to the same agent in one session, it will use the same
conversation, allowing for follow-ups. If you switch agents, a new
conversation will be started for the new agent.

## How to Run

1. Navigate to the root of the ADK repository.
2. Run the agent using the ADK CLI:
```bash
adk run --agent-path contributing/samples/data_agent
```
3. The CLI will prompt you for input. You can ask questions like the examples
below.

## Sample prompts

* "List accessible data agents."
* "Using agent
`projects/my-project/locations/global/dataAgents/sales-agent-123`, who were
my top 3 customers last quarter?"
* "How does that compare to the quarter before?"
15 changes: 15 additions & 0 deletions contributing/samples/data_agent/__init__.py
Original file line number Diff line number Diff line change
@@ -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
84 changes: 84 additions & 0 deletions contributing/samples/data_agent/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# 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 os

from google.adk.agents import Agent
from google.adk.auth.auth_credential import AuthCredentialTypes
from google.adk.tools.data_agent.config import DataAgentToolConfig
from google.adk.tools.data_agent.credentials import DataAgentCredentialsConfig
from google.adk.tools.data_agent.data_agent_toolset import DataAgentToolset
import google.auth
import google.auth.transport.requests

# Define the desired credential type.
# By default use Application Default Credentials (ADC) from the local
# environment, which can be set up by following
# https://cloud.google.com/docs/authentication/provide-credentials-adc.
CREDENTIALS_TYPE = None

if CREDENTIALS_TYPE == AuthCredentialTypes.OAUTH2:
# Initiaze the tools to do interactive OAuth
# The environment variables OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET
# must be set
credentials_config = DataAgentCredentialsConfig(
client_id=os.getenv("OAUTH_CLIENT_ID"),
client_secret=os.getenv("OAUTH_CLIENT_SECRET"),
)
elif CREDENTIALS_TYPE == AuthCredentialTypes.SERVICE_ACCOUNT:
# Initialize the tools to use the credentials in the service account key.
# If this flow is enabled, make sure to replace the file path with your own
# service account key file
# https://cloud.google.com/iam/docs/service-account-creds#user-managed-keys
creds, _ = google.auth.load_credentials_from_file(
"service_account_key.json",
scopes=["https://www.googleapis.com/auth/cloud-platform"],
)
creds.refresh(google.auth.transport.requests.Request())
credentials_config = DataAgentCredentialsConfig(credentials=creds)
else:
# Initialize the tools to use the application default credentials.
# https://cloud.google.com/docs/authentication/provide-credentials-adc
application_default_credentials, _ = google.auth.default()
credentials_config = DataAgentCredentialsConfig(
credentials=application_default_credentials
)

tool_config = DataAgentToolConfig(
max_query_result_rows=100,
)
da_toolset = DataAgentToolset(
credentials_config=credentials_config,
data_agent_tool_config=tool_config,
tool_filter=[
"list_accessible_data_agents",
"get_data_agent_info",
"ask_data_agent",
],
)

root_agent = Agent(
name="data_agent",
model="gemini-2.0-flash",
description="Agent to answer user questions using Data Agents.",
instruction=(
"## Persona\nYou are a helpful assistant that uses Data Agents"
" to answer user questions about their data.\n\n## Tools\n- You can"
" list available data agents using `list_accessible_data_agents`.\n-"
" You can get information about a specific data agent using"
" `get_data_agent_info`.\n- You can chat with a specific data"
" agent using `ask_data_agent`.\n"
),
tools=[da_toolset],
)
8 changes: 8 additions & 0 deletions src/google/adk/features/_feature_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class FeatureName(str, Enum):
BIGTABLE_TOOL_SETTINGS = "BIGTABLE_TOOL_SETTINGS"
BIGTABLE_TOOLSET = "BIGTABLE_TOOLSET"
COMPUTER_USE = "COMPUTER_USE"
DATA_AGENT_TOOL_CONFIG = "DATA_AGENT_TOOL_CONFIG"
DATA_AGENT_TOOLSET = "DATA_AGENT_TOOLSET"
GOOGLE_CREDENTIALS_CONFIG = "GOOGLE_CREDENTIALS_CONFIG"
GOOGLE_TOOL = "GOOGLE_TOOL"
JSON_SCHEMA_FOR_FUNC_DECL = "JSON_SCHEMA_FOR_FUNC_DECL"
Expand Down Expand Up @@ -97,6 +99,12 @@ class FeatureConfig:
FeatureName.COMPUTER_USE: FeatureConfig(
FeatureStage.EXPERIMENTAL, default_on=True
),
FeatureName.DATA_AGENT_TOOL_CONFIG: FeatureConfig(
FeatureStage.EXPERIMENTAL, default_on=True
),
FeatureName.DATA_AGENT_TOOLSET: FeatureConfig(
FeatureStage.EXPERIMENTAL, default_on=True
),
FeatureName.GOOGLE_CREDENTIALS_CONFIG: FeatureConfig(
FeatureStage.EXPERIMENTAL, default_on=True
),
Expand Down
77 changes: 66 additions & 11 deletions src/google/adk/tools/agent_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
from __future__ import annotations

from typing import Any
from typing import Optional
from typing import TYPE_CHECKING

from google.genai import types
from pydantic import BaseModel
from pydantic import model_validator
from typing_extensions import override

Expand All @@ -37,6 +39,56 @@
from ..agents.base_agent import BaseAgent


def _get_input_schema(agent: BaseAgent) -> Optional[type[BaseModel]]:
"""Extracts the input_schema from an agent.

For LlmAgent, returns its input_schema directly.
For agents with sub_agents, recursively searches the first sub-agent for an
input_schema.

Args:
agent: The agent to extract input_schema from.

Returns:
The input_schema if found, None otherwise.
"""
from ..agents.llm_agent import LlmAgent

if isinstance(agent, LlmAgent):
return agent.input_schema

# For composite agents, check the first sub-agent
if agent.sub_agents:
return _get_input_schema(agent.sub_agents[0])

return None


def _get_output_schema(agent: BaseAgent) -> Optional[type[BaseModel]]:
"""Extracts the output_schema from an agent.

For LlmAgent, returns its output_schema directly.
For agents with sub_agents, recursively searches the last sub-agent for an
output_schema.

Args:
agent: The agent to extract output_schema from.

Returns:
The output_schema if found, None otherwise.
"""
from ..agents.llm_agent import LlmAgent

if isinstance(agent, LlmAgent):
return agent.output_schema

# For composite agents, check the last sub-agent
if agent.sub_agents:
return _get_output_schema(agent.sub_agents[-1])

return None


class AgentTool(BaseTool):
"""A tool that wraps an agent.

Expand Down Expand Up @@ -74,12 +126,14 @@ def populate_name(cls, data: Any) -> Any:

@override
def _get_declaration(self) -> types.FunctionDeclaration:
from ..agents.llm_agent import LlmAgent
from ..utils.variant_utils import GoogleLLMVariant

if isinstance(self.agent, LlmAgent) and self.agent.input_schema:
input_schema = _get_input_schema(self.agent)
output_schema = _get_output_schema(self.agent)

if input_schema:
result = _automatic_function_calling_util.build_function_declaration(
func=self.agent.input_schema, variant=self._api_variant
func=input_schema, variant=self._api_variant
)
# Override the description with the agent's description
result.description = self.agent.description
Expand Down Expand Up @@ -114,7 +168,7 @@ def _get_declaration(self) -> types.FunctionDeclaration:
# Set response schema for non-GEMINI_API variants
if self._api_variant != GoogleLLMVariant.GEMINI_API:
# Determine response type based on agent's output schema
if isinstance(self.agent, LlmAgent) and self.agent.output_schema:
if output_schema:
# Agent has structured output schema - response is an object
if is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL):
result.response_json_schema = {'type': 'object'}
Expand All @@ -137,15 +191,15 @@ async def run_async(
args: dict[str, Any],
tool_context: ToolContext,
) -> Any:
from ..agents.llm_agent import LlmAgent
from ..runners import Runner
from ..sessions.in_memory_session_service import InMemorySessionService

if self.skip_summarization:
tool_context.actions.skip_summarization = True

if isinstance(self.agent, LlmAgent) and self.agent.input_schema:
input_value = self.agent.input_schema.model_validate(args)
input_schema = _get_input_schema(self.agent)
if input_schema:
input_value = input_schema.model_validate(args)
content = types.Content(
role='user',
parts=[
Expand Down Expand Up @@ -212,10 +266,11 @@ async def run_async(
merged_text = '\n'.join(
p.text for p in last_content.parts if p.text and not p.thought
)
if isinstance(self.agent, LlmAgent) and self.agent.output_schema:
tool_result = self.agent.output_schema.model_validate_json(
merged_text
).model_dump(exclude_none=True)
output_schema = _get_output_schema(self.agent)
if output_schema:
tool_result = output_schema.model_validate_json(merged_text).model_dump(
exclude_none=True
)
else:
tool_result = merged_text
return tool_result
Expand Down
25 changes: 25 additions & 0 deletions src/google/adk/tools/data_agent/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 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.

"""Data Agent Tools."""

from __future__ import annotations

from .credentials import DataAgentCredentialsConfig
from .data_agent_toolset import DataAgentToolset

__all__ = [
"DataAgentCredentialsConfig",
"DataAgentToolset",
]
35 changes: 35 additions & 0 deletions src/google/adk/tools/data_agent/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# 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 __future__ import annotations

from pydantic import BaseModel
from pydantic import ConfigDict

from ...features import experimental
from ...features import FeatureName


@experimental(FeatureName.DATA_AGENT_TOOL_CONFIG)
class DataAgentToolConfig(BaseModel):
"""Configuration for Data Agent tools."""

# Forbid any fields not defined in the model
model_config = ConfigDict(extra='forbid')

max_query_result_rows: int = 50
"""Maximum number of rows to return from a query.
By default, the query result will be limited to 50 rows.
"""
Loading