diff --git a/contributing/samples/bigquery/agent.py b/contributing/samples/bigquery/agent.py
index 39663e063b..0999ca12ac 100644
--- a/contributing/samples/bigquery/agent.py
+++ b/contributing/samples/bigquery/agent.py
@@ -17,11 +17,15 @@
from google.adk.agents import llm_agent
from google.adk.tools.bigquery import BigQueryCredentialsConfig
from google.adk.tools.bigquery import BigQueryToolset
+from google.adk.tools.bigquery.config import BigQueryToolConfig
+from google.adk.tools.bigquery.config import WriteMode
import google.auth
RUN_WITH_ADC = False
+tool_config = BigQueryToolConfig(write_mode=WriteMode.ALLOWED)
+
if RUN_WITH_ADC:
# Initialize the tools to use the application default credentials.
application_default_credentials, _ = google.auth.default()
@@ -37,7 +41,9 @@
client_secret=os.getenv("OAUTH_CLIENT_SECRET"),
)
-bigquery_toolset = BigQueryToolset(credentials_config=credentials_config)
+bigquery_toolset = BigQueryToolset(
+ credentials_config=credentials_config, bigquery_tool_config=tool_config
+)
# The variable name `root_agent` determines what your root agent is for the
# debug CLI
diff --git a/contributing/samples/integration_connector_euc_agent/__init__.py b/contributing/samples/integration_connector_euc_agent/__init__.py
index 02c597e11e..c48963cdc7 100644
--- a/contributing/samples/integration_connector_euc_agent/__init__.py
+++ b/contributing/samples/integration_connector_euc_agent/__init__.py
@@ -1 +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/tests/integration/fixture/customer_support_ma/__init__.py b/contributing/samples/rag_agent/__init__.py
similarity index 100%
rename from tests/integration/fixture/customer_support_ma/__init__.py
rename to contributing/samples/rag_agent/__init__.py
diff --git a/contributing/samples/rag_agent/agent.py b/contributing/samples/rag_agent/agent.py
new file mode 100644
index 0000000000..3c6dca8df5
--- /dev/null
+++ b/contributing/samples/rag_agent/agent.py
@@ -0,0 +1,51 @@
+# 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 os
+
+from dotenv import load_dotenv
+from google.adk.agents import Agent
+from google.adk.tools.retrieval.vertex_ai_rag_retrieval import VertexAiRagRetrieval
+from vertexai.preview import rag
+
+load_dotenv()
+
+ask_vertex_retrieval = VertexAiRagRetrieval(
+ name="retrieve_rag_documentation",
+ description=(
+ "Use this tool to retrieve documentation and reference materials for"
+ " the question from the RAG corpus,"
+ ),
+ rag_resources=[
+ rag.RagResource(
+ # please fill in your own rag corpus
+ # e.g. projects/123/locations/us-central1/ragCorpora/456
+ rag_corpus=os.environ.get("RAG_CORPUS"),
+ )
+ ],
+ similarity_top_k=1,
+ vector_distance_threshold=0.6,
+)
+
+root_agent = Agent(
+ model="gemini-2.0-flash-001",
+ name="root_agent",
+ instruction=(
+ "You are an AI assistant with access to specialized corpus of"
+ " documents. Your role is to provide accurate and concise answers to"
+ " questions based on documents that are retrievable using"
+ " ask_vertex_retrieval."
+ ),
+ tools=[ask_vertex_retrieval],
+)
diff --git a/contributing/samples/session_state_agent/__init__.py b/contributing/samples/session_state_agent/__init__.py
index 02c597e11e..c48963cdc7 100644
--- a/contributing/samples/session_state_agent/__init__.py
+++ b/contributing/samples/session_state_agent/__init__.py
@@ -1 +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/src/google/adk/agents/llm_agent.py b/src/google/adk/agents/llm_agent.py
index 6f211f4936..fe145a60e0 100644
--- a/src/google/adk/agents/llm_agent.py
+++ b/src/google/adk/agents/llm_agent.py
@@ -129,7 +129,7 @@ class LlmAgent(BaseAgent):
global_instruction: Union[str, InstructionProvider] = ''
"""Instructions for all the agents in the entire agent tree.
- global_instruction ONLY takes effect in root agent.
+ ONLY the global_instruction in root agent will take effect.
For example: use global_instruction to make all agents have a stable identity
or personality.
@@ -204,11 +204,6 @@ class LlmAgent(BaseAgent):
"""
# Advance features - End
- # TODO: remove below fields after migration. - Start
- # These fields are added back for easier migration.
- examples: Optional[ExamplesUnion] = None
- # TODO: remove above fields after migration. - End
-
# Callbacks - Start
before_model_callback: Optional[BeforeModelCallback] = None
"""Callback or list of callbacks to be called before calling the LLM.
diff --git a/src/google/adk/approval/approval_policy.py b/src/google/adk/approval/approval_policy.py
index 1a0dada26b..6c8c705e4e 100644
--- a/src/google/adk/approval/approval_policy.py
+++ b/src/google/adk/approval/approval_policy.py
@@ -128,8 +128,13 @@ def register_tool_policy(cls, policy: FunctionToolPolicy):
"""
if policy.policy_name is None:
raise ValueError("Policy name cannot be None")
- if policy not in cls.tool_policies:
- cls.tool_policies.append(policy)
+ for existing_policy in cls.tool_policies:
+ if existing_policy.policy_name != policy.policy_name:
+ continue
+ if existing_policy.actions != policy.actions:
+ continue
+ return
+ cls.tool_policies.append(policy)
@classmethod
def get_tool_policies(cls, tool_name: str) -> list[FunctionToolPolicy]:
diff --git a/src/google/adk/auth/auth_handler.py b/src/google/adk/auth/auth_handler.py
index a0cabc28e5..1607dcadf5 100644
--- a/src/google/adk/auth/auth_handler.py
+++ b/src/google/adk/auth/auth_handler.py
@@ -112,7 +112,7 @@ def exchange_auth_token(
def parse_and_store_auth_response(self, state: State) -> None:
- credential_key = self.get_credential_key()
+ credential_key = "temp:" + self.auth_config.get_credential_key()
state[credential_key] = self.auth_config.exchanged_auth_credential
if not isinstance(
@@ -130,7 +130,7 @@ def _validate(self) -> None:
raise ValueError("auth_scheme is empty.")
def get_auth_response(self, state: State) -> AuthCredential:
- credential_key = self.get_credential_key()
+ credential_key = "temp:" + self.auth_config.get_credential_key()
return state.get(credential_key, None)
def generate_auth_request(self) -> AuthConfig:
@@ -192,29 +192,6 @@ def generate_auth_request(self) -> AuthConfig:
exchanged_auth_credential=exchanged_credential,
)
- def get_credential_key(self) -> str:
- """Generates a unique key for the given auth scheme and credential."""
- auth_scheme = self.auth_config.auth_scheme
- auth_credential = self.auth_config.raw_auth_credential
- if auth_scheme.model_extra:
- auth_scheme = auth_scheme.model_copy(deep=True)
- auth_scheme.model_extra.clear()
- scheme_name = (
- f"{auth_scheme.type_.name}_{hash(auth_scheme.model_dump_json())}"
- if auth_scheme
- else ""
- )
- if auth_credential.model_extra:
- auth_credential = auth_credential.model_copy(deep=True)
- auth_credential.model_extra.clear()
- credential_name = (
- f"{auth_credential.auth_type.value}_{hash(auth_credential.model_dump_json())}"
- if auth_credential
- else ""
- )
-
- return f"temp:adk_{scheme_name}_{credential_name}"
-
def generate_auth_uri(
self,
) -> AuthCredential:
diff --git a/src/google/adk/auth/auth_tool.py b/src/google/adk/auth/auth_tool.py
index d560424329..a1a19ab876 100644
--- a/src/google/adk/auth/auth_tool.py
+++ b/src/google/adk/auth/auth_tool.py
@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import annotations
+
from .auth_credential import AuthCredential
from .auth_credential import BaseModelWithConfig
from .auth_schemes import AuthScheme
@@ -43,6 +45,34 @@ class AuthConfig(BaseModelWithConfig):
this field to guide the user through the OAuth2 flow and fill auth response in
this field"""
+ def get_credential_key(self):
+ """Generates a hash key based on auth_scheme and raw_auth_credential. This
+ hash key can be used to store / retrieve exchanged_auth_credential in a
+ credentials store.
+ """
+ auth_scheme = self.auth_scheme
+
+ if auth_scheme.model_extra:
+ auth_scheme = auth_scheme.model_copy(deep=True)
+ auth_scheme.model_extra.clear()
+ scheme_name = (
+ f"{auth_scheme.type_.name}_{hash(auth_scheme.model_dump_json())}"
+ if auth_scheme
+ else ""
+ )
+
+ auth_credential = self.raw_auth_credential
+ if auth_credential.model_extra:
+ auth_credential = auth_credential.model_copy(deep=True)
+ auth_credential.model_extra.clear()
+ credential_name = (
+ f"{auth_credential.auth_type.value}_{hash(auth_credential.model_dump_json())}"
+ if auth_credential
+ else ""
+ )
+
+ return f"adk_{scheme_name}_{credential_name}"
+
class AuthToolArguments(BaseModelWithConfig):
"""the arguments for the special long running function tool that is used to
diff --git a/src/google/adk/cli/agent_graph.py b/src/google/adk/cli/agent_graph.py
index a0b8a467df..249e5bfb70 100644
--- a/src/google/adk/cli/agent_graph.py
+++ b/src/google/adk/cli/agent_graph.py
@@ -64,11 +64,11 @@ def get_node_name(tool_or_agent: Union[BaseAgent, BaseTool]):
if isinstance(tool_or_agent, BaseAgent):
# Added Workflow Agent checks for different agent types
if isinstance(tool_or_agent, SequentialAgent):
- return tool_or_agent.name + f' (Sequential Agent)'
+ return tool_or_agent.name + ' (Sequential Agent)'
elif isinstance(tool_or_agent, LoopAgent):
- return tool_or_agent.name + f' (Loop Agent)'
+ return tool_or_agent.name + ' (Loop Agent)'
elif isinstance(tool_or_agent, ParallelAgent):
- return tool_or_agent.name + f' (Parallel Agent)'
+ return tool_or_agent.name + ' (Parallel Agent)'
else:
return tool_or_agent.name
elif isinstance(tool_or_agent, BaseTool):
@@ -144,49 +144,53 @@ def should_build_agent_cluster(tool_or_agent: Union[BaseAgent, BaseTool]):
)
return False
- def build_cluster(child: graphviz.Digraph, agent: BaseAgent, name: str):
- if isinstance(agent, LoopAgent) and parent_agent:
+ async def build_cluster(child: graphviz.Digraph, agent: BaseAgent, name: str):
+ if isinstance(agent, LoopAgent):
# Draw the edge from the parent agent to the first sub-agent
- draw_edge(parent_agent.name, agent.sub_agents[0].name)
+ if parent_agent:
+ draw_edge(parent_agent.name, agent.sub_agents[0].name)
length = len(agent.sub_agents)
- currLength = 0
+ curr_length = 0
# Draw the edges between the sub-agents
for sub_agent_int_sequential in agent.sub_agents:
- build_graph(child, sub_agent_int_sequential, highlight_pairs)
+ await build_graph(child, sub_agent_int_sequential, highlight_pairs)
# Draw the edge between the current sub-agent and the next one
# If it's the last sub-agent, draw an edge to the first one to indicating a loop
draw_edge(
- agent.sub_agents[currLength].name,
+ agent.sub_agents[curr_length].name,
agent.sub_agents[
- 0 if currLength == length - 1 else currLength + 1
+ 0 if curr_length == length - 1 else curr_length + 1
].name,
)
- currLength += 1
- elif isinstance(agent, SequentialAgent) and parent_agent:
+ curr_length += 1
+ elif isinstance(agent, SequentialAgent):
# Draw the edge from the parent agent to the first sub-agent
- draw_edge(parent_agent.name, agent.sub_agents[0].name)
+ if parent_agent:
+ draw_edge(parent_agent.name, agent.sub_agents[0].name)
length = len(agent.sub_agents)
- currLength = 0
+ curr_length = 0
# Draw the edges between the sub-agents
for sub_agent_int_sequential in agent.sub_agents:
- build_graph(child, sub_agent_int_sequential, highlight_pairs)
+ await build_graph(child, sub_agent_int_sequential, highlight_pairs)
# Draw the edge between the current sub-agent and the next one
# If it's the last sub-agent, don't draw an edge to avoid a loop
- draw_edge(
- agent.sub_agents[currLength].name,
- agent.sub_agents[currLength + 1].name,
- ) if currLength != length - 1 else None
- currLength += 1
+ if curr_length != length - 1:
+ draw_edge(
+ agent.sub_agents[curr_length].name,
+ agent.sub_agents[curr_length + 1].name,
+ )
+ curr_length += 1
- elif isinstance(agent, ParallelAgent) and parent_agent:
+ elif isinstance(agent, ParallelAgent):
# Draw the edge from the parent agent to every sub-agent
for sub_agent in agent.sub_agents:
- build_graph(child, sub_agent, highlight_pairs)
- draw_edge(parent_agent.name, sub_agent.name)
+ await build_graph(child, sub_agent, highlight_pairs)
+ if parent_agent:
+ draw_edge(parent_agent.name, sub_agent.name)
else:
for sub_agent in agent.sub_agents:
- build_graph(child, sub_agent, highlight_pairs)
+ await build_graph(child, sub_agent, highlight_pairs)
draw_edge(agent.name, sub_agent.name)
child.attr(
@@ -196,21 +200,20 @@ def build_cluster(child: graphviz.Digraph, agent: BaseAgent, name: str):
fontcolor=light_gray,
)
- def draw_node(tool_or_agent: Union[BaseAgent, BaseTool]):
+ async def draw_node(tool_or_agent: Union[BaseAgent, BaseTool]):
name = get_node_name(tool_or_agent)
shape = get_node_shape(tool_or_agent)
caption = get_node_caption(tool_or_agent)
- asCluster = should_build_agent_cluster(tool_or_agent)
- child = None
+ as_cluster = should_build_agent_cluster(tool_or_agent)
if highlight_pairs:
for highlight_tuple in highlight_pairs:
if name in highlight_tuple:
# if in highlight, draw highlight node
- if asCluster:
+ if as_cluster:
cluster = graphviz.Digraph(
name='cluster_' + name
) # adding "cluster_" to the name makes the graph render as a cluster subgraph
- build_cluster(cluster, agent, name)
+ await build_cluster(cluster, agent, name)
graph.subgraph(cluster)
else:
graph.node(
@@ -224,12 +227,12 @@ def draw_node(tool_or_agent: Union[BaseAgent, BaseTool]):
)
return
# if not in highlight, draw non-highlight node
- if asCluster:
+ if as_cluster:
cluster = graphviz.Digraph(
name='cluster_' + name
) # adding "cluster_" to the name makes the graph render as a cluster subgraph
- build_cluster(cluster, agent, name)
+ await build_cluster(cluster, agent, name)
graph.subgraph(cluster)
else:
@@ -264,10 +267,9 @@ def draw_edge(from_name, to_name):
else:
graph.edge(from_name, to_name, arrowhead='none', color=light_gray)
- draw_node(agent)
+ await draw_node(agent)
for sub_agent in agent.sub_agents:
-
- build_graph(graph, sub_agent, highlight_pairs, agent)
+ await build_graph(graph, sub_agent, highlight_pairs, agent)
if not should_build_agent_cluster(
sub_agent
) and not should_build_agent_cluster(
@@ -276,7 +278,7 @@ def draw_edge(from_name, to_name):
draw_edge(agent.name, sub_agent.name)
if isinstance(agent, LlmAgent):
for tool in await agent.canonical_tools():
- draw_node(tool)
+ await draw_node(tool)
draw_edge(agent.name, get_node_name(tool))
diff --git a/src/google/adk/cli/browser/index.html b/src/google/adk/cli/browser/index.html
index 97ebf041e3..a629979cee 100644
--- a/src/google/adk/cli/browser/index.html
+++ b/src/google/adk/cli/browser/index.html
@@ -29,5 +29,5 @@
-
+