工具(Tools)¶
什麼是工具(Tool)?¶
在 Agent Development Kit (ADK) 的語境下,工具(Tool)代表賦予 AI agent 特定能力的元件,使其能執行動作並與外部世界互動,突破僅有文字生成與推理的核心能力。能夠有效運用工具,正是有能力的 agent 與基礎大型語言模型 (LLM) 之間的主要區別。
從技術層面來看,工具通常是一個模組化的程式碼元件——例如 Python/Java 函式、類別方法,甚至是另一個專門的 agent——設計來執行明確、預定義的任務。這些任務往往涉及與外部系統或資料的互動。

主要特點¶
以行動為導向: 工具用於執行特定動作,例如:
- 查詢資料庫
- 發送 API 請求(例如取得天氣資料、預訂系統)
- 網頁搜尋
- 執行程式碼片段
- 從文件中擷取資訊(RAG)
- 與其他軟體或服務互動
擴展 agent 能力: 工具讓 agent 能夠存取即時資訊、影響外部系統,並突破訓練資料本身的知識限制。
執行預定義邏輯: 關鍵在於,工具僅執行特定、由開發者定義的邏輯。工具本身並不具備像 agent 的核心大型語言模型 (LLM) 那樣的獨立推理能力。LLM 負責判斷何時、以何種輸入使用哪個工具,而工具本身僅執行其指定的功能。
agent 如何使用工具¶
agent 會透過函式呼叫等機制動態運用工具。一般流程如下:
- 推理: agent 的大型語言模型 (LLM) 會分析系統指令、對話歷史與使用者請求。
- 選擇: 根據分析結果,LLM 會根據 agent 可用的工具及每個工具的 docstring,決定是否執行某個工具。
- 呼叫: LLM 產生所需的參數(輸入),並觸發選定工具的執行。
- 觀察: agent 接收工具回傳的輸出(結果)。
- 整合: agent 將工具的輸出納入持續的推理過程,用於產生下一步回應、決定後續動作,或判斷目標是否已完成。
你可以將工具想像成 agent 智慧核心(LLM)可隨需取用的專業工具箱,協助完成複雜任務。
ADK 中的工具類型¶
Agent Development Kit (ADK) 支援多種工具類型,靈活滿足不同需求:
- Function Tools: 由你自行建立、針對應用需求設計的工具。
- Functions/Methods: 在程式碼中定義標準同步函式或方法(如 Python 的 def)。
- Agents-as-Tools: 將另一個(可能是專門的)agent 作為父 agent 的工具使用。
- Long Running Function Tools: 支援執行非同步操作或需較長時間完成的工具。
- Built-in Tools: 框架內建、可直接使用的常用工具。 例如:Google Search、程式碼執行、檢索增強生成(RAG)。
- Third-Party Tools: 無縫整合自熱門外部函式庫的工具。 例如:LangChain Tools、CrewAI Tools。
請參閱上方連結的各類型工具文件頁面,獲取詳細說明與範例。
在 agent 指令中引用工具¶
在 agent 的指令中,你可以直接以函式名稱引用工具。如果工具的函式名稱與docstring足夠清楚,則你的指令可著重於何時讓大型語言模型 (LLM) 使用該工具。這有助於提升指令明確性,並協助模型理解每個工具的預期用途。
明確指示 agent 如何處理工具可能產生的各種回傳值也非常重要。例如,若工具回傳錯誤訊息,你應在指令中說明 agent 應該重試操作、放棄任務,或向使用者請求更多資訊。
此外,ADK 支援工具的串接使用,即一個工具的輸出可作為另一個工具的輸入。實作這類工作流程時,請務必在 agent 指令中描述預期的工具使用順序,以引導模型依序完成必要步驟。
範例¶
以下範例展示 agent 如何在指令中引用工具的函式名稱來使用工具,同時說明如何引導 agent 處理工具的不同回傳值(如成功或錯誤訊息),以及如何協調多個工具的串接使用來完成任務。
# 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
from google.adk.agents import Agent
from google.adk.tools import FunctionTool
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
APP_NAME="weather_sentiment_agent"
USER_ID="user1234"
SESSION_ID="1234"
MODEL_ID="gemini-2.0-flash"
# Tool 1
def get_weather_report(city: str) -> dict:
"""Retrieves the current weather report for a specified city.
Returns:
dict: A dictionary containing the weather information with a 'status' key ('success' or 'error') and a 'report' key with the weather details if successful, or an 'error_message' if an error occurred.
"""
if city.lower() == "london":
return {"status": "success", "report": "The current weather in London is cloudy with a temperature of 18 degrees Celsius and a chance of rain."}
elif city.lower() == "paris":
return {"status": "success", "report": "The weather in Paris is sunny with a temperature of 25 degrees Celsius."}
else:
return {"status": "error", "error_message": f"Weather information for '{city}' is not available."}
weather_tool = FunctionTool(func=get_weather_report)
# Tool 2
def analyze_sentiment(text: str) -> dict:
"""Analyzes the sentiment of the given text.
Returns:
dict: A dictionary with 'sentiment' ('positive', 'negative', or 'neutral') and a 'confidence' score.
"""
if "good" in text.lower() or "sunny" in text.lower():
return {"sentiment": "positive", "confidence": 0.8}
elif "rain" in text.lower() or "bad" in text.lower():
return {"sentiment": "negative", "confidence": 0.7}
else:
return {"sentiment": "neutral", "confidence": 0.6}
sentiment_tool = FunctionTool(func=analyze_sentiment)
# Agent
weather_sentiment_agent = Agent(
model=MODEL_ID,
name='weather_sentiment_agent',
instruction="""You are a helpful assistant that provides weather information and analyzes the sentiment of user feedback.
**If the user asks about the weather in a specific city, use the 'get_weather_report' tool to retrieve the weather details.**
**If the 'get_weather_report' tool returns a 'success' status, provide the weather report to the user.**
**If the 'get_weather_report' tool returns an 'error' status, inform the user that the weather information for the specified city is not available and ask if they have another city in mind.**
**After providing a weather report, if the user gives feedback on the weather (e.g., 'That's good' or 'I don't like rain'), use the 'analyze_sentiment' tool to understand their sentiment.** Then, briefly acknowledge their sentiment.
You can handle these tasks sequentially if needed.""",
tools=[weather_tool, sentiment_tool]
)
async def main():
"""Main function to run the agent asynchronously."""
# Session and Runner Setup
session_service = InMemorySessionService()
# Use 'await' to correctly create the session
await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
runner = Runner(agent=weather_sentiment_agent, app_name=APP_NAME, session_service=session_service)
# Agent Interaction
query = "weather in london?"
print(f"User Query: {query}")
content = types.Content(role='user', parts=[types.Part(text=query)])
# The runner's run method handles the async loop internally
events = runner.run(user_id=USER_ID, session_id=SESSION_ID, new_message=content)
for event in events:
if event.is_final_response():
final_response = event.content.parts[0].text
print("Agent Response:", final_response)
# Standard way to run the main async function
if __name__ == "__main__":
asyncio.run(main())
import com.google.adk.agents.BaseAgent;
import com.google.adk.agents.LlmAgent;
import com.google.adk.runner.Runner;
import com.google.adk.sessions.InMemorySessionService;
import com.google.adk.sessions.Session;
import com.google.adk.tools.Annotations.Schema;
import com.google.adk.tools.FunctionTool;
import com.google.adk.tools.ToolContext; // Ensure this import is correct
import com.google.common.collect.ImmutableList;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
public class WeatherSentimentAgentApp {
private static final String APP_NAME = "weather_sentiment_agent";
private static final String USER_ID = "user1234";
private static final String SESSION_ID = "1234";
private static final String MODEL_ID = "gemini-2.0-flash";
/**
* Retrieves the current weather report for a specified city.
*
* @param city The city for which to retrieve the weather report.
* @param toolContext The context for the tool.
* @return A dictionary containing the weather information.
*/
public static Map<String, Object> getWeatherReport(
@Schema(name = "city")
String city,
@Schema(name = "toolContext")
ToolContext toolContext) {
Map<String, Object> response = new HashMap<>();
if (city.toLowerCase(Locale.ROOT).equals("london")) {
response.put("status", "success");
response.put(
"report",
"The current weather in London is cloudy with a temperature of 18 degrees Celsius and a"
+ " chance of rain.");
} else if (city.toLowerCase(Locale.ROOT).equals("paris")) {
response.put("status", "success");
response.put(
"report", "The weather in Paris is sunny with a temperature of 25 degrees Celsius.");
} else {
response.put("status", "error");
response.put(
"error_message", String.format("Weather information for '%s' is not available.", city));
}
return response;
}
/**
* Analyzes the sentiment of the given text.
*
* @param text The text to analyze.
* @param toolContext The context for the tool.
* @return A dictionary with sentiment and confidence score.
*/
public static Map<String, Object> analyzeSentiment(
@Schema(name = "text")
String text,
@Schema(name = "toolContext")
ToolContext toolContext) {
Map<String, Object> response = new HashMap<>();
String lowerText = text.toLowerCase(Locale.ROOT);
if (lowerText.contains("good") || lowerText.contains("sunny")) {
response.put("sentiment", "positive");
response.put("confidence", 0.8);
} else if (lowerText.contains("rain") || lowerText.contains("bad")) {
response.put("sentiment", "negative");
response.put("confidence", 0.7);
} else {
response.put("sentiment", "neutral");
response.put("confidence", 0.6);
}
return response;
}
/**
* Calls the agent with the given query and prints the final response.
*
* @param runner The runner to use.
* @param query The query to send to the agent.
*/
public static void callAgent(Runner runner, String query) {
Content content = Content.fromParts(Part.fromText(query));
InMemorySessionService sessionService = (InMemorySessionService) runner.sessionService();
Session session =
sessionService
.createSession(APP_NAME, USER_ID, /* state= */ null, SESSION_ID)
.blockingGet();
runner
.runAsync(session.userId(), session.id(), content)
.forEach(
event -> {
if (event.finalResponse()
&& event.content().isPresent()
&& event.content().get().parts().isPresent()
&& !event.content().get().parts().get().isEmpty()
&& event.content().get().parts().get().get(0).text().isPresent()) {
String finalResponse = event.content().get().parts().get().get(0).text().get();
System.out.println("Agent Response: " + finalResponse);
}
});
}
public static void main(String[] args) throws NoSuchMethodException {
FunctionTool weatherTool =
FunctionTool.create(
WeatherSentimentAgentApp.class.getMethod(
"getWeatherReport", String.class, ToolContext.class));
FunctionTool sentimentTool =
FunctionTool.create(
WeatherSentimentAgentApp.class.getMethod(
"analyzeSentiment", String.class, ToolContext.class));
BaseAgent weatherSentimentAgent =
LlmAgent.builder()
.model(MODEL_ID)
.name("weather_sentiment_agent")
.description("Weather Sentiment Agent")
.instruction("""
You are a helpful assistant that provides weather information and analyzes the
sentiment of user feedback
**If the user asks about the weather in a specific city, use the
'get_weather_report' tool to retrieve the weather details.**
**If the 'get_weather_report' tool returns a 'success' status, provide the
weather report to the user.**
**If the 'get_weather_report' tool returns an 'error' status, inform the
user that the weather information for the specified city is not available
and ask if they have another city in mind.**
**After providing a weather report, if the user gives feedback on the
weather (e.g., 'That's good' or 'I don't like rain'), use the
'analyze_sentiment' tool to understand their sentiment.** Then, briefly
acknowledge their sentiment.
You can handle these tasks sequentially if needed.
""")
.tools(ImmutableList.of(weatherTool, sentimentTool))
.build();
InMemorySessionService sessionService = new InMemorySessionService();
Runner runner = new Runner(weatherSentimentAgent, APP_NAME, null, sessionService);
// Change the query to ensure the tool is called with a valid city that triggers a "success"
// response from the tool, like "london" (without the question mark).
callAgent(runner, "weather in paris");
}
}
Tool Context¶
對於更進階的情境,Agent Development Kit (ADK) 允許你在工具函式(tool function)中,透過加入特殊參數 tool_context: ToolContext,存取額外的上下文資訊。只要在函式簽名中加入此參數,當 agent 執行期間呼叫你的工具時,ADK 會自動提供一個 ToolContext 類別的實例。
ToolContext 提供了多項關鍵資訊與控制選項:
-
state: State:讀取與修改目前 session state。這裡所做的變更會被追蹤並持久化。 -
actions: EventActions:影響 agent 在工具執行後的後續行為(例如:跳過摘要、轉交給其他 agent 等)。 -
function_call_id: str:由框架指派給本次工具呼叫的唯一識別碼。可用於追蹤與驗證回應的關聯。當單一模型回應中呼叫多個工具時,也很有幫助。 -
function_call_event_id: str:此屬性提供觸發本次工具呼叫的事件唯一識別碼。適合用於追蹤與記錄。 -
auth_response: Any:如果在本次工具呼叫前已完成驗證流程,這個屬性會包含驗證回應/憑證。 -
服務存取:可用於與已設定的服務(如 Artifacts、Memory)互動的方法。
請注意,不應在工具函式的 docstring 中包含 tool_context 參數。由於 ToolContext 是在大型語言模型 (LLM) 決定呼叫工具函式之後,由 ADK 框架自動注入,因此這個參數與 LLM 的決策無關,包含它反而可能讓 LLM 感到困惑。
State Management¶
tool_context.state 屬性提供對目前 session state 的直接讀寫存取。它的行為類似於字典,但會確保所有修改都被追蹤為差異(delta),並由 SessionService 持久化。這讓工具可以在不同互動與 agent 步驟間維持與共用資訊。
-
讀取狀態:可使用標準字典存取方式(
tool_context.state['my_key'])或.get()方法(tool_context.state.get('my_key', default_value))。 -
寫入狀態:可直接指定值(
tool_context.state['new_key'] = 'new_value')。這些變更會被記錄在產生事件的 state_delta 中。 -
狀態前綴:請記得標準的 state 前綴:
-
app:*:全應用程式共用。 -
user:*:目前使用者在所有 session 間共用。 -
(無前綴):僅限於目前 session。
-
temp:*:暫時性,不會在多次呼叫間持久化(適合於單次 run 呼叫內傳遞資料,但在工具 context(跨 LLM 呼叫)中通常較少用)。
-
from google.adk.tools import ToolContext, FunctionTool
def update_user_preference(preference: str, value: str, tool_context: ToolContext):
"""Updates a user-specific preference."""
user_prefs_key = "user:preferences"
# Get current preferences or initialize if none exist
preferences = tool_context.state.get(user_prefs_key, {})
preferences[preference] = value
# Write the updated dictionary back to the state
tool_context.state[user_prefs_key] = preferences
print(f"Tool: Updated user preference '{preference}' to '{value}'")
return {"status": "success", "updated_preference": preference}
pref_tool = FunctionTool(func=update_user_preference)
# In an Agent:
# my_agent = Agent(..., tools=[pref_tool])
# When the LLM calls update_user_preference(preference='theme', value='dark', ...):
# The tool_context.state will be updated, and the change will be part of the
# resulting tool response event's actions.state_delta.
import com.google.adk.tools.FunctionTool;
import com.google.adk.tools.ToolContext;
// Updates a user-specific preference.
public Map<String, String> updateUserThemePreference(String value, ToolContext toolContext) {
String userPrefsKey = "user:preferences:theme";
// Get current preferences or initialize if none exist
String preference = toolContext.state().getOrDefault(userPrefsKey, "").toString();
if (preference.isEmpty()) {
preference = value;
}
// Write the updated dictionary back to the state
toolContext.state().put("user:preferences", preference);
System.out.printf("Tool: Updated user preference %s to %s", userPrefsKey, preference);
return Map.of("status", "success", "updated_preference", toolContext.state().get(userPrefsKey).toString());
// When the LLM calls updateUserThemePreference("dark"):
// The toolContext.state will be updated, and the change will be part of the
// resulting tool response event's actions.stateDelta.
}
控制 agent 流程¶
tool_context.actions 屬性(在 Java 中為 ToolContext.actions())包含一個 EventActions 物件。透過修改此物件的屬性,您的工具可以影響 agent 或框架在工具執行結束後的行為。
-
skip_summarization: bool:(預設值:False)若設為 True,則指示 Agent Development Kit (ADK) 跳過通常會摘要工具輸出的大型語言模型 (LLM) 呼叫。如果您的工具回傳值已經是可直接給使用者的訊息,這會很有用。 -
transfer_to_agent: str:將此屬性設為另一個 agent 的名稱。框架會停止目前 agent 的執行,並將對話控制權轉交給指定的 agent。這讓工具可以動態地將任務交給更專業的 agent 處理。 -
escalate: bool:(預設值:False)設為 True 時,表示目前 agent 無法處理該請求,應將控制權向上傳遞給其父 agent(若有階層結構)。在 LoopAgent 中,若子 agent 的工具設為 escalate=True,則會終止該迴圈。
範例¶
# 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 FunctionTool
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools import ToolContext
from google.genai import types
APP_NAME="customer_support_agent"
USER_ID="user1234"
SESSION_ID="1234"
def check_and_transfer(query: str, tool_context: ToolContext) -> str:
"""Checks if the query requires escalation and transfers to another agent if needed."""
if "urgent" in query.lower():
print("Tool: Detected urgency, transferring to the support agent.")
tool_context.actions.transfer_to_agent = "support_agent"
return "Transferring to the support agent..."
else:
return f"Processed query: '{query}'. No further action needed."
escalation_tool = FunctionTool(func=check_and_transfer)
main_agent = Agent(
model='gemini-2.0-flash',
name='main_agent',
instruction="""You are the first point of contact for customer support of an analytics tool. Answer general queries. If the user indicates urgency, use the 'check_and_transfer' tool.""",
tools=[check_and_transfer]
)
support_agent = Agent(
model='gemini-2.0-flash',
name='support_agent',
instruction="""You are the dedicated support agent. Mentioned you are a support handler and please help the user with their urgent issue."""
)
main_agent.sub_agents = [support_agent]
# Session and Runner
async def setup_session_and_runner():
session_service = InMemorySessionService()
session = await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
runner = Runner(agent=main_agent, app_name=APP_NAME, session_service=session_service)
return session, runner
# Agent Interaction
async def call_agent_async(query):
content = types.Content(role='user', parts=[types.Part(text=query)])
session, runner = await setup_session_and_runner()
events = runner.run_async(user_id=USER_ID, session_id=SESSION_ID, new_message=content)
async for event in events:
if event.is_final_response():
final_response = event.content.parts[0].text
print("Agent Response: ", final_response)
# Note: In Colab, you can directly use 'await' at the top level.
# If running this code as a standalone Python script, you'll need to use asyncio.run() or manage the event loop.
await call_agent_async("this is urgent, i cant login")
import com.google.adk.agents.LlmAgent;
import com.google.adk.runner.Runner;
import com.google.adk.sessions.InMemorySessionService;
import com.google.adk.sessions.Session;
import com.google.adk.tools.Annotations.Schema;
import com.google.adk.tools.FunctionTool;
import com.google.adk.tools.ToolContext;
import com.google.common.collect.ImmutableList;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
public class CustomerSupportAgentApp {
private static final String APP_NAME = "customer_support_agent";
private static final String USER_ID = "user1234";
private static final String SESSION_ID = "1234";
private static final String MODEL_ID = "gemini-2.0-flash";
/**
* Checks if the query requires escalation and transfers to another agent if needed.
*
* @param query The user's query.
* @param toolContext The context for the tool.
* @return A map indicating the result of the check and transfer.
*/
public static Map<String, Object> checkAndTransfer(
@Schema(name = "query", description = "the user query")
String query,
@Schema(name = "toolContext", description = "the tool context")
ToolContext toolContext) {
Map<String, Object> response = new HashMap<>();
if (query.toLowerCase(Locale.ROOT).contains("urgent")) {
System.out.println("Tool: Detected urgency, transferring to the support agent.");
toolContext.actions().setTransferToAgent("support_agent");
response.put("status", "transferring");
response.put("message", "Transferring to the support agent...");
} else {
response.put("status", "processed");
response.put(
"message", String.format("Processed query: '%s'. No further action needed.", query));
}
return response;
}
/**
* Calls the agent with the given query and prints the final response.
*
* @param runner The runner to use.
* @param query The query to send to the agent.
*/
public static void callAgent(Runner runner, String query) {
Content content =
Content.fromParts(Part.fromText(query));
InMemorySessionService sessionService = (InMemorySessionService) runner.sessionService();
// Fixed: session ID does not need to be an optional.
Session session =
sessionService
.createSession(APP_NAME, USER_ID, /* state= */ null, SESSION_ID)
.blockingGet();
runner
.runAsync(session.userId(), session.id(), content)
.forEach(
event -> {
if (event.finalResponse()
&& event.content().isPresent()
&& event.content().get().parts().isPresent()
&& !event.content().get().parts().get().isEmpty()
&& event.content().get().parts().get().get(0).text().isPresent()) {
String finalResponse = event.content().get().parts().get().get(0).text().get();
System.out.println("Agent Response: " + finalResponse);
}
});
}
public static void main(String[] args) throws NoSuchMethodException {
FunctionTool escalationTool =
FunctionTool.create(
CustomerSupportAgentApp.class.getMethod(
"checkAndTransfer", String.class, ToolContext.class));
LlmAgent supportAgent =
LlmAgent.builder()
.model(MODEL_ID)
.name("support_agent")
.description("""
The dedicated support agent.
Mentions it is a support handler and helps the user with their urgent issue.
""")
.instruction("""
You are the dedicated support agent.
Mentioned you are a support handler and please help the user with their urgent issue.
""")
.build();
LlmAgent mainAgent =
LlmAgent.builder()
.model(MODEL_ID)
.name("main_agent")
.description("""
The first point of contact for customer support of an analytics tool.
Answers general queries.
If the user indicates urgency, uses the 'check_and_transfer' tool.
""")
.instruction("""
You are the first point of contact for customer support of an analytics tool.
Answer general queries.
If the user indicates urgency, use the 'check_and_transfer' tool.
""")
.tools(ImmutableList.of(escalationTool))
.subAgents(supportAgent)
.build();
// Fixed: LlmAgent.subAgents() expects 0 arguments.
// Sub-agents are now added to the main agent via its builder,
// as `subAgents` is a property that should be set during agent construction
// if it's not dynamically managed.
InMemorySessionService sessionService = new InMemorySessionService();
Runner runner = new Runner(mainAgent, APP_NAME, null, sessionService);
// Agent Interaction
callAgent(runner, "this is urgent, i cant login");
}
}
說明¶
- 我們定義了兩個 agent:
main_agent和support_agent。main_agent被設計為初始聯絡點。 - 當
main_agent呼叫check_and_transfer工具時,該工具會檢查使用者的查詢。 - 如果查詢內容包含「urgent」這個詞,該工具會存取
tool_context,特別是tool_context.actions,並將 transfer_to_agent 屬性設為support_agent。 - 這個動作會通知框架,將對話控制權轉交給名為
support_agent的 agent。 - 當
main_agent處理緊急查詢時,check_and_transfer工具會觸發轉交。接下來的回應理想上會來自support_agent。 - 對於沒有緊急性的普通查詢,該工具僅會處理查詢,不會觸發轉交。
這個範例說明了工具如何透過其 ToolContext 中的 EventActions,動態影響對話流程,將控制權轉交給其他專門的 agent。
驗證(Authentication)¶
ToolContext 提供工具與需驗證 API 互動的機制。如果你的工具需要處理驗證,可以使用以下方式:
-
auth_response:若驗證已由框架在呼叫你的工具前處理(常見於 RestApiTool 和 OpenAPI 安全機制),則此處會包含憑證(例如 token)。 -
request_credential(auth_config: dict):如果你的工具判斷需要驗證但尚未取得憑證,請呼叫此方法。這會通知框架根據提供的 auth_config 啟動驗證流程。 -
get_auth_response():在後續呼叫(於 request_credential 成功處理後)呼叫此方法,以取得使用者所提供的憑證。
有關驗證流程、設定與範例的詳細說明,請參閱專門的 Tool Authentication 文件頁面。
具情境感知的資料存取方法¶
這些方法為你的工具提供便利的方式,以與由已設定服務管理、與 session 或使用者相關的持久性資料互動。
-
list_artifacts()(或 Java 中的listArtifacts()):透過 artifact_service,回傳目前 session 所儲存的所有 artifact 檔名(或鍵值)清單。artifact 通常是使用者上傳或工具/agent 產生的檔案(如圖片、文件等)。 -
load_artifact(filename: str):從 artifact_service 依檔名取得特定 artifact。你可以選擇性指定版本;若未指定則回傳最新版本。回傳一個google.genai.types.Part物件,內含 artifact 資料與 mime type,若找不到則回傳 None。 -
save_artifact(filename: str, artifact: types.Part):將 artifact 的新版本儲存至 artifact_service。回傳新版本號(從 0 開始)。 -
search_memory(query: str)使用已設定的
memory_service查詢使用者的長期記憶。這對於從過往互動或已儲存知識中檢索相關資訊非常有用。SearchMemoryResponse 的結構取決於具體的記憶服務實作,但通常包含相關的文字片段或對話摘錄。
範例¶
# 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.tools import ToolContext, FunctionTool
from google.genai import types
def process_document(
document_name: str, analysis_query: str, tool_context: ToolContext
) -> dict:
"""Analyzes a document using context from memory."""
# 1. Load the artifact
print(f"Tool: Attempting to load artifact: {document_name}")
document_part = tool_context.load_artifact(document_name)
if not document_part:
return {"status": "error", "message": f"Document '{document_name}' not found."}
document_text = document_part.text # Assuming it's text for simplicity
print(f"Tool: Loaded document '{document_name}' ({len(document_text)} chars).")
# 2. Search memory for related context
print(f"Tool: Searching memory for context related to: '{analysis_query}'")
memory_response = tool_context.search_memory(
f"Context for analyzing document about {analysis_query}"
)
memory_context = "\n".join(
[
m.events[0].content.parts[0].text
for m in memory_response.memories
if m.events and m.events[0].content
]
) # Simplified extraction
print(f"Tool: Found memory context: {memory_context[:100]}...")
# 3. Perform analysis (placeholder)
analysis_result = f"Analysis of '{document_name}' regarding '{analysis_query}' using memory context: [Placeholder Analysis Result]"
print("Tool: Performed analysis.")
# 4. Save the analysis result as a new artifact
analysis_part = types.Part.from_text(text=analysis_result)
new_artifact_name = f"analysis_{document_name}"
version = await tool_context.save_artifact(new_artifact_name, analysis_part)
print(f"Tool: Saved analysis result as '{new_artifact_name}' version {version}.")
return {
"status": "success",
"analysis_artifact": new_artifact_name,
"version": version,
}
doc_analysis_tool = FunctionTool(func=process_document)
# In an Agent:
# Assume artifact 'report.txt' was previously saved.
# Assume memory service is configured and has relevant past data.
# my_agent = Agent(..., tools=[doc_analysis_tool], artifact_service=..., memory_service=...)
// Analyzes a document using context from memory.
// You can also list, load and save artifacts using Callback Context or LoadArtifacts tool.
public static @NonNull Maybe<ImmutableMap<String, Object>> processDocument(
@Annotations.Schema(description = "The name of the document to analyze.") String documentName,
@Annotations.Schema(description = "The query for the analysis.") String analysisQuery,
ToolContext toolContext) {
// 1. List all available artifacts
System.out.printf(
"Listing all available artifacts %s:", toolContext.listArtifacts().blockingGet());
// 2. Load an artifact to memory
System.out.println("Tool: Attempting to load artifact: " + documentName);
Part documentPart = toolContext.loadArtifact(documentName, Optional.empty()).blockingGet();
if (documentPart == null) {
System.out.println("Tool: Document '" + documentName + "' not found.");
return Maybe.just(
ImmutableMap.<String, Object>of(
"status", "error", "message", "Document '" + documentName + "' not found."));
}
String documentText = documentPart.text().orElse("");
System.out.println(
"Tool: Loaded document '" + documentName + "' (" + documentText.length() + " chars).");
// 3. Perform analysis (placeholder)
String analysisResult =
"Analysis of '"
+ documentName
+ "' regarding '"
+ analysisQuery
+ " [Placeholder Analysis Result]";
System.out.println("Tool: Performed analysis.");
// 4. Save the analysis result as a new artifact
Part analysisPart = Part.fromText(analysisResult);
String newArtifactName = "analysis_" + documentName;
toolContext.saveArtifact(newArtifactName, analysisPart);
return Maybe.just(
ImmutableMap.<String, Object>builder()
.put("status", "success")
.put("analysis_artifact", newArtifactName)
.build());
}
// FunctionTool processDocumentTool =
// FunctionTool.create(ToolContextArtifactExample.class, "processDocument");
// In the Agent, include this function tool.
// LlmAgent agent = LlmAgent().builder().tools(processDocumentTool).build();
透過運用 ToolContext,開發者可以建立更為進階且具備情境感知能力的自訂工具(custom tools),這些工具能無縫整合至 Agent Development Kit (ADK) 的架構中,並提升 agent 的整體能力。
定義有效的工具函式¶
當你將某個方法或函式作為 ADK 工具(Tool)使用時,其定義方式會顯著影響 agent 是否能正確使用它。agent 所依賴的大型語言模型 (Large Language Model, LLM) 主要根據該函式的 名稱、參數(arguments)、型別提示(type hints),以及 docstring / 原始碼註解 來理解其用途並產生正確的呼叫。
以下是定義有效工具函式的關鍵指引:
-
函式名稱:
- 使用描述性、以動詞-名詞組合為主的名稱,清楚指出動作內容(例如:
get_weather、searchDocuments、schedule_meeting)。 - 避免使用像
run、process、handle_data這類過於通用的名稱,或doStuff這種過於模糊的名稱。即使描述良好,像do_stuff這樣的名稱也可能讓模型混淆該在何時使用此工具,而不是例如cancelFlight。 - LLM 在選擇工具時,會將函式名稱作為主要識別依據。
- 使用描述性、以動詞-名詞組合為主的名稱,清楚指出動作內容(例如:
-
參數(Arguments):
- 你的函式可以有任意數量的參數。
- 請使用清楚且具描述性的名稱(例如:
city取代c,search_query取代q)。 - 在 Python 中為所有參數提供型別提示(type hints)(例如:
city: str、user_id: int、items: list[str])。這對於 ADK 產生正確的 LLM schema 至關重要。 - 確保所有參數型別皆為 JSON 可序列化。所有 Java 原始型別以及標準 Python 型別(如
str、int、float、bool、list、dict)及其組合通常都安全。除非自訂類別實例有明確的 JSON 表示,否則請避免直接作為參數傳遞複雜的自訂類別實例。 - 請勿為參數設定預設值。例如:
def my_func(param1: str = "default")。底層模型在產生函式呼叫時,對預設值的支援並不可靠。所有必要資訊應由 LLM 從情境中取得,或在缺少時明確要求。 self/cls自動處理: 隱含參數如self(用於實例方法)或cls(用於類別方法)會由 ADK 自動處理,並從提供給 LLM 的 schema 中排除。你只需為工具實際需要 LLM 提供的邏輯參數定義型別提示與描述即可。
-
回傳型別:
- 函式的回傳值在 Python 中必須為字典(
dict),在 Java 中則為 Map。 - 若你的函式回傳非字典型別(如字串、數字、串列),ADK 框架會自動將其包裝為字典/Map(如
{'result': your_original_return_value}),再將結果回傳給模型。 - 設計字典/Map 的鍵與值時,請確保對 LLM 來說具描述性且易於理解。請記住,模型會讀取這個輸出來決定下一步行動。
- 請包含有意義的鍵。例如,不要只回傳錯誤碼(如
500),而是回傳{'status': 'error', 'error_message': 'Database connection failed'}。 - 強烈建議在回傳值中加入
status鍵(例如:'success'、'error'、'pending'、'ambiguous'),以明確告知模型工具執行的結果。
- 函式的回傳值在 Python 中必須為字典(
-
Docstring / 原始碼註解:
- 這非常重要。 docstring 是 LLM 取得描述性資訊的主要來源。
- 明確說明工具的「功能」。 具體描述其用途與限制。
- 說明「何時」應使用此工具。 提供情境或範例情境,協助 LLM 做出判斷。
- 清楚描述「每個參數」。 解釋 LLM 需要為該參數提供哪些資訊。
- 說明預期
dict回傳值的結構與意義,特別是不同的status值及其相關資料鍵。 - 不要描述被注入的 ToolContext 參數。 請避免在 docstring 描述中提及可選的
tool_context: ToolContext參數,因為這不是 LLM 需要知道的參數。ToolContext 會由 ADK 注入,在 LLM 決定呼叫該工具之後才會傳入。
良好定義的範例:
def lookup_order_status(order_id: str) -> dict:
"""Fetches the current status of a customer's order using its ID.
Use this tool ONLY when a user explicitly asks for the status of
a specific order and provides the order ID. Do not use it for
general inquiries.
Args:
order_id: The unique identifier of the order to look up.
Returns:
A dictionary indicating the outcome.
On success, status is 'success' and includes an 'order' dictionary.
On failure, status is 'error' and includes an 'error_message'.
Example success: {'status': 'success', 'order': {'state': 'shipped', 'tracking_number': '1Z9...'}}
Example error: {'status': 'error', 'error_message': 'Order ID not found.'}
"""
# ... function implementation to fetch status ...
if status_details := fetch_status_from_backend(order_id):
return {
"status": "success",
"order": {
"state": status_details.state,
"tracking_number": status_details.tracking,
},
}
else:
return {"status": "error", "error_message": f"Order ID {order_id} not found."}
/**
* Retrieves the current weather report for a specified city.
*
* @param city The city for which to retrieve the weather report.
* @param toolContext The context for the tool.
* @return A dictionary containing the weather information.
*/
public static Map<String, Object> getWeatherReport(String city, ToolContext toolContext) {
Map<String, Object> response = new HashMap<>();
if (city.toLowerCase(Locale.ROOT).equals("london")) {
response.put("status", "success");
response.put(
"report",
"The current weather in London is cloudy with a temperature of 18 degrees Celsius and a"
+ " chance of rain.");
} else if (city.toLowerCase(Locale.ROOT).equals("paris")) {
response.put("status", "success");
response.put("report", "The weather in Paris is sunny with a temperature of 25 degrees Celsius.");
} else {
response.put("status", "error");
response.put("error_message", String.format("Weather information for '%s' is not available.", city));
}
return response;
}
- 簡潔與專注:
- 保持工具聚焦: 每個 tool 應該盡可能只執行一個明確定義的任務。
- 參數越少越好: 大型語言模型 (LLM) 通常能更可靠地處理參數較少且定義清楚的工具,相較於有許多可選或複雜參數的工具。
- 使用簡單資料型別: 優先使用基本型別(如 Python 的
str、int、bool、float、List[str],或 Java 的int、byte、short、long、float、double、boolean、char),盡量避免將複雜的自訂類別或深層巢狀結構作為參數。 - 拆解複雜任務: 將執行多個不同邏輯步驟的函式拆分為更小、更專注的工具。例如,與其設計一個單一的
update_user_profile(profile: ProfileObject)工具,不如考慮分別設計update_user_name(name: str)、update_user_address(address: str)、update_user_preferences(preferences: list[str])等工具。這能讓大型語言模型 (LLM) 更容易選擇並使用正確的功能。
遵循這些指引,能為大型語言模型 (LLM) 提供所需的清晰結構,讓其更有效率地運用你的自訂工具函式,進而帶來更強大且可靠的 agent 行為。
工具組(Toolsets):分組與動態提供工具
¶
除了單一工具外,Agent Development Kit (ADK) 引入了 工具組(Toolset) 的概念,透過 BaseToolset 介面(定義於 google.adk.tools.base_toolset)。工具組允許你管理並動態提供一組 BaseTool 實例給 agent。
這種方式的優點包括:
- 組織相關工具: 將具有共同用途的工具分組(例如:所有數學運算工具,或所有與特定 API 互動的工具)。
- 動態工具可用性: 讓 agent 能根據當前情境(例如:使用者權限、session state 或其他執行時條件)動態擁有不同的工具。工具組的
get_tools方法可決定要暴露哪些工具。 - 整合外部工具提供者: 工具組可以作為外部系統(如 OpenAPI 規格或 MCP server)工具的轉接器,將其轉換為 ADK 相容的
BaseTool物件。
BaseToolset 介面¶
任何作為 ADK 工具組的類別,都應實作 BaseToolset 抽象基底類別。此介面主要定義兩個方法:
-
async def get_tools(...) -> list[BaseTool]:這是工具組的核心方法。當 ADK agent 需要知道可用工具時,會對其tools清單中的每個BaseToolset實例呼叫get_tools()。- 此方法會接收一個可選的
readonly_context(ReadonlyContext的實例)。這個 context 提供唯讀存取,例如當前 session state(readonly_context.state)、agent 名稱及呼叫 ID。工具組可利用這個 context 動態決定要回傳哪些工具。 - 必須回傳一個
list,內容為多個BaseTool實例(如FunctionTool、RestApiTool)。
- 此方法會接收一個可選的
-
async def close(self) -> None:這個非同步方法會在工具組不再需要時,由 ADK 框架呼叫,例如 agent server 關閉或Runner被關閉時。可在此實作任何必要的清理動作,例如關閉網路連線、釋放檔案控制代碼,或清理工具組管理的其他資源。
在 agent 中使用工具組¶
你可以直接將自訂的 BaseToolset 實作實例,與個別 BaseTool 實例一起,加入 LlmAgent 的 tools 清單中。
當 agent 初始化或需要判斷可用功能時,ADK 框架會遍歷 tools 清單:
- 若項目為
BaseTool實例,則直接使用。 - 若項目為
BaseToolset實例,則會呼叫其get_tools()方法(傳入目前的ReadonlyContext),並將回傳的BaseTool清單加入 agent 的可用工具中。
範例:簡單的數學工具組¶
以下將建立一個提供基本算術運算的工具組範例。
# 1. Define the individual tool functions
def add_numbers(a: int, b: int, tool_context: ToolContext) -> Dict[str, Any]:
"""Adds two integer numbers.
Args:
a: The first number.
b: The second number.
Returns:
A dictionary with the sum, e.g., {'status': 'success', 'result': 5}
"""
print(f"Tool: add_numbers called with a={a}, b={b}")
result = a + b
# Example: Storing something in tool_context state
tool_context.state["last_math_operation"] = "addition"
return {"status": "success", "result": result}
def subtract_numbers(a: int, b: int) -> Dict[str, Any]:
"""Subtracts the second number from the first.
Args:
a: The first number.
b: The second number.
Returns:
A dictionary with the difference, e.g., {'status': 'success', 'result': 1}
"""
print(f"Tool: subtract_numbers called with a={a}, b={b}")
return {"status": "success", "result": a - b}
# 2. Create the Toolset by implementing BaseToolset
class SimpleMathToolset(BaseToolset):
def __init__(self, prefix: str = "math_"):
self.prefix = prefix
# Create FunctionTool instances once
self._add_tool = FunctionTool(
func=add_numbers,
name=f"{self.prefix}add_numbers", # Toolset can customize names
)
self._subtract_tool = FunctionTool(
func=subtract_numbers, name=f"{self.prefix}subtract_numbers"
)
print(f"SimpleMathToolset initialized with prefix '{self.prefix}'")
async def get_tools(
self, readonly_context: Optional[ReadonlyContext] = None
) -> List[BaseTool]:
print(f"SimpleMathToolset.get_tools() called.")
# Example of dynamic behavior:
# Could use readonly_context.state to decide which tools to return
# For instance, if readonly_context.state.get("enable_advanced_math"):
# return [self._add_tool, self._subtract_tool, self._multiply_tool]
# For this simple example, always return both tools
tools_to_return = [self._add_tool, self._subtract_tool]
print(f"SimpleMathToolset providing tools: {[t.name for t in tools_to_return]}")
return tools_to_return
async def close(self) -> None:
# No resources to clean up in this simple example
print(f"SimpleMathToolset.close() called for prefix '{self.prefix}'.")
await asyncio.sleep(0) # Placeholder for async cleanup if needed
# 3. Define an individual tool (not part of the toolset)
def greet_user(name: str = "User") -> Dict[str, str]:
"""Greets the user."""
print(f"Tool: greet_user called with name={name}")
return {"greeting": f"Hello, {name}!"}
greet_tool = FunctionTool(func=greet_user)
# 4. Instantiate the toolset
math_toolset_instance = SimpleMathToolset(prefix="calculator_")
# 5. Define an agent that uses both the individual tool and the toolset
calculator_agent = LlmAgent(
name="CalculatorAgent",
model="gemini-2.0-flash", # Replace with your desired model
instruction="You are a helpful calculator and greeter. "
"Use 'greet_user' for greetings. "
"Use 'calculator_add_numbers' to add and 'calculator_subtract_numbers' to subtract. "
"Announce the state of 'last_math_operation' if it's set.",
tools=[greet_tool, math_toolset_instance], # Individual tool # Toolset instance
)
在此範例中:
SimpleMathToolset實作了BaseToolset,其get_tools()方法會針對add_numbers和subtract_numbers回傳FunctionTool實例。此外,還會使用前綴字自訂這些工具的名稱。calculator_agent同時設定了個別的greet_tool以及一個SimpleMathToolset實例。- 當執行
calculator_agent時,Agent Development Kit (ADK) 會呼叫math_toolset_instance.get_tools()。此時 agent 的大型語言模型 (LLM) 將能存取greet_user、calculator_add_numbers和calculator_subtract_numbers,以處理使用者請求。 add_numbers工具展示了如何寫入tool_context.state,而 agent 的指令則提及了讀取這個狀態。- 會呼叫
close()方法,以確保工具組所持有的任何資源都能被釋放。
工具組(Toolsets)提供了一種強大的方式,能夠組織、管理並動態地將多個工具集合提供給你的 ADK agent,讓 agent 應用程式更加模組化、易於維護且具備高度適應性。