跳转到内容

工具

什么是工具?

在ADK的上下文中,工具代表提供给AI智能体的特定能力,使其能够执行操作并与核心文本生成和推理能力之外的世界进行交互。强大智能体与基础语言模型之间的区别通常在于它们对工具的有效使用。

从技术上讲,工具通常是模块化的代码组件——比如Python函数、类方法,甚至是另一个专门的智能体——旨在执行一个独特的、预定义的任务。这些任务通常涉及与外部系统或数据的交互。

Agent tool call

关键特性

面向行动: 工具执行特定操作,例如:

  • 查询数据库
  • 发送API请求(例如,获取天气数据、预订系统)
  • 搜索网页
  • 执行代码片段
  • 从文档中检索信息(RAG)
  • 与其他软件或服务交互

扩展智能体能力: 它们使智能体能够访问实时信息、影响外部系统,并克服其训练数据中固有的知识限制。

执行预定义逻辑: 关键在于,工具执行的是开发者定义的特定逻辑。它们不像智能体核心的大型语言模型(LLM)那样具备独立的推理能力。LLM负责决定使用哪个工具、何时使用以及输入什么参数,而工具本身仅执行其指定的功能。

智能体如何使用工具

智能体通过通常涉及函数调用的机制动态利用工具。该过程通常遵循以下步骤:

  1. 推理: 智能体的LLM会分析其系统指令、对话历史和用户请求。
  2. 选择: 根据分析结果,大语言模型(LLM)会基于智能体可用的工具以及描述每个工具的文档字符串,决定是否执行某个工具。
  3. 调用: LLM为选定的工具生成所需的参数(输入)并触发其执行。
  4. 观察:智能体接收到工具返回的输出(结果)。
  5. 最终确定: 智能体将工具的输出整合到其持续推理过程中,以制定下一个响应、决定后续步骤或判断目标是否已达成。

可以将这些工具视为一个专业工具包,智能体的核心智能(LLM)能够根据需要访问和利用它们来完成复杂任务。

ADK中的工具类型

Agent Development Kit通过支持多种工具类型提供灵活性:

  1. 功能工具: Tools created by you, tailored to your specific application's needs.
  2. Built-in Tools: 框架提供的开箱即用工具,用于常见任务。 示例:Google搜索、代码执行、检索增强生成(RAG)。
  3. Third-Party Tools: 无缝集成来自流行外部库的工具。 示例:LangChain Tools, CrewAI Tools.

请访问上方链接的相应文档页面,获取每种工具类型的详细信息和示例。

在智能体指令中引用工具

在智能体的指令中,您可以直接通过函数名称引用工具。如果工具的函数名称文档字符串足够描述性,您的指令可以主要关注大型语言模型(LLM)何时应该使用该工具。这有助于提高清晰度,并帮助模型理解每个工具的预期用途。

明确指导智能体如何处理工具可能产生的不同返回值至关重要。例如,如果工具返回错误消息,您的指令应明确智能体应重试操作、放弃任务还是向用户请求更多信息。

此外,Agent Development Kit支持工具的连续使用,其中一个工具的输出可以作为另一个工具的输入。在实现此类工作流时,重要的是在智能体的指令中描述工具使用的预期顺序,以引导模型完成必要的步骤。

示例

以下示例展示了智能体如何通过在指令中引用工具的函数名称来使用工具。它还演示了如何引导智能体处理来自工具的不同返回值,例如成功或错误消息,以及如何协调多个工具的连续使用来完成一项任务。

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]
)

# Session and Runner
session_service = InMemorySessionService()
session = 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
def call_agent(query):
    content = types.Content(role='user', parts=[types.Part(text=query)])
    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)

call_agent("weather in london?")

工具上下文

对于更高级的场景,Agent Development Kit允许您通过包含特殊参数tool_context: ToolContext来访问工具函数中的额外上下文信息。通过在函数签名中包含此参数,当智能体执行期间调用您的工具时,ADK将自动提供一个ToolContext类的实例

ToolContext 提供了对多个关键信息和控制手段的访问:

  • state: State: 读取和修改当前会话的状态。此处所做的更改会被跟踪并持久化保存。

  • actions: EventActions: 影响智能体在工具运行后的后续行为(例如跳过总结、转移到另一个智能体)。

  • function_call_id: str: 框架为该工具此次特定调用分配的唯一标识符。用于跟踪并与认证响应关联。当单个模型响应中调用多个工具时,这也很有帮助。

  • function_call_event_id: str: 该属性提供了触发当前工具调用的事件的唯一标识符。这对于跟踪和日志记录非常有用。

  • auth_response: Any: 如果在此工具调用前已完成身份验证流程,则包含身份验证响应/凭据。

  • 访问服务:与已配置服务(如Artifacts和Memory)交互的方法。

状态管理

tool_context.state 属性提供了对当前会话关联状态的直接读写访问。它的行为类似于字典,但能确保所有修改都被作为增量变更追踪,并由会话服务持久化存储。这使得工具能够在不同的交互和智能体步骤之间维护和共享信息。

  • 读取状态: 使用标准字典访问方式 (tool_context.state['my_key']) 或 .get() 方法 (tool_context.state.get('my_key', default_value))。

  • 写入状态: 直接赋值(tool_context.state['new_key'] = 'new_value')。这些变更会被记录在结果事件的state_delta中。

  • 状态前缀: 记住标准的状态前缀:

    • app:*: 应用程序的所有用户共享。

    • user:*: 针对当前用户在所有会话中的特定设置。

    • (无前缀): 仅限当前会话使用。

    • temp:*: 临时性,不会在多次调用间持久化(适用于在单次运行调用内传递数据,但在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.

控制智能体流程

tool_context.actions 属性包含一个 EventActions 对象。修改此对象的属性可以让您的工具影响智能体或框架在工具执行完成后的行为。

  • skip_summarization: bool: (默认值: False) 如果设为True,指示ADK跳过通常用于汇总工具输出的LLM调用。当您的工具返回值已经是用户可直接使用的消息时,这个参数很有用。

  • transfer_to_agent: str: 将此参数设置为另一个智能体的名称。框架将停止当前智能体的执行,并将会话控制权转移给指定的智能体。这允许工具将任务动态地移交给更专业的智能体。

  • escalate: bool: (默认值: False) 将此参数设为True表示当前智能体无法处理该请求,应将控制权传递给其父级智能体(如果在层级结构中)。在LoopAgent中,在子智能体的工具中设置escalate=True将终止循环。

示例

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
session_service = InMemorySessionService()
session = 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)


# Agent Interaction
def call_agent(query):
    content = types.Content(role='user', parts=[types.Part(text=query)])
    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)

call_agent("this is urgent, i cant login")
说明
  • 我们定义了两个智能体:main_agentsupport_agentmain_agent 被设计为初始接触点。
  • main_agent调用check_and_transfer工具时,会检查用户的查询。
  • 如果查询中包含"urgent"这个词,该工具会访问tool_context,特别是tool_context.actions,并将transfer_to_agent属性设置为support_agent
  • 此操作向框架发出信号,将会话控制权转移给名为support_agent的智能体
  • main_agent处理紧急查询时,check_and_transfer工具会触发转移。理想情况下,后续响应应来自support_agent
  • 对于没有紧急性的普通查询,该工具仅进行处理而不触发转移。

本示例展示了工具如何通过其ToolContext中的EventActions动态影响对话流程,将控制权转移给另一个专门的智能体。

认证

ToolContext 提供了智能体工具与经过身份验证的API交互的机制。如果您的工具需要处理身份验证,可以使用以下方法:

  • auth_response: 如果框架在调用你的工具之前已经处理了认证(常见于RestApiTool和OpenAPI安全方案),则包含凭据(例如令牌)。

  • request_credential(auth_config: dict): 当您的工具判断需要认证但凭据不可用时调用此方法。这会向框架发出信号,基于提供的auth_config启动认证流程。

  • get_auth_response(): 在后续调用中(当request_credential已成功处理后)调用此函数以获取用户提供的凭据。

有关认证流程、配置和示例的详细说明,请参阅专门的工具认证文档页面。

上下文感知数据访问方法

这些方法为您的工具提供了便捷方式,以与会话或用户相关联的持久化数据进行交互,这些数据由配置的服务管理。

  • list_artifacts(): 返回当前会话中通过artifact_service存储的所有工件的文件名(或键)列表。工件通常是用户上传或由工具/智能体生成的文件(如图片、文档等)。

  • load_artifact(filename: str): 从artifact_service中通过文件名获取特定构件。可以选择性地指定版本号;如果省略,则返回最新版本。返回一个包含构件数据和mime类型的google.genai.types.Part对象,如果未找到则返回None。

  • save_artifact(filename: str, artifact: types.Part): 将工件的新版本保存到artifact_service。返回新的版本号(从0开始)。

  • search_memory(query: str): 通过配置的memory_service查询用户的长期记忆。该功能可用于从过往交互或存储的知识中检索相关信息。SearchMemoryResponse的结构取决于具体的内存服务实现,但通常包含相关的文本片段或对话摘录。

示例

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 = 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=...)

通过利用ToolContext,开发者可以创建更复杂且具备上下文感知能力的自定义工具,这些工具能与ADK架构无缝集成,并增强其智能体的整体能力。

定义有效的工具函数

当使用标准Python函数作为ADK工具时,如何定义它将显著影响智能体正确使用该功能的能力。智能体的大型语言模型(LLM)在很大程度上依赖于函数的名称参数类型提示文档字符串来理解其用途并生成正确的调用。

以下是定义高效工具功能的关键准则:

  • 函数名称:

    • 使用描述性的、基于动词-名词组合的名称,明确表示操作(例如:get_weathersearch_documentsschedule_meeting)。
    • 避免使用通用名称如runprocesshandle_data,或过于模糊的名称如do_stuff。即使有良好的描述,像do_stuff这样的名称可能会让模型混淆何时使用该工具,而不是例如cancel_flight
    • LLM在工具选择过程中将函数名称作为主要标识符。
  • 参数(参数):

    • 您的函数可以包含任意数量的参数。
    • 使用清晰且具有描述性的名称(例如,city而不是csearch_query而不是q)。
    • 提供类型提示 给所有参数 (例如 city: str, user_id: int, items: list[str])。这对ADK为LLM生成正确的模式至关重要。
    • 确保所有参数类型都是可JSON序列化的。标准的Python类型如strintfloatboollistdict及其组合通常是安全的。除非有明确的JSON表示形式,否则应避免将复杂的自定义类实例作为直接参数。
    • 不要为参数设置默认值。例如,def my_func(param1: str = "default")。在生成函数调用时,底层模型不能可靠地支持或使用默认值。所有必要的信息应该由LLM从上下文中推导出来,或者在缺失时明确请求。
  • 返回类型:

    • 函数的返回值必须是一个字典(dict)
    • 如果您的函数返回非字典类型(例如字符串、数字、列表),ADK框架会自动将其包装成类似{'result': your_original_return_value}的字典格式,再将结果传回模型。
    • 设计字典键值对时要确保描述清晰且便于LLM理解。请记住,模型会读取这些输出来决定下一步操作。
    • 包含有意义的键值。例如,不要只返回像500这样的错误代码,而应该返回{'status': 'error', 'error_message': 'Database connection failed'}
    • 这是一个强烈推荐的做法,包含一个status键(例如'success''error''pending''ambiguous')来清晰地向模型展示工具执行的结果。
  • 文档字符串:

    • 这一点至关重要。 文档字符串是LLM获取描述性信息的主要来源。
    • 明确说明该工具的功能 具体描述其用途和限制。
    • 解释何时应使用该工具。 提供上下文或示例场景来指导大语言模型的决策过程。
    • 清晰描述每个参数 解释LLM需要为该参数提供哪些信息。
    • 描述预期返回的dict结构及其含义,特别是不同的status状态值及其关联的数据键。

    良好定义的示例:

    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 containing the order status.
          Possible statuses: 'shipped', 'processing', 'pending', 'error'.
          Example success: {'status': 'shipped', 'tracking_number': '1Z9...'}
          Example error: {'status': 'error', 'error_message': 'Order ID not found.'}
      """
      # ... function implementation to fetch status ...
      if status := fetch_status_from_backend(order_id):
           return {"status": status.state, "tracking_number": status.tracking} # Example structure
      else:
           return {"status": "error", "error_message": f"Order ID {order_id} not found."}
    
  • 简洁与专注:

    • 保持工具专注性: 每个工具理想情况下应该执行一个明确定义的任务。
    • 参数越少越好: 模型通常能更可靠地处理参数较少且定义明确的工具,而不是那些有许多可选或复杂参数的工具。
    • 使用简单数据类型:尽可能使用基本类型(strintboolfloatList[str]等)作为参数,而不是复杂的自定义类或深度嵌套结构。
    • 分解复杂任务: 将执行多个不同逻辑步骤的函数拆分为更小、更专注的工具。例如,与其使用单一的update_user_profile(profile: ProfileObject)工具,不如考虑使用单独的工具,如update_user_name(name: str)update_user_address(address: str)update_user_preferences(preferences: list[str])等。这样可以使LLM更容易选择和使用正确的功能。

遵循这些准则,您可以为大语言模型提供所需的清晰度和结构,使其能够有效利用您的自定义函数工具,从而产生更强大可靠的智能体行为。