ai-agents-for-beginners

如何设计优秀的 AI 代理

(点击上方图片观看本课视频)

工具使用设计模式

工具很有趣,因为它们让 AI 代理拥有了更广泛的能力范围。代理不再局限于执行一组有限的动作,而是通过添加工具,可以执行更广泛的操作。本章将介绍工具使用设计模式,阐述 AI 代理如何利用特定工具来实现其目标。

引言

在本课中,我们将解答以下问题:

学习目标

完成本课后,你将能够:

什么是工具使用设计模式?

工具使用设计模式着重于赋予大型语言模型(LLM)与外部工具交互以实现特定目标的能力。工具是代理可以执行的代码,用以完成操作。工具可以是简单的函数,比如计算器,或是调用第三方服务的 API,如股票价格查询或天气预报。在 AI 代理的语境中,工具设计为由代理执行,以响应模型生成的函数调用

它适用于哪些用例?

AI 代理可以利用工具完成复杂任务、获取信息或做出决策。工具使用设计模式常用在需要与外部系统(如数据库、网络服务或代码解释器)动态交互的场景。该功能适用于多种用例,包括:

实现工具使用设计模式所需的元素/构建块有哪些?

这些构建块使 AI 代理能够执行多种任务。让我们看看实现工具使用设计模式所需的关键元素:

接下来,让我们详细了解函数/工具调用。

函数/工具调用

函数调用是让大型语言模型(LLM)与工具交互的主要方式。你会发现“函数”和“工具”常被互换使用,因为“函数”(可重用的代码块)即是代理用来执行任务的“工具”。为了调用函数代码,LLM 必须将用户请求与函数描述进行匹配。为此,将包含所有可用函数描述的 Schema 发送给 LLM。LLM 然后选择最合适的函数,返回函数名称及参数。所选函数被调用,其响应返回给 LLM,LLM 利用这些信息回应用户请求。

为了实现代理的函数调用,开发者需要:

  1. 支持函数调用的 LLM 模型
  2. 包含函数描述的 Schema
  3. 为每个描述的函数编写代码

让我们用获取某城市当前时间的例子说明:

  1. 初始化支持函数调用的 LLM:

    并非所有模型都支持函数调用,因此需要确认使用的 LLM 是否支持。Azure OpenAI 支持函数调用。我们可以通过调用 Azure OpenAI Responses API(稳定的 /openai/v1/ 端点,无需 api_version)来启动 OpenAI 客户端。

     # 初始化用于 Azure OpenAI 的 OpenAI 客户端(响应 API,v1 端点)
     client = OpenAI(
         base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
         api_key=os.environ["AZURE_OPENAI_API_KEY"],
     )
     deployment_name = os.environ["AZURE_OPENAI_DEPLOYMENT"]
    
  2. 创建函数 Schema:

    接下来定义一个 JSON Schema,包含函数名称、功能描述以及函数参数的名称和说明。 然后将该 Schema 与请求查询旧金山时间的用户请求一起传递给之前创建的客户端。重要的是,返回的是工具调用,而非问题的最终答案。如前所述,LLM 返回为任务选中的函数名称和将传递给它的参数。

     # 模型读取的函数描述(响应 API 扁平工具格式)
     tools = [
         {
             "type": "function",
             "name": "get_current_time",
             "description": "Get the current time in a given location",
             "parameters": {
                 "type": "object",
                 "properties": {
                     "location": {
                         "type": "string",
                         "description": "The city name, e.g. San Francisco",
                     },
                 },
                 "required": ["location"],
             },
         }
     ]
    
      
     # 初始用户消息
     messages = [{"role": "user", "content": "What's the current time in San Francisco"}]
    
     # 第一次 API 调用:要求模型使用该功能
     response = client.responses.create(
         model=deployment_name,
         input=messages,
         tools=tools,
         tool_choice="auto",
         store=False,
     )
    
     # Responses API 在 response.output 中返回工具调用作为 function_call 项。
     # 将它们附加到对话中,以便模型在下一轮有完整的上下文。
     messages += response.output
    
     print("Model's response:")
     print(response.output)
      
    
     Model's response:
     [ResponseFunctionToolCall(arguments='{"location":"San Francisco"}', call_id='call_pOsKdUlqvdyttYB67MOj434b', name='get_current_time', type='function_call')]
    
  3. 执行任务所需的函数代码:

    既然 LLM 已选定需调用的函数,需要实现并执行完成任务的代码。 我们用 Python 实现获取当前时间的代码。同样还需要编写代码来从 response_message 中提取函数名和参数,以获得最终结果。

       def get_current_time(location):
         """Get the current time for a given location"""
         print(f"get_current_time called with location: {location}")  
         location_lower = location.lower()
            
         for key, timezone in TIMEZONE_DATA.items():
             if key in location_lower:
                 print(f"Timezone found for {key}")  
                 current_time = datetime.now(ZoneInfo(timezone)).strftime("%I:%M %p")
                 return json.dumps({
                     "location": location,
                     "current_time": current_time
                 })
          
         print(f"No timezone data found for {location_lower}")  
         return json.dumps({"location": location, "current_time": "unknown"})
    
     # 处理函数调用
     tool_calls = [item for item in response.output if item.type == "function_call"]
     if tool_calls:
         for tool_call in tool_calls:
             if tool_call.name == "get_current_time":
    
                 function_args = json.loads(tool_call.arguments)
    
                 time_response = get_current_time(
                     location=function_args.get("location")
                 )
    
                 # 将工具结果作为 function_call_output 项返回
                 messages.append({
                     "type": "function_call_output",
                     "call_id": tool_call.call_id,
                     "output": time_response,
                 })
     else:
         print("No tool calls were made by the model.")
    
     # 第二次 API 调用:从模型获取最终响应
     final_response = client.responses.create(
         model=deployment_name,
         input=messages,
         tools=tools,
         store=False,
     )
    
     return final_response.output_text
    
       get_current_time called with location: San Francisco
       Timezone found for san francisco
       The current time in San Francisco is 09:24 AM.
    

函数调用是大多数(如果不是全部)代理工具使用设计的核心,然而从零实现有时颇具挑战。 正如我们在课程 2所学,代理框架为我们提供了预构建的构建块以实现工具使用。

使用代理框架的工具使用示例

以下是使用不同代理框架实现工具使用设计模式的一些示例:

Microsoft 代理框架

Microsoft 代理框架 是用于构建 AI 代理的开源 AI 框架。它通过允许你用 @tool 装饰器将工具定义为 Python 函数,简化了函数调用的过程。该框架处理模型与代码之间的双向通信。同时,FoundryChatClient 还提供了预构建工具,如文件搜索和代码解释器。

下图说明了 Microsoft 代理框架中函数调用的流程:

函数调用

在 Microsoft 代理框架中,工具定义为被装饰的函数。我们可以将之前看到的 get_current_time 函数用 @tool 装饰器转换为工具。框架会自动序列化该函数及其参数,创建发送给 LLM 的 Schema。

import os
from agent_framework import tool
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential

@tool(approval_mode="never_require")
def get_current_time(location: str) -> str:
    """Get the current time for a given location"""
    ...

# 创建客户端
provider = FoundryChatClient(
    project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
    model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
    credential=AzureCliCredential(),
)

# 创建一个代理并使用该工具运行
agent = provider.as_agent(name="TimeAgent", instructions="Use available tools to answer questions.", tools=get_current_time)
response = await agent.run("What time is it?")

Microsoft Foundry 代理服务

Microsoft Foundry 代理服务 是一个较新的代理框架,旨在帮助开发者安全地构建、部署和扩展高质量且可扩展的 AI 代理,而无需管理底层计算与存储资源。对企业应用尤其有用,因为它是完全托管的服务,具备企业级安全性。

与直接使用 LLM API 开发相比,Microsoft Foundry 代理服务提供了以下优势:

Microsoft Foundry 代理服务中的工具可分为两类:

  1. 知识工具:
  2. 操作工具:

该代理服务允许我们将这些工具作为 工具集 一起使用。同时它利用 线程 来跟踪特定对话的消息历史。

假设你是 Contoso 公司的销售代理,想开发一个能回答销售数据相关问题的对话代理。

下图展示了如何使用 Microsoft Foundry 代理服务分析销售数据:

代理服务实操

要使用服务中的任何工具,我们可以创建客户端并定义单个工具或工具集。下面的 Python 代码演示了这一实现。LLM 将能够查看工具集,并根据用户请求决定是使用用户创建的函数 fetch_sales_data_using_sqlite_query 还是预构建的代码解释器。

import os
from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential
from fetch_sales_data_functions import fetch_sales_data_using_sqlite_query # 可以在 fetch_sales_data_functions.py 文件中找到的 fetch_sales_data_using_sqlite_query 函数。
from azure.ai.projects.models import ToolSet, FunctionTool, CodeInterpreterTool

project_client = AIProjectClient.from_connection_string(
    credential=DefaultAzureCredential(),
    conn_str=os.environ["PROJECT_CONNECTION_STRING"],
)

# 初始化工具集
toolset = ToolSet()

# 使用 fetch_sales_data_using_sqlite_query 函数初始化函数调用代理,并将其添加到工具集中
fetch_data_function = FunctionTool(fetch_sales_data_using_sqlite_query)
toolset.add(fetch_data_function)

# 初始化代码解释器工具并将其添加到工具集中。
code_interpreter = CodeInterpreterTool()toolset.add(code_interpreter)

agent = project_client.agents.create_agent(
    model="gpt-5-mini", name="my-agent", instructions="You are helpful agent", 
    toolset=toolset
)

使用工具使用设计模式构建可信 AI 代理的特别注意事项?

一个常见的安全问题是 LLM 动态生成的 SQL,特别是存在 SQL 注入或恶意操作(如删除或篡改数据库)的风险。虽然这些担忧合理,但通过正确配置数据库访问权限可以有效避免。对大多数数据库来说,这意味着配置为只读。对于 PostgreSQL 或 Azure SQL 等数据库服务,应为应用分配只读(SELECT)角色。

在安全环境中运行应用进一步增强了保护。在企业场景中,数据通常从操作系统中提取并转换到只读的数据库或数据仓库,并采用友好的 Schema。这保证了数据安全、性能及可访问性的优化,并且应用权限仅限于只读。

示例代码

对工具使用设计模式还有疑问吗?

加入 Microsoft Foundry Discord 与其他学习者交流,参加答疑时间,解答你的 AI 代理相关问题。

额外资源

这个代理的冒烟测试(可选)

在学习了如何部署代理(参见第16课)后,你可以用tests/lesson-04-smoke-tests.json对本课的TravelToolAgent进行冒烟测试(它是否仍然调用其工具并作出回答?)。有关如何运行它,请参见tests/README.md

上一课

理解代理设计模式

下一课

代理式RAG


免责声明: 本文件由 AI 翻译服务 Co-op Translator 翻译完成。尽管我们力求准确,但请注意,自动翻译可能包含错误或不准确之处。原始语言版文件应视为权威来源。对于重要信息,建议使用专业人工翻译。我们对因使用本翻译而产生的任何误解或误释不承担责任。