ai-agents-for-beginners

如何设计优秀的 AI 代理

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

工具使用设计模式

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

简介

在本课中,我们希望回答以下问题:

学习目标

完成本课后,您将能够:

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

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

它适用于哪些用例?

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

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

这些构建模块使 AI 代理能够执行广泛任务。下面是实现工具使用设计模式的关键元素:

接下来,我们将更详细地讲解函数/工具调用。

函数/工具调用

函数调用是使大型语言模型(LLM)与工具交互的主要方式。常会看到“函数”和“工具”交替使用,因为“函数”(可复用代码块)就是代理用来完成任务的“工具”。为了调用函数代码,LLM 必须将用户请求与函数描述进行匹配。为此,会向 LLM 发送包含所有可用函数描述的模式(schema)。LLM 会选择最适合该任务的函数,并返回其名称与参数。随后调用该函数,函数响应返回给 LLM,LLM 使用这些信息回应用户请求。

开发者实现代理的函数调用时需要:

  1. 支持函数调用的 LLM 模型
  2. 包含函数描述的模式(schema)
  3. 描述的各函数代码

以获取某城市当前时间为例说明:

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

    并非所有模型都支持函数调用,因此需确认所用 LLM 支持。Azure OpenAI 支持函数调用。我们可以从初始化 Azure OpenAI 客户端开始。

     # 初始化 Azure OpenAI 客户端
     client = AzureOpenAI(
         azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT"), 
         api_key=os.getenv("AZURE_OPENAI_API_KEY"),  
         api_version="2024-05-01-preview"
     )
    
  2. 创建函数模式:

    接下来定义包含函数名称、功能描述及函数参数名称和描述的 JSON 模式。 然后将此模式与用户请求(例如查询旧金山时间)一起传给先前建立的客户端。需要注意的是,返回的结果是工具调用,而问题的最终答案。如前所述,LLM 返回它为任务选择的函数名称及将传递的参数。

     # 供模型读取的功能描述
     tools = [
         {
             "type": "function",
             "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.chat.completions.create(
           model=deployment_name,
           messages=messages,
           tools=tools,
           tool_choice="auto",
       )
      
       # 处理模型的回复
       response_message = response.choices[0].message
       messages.append(response_message)
      
       print("Model's response:")  
    
       print(response_message)
      
    
     Model's response:
     ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_pOsKdUlqvdyttYB67MOj434b', function=Function(arguments='{"location":"San Francisco"}', name='get_current_time'), type='function')])
    
  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"})
    
      # 处理函数调用
       if response_message.tool_calls:
           for tool_call in response_message.tool_calls:
               if tool_call.function.name == "get_current_time":
         
                   function_args = json.loads(tool_call.function.arguments)
         
                   time_response = get_current_time(
                       location=function_args.get("location")
                   )
         
                   messages.append({
                       "tool_call_id": tool_call.id,
                       "role": "tool",
                       "name": "get_current_time",
                       "content": time_response,
                   })
       else:
           print("No tool calls were made by the model.")  
      
       # 第二次API调用:从模型获取最终响应
       final_response = client.chat.completions.create(
           model=deployment_name,
           messages=messages,
       )
      
       return final_response.choices[0].message.content
    
       get_current_time called with location: San Francisco
       Timezone found for san francisco
       The current time in San Francisco is 09:24 AM.
    

函数调用是大多数(如果不是全部)代理工具使用设计的核心,但从零实现有时存在挑战。 正如我们在第 2 课中了解到的,代理框架为我们提供了预构建的构建模块,以实现工具使用。

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

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

语义内核(Semantic Kernel)

Semantic Kernel 是一个开源 AI 框架,面向 .NET、Python 和 Java 开发者,用于处理大型语言模型(LLM)。它通过一种称为序列化的过程,自动向模型描述函数及其参数,从而简化函数调用。此外它还处理模型与代码间的往返通信。使用如 Semantic Kernel 这样的代理框架的另一个优势是,可以访问预构建工具,如文件搜索代码解释器

下图展示了使用 Semantic Kernel 进行函数调用的流程:

function calling

在 Semantic Kernel 中,函数/工具被称为插件。我们可以将之前的 get_current_time 函数通过封装为带函数的类转换成插件。还可导入 kernel_function 装饰器,传入函数描述。当用 GetCurrentTimePlugin 创建内核时,内核会自动序列化函数及其参数,创建发送给 LLM 的模式。

from semantic_kernel.functions import kernel_function

class GetCurrentTimePlugin:
    async def __init__(self, location):
        self.location = location

    @kernel_function(
        description="Get the current time for a given location"
    )
    def get_current_time(location: str = ""):
        ...

from semantic_kernel import Kernel

# 创建内核
kernel = Kernel()

# 创建插件
get_current_time_plugin = GetCurrentTimePlugin(location)

# 将插件添加到内核
kernel.add_plugin(get_current_time_plugin)

Azure AI 代理服务

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

与直接使用 LLM API 开发相比,Azure AI 代理服务的优势包括:

Azure AI 代理服务中的工具分为两类:

  1. 知识工具:
  2. 行动工具:

代理服务使我们能够将这些工具作为一个 toolset 一起使用。它还利用了threads,用于跟踪特定对话的消息历史。

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

下图演示了如何使用 Azure AI 代理服务分析销售数据:

Agentic Service In Action

要使用服务中的任一工具,我们可以创建客户端并定义工具或工具集。以下 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_using_sqlite_query 函数,位于 fetch_sales_data_functions.py 文件中。
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 = code_interpreter = CodeInterpreterTool()
toolset.add(code_interpreter)

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

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

LLM 动态生成的 SQL 最常见的担忧是安全性,特别是 SQL 注入或恶意操作(如删除或篡改数据库)的风险。尽管这些担忧是合理的,但通过正确配置数据库访问权限可以有效缓解。对于大多数数据库,需将数据库配置为只读。在 PostgreSQL 或 Azure SQL 等数据库服务中,应用应被分配只读(SELECT)角色。 在安全环境中运行应用程序可以进一步增强保护。在企业场景中,数据通常从运营系统中提取并转换到一个只读数据库或数据仓库,且采用用户友好的架构。这种方法确保数据安全、性能和可访问性得到优化,并且应用程序具有受限的只读访问权限。

Sample Codes

Got More Questions about the Tool Use Design Patterns?

Join the Azure AI Foundry Discord to meet with other learners, attend office hours and get your AI Agents questions answered.

Additional Resources

Previous Lesson

Understanding Agentic Design Patterns

Next Lesson

Agentic RAG


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