ai-agents-for-beginners

如何設計優秀的 AI 代理

(點擊上方圖片觀看本課程影片)

工具使用設計模式

工具很有趣,因為它們使 AI 代理擁有更廣泛的能力。代理不再只有一組有限的動作可執行,透過加入工具,代理現在可以執行多種動作。在本章中,我們將探討工具使用設計模式,說明 AI 代理如何利用特定工具達成目標。

簡介

本課我們將嘗試回答以下問題:

學習目標

完成本課後,您將能夠:

什麼是工具使用設計模式?

工具使用設計模式 主要聚焦於賦予大型語言模型(LLM)與外部工具互動的能力,以達成特定目標。工具是代理可以執行的程式碼,用於完成各種動作。工具可以是簡單的函式,例如計算器,也可以是第三方服務的 API 調用,如查詢股價或天氣預報。在 AI 代理的語境下,工具設計是由代理依據 模型生成的函式呼叫 來執行的。

它可以應用於哪些使用情境?

AI 代理可以利用工具完成複雜任務、檢索資訊或做決策。工具使用設計模式常在需要與外部系統動態互動的情境中使用,例如資料庫、網絡服務或程式碼解譯器。此能力適用於多種不同用例,包括:

實現工具使用設計模式所需的元素/組件?

這些組件使 AI 代理能執行各種任務。讓我們來看看實現工具使用設計模式所需的關鍵元素:

接下來,我們將更詳細地探討函式/工具呼叫。

函式/工具呼叫

函式呼叫是啟用大型語言模型(LLM)與工具互動的主要方式。您會常見「函式」與「工具」可互換使用,因為「函式」(可重用程式碼塊)即是代理用以執行任務的「工具」。為了讓函式程式碼被調用,LLM 必須將用戶請求與函式描述做比較。為此,要將包含所有可用函式描述的結構(schema)傳送給 LLM。LLM 接著選擇最合適的函式並回傳其名稱與參數。系統調用所選函式,將其回應返回給 LLM,LLM 利用此資訊回應用戶請求。

開發者要為代理實現函式呼叫,需要:

  1. 支援函式呼叫的 LLM 模型
  2. 包含函式描述的結構
  3. 各函式的程式碼

用取得某城市當前時間的例子來說明:

  1. 初始化一個支援函式呼叫的 LLM:

    並非所有模型都支援函式呼叫,因此重要的是確認您使用的 LLM 具備此功能。Azure OpenAI 支援函式呼叫。我們可以從對 Azure OpenAI Response API 發送請求開始(穩定的 /openai/v1/ 端點——不需 api_version)。

     # 初始化用於 Azure OpenAI 的 OpenAI 客戶端(Responses 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. 建立函式結構:

    接著我們定義一個 JSON 結構,其中包含函式名稱、功能說明,以及函式參數的名稱與說明。 然後將此結構與用戶請求(尋找舊金山時間)一同傳給前述的客戶端。重要的是,工具呼叫會被返回,而非問題的最終答案。如先前所述,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.
    

函式呼叫是大多數(若非全部)代理工具使用設計的核心,但自行從頭實作有時會具挑戰性。 如我們在第二課中學到的,代理框架為我們提供了預建組件來實現工具使用。

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

以下是如何使用不同代理框架實作工具使用設計模式的一些範例:

Microsoft 代理框架

Microsoft 代理框架 是一個開源 AI 框架,用於構建 AI 代理。它簡化了函式呼叫的過程,允許您使用 @tool 裝飾器將 Python 函式定義為工具。此框架處理模型與您的程式碼之間的雙向通訊,並透過 FoundryChatClient 提供對預建工具如檔案搜索和程式碼解譯器的存取。

下圖說明了 Microsoft 代理框架中函式呼叫的過程:

function calling

在 Microsoft 代理框架中,工具是以裝飾函式定義。我們可以用 @tool 裝飾器將先前看到的 get_current_time 函式轉換為工具。框架會自動序列化函式及其參數,建立要送給 LLM 的結構。

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. 行動工具:

代理服務讓我們能將這些工具一起組成 toolset,並使用 threads 追蹤特定對話的訊息歷史。

想像您是 Contoso 公司的業務代理,想開發一個會話代理來回答關於銷售資料的問題。

下圖展示如何使用 Microsoft Foundry 代理服務分析銷售資料:

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 = 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)角色。

在安全環境中執行應用程式能進一步強化防護。在企業場景中,資料通常會從營運系統中擷取並轉換到結構化且方便使用的唯讀資料庫或資料倉儲。此方法確保資料安全、效能與可用性優化,且應用程式擁有受限的唯讀存取。

範例程式碼

關於工具使用設計模式有更多問題?

加入 Microsoft Foundry Discord 與其他學習者交流,參加辦公時間並獲取 AI 代理相關問題的解答。

額外資源

對此代理進行煙霧測試(可選)

在學習如何於第16課部署代理後,你可以透過tests/lesson-04-smoke-tests.json對本課的TravelToolAgent進行煙霧測試(它是否仍會調用工具並回應?)。關於如何運行,請參閱tests/README.md

上一課

理解代理設計模式

下一課

代理式RAG


免責聲明: 本文件使用 AI 翻譯服務 Co-op Translator 進行翻譯。雖然我們力求準確,但請注意,自動翻譯可能包含錯誤或不準確之處。原始文件的母語版本應被視為權威來源。對於重要資訊,建議尋求專業人工翻譯。我們不對因使用本翻譯而引起的任何誤解或曲解承擔責任。