共用方式為


使用函數工具搭配代理程式

本教學課程步驟示範如何搭配代理程式使用函式工具,其中代理程式是以 Azure OpenAI 聊天完成服務為基礎。

這很重要

並非所有代理程式類型都支援函數工具。 有些可能只支援自訂內建工具,而不允許呼叫端提供自己的函式。 此步驟使用 ChatClientAgent,它確實支援函式工具。

先決條件

如需必要條件和安裝 NuGet 套件,請參閱本教學課程中的 建立並執行簡單代理程式 步驟。

使用函數工具建立代理程式

函數工具只是您希望客服專員能夠在需要時呼叫的自訂程式碼。 您可以使用方法從 AIFunctionFactory.Create 方法建立 AIFunction 實例,將任何 C# 方法變成函式工具。

如果您需要向代理程式提供有關函數或其參數的額外描述,以便代理程式可以更準確地在不同函數之間進行選擇,您可以使用 System.ComponentModel.DescriptionAttribute 方法及其參數上的屬性。

這是一個簡單的函數工具的示例,該工具可以偽造獲取給定位置的天氣。 它會以說明屬性裝飾,以向代理程式提供有關其本身及其位置參數的其他說明。

using System.ComponentModel;

[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
    => $"The weather in {location} is cloudy with a high of 15°C.";

建立代理程式時,您現在可以透過將工具清單傳遞給 AsAIAgent 方法,將函數工具提供給代理程式。

using System;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;

AIAgent agent = new AzureOpenAIClient(
    new Uri("https://<myresource>.openai.azure.com"),
    new DefaultAzureCredential())
     .GetChatClient("gpt-4o-mini")
     .AsAIAgent(instructions: "You are a helpful assistant", tools: [AIFunctionFactory.Create(GetWeather)]);

警告

DefaultAzureCredential 開發方便,但在生產過程中需謹慎考量。 在生產環境中,建議使用特定的憑證(例如 ManagedIdentityCredential),以避免延遲問題、意外的憑證探測,以及備援機制帶來的安全風險。

現在您可以正常執行代理程式,且代理程式將能夠在需要時呼叫 GetWeather 函式工具。

Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?"));

小提示

完整可執行範例請參閱 .NET 範例

這很重要

並非所有代理程式類型都支援函數工具。 有些可能只支援自訂內建工具,而不允許呼叫端提供自己的函式。 此步驟使用透過聊天用戶端建立的代理程式,這些用戶端支援功能工具。

先決條件

如需必要條件和安裝 Python 套件,請參閱本教學課程中的 建立並執行簡式代理程式 步驟。

使用函數工具建立代理程式

函數工具只是您希望客服專員能夠在需要時呼叫的自訂程式碼。 您可以在建立代理程式時將任何 Python 函數傳遞給代理程式的 tools 參數,將其轉換為函數工具。

如果需要向代理提供有關函數或其參數的額外描述,以便其能夠更準確地在不同函數之間進行選擇,可以使用Python的類型註解和 Annotated Pydantic的 Field 來提供描述。

這是一個簡單的函數工具的示例,該工具可以偽造獲取給定位置的天氣。 它使用類型註釋,向代理程式提供有關函數及其位置參數的其他描述。

from typing import Annotated
from pydantic import Field

def get_weather(
    location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
    """Get the weather for a given location."""
    return f"The weather in {location} is cloudy with a high of 15°C."

您也可以使用 @tool 裝飾器來明確指定函數的名稱和描述:

from typing import Annotated
from pydantic import Field
from agent_framework import tool

@tool(name="weather_tool", description="Retrieves weather information for any location")
def get_weather(
    location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
    return f"The weather in {location} is cloudy with a high of 15°C."

如果您未在name裝飾器中指定description@tool參數,框架將自動使用函數的名稱和說明字串作為後備。

使用明確的結構模式 @tool

當你需要完全控制暴露給模型的結構時,將參數傳給 schema@tool。 你可以提供 Pydantic 模型或原始的 JSON 結構字典。

# Load environment variables from .env file
load_dotenv()


# Approach 1: Pydantic model as explicit schema
class WeatherInput(BaseModel):
    """Input schema for the weather tool."""

    location: Annotated[str, Field(description="The city name to get weather for")]
    unit: Annotated[str, Field(description="Temperature unit: celsius or fahrenheit")] = "celsius"


@tool(
    name="get_weather",
    description="Get the current weather for a given location.",
    schema=WeatherInput,
    approval_mode="never_require",
    """Get the current weather for a location."""
    return f"The weather in {location} is 22 degrees {unit}."


# Approach 2: JSON schema dictionary as explicit schema
get_current_time_schema = {
    "type": "object",
    "properties": {
        "timezone": {"type": "string", "description": "The timezone to get the current time for", "default": "UTC"},
    },
}


@tool(
    name="get_current_time",
    description="Get the current time in a given timezone.",

將僅執行時的上下文傳遞給工具

使用模型應提供的標準函數參數。 使用 FunctionInvocationContext 用於運行時值,例如 function_invocation_kwargs 或當前會話。 注入的上下文參數在暴露給模型的架構中是隱藏的。

import asyncio
from typing import Annotated

from agent_framework import FunctionInvocationContext, tool
from agent_framework.openai import OpenAIResponsesClient
from dotenv import load_dotenv
from pydantic import Field
# Define the function tool with explicit invocation context.
# The context parameter can also be declared as an untyped ``ctx`` parameter.
@tool(approval_mode="never_require")
def get_weather(
    location: Annotated[str, Field(description="The location to get the weather for.")],
    ctx: FunctionInvocationContext,
) -> str:
    """Get the weather for a given location."""
    # Extract the injected argument from the explicit context
    user_id = ctx.kwargs.get("user_id", "unknown")

    # Simulate using the user_id for logging or personalization
    print(f"Getting weather for user: {user_id}")

    return f"The weather in {location} is cloudy with a high of 15°C."


async def main() -> None:
    agent = OpenAIResponsesClient().as_agent(
        name="WeatherAgent",
        instructions="You are a helpful weather assistant.",
        tools=[get_weather],
    )

    # Pass the runtime context explicitly when running the agent.
    response = await agent.run(
        "What is the weather like in Amsterdam?",
        function_invocation_kwargs={"user_id": "user_123"},
    )

關於 、 ctx.kwargs和 函式中介軟體的更多細節ctx.session,請參見執行時情境

建立僅供宣告使用的工具

如果工具是在框架外實作的(例如用戶端介面),你可以用 FunctionTool(..., func=None)宣告它而不做實作。 模型仍能推理並呼叫工具,而你的應用程式之後也能提供結果。


# Load environment variables from .env file
load_dotenv()

# A declaration-only tool: the schema is sent to the LLM, but the framework
# has no implementation to execute. The caller must supply the result.
get_user_location = FunctionTool(
    name="get_user_location",
    func=None,
    description="Get the user's current city. Only the client application can resolve this.",
    input_model={
        "type": "object",
        "properties": {
            "reason": {"type": "string", "description": "Why the location is needed"},

建立代理程式時,您現在可以將函數工具傳遞給 tools 參數,以將函數工具提供給代理程式。

import asyncio
from agent_framework.azure import AzureOpenAIChatClient
from azure.identity import AzureCliCredential

agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
    instructions="You are a helpful assistant",
    tools=get_weather
)

現在您可以正常執行代理程式,且代理程式將能夠在需要時呼叫 get_weather 函式工具。

async def main():
    result = await agent.run("What is the weather like in Amsterdam?")
    print(result.text)

asyncio.run(main())

建立具有多個函數工具的類別

當多個工具共享相依性或可變狀態時,將它們包裝成類別,並將綁定方法傳給代理。 對於模型不應該提供的值,例如服務客戶端、功能標誌或快取狀態,使用類別屬性。

import asyncio
from typing import Annotated

from agent_framework import tool
from agent_framework.openai import OpenAIResponsesClient
from dotenv import load_dotenv
class MyFunctionClass:
    def __init__(self, safe: bool = False) -> None:
        """Simple class with two tools: divide and add.

        The safe parameter controls whether divide raises on division by zero or returns `infinity` for divide by zero.
        """
        self.safe = safe

    def divide(
        self,
        a: Annotated[int, "Numerator"],
        b: Annotated[int, "Denominator"],
    ) -> str:
        """Divide two numbers, safe to use also with 0 as denominator."""
        result = "∞" if b == 0 and self.safe else a / b
        return f"{a} / {b} = {result}"

    def add(
        self,
        x: Annotated[int, "First number"],
        y: Annotated[int, "Second number"],
    ) -> str:
        return f"{x} + {y} = {x + y}"


async def main():
    # Creating my function class with safe division enabled
    tools = MyFunctionClass(safe=True)
    # Applying the tool decorator to one of the methods of the class
    add_function = tool(description="Add two numbers.")(tools.add)

    agent = OpenAIResponsesClient().as_agent(
        name="ToolAgent",
        instructions="Use the provided tools.",
    )
    print("=" * 60)
    print("Step 1: Call divide(10, 0) - tool returns infinity")
    query = "Divide 10 by 0"
    response = await agent.run(
        query,
        tools=[add_function, tools.divide],
    )
    print(f"Response: {response.text}")
    print("=" * 60)
    print("Step 2: Call set safe to False and call again")
    # Disabling safe mode to allow exceptions
    tools.safe = False
    response = await agent.run(query, tools=[add_function, tools.divide])

此模式非常適合長期使用的工具狀態。 當值在每次調用時發生變化時,應改用 FunctionInvocationContext

後續步驟