本教學課程步驟示範如何搭配代理程式使用函式工具,其中代理程式是以 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 。