本教程步骤介绍如何将函数工具与代理配合使用,其中代理是在 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 包,请参阅本教程中的 “创建并运行简单代理 ”步骤。
使用函数工具创建代理
函数工具只是希望代理在需要时能够调用的自定义代码。
可以通过在创建代理时将其传递给代理 tools 的参数,将任何 Python 函数转换为函数工具。
如果需要向代理提供有关函数或其参数的其他说明,以便它可以更准确地在不同的函数之间进行选择,可以使用 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.",
创建声明专用工具
如果工具是在框架外部实现的(例如在 UI 中的客户端),可以在没有具体实现的情况下使用 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())
使用多个函数工具创建类
还可以创建包含多个函数工具作为方法的类。 这对于将相关函数组织在一起或想要在它们之间传递状态时非常有用。
class WeatherTools:
def __init__(self):
self.last_location = None
def get_weather(
self,
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."
def get_weather_details(self) -> int:
"""Get the detailed weather for the last requested location."""
if self.last_location is None:
return "No location specified yet."
return f"The detailed weather in {self.last_location} is cloudy with a high of 15°C, low of 7°C, and 60% humidity."
创建代理时,现在可以将类的所有方法作为函数提供:
tools = WeatherTools()
agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
instructions="You are a helpful assistant",
tools=[tools.get_weather, tools.get_weather_details]
)
还可以使用与以前相同的 @tool 修饰器修饰函数。
完整示例
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from typing import Annotated, Any
from agent_framework import tool
from agent_framework.openai import OpenAIResponsesClient
from pydantic import Field
"""
AI Function with kwargs Example
This example demonstrates how to inject custom keyword arguments (kwargs) into an AI function
from the agent's run method, without exposing them to the AI model.
This is useful for passing runtime information like access tokens, user IDs, or
request-specific context that the tool needs but the model shouldn't know about
or provide.
"""
# Define the function tool with **kwargs to accept injected arguments
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
**kwargs: Any,
) -> str:
"""Get the weather for a given location."""
# Extract the injected argument from kwargs
user_id = 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 injected argument when running the agent
# The 'user_id' kwarg will be passed down to the tool execution via **kwargs
response = await agent.run("What is the weather like in Amsterdam?", user_id="user_123")
print(f"Agent: {response.text}")
if __name__ == "__main__":
asyncio.run(main())
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from typing import Annotated, Any
from agent_framework import tool
from agent_framework.openai import OpenAIResponsesClient
from pydantic import Field
"""
AI Function with kwargs Example
This example demonstrates how to inject custom keyword arguments (kwargs) into an AI function
from the agent's run method, without exposing them to the AI model.
This is useful for passing runtime information like access tokens, user IDs, or
request-specific context that the tool needs but the model shouldn't know about
or provide.
"""
# Define the function tool with **kwargs to accept injected arguments
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
**kwargs: Any,
) -> str:
"""Get the weather for a given location."""
# Extract the injected argument from kwargs
user_id = 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 injected argument when running the agent
# The 'user_id' kwarg will be passed down to the tool execution via **kwargs
response = await agent.run("What is the weather like in Amsterdam?", user_id="user_123")
print(f"Agent: {response.text}")
if __name__ == "__main__":
asyncio.run(main())