你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

如何将函数调用与 Azure OpenAI 服务配合使用(预览版)

最新版本的 gpt-35-turbo 和 gpt-4 经过微调,可使用函数并且能够确定何时以及如何调用函数。 如果请求中包含一个或多个函数,则模型会根据提示的上下文确定是否应调用任何函数。 当模型确定应调用函数时,它会使用包含函数参数的 JSON 对象进行响应。

这些模型会构建 API 调用和构造数据输出,所有这些操作都基于你指定的函数。 请务必注意,虽然模型可以生成这些调用,但执行它们取决于你,确保你保持控制。

概括而言,可以将函数的处理分解为三个步骤:

  1. 使用函数和用户的输入调用聊天完成 API
  2. 使用模型的响应调用 API 或函数
  3. 再次调用聊天完成 API,包括函数的响应以获取最终响应

重要

随着 API 的 2023-12-01-preview 版本的发布,functionsfunction_call 参数已被弃用。 functions 的替代项是 tools 参数。 function_call 的替代项是 tool_choice 参数。

函数调用支持

并行函数调用

  • gpt-35-turbo (1106)
  • gpt-35-turbo (0125)
  • gpt-4 (1106-Preview)
  • gpt-4 (0125-Preview)
  • gpt-4 (vision-preview)
  • gpt-4 (2024-04-09)
  • gpt-4o (2024-05-13)
  • gpt-4o-mini (2024-07-18)

对并行函数的支持最初在 API 版本 2023-12-01-preview 中添加

使用工具调用基本函数

  • 支持并行函数调用的所有模型
  • gpt-4 (0613)
  • gpt-4-32k (0613)
  • gpt-35-turbo-16k (0613)
  • gpt-35-turbo (0613)

单个工具/函数调用示例

首先,我们将演示一个简单的玩具函数调用,它可以使用一个定义的工具/函数来检查三个硬编码位置的时间。 我们添加了 print 语句,以帮助更轻松地跟踪代码的执行:

import os
import json
from openai import AzureOpenAI
from datetime import datetime
from zoneinfo import ZoneInfo

# Initialize the Azure OpenAI client
client = AzureOpenAI(
    azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT"), 
    api_key=os.getenv("AZURE_OPENAI_API_KEY"),  
    api_version="2024-05-01-preview"
)

# Define the deployment you want to use for your chat completions API calls

deployment_name = "<YOUR_DEPLOYMENT_NAME_HERE>"

# Simplified timezone data
TIMEZONE_DATA = {
    "tokyo": "Asia/Tokyo",
    "san francisco": "America/Los_Angeles",
    "paris": "Europe/Paris"
}

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"})

def run_conversation():
    # Initial user message
    messages = [{"role": "user", "content": "What's the current time in San Francisco"}] # Single function call
    #messages = [{"role": "user", "content": "What's the current time in San Francisco, Tokyo, and Paris?"}] # Parallel function call with a single tool/function defined

    # Define the function for the model
    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"],
                },
            }
        }
    ]

    # First API call: Ask the model to use the function
    response = client.chat.completions.create(
        model=deployment_name,
        messages=messages,
        tools=tools,
        tool_choice="auto",
    )

    # Process the model's response
    response_message = response.choices[0].message
    messages.append(response_message)

    print("Model's response:")  
    print(response_message)  

    # Handle function calls
    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)
                print(f"Function arguments: {function_args}")  
                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.")  

    # Second API call: Get the final response from the model
    final_response = client.chat.completions.create(
        model=deployment_name,
        messages=messages,
    )

    return final_response.choices[0].message.content

# Run the conversation and print the result
print(run_conversation())

输出:

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')])
Function arguments: {'location': 'San Francisco'}
get_current_time called with location: San Francisco
Timezone found for san francisco
The current time in San Francisco is 09:24 AM.

如果使用支持并行函数调用的模型部署,则可以通过更改消息数组来请求在多个位置而不是一个位置中请求时间,从而将其转换为并行函数调用示例。

为此,请在以下两行中交换注释:

    messages = [{"role": "user", "content": "What's the current time in San Francisco"}] # Single function call
    #messages = [{"role": "user", "content": "What's the current time in San Francisco, Tokyo, and Paris?"}] # Parallel function call with a single tool/function defined

若要达到这样的效果,请再次运行代码:

    #messages = [{"role": "user", "content": "What's the current time in San Francisco"}] # Single function call
    messages = [{"role": "user", "content": "What's the current time in San Francisco, Tokyo, and Paris?"}] # Parallel function call with a single tool/function defined

此操作生成以下输出:

输出:

Model's response:
ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_IjcAVz9JOv5BXwUx1jd076C1', function=Function(arguments='{"location": "San Francisco"}', name='get_current_time'), type='function'), ChatCompletionMessageToolCall(id='call_XIPQYTCtKIaNCCPTdvwjkaSN', function=Function(arguments='{"location": "Tokyo"}', name='get_current_time'), type='function'), ChatCompletionMessageToolCall(id='call_OHIB5aJzO8HGqanmsdzfytvp', function=Function(arguments='{"location": "Paris"}', name='get_current_time'), type='function')])
Function arguments: {'location': 'San Francisco'}
get_current_time called with location: San Francisco
Timezone found for san francisco
Function arguments: {'location': 'Tokyo'}
get_current_time called with location: Tokyo
Timezone found for tokyo
Function arguments: {'location': 'Paris'}
get_current_time called with location: Paris
Timezone found for paris
As of now, the current times are:

- **San Francisco:** 11:15 AM
- **Tokyo:** 03:15 AM (next day)
- **Paris:** 08:15 PM

通过并行函数调用,可以一起执行多个函数调用,从而允许并行执行和检索结果。 这减少了需要对 API 进行的调用数,并可以提高整体性能。

例如,在我们的简单时间应用中,我们同时检索了多个时间。 这带来的结果是在 tool_calls 数组中具有三个函数调用的聊天补全消息,其中每个调用都具有唯一的 id。 如果想要响应这些函数调用,则需要向对话添加 3 个新消息,每个消息包含一个函数调用的结果,其中 tool_call_id 引用了来自 tools_callsid

要强制模型调用特定函数,请使用特定函数名称设置 tool_choice 参数。 还可以通过设置 tool_choice: "none" 来强制模型生成面向用户的消息。

注意

默认行为(tool_choice: "auto")是由模型自行决定是否调用函数以及调用哪个函数(如果决定调用)。

使用多个函数进行并行函数调用

现在,我们将演示另一个玩具函数调用示例,它定义了两个不同的工具/函数。

import os
import json
from openai import AzureOpenAI
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

# Initialize the Azure OpenAI client
client = AzureOpenAI(
    azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT"), 
    api_key=os.getenv("AZURE_OPENAI_API_KEY"),  
    api_version="2024-05-01-preview"
)

# Provide the model deployment name you want to use for this example

deployment_name = "YOUR_DEPLOYMENT_NAME_HERE" 

# Simplified weather data
WEATHER_DATA = {
    "tokyo": {"temperature": "10", "unit": "celsius"},
    "san francisco": {"temperature": "72", "unit": "fahrenheit"},
    "paris": {"temperature": "22", "unit": "celsius"}
}

# Simplified timezone data
TIMEZONE_DATA = {
    "tokyo": "Asia/Tokyo",
    "san francisco": "America/Los_Angeles",
    "paris": "Europe/Paris"
}

def get_current_weather(location, unit=None):
    """Get the current weather for a given location"""
    print(f"get_current_weather called with location: {location}, unit: {unit}")  
    
    for key in WEATHER_DATA:
        if key in location_lower:
            print(f"Weather data found for {key}")  
            weather = WEATHER_DATA[key]
            return json.dumps({
                "location": location,
                "temperature": weather["temperature"],
                "unit": unit if unit else weather["unit"]
            })
    
    print(f"No weather data found for {location_lower}")  
    return json.dumps({"location": location, "temperature": "unknown"})

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"})

def run_conversation():
    # Initial user message
    messages = [{"role": "user", "content": "What's the weather and current time in San Francisco, Tokyo, and Paris?"}]

    # Define the functions for the model
    tools = [
        {
            "type": "function",
            "function": {
                "name": "get_current_weather",
                "description": "Get the current weather in a given location",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "The city name, e.g. San Francisco",
                        },
                        "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                    },
                    "required": ["location"],
                },
            }
        },
        {
            "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"],
                },
            }
        }
    ]

    # First API call: Ask the model to use the functions
    response = client.chat.completions.create(
        model=deployment_name,
        messages=messages,
        tools=tools,
        tool_choice="auto",
    )

    # Process the model's response
    response_message = response.choices[0].message
    messages.append(response_message)

    print("Model's response:")  
    print(response_message)  

    # Handle function calls
    if response_message.tool_calls:
        for tool_call in response_message.tool_calls:
            function_name = tool_call.function.name
            function_args = json.loads(tool_call.function.arguments)
            print(f"Function call: {function_name}")  
            print(f"Function arguments: {function_args}")  
            
            if function_name == "get_current_weather":
                function_response = get_current_weather(
                    location=function_args.get("location"),
                    unit=function_args.get("unit")
                )
            elif function_name == "get_current_time":
                function_response = get_current_time(
                    location=function_args.get("location")
                )
            else:
                function_response = json.dumps({"error": "Unknown function"})
            
            messages.append({
                "tool_call_id": tool_call.id,
                "role": "tool",
                "name": function_name,
                "content": function_response,
            })
    else:
        print("No tool calls were made by the model.")  

    # Second API call: Get the final response from the model
    final_response = client.chat.completions.create(
        model=deployment_name,
        messages=messages,
    )

    return final_response.choices[0].message.content

# Run the conversation and print the result
print(run_conversation())

输出

Model's response:
ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_djHAeQP0DFEVZ2qptrO0CYC4', function=Function(arguments='{"location": "San Francisco", "unit": "celsius"}', name='get_current_weather'), type='function'), ChatCompletionMessageToolCall(id='call_q2f1HPKKUUj81yUa3ITLOZFs', function=Function(arguments='{"location": "Tokyo", "unit": "celsius"}', name='get_current_weather'), type='function'), ChatCompletionMessageToolCall(id='call_6TEY5Imtr17PaB4UhWDaPxiX', function=Function(arguments='{"location": "Paris", "unit": "celsius"}', name='get_current_weather'), type='function'), ChatCompletionMessageToolCall(id='call_vpzJ3jElpKZXA9abdbVMoauu', function=Function(arguments='{"location": "San Francisco"}', name='get_current_time'), type='function'), ChatCompletionMessageToolCall(id='call_1ag0MCIsEjlwbpAqIXJbZcQj', function=Function(arguments='{"location": "Tokyo"}', name='get_current_time'), type='function'), ChatCompletionMessageToolCall(id='call_ukOu3kfYOZR8lpxGRpdkhhdD', function=Function(arguments='{"location": "Paris"}', name='get_current_time'), type='function')])
Function call: get_current_weather
Function arguments: {'location': 'San Francisco', 'unit': 'celsius'}
get_current_weather called with location: San Francisco, unit: celsius
Weather data found for san francisco
Function call: get_current_weather
Function arguments: {'location': 'Tokyo', 'unit': 'celsius'}
get_current_weather called with location: Tokyo, unit: celsius
Weather data found for tokyo
Function call: get_current_weather
Function arguments: {'location': 'Paris', 'unit': 'celsius'}
get_current_weather called with location: Paris, unit: celsius
Weather data found for paris
Function call: get_current_time
Function arguments: {'location': 'San Francisco'}
get_current_time called with location: San Francisco
Timezone found for san francisco
Function call: get_current_time
Function arguments: {'location': 'Tokyo'}
get_current_time called with location: Tokyo
Timezone found for tokyo
Function call: get_current_time
Function arguments: {'location': 'Paris'}
get_current_time called with location: Paris
Timezone found for paris
Here's the current information for the three cities:

### San Francisco
- **Time:** 09:13 AM
- **Weather:** 72°C (quite warm!)

### Tokyo
- **Time:** 01:13 AM (next day)
- **Weather:** 10°C

### Paris
- **Time:** 06:13 PM
- **Weather:** 22°C

Is there anything else you need?

重要

JSON 响应可能并不总是有效的,因此需要向代码添加其他逻辑才能处理错误。 对于某些用例,你可能会发现需要使用微调来提高函数调用性能

使用函数进行提示工程

将函数定义为请求的一部分时,将使用已为其训练模型的特定语法将详细信息注入系统消息中。 这意味着函数会在提示中使用令牌,并且你可以应用提示工程技术来优化函数调用的性能。 该模型使用提示的完整上下文来确定是否应调用函数,包括函数定义、系统消息和用户消息。

提高质量和可靠性

如果模型未按预期时间或方式调用函数,则可以尝试执行一些操作来提高质量。

在函数定义中提供更多详细信息

请务必提供有意义的 description 函数,并为任何可能对模型不明显的参数提供描述。 例如,在 location 参数的描述中,可以包含有关位置格式的额外详细信息和示例。

"location": {
    "type": "string",
    "description": "The location of the hotel. The location should include the city and the state's abbreviation (i.e. Seattle, WA or Miami, FL)"
},
在系统消息中提供更多上下文

系统消息还可用于为模型提供更多上下文。 例如,如果有一个名为 search_hotels 的函数,则可以包含如下系统消息,指示模型在用户请求帮助查找酒店时调用该函数。

{"role": "system", "content": "You're an AI assistant designed to help users search for hotels. When a user asks for help finding a hotel, you should call the search_hotels function."}
指示模型提出澄清性问题

在某些情况下,你希望指示模型提出澄清性问题,以防止对函数使用的值做出假设。 例如,对于 search_hotels,如果用户请求不包含有关 location 的详细信息,则你希望模型要求澄清。 若要指示模型提出澄清性问题,可以在系统消息中包含下一个示例之类的内容。

{"role": "system", "content": "Don't make assumptions about what values to use with functions. Ask for clarification if a user request is ambiguous."}

减少错误

提示工程可能有价值的另一个领域是减少函数调用中的错误。 已训练模型来生成与你定义的架构匹配的函数调用,但模型会生成与你定义的架构不匹配的函数调用,或者尝试调用你未包含的函数。

如果发现模型正在生成未提供的函数调用,请尝试在系统消息中包含一个句子,该句子显示 "Only use the functions you have been provided with."

负责任地使用函数调用

与任何 AI 系统一样,使用函数调用将语言模型与其他工具和系统集成会带来潜在风险。 请务必了解函数调用可能存在的风险,并采取措施确保负责任地使用这些功能。

下面是一些可帮助你安全可靠地使用函数的提示:

  • 验证函数调用:始终验证模型生成的函数调用。 这包括检查参数、正在调用的函数,以及确保调用与预期操作一致。
  • 使用受信任的数据和工具:仅使用来自受信任和已验证源的数据。 函数输出中的不受信任数据可用于指示模型以非预期方式编写函数调用。
  • 遵循最低特权原则:仅授予函数执行其作业所需的最低访问权限。 这可以减少滥用或利用函数时的潜在影响。 例如,如果使用函数调用来查询数据库,则只应向应用程序授予对该数据库的只读访问权限。 此外,不应仅依赖于将函数定义中的功能排除为安全控件。
  • 考虑实际影响:请注意你计划执行的函数调用的实际影响,尤其是那些触发执行代码、更新数据库或发送通知等操作的函数调用。
  • 实施用户确认步骤:特别是对于执行操作的函数,我们建议包括用户在执行操作前确认操作的步骤。

若要详细了解有关如何负责任地使用 Azure OpenAI 模型的建议,请参阅 Azure OpenAI 模型的负责任 AI 做法概述

后续步骤