你当前正在访问 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)

API 支持

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

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

例如,对于简单的天气应用,你可能希望同时检索多个位置的天气。 这将通过在 tool_calls 数组中进行三个函数调用来生成聊天完成消息,其中每个函数调用都是唯一的 id。 如果想要响应这些函数调用,则会向对话添加 3 个新消息,每个消息包含一个函数调用的结果,其中 tool_call_id 引用了来自 tools_callsid

我们在下面提供了 OpenAI 示例 get_current_weather 的修改版本。 与 OpenAI 中的原始示例一样,此示例提供的是基本结构,而不是全功能的独立示例。 在不进行进一步修改的情况下尝试执行此代码将导致错误。

此示例定义了单个函数 get_current_weather。 模型会多次调用该函数,并在将函数响应发送回模型后决定下一步。 它使用面向用户的信息做出响应,以告知用户旧金山、东京和巴黎的气温。 根据查询,它可以选择再次调用函数。

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

注意

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

import os
from openai import AzureOpenAI
import json

client = AzureOpenAI(
  azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT"), 
  api_key=os.getenv("AZURE_OPENAI_API_KEY"),  
  api_version="2024-03-01-preview"
)


# Example function hard coded to return the same weather
# In production, this could be your backend API or an external API
def get_current_weather(location, unit="fahrenheit"):
    """Get the current weather in a given location"""
    if "tokyo" in location.lower():
        return json.dumps({"location": "Tokyo", "temperature": "10", "unit": unit})
    elif "san francisco" in location.lower():
        return json.dumps({"location": "San Francisco", "temperature": "72", "unit": unit})
    elif "paris" in location.lower():
        return json.dumps({"location": "Paris", "temperature": "22", "unit": unit})
    else:
        return json.dumps({"location": location, "temperature": "unknown"})

def run_conversation():
    # Step 1: send the conversation and available functions to the model
    messages = [{"role": "user", "content": "What's the weather like in San Francisco, Tokyo, and Paris?"}]
    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 and state, e.g. San Francisco, CA",
                        },
                        "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                    },
                    "required": ["location"],
                },
            },
        }
    ]
    response = client.chat.completions.create(
        model="<REPLACE_WITH_YOUR_MODEL_DEPLOYMENT_NAME>",
        messages=messages,
        tools=tools,
        tool_choice="auto",  # auto is default, but we'll be explicit
    )
    response_message = response.choices[0].message
    tool_calls = response_message.tool_calls
    # Step 2: check if the model wanted to call a function
    if tool_calls:
        # Step 3: call the function
        # Note: the JSON response may not always be valid; be sure to handle errors
        available_functions = {
            "get_current_weather": get_current_weather,
        }  # only one function in this example, but you can have multiple
        messages.append(response_message)  # extend conversation with assistant's reply
        # Step 4: send the info for each function call and function response to the model
        for tool_call in tool_calls:
            function_name = tool_call.function.name
            function_to_call = available_functions[function_name]
            function_args = json.loads(tool_call.function.arguments)
            function_response = function_to_call(
                location=function_args.get("location"),
                unit=function_args.get("unit"),
            )
            messages.append(
                {
                    "tool_call_id": tool_call.id,
                    "role": "tool",
                    "name": function_name,
                    "content": function_response,
                }
            )  # extend conversation with function response
        second_response = client.chat.completions.create(
            model="<REPLACE_WITH_YOUR_1106_MODEL_DEPLOYMENT_NAME>",
            messages=messages,
        )  # get a new response from the model where it can see the function response
        return second_response
print(run_conversation())

在聊天完成 API(已弃用)中使用函数

函数调用在 2023-07-01-preview API 版本中可用,适用于 gpt-35-turbo、gpt-35-turbo-16k、gpt-4 和 gpt-4-32k 版本 0613。

若要将函数调用与聊天完成 API 配合使用,需要在请求中包含两个新属性:functionsfunction_call。 可以在请求中包含一个或多个 functions,并且可以在“定义函数”部分中详细了解如何定义函数。 请记住,函数会注入到后台的系统消息中,因此函数计入令牌使用情况。

提供函数时,默认情况下,function_call 将设置为 "auto",并模型将决定是否应调用函数。 或者,可以将 function_call 参数设置为 {"name": "<insert-function-name>"},以强制 API 调用特定函数,也可以将参数设置为 "none",以防止模型调用任何函数。

注意

OpenAI Python 库版本 0.28.1 已弃用。 我们建议使用 1.x。 有关如何从 0.28.1 迁移到 1.x 的信息,请参阅我们的迁移指南


import os
import openai

openai.api_key = os.getenv("AZURE_OPENAI_API_KEY")
openai.api_version = "2023-07-01-preview"
openai.api_type = "azure"
openai.api_base = os.getenv("AZURE_OPENAI_ENDPOINT")

messages= [
    {"role": "user", "content": "Find beachfront hotels in San Diego for less than $300 a month with free breakfast."}
]

functions= [  
    {
        "name": "search_hotels",
        "description": "Retrieves hotels from the search index based on the parameters provided",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The location of the hotel (i.e. Seattle, WA)"
                },
                "max_price": {
                    "type": "number",
                    "description": "The maximum price for the hotel"
                },
                "features": {
                    "type": "string",
                    "description": "A comma separated list of features (i.e. beachfront, free wifi, etc.)"
                }
            },
            "required": ["location"]
        }
    }
]  

response = openai.ChatCompletion.create(
    engine="gpt-35-turbo-0613", # engine = "deployment_name"
    messages=messages,
    functions=functions,
    function_call="auto", 
)

print(response['choices'][0]['message'])
{
  "role": "assistant",
  "function_call": {
    "name": "search_hotels",
    "arguments": "{\n  \"location\": \"San Diego\",\n  \"max_price\": 300,\n  \"features\": \"beachfront,free breakfast\"\n}"
  }
}

如果模型确定应调用函数,则来自 API 的响应包含 function_call 属性。 属性 function_call 包括要调用的函数的名称和要传递给函数的参数。 参数是可以分析和用于调用函数的 JSON 字符串。

在某些情况下,模型会同时生成 contentfunction_call。 例如,对于上面的提示,内容可以呈现“当然,我可以帮你在圣地亚哥找到一些符合你标准的酒店”之类的内容以及 function_call。

使用函数调用

以下部分详细介绍了如何通过聊天完成 API 有效地使用函数。

定义函数

函数有三个主参数:namedescriptionparameters。 模型使用 description 参数来确定何时以及如何调用函数,因此请务必对函数执行的操作提供有意义的说明。

parameters 是描述函数接受的参数的 JSON 架构对象。 可以在 JSON 架构参考中详细了解 JSON 架构对象。

如果要描述不接受任何参数的函数,请使用 {"type": "object", "properties": {}} 作为 parameters 属性的值。

使用函数管理流

Python 中的示例。


response = openai.ChatCompletion.create(
    deployment_id="gpt-35-turbo-0613",
    messages=messages,
    functions=functions,
    function_call="auto", 
)
response_message = response["choices"][0]["message"]

# Check if the model wants to call a function
if response_message.get("function_call"):

    # Call the function. The JSON response may not always be valid so make sure to handle errors
    function_name = response_message["function_call"]["name"]

    available_functions = {
            "search_hotels": search_hotels,
    }
    function_to_call = available_functions[function_name] 

    function_args = json.loads(response_message["function_call"]["arguments"])
    function_response = function_to_call(**function_args)

    # Add the assistant response and function response to the messages
    messages.append( # adding assistant response to messages
        {
            "role": response_message["role"],
            "function_call": {
                "name": function_name,
                "arguments": response_message["function_call"]["arguments"],
            },
            "content": None
        }
    )
    messages.append( # adding function response to messages
        {
            "role": "function",
            "name": function_name,
            "content": function_response,
        }
    ) 

    # Call the API again to get the final response from the model
    second_response = openai.ChatCompletion.create(
            messages=messages,
            deployment_id="gpt-35-turbo-0613"
            # optionally, you could provide functions in the second call as well
        )
    print(second_response["choices"][0]["message"])
else:
    print(response["choices"][0]["message"])

PowerShell 中的示例。

# continues from the previous PowerShell example

$response = Invoke-RestMethod -Uri $url -Headers $headers -Body $body -Method Post -ContentType 'application/json'
$response.choices[0].message | ConvertTo-Json

# Check if the model wants to call a function
if ($null -ne $response.choices[0].message.function_call) {

    $functionName  = $response.choices[0].message.function_call.name
    $functionArgs = $response.choices[0].message.function_call.arguments

    # Add the assistant response and function response to the messages
    $messages += @{
        role          = $response.choices[0].message.role
        function_call = @{
            name      = $functionName
            arguments = $functionArgs
        }
        content       = 'None'
    }
    $messages += @{
        role          = 'function'
        name          = $response.choices[0].message.function_call.name
        content       = "$functionName($functionArgs)"
    }

    # Call the API again to get the final response from the model
    
    # these API arguments are introduced in model version 0613
    $body = [ordered]@{
        messages      = $messages
        functions         = $functions
        function_call   = 'auto'
    } | ConvertTo-Json -depth 6

    $url = "$($openai.api_base)/openai/deployments/$($openai.name)/chat/completions?api-version=$($openai.api_version)"

    $secondResponse = Invoke-RestMethod -Uri $url -Headers $headers -Body $body -Method Post -ContentType 'application/json'
    $secondResponse.choices[0].message | ConvertTo-Json
}

示例输出。

{
  "role": "assistant",
  "content": "I'm sorry, but I couldn't find any beachfront hotels in San Diego for less than $300 a month with free breakfast."
}

在这些示例中,我们不进行任何验证或错误处理,因此你需要确保将它添加到代码中。

有关使用函数的完整示例,请参阅有关函数调用的示例笔记本。 还可以应用更复杂的逻辑将多个函数调用链接在一起,此示例中也介绍了这一点。

使用函数进行提示工程

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

提高质量和可靠性

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

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

请务必提供有意义的 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 做法概述

后续步骤