Azure OpenAI Service (プレビュー) で関数呼び出しを使用する方法

gpt-35-turbo と gpt-4 の最新バージョンは、関数と協働するように微調整されており、関数を呼び出す必要があるタイミングと方法の両方を判断できます。 要求に 1 つ以上の関数が含まれている場合、モデルはプロンプトのコンテキストに基づいて、いずれかの関数を呼び出す必要があるかどうかを判断します。 モデルは、関数を呼び出す必要があると判断すると、その関数の引数を含む JSON オブジェクトで応答します。

モデルは、すべて指定された関数に基づいて、API 呼び出しを作成し、データ出力を構造化します。 モデルはこれらの呼び出しを生成できますが、それらを実行するのは自分であり、制御を行うのは依然として自分であることに注意することが重要です。

大まかに言うと、関数の操作は次の 3 つのステップに分けることができます。

  1. 関数とユーザーの入力を使用して Chat Completions API を呼び出す
  2. モデルの応答を使用して API または関数を呼び出す
  3. Chat Completions API をもう一度呼び出し、関数からの応答を含め、最終的な応答を取得する

重要

API の 2023-12-01-preview バージョンのリリースに伴い、functions および function_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)

単一ツールや関数の呼び出しの例

最初に、定義されている 1 つのツールや関数を使って、ハードコーディングされている 3 つの場所の時刻を調べることができる、簡単な小さい関数呼び出しを見ていきます。 コードの実行を簡単に辿ることができるように、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.

並列関数呼び出しをサポートするモデル デプロイを使っている場合は、メッセージの配列を変更し、1 つではなく複数の場所で時刻を要求するようにして、これを並列関数呼び出しの例に変換できます。

これを行うには、次の 2 つの行のコメントを入れ替えます。

    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 つの関数呼び出しで、チャット完了メッセージが生成されました。 これらの関数呼び出しに応答する場合は、3 つの新しいメッセージを会話に追加し、それぞれに 1 つの関数呼び出しの結果を含めて、tool_call_idtools_calls からの id を参照します。

モデルで特定の関数を強制的に呼び出すには、tool_choice パラメータに特定の関数名を設定します。 また、tool_choice: "none" を設定して、モデルにユーザー向けメッセージを生成させることもできます。

Note

既定の動作 (tool_choice: "auto") は、モデルが関数を呼び出すかどうかと、呼び出す場合はどの関数を呼び出すかを独自に決定することです。

複数の関数を含む並列関数呼び出し

もう 1 つの簡単な関数呼び出しの例を見ていきます。今度は、2 つの異なるツールや関数が定義されています。

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

エラーの削減

プロンプト エンジニアリングが重要になり得るもう 1 つの領域は、関数呼び出し時のエラー削減です。 モデルは、自分が定義するスキーマに一致する関数呼び出しを生成するようにトレーニングされているにも関わらず、自分が定義したスキーマと一致しない関数呼び出しを生成したり、自分が含めていない関数を呼び出そうとしたりします。

モデルが指定されていない関数呼び出しを生成していることに気付いた場合は、"Only use the functions you have been provided with." という文をシステム メッセージに含めることを試してください。

責任を持った関数呼び出しの使用

他の AI システムと同様に、言語モデルを他のツールやシステムと統合するために関数呼び出しを使用することは、潜在的なリスクを生み出します。 関数呼び出しが生み出す可能性があるリスクを理解し、自分がその機能を責任を持って使用するようにするための対策を講じることが重要です。

関数を安心かつセキュリティ的に安全に使用するのに役立つヒントをいくつか次に示します。

  • 関数呼び出しの検証: モデルによって生成された関数呼び出しを常に検証します。 これには、パラメーター、呼び出される関数のチェック、および呼び出しが目的のアクションに合うものであることの確認が含まれます。
  • 信頼できるデータとツールの使用: 信頼できる検証済みのソースからのデータのみを使用します。 関数の出力内の信頼できないデータは、自分が意図したのと違う方法で関数呼び出しを記述するようモデルに指示するために利用される可能性があります。
  • 最小特権の原則の順守: 関数が自身のジョブを実行するために必要な最小限のアクセス権のみを付与します。 これは関数が誤用または悪用された場合に起こり得る影響を軽減します。 たとえば、データベースにクエリを実行するために関数呼び出しを使用する場合は、アプリケーションにはデータベースへの読み取り専用アクセス権のみを付与するべきです。 また、セキュリティ制御として関数定義の除外機能だけに依存しないようにする必要もあります。
  • 実際の影響の考慮: 実行する予定の関数呼び出し、特にコードの実行、データベースの更新、通知の送信などのアクションをトリガーする関数呼び出しの実際の影響を意識するようにします。
  • ユーザーによる確認ステップの実装: 特にアクションを実行する関数の場合は、実行前にユーザーがアクションを確認するステップを含めることをお勧めします。

Azure OpenAI モデルを責任を持って使用する方法に関する推奨事項の詳細については、「Azure OpenAI モデルのための責任ある AI プラクティスの概要」を参照してください。

次のステップ