0Pricing
AI Engineering Academy · レッスン

アプリケーションでツール呼び出しを処理する

APIレスポンスのfinish_reasonがtool_callsであることを検出し、関数名と引数を取り出して対応するPython関数を実行し、その結果をモデルに返します。

「アプリケーションでツール呼び出しを処理する」はCoddyKit上の無料AI Engineering Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Engineering Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Engineering Academyコースには全4レッスンが含まれています。

ツール呼び出しのレスポンスオブジェクト

モデルが関数の呼び出しを決定すると、APIレスポンスのメッセージオブジェクトにtool_callsリストが含まれます。各ツール呼び出しには、一意のid、呼び出す関数のfunction.name、そしてモデルが渡そうとしている引数を含むJSON文字列function.argumentsがあります。これを解析して関数を実行するのは、アプリケーションコードの役割です。

from openai import OpenAI
import json

client = OpenAI()

response = client.chat.completions.create(
    model='gpt-4o',
    messages=[{'role': 'user', 'content': 'What is the weather in Paris?'}],
    tools=tools  # defined previously
)

message = response.choices[0].message

if response.choices[0].finish_reason == 'tool_calls':
    for tool_call in message.tool_calls:
        print('Call ID:', tool_call.id)
        print('Function name:', tool_call.function.name)
        print('Arguments (JSON string):', tool_call.function.arguments)

関数引数を解析する

function.argumentsフィールドはJSONエンコードされた文字列であり、Pythonのdictではありません。json.loads()で解析する必要があります。スキーマによる指示があっても、モデルが不正なJSONを生成することは時々あるため、必ずtry/exceptで囲み、適切に処理してください。

import json

def parse_tool_call(tool_call) -> dict:
    '''Parse a tool call's arguments from JSON string to dict.'''
    try:
        args = json.loads(tool_call.function.arguments)
        return args
    except json.JSONDecodeError as e:
        print(f'Failed to parse arguments for {tool_call.function.name}: {e}')
        print(f'Raw arguments: {tool_call.function.arguments}')
        return {}

# Usage
tool_call = message.tool_calls[0]
args = parse_tool_call(tool_call)
print('Parsed args:', args)  # {'location': 'Paris', 'unit': 'celsius'}

正しい関数にディスパッチする

function.nameを使用して、正しいPython関数にディスパッチしてください。すっきりした方法は、関数名と呼び出し可能オブジェクトを対応付ける辞書に関数をまとめることです。これにより、壊れやすいif/elifの連鎖を避けられ、後から新しいツールを簡単に追加できます。

def get_current_weather(location: str, unit: str = 'celsius') -> str:
    # Real implementation calls a weather API
    return f'{location}: 18{chr(176)}C, partly cloudy'

def create_calendar_event(title: str, start_time: str, duration_minutes: int, **kwargs) -> str:
    return f'Event created: {title} at {start_time} for {duration_minutes} minutes'

# Tool registry: maps function names to callables
TOOL_REGISTRY = {
    'get_current_weather': get_current_weather,
    'create_calendar_event': create_calendar_event
}

def execute_tool_call(tool_call) -> str:
    name = tool_call.function.name
    args = parse_tool_call(tool_call)

    if name not in TOOL_REGISTRY:
        return f'Unknown function: {name}'

    try:
        result = TOOL_REGISTRY[name](**args)
        return str(result)
    except Exception as e:
        return f'Function {name} raised an error: {str(e)}'

結果をモデルに返す

関数を実行したら、後続のAPI呼び出しで結果をモデルに返す必要があります。まず、tool_callsを含むassistantのメッセージを会話に追加し、次にrole='tool'、tool_call_id、関数の結果をcontentとして含む新しいメッセージを追加します。その後、もう一度APIを呼び出してください。

def run_tool_call_loop(messages: list, tools: list) -> str:
    response = client.chat.completions.create(
        model='gpt-4o',
        messages=messages,
        tools=tools
    )
    message = response.choices[0].message
    messages.append(message)  # Add assistant's tool_calls message

    # Execute all tool calls and collect results
    for tool_call in (message.tool_calls or []):
        result = execute_tool_call(tool_call)
        # Add each tool result as a 'tool' role message
        messages.append({
            'role': 'tool',
            'tool_call_id': tool_call.id,
            'content': result
        })

    # Second API call with results appended
    final_response = client.chat.completions.create(
        model='gpt-4o',
        messages=messages,
        tools=tools
    )
    return final_response.choices[0].message.content

完全な会話ターン

完全なツール呼び出しのやり取りでは、会話履歴に4つのメッセージが含まれます。ユーザーのメッセージ、ツール呼び出しを要求するassistantのメッセージ、ツールの結果メッセージ、そして結果を反映したassistantの最終レスポンスです。この構造を理解することは、複数ターンにわたってツールを使用するアシスタントを構築するうえで不可欠です。

# The full message history for a tool-calling conversation:
conversation = [
    {'role': 'user', 'content': 'What is the weather in Tokyo?'},

    # Model requests a tool call (added by run_tool_call_loop)
    # {'role': 'assistant', 'content': None, 'tool_calls': [...]},

    # Application sends tool result back
    # {'role': 'tool', 'tool_call_id': 'call_abc123', 'content': 'Tokyo: 22C, sunny'},

    # Model produces final human-readable response
    # {'role': 'assistant', 'content': 'The weather in Tokyo is 22 degrees Celsius and sunny.'}
]

final_answer = run_tool_call_loop(
    [{'role': 'user', 'content': 'What is the weather in Tokyo?'}],
    tools
)
print(final_answer)

ツールが呼び出されない場合の処理

モデルがツールを呼び出さずに直接回答することがあります。この場合、finish_reasonは'tool_calls'ではなく'stop'になります。ツール呼び出しを処理する前に、必ずこのケースを確認してください。堅牢な実装では、どちらの分岐も適切に処理します。

def smart_complete(user_message: str) -> str:
    messages = [{'role': 'user', 'content': user_message}]
    response = client.chat.completions.create(
        model='gpt-4o',
        messages=messages,
        tools=tools
    )
    choice = response.choices[0]

    if choice.finish_reason == 'stop':
        # Model answered directly without calling a tool
        return choice.message.content

    elif choice.finish_reason == 'tool_calls':
        # Process tool calls
        messages.append(choice.message)
        for tc in choice.message.tool_calls:
            result = execute_tool_call(tc)
            messages.append({'role': 'tool', 'tool_call_id': tc.id, 'content': result})
        # Get final answer
        final = client.chat.completions.create(model='gpt-4o', messages=messages)
        return final.choices[0].message.content

    return 'Unexpected finish reason: ' + choice.finish_reason

実行前に引数を検証する

モデルが、ビジネスロジックの検証に失敗する引数を渡すことがあります。たとえば、負の期間、不正なメールアドレス、過去の日付などです。実際の関数を呼び出す前に引数を検証し、検証に失敗した場合は具体的なエラー文字列を返してください。そうすれば、モデルは次のターンで引数を修正できます。

from pydantic import BaseModel, ValidationError
from datetime import datetime

class CreateEventArgs(BaseModel):
    title: str
    start_time: str  # ISO 8601
    duration_minutes: int

def safe_create_event(tool_call) -> str:
    try:
        raw_args = json.loads(tool_call.function.arguments)
        validated = CreateEventArgs(**raw_args)
        # Additional business rule
        event_time = datetime.fromisoformat(validated.start_time)
        if event_time < datetime.now():
            return 'Error: start_time must be in the future.'
        return create_calendar_event(**validated.dict())
    except ValidationError as e:
        return f'Invalid arguments: {e}'

ツール呼び出しのやり取りをログに記録する

デバッグと分析のために、ツール呼び出しのやり取りは必ずログに記録してください。関数名、引数、結果、実行時間を記録します。このデータから、どのツールが最も頻繁に呼び出されているか、どのツールが失敗しているか、モデルがどのような引数パターンを生成しているかを把握できます。これはスキーマや関数の実装を改善するうえで非常に役立ちます。

import time
import logging

logger = logging.getLogger('tool_calls')

def logged_execute(tool_call) -> str:
    name = tool_call.function.name
    args_str = tool_call.function.arguments
    start = time.time()
    result = execute_tool_call(tool_call)
    elapsed = time.time() - start

    logger.info(
        'Tool call executed',
        extra={
            'function': name,
            'arguments': args_str,
            'result_length': len(result),
            'elapsed_ms': round(elapsed * 1000)
        }
    )
    return result

ツール呼び出しに関するセキュリティ上の考慮事項

モデルの出力に基づいて、検証なしに任意の関数を実行してはいけません。TOOL_REGISTRYには正確な関数名を必ず許可リストとして登録し、すべての引数を検証し、アクションを実行する前に認可を確認してください。モデルは信頼できない呼び出し元です。ディスパッチのロジックが寛容すぎると、悪意のあるプロンプトによって破壊的な関数が呼び出されるおそれがあります。

  • TOOL_REGISTRYに明示的に登録された関数だけを許可する
  • 実行前にPydanticで入力を検証する
  • 書き込みや削除の操作には認可を必須にする

豊富な構造化結果を返す

ツールの結果は単なる文字列である必要はありません。JSON形式のデータ、テーブル、要約などを返すことができます。構造化データをJSONとして返すと、モデルは最終回答で特定のフィールドを解析して参照できます。大きな結果の場合は、すべての生データをコンテキストに詰め込むのではなく、重要な事実を含む要約を返してください。

def get_order_status(order_id: str) -> str:
    # Fetch from real database
    order = {'id': order_id, 'status': 'shipped', 'estimated_delivery': '2024-03-15', 'carrier': 'FedEx', 'tracking': 'FX123456'}
    # Return concise summary, not raw DB record
    return (
        f'Order {order_id}: Status={order["status"]}, '
        f'Estimated delivery: {order["estimated_delivery"]}, '
        f'Carrier: {order["carrier"]}, Tracking: {order["tracking"]}'
    )

複数ターンにわたるツール使用の会話

強力なパターンの一つに、モデルが複数のユーザーメッセージにまたがってツールを呼び出し、コンテキストを積み上げていく複数ターンの会話があります。過去の回答を参照して、不要なツールの再呼び出しを避けられるよう、過去のツール呼び出しと結果を含む完全な会話履歴を常に維持してください。

クイックチェック

アプリケーションでのツール呼び出しの処理について、理解度を確認しましょう。

レッスンのまとめ

このレッスンでは、ツール呼び出しの引数はJSON文字列として渡され、json.loads()で解析する必要があること、TOOL_REGISTRY辞書によって関数名を呼び出し可能オブジェクトに対応付け、すっきりとディスパッチできること、そして結果は対応するtool_call_idを持つrole='tool'メッセージとしてモデルに返すことを学びました。次は、モデルが複数の関数を同時に呼び出す場合を、並列関数呼び出しによって処理する方法を学びます。

よくある質問

「アプリケーションでツール呼び出しを処理する」レッスンは無料ですか?

はい。「アプリケーションでツール呼び出しを処理する」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Engineering Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Engineering Academyコースには全4レッスンが含まれています。

「アプリケーションでツール呼び出しを処理する」で何を学びますか?

APIレスポンスのfinish_reasonがtool_callsであることを検出し、関数名と引数を取り出して対応するPython関数を実行し、その結果をモデルに返します。 ブラウザで直接実行するハンズオンコードでAI Engineering Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

AI Engineering Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのAI Engineering Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「アプリケーションでツール呼び出しを処理する」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このAI Engineering Academyレッスンでコードを書いて実行できますか?

はい。すべてのAI Engineering Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. API用の関数スキーマを定義する
  2. アプリケーションでツール呼び出しを処理する
  3. 関数の並列呼び出し
  4. 自然言語データベースインターフェースを構築する
← AI Engineering Academyに戻る