技術資料

PHPで実装する Response API と Function Calling を活用したチャットボットの開発ガイド

作成日:2025.03.24

PHPでOpenAIのResponse APIとFunction Calling機能を活用し、チャットボットにタスク管理機能を実装する方法を解説します。モデルとの再帰的な対話処理や、独自関数によるタスク登録・照会など、具体的なコード例を通して段階的に解説します。

OpenAI の Response API で Function calling の機能を使って、チャットボットに簡易的なタスク管理機能を実装してみようと思います。

前回までの Response API の記事:

Function calling とは

Function calling は、APIな組み込まれた機能である Web search や File search とは違い、アプリケーション開発者が作成したオリジナルの関数を呼び出すための仕組みです。この機能により、モデルが外部のデータやサービスと連携し、特定のコンテキストに最適化された出力を行なうことが可能になったり、何らかの処理の実行が可能になります。

Funcion calling には、大きく分けて以下の二つの利用法があります。

データの取得
モデルがユーザーからの入力に回答するために必要な情報を取得します
例: 今日の天気を取得する、今日のタスクリストを取得する など
処理の実行
モデルがユーザーの指示に応じて処理を実行します
例: メールの送信、データベースの更新 など

Function calling の処理の流れ

  1. 関数をJSON形式でモデルに伝えます。関数の定義には、関数名や説明、必要な引数の情報などが含まれます
  2. 関数の定義をモデルに伝えると共に、ユーザーからの入力をモデルに伝えます
  3. モデルはユーザーの入力に基づいて、必要な関数を呼び出すべきかどうかを決定します。関数を呼び出す場合は、必要な引数を含むJSON形式の出力を生成します
  4. アプリケーションが、モデルが生成したJSON出力を解析し、指定された関数を実行します
  5. アプリケーションが、関数の実行結果を再度モデルに返却します
  6. モデルは、受け取った関数の実行結果をもとに、最終的なレスポンスを生成します

公式ドキュメントの以下の図がわかりやすいです。

Function calling - OpenAI API より画像引用

Function calling の実装

「タスク一覧取得」「新規タスク登録」の二つの関数が実行できるチャットボットを、前回までの記事で作成したコードをベースに実装してみます。

とは言え、前回までの記事で利用した chat.php は大幅に内容を変更する必要があります。前述のとおり、 Function calling では、モデルが出力した関数呼び出しレスポンスを元に、関数を実行して結果をモデルに返し、それを元に生成された最終的なレスポンスを受け取る、という再帰的な処理が必要になるためです。

ということで、段階を追って chat.php のコードを改めて書いていきます。

<?php
session_start();
// データベース接続情報
$db_host = 'localhost';
$db_user = 'root';
$db_pass = 'password';
$db_name = 'chat_history';
// OpenAI APIキー
define('OPENAI_API_KEY', 'sk-proj-1234567890');

// メッセージ未入力の場合、view.php にリダイレクト
if (empty($_POST['message'])) {
    header('Location: view.php');
    exit;
}

// データベース接続 
$conn = new mysqli($db_host, $db_user, $db_pass, $db_name);
if ($conn->connect_error) {
    die('データベース接続に失敗しました: ' . $conn->connect_error);
}
$conn->set_charset("utf8mb4");

// Response API へのリクエストを準備
$response = prepareResponse('user',$_POST['message'],$conn);

// データベース接続を閉じる
$conn->close(); 

// view.php にリダイレクト
header('Location: view.php');
exit;

Response API へのリクエストを準備する部分を prepareResponse という関数として分けました。この部分の処理を、再帰的に呼び出すことになります。

続いて、 function prepareResponse の中身。

// Response API へのリクエストを準備
function prepareResponse($role, $message, $conn){

    // メッセージをデータベースに保存
    if($role == 'user'){
        $stmt = $conn->prepare("INSERT INTO chat_history (role, message) VALUES (?, ?)");
        $stmt->bind_param("ss", $role, $message);
        if (!$stmt->execute()) {
            die('クエリの実行に失敗しました: ' . $stmt->error);
        }
        $stmt->close();
    }

    // Response API 実行
    $response = createResponse($message);

    // セッションに previous_response_id を保存
    if (isset($response->id)) {
        $_SESSION['previous_response_id'] = $response->id;
    }

    // レスポンスをデータベースに保存
    if(isset($response->output)){
        foreach($response->output as $output){
            if(isset($output->type) && $output->type == 'message'){
                $content = $output->content[0]->text;
                $stmt = $conn->prepare("INSERT INTO chat_history (role, message, raw_data) VALUES ('assistant', ?, ?)");
                $stmt->bind_param("ss", $content, json_encode($response));
                if (!$stmt->execute()) {
                    die('クエリの実行に失敗しました: ' . $stmt->error);
                }
                $stmt->close();
                break;
        }
    }else{
        var_dump($response);
        die('APIレスポンスの形式が不正です');
    }

    return $response;

}

別の関数に分けましたが、一旦前回までのコードとほぼ同じ。

続いて、 Response API を実行する処理 function createResponse の中身。

// Response API 実行
function createResponse($message) {
    // リクエストデータの作成
    $data = array(
        'input' => $message,
        'model' => 'gpt-4o-mini',
        'temperature' => 0.8,
        'instructions' => 'あなたはユーザーのタスク管理を行なうAIアシスタントです。ユーザーの要望に応じて、タスクの追加と確認を行ないます。',
        'tool_choice' => 'auto',
    );
    // previous_response_id がセッションに存在する場合、リクエストデータに追加
    if(isset($_SESSION['previous_response_id'])) {
        $data['previous_response_id'] = $_SESSION['previous_response_id'];
    }
    // リクエストデータに Function を追加
    $functions = array();
    $functions[] = makeFunction_add_task();
    $functions[] = makeFunction_list_tasks();
    $data['tools'] = $functions;

    // リクエストデータをJSON形式に変換
    $json_data = json_encode($data);
    
    // curl でリクエスト送信
    $url = 'https://api.openai.com/v1/responses';
    $headers = array(
        'Content-Type: application/json',
        "Authorization: Bearer ".OPENAI_API_KEY
    );
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $json_data);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 300);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 300);
    $api_response = curl_exec($ch);
    if($api_response === false){
        echo 'Curl error: ' . curl_error($ch);
        exit;
    }
    curl_close($ch);
    $apiresult = json_decode($api_response);
    if (json_last_error() !== JSON_ERROR_NONE) {
        die('APIレスポンスの解析に失敗しました: ' . json_last_error_msg());
    }
    return $apiresult;
}

function makeFunction_add_task(){

    $function = array(
        'type' => 'function',
        'name' => 'add_task',
        'description' => 'ユーザーのタスクをデータベースに登録します',
        'parameters' => array(
            'type' => 'object',
            'properties' => array(
                'task_name' => array(
                    'type' => 'string',
                    'description' => 'タスクの名前',
                ),
                'due_date' => array(
                    'type' => 'string',
                    'description' => 'タスクの締め切り日 (YYYY-MM-DD)',
                ),
            ),
            'required' => array('task_name','due_date'),
        ),
    );
    return $function;

}

function makeFunction_list_tasks(){

    $function = array(
        'type' => 'function',
        'name' => 'list_tasks',
        'description' => 'ユーザーのタスクをデータベースから取得します',
        'parameters' => array(
            'type' => 'object',
            'properties' => array(
                'due_date' => array(
                    'type' => 'string',
                    'description' => 'タスクの締め切り日 (YYYY-MM-DD)',
                ),
            ),
            'required' => array('due_date'),
        ),
    );
    return $function;

}

前回までのコードからの大きな変更点としては、

instructions
カスタムインストラクションの内容を、「あなたはユーザーのタスク管理を行なうAIアシスタントです。ユーザーの要望に応じて、タスクの追加と確認を行ないます。」と変更しました。
tool_choice
tool_choice を 'auto' と指定しました。
これにより、モデルが自身の判断で、使用するツールを(そもそもツールを使用するか否かも含めて)決定します。
tools
tools に 独自の関数を指定しています
makeFunction_add_taskmakeFunction_list_tasks で、関数の定義を配列として作成しています。
関数の定義には、
type
function で固定
name
関数の名前
description
関数の説明
parameters
引数
を指定します。
makeFunction_add_task では、add_task 関数を定義し、引数として task_namedue_date を指定しています。また、 requiredtask_name, due_date を必須の引数として定義しています。
makeFunction_list_tasks では、list_tasks 関数を定義し、引数として due_date を指定しています。また、 requireddue_date を必須の引数として定義しています。

これで、チャットボットが自身の判断で、関数の実行を行なうようになりました。

しかし、モデルから関数実行のレスポンスが返ってきた時に、アプリケーション側で行なう処理が未実装です。ということで、改めて function prepareResponse を修正します。

// Response API へのリクエストを準備
function prepareResponse($role, $message, $conn){

    // メッセージをデータベースに保存
    if($role == 'user'){
        $stmt = $conn->prepare("INSERT INTO chat_history (role, message) VALUES (?, ?)");
        $stmt->bind_param("ss", $role, $message);
        if (!$stmt->execute()) {
            die('クエリの実行に失敗しました: ' . $stmt->error);
        }
        $stmt->close();
    }

    // Response API 実行
    $response = createResponse($message);

    // セッションに previous_response_id を保存
    if (isset($response->id)) {
        $_SESSION['previous_response_id'] = $response->id;
    }

    // レスポンスをデータベースに保存
    $function_results = array();
    if(isset($response->output)){
        foreach($response->output as $output){
            if(isset($output->type) && $output->type == 'message'){
                $content = $output->content[0]->text;
                $stmt = $conn->prepare("INSERT INTO chat_history (role, message, raw_data) VALUES ('assistant', ?, ?)");
                $stmt->bind_param("ss", $content, json_encode($response));
                if (!$stmt->execute()) {
                    die('クエリの実行に失敗しました: ' . $stmt->error);
                }
                $stmt->close();
                break;
            }elseif(isset($output->type) && $output->type == 'function_call'){
                if($output->name == 'add_task'){
                    $function_results[] = executeFunction_add_task($output);
                }elseif($output->name == 'list_tasks'){
                    $function_results[] = executeFunction_list_tasks($output);
                }
            }
        }
    }else{
        var_dump($response);
        die('APIレスポンスの形式が不正です');
    }

    if(!empty($function_results)){
        $message = array();
        foreach($function_results as $result){
            $message[] = array(
                'type' => 'function_call_output',
                'call_id' => $result['call_id'],
                'output' => $result['result'],
            );
        }
        $stmt = $conn->prepare("INSERT INTO chat_history (role, message, raw_data) VALUES ('assistant', 'tool_call', ?)");
        $stmt->bind_param("s", json_encode($response));
        if (!$stmt->execute()) {
            die('クエリの実行に失敗しました: ' . $stmt->error);
        }
        $stmt->close();
        $response = prepareResponse('tool',$message,$conn);
    }

    return $response;

}

$response->output[n]->typefunction_call が入っている場合、モデルからのレスポンスは関数呼び出しとなります。

関数が呼ばれていた場合、 $response->output[n]->name に呼び出された関数の名前が入っているので、それを元に関数を実行。関数の実行結果を配列に格納して、再帰的に function prepareResponse を実行しています。

関数の実行結果をモデルに返す際には、以下の形式でデータを渡します。

type
function_call_output で固定
call_id
個別の関数呼び出しに割当たられたID。モデルからの関数呼び出し時に $response->output[n]->call_id に入っています
output
関数の実行結果。JSON形式で渡します

呼び出された関数を実行する処理 executeFunction_add_task, executeFunction_list_tasks は以下の通り。

function executeFunction_add_task($output){
    // Do something
    $result = "{'success':'true'}";
    return ['call_id' => $output->call_id, 'result' => $result];
}

function executeFunction_list_tasks($output){
    // Do something
    $task = array(
        ['task_name' => '案件Aのデータベース設計'],
        ['task_name' => '案件Bのプログラミング'],
    );
    return ['call_id' => $output->call_id, 'result' => json_encode($task)];
}

実行結果を JSON 形式で返す関数です。今回は動作確認ということで、固定の結果を返すだけの関数になっています。

本来は、引数のバリデーションや、データベースへの insert 処理(add_task の場合)を行なったり、due_date をキーにデータベースから該当タスクを検索したり(list_tasks の場合)といった処理を行なって、結果を返します。

ひとまず、これで Function calling に対応したチャットボットができました。

動作確認

動作確認のため、チャットボットに以下のような質問をしてみます。

2025年3月22日締め切りのタスクは?

返答は以下の通り。

2025年3月22日締め切りのタスクは以下の2つです:

1. 案件Aのデータベース設計
2. 案件Bのプログラミング

他に何かお手伝いできることがありますか?

上記のコードの executeFunction_list_tasks で設定した通りの答えが返ってきました。

アプリケーションとモデルとのやり取りのログを確認してみると、最初の質問に対して、モデルからのレスポンスは以下のような感じ。

"output": [
    {
        "type": "function_call",
        "id": "fc_0123456789",
        "call_id": "call_0123456789",
        "name": "list_tasks",
        "arguments": "{\"due_date\":\"2025-03-22\"}",
        "status": "completed"
    }
],

ちゃんとlist_tasks関数が呼び出され、引数としてdue_date: 2025-03-22 が指定されていました。

続いて、タスク追加処理もお願いしてみます。

3/30締め切りのタスクとして、以下の二件を追加お願い。
- 案件Aのフロント設計
- 案件Bのテスト

返答は以下の通り。

2025年3月30日締め切りのタスクとして以下の2件を追加しました:

1. 案件Aのフロント設計
2. 案件Bのテスト

他に何かお手伝いできることがありますか?

ログを確認すると、以下のように関数が呼び出されていました。

"output": [
    {
        "type": "function_call",
        "id": "fc_0123456789",
        "call_id": "call_0123456789",
        "name": "add_task",
        "arguments": "{\"task_name\":\"案件Aのフロント設計\",\"due_date\":\"2025-03-30\"}",
        "status": "completed"
    },
    {
        "type": "function_call",
        "id": "fc_9876543210",
        "call_id": "call_9876543210",
        "name": "add_task",
        "arguments": "{\"task_name\":\"案件Bのテスト\",\"due_date\":\"2025-03-30\"}",
        "status": "completed"
    }
],

add_task関数が二回呼び出され、指定したタスクをそれぞれ登録しています。

このように、モデルが自律的に判断し、最適な関数を実行してくれるのが Function calling です。関数の結果を元に別の関数を実行する、というようなこともやってくれる(例:在庫一覧から在庫切れ商品を検索して、結果を元に在庫の切れた商品の発注処理を行なう、とか)ので、できることが非常に広がる機能だと思います。

まとめ

Function calling の概要
アプリケーション開発者が作成したオリジナルの関数を呼び出すための仕組み
主な処理の流れ
  • 関数定義(JSON形式)をモデルに伝達し、ユーザー入力を元に処理を判断
  • モデルが関数呼び出しレスポンスを返し、アプリケーションが指定された関数を実行
  • 実行結果をモデルに返し、再帰的に次のレスポンス生成へ反映
実装のポイント
  • カスタム関数の定義:makeFunction_add_taskmakeFunction_list_tasks により独自関数を設定
  • モデルが関数呼び出しをレスポンスとして返した場合、関数実行結果を含めたメッセージを送って再帰呼び出しによるレスポンス更新を行なう
  • モデルからの関数呼び出しに対する適切なエラーチェックおよびJSON形式の検証の実装が必要
動作確認の結果
テスト用の問い合わせとタスク追加の指示に対して、モデルから正確な関数呼び出しレスポンスが返却され、意図した通りにタスクが登録・一覧表示されたことを確認しました。

この記事を書いた人

※上が私です。

奈良市を拠点に、25年以上の経験を持つフリーランスWebエンジニア、阿部辰也です。

これまで、ECサイトのバックエンド開発や業務効率化システム、公共施設の予約システムなど、多彩なプロジェクトを手がけ、企業様や制作会社様のパートナーとして信頼を築いてまいりました。

【制作会社・企業様向けサポート】
  • 専任エンジニアのいない企業様に対するシステム面の不安を解消
  • 柔軟な契約形態や短納期での対応により、急なニーズにも迅速にサポート
  • システムの企画段階から運用まで、ワンストップでのサービスを提供

Webシステムの開発やサイト改善でお困りの際は、どうぞお気軽にご相談ください。小さな疑問から大規模プロジェクトまで、最適なご提案を心を込めてさせていただきます。

ぜひ、プロフィールWeb制作会社様向け業務案内一般企業様向け業務案内もご覧くださいね。

Response APIのFile Search機能をPHPで実装:ファイルから情報を取得できるチャットボットの作り方

2025.03.18

OpenAIのResponse APIに新しく追加されたFile Search機能を使って、ファイルから情報を取得できるチャットボットをPHPで実装する方法を解説します。Vector Storeの作成から、実際のコード例まで、ステップバイステップで説明していきます。

OpenAI API PHP

Response APIのWeb Search機能をPHPで実装:最新情報を取得できるチャットボットの作り方

2025.03.17

Response APIの新機能"Web Search"を使って、インターネット上の最新情報を取得できるチャットボットを作ります。PHPによる実装方法や、APIレスポンスの処理方法など、実用的な内容をコード例とともに解説していきます

OpenAI API PHP

OpenAIの新APIを使ってみた:Response APIによるチャットボットをPHPで実装

2025.03.16

OpenAIが新たに発表したResponse APIを使用して、PHPでシンプルなチャットボットを実装してみました。Chat Completions APIとの違いや、実装時のポイントを、実際のコード例と共に解説します。特に会話履歴の管理方法の違いに注目して、両APIの特徴を比較しています。

OpenAI API PHP

GPTのfunction callingを活用した関数呼び出し入門

2023.11.23

OpenAI APIのfunction calling機能を使えば、GPTに特定の関数を呼び出させることで、複雑なタスクの自動化が可能になります。本記事では、PHPを用いた基本的な関数指定の方法やレスポンス処理のフローを詳しく解説。日記投稿を例に、APIの設定から応答データの再利用まで実践的なスクリプトを紹介します。

OpenAI API PHP

阿部辰也へのお仕事の依頼・お問い合わせ

軽いご相談もお気軽にどうぞ!

個人情報の取り扱いについて *必須 プライバシーポリシーをご確認いただき、同意いただける場合は「同意する」にチェックをしてください。

keyboard_double_arrow_up
TOP