最近常用 AI 產生程式碼,結果一堆程式自己都看不懂,只有錯誤發生時才會回頭研究到底寫了什麼。想說乾脆寫篇文章記錄一下,當作學習筆記。本篇主題是 OpenAI API 與 WordPress 的整合,讓 AI 能直接操作 WordPress。
初探工具請求(Function calling)
像 GPT 這類自然語言模型是靠訓練資料回應問題,但資料若過時,或想取得即時資訊,模型本身無法處理。這時可以透過「工具請求(Function calling)」來解決。
原理是:先告訴 AI 有哪些工具可用,當對話中出現相關需求,AI 就能自動判斷是否要使用某個工具。這些工具可以是 WordPress 的函式(如 WP_Query 查文章),也可以是外部 API(例如 Google 搜尋),執行後把結果再交給 AI 處理並產出回應,自然語言化地回答使用者。
適用場景
1. 整合客服機器人
如果你想設計聊天介面給其他人使用,像是客服機器人外掛,就很適合採用這個技術來取得網站內的資訊,像是讓客人查詢商品、訂單相關資訊、搜尋產品使用說明文件,或是讓管理員查詢營業額、網站瀏覽數據。
近期在開發的網站助理就是使用這個技術來讓管理者查詢訂單資料:
https://oberonlai.blog/dwp-site-assist/
2. 整合 LINE 聊天機器人
只要透過 Messaging API 的 webhook 呼叫指定的 API,再讓這個 API 去執行 OpenAI 的工具請求,就能把執行結果回傳到 LINE 裡面,這樣就能讓有加入官方帳號或是群組裡的好友直接使用你提供的工具。
例如查星座運勢、摘要表單內容、根據提交資料自動整理資訊等,都能透過工具組合不同資料來源,提供有脈絡的回答。像是萬一有客人問 A 產品的相關資訊,除了先讀取產品描述外,還能讀取近期的訂單資料、產品評價、相關問題,最後整理成有脈絡的回應給客戶:
「您詢問的 A 產品功能包含 XXX,最近有 X 位客人選購,其中 Y 留下好評,對於 XXX 特別讚譽,解決了原本的 X 問題。」
設定工具時需要以下要素:
- 工具名稱:與你實作的函式名稱一致
- 工具描述:自然語言說明用途,AI 判斷是否使用工具的依據
- 工具屬性:呼叫時需傳入的參數(如 post_id、日期等)
- 屬性描述:自然語言說明屬性的用途
AI 使用流程:
- 使用者提問
- 呼叫 API,附上工具清單
- AI 判斷是否使用工具
- 使用工具,取得結果
- 將結果回應給使用者應用案例
這邊以 OpenAI API 為範例,設計一個根據文章 ID 來取得文章內容的工具。在設計上我們先處理工具的回應結果,這邊用 get_post_content_by_id 來取得文章內容,這個函式帶有一個參數也就是文章 ID:
function get_post_content_by_id( int $post_id ): ?string {
$post = get_post( $post_id );
if ( ! $post || 'publish' !== $post->post_status ) {
return null;
}
// 回傳處理過的內文
return apply_filters( 'the_content', $post->post_content );
}
接下來定義工具的清單,需要包含上述提及的幾個基本要素:
$functions = array(
array(
'name' => 'get_post_content_by_id', // 工具名稱
'description' => 'Get the content of a post by post ID.', // 工具描述
'parameters' => array(
'type' => 'object',
'properties' => array(
'post_id' => array( // 工具屬性
'type' => 'integer',
'description' => 'The ID of the WordPress post.', // 工具屬性描述
),
),
'required' => array( 'post_id' ),
),
),
);
不管是工具描述還是屬性描述,記得要寫清楚,因為這是 AI 判斷呼叫工具的依據,然後工具名稱要跟上一步驟定義的函式名稱一樣,AI 才知道要呼叫誰,工具屬性 post_id 以及函式參數 $post_id 的也是要相同。
準備好工具後就可以開始請求 API 進行聊天:
$messages = array(
array( 'role' => 'user', 'content' => 'Please show me the content of post 42.' ),
);
$response = wp_remote_post(
'https://api.openai.com/v1/chat/completions',
array(
'headers' => array(
'Authorization' => 'Bearer ' . YOUR_OPENAI_API_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array(
'model' => 'gpt-4.1-nano,
'messages' => $messages,
'functions' => $functions,
) ),
'method' => 'POST',
)
);
$data = json_decode( wp_remote_retrieve_body( $response ), true );
$tool_call = $data['choices'][0]['message']['tool_calls'][0];
在 body 的地方傳入 $functions 就代表這次的對話有提供我們的工具,回應的結果如果帶有 tool_calls 的話,就代表使用者的提問有觸發到我們的工具,接下來我們需要拆解 AI 從使用者提問中拿到的工具名稱以及屬性:
$function_args = json_decode( $tool_call['function']['arguments'], true );
$function_name = $tool_call['function']['name'];
判斷 $function_name 是 get_post_content_by_id 的話,就執行我們的函式取得工具執行的結果,這邊要注意的是回應給 AI 的結果有固定結構化格式,要依照每一家模型的規定組成要求的格式:
$post_content = get_post_content_by_id( $function_args['post_id'] );
$tool_response = array(
'role' => 'tool',
'tool_call_id' => $tool_call['id'],
'content' => $post_content ? $post_content : 'Post not found.',
);
然後連同工具執行結果再一次請求 API 來取得實際的回應內容,在第一次請求回傳的 tool_call 也要一併放在 message 中進行呼叫,才會讓 AI 知道第二次的請求是執行工具的結果,不然它會看成是一段新的對話請求:
$messages[] = array(
'role' => 'assistant',
'tool_calls' => array( $tool_call ), // 第一次請求回傳的工具資訊
);
$messages[] = $tool_response; // 工具的執行結果
$final_response = wp_remote_post(
'https://api.openai.com/v1/chat/completions',
array(
'headers' => array(
'Authorization' => 'Bearer ' . YOUR_OPENAI_API_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array(
'model' => 'gpt-4.1-nano',
'messages' => $messages,
) ),
'method' => 'POST',
)
);
$final_data = json_decode( wp_remote_retrieve_body( $final_response ), true );
echo $final_data['choices'][0]['message']['content'];
注意事項
以上的基本架構就能完成最簡單的工具使用,可以進一步擴展並整合更多的工具。在使用多個工具的情況下有可能會回傳多個 $tool_call 陣列,因此要記得處理多筆資料的情境,另外如果你有實作對話紀錄讓 AI 可以根據上下文進行回答的話,記得要在提示詞裡面指定優先使用工具來取得資訊,不然不會觸發到工具的使用。
我用的提示詞如下:
You must always call a tool if one is available before attempting to answer. Do NOT guess. Do NOT use internal memory before calling tools.
另外有一些動態的資訊想要讓 AI 知道的話,也可以在提示詞裡面用變數的方式加入,我卡最久的是讓 AI 取得今天日期。由於要查詢報表需要有日期區間,每次問結果都錯誤,後來才發現它根本不知道今天是幾號就胡亂回答一通。
另外還有時區的問題,因此我先把這些資訊用 PHP 拿到後再放到提示詞裡面,確保它能正確理解這些背景知識:
$today = current_time( 'Y-m-d' );
$time = current_time( 'H:i:s' );
$timezone = get_option( 'timezone_string' );
$gmt_offset = get_option( 'gmt_offset' );
$timezone_info = $timezone ? $timezone : 'GMT' . sprintf( '%+g', $gmt_offset );
$start_of_week = (int) get_option( 'start_of_week', 1 );
$weekday_map = array(
0 => 'Sunday',
1 => 'Monday',
2 => 'Tuesday',
3 => 'Wednesday',
4 => 'Thursday',
5 => 'Friday',
6 => 'Saturday',
);
$weekday = $weekday_map[ $start_of_week ];
return "
You must always call a tool if one is available before attempting to answer. Do NOT guess. Do NOT use internal memory before calling tools.
Today is `{$today}` and time now is `{$time}`, based on the WordPress site timezone setting: `{$timezone_info}`. You must use the WordPress site timezone for all date-related logic.
When calculating a week range:
- The first day of the week is `{$weekday}` (from get_option('start_of_week')).
- If {$today} is the first day of the week, it **must be considered** the start of the current week.
- The date range must cover from this day through 6 days later (inclusive).
- All date ranges must be computed dynamically based on the current time in the WordPress site timezone.";
結語
AI 實在太方便,連範例程式碼都可以不用自己寫。這篇記錄也讓我回頭終於看懂之前 Cursor 產的程式碼到底在幹嘛 XD
果然寫文章是最好的學習方式!
參考資料
https://platform.openai.com/docs/guides/function-calling