こんにちは
エンジニアの上本です。
6月13日にOpenAIの大きなアプデートが発表されました。
その中で目玉機能として紹介されていた「Function callng」を遅ればせながら試してみたので、その概要と所管をお伝えしたいと思います。
Function callingとは
Function callingを簡単に説明すると、外部関数の呼び出しを検知して、教えてくれる仕組みのことです。
スケジュールを管理するボットを例に考えて見ましょう。「明日のスケジュールは?」と聞いてもGPTはスケジュールの情報を持っていません。
その情報を取得するにはスケジュールを管理しているサービスの予定を取得する関数(API)を呼び出す必要があるのですが、それを教えてくれるのがFunction callingです。
また、APIの定義に則ってパラメータを調整してくれます。例えば、スケジュールを取得するAPIは日付を年月日で指定する必要があるとします。しかしAPIに「明日」と指定することはできません。この部分をOpenAIがよしなに変換してくれます。
めちゃくちゃ有り難いですね!
自社サービスを想定して試してみる
概要がわかったところで、実際に自社で提供しているスマートホーム化ツール「Home Hub」を想定し、自然言語の指示からAPI操作を行う処理を試してみます。(※Home HubのAPIは公開していないためモックで表現)
インストール
現在公式で公開しているライブラリは「Python」「Node.js」「Azure OpenAI libraries」ということなので、今回は「Node.js」を使用します。
npm install openai dotenv
npm install openai dotenv
イニシャライズ
OpenAI APIの認証情報を指定します。
# index.ts
import {Configuration, OpenAIApi} from 'openai'
import * as dotenv from "dotenv"
dotenv.config()
const configuration = new Configuration({
organization: process.env.OPENAI_ORGANIZATION_ID,
apiKey: process.env.OPENAI_API_KEY,
})
const openai = new OpenAIApi(configuration)
# index.ts
import {Configuration, OpenAIApi} from 'openai'
import * as dotenv from "dotenv"
dotenv.config()
const configuration = new Configuration({
organization: process.env.OPENAI_ORGANIZATION_ID,
apiKey: process.env.OPENAI_API_KEY,
})
const openai = new OpenAIApi(configuration)
モックを用意
実際のAPIを呼び出すための関数を用意します。今回は、「照明の操作」「鍵の操作」を行うためのAPIを呼び出す関数を用意しました。それぞれ、部屋と操作内容を指定する想定です。
const lightAction = ({room, action}: { room: string, action: string }): Promise<{ room: string, action: string }> => {
// 照明操作のAPIを呼び出す
return new Promise((resolve) => resolve({room, action}))
}
const lockAction = ({room, action}: { room: string, action: string }): Promise<{ room: string, action: string }> => {
// スマートロック操作のAPIを呼び出す
return new Promise((resolve) => resolve({room, action}))
}
const functions = {lightAction, lockAction}
const lightAction = ({room, action}: { room: string, action: string }): Promise<{ room: string, action: string }> => {
// 照明操作のAPIを呼び出す
return new Promise((resolve) => resolve({room, action}))
}
const lockAction = ({room, action}: { room: string, action: string }): Promise<{ room: string, action: string }> => {
// スマートロック操作のAPIを呼び出す
return new Promise((resolve) => resolve({room, action}))
}
const functions = {lightAction, lockAction}
APIの定義を指定
「name」に先程作成したモックを呼び出す関数。「description」にその関数の説明。「parameters」に関数の引数の情報を指定します。
const functionDefinitions = [{
name: 'lightAction',
description: '指定した部屋の照明を操作する',
parameters: {
type: 'object',
properties: {
room: {
type: 'string',
description: '部屋',
},
action: {
type: 'string',
enum: [
'ON',
'OFF',
],
description: '操作',
},
},
required: ['room', 'action']
},
}, {
name: 'lockAction',
description: '指定した部屋の鍵を操作する',
parameters: {
type: 'object',
properties: {
room: {
type: 'string',
description: '部屋',
},
action: {
type: 'string',
enum: [
'LOCK',
'UNLOCK',
],
description: '操作',
},
},
required: ['room', 'action']
},
}];
const functionDefinitions = [{
name: 'lightAction',
description: '指定した部屋の照明を操作する',
parameters: {
type: 'object',
properties: {
room: {
type: 'string',
description: '部屋',
},
action: {
type: 'string',
enum: [
'ON',
'OFF',
],
description: '操作',
},
},
required: ['room', 'action']
},
}, {
name: 'lockAction',
description: '指定した部屋の鍵を操作する',
parameters: {
type: 'object',
properties: {
room: {
type: 'string',
description: '部屋',
},
action: {
type: 'string',
enum: [
'LOCK',
'UNLOCK',
],
description: '操作',
},
},
required: ['room', 'action']
},
}];
実行してみる
諸々定義が用意できたので試しに「リビングの電気をつけて」と指示してみます。
(async () => {
const prompt = 'リビングの電気をつけて'
const res = await openai.createChatCompletion({
model: 'gpt-4-0613',
messages: [{
role: 'user',
content: prompt
}],
function_call: 'auto',
functions: functionDefinitions
})
console.log(res.data.choices[0].message)
})()
(async () => {
const prompt = 'リビングの電気をつけて'
const res = await openai.createChatCompletion({
model: 'gpt-4-0613',
messages: [{
role: 'user',
content: prompt
}],
function_call: 'auto',
functions: functionDefinitions
})
console.log(res.data.choices[0].message)
})()
実行結果
{
role: 'assistant',
content: null,
function_call: {
name: 'lightAction',
arguments: '{\n "room": "リビング",\n "action": "ON"\n}'
}
}
{
role: 'assistant',
content: null,
function_call: {
name: 'lightAction',
arguments: '{\n "room": "リビング",\n "action": "ON"\n}'
}
}
実行するとこのように、照明を操作するAPI「lightAction」を使い、引数には「room: リビング」「action: ON」を指定すると返ってきました。
続いて、指示を「玄関の鍵を締めて」に変更してみます。
実行結果
{
role: 'assistant',
content: null,
function_call: {
name: 'lockAction',
arguments: '{\n "room": "玄関",\n "action": "LOCK"\n}'
}
}
{
role: 'assistant',
content: null,
function_call: {
name: 'lockAction',
arguments: '{\n "room": "玄関",\n "action": "LOCK"\n}'
}
}
はい、凄すぎますね!
取得したAPIを叩く
あんな簡単なAPI定義を指定するだけで、自然言語での指示を実際に利用できる形式になりました。あとは実際にAPIを叩いてあげるだけです。
createChatCompletionの実行の後に下記を追加します。
const message = res.data.choices[0].message
const functionCall = message?.function_call
if (functionCall) {
const args = JSON.parse(functionCall.arguments)
const response = await functions[functionCall.name](args)
}
const message = res.data.choices[0].message
const functionCall = message?.function_call
if (functionCall) {
const args = JSON.parse(functionCall.arguments)
const response = await functions[functionCall.name](args)
}
実行すると、すんなり操作が行えました。
実行結果をGPTに返答させる
GPTに返答させるには、実行結果を「messages」に追加します。
先程のif文の終了前に書きを追加します。
const response2 = await openai.createChatCompletion({
model: 'gpt-4-0613',
messages: [{
role: 'user',
content: prompt
}, {
role: 'function',
content: JSON.stringify(response),
name: functionCall.name
}],
function_call: 'auto',
functions: functionDefinitions
})
console.log(response2.data.choices[0].message)
const response2 = await openai.createChatCompletion({
model: 'gpt-4-0613',
messages: [{
role: 'user',
content: prompt
}, {
role: 'function',
content: JSON.stringify(response),
name: functionCall.name
}],
function_call: 'auto',
functions: functionDefinitions
})
console.log(response2.data.choices[0].message)
実行結果
{ role: 'assistant', content: '玄関の鍵を締めました。' }
{ role: 'assistant', content: '玄関の鍵を締めました。' }
よしなに、人が理解できる文章で返事をしてくれました。
所管
Function callingを試してみて、自然言語の曖昧な指示から「どのAPIを」「どんなパラメータで」叩けば良いのかという具体的な指示に、こんなに簡単に実現できるのに驚きました。
音声入力を受け付ければ、自アプリだけで簡単なスマートスピーカーを実現できたりと可能性が拡がりまくりですね。今後も色々と試して見たいと思います。