1. はじめに
はじめまして、Sreake事業部インターン生の井上です。私はSreake事業部にてSRE技術の調査と研究を行う目的で2023年3月6日から長期インターン生として参加しています。
今回、ChatGPTの新機能「FunctionCall」が登場したのでGolangで検証進めた事について、本記事で報告します。
2. ChatGPTのFunctionCallとは?
ChatGPTとは?
ChatGPTはOpenAIによって開発された大規模な自然言語処理モデルです。ChatGPTは、Webブラウザ上からGUI形式の対話ベースで利用するChatGPTと、開発者向けのChatGPT APIが提供されています。
対話形式で進めるのが基本で、以下の様な事が出来ます。
- 適切な文脈に基づく自然な返答
- プログラムコードの生成
- 様々な自然言語処理タスク
- テキスト分類
- 情報検索
- テキスト要約
- 情報抽出
- etc…
例えば、以下のプロンプトを用いて、文章の要約をJSON等で受け取る事ができます。
次のフォーマットで返信してください。
{
"summary":<Write here the summary results of the text.>,
"topic":<Write here in list form>
}
しかし、フォーマット以外の不要なレスポンスメッセージ、例えば「はい、以下の様になります」等が混入する問題があります。
FunctionCallとは?
FunctionCallとは、ChatGPT APIから利用できる機能の一つです。関数の呼び出し定義を事前に定義する事で、会話の流れに応じて、通常のレスポンスの代わりに、呼び出す関数の名前と、呼び出しに必要な引数を生成します。フォーマット以外の不要なレスポンスメッセージが混入するという問題を解決しています。
つまりなにが出来るようになったのか?
上記図は、FunctionCallを使ったIoT連携のアイデア例です。この例では、予めChatGPT Bot内で外部サービスと連携出来る様に、関数の呼び出しを定義する必要があります。
スレッド内でユーザがChatGPT Botをメンションすると、スレッド内部の会話と関数の呼び出し定義をChatGPT APIに送信します。会話の内容に応じて、ChatGPTは関数の呼び出し定義を基に、関数をレスポンスするので、ChatGPT Bot側でサービスの操作ができます。
3.検証方法
本記事で行う検証内容について説明します。本記事では、複数の関数の呼び出し定義を用意して、Go言語を通して、ChatGPT APIに関数の呼び出し定義と会話文を送信します。
ChatGPTは会話文に応じて、複数の関数の呼び出し定義の中から、適切な関数を生成します。また、特定の関数(get_official_documents.json
)がレスポンスされた場合は、追加で処理を行う様にしました。
関数の呼び出し定義
関数の呼び出し定義には、JSON Schemaを利用します。
[
{
"name": "get_recommended_coffee", # 関数名
"description": "Recommended coffee", # 関数の説明
"parameters": {
"type": "object",
"properties": { # 関数の引数をこの中に定義
"producing_country": { # 引数名
"type": "string", # 引数の型
"description": "Random country name." # 引数の説明
}
},
"required": ["producing_country"] # 必ず生成する引数を指定
}
}
]
検証手順
検証課題として、以下のa~cの関数の呼び出し定義(JSON Schema)を定義します。
a. get_recommended_coffee.json
おすすめのコーヒを取得するための関数
[
{
"name": "get_recommended_coffee",
"description": "Recommended coffee",
"parameters": {
"type": "object",
"properties": {
"producing_country": {
"type": "string",
"description": "Random country name but make sure its produce coffee."
}
},
"required": ["producing_country"]
}
}
]
b. get_weather_information.json
天気情報を取得するための関数
[
{
"name": "get_weather_information",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"]
}
}
]
c. get_official_documents.json
公式ドキュメントのURLを取得するための関数
[
{
"name": "get_official_documents",
"description": "Refer to the URL of the relevant document.",
"parameters": {
"type": "object",
"properties": {
"document_URL": {
"type": "array",
"description": "URL of the relevant official document, even if there is more than one.",
"items": {
"type": "string"
}
},
"tag": {
"type": "array",
"description": "Useful tags to investigate. e.g.) AWS, EC2, payment method",
"items": {
"type": "string"
}
}
},
"required": ["document_URL","tag"]
}
}
]
1で定義したJSON Schemaを携えて会話文を送信して、適切なタイミングで関数が生成されるか検証します。本検証で利用するgolangのコードはコチラで公開しています。使用している技術は以下の通りです。
- Docker version 24.0.2, build cb74dfc # 環境構築用
- golang 1.20
- github.com/sashabaranov/go-openai v1.13.0 # ChatGPT APIを利用するためのライブラリ
検証結果
1.紅茶を頂けますか? “Can I have a cup of tea?”
$ go run . "Can I have a cup of tea?"
Input -> Can I have a cup of tea?
resp.Choices[0].Message -> {assistant Of course! I can't physically make you a cup of tea, but I can recommend a type of tea for you. What kind of tea do you usually enjoy? <nil>}
紅茶に関するJSON Schemaは、定義していないので通常の文章でレスポンスされる。
2.コーヒーを頂けますか? “Can I have a cup of coffee?”
$ go run . "Can I have a cup of coffee?"
Input -> Can I have a cup of coffee?
resp.Choices[0].Message -> {assistant 0xc0000ae180}
get_recommended_coffee
{
"producing_country": "Ethiopia"
}
おすすめのコーヒーとして、エチオピア産のコーヒーを取得する事ができる。
3.東京の天気予報を教えて。 ”What is the weather forecast in Tokyo?”
$ go run . "What is the weather forecast in Tokyo?"
Input -> What is the weather forecast in Tokyo?
resp.Choices[0].Message -> {assistant 0xc0000af4c0}
get_weather_information
{
"location": "Tokyo"
}
天気情報を取得するAPIがあれば、レスポンスされたJSONを利用して、天気情報を取得できるかもしれません。
4.Google Cloud PlatformのIAMロールについて教えて。 “Tell me about IAM roles on Google Cloud Platform.”
$ go run . "Tell me about IAM roles on Google Cloud Platform." | head -n 15
Input -> Tell me about IAM roles on Google Cloud Platform.
resp.Choices[0].Message -> {assistant 0xc0004cf4a0}
get_official_documents
{
"document_URL": ["https://cloud.google.com/iam/docs/overview", "https://cloud.google.com/iam/docs/understanding-roles", "https://cloud.google.com/iam/docs/creating-managing-roles"],
"tag": ["IAM", "roles", "Google Cloud Platform"]
}
URL 0: https://cloud.google.com/iam/docs/overview
<!doctype html>
<html
lang="en"
dir="ltr">
<head>
<meta name="google-signin-client-id" content="721724668570-nbkv1cfusk7kk4eni4pjvepaus73b13t.apps.googleusercontent.com">
<meta name="google-signin-scope"
...長いので省略
get_official_documentsがレスポンスされた際には、HTTPリクエストを送信して、ページ内容を取得するという処理を記述していました。従って、ここではbodyの情報が出力されます。
おまけでわかった事
JSON Schemaのrequiredで必要なパラメータを指定しないと、ほぼ呼び出されない。会話文中で明示しても帰ってこないケースが多々ありました。
一つの文章で、複数のFunctionCallを呼び出す様な文章を与えるとどうなるのか、気になったので追加調査をしてみました。
1.東京の天気予報は?どんなコーヒーを飲めばいい? ”What is the weather forecast in Tokyo? And what coffee should i drink?”
$ go run . "What is the weather forecast in Tokyo? And what coffee should i drink?"
Input -> What is the weather forecast in Tokyo? And what coffee should i drink?
resp.Choices[0].Message -> {assistant 0xc0002e4080}
get_weather_information
{
"location": "Tokyo"
}
2.今日は何のコーヒーを飲めばいい?あと東京の天気予報も教えて? “What coffee should I have today? And can you tell me the weather forecast for Tokyo?”
$ go run . "What coffee should I have today? And can you tell me the weather forecast for Tokyo?"
Input -> What coffee should I have today? And can you tell me the weather forecast for Tokyo?
resp.Choices[0].Message -> {assistant 0xc00011a180}
get_recommended_coffee
{
"producing_country": "Colombia"
}
結果: 一つのメッセージに、二つのFunctionCallを呼び出すような文章を書いても一つしか呼び出されない。10回ほどの検証で、先頭の会話文に対応するFunctionCallしか呼び出されなかった。
ChatGPTに生成済みのFunctionCallを提示して、別のFunctionCallの生成促すとどうなるか?
- イメージ
フローチャートに示した手法は、オリジナルの文章からFunciton Callが生成された場合に、他にも生成できるFuncitonCallがあるか調べるためのものです。Funciton Callが生成されると、生成されたFunctionCallとオリジナルのメッセージを使ったプロンプトを作成します。以下は本検証で使用したプロンプトです。
%s //生成済みのFunctionCall
The above is a generated Function Call.
It is generated based on the following text.
---
%s //オリジナルのメッセージ
---
Are there any other Function Calls that should be generated?
If no, please say "We have generated a Function Call in response to your request".
作成したプロンプトを利用して、再度ChatGPTに生成できるFunctionCallがあるか、問い合わせます。期待通りの動作が行われれば、一つの文章から複数のFunctionCallを生成できます。
検証A: 東京の天気予報は?どんなコーヒーを飲めばいい? ”What is the weather forecast in Tokyo? And what coffee should i drink?”
$ go run . "What is the weather forecast in Tokyo? And what coffee should i drink?"
---- 0 ----
Input -> What is the weather forecast in Tokyo? And what coffee should i drink?
# FunctionCallでget_weather_informationが生成された
Generated function -> get_weather_information
---- 1 ----
Input ->
{
"get_weather_information": "{\n \"location\": \"Tokyo\"\n}"
}
The above is a generated Function Call.
It is generated based on the following text.
---
What is the weather forecast in Tokyo? And what coffee should i drink?
---
Are there any other Function Calls that should be generated?
If no, please say "We have generated a Function Call in response to your request".
# なぜかFunctionCallのフィールドではなく、通常のメッセージフィールで作成されている。
Generated message -> {assistant {
"get_recommended_coffee": {
"producing_country": "Japan"
}
}
We have generated a Function Call in response to your request. %!s(*openai.FunctionCall=<nil>)}
---- Done ----
results
# FunctionCallとして生成されたのは、一つのみ
{
"get_weather_information": "{\n \"location\": \"Tokyo\"\n}"
}
検証B: プロンプトを少し変更して検証します。
%s //生成済みのFunctionCall
The above is a generated FunctionCall.
It is generated based on the following text.
---
%s //オリジナルのメッセージ
---
Are there any other Function Calls?
東京の天気予報は?どんなコーヒーを飲めばいい? ”What is the weather forecast in Tokyo? And what coffee should i drink?”
go run . "What is the weather forecast in Tokyo? And what coffee should i drink?"
---- 0 ----
Input -> What is the weather forecast in Tokyo? And what coffee should i drink?
Generated function -> get_weather_information
---- 1 ----
Input ->
{
"get_weather_information": "{\n \"location\": \"Tokyo\"\n}"
}
The above is a generated Function Call.
It is generated based on the following text.
---
What is the weather forecast in Tokyo? And what coffee should i drink?
---
Are there any other Function Calls?
# 生成するべきFunctionCallは無いと言われてしまう。
Generated message -> {assistant No, there are no other Function Calls. %!s(*openai.FunctionCall=<nil>)}
---- Done ----
results
{
"get_weather_information": "{\n \"location\": \"Tokyo\"\n}"
}
結果: 提案した手法でも、一つの文章からFunctionCallを複数生成する事が出来ませんでした。
まとめ
FunctionCallはとても魅力的で、APIを介する事で、新しいサービスや拡張が期待できます。しかし、一つの文章から一つのFunctionCallしか呼び出せないという課題が本調査によって明らかになりました。
おわりに
本記事では、ChatGPTのFunctionCall機能について説明しました。次にGo言語を利用して、ChatGPT APIを操作して、FunctionCall機能を使用して調査しました。
元々Pythonユーザだった事もあり、コーディングするのに時間がかかりました。ただし、コードの可読性と保守性等やライブラリのインポート、Go製のツールの導入等が遥かに簡単という事が学べました。
今後、ChatGPTそのものの機能だけでなく、ChatGPT等の大規模言語モデルの機能を拡張できるLangChain等についても検証を行いたいと考えています。