実装例① 文章を読み解いて必要なデータを抽出する
以下の文章をご覧ください。
お疲れ様です。
新しいプロジェクト「フューチャー・イノベーション2024」のキックオフミーティングを以下の日程で行います。
事前に関連資料をメールにて送付いたしますので、ご一読いただき、積極的な参加をお願いします。
場所は会議室Bとなります。
3月1日(金)14:00〜15:00
また、別件となりますが、明後日の20:00から渋谷の居酒屋にて部署全員での親睦会を予定しております。
親睦を深める良い機会となることを期待しておりますので、ぜひご参加ください。
よろしくお願い致します。
こちらのメッセージからスケジュール情報を抜き出して自動的にカレンダーへ登録するプログラムを考えてみます。
本文中には「キックオフミーティング」と「親睦会」の2つの予定情報が含まれていますが、それぞれ記載のフォーマットが全く異なっています。キックオフミーティングは具体的な日付が言及されていますが、親睦会の日付は「明後日」という相対的な表記で、メッセージの受信日時を考慮に入れないと具体的な日付を割り出すことはできません。これらの事情を鑑みると、ここから機械的に情報を抽出するのはかなり困難なのがわかると思います。この課題を克服するためには、人間のように文章の内容を理解する読解力を備えたパーツが必要となるでしょう。そこにChatGPT APIを組み込むことによって、従来では実現困難だったこのような処理を簡単に実装する事ができます。
しかし、ChatGPTを利用してデータを抽出する場合でも、解決すべき問題はまだ残っています。通常のChatGPTの出力は自由形式のテキストですので、それをスケジュール登録に必要な要素(タイトル、開始時刻、場所など)に分解する方法が不確定であるという点です。そこで、ChatGPTに搭載されている「Function Calling」という機能を使って、ChatGPTからの返答をこちらが指定した通りのJSONデータで受け取れるようにします。
出力形式を定義する
Function Callingでは、ChatGPTに実行させる関数の引数の仕様を、プロンプトとは別のパラメーターとして送信します。今回はスケジュール登録関数として以下のように引数を定義しました。
tools = [
{
"type": "function",
"function": {
"name": "register_schedule",
"description" : "スケジュールを登録します",
"parameters": {
"type": "object",
"properties":{
"events": {
# 複数の予定を登録可能とするため、最上位を配列として定義
"type": "array",
"items": {
# 配列の中身として、予定の内容を表すオブジェクトを定義
"type": "object",
"properties":{
# 表題
"title" : {
"type": "string",
},
# 開始時刻
"start":{
"type": "string",
"description": "Format: YYYY/MM/DD hh:mm:ss"
},
# 終了時刻
"end":{
"type": "string",
"description": "Format: YYYY/MM/DD hh:mm:ss"
},
# 場所
"place":{
"type": "string"
},
# 詳細説明
"description":{
"type": "string"
}
},
"required": ["title"]
},
}
},
"required":["events"]
}
}
}
]
複数の予定を抽出するために最上位の階層を「events」という配列とし、その中に予定を表すオブジェクトが格納されるように指定しています。このような親子関係を持つ複雑な構造をプロンプトで説明するのは簡単ではないと思いますが、Function Callingを使用すれば必要なアウトプットを明確に伝えることができます。
プロンプトを作成する
次に、ChatGPTへの指示文にあたるプロンプトを作成します。
# ChatGPTへの指示文
prompt_template = """
以下のメッセージから、予定として登録すべき内容を抽出して登録してください。
複数含まれている場合は全て登録してください。
# メッセージ内容
{message}
# 受信日時
{datetime}
"""
#メッセージ内容
message = """お疲れ様です。
新しいプロジェクト「フューチャー・イノベーション2024」のキックオフミーティングを以下の日程で行います。
事前に関連資料をメールにて送付いたしますので、ご一読いただき、積極的な参加をお願いします。
場所は会議室Bとなります。
3月1日(金)14:00〜15:00
また、別件となりますが、明後日の20:00から渋谷の居酒屋にて部署全員での親睦会を予定しております。
親睦を深める良い機会となることを期待しておりますので、ぜひご参加ください。
よろしくお願い致します。
"""
# 受信日時
receive_datetime = "2024/02/01 12:00:00"
# プロンプトとして結合
prompt = prompt_template.format(message=message, datetime=receive_datetime)
ChatGPTへの指示文として、予定の登録を実行してほしいという旨と、メッセージ内容を記載しています。
また、メッセージ内に「明日」や「一週間後」と言った表記があった場合、本来ChatGPTには現在の日時を知る術がありませんが、プロンプト内にそれとわかる形で具体的な日時を記載すれば考慮に入れてくれるようになります。今回は「受信日時」として日時を記載しています。
APIを実行する
次に、作成したプロンプトと関数定義をChatGPTへ送信します。
client = OpenAI()
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{'role': 'system', 'content': prompt}],
temperature=0,
tools=tools,
tool_choice={"type": "function", "function": {"name": "register_schedule"}},
)
ここで鍵となるパラメーターは以下です。
tools
Function Callingで使用する関数定義を入力します。通常利用では複数の関数を渡し、実行する関数をAIに選択させることができますが、今回は後述の「tool_choice」によって実行する関数を固定するため、一つだけ渡します。なお、関数定義の内容は入力トークン数にカウントされますので、定義内容は英語で書いた方がコスト削減に繋がります。詳しくはドキュメントのtoolsの解説をご参照ください。
tool_choice
未指定の場合に適用されるデフォルト値の「auto」では、AIが関数の実行要否を判断し、必要なときだけJSONデータが返却されます。今回のように要否判定を行わずに必ずJSONデータを返却してほしい場合は、{"type": "function", "function": {"name": "関数名"}}という形で関数名を指定することで、定義したJSONデータが必ず返却されるようになります。関数名はtoolsパラメーターに渡したものを指定してください。詳しくはドキュメントのtool_choiceの解説をご参照ください。
temperature
主に生成結果のクオリティコントロールに使われるパラメーターで、0~2の数値で指定します。低い値を設定すると、精度の良い回答を得られる代わりに、回答のバリエーションが減ります。高い値を設定すると、回答のバリエーションが豊かになる代わりに、不正確な回答や命令違反を行う可能性が高まります。今回のように、回答のバリエーションは不要で、オペレーションの正確な遂行のみを求める場合は、「0」を指定すると最も性能が良くなります。また、temperatureの代わりにtop_pというパラメーターを指定しても同様のクオリティコントロールが可能です。詳しくはドキュメントのtemperatureの解説をご覧ください。
実行結果を確認する
# 生成結果を取得
response_message = response.choices[0].message
tool_calls = response_message.tool_calls
# 結果確認
for tool_call in tool_calls:
function_args = json.loads(tool_call.function.arguments)
events = function_args["events"]
# 抽出結果を項目別に表示
for event in events:
print("表題", event.get("title"))
print("開始時刻", event.get("start"))
print("終了時刻", event.get("end"))
print("場所", event.get("place"))
print("詳細", event.get("description"))
print("----------------------")
レスポンスオブジェクトから結果を取り出す方法は、通常のFunction Callingで関数の引数を取得する場合と同じです。この例では、各要素をprint関数で標準出力に表示しているだけですが、これらを任意のAPIに送信すれば実際にスケジュールとして登録することができるでしょう。
実行した結果は以下のようになりました。
表題: フューチャー・イノベーション2024キックオフミーティング
開始時刻: 2024/03/01 14:00:00
終了時刻: 2024/03/01 15:00:00
場所: 会議室B
詳細: 新しいプロジェクト「フューチャー・イノベーション2024」のキックオフミーティング。事前に関連資料をメールにて送付。
----------------------
表題: 部署の全員での親睦会
開始時刻: 2024/02/03 20:00:00
終了時刻:
場所: 渋谷の居酒屋
詳細: 部署の全員で親睦会を予定。親睦を深める良い機会となることを期待。
----------------------
メッセージに記載してあった2件のスケジュールが、適切な項目に分けて正しく抽出できています。元のメッセージではキックオフミーティングの日付は年の部分が省略されていましたが、受信日時に指定した日付と同じ「2024年」が補足されています。また、親睦会の日付は「明後日」と記載されていましたが、受信日時の2日後の日付が正しく抽出できています。
このように、フリーフォーマットの文章からデータを抽出するという、従来の環境では実現困難だった機能も、ChatGPTをシステムに組み込めば比較的容易に実装できます。
Function Callingについては前回の記事でも解説していますので、より詳しく知りたい方はそちらも合わせてお読みください。
