はじめに
こんにちは、慶應義塾大学理工学部機械工学科3年の金賢佑です!
私は東大発AIベンチャーの株式会社2WINSの元でインターンをしています!
これからの記事では自分がインターンを通して最強のAIエンジニアを目指していきます!
第三回はFanction callingの基礎を記録しました!
Function callingとは
Function callingとはLLMに「この機能が使える!」と判断させる機能のこと!
なのでLLM側がその機能を実行するわけではありません。
例えばあなたが「東京の天気を摂氏で教えて」といった時、
1.LLMがツールから使える関数、例えばget_current_weather
を選んでくれる。
2.{"location": "東京", "unit": "celsius"}
という引数を組み立ててあなたのプログラムにこの関数を実行して!と伝えてくれる。
3.あなたのプログラム側でその関数を実行する。
4.結果をLLMに返し、それを元に自然な文章で返事をしてくれる。
LLMって?
LLMとはLarge Language Model(大規模言語モデル)の略で言語モデルの一種!
前回扱ったChat GPTもLLmの一つですね。
言語モデルについては「3Blue1BrownJapan」さんが出している動画がとてもわかりやすいです!
2025/4/1現在、Chapter7くらいまであります。
https://youtu.be/tc8RTtwvd5U?si=ynBpv-bFHHjfwOCz
Function callingを使ってみよう
関数の定義
以下のコードを実行してください。
import json
def get_current_weather(location, unit='fahrenheit'):
if 'tokyo' in location.lower():
return json.dumps({'location':'Tokyo', 'temperature':'10', 'unit': unit})
elif 'san francisco' in location.lower():
return json.dumps({'location': 'San Francisco', 'temperature': '72', 'unit':unit})
elif 'paris' in location.lower():
return json.dumps({'location':'Paris', 'temperature': '22', 'unit': unit})
else:
return json.dumps({'location': location, 'temperature': 'unknown'})
これはもらった場所の名前が「Tokyo」「San Francisco」「Paris」だったらそれぞれの温度を返す、それ以外であればunknown
と返す関数!
ちなみに返す内容はJSON形式の文字列。
今回も細かく解説していきます。
import json
json
ライブラリを読み込みます。
この後json.dumps()
を使うためにインポートしておきます。
def get_current_weather(location, unit='fahrenheit'):
get_current_weather
という関数を定義しています。
location
とunit
という二つの引数が必要とされていますが、unit
にはデフォルトで'fahrenheit'
という引数が与えられていますね。
2つ目の引数について実例を記載しておきます。
get_current_weather("Tokyo") # → unit は省略されて 'fahrenheit' になる
get_current_weather("Tokyo", "celsius") # → unit = 'celsius' に上書き
if 'tokyo' in location.lower():
return json.dumps({'location':'Tokyo', 'temperature':'10', 'unit': unit})
lower()
は小文字に直す関数です。つまりlocation
に渡された値(ユーザーが入力した値)がTOKYO
でもTokyo
でもToKyo
でも全てtokyo
に直してくれるんですね。
そうすることで一文目のif分でlocation
にtokyo
が入力されているのかどうか正確に判断することができますね。
その後、Tokyo
のデータをJSON形式で返しています。
どうしてJSON形式に直すの?
最初に説明したようにFunction callingではプログラムはPython側で実行し、その結果はLLM(今回だとChatGPT)に返しますよね。(その後LLMが自然な文章に直して返事をしてくれる!)
だからLLMが読みやすい形式に直すのがいいんです!
elif 'san francisco' in location.lower():
return json.dumps({'location': 'San Francisco', 'temperature': '72', 'unit':unit})
elif 'paris' in location.lower():
return json.dumps({'location':'Paris', 'temperature': '22', 'unit': unit})
else:
return json.dumps({'location': location, 'temperature': 'unknown'})
その後も同じロジックで処理が書かれていますね。
これでget_current_weather
関数の定義が完成しましたね。
toolの作成
先程関数の定義を行いましたが、これだけではFunction callingは機能しません。
LLMが🤖「この関数が使える!」と処理できるようにLLM用の関数の説明書、メニュー表のようなものが必要です。
それをtool
と言ったりします。
以下のコマンドを打ってみてください。
tools = [
{
type: "function",
function: {
name: "get_current_weather",
description: "Get the current weather in a given location",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Fracisco, CA",
},
unit: { type: "string", enum: ["celsius", "fahrenheit"] },
},
required: ["location"],
},
},
},
];
今回も細かく解説していきます。
tools = [
{
'type': 'function',
「このツールは関数だよ!」と書いてあります。
'function': {
'name': 'get_current_weather',
'description': 'Get the current weather in a given location',
関数の名前と説明文が記載されていますね。
'parameters': {
'type':'object',
関数が受け取る引数の情報です。
'type': 'object'
は引数がオブジェクトですということを意味しています。
オブジェクトとは複数の値をセットで受け取るためのまとまりだと思ってください。(dict型みたいなもの!)
今回だとlocation
というキーにTokyo
という値が入っていたりしますよね。
'properties': {
'location': {
'type': 'string',
'description': 'The city and state, e.g. San Fracisco, CA',
},
'unit':{'type': 'string', 'enum': ['celsius', 'fahrenheit']},
},
properties
は使えるパラメータの一覧です。
location
は"Tokyo"
などが入ります。
unit
は"celsius"
か"fahrenheit"
のどちらかに限定します。
enum
で列挙型にすることでこれを可能にしています。
'required': ['location'],
},
},
}
]
required
は必須パラメータです。
ここにunit
が入っていないのは、unit
にはデフォルトで値が設定されているからでしたね。
Chat Completion APIを呼び出してみよう
以下のコマンドを打ってみてください。
from openai import OpenAI
client = OpenAI()
messages = [
{'role': 'user', 'content':'東京の天気はどうですか?'}
]
response = client.chat.completions.create(
model='gpt-4o',
messages=messages,
tools=tools,
)
print(response.to_json(indent=2))
今回の会話の履歴はユーザーからの「東京の天気を知りたい!」というだけですね。
model
は'gpt-4o'
を使っています。
messages
は先程の会話の履歴のmessages
を(名前が一緒だからわかりにくい)
tools
にも先程作成したtools
を代入しています。(これも名前が一緒だからわかりにくい)
返ってっきたresponse
をJSON形式に直してprintしていますね。
ちなみにindent=2
は見やすく整形しているだけです。
実行結果
長々と書かれていますが、注目して欲しいのは赤枠の部分。
LLMが先程の会話履歴から、「get_current_weather
関数を東京
という引数で実行するべきだ」と判断したことが書かれています。
(if 'tokyo' in location.lower():は
if 'tokyo' in location.lower() or '東京' in location:`に直しておいた方が良さそうですね。)
この判断をもとにPythonで関数を実行してあげれば良さそうですね。
ではこのメッセージを会話履歴として残しておきましょう。
以下のコマンドを実行してください。
response_message = response.choices[0].message
messages.append(response_message.to_dict())
response
とはさっき長々とprintされた文のことです。
LLMは出力を何種類も出すのでresponse
の最初の選択肢にするようchoices[0]
としています。
ちなみに今回[0]
としたのは先程の出力で"index":0
となっていたのでそれに対応するようにですね。
まとめるとさっきの出力のmessage
の部分を丸々response_message
に記録(代入)しています。
2行目では、そうして記録された会話をmessages
にappend()
、つまり追加しています。
こうして会話がどんどん残るようにしているのですね。
LLMが実行したい関数をこちらで実行してあげよう
以下のコマンドを打ってください。
available_functions = {
'get_current_weather': get_current_weather,
}
for tool_call in response_message.tool_calls:
function_name = tool_call.function.name
function_to_call = available_functions[function_name]
function_args = json.loads(tool_call.function.arguments)
function_response = function_to_call(
location=function_args.get('location'),
unit=function_args.get('unit'),
)
messages.append(
{
'tool_call_id': tool_call.id,
'role': 'tool',
'name': function_name,
'content': function_response,
}
)
print(function_response)
ここあたりから情報量が多くなってくるので、躓いたら前に戻りながら確認してください。
available_functions = {
'get_current_weather': get_current_weather,
}
先程GPTからの返事の中に"tool_calls"
がありました。
その中に使いたい関数が"name": "get_current_weather"
として書かれています。
しかし"get_current_weather"
は文字列のため、対応する関数と紐付ける必要があります。
上のコードはそれを行なっています。
for tool_call in response_message.tool_calls:
response_massage
とは会話が記録される箱でしたね。さっき作りました。
その中で必要とされている関数、tool_calls
を一つずつ呼び出しているんですね。
function_name = tool_call.function.name
呼び出したい関数
function_to_call = available_functions[function_name]
呼び出したい関数の名前を、最初に作ったavailable_functions
の辞書から取得する。
get_current_weather()
が入ります。
function_args = json.loads(tool_call.function.arguments)
function_args
にはGPTが提案してきた関数の引数を渡します。
今回だと"arguments": "{\"location\":\"東京\"}"
でしたね。
function_response = function_to_call(
location=function_args.get('location'),
unit=function_args.get('unit'),
)
実際に関数を呼び出します。
今回、function_to_call
にはget_current_weather()
が入っていましたね。
messages.append(
{
'tool_call_id': tool_call.id,
'role': 'tool',
'name': function_name,
'content': function_response,
}
)
messages
にこのtool_call
にはこういう返信をしたよ〜って記録する!
print(function_response)
結果は
{"location": "Tokyo", "temperature": "10", "unit": "celsius"}
となって、ちゃんと返ってきていますね。
もう一度APIリクエストを送ってみよう
先程の7.で追加したメッセージを使ってAPIにメッセージを送ってみます。
second_response = client.chat.completions.create(
model='gpt-4o',
messages=messages,
)
print(second_response.to_json(indent=2))
出力は
{
"id": "chatcmpl-BJXBntAtL9pgUlUEeKUkRWGBZDi3M",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"message": {
"content": "東京の現在の気温は約10℃です。天気の詳細について知りたい場合は、具体的な情報をご確認ください。",
"refusal": null,
"role": "assistant",
"annotations": []
}
}
],
"created": 1743995511,
"model": "gpt-4o-2024-08-06",
"object": "chat.completion",
"service_tier": "default",
"system_fingerprint": "fp_898ac29719",
"usage": {
"completion_tokens": 32,
"prompt_tokens": 64,
"total_tokens": 96,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
}
}
先程の実行結果も踏まえた回答が送られてきていますね。
実行コードまとめ
#JSONのインポート
import json
def get_current_weather(location, unit='fahrenheit'):
if 'tokyo' in location.lower():
return json.dumps({'location':'Tokyo', 'temperature':'10', 'unit': unit})
elif 'san francisco' in location.lower():
return json.dumps({'location': 'San Francisco', 'temperature': '72', 'unit':unit})
elif 'paris' in location.lower():
return json.dumps({'location':'Paris', 'temperature': '22', 'unit': unit})
else:
return json.dumps({'location': location, 'temperature': 'unknown'})
#ツールの作成
tools = [
{
type: "function",
function: {
name: "get_current_weather",
description: "Get the current weather in a given location",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Fracisco, CA",
},
unit: { type: "string", enum: ["celsius", "fahrenheit"] },
},
required: ["location"],
},
},
},
];
# OpenAIにAPIを送る
from openai import OpenAI
client = OpenAI()
messages = [
{'role': 'user', 'content':'東京の天気はどうですか?'}
]
response = client.chat.completions.create(
model='gpt-4o',
messages=messages,
tools=tools,
#こちら側で関数を実行してあげよう
available_functions = {
'get_current_weather': get_current_weather,
}
for tool_call in response_message.tool_calls:
function_name = tool_call.function.name
function_to_call = available_functions[function_name]
function_args = json.loads(tool_call.function.arguments)
function_response = function_to_call(
location=function_args.get('location'),
unit=function_args.get('unit'),
)
messages.append(
{
'tool_call_id': tool_call.id,
'role': 'tool',
'name': function_name,
'content': function_response,
}
)
print(function_response)
#もう一度リクエストを送ってみよう
second_response = client.chat.completions.create(
model='gpt-4o',
messages=messages,
)
print(second_response.to_json(indent=2))
まとめ
今回はFunction callingの基礎を解説していきました。
次回はcurlでopenaiのAPIを動かしてみます!
お疲れ様でした!