皆さん、こんにちは。サイオステクノロジー武井です。今回は、生成AIの発展形であり、与えられた課題に対して自分で思考し、行動を決定し、実行する「AIエージェント」について、一筆したためたいと思います。
AIエージェントとは?
トム・クルーズの誕生日にケーキをプレゼントしたいです。彼の年齢分のろうそくを購入するための金額を計算してください。ろうそくは一本100円です。
AIエージェントを使わない場合と使った場合との比較
AIエージェントを実現するプロンプトエンジニアリング「ReAct」
- まず、10個のりんごを5人で割り算します。
- 10 ÷ 5 = 2 です。
- それぞれ2個ずつ持つことになります。
ReActの手法
質問
トム・クルーズの誕生日にケーキをプレゼントしたいです。彼の年齢分のろうそくを購入するための金額を計算してください。ろうそくは一本100円です。
思考 (1回目)
行動 (1回目)
思考 (2回目)
行動 (2回目)
思考 (3回目)
最終回答
Reactを実現するためのプロンプト
次の質問にできる限り答えてください。次のツールにアクセスできます。– 検索ツール:検索するときに使います– 計算ツール:計算するときに使います。次のフォーマットを使用します。質問:回答する必要がある入力質問思考:次に何をすべきかを常に考える行動:実行するアクションは、「計算ツール」「検索ツール」のいずれかである必要があります。行動の入力:アクションへの入力観察:行動の結果…(この思考/行動/行動の入力/観察はN回繰り返すことができます)最終的な答えがわかったら以下を出力します。思考:今、最終的な答えが分かりました最終回答:元の入力質問に対する最終回答計算ツールを使うときの行動の入力は、計算式を入力してください。例:52 * 100例:100 – 1質問:{question}さぁ始めましょう。思考:
次の質問にできる限り答えてください。次のツールにアクセスできます。– 検索ツール:検索するときに使います– 計算ツール:計算するときに使います。
次のフォーマットを使用します。質問:回答する必要がある入力質問思考:次に何をすべきかを常に考える行動:実行するアクションは、「計算ツール」「検索ツール」のいずれかである必要があります。行動の入力:アクションへの入力観察:行動の結果…(この思考/行動/行動の入力/観察はN回繰り返すことができます)最終的な答えがわかったら以下を出力します。思考:今、最終的な答えが分かりました最終回答:元の入力質問に対する最終回答
計算ツールを使うときの行動の入力は、計算式を入力してください。例:52 * 100例:100 – 1
質問:トム・クルーズの誕生日にケーキをプレゼントしたいです。彼の年齢分のろうそくを購入するための金額を計算してください。ろうそくは一本100円です。さぁ始めましょう。思考:
行動(1回目)の結果
行動(2回目)の結果
ReActの実装
from openai import AzureOpenAI
from duckduckgo_search import DDGS
# OpenAI APIの初期化
aoai_endpoint = "https://hogehoge.openai.azure.com/"
aoai_api_version = "2024-06-01"
aoai_api_key = "XXXXXX"
aoai_embedding_model_name = "text-embedding-ada-002-deploy"
aoai_chat_model_name = "gpt-4-deploy"
template = """
次の質問にできる限り答えてください。次のツールにアクセスできます。
- 検索ツール:検索するときに使います
- 計算ツール:計算するときに使います。
次のフォーマットを使用します。
質問:回答する必要がある入力質問
思考:次に何をすべきかを常に考える
行動:実行するアクションは、「計算ツール」「検索ツール」のいずれかである必要があります。
行動の入力:アクションへの入力
観察:行動の結果
...(この思考/行動/行動の入力/観察はN回繰り返すことができます)
最終的な答えがわかったら以下を出力します。
思考:今、最終的な答えが分かりました
最終回答:元の入力質問に対する最終回答
計算ツールを使うときの行動の入力は、計算式を入力してください。
例:52 * 100
例:100 - 1
質問:{question}
さぁ始めましょう。
思考:
"""
question = "トム・クルーズの誕生日にケーキをプレゼントしたいです。彼の年齢分のろうそくを購入するための金額を計算してください。ろうそくは一本本100円です。"
message = template.format(question=question)
final_observation = ""
while True:
# 推論と行動のプロセス
openai_client = AzureOpenAI(azure_endpoint=aoai_endpoint, api_key=aoai_api_key, api_version = aoai_api_version)
response = openai_client.chat.completions.create(
model=aoai_chat_model_name,
messages=[
{"role": "assistant", "content": message},
],
stop=["観察:"]
)
result = response.choices[0].message.content.strip()
# resultの変数の最後の行に、つまりresultの変数を改行で分割した配列の最後の要素に「最終回答:」がある場合、終了する。
if "最終回答:" in result.split("\n")[-1]:
final_observation = result.split("最終回答:")[1].strip()
break
# resultの"行動"と"行動の入力"を取得し、それにあったツールを実行する。
if "行動" in result:
action = result.split("行動の入力:")[0].split("行動:")[1].strip()
action_input = result.split("行動の入力:")[1].strip()
observation = ""
if action in "検索ツール":
# 検索ツール実行
search_results = DDGS().text(action_input, max_results=5)
for search_result in search_results:
observation = observation + search_result["body"] + "\n"
elif action in "計算ツール":
# 計算ツール実行
observation = str(eval(action_input))
message = message + "\n行動:" + action + "\n行動の入力:" + action_input + "\n観察:" + observation + "\n思考:"
print(message)
print("\n最終的な観察:")
print(final_observation)
pip install openai duckduckgo-search
Azure OpenAPI Serviceの初期化
# OpenAI APIの初期化
aoai_endpoint = "https://hogehoge.openai.azure.com/"
aoai_api_version = "2024-06-01"
aoai_api_key = "XXXXXX"
aoai_embedding_model_name = "text-embedding-ada-002-deploy"
aoai_chat_model_name = "gpt-4-deploy"
プロンプトのテンプレートの設定
template = """
次の質問にできる限り答えてください。次のツールにアクセスできます。
- 検索ツール:検索するときに使います
- 計算ツール:計算するときに使います。
次のフォーマットを使用します。
質問:回答する必要がある入力質問
...(省略)...
質問:{question}
さぁ始めましょう。
思考:
"""
question = "トム・クルーズの誕生日にケーキをプレゼントしたいです。彼の年齢分のろうそくを購入するための金額を計算してください。ろうそくは一本本100円です。"
message = template.format(question=question)
回答の取得
final_observation = ""
while True:
# 推論と行動のプロセス
openai_client = AzureOpenAI(azure_endpoint=aoai_endpoint, api_key=aoai_api_key, api_version = aoai_api_version)
response = openai_client.chat.completions.create(
model=aoai_chat_model_name,
messages=[
{"role": "assistant", "content": message},
],
stop=["観察:"]
)
result = response.choices[0].message.content.strip()
最終回答のチェック
if "最終回答:" in result.split("\n")[-1]:
final_observation = result.split("最終回答:")[1].strip()
break
ツールの実行
if "行動" in result:
action = result.split("行動の入力:")[0].split("行動:")[1].strip()
action_input = result.split("行動の入力:")[1].strip()
observation = ""
if action in "検索ツール":
# 検索ツール実行
search_results = DDGS().text(action_input, max_results=5)
for search_result in search_results:
observation = observation + search_result["body"] + "\n"
elif action in "計算ツール":
# 計算ツール実行
observation = str(eval(action_input))
message = message + "\n行動:" + action + "\n行動の入力:" + action_input + "\n観察:" + observation + "\n思考:"
最終的な観察結果の表示
print("\n最終的な観察:")
print(final_observation)
Function calling
Function callingのプロンプト
Function callingのプロンプトを見てみます。
{
"messages": [
{
"role": "system",
"content": "あなたは役立つアシスタントです。"
},
{
"role": "user",
"content": "トム・クルーズの誕生日にケーキをプレゼントしたいです。彼の年齢分のろうそくを購入するための金額を計算してください。ろうそくは一本100円です。"
}
],
"functions": [ ⋯ ①
{
"name": "search_web", ⋯ ②
"description": "指定されたクエリでWebを検索します。", ⋯ ③
"parameters": { ⋯ ④
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "検索するクエリ"
}
},
"required": [
"query"
]
}
},
{
"name": "calculate",
"description": "与えられた計算式に基づき計算を行います。",
"parameters": {
"type": "object",
"properties": {
"formula": {
"type": "string",
"description": "計算式(例: 52 * 100、100 - 1)"
}
},
"required": [
"formula"
]
}
}
],
"function_call": "auto"
}
①のfuntionsは、関数を定義するフィールドです。ここに関数を定義してきます。
②のnameは、関数名を指定します。
③のdescriptionは、関数の説明を指定します。この内容は超重要で、ユーザーの質問とこの関数の説明を比較して、LLMは関数の実施を決定します。
④のparametersは、関数に与える引数になります。関数の引数はJSONの形で与えられ、propertiesフィールドで定義した内容がそのまま引数になります。例えば、上記の内容であれば、{ “query”: “検索するクエリ”}という形式で引数が与えられます。
そして、以下がプロンプトに対するレスポンスになります。
{
"choices": [
{
"content_filter_results": {},
"finish_reason": "function_call",
"index": 0,
"logprobs": null,
"message": {
"content": null, ⋯ ①
"function_call": { ⋯ ②
"arguments": "{\n \"query\": \"トム・クルーズの年齢\"\n}",
"name": "search_web"
},
"role": "assistant"
}
}
],
…(省略)…
}
①の「content」フィールドには、本来質問に対する回答が返ってくるはずですが、Function callingで実施すべき関数がある場合は、nullとなります。
②では、そのかわりにfunction_callというフィールドが登場し、そこのargumentsに関数の引数、nameに実施すべき関数名が入っています。
これらの情報を見て、実施すべき関数を決定します。
シンプルなFunction callingの実装
import requests
import json
from duckduckgo_search import DDGS
# Azure OpenAI Serviceの設定
AOAI_ENDPOINT = "https://hogehoge.openai.azure.com/" # Azure OpenAI Serviceのエンドポイント
AOAI_API_VERSION = "2024-06-01" # Azure OpenAI ServiceのAPIバージョン
AOAI_API_KEY = "XXXXXXX" # Azure OpenAI ServiceのAPIキー
AOAI_CHAT_MODEL_NAME = "gpt-4-deploy" # Azure OpenAI Serviceのデプロイモデル名
# リクエストURL
url = f"{AOAI_ENDPOINT}openai/deployments/{AOAI_CHAT_MODEL_NAME}/chat/completions?api-version={AOAI_API_VERSION}"
# リクエストヘッダー
headers = {
"Content-Type": "application/json",
"api-key": AOAI_API_KEY
}
# 最初のリクエストデータ: Web検索関数を自動呼び出し
data = {
"messages": [
{"role": "system", "content": "あなたは役立つアシスタントです。"},
{"role": "user", "content": "トム・クルーズの現在の年齢を調べて。"}
],
"functions": [
{
"name": "search_web",
"description": "指定されたクエリでWebを検索します。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "検索するクエリ"
}
},
"required": ["query"]
}
},
{
"name": "calculate",
"description": "与えられた計算式に基づき計算を行います。",
"parameters": {
"type": "object",
"properties": {
"formula": {
"type": "string",
"description": "計算式(例: 52 * 100、100 - 1)"
}
},
"required": ["formula"]
}
}
]
}
# 指定されたクエリでWebを検索する関数
def search_web(query: str):
final_result = ""
search_results = DDGS().text(query, max_results=10)
for search_result in search_results:
final_result = final_result + search_result["body"] + "\n"
return final_result
# 与えられた計算式に基づき計算を行う関数
def calculate(formula: str):
return eval(formula)
# リクエストを送信
response = requests.post(url, headers=headers, data=json.dumps(data))
# 結果を出力
result = response.json()
# 関数を実行する。
message = result["choices"][0]["message"]["function_call"]
function_name = message["name"]
arguments = json.loads(message["arguments"]) # 文字列を辞書に変換
print(f"実行された関数: {function_name}")
print(f"関数に渡された引数: {arguments}")
func = globals()[function_name]
result = func(**arguments)
print(f"関数の実行結果: {result}")
Azure OpenAI Serviceの設定
# Azure OpenAI Serviceの設定
AOAI_ENDPOINT = "https://hogehoge.openai.azure.com/" # Azure OpenAI Serviceのエンドポイント
AOAI_API_VERSION = "2024-06-01" # Azure OpenAI ServiceのAPIバージョン
AOAI_API_KEY = "XXXXXXX" # Azure OpenAI ServiceのAPIキー
AOAI_CHAT_MODEL_NAME = "gpt-4-deploy" # Azure OpenAI Serviceのデプロイモデル名
リクエストURL
url = f"{AOAI_ENDPOINT}openai/deployments/{AOAI_CHAT_MODEL_NAME}/chat/completions?api-version={AOAI_API_VERSION}"
リクエストヘッダー
# リクエストヘッダー
headers = {
"Content-Type": "application/json",
"api-key": AOAI_API_KEY
}
メッセージの定義
# 最初のリクエストデータ: Web検索関数を自動呼び出し
data = {
"messages": [
{"role": "system", "content": "あなたは役立つアシスタントです。"},
{"role": "user", "content": "トム・クルーズの現在の年齢を調べて。"}
],
関数の定義
"functions": [
{
"name": "search_web",
"description": "指定されたクエリでWebを検索します。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "検索するクエリ"
}
},
"required": ["query"]
}
},
{
"name": "calculate",
"description": "与えられた計算式に基づき計算を行います。",
"parameters": {
"type": "object",
"properties": {
"formula": {
"type": "string",
"description": "計算式(例: 52 * 100、100 - 1)"
}
},
"required": ["formula"]
}
}
]
指定されたクエリでWebを検索する関数
# 指定されたクエリでWebを検索する関数
def search_web(query: str):
final_result = ""
search_results = DDGS().text(query, max_results=10)
for search_result in search_results:
final_result = final_result + search_result["body"] + "\n"
return final_result
与えられた計算式に基づき計算を行う関数
# 与えられた計算式に基づき計算を行う関数
def calculate(formula: str):
return eval(formula)
リクエストの送信
# リクエストを送信
response = requests.post(url, headers=headers, data=json.dumps(data))
# 結果を出力
result = response.json()
関数の実行
# 関数を実行する。
message = result["choices"][0]["message"]["function_call"]
function_name = message["name"]
arguments = json.loads(message["arguments"]) # 文字列を辞書に変換
print(f"実行された関数: {function_name}")
print(f"関数に渡された引数: {arguments}")
func = globals()[function_name]
result = func(**arguments)
print(f"関数の実行結果: {result}")
実行された関数: search_web
関数に渡された引数: {'query': 'トム・クルーズ現在の年齢'}
関数の実行結果: トム・クルーズのサイエントロジーに対する開けた態度は、...(以下略)...
実行された関数: calculate
関数に渡された引数: {'formula': '52 * 100'}
関数の実行結果: 5200
Function callingでAIエージェント
処理の流れ
Function callingによるAIエージェントは、何回もLLMにリクエストを送ります。最終結果がでていないとLLMが判断した場合は、実行すべき関数をレスポンスとして返し、アプリケーションはその関数に対応したアプリケーションの実装を実行します。LLMが最終結果がでたと判断したら処理は終了です。「思考」と「行動」を繰り返すReActと同じ流れです。
1回目の処理 (概念図)
トム・クルーズの誕生日にケーキをプレゼントしたいです。彼の年齢分のろうそくを購入するための金額を計算してください。ろうそくは一本本100円です。
1回目の処理 (プロンプト)
{
"messages": [
{
"role": "system",
"content": "あなたは役立つアシスタントです。"
},
{
"role": "user",
"content": "トム・クルーズの誕生日にケーキをプレゼントしたいです。彼の年齢分のろうそくを購入するための金額を計算してください。ろうそくは一本100円です。"
}
],
"functions": [ ⋯ ①
{
"name": "search_web", ⋯ ②
"description": "指定されたクエリでWebを検索します。", ⋯ ③
"parameters": { ⋯ ④
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "検索するクエリ"
}
},
"required": [
"query"
]
}
},
{
"name": "calculate",
"description": "与えられた計算式に基づき計算を行います。",
"parameters": {
"type": "object",
"properties": {
"formula": {
"type": "string",
"description": "計算式(例: 52 * 100、100 - 1)"
}
},
"required": [
"formula"
]
}
}
],
"function_call": "auto"
}
①のfuntionsは、関数を定義するフィールドです。ここに関数を定義してきます。
②のnameは、関数名を指定します。
③のdescriptionは、関数の説明を指定します。この内容は超重要で、ユーザーの質問とこの関数の説明を比較して、LLMは関数の実施を決定します。
④のparametersは、関数に与える引数になります。関数の引数はJSONの形で与えられ、propertiesフィールドで定義した内容がそのまま引数になります。例えば、上記の内容であれば、{ “query”: “検索するクエリ”}という形式で引数が与えられます。
1回目の処理 (レスポンス)
{
"choices": [
{
"content_filter_results": {},
"finish_reason": "function_call",
"index": 0,
"logprobs": null,
"message": {
"content": null, ⋯ ①
"function_call": { ⋯ ②
"arguments": "{\n \"query\": \"トム・クルーズの年齢\"\n}",
"name": "search_web"
},
"role": "assistant"
}
}
],
…(省略)…
}
①の「content」フィールドには、本来質問に対する回答が返ってくるはずですが、Function callingで実施すべき関数がある場合は、nullとなります。
②では、そのかわりにfunction_callというフィールドが登場し、そこのargumentsに関数の引数、nameに実施すべき関数名が入っています。
ここでは、LLMはプロンプトにて与えられた質問と関数の内容から判断して、まずはトム・クルーズの年齢をsearch_web(インターネットを検索する関数)を使って調べて、その後に計算をする関数(calculate)を使って、トムの年齢とろうそく1本あたりの値段をかけるという推論をしました。その結果、まず一発目に実施する関数はsearch_webであり、その引数は「トム・クルーズの年齢」というレスポンスを返したという結果になります。
2回目の処理 (概念図)
最初に段取りした「トム・クルーズの年齢分のろうそくの合計金額を計算するためには、まずWebを検索して、トム・クルーズの年齢を調べて、その後に計算を行う必要がある」という流れのうち、最初の段取りである「トム・クルーズの年齢を調べる」はすでに完了しているんだな。では、その実行結果を見て、トム・クルーズの現在の年齢を割り出して、次にろうそくの合計金額の計算を行おう。
2回目の処理 (プロンプト)
{
"messages": [
{
"role": "system",
"content": "あなたは役立つアシスタントです。"
},
{
"role": "user",
"content": "トム・クルーズの誕生日にケーキをプレゼントしたいです。彼の年齢分のろうそくを購入するための金額を計算してください。ろうそくは一本100円です。"
},
{ ⋯ ①
"content": null,
"function_call": {
"arguments": "{\n \"query\": \"トム・クルーズの年齢\"\n}",
"name": "search_web"
},
"role": "assistant"
},
{ ⋯ ②
"role": "function",
"name": "search_web",
“content”: “トム・クルーズ. トム・クルーズ (英: Tom Cruise, 1962年 7月3日 - )、…(以下省略)… ],
}
],
"functions": [
{
"name": "search_web",
"description": "指定されたクエリでWebを検索します。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "検索するクエリ"
}
},
"required": [
"query"
]
}
},
…(以下省略)…
],
"function_call": "auto"
}
2回目の処理 (レスポンス)
{
"choices": [
{
"content_filter_results": {},
"finish_reason": "function_call",
"index": 0,
"logprobs": null,
"message": {
"content": null, ⋯ ①
"function_call": { ⋯ ②
"arguments": "{\n \"formula\": \"62 * 100\"\n}",
"name": "calculate"
},
"role": "assistant"
}
}
],
…(省略)…
}
①の「content」フィールドには、本来質問に対する回答が返ってくるはずですが、Function callingで実施すべき関数がある場合は、nullとなります。
②では、そのかわりにfunction_callというフィールドが登場し、そこのargumentsに関数の引数、nameに実施すべき関数名が入っています。
ここでは、先程のプロンプト内にあったsearch_webの実行結果(トム・クルーズ. トム・クルーズ (英: Tom Cruise, 1962年 7月3日 – )、…(以下省略)… )を受けて、LLMはトム・クルーズの年齢は62歳だと判別し、次に実行するべき処理は、年齢 ✕ ろうそく一本あたりの値段だと思考し、その内容のfunction_callを返しています。
3回目の処理 (概念図)
3回目の処理 (プロンプト)
{
"messages": [
{
"role": "system",
"content": "あなたは役立つアシスタントです。"
},
{
"role": "user",
"content": "トム・クルーズの誕生日にケーキをプレゼントしたいです。彼の年齢分のろうそくを購入するための金額を計算してください。ろうそくは一本100円です。"
},
{
"content": null,
"function_call": {
"arguments": "{\n \"query\": \"トム・クルーズの年齢\"\n}",
"name": "search_web"
},
"role": "assistant"
},
{
"role": "function",
"name": "search_web",
“content”: “トム・クルーズ. トム・クルーズ (英: Tom Cruise, 1962年 7月3日 - )、…(以下省略)… ],
},
{ ⋯ ①
"content": null,
"function_call": {
"arguments": "{\n \"formula\": \"62 * 100\"\n}",
"name": "calculate"
},
"role": "assistant"
},
{ ⋯ ②
"role": "function",
"name": "calculate",
"content": "6000"
}
],
"functions": [
…(以下省略)…
],
"function_call": "auto"
}
3回目の処理 (レスポンス)
{
"choices": [
{
…(省略)…
"message": {
"content": "トム・クルーズは62歳なので、彼の年齢分のろうそくを購入するためには6200円が必要です。", ⋯ ①
"role": "assistant"
}
}
],
…(省略)…
}
Function callingによるAIエージェントの実装
import requests
import json
from duckduckgo_search import DDGS
# Azure OpenAI Serviceの設定
AOAI_ENDPOINT = "https://hogehoge.openai.azure.com/" # Azure OpenAI Serviceのエンドポイント
AOAI_API_VERSION = "2024-06-01" # Azure OpenAI ServiceのAPIバージョン
AOAI_API_KEY = "XXXXXXXX" # Azure OpenAI ServiceのAPIキー
AOAI_CHAT_MODEL_NAME = "gpt-4-deploy" # Azure OpenAI Serviceのデプロイモデル名
# リクエストURL
url = f"{AOAI_ENDPOINT}openai/deployments/{AOAI_CHAT_MODEL_NAME}/chat/completions?api-version={AOAI_API_VERSION}"
# リクエストヘッダー
headers = {
"Content-Type": "application/json",
"api-key": AOAI_API_KEY
}
# 初期のメッセージ
messages = [
{"role": "system", "content": "あなたは役立つアシスタントです。"},
{"role": "user", "content": "トム・クルーズの誕生日にケーキをプレゼントしたいです。彼の年齢分のろうそくを購入するための金額を計算してください。ろうそくは一本本100円です。"}
]
# 関数の定義
functions = [
{
"name": "search_web",
"description": "指定されたクエリでWebを検索します。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "検索するクエリ"
}
},
"required": ["query"]
}
},
{
"name": "calculate",
"description": "与えられた計算式に基づき計算を行います。",
"parameters": {
"type": "object",
"properties": {
"formula": {
"type": "string",
"description": "計算式(例: 52 * 100、100 - 1)"
}
},
"required": ["formula"]
}
}
]
# 指定されたクエリでWebを検索する関数
def search_web(query: str):
final_result = ""
search_results = DDGS().text(query, max_results=10)
for search_result in search_results:
final_result = final_result + search_result["body"] + "\n"
return final_result
# 与えられた計算式に基づき計算を行う関数
def calculate(formula: str):
return str(eval(formula))
# メインループ
while True:
# リクエストデータの準備
data = {
"messages": messages,
"functions": functions,
"function_call": "auto"
}
# リクエストを送信
response = requests.post(url, headers=headers, data=json.dumps(data))
result = response.json()
# アシスタントの返信を取得
assistant_message = result["choices"][0]["message"]
messages.append(assistant_message) # アシスタントのメッセージを履歴に追加
# アシスタントが関数を呼び出したか確認
if "function_call" in assistant_message:
function_call = assistant_message["function_call"]
function_name = function_call["name"]
arguments = json.loads(function_call["arguments"]) # 文字列を辞書に変換
# 関数を実行
func = globals()[function_name]
function_response = func(**arguments)
print(f"実行された関数: {function_name}")
print(f"関数に渡された引数: {arguments}")
print(f"関数の実行結果: {function_response}")
# 関数の応答をメッセージに追加
messages.append({
"role": "function",
"name": function_name,
"content": function_response
})
else:
# 最終的な回答が得られた場合
final_observation = assistant_message["content"]
print(f"最終回答: {final_observation}")
break # ループを終了
メッセージの定義
messages = [
{"role": "system", "content": "あなたは役立つアシスタントです。"},
{"role": "user", "content": "トム・クルーズの誕生日にケーキをプレゼントしたいです。彼の年齢分のろうそくを購入するための金額を計算してください。ろうそくは一本本100円です。"}
]
関数の定義
functions = [
{
"name": "search_web",
"description": "指定されたクエリでWebを検索します。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "検索するクエリ"
}
},
"required": ["query"]
}
},
{
"name": "calculate",
"description": "与えられた計算式に基づき計算を行います。",
"parameters": {
"type": "object",
"properties": {
"formula": {
"type": "string",
"description": "計算式(例: 52 * 100、100 - 1)"
}
},
"required": ["formula"]
}
}
]
関数の実装
# 指定されたクエリでWebを検索する関数
def search_web(query: str):
final_result = ""
search_results = DDGS().text(query, max_results=10)
for search_result in search_results:
final_result = final_result + search_result["body"] + "\n"
return final_result
# 与えられた計算式に基づき計算を行う関数
def calculate(formula: str):
return str(eval(formula))
メインループ
# メインループ
while True:
# リクエストデータの準備
data = {
"messages": messages,
"functions": functions,
"function_call": "auto"
}
リクエストの送信
# リクエストを送信
response = requests.post(url, headers=headers, data=json.dumps(data))
result = response.json()
アシスタントの返信を取得
# アシスタントの返信を取得
assistant_message = result["choices"][0]["message"]
messages.append(assistant_message) # アシスタントのメッセージを履歴に追加
関数の実行
# アシスタントが関数を呼び出したか確認
if "function_call" in assistant_message:
function_call = assistant_message["function_call"]
function_name = function_call["name"]
arguments = json.loads(function_call["arguments"]) # 文字列を辞書に変換
# 関数を実行
func = globals()[function_name]
function_response = func(**arguments)
print(f"実行された関数: {function_name}")
print(f"関数に渡された引数: {arguments}")
print(f"関数の実行結果: {function_response}")
# 関数の応答をメッセージに追加
messages.append({
"role": "function",
"name": function_name,
"content": function_response
})
else:
# 最終的な回答が得られた場合
final_observation = assistant_message["content"]
print(f"最終回答: {final_observation}")
break # ループを終了
{
"content": None,
"function_call": {
"name": "search_web",
"arguments": "{\"query\": \"トム・クルーズの年齢\"}"
},
"role": "assistant"
}
{
"content": "トム・クルーズは現在60歳なので、彼の年齢分のろうそくを購入するには6000円必要です。",
"role": "assistant"
}
実行された関数: search_web
関数に渡された引数: {'query': 'トム・クルーズの年齢'}
関数の実行結果: トム・クルーズのサイエントロジーに対する開けた態度は、...(以下略)...
実行された関数: calculate
関数に渡された引数: {'formula': '60 * 100'}
関数の実行結果: 6000
最終回答: トム・クルーズの年齢は60歳なので、彼の年齢分のろうそくを購入するためには6000円が必要です。
LangChainでAIエージェント
from langchain_openai import AzureChatOpenAI
from langchain.agents import tool
from langchain_community.tools import DuckDuckGoSearchRun
from langchain.callbacks import StdOutCallbackHandler
from langchain_core.prompts import MessagesPlaceholder, ChatPromptTemplate
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain.memory import ConversationBufferWindowMemory
# Azure OpenAI Serviceの設定
AOAI_ENDPOINT = "https://hogehoge.openai.azure.com/" # Azure OpenAI Serviceのエンドポイント
AOAI_API_VERSION = "2024-06-01" # Azure OpenAI ServiceのAPIバージョン
AOAI_API_KEY = "XXXXXXX" # Azure OpenAI ServiceのAPIキー
AOAI_CHAT_MODEL_NAME = "gpt-4-deploy" # Azure OpenAI Serviceのデプロイモデル名
# LLMの初期化
llm = AzureChatOpenAI(
azure_endpoint=AOAI_ENDPOINT,
api_key=AOAI_API_KEY,
api_version=AOAI_API_VERSION,
openai_api_type="azure",
azure_deployment=AOAI_CHAT_MODEL_NAME)
# 指定されたクエリでWebを検索する関数
@tool
def search_web(query: str):
"""
指定されたクエリでWebを検索します。
"""
search = DuckDuckGoSearchRun()
return search.run(query)
# 与えられた計算式に基づき計算を行う関数
@tool
def calculate(formula: str):
"""
与えられた計算式に基づき計算を行います。
"""
return str(eval(formula))
tools = [search_web, calculate]
prompt = ChatPromptTemplate.from_messages([
("system", "あなたは役立つアシスタントです。"),
MessagesPlaceholder(variable_name="chat_history"),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad")
])
agent = create_tool_calling_agent(llm, tools, prompt)
exucutor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
memory=ConversationBufferWindowMemory(
return_messages=True,
memory_key="chat_history",
k=10
)
)
exucutor.invoke(
{"input": "トム・クルーズの誕生日にケーキをプレゼントしたいです。彼の年齢分のろうそくを購入するための金額を計算してください。ろうそくは一本本100円です。"},
callback_handler=StdOutCallbackHandler()
)
Azure OpenAI Serviceの設定
# Azure OpenAI Serviceの設定
AOAI_ENDPOINT = "https://hogehoge.openai.azure.com/" # Azure OpenAI Serviceのエンドポイント
AOAI_API_VERSION = "2024-06-01" # Azure OpenAI ServiceのAPIバージョン
AOAI_API_KEY = "XXXXXXX" # Azure OpenAI ServiceのAPIキー
AOAI_CHAT_MODEL_NAME = "gpt-4-deploy" # Azure OpenAI Serviceのデプロイモデル名
LLMの初期化
# LLMの初期化
llm = AzureChatOpenAI(
azure_endpoint=AOAI_ENDPOINT,
api_key=AOAI_API_KEY,
api_version=AOAI_API_VERSION,
openai_api_type="azure",
azure_deployment=AOAI_CHAT_MODEL_NAME)
指定されたクエリでWebを検索する関数
# 指定されたクエリでWebを検索する関数
@tool
def search_web(query: str):
"""
指定されたクエリでWebを検索します。
"""
search = DuckDuckGoSearchRun()
return search.run(query)
与えられた計算式に基づき計算を行う関数
# 与えられた計算式に基づき計算を行う関数
@tool
def calculate(formula: str):
"""
与えられた計算式に基づき計算を行います。
"""
return str(eval(formula))
関数のリスト化
tools = [search_web, calculate]
プロンプトの定義
prompt = ChatPromptTemplate.from_messages([
("system", "あなたは役立つアシスタントです。"),
MessagesPlaceholder(variable_name="chat_history"),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad")
])
エージェントの作成
agent = create_tool_calling_agent(llm, tools, prompt)
AgentExcecutorの定義
exucutor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
memory=ConversationBufferWindowMemory(
return_messages=True,
memory_key="chat_history",
k=10
)
)
エージェントの実行
exucutor.invoke(
{"input": "トム・クルーズの誕生日にケーキをプレゼントしたいです。彼の年齢分のろうそくを購入するための金額を計算してください。ろうそくは一本本100円です。"},
callback_handler=StdOutCallbackHandler()
)
> Entering new AgentExecutor chain...
まず、トム・クルーズの現在の年齢を知る必要があります。それを知るためには、インターネットで彼の誕生日を調べる必要があります。
Action: duckduckgo-search
Action Input: 'Tom Cruise birth date'Tom Cruise. Thomas Cruise Mapother IV (born July 3, 1962) is an ...(以下略)...
Action: duckduckgo-search
Action Input: 'today's date'Details about today's date with count of days, weeks, and months, Sun and Moon cycles, Zodiac signs and holidays. ...(以下略)...
Action: duckduckgo-search
Action Input: 'current date'Details about today's date with count of days, weeks, and months, Sun and Moon cycles, Zodiac signs and holidays. Tuesday September 24, 2024 . ...(以下略)...
Action: Calculator
Action Input: {'operation': 'subtract', 'operands': [2024, 1962]}Answer: 62トム・クルーズは今年で62歳になります。次に、ろうそくの価格とトム・クルーズの年齢を掛けることで、必要なろうそくの総額を計算します。
Action: Calculator
Action Input: {'operation': 'multiply', 'operands': [62, 100]}Answer: 6200トム・クルーズの年齢分のろうそくを購入するためには、6200円が必要です。
Final Answer: 6200円が必要です。