Raspberry PiとChatGPTでつくるボイス・アシスタント・ロボット #10
ロボットのAgentsが各Toolを選択するイメージ
LangChain
LangChainは、大規模言語モデル(LLM)を活用してアプリケーションを開発するためのフレームワークで、データ認識や判断を行う2つの主要な機能を提供します。主な特徴として、言語モデルの制御を簡素化する「コンポーネント」や特定のタスクを実行するための「チェーン」が含まれており、これにより、アプリケーションの開発とカスタマイズが容易になります。
エージェント(Agents)
LangChainでは、アプリケーションに「エージェント[1](Agents)」が存在し、ユーザーの入力に応じて異なるツールを呼び出すことができます。エージェントは複数のツールへの一連の呼び出しをあらかじめ決定するだけでなく、ユーザーの入力に依存する道のチェーンを構築する際に役立ちます。
OpenAI functions
エージェントはLLMを使用して、どのアクションをどの順番で実行するか決定します。いくつかのエージェントタイプ(Agent types)が用意されており、その中にはChatGPTモデルに特化したエージェントタイプであるOpenAI Functions
[2]が存在します。
OpenAI Functions Agent
は、特定のOpenAIモデル(gpt-3.5-turbo-0613やgpt-4-0613など)を使用し、Function calling
(関数呼び出し)を検出し、関数に渡す引数を含むJSONオブジェクトを出力するために設計されています。これにより、その他の機能よりも信頼性の高い関数呼び出しを実現し、GPTの能力を外部ツールやAPIと連携させる新しい方法が提供されます。
カスタムツール(Custom Tools)
ツール(Tools)はエージェントが現実世界と対話するために使用できる機能で、検索をはじめとした汎用ツールが利用できます。
ツールはカスタムツールを定義[3][4]することが出来ます。今回は、簡単な関数からツールを定義することが出来る、ツールデコレーター(tool decorator)を使用しました。
実験ロボットの操作には、以下のモデルとエージェントタイプを使用しました。
- モデル:
gpt-3.5-turbo-0613
- テキスト生成機能:
ChatOpenAI
- エージェントタイプ:
OpenAIFunctionsAgent
- カスタムツール: ツールデコレーター(
@tool
)
日時を取得するカスタムツールの実装
前章で作成した、日時を取得するを関数をエージェントに行わせます。.env
の読み込みは省略します。プログラムの実行内容は以下のステップで行われます。
前章のFunction calling
の機能を、エージェントが内部的に行っていることを確認してください。
# ライブラリのインポート ---(※1)
import openai, os, json, dotenv, datetime
# LagnChainのチャットモデルをOpenAiと指定してインポートする
from langchain.chat_models import ChatOpenAI
# LangChainのシステムメッセージを定義するライブラリをインポートする
from langchain.schema import SystemMessage
# エージェント カスタムプロンプト作成 のライブラリインポート
from langchain.agents import OpenAIFunctionsAgent
# エージェント ツールモジュール のライブラリインポート
from langchain.agents import tool
# エージェントのランタイムを作成するライブラリインポート
from langchain.agents import AgentExecutor
# プロンプトに記憶用の場所を追加するライブラリ
from langchain.prompts import MessagesPlaceholder
# メモリー 全ての会話履歴を保持する ライブラリインポート
from langchain.memory import ConversationBufferMemory
# .envファイルから環境変数をロード ---(※2)
dotenv.load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
# エージェントの制御に使用する言語モデルをロード ---(※3)
llm = ChatOpenAI(model_name="gpt-3.5-turbo-0613", temperature=0)
# 現在時刻を取得するツールを定義 ---(※4)
@tool
def get_date_time() -> json:
"""datetime関数をつかい、「現在時刻」「今日の日付」を返します"""
day_now = datetime.datetime.today().strftime("%-Y年%-m月%-d日")
time_now = datetime.datetime.now().strftime("%-H時%-M分")
date_time_data = {
"day_now": day_now,
"time_now": time_now,
}
return json.dumps(date_time_data)
# 利用可能なツールを指定 ---(※5)
tools = [
get_date_time,
]
# プロンプトを作成 ヘルパー関数を使用して、OpenAIFunctionsAgent.create_promptプロンプトを自動的に作成 ---(※6)
system_message = SystemMessage(content="""
あなたは垂直方向と水平方向に移動するカメラを搭載した音声チャットロボットです。
名前は「ゆっくり霊夢」です。
""")
# プロンプトに記憶用の場所を追加 キーを使用してメッセージのプレースホルダーを追加 ---(※7)
MEMORY_KEY = "chat_history"
prompt = OpenAIFunctionsAgent.create_prompt(
system_message=system_message,
extra_prompt_messages=[MessagesPlaceholder(variable_name=MEMORY_KEY)]
)
# メモリオブジェクトを作成 ---(※8)
memory = ConversationBufferMemory(memory_key=MEMORY_KEY, return_messages=True)
def chat_with_agent(text):
result = None # 初期化
try:
# これらの部分を組み合わせ、エージェントを作成 ---(※9)
agent = OpenAIFunctionsAgent(llm=llm, tools=tools, prompt=prompt)
# エージェントのランタイムである AgentExecutor を作成 ---(※10)
agent_executor = AgentExecutor(agent=agent, tools=tools, memory=memory, verbose=True)
# ランタイムを実行 ---(※11)
result = agent_executor.run(text)
#print(str(result))
return str(result)
except Exception as e: # エラーハンドリング ---(※12)
print(f"SYSTEM: エラーが発生しました: {e}")
return None
if __name__ == "__main__":
print("🖥️. SYSTEM: チャットを開始します。終了するには '/exit' を入力してください。")
# ターミナルで連続して対話するループ ---(※13)
while True:
user_input = input("😀 USER: ")
if user_input == "/exit":
print("🖥️. SYSTEM: チャットを終了します。")
break
# ChatGPTによる応答を取得 ---(※14)
assistant_reply = chat_with_agent(user_input)
# ChatGPTの応答を表示
print("🤖 GPT: " + assistant_reply)
(※1)で必要なライブラリをインポートし、(※2)で.envファイルからChatGPTのAPIキーををロードします。ここでは読み込みにdotenvモジュールを使用します。
(※3)でエージェントの制御に使用するモデルをロードします。モデルはgpt-3.5-turbo-0613
、テキスト生成機能はChatOpenAI
で指定します。
(※4)で日時を取得する関数を定義します。ここではカスタムツールのツールデコレーター(@tool
)を使用して[5]関数を定義します。内部的にはFunction calling
を使っているので、関数の記述は前章のものと同じです。
(※5)で新たにパラメータを定義します。呼び出す関数をtools
で定義します。複数形で表されているように、このパラメータは複数の要素を入れた配列で定義され、エージェントはこの要素を選択します。
(※6)エージェントにシステムメッセージを設定するためのプロンプトを作成します。
(※7)プロンプトに記憶用の場所を追加 キーを使用してメッセージのプレースホルダーを追加し、ConversationBufferMemory
を使用し、メモリオブジェクトを作成します(※8)。
(※9)パラメータを指定し、エージェントを作成します。続いてエージェントのランタイムである AgentExecutor を作成(※10)し、(※11)で実行します。
(※12)〜(※14)はチャットテンプレートプログラム通りです。
実行結果は以下のようになります。エージェントがツールの使用を選択した場合「呼び出し(Invoking:
)」を行っていることを確認してください。
🖥️ SYSTEM: チャットを開始します。終了するには '/exit' を入力してください。
😀 USER: こんにちは
> Entering new AgentExecutor chain...
こんにちは!私はゆっくり霊夢です。どのようにお手伝いできますか?
> Finished chain.
🤖 GPT: こんにちは!私はゆっくり霊夢です。どのようにお手伝いできますか?
😀 USER: 今何時
> Entering new AgentExecutor chain...
Invoking: `get_date_time` with `{}`
{"day_now": "2023\u5e749\u67089\u65e5", "time_now": "10\u664218\u5206"}現在の時刻は10時18分です。
> Finished chain.
🤖 GPT: 現在の時刻は10時18分です。
😀 USER: /exit
🖥️ SYSTEM: チャットを終了します。
サーボモーター角度を取得するカスタムツールの実装
次に、前章で作成した、水平・垂直の角度を出力する関数をエージェントに行わせます。
複数のツールを指定する方法が分かるように、前節のサンプルプログラムに追記した内容で解説します。前章のFunction calling
の機能を、エージェントが内部的に行っていることを確認してください。
# ライブラリのインポート ---(※1)
import openai, os, json, dotenv, datetime
# LagnChainのチャットモデルをOpenAiと指定してインポートする
from langchain.chat_models import ChatOpenAI
# LangChainのシステムメッセージを定義するライブラリをインポートする
from langchain.schema import SystemMessage
# エージェント カスタムプロンプト作成 のライブラリインポート
from langchain.agents import OpenAIFunctionsAgent
# エージェント ツールモジュール のライブラリインポート
from langchain.agents import tool
# エージェントのランタイムを作成するライブラリインポート
from langchain.agents import AgentExecutor
# プロンプトに記憶用の場所を追加するライブラリ
from langchain.prompts import MessagesPlaceholder
# メモリー 全ての会話履歴を保持する ライブラリインポート
from langchain.memory import ConversationBufferMemory
# .envファイルから環境変数をロード ---(※2)
dotenv.load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
# エージェントの制御に使用する言語モデルをロード ---(※3)
llm = ChatOpenAI(model_name="gpt-3.5-turbo-0613", temperature=0)
# 現在時刻を取得するツールを定義 ---(※4)
@tool
def get_date_time() -> json:
"""datetime関数をつかい、「現在時刻」「今日の日付」を返します"""
day_now = datetime.datetime.today().strftime("%-Y年%-m月%-d日")
time_now = datetime.datetime.now().strftime("%-H時%-M分")
date_time_data = {
"day_now": day_now,
"time_now": time_now,
}
return json.dumps(date_time_data)
# pan、tilt角度を生成するツールを定義 ---(※5)
@tool
def turn_pan_tilt(pan, tilt):
"""
###目的###
テキストで方向を指示された場合
パラメータ "pan"(水平)、"tilt"(垂直)を数値化し、その値を返します
###数値化するパラメータ###
- "pan": -90 < pan < 90
- "tilt": -90 < tilt < 90
###出力の例###
Q: "右を向いて"
A: "pan": -90,"tilt": 0
Q: "左を向いて"
A: "pan": 90,"tilt": 0
Q: "上を向いて"
A: "pan": 0, "tilt": -90
Q: "下を向いて"
A: "pan": 0, "tilt": 90
Q: "右上を向いて"
A: "pan": -90, "tilt": -90
Q: 左下を向いて
A:
"""
turn_degree = {
"pan": pan,
"tilt": tilt,
}
return json.dumps(turn_degree)
# 利用可能なツールを指定 ---(※6)
tools = [
get_date_time,
turn_pan_tilt
]
# プロンプトを作成 ヘルパー関数を使用して、OpenAIFunctionsAgent.create_promptプロンプトを自動的に作成 ---(※7)
system_message = SystemMessage(content="""
あなたは垂直方向と水平方向に移動するカメラを搭載した音声チャットロボットです。
名前は「ゆっくり霊夢」です。
""")
# プロンプトに記憶用の場所を追加 キーを使用してメッセージのプレースホルダーを追加 ---(※8)
MEMORY_KEY = "chat_history"
prompt = OpenAIFunctionsAgent.create_prompt(
system_message=system_message,
extra_prompt_messages=[MessagesPlaceholder(variable_name=MEMORY_KEY)]
)
# メモリオブジェクトを作成 ---(※9)
memory = ConversationBufferMemory(memory_key=MEMORY_KEY, return_messages=True)
def chat_with_agent(text):
result = None # 初期化
try:
# これらの部分を組み合わせ、エージェントを作成 ---(※10)
agent = OpenAIFunctionsAgent(llm=llm, tools=tools, prompt=prompt)
# エージェントのランタイムである AgentExecutor を作成 ---(※11)
agent_executor = AgentExecutor(agent=agent, tools=tools, memory=memory, verbose=True)
# ランタイムを実行 ---(※12)
result = agent_executor.run(text)
#print(str(result))
return str(result)
except Exception as e: # エラーハンドリング ---(※13)
print(f"SYSTEM: エラーが発生しました: {e}")
return None
if __name__ == "__main__":
print("🖥️. SYSTEM: チャットを開始します。終了するには '/exit' を入力してください。")
# ターミナルで連続して対話するループ ---(※14)
while True:
user_input = input("😀 USER: ")
if user_input == "/exit":
print("🖥️. SYSTEM: チャットを終了します。")
break
# ChatGPTによる応答を取得 ---(※15)
assistant_reply = chat_with_agent(user_input)
# ChatGPTの応答を表示
print("🤖 GPT: " + assistant_reply)
(※5)はpan
とtilt
角度を生成する関数です。カスタムツールのツールデコレーター(@tool
)を使用して関数を定義します。関数の記述は前章のものと同じです。
複数のツールを指定する場合、(※6)のtools
配列の要素としてそれらを記述します。関数名"turn_pan_tilt"
を記述します。関数呼び出しと異なり、ツールの選択をエージェントが内部的に行うのでキーワードの指定などは行いません。
実行結果は以下のようになります。
🖥️ SYSTEM: チャットを開始します。終了するには '/exit' を入力してください。
😀 USER: こんにちは
> Entering new AgentExecutor chain...
こんにちは!私はゆっくり霊夢です。どのようにお手伝いできますか?
> Finished chain.
🤖 GPT: こんにちは!私はゆっくり霊夢です。どのようにお手伝いできますか?
😀 USER: 右を向いて
> Entering new AgentExecutor chain...
Invoking: `turn_pan_tilt` with `{'pan': -90, 'tilt': 0}`
{"pan": -90, "tilt": 0}了解しました。カメラを右に向けます。
> Finished chain.
🤖 GPT: 了解しました。カメラを右に向けます。
😀 USER: /exit
🖥️ SYSTEM: チャットを終了します。
-
翻訳ドキュメントは、以下のリンク PonDad/langchain_agent_guide.md / Gist から確認できます。 ↩︎
-
翻訳ドキュメントは、以下のリンクPonDad/openai_functions_guide.md / Gist から確認できます。 ↩︎
-
翻訳ドキュメントは、以下のリンクPonDad/tools_guide.md / Gist から確認できます。 ↩︎
-
実装方法は、以下のリンクSet up the agent / 🦜️🔗 LangChainから確認できます ↩︎
-
ドキュメントは以下のリンク Using the tool decorator / 🦜️🔗 LangChain で確認できます。 ↩︎
-
ドキュメントは以下のリンク Conversation Buffer / 🦜️🔗 LangChain で確認できます。 ↩︎
Discussion