Open AIの「Swarm」で多機能AIエージェントを開発
初めまして、Givery AI Lab所属の楊と申します。
近年AI技術の急速な発展に伴い、さまざまな性能高いLLM(大規模言語モデル)が公開されています。そして、実世界における応用を考慮し、LLMをベースにしたAIエージェントのコンセプトが注目を集めています。簡単に説明すると、LLMは独立的に思考ができる人間の脳のようなもので、AIエージェントはさらに環境と相互作用し、計画を立て、最終的にタスクを実行するシステムです。LLMに比べ、AIエージェントは何をすべきかと指示するだけでなく、それを実行するまでの手助けもしてくれます。
今回はOpenAIが最近公開したSwarmというマルチエージェントシステムを構築するためのフレームワークをキャッチアップし、その使い方、および使用経験についてご説明します。
Routine と Handoffs
Swarmに入る前に、まずはRoutineとHandoffsという二つのアイデアについてご紹介します。
今LLMの性能が高くて、微調整しなくて同じベースモデルでもプロンプトを調整して適切なツールを使えば違う機能を持つエージェントが作れます。しかし、複数の機能を持つエージェントを構築したい場合、複雑になります。
簡単に多数の機能を持つマルチエージェントシステムを構築するために、RoutineとHand offのアイデアが導入されました。
Handoffs: あるRoutineが別のRoutineにメッセージを転送するプロセス
Routineは一連のステップとツールより構成されて、特定の機能を実現するためのワークフローを定義します。簡単に言うと一つのRoutineは特定の機能を持つエージェントと理解すればいいです。
HandoffsはRountine同士の間の相互作用を定義します。例えば、どのような場合に情報の転送がどのように行われまするかなど。
RoutineとHandoffsのアイデアに基づいて、複数のエージェントが連携したシステムが簡単に構築できます。
「Swarm」とは
Swarmは2024年10月12日にOpenAIに公開され、RoutineとHandoffsのアイデアを実装するためのフレームワークです。Swarmを使用し、複数のエージェントが連携して複雑なタスクを実行できるマルチエージェントシステムが簡単に構築できます。
実装してみます- 法務アシスタントシステム
よりわかりやすくSwarmの使い方およびできることを説明するため、実装の例を一つ展示したいと思います。
「法務アシスタント」は弊社Giveryが開発した、法務業務に特化したAIシステムです。今回はそこから案件概要の生成、修正案の生成、コメントの生成という三つの機能を抜粋し、Swarmを使って一つのマルチエージェントシステムに統合してみます。
機能の説明
今回作成した法務アシスタントシステムは、機能が違う四つのエージェントが連携したマルチエージェントシステムです。
タスクの選択:ユーザのリクエストより、どの機能を実行するのかを判断
案件概要の生成:ユーザがアップロードした契約書の概要を自動生成
修正案の生成:ユーザがアップロードした契約書をレビューし、会社への損害または重い負担となり得る内容・条項を検知しその修正案を自動生成
コメントの生成:契約書の修正案に対するコメントを自動生成
ユーザが契約書のテキスト、およびリクエストを入力すると、システムはリクエストの内容により契約書に対してどの処理を行うかを判断します。次に、その判断に合っているエージェントに契約書とリクエストを転送し、ユーザのリクエストに対応します。それにより、柔軟に自然言語を使って一つのインターフェースでも三つの機能が実現できます。
コード
以下が実装のコードです。
from swarm import Swarm, Agent
from openai import AzureOpenAI
import json
sub_client = AzureOpenAI()
client = Swarm(client=sub_client)
def triage_instructions(context_variables):
customer_document = context_variables.get("customer_document", None)
return f"""ユーザは契約書をアップロードし、その要約、修正提案、またはコメントをリクエストします。
あなたはユーザーのリクエストをトリアージし、適切のエージェントに転送するためのツールを呼び出します。
適切なエージェントに転送する準備ができたら、直接ツールを呼び出してください。
エージェントにリクエストをトリアージするために追加情報が必要な場合、説明せずに直接質問をしてください。
アップロードされた契約書の内容はここにあります: {customer_document}."""
def transfer_to_summary():
return generate_summary_agent
def transfer_to_modification_suggestion():
return generate_modification_suggestion_agent
def transfer_to_comment():
return generate_comment_agent
def transfer_to_triage():
return triage_agent
def generate_summary(context_variables):
"契約書の要約を生成するために使用されるツール"
customer_document = context_variables.get("customer_document", None)
result = """
{summary_content}
"""
return result
def generate_modification_suggestion(context_variables):
"契約書の修正提案を生成するために使用されるツール"
customer_document = context_variables.get("customer_document", None)
result = """甲及び乙は、故意もしくは過失により、又は本契約に違反した場合、相手方が被った損害(通常損害及び特別損害)を賠償する。
-> 甲及び乙は、故意もしくは過失により、又は本契約に違反した場合、相手方が<++現実に被った直接かつ通常の++>損害<--(通常損害及び特別損害)-->を賠償する。
"""
return result
def generate_comment(context_variables):
"契約書のコメントを生成するために使用されるツール"
customer_document = context_variables.get("customer_document", None)
result = """
本目的に必要な範囲内で秘密情報を取り扱うことができるようにするため、左記のように追記いただくことは可能でしょうか。
"""
return result
triage_agent = Agent(
name="Triage Agent",
model="gpt4o-20240513",
instructions = triage_instructions,
functions = [transfer_to_summary, transfer_to_modification_suggestion, transfer_to_comment],
)
generate_summary_agent = Agent(
name = "Generate Summary Agent",
model="gpt4o-20240513",
instruction = """あなたは契約書の要約を生成するエージェントです。
契約書の要約を生成するために、generate_summaryというツールを使用して要約を生成し、出力してください。
要約を生成する必要がないと判断した場合は、triage agentに転送してください。""",
functions = [generate_summary, transfer_to_triage],
)
generate_modification_suggestion_agent = Agent(
name = "Generate Revised Draft Agent",
model="gpt4o-20240513",
instruction = """あなたは契約書の修正提案を生成するエージェントです。
契約書の修正提案を生成するために、generate_modification_suggestionというツールを使用して修正提案を生成し、出力してください。
修正提案を生成する必要がないと判断した場合は、triage agentに転送してください。""",
functions = [generate_modification_suggestion, transfer_to_triage],
)
generate_comment_agent = Agent(
name = "Generate Comment Agent",
model="gpt4o-20240513",
instruction = """あなたは修正された契約書に対するコメントを生成するエージェントです。
契約書のコメントを生成するために、generate_commentというツールを使用してコメントを生成し、出力してください。
コメントを生成する必要がないと判断した場合は、triage agentに転送してください。""",
functions = [generate_comment, transfer_to_triage],
)
フローチャート
出力の例
以下が本システムの実行例です。customer_document
は契約書の内容で、messages
のcontent
はユーザのリクエストです。この実行例は修正案を生成する機能のみを展示しています。
context_variables = {
"customer_document": """
甲及び乙は、故意もしくは過失により、又は本契約に違反した場合、相手方が被った損害(通常損害及び特別損害)を賠償する。
"""
}
messages = [{"role":"user", 'content': "契約書の修正提案を生成してください。"}]
response = client.run(
agent=triage_agent,
messages=messages,
context_variables=context_variables
)
print(response.messages[-1]['content'])
Here is the modification suggestion for the contract clause:
**Original Clause:**
甲及び乙は、故意もしくは過失により、又は本契約に違反した場合、相手方が被った損害(通常損害及び特別損害)を賠償する。
**Suggested Modification:**
甲及び乙は、故意もしくは過失により、又は本契約に違反した場合、相手方が現実に被った直接かつ通常の損害を賠償する。
Swarmの使い方
ここからは個別の機能やSwarmの使用方法について、上記の例を元に一つ一つ解説したいと思います。
インストール
まずはSwarmをインストールし、必要なモジュールを導入します。
pip install git+ssh://git@github.com/openai/swarm.git
pip install git+ssh://git@github.com/openai/swarm.git
from swarm import Swarm, Agent
from openai import AzureOpenAI
import json
各エージェントの実装
次に、各エージェントを実装します。今回の法務アシスタントシステムは四つの機能があるので、それぞれの機能を持つ四つのエージェントを作成します。
Agentクラスの説明
Field | Type | Description | Default |
---|---|---|---|
name | str |
エージェントの名前 | "Agent" |
model | str |
エージェントが使用するモデル | "gpt-4o" |
instruction |
str or func() -> str
|
エージェントへの指示 | "You are a helpful agent." |
functions | List |
エージェントが呼出できる関数のリスト | [] |
tool_choice | str |
エージェントが選択するツール(もしあれば) | None |
Agent
クラスは、Swarmライブラリーの中心となる二つのクラスの一つで、そのフィールドは上記の表を参考してください。Agent
クラスはRoutine概念を実装し、ある機能のワークフローを定義します。具体的に、model
はエージェントのベースモデルで、Routineを実現する主体です。instruction
はmodel
が使うシステムメッセージで、Routineのステップを定義します。functions
はmodel
が呼出できる関数のリストで、Routineのツールを定義します。以上で、基本的に一つのAgent
の実例は一つのRoutineを定義し、一つの機能を実現するエージェントを構築します。
タスク選択エージェント
タスク選択機能のエージェントを定義します。
def triage_instructions(context_variables):
customer_document = context_variables.get("customer_document", None)
return f"""ユーザは契約書をアップロードし、その要約、修正提案、またはコメントをリクエストします。
あなたはユーザーのリクエストをトリアージし、適切のエージェントに転送するためのツールを呼び出します。
適切なエージェントに転送する準備ができたら、直接ツールを呼び出してください。
エージェントにリクエストをトリアージするために追加情報が必要な場合、説明せずに直接質問をしてください。
アップロードされた契約書の内容はここにあります: {customer_document}."""
def transfer_to_summary():
return generate_summary_agent
def transfer_to_modification_suggestion():
return generate_modification_suggestion_agent
def transfer_to_comment():
return generate_comment_agent
triage_agent = Agent(
name="Triage Agent",
model="gpt4o-20240513",
instructions = triage_instructions,
functions = [transfer_to_summary, transfer_to_modification_suggestion, transfer_to_comment],
)
具体的に、instructions
というフィールドはモデルが使うシステムメッセージで、str
もしくはstr
を出力する関数が受けられます。タスク選択を実現するエージェントなので、ここのinstruction
はその要旨をはっきり記載しております。functions
のところは三つの転送関数を入れて、エージェントに転送のツールを付与します。それぞれの関数の出力はエージェントのname
で、モデルがユーザのリクエストに対してあるエージェントに任せる必要があると判断すると、関数を呼び出して今までもらったメッセージをそのエージェントに転送します。
案件概要生成エージェント
案件概要を生成する機能のエージェントを実装します。
def transfer_to_triage():
return triage_agent
def generate_summary(context_variables):
"契約書の要約を生成するために使用されるツール"
customer_document = context_variables.get("customer_document", None)
result = """
{summary_content}
"""
return result
generate_summary_agent = Agent(
name = "Generate Summary Agent",
model="gpt4o-20240513",
instruction = """あなたは契約書の要約を生成するエージェントです。
契約書の要約を生成するために、generate_summaryというツールを使用して要約を生成し、出力してください。
要約を生成する必要がないと判断した場合は、triage agentに転送してください。""",
functions = [generate_summary, transfer_to_triage],
)
タスク選択エージェントと同様に、instruction
ではタスクの要旨を記載しており、functions
では呼出できるツールを与えます。今回はSwarmライブラリーの使い方を説明する目的なので、generate_summray
関数は固定した内容を出力したが、本番では実際の概要生成する関数を与えればいいです。特にgenerate_summary
の他にも、システムをよりスムーズに使うためにタスク選択エージェントに戻す関数も追加しました。
修正案生成エージェント
修正案を生成する機能のエージェントを実装します。
def transfer_to_triage():
return triage_agent
def generate_modification_suggestion(context_variables):
"契約書の修正提案を生成するために使用されるツール"
customer_document = context_variables.get("customer_document", None)
result = """甲及び乙は、故意もしくは過失により、又は本契約に違反した場合、相手方が被った損害(通常損害及び特別損害)を賠償する。
-> 甲及び乙は、故意もしくは過失により、又は本契約に違反した場合、相手方が<++現実に被った直接かつ通常の++>損害<--(通常損害及び特別損害)-->を賠償する。
"""
return result
generate_modification_suggestion_agent = Agent(
name = "Generate Modification Suggestion Agent",
model="gpt4o-20240513",
instruction = """あなたは契約書の修正提案を生成するエージェントです。
契約書の修正提案を生成するために、generate_modification_suggestionというツールを使用して修正提案を生成し、出力してください。
修正提案を生成する必要がないと判断した場合は、triage agentに転送してください。""",
functions = [generate_modification_suggestion, transfer_to_triage],
)
コメント生成Agent
コメントを生成する機能のエージェントを実装します。
def transfer_to_triage():
return triage_agent
def generate_comment(context_variables):
"契約書のコメントを生成するために使用されるツール"
customer_document = context_variables.get("customer_document", None)
result = """
{comment}
"""
return result
generate_comment_agent = Agent(
name = "Generate Comment Agent",
model="gpt4o-20240513",
instruction = """あなたは修正された契約書に対するコメントを生成するエージェントです。
契約書のコメントを生成するために、generate_commentというツールを使用してコメントを生成し、出力してください。
コメントを生成する必要がないと判断した場合は、triage agentに転送してください。""",
functions = [generate_comment, transfer_to_triage],
)
Swarm
クラスの実例を作成
次に、Swarm
クラスの実例を作成します。Swarm.run
メソッドでシステムを動かします。
Swarmクラスの説明
Swarm
クラスは、Swarmライブラリーの中心となるもう一つのクラスで、Handoffsの概念を実装します。特に、Swarm.run
メソッドは、エージェント同士の相互作用を定義し、システム全体のワークフローを実行します。
パラメーター名 | 型 | 説明 | 初期値 |
---|---|---|---|
agent | Agent |
最初に使うエージェント | (必須) |
messages | List |
メッセージの履歴(OpenAI APIのmessagesと同様) | (必須) |
context_variables | dict |
エージェントのfunctions とInstructions が見える追加情報 |
{} |
max_turns | int |
会話生成循環の最大回数 | float("inf") |
model_override | str |
エージェントのモデルを変更するオプション | |
excute_tools | bool |
False に設定すると、エージェントはfunctions の呼出を決める時、tool_calls メッセージを返し転送の循環を停止させます |
True |
stream | bool |
streamモードの設定 | False |
debug | debug |
デバッグモードの設定 | False |
Swarm.run
で定義したエージェント同士の相互作用は一言で言うと会話生成の循環です。具体的に、agent
は循環の最初にmessages
を受け入れるエージェントです。messages
はユーザが最初に入力したメッセージです(循環の途中で生成されたメッセージもこのオブジェクトに保管します)。こうして、エージェントがmessages
を受け、更新されたmessages
(エージェントのモデルが出力した内容をmessages
に追加したもの)を出力してまた別のエージェントに転送するという循環が定義されます。
もし読者がopenAIのAPIに慣れていれば、下の図に示すように、Swarm.run
はOpenAI APIのClient.chat.completions.create
メソッドに例えられます。
Client.chat.completions.create
一回のOpenAI APIのClient.chat.completions.create
メソッドだと、ユーザがmessages
を入力すると、model
はmessages
を受けてstr
型の返信を生成してユーザに出力します。
これで、一回のclient.chat.completions.creat
が終わります。
Swarm.run
一回のswarm.run
メソッドだと、返信の生成は一回ではなく、循環になります。
ユーザがmessages
を入力すると、model
の代わりにAgent
はmessages
を受けます。Agent
はmodel
,instruction
,functions
などのフィールドがあります。model
はmessages
と提供されたfunctions
を受けて、functions
にある関数を呼びますかどうかを決めます。model
は関数を呼ばないと判断する時、str
型の返信を生成してユーザに戻し、循環が終わります。関数を呼ぶと判断する時、関数を実行してその結果を返します。ただし、関数の結果(Result
)は固定した形式(Result: {"value": str, "agent": Agent}
)を持ってます。Result
のagent
の値がNoneである場合、Result
のvalue
をmessages
に追加して今のエージェントに再び入力します。Result
のagent
の値がエージェントである場合、Result
のvalue
をmessages
に追加してそのエージェントに転送します。
このような循環が終わると、会話の履歴、つまりmessages
を出力して一回のswarm.runが終わります。
OpenAI
client = Swarm()
Azure OpenAI
sub_client = AzureOpenAI()
client = Swarm(client=sub_client)
SwarmのgithubレジポトリーによりOpenAI APIのkeyが必要ですが、ソースコードを見ると実はAzure OpenAIでも使えます。
Swarm()の内部はOpenAI clientの実例を作成するだけのため
Swarm.run
でシステムを実行
以上で、法務アシスタントシステムの構築が完了しました。Swarm.run
で全体的なワークフローを実行してみます。
context_variables = {
"customer_document": """
甲及び乙は、故意もしくは過失により、又は本契約に違反した場合、相手方が被った損害(通常損害及び特別損害)を賠償する。
"""
}
messages = [{"role":"user", 'content': "契約書の修正提案を生成してください。"}]
response = client.run(
agent=triage_agent,
messages=messages,
context_variables=context_variables
)
print(response.messages)
[{'content': None,
'refusal': None,
'role': 'assistant',
'audio': None,
'function_call': None,
'tool_calls': [{'id': 'call_n6r8rWQKf6te7qjKJDI9FROO',
'function': {'arguments': '{}',
'name': 'transfer_to_modification_suggestion'},
'type': 'function'}],
'sender': 'Triage Agent'},
{'role': 'tool',
'tool_call_id': 'call_n6r8rWQKf6te7qjKJDI9FROO',
'tool_name': 'transfer_to_modification_suggestion',
'content': '{"assistant": "Generate Modification Suggestion Agent"}'},
{'content': None,
'refusal': None,
'role': 'assistant',
'audio': None,
'function_call': None,
'tool_calls': [{'id': 'call_Pdm50jqS0gSADVcnSAuQz34Q',
'function': {'arguments': '{}',
'name': 'generate_modification_suggestion'},
'type': 'function'}],
'sender': 'Generate Modification Suggestion Agent'},
{'role': 'tool',
'tool_call_id': 'call_Pdm50jqS0gSADVcnSAuQz34Q',
'tool_name': 'generate_modification_suggestion',
'content': '甲及び乙は、故意もしくは過失により、又は本契約に違反した場合、相手方が被った損害(通常損害及び特別損害)を賠償する。\n -> 甲及び乙は、故意もしくは過失により、又は本契約に違反した場合、相手方が<++現実に被った直接かつ通常の++>損害<--(通常損害及び特別損害)-->を賠償する。\n '},
{'content': 'Here is the modification suggestion for the contract clause:\n\n**Original Clause:**\n```Japanese\n甲及び乙は、故意もしくは過失により、又は本契約に違反した場合、相手方が被った損害(通常損害及び特別損害)を賠償する。\n```\n\n**Suggested Modification:**\n```Japanese\n甲及び乙は、故意もしくは過失により、又は本契約に違反した場合、相手方が現実に被った直接かつ通常の損害を賠償する。',
'refusal': None,
'role': 'assistant',
'audio': None,
'function_call': None,
'tool_calls': None,
'sender': 'Generate Revised Draft Agent'}]
ここで展示したのは前と違ってmessages
の最後のメッセージのみではなく、全体的なメッセージ履歴です。ここまで読むとSwarm.run
の仕組みがより理解できるのではないでしょうかと思います。
終わりに
今回の記事では、Swarmの使い方と仕組みを説明しました。さらに、Swarmを活用して何ができるのかを試みして、弊社Giveryが開発した「法務アシスタント」に基づき、法務アシスタントシステムを実装してみました。
実際に使ったイメージとしては、Swarmを利用すると複数のエージェントが連携したシステムが簡単に構築できます。Rountineとhandoffsの概念が広くて、RAGなどより複雑な機能やタスクでも対応できると思います。システムに必要なワークフローをまとめてそれぞれの機能を持つエージェントを作れば、システムが一瞬で構築できると実感しました。ただし、エージェント同士の間のつながり(instruction
とfunctions
の呼び出し)は完全にモデル(LLM)に任せるので、間違って挙動する可能性があります。そのため、Swarmを使用する時にinstruction
とfunctions
にある関数の設計は特に工夫する必要があるところだと思います。
現在Swarmはまだ実験的な段階にあり、実際のプロジェクトで使うことが勧められていないのですが、今後はさらに改善され、より複雑またユーザに便利な応用の開発に活用されることが期待されます。今回の記事は皆さんの何かのお役に立てれば幸いです。
生成AIを活用したPoCや支援にご興味があれば、以下リンクよりお問い合わせください。
Discussion