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のアイデアが導入されました。
Routine: 一連のステップ + ステップを実行するためのツール
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はまだ実験的な段階にあり、実際のプロジェクトで使うことが勧められていないのですが、今後はさらに改善され、より複雑またユーザに便利な応用の開発に活用されることが期待されます。今回の記事は皆さんの何かのお役に立てれば幸いです。
最後に
Givery AI Labが独自保有するフリーランス・副業の高単価AI案件や、随時開催しているセミナーやパーティなどのイベントにご興味ございましたら、ぜひTrack Worksのアカウント登録いただき、最新情報を受け取ってください!
「Track Works」とは?
Givery AI Labの運営会社である株式会社ギブリーが提供する、AI時代のフリーランスエンジニアとして「スキル」と「実績」を強化できる実践的なAI案件を、ご経歴やスキルに合わせてご紹介するフリーランスエンジニア案件マッチングサービスです。Givery AI Labが独自保有するフリーランス・副業案件を紹介したり、AI関連技術やエンジニアのキャリアに関するイベントを随時開催しています。
また、Givery AI Labメンバーとして就職・転職をご検討いただく場合は、下記からご応募くださいませ! (運営会社である株式会社ギブリーのエンジニア向け求人一覧ページです)
【企業のご担当者様へ】
Givery AI Labでは、PoCで終わらせない「AIの社会実装」を実現するため、AI開発プロジェクトのPoCから本格実装・運用まで、幅広く伴走支援しております。ぜひお気軽にお問合せください。
・AI開発プロジェクト伴走支援サービス:https://givery.co.jp/services/ai-lab/
・生成AI技術に関するお悩み解決サービス「Givery AI 顧問」:https://givery.co.jp/services/ai_advisor/
Discussion