🤖

Open AIの「Swarm」で多機能AIエージェントを開発

2024/11/12に公開

初めまして、Givery AI Lab所属の楊と申します。
近年AI技術の急速な発展に伴い、さまざまな性能高いLLM(大規模言語モデル)が公開されています。そして、実世界における応用を考慮し、LLMをベースにしたAIエージェントのコンセプトが注目を集めています。簡単に説明すると、LLMは独立的に思考ができる人間の脳のようなもので、AIエージェントはさらに環境と相互作用し、計画を立て、最終的にタスクを実行するシステムです。LLMに比べ、AIエージェントは何をすべきかと指示するだけでなく、それを実行するまでの手助けもしてくれます。

今回はOpenAIが最近公開したSwarmというマルチエージェントシステムを構築するためのフレームワークをキャッチアップし、その使い方、および使用経験についてご説明します。

Routine と Handoffs

Swarmに入る前に、まずはRoutineとHandoffsという二つのアイデアについてご紹介します。
今LLMの性能が高くて、微調整しなくて同じベースモデルでもプロンプトを調整して適切なツールを使えば違う機能を持つエージェントが作れます。しかし、複数の機能を持つエージェントを構築したい場合、複雑になります。

簡単に多数の機能を持つマルチエージェントシステムを構築するために、RoutineとHand offのアイデアが導入されました。
https://cookbook.openai.com/examples/orchestrating_agents
Routine: 一連のステップ + ステップを実行するためのツール
Handoffs: あるRoutineが別のRoutineにメッセージを転送するプロセス

Routineは一連のステップとツールより構成されて、特定の機能を実現するためのワークフローを定義します。簡単に言うと一つのRoutineは特定の機能を持つエージェントと理解すればいいです。
HandoffsはRountine同士の間の相互作用を定義します。例えば、どのような場合に情報の転送がどのように行われまするかなど。

RoutineとHandoffsのアイデアに基づいて、複数のエージェントが連携したシステムが簡単に構築できます。

「Swarm」とは

Swarmは2024年10月12日にOpenAIに公開され、RoutineとHandoffsのアイデアを実装するためのフレームワークです。Swarmを使用し、複数のエージェントが連携して複雑なタスクを実行できるマルチエージェントシステムが簡単に構築できます。
https://github.com/openai/swarm

実装してみます- 法務アシスタントシステム

よりわかりやすくSwarmの使い方およびできることを説明するため、実装の例を一つ展示したいと思います。
「法務アシスタント」は弊社Giveryが開発した、法務業務に特化したAIシステムです。今回はそこから案件概要の生成、修正案の生成、コメントの生成という三つの機能を抜粋し、Swarmを使って一つのマルチエージェントシステムに統合してみます。
https://zenn.dev/givery_ai_lab/articles/bb39836efb2e95

機能の説明

今回作成した法務アシスタントシステムは、機能が違う四つのエージェントが連携したマルチエージェントシステムです。

タスクの選択:ユーザのリクエストより、どの機能を実行するのかを判断
案件概要の生成:ユーザがアップロードした契約書の概要を自動生成
修正案の生成:ユーザがアップロードした契約書をレビューし、会社への損害または重い負担となり得る内容・条項を検知しその修正案を自動生成
コメントの生成:契約書の修正案に対するコメントを自動生成

ユーザが契約書のテキスト、およびリクエストを入力すると、システムはリクエストの内容により契約書に対してどの処理を行うかを判断します。次に、その判断に合っているエージェントに契約書とリクエストを転送し、ユーザのリクエストに対応します。それにより、柔軟に自然言語を使って一つのインターフェースでも三つの機能が実現できます。

コード

以下が実装のコードです。

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は契約書の内容で、messagescontentはユーザのリクエストです。この実行例は修正案を生成する機能のみを展示しています。

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を実現する主体です。instructionmodelが使うシステムメッセージで、Routineのステップを定義します。functionsmodelが呼出できる関数のリストで、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 エージェントのfunctionsInstructionsが見える追加情報 {}
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を入力すると、modelmessagesを受けてstr型の返信を生成してユーザに出力します。
これで、一回のclient.chat.completions.creatが終わります。

一回のSwarm.run

swarm.runメソッドだと、返信の生成は一回ではなく、循環になります。
ユーザがmessagesを入力すると、modelの代わりにAgentmessagesを受けます。Agentmodel,instruction,functionsなどのフィールドがあります。modelmessagesと提供されたfunctionsを受けて、functionsにある関数を呼びますかどうかを決めます。modelは関数を呼ばないと判断する時、str型の返信を生成してユーザに戻し、循環が終わります。関数を呼ぶと判断する時、関数を実行してその結果を返します。ただし、関数の結果(Result)は固定した形式(Result: {"value": str, "agent": Agent})を持ってます。Resultagentの値がNoneである場合、Resultvaluemessagesに追加して今のエージェントに再び入力します。Resultagentの値がエージェントである場合、Resultvaluemessagesに追加してそのエージェントに転送します。
このような循環が終わると、会話の履歴、つまり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などより複雑な機能やタスクでも対応できると思います。システムに必要なワークフローをまとめてそれぞれの機能を持つエージェントを作れば、システムが一瞬で構築できると実感しました。ただし、エージェント同士の間のつながり(instructionfunctionsの呼び出し)は完全にモデル(LLM)に任せるので、間違って挙動する可能性があります。そのため、Swarmを使用する時にinstructionfunctionsにある関数の設計は特に工夫する必要があるところだと思います。

現在Swarmはまだ実験的な段階にあり、実際のプロジェクトで使うことが勧められていないのですが、今後はさらに改善され、より複雑またユーザに便利な応用の開発に活用されることが期待されます。今回の記事は皆さんの何かのお役に立てれば幸いです。

生成AIを活用したPoCや支援にご興味があれば、以下リンクよりお問い合わせください。
https://givery.co.jp/lp/ai-lab/members/

Givery AI Lab

Discussion