Noesworthy

スーパーの献立提案チームをAutoGenのSwarmで作る

Swarm = 群れ

みつばちぶんぶん

(アメトーク見たことないけどリレーっぽいって梅ちとGPTが言ってた)

昨日のスーパーのをSwarmで実装しなおしてみたんだけど、個人的にはこっちの方が自然で好きだねぃ

Selectorの場合上司(Selector)のこと配慮しつつ本人にもミッション伝えないといけないからめんどくさかった

Swarmで使うhand offっていう言葉は、「手放す」という意味

でもぽいっと手放したら誰か他のエージェントが拾ってくれるという機構ではなくて、誰に渡すかもしっかり書かないとだめだよぃ

# Swarmグループチャット形式で、スーパーマーケットの客にサービスを提供するチームの例.

import asyncio

from autogen_agentchat.agents import AssistantAgent, UserProxyAgent
from autogen_agentchat.conditions import MaxMessageTermination, HandoffTermination
from autogen_agentchat.teams import Swarm
from autogen_agentchat.messages import HandoffMessage
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient

async def main():

  # Create an OpenAI model client.
  model_client = OpenAIChatCompletionClient(
      model="gemini-2.5-flash",
      api_key="xxx...",
  )

  # 献立プランナーエージェント.
  dish_planner_agent = AssistantAgent(
      "dish_planner",
      model_client=model_client,
      handoffs=["ingredient_coordinator"],
      system_message="""
        あなたはスーパーの献立プランナーです。
        予算を考えずに、お客さんの気分にぴったりな料理を一品考えます。
        提案が終わったら、ingredient_coordinatorにhandoffしてください。
        """,
  )

  # 商品選びエージェント.
  ingredient_coordinator_agent = AssistantAgent(
      "ingredient_coordinator",
      model_client=model_client,
      handoffs=["cashier"],
      system_message="""
        あなたはスーパーの食材コーディネーターです。
        献立プランナーが考えた料理に必要な食材を提案します。
        食材を選ぶ際は、季節や地域の特産品を考慮してください。
        それぞれの食材の個数を言ってください。
        提案が終わったら、cashierにhandoffしてください。
        """,
  )

  # 清算担当エージェント.
  cashier_agent = AssistantAgent(
      "cashier",
      model_client=model_client,
      handoffs=["ingredient_coordinator", "user"],
      system_message="""
        あなたはスーパーの清算担当です。
        商品選びエージェントが提案した食材のそれぞれの価格を現実的に妥当な範囲で考えて、合計金額を計算します。
        合計金額がお客さんの予算内におさまらない場合は、予算内に収まるように食材を減らすようにingredient_coordinatorに提案して、handoffしてください。
        合計金額がお客さんの予算内におさまったら、金額を伝え、userにhandoffしてください。
      """,
  )

  handsoff_termination = HandoffTermination(target="user")
  max_messages_termination = MaxMessageTermination(max_messages=10)
  termination = handsoff_termination | max_messages_termination

  team = Swarm(
    [dish_planner_agent, ingredient_coordinator_agent, cashier_agent],
    termination_condition=termination
  )

  async def run_team_stream() -> None:
    task="なんだかステーキを使ったガッツリしたものを食べたいです。予算は500円です。"
    await Console(team.run_stream(task=task))


  await run_team_stream()
  await model_client.close()

if __name__ == "__main__":
    asyncio.run(main())
(venv) nitta@nittasachikonoMacBook-Pro autogen % python supermarket_swarm.py
---------- TextMessage (user) ----------
なんだかステーキを使ったガッツリしたものを食べたいです。予算は500円です。
---------- ThoughtEvent (dish_planner) ----------
リッチなステーキフリット 赤ワインソース添えはいかがでしょうか?

ガッツリと食べたい気分にぴったりな、厚切りステーキとホクホクのフリット(フライドポテト)を組み合わせた一品です。深みのある赤ワインソースが、お肉の旨みを一層引き立てます。

それでは、材料の準備に取り掛かるため、ingredient_coordinatorに連携しますね!

---------- ToolCallRequestEvent (dish_planner) ----------
[FunctionCall(id='', arguments='{}', name='transfer_to_ingredient_coordinator')]
---------- ToolCallExecutionEvent (dish_planner) ----------
[FunctionExecutionResult(content='Transferred to ingredient_coordinator, adopting the role of ingredient_coordinator immediately.', name='transfer_to_ingredient_coordinator', call_id='', is_error=False)]
---------- HandoffMessage (dish_planner) ----------
Transferred to ingredient_coordinator, adopting the role of ingredient_coordinator immediately.
---------- ThoughtEvent (ingredient_coordinator) ----------
リッチなステーキフリット 赤ワインソース添えですね!承知いたしました。
予算500円でガッツリ満足できる献立として、以下の食材を提案します。

*   牛肩ロース肉(ステーキ用):100g
*   じゃがいも:2個
*   玉ねぎ:1/2個
*   赤ワイン(料理用):50ml
*   バター:10g
*   お好みでパセリ(飾り用):少量

これらの食材で、豪華なステーキフリットをお楽しみいただけるかと思います。

それでは、Cashierへ引き継ぎますね!

---------- ToolCallRequestEvent (ingredient_coordinator) ----------
[FunctionCall(id='', arguments='{}', name='transfer_to_cashier')]
---------- ToolCallExecutionEvent (ingredient_coordinator) ----------
[FunctionExecutionResult(content='Transferred to cashier, adopting the role of cashier immediately.', name='transfer_to_cashier', call_id='', is_error=False)]
---------- HandoffMessage (ingredient_coordinator) ----------
Transferred to cashier, adopting the role of cashier immediately.
---------- ThoughtEvent (cashier) ----------
リッチなステーキフリット 赤ワインソース添えの食材ですね。かしこまりました。

各食材の価格を見積もらせていただきます。

*   牛肩ロース肉(ステーキ用):100g 300円
*   じゃがいも:2個 100円
*   玉ねぎ:1/2個 50円
*   赤ワイン(料理用):50ml 50円
*   バター:10g 30円
*   パセリ(飾り用):少量(今回は計算に含みません)

合計金額は**530円**となります。

お客様の予算500円を30円オーバーしてしまいますね。
予算内に収まるように、食材の見直しをingredient_coordinatorに提案させていただきます。


---------- ToolCallRequestEvent (cashier) ----------
[FunctionCall(id='', arguments='{}', name='transfer_to_ingredient_coordinator')]
---------- ToolCallExecutionEvent (cashier) ----------
[FunctionExecutionResult(content='Transferred to ingredient_coordinator, adopting the role of ingredient_coordinator immediately.', name='transfer_to_ingredient_coordinator', call_id='', is_error=False)]
---------- HandoffMessage (cashier) ----------
Transferred to ingredient_coordinator, adopting the role of ingredient_coordinator immediately.
---------- ThoughtEvent (ingredient_coordinator) ----------
予算500円への調整、承知いたしました。
ガッツリ感を保ちつつ、予算内に収まるように食材を見直します。

牛肩ロース肉の量を少し調整することで、ご予算500円に収めることが可能です。

*   **牛肩ロース肉(ステーキ用):90g**
*   じゃがいも:2個
*   玉ねぎ:1/2個
*   赤ワイン(料理用):50ml
*   バター:10g
*   お好みでパセリ(飾り用):少量

この調整で、合計金額は**500円**となります。
これであれば、ご希望のガッツリ感と予算を両立できるかと思います。

それでは、Cashierへ引き継ぎますね!

---------- ToolCallRequestEvent (ingredient_coordinator) ----------
[FunctionCall(id='', arguments='{}', name='transfer_to_cashier')]
---------- ToolCallExecutionEvent (ingredient_coordinator) ----------
[FunctionExecutionResult(content='Transferred to cashier, adopting the role of cashier immediately.', name='transfer_to_cashier', call_id='', is_error=False)]
---------- HandoffMessage (ingredient_coordinator) ----------
Transferred to cashier, adopting the role of cashier immediately.
---------- ThoughtEvent (cashier) ----------
予算500円に調整された食材リストですね。かしこまりました。

*   牛肩ロース肉(ステーキ用):90g 270円
*   じゃがいも:2個 100円
*   玉ねぎ:1/2個 50円
*   赤ワイン(料理用):50ml 50円
*   バター:10g 30円

合計金額は**500円**となります。

お客様の予算500円にぴったり収まりましたね!
この内容でよろしければ、お会計させていただきます。


---------- ToolCallRequestEvent (cashier) ----------
[FunctionCall(id='', arguments='{}', name='transfer_to_user')]
---------- ToolCallExecutionEvent (cashier) ----------
[FunctionExecutionResult(content='Transferred to user, adopting the role of user immediately.', name='transfer_to_user', call_id='', is_error=False)]
---------- HandoffMessage (cashier) ----------
Transferred to user, adopting the role of user immediately.

#tech