🕌

LlamaIndexのAgentWorkflowについて学ぶ

2025/02/21に公開

こんにちは。ZENKIGENデータサイエンスチームの栗原です。現在は『harutaka EF(エントリーファインダー)』の自然言語処理周りの研究開発などに携わっています。
所属チームで Xアカウント を運用しており、AIに関する情報を発信していますので、よろしければこちらも覗いてみてください。

LlamaIndexは、LLMを用いたアプリケーション構築をするためのシンプルで柔軟なフレームワークです。
https://www.llamaindex.ai/framework
AgentWorkflow は、LlamaIndexに新たに(2025年1月22日に)導入されたシステムで、複数のエージェントを簡単にオーケストレーション(調整・管理)するための仕組みです。
https://www.llamaindex.ai/blog/introducing-agentworkflow-a-powerful-system-for-building-ai-agent-systems
今回はLlamaIndexから提供されているExamplesからAgentWorkflowの使い方を学びます。
https://docs.llamaindex.ai/en/stable/examples/

AgentWorkflowの基本

まず、AgentWorkflowの簡単な使い方を見ていきましょう。
pipでLlamaIndexをインストールしておきます。

%pip install llama-index

単一エージェント作成

ここでは、Web検索を行うエージェントを構築します。
Tavily Serch APIを利用[1]し(%pip install tavily-python)、ユーザーのクエリに対しWeb検索結果に基づき返答します。

では、ステップごとに見ていきましょう。
LLMとしてgpt-4o-miniを利用します。OPENAI_API_KEYは取得し、設定してください。

from google.colab import userdata # ColabでAPIキーを登録して利用する場合
OPENAI_API_KEY = userdata.get('OPENAI_API_KEY') # ColabでAPIキーを登録して利用する場合

from llama_index.llms.openai import OpenAI
llm = OpenAI(model="gpt-4o-mini", api_key=OPENAI_API_KEY)

ウェブ検索をするツールを定義します。

from tavily import AsyncTavilyClient

TAVILY_API_KEY = userdata.get('TAVILY_API_KEY') # ColabでAPIキーを登録して利用する場合

async def search_web(query: str) -> str:
    """Useful for using the web to answer questions."""
    client = AsyncTavilyClient(api_key=TAVILY_API_KEY)
    return str(await client.search(query))

ツールを定義する際に、以下の点がとても重要だと述べられています。

  • ツールに適切な名前とdocstringを与える。 LLMはツールがどのようなものなのかをここから理解します。
  • 型アノテーションをする。 LLMが入出力に期待される型を理解するのに役立ちます。
  • 可能な限りasyncを使う。 ワークフローがより効率的になるとのことです。

ツールが定義できたら、定義したツールを使用する AgentWorkflow を以下のように作成できます。

from llama_index.core.agent.workflow import AgentWorkflow

workflow = AgentWorkflow.from_tools_or_functions(
    [search_web], # ワークフローに使用させるツールをリストで渡す。
    llm=llm,
    system_prompt="You are a helpful assistant that can search the web for information.",
)

以上でWeb検索をするエージェントが作成できたので実行してみましょう[2]

response = await workflow.run(user_msg="東京の現在の天気は?")
print(str(response))
# 現在の東京の天気は以下の通りです:
# 
# - **気温**: 約8°C
# - **天気**: 晴れ時々曇り
# - **風**: 北西の風がやや強い
# - **降水確率**: 10%
# 
# 詳細な情報は、[Yahoo!天気](https://weather.yahoo.co.jp/weather/jp/13/4410.html)や[AccuWeather](https://www.accuweather.com/ja/jp/tokyo/226396/current-weather/226396)で確認できます。

ここからはAgentWorkflowのいくつかの基本的な機能の紹介をしていきます。

状態管理

デフォルトではAgentWorkflowはステートレスであり、過去の実行結果を記憶しません。
状態を維持したい場合は、AgentWorkflowでは Context に状態が保存されるのでこれを実行間で渡すことで実現できます。

from llama_index.core.workflow import Context

ctx = Context(workflow) # 対話コンテキストの作成。(Web検索を行うエージェントを作成した際のworkflowを流用しています)

response = await workflow.run(
    user_msg="初めまして。私の名前はmasakuriです。", ctx=ctx # ctxとして状態を実行時に渡す。
)
print(str(response))
# 初めまして、masakuriさん!どのようにお手伝いできますか?

上記実行後に以下を実行すると、私の名前を覚えてくれていました。

response = await workflow.run(user_msg="私の名前はなんですか?", ctx=ctx)
print(str(response))
# あなたの名前はmasakuriです。何か他にお知りになりたいことがありますか?

ストリーミング

AgentWorkflowではエージェントの動きをリアルタイムで追跡できます。
workflow.run()を実行する際に、戻り値(ハンドラー)からイベントストリームを取得し、各ステップの内容を逐次表示・確認するといったことが可能です。

from llama_index.core.agent.workflow import (
    AgentInput,
    AgentOutput,
    ToolCall,
    ToolCallResult,
    AgentStream,
)

handler = workflow.run(user_msg="What is the weather in Saskatoon?")

async for event in handler.stream_events():
    if isinstance(event, AgentStream):
        print(event.delta, end="", flush=True)
        # print(event.response)  # 現在の全応答
        # print(event.raw)  # LLMの生のAPIレスポンス
        # print(event.current_agent_name)  # 現在アクティブなエージェント名
    # elif isinstance(event, AgentInput):
    #    print(event.input)  # 現在のインプット
    #    print(event.current_agent_name)  # 現在アクティブなエージェント名
    # elif isinstance(event, AgentOutput):
    #    print(event.response)  # 現在の全応答
    #    print(event.tool_calls)  # もしあれば選択されたツールコール群
    #    print(event.raw)  # LLMの生のAPIレスポンス
    # elif isinstance(event, ToolCallResult):
    #    print(event.tool_name)  # 呼ばれたツール名
    #    print(event.tool_kwargs)  # ツールに渡された引数
    #    print(event.tool_output)  # ツールのアウトプット
    # elif isinstance(event, ToolCall):
    #     print(event.tool_name)  # 呼ばれたツール名
    #     print(event.tool_kwargs)  # ツールに渡された引数

LLM出力をストリーミング(print(event.delta, end="", flush=True))させると以下のように逐次出力がされました。

LLMの生のAPIレスポンスの出力例
{'id': 'chatcmpl-B2U0V706Tn3rZZ5xnfTncrqabyKQ8', 'choices': [{'delta': {'content': None, 'function_call': None, 'refusal': None, 'role': 'assistant', 'tool_calls': [{'index': 0, 'id': 'call_rZttbASpXB8ijdBfonwxEBcg', 'function': {'arguments': '', 'name': 'search_web'}, 'type': 'function'}]}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931703, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_00428b782a', 'usage': None}
{'id': 'chatcmpl-B2U0V706Tn3rZZ5xnfTncrqabyKQ8', 'choices': [{'delta': {'content': None, 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': '{"', 'name': None}, 'type': None}]}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931703, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_00428b782a', 'usage': None}
{'id': 'chatcmpl-B2U0V706Tn3rZZ5xnfTncrqabyKQ8', 'choices': [{'delta': {'content': None, 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': 'query', 'name': None}, 'type': None}]}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931703, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_00428b782a', 'usage': None}
{'id': 'chatcmpl-B2U0V706Tn3rZZ5xnfTncrqabyKQ8', 'choices': [{'delta': {'content': None, 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': '":"', 'name': None}, 'type': None}]}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931703, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_00428b782a', 'usage': None}
{'id': 'chatcmpl-B2U0V706Tn3rZZ5xnfTncrqabyKQ8', 'choices': [{'delta': {'content': None, 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': '東京', 'name': None}, 'type': None}]}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931703, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_00428b782a', 'usage': None}
{'id': 'chatcmpl-B2U0V706Tn3rZZ5xnfTncrqabyKQ8', 'choices': [{'delta': {'content': None, 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': ' 現', 'name': None}, 'type': None}]}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931703, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_00428b782a', 'usage': None}
{'id': 'chatcmpl-B2U0V706Tn3rZZ5xnfTncrqabyKQ8', 'choices': [{'delta': {'content': None, 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': '在', 'name': None}, 'type': None}]}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931703, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_00428b782a', 'usage': None}
{'id': 'chatcmpl-B2U0V706Tn3rZZ5xnfTncrqabyKQ8', 'choices': [{'delta': {'content': None, 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': 'の', 'name': None}, 'type': None}]}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931703, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_00428b782a', 'usage': None}
{'id': 'chatcmpl-B2U0V706Tn3rZZ5xnfTncrqabyKQ8', 'choices': [{'delta': {'content': None, 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': '天', 'name': None}, 'type': None}]}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931703, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_00428b782a', 'usage': None}
{'id': 'chatcmpl-B2U0V706Tn3rZZ5xnfTncrqabyKQ8', 'choices': [{'delta': {'content': None, 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': '気', 'name': None}, 'type': None}]}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931703, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_00428b782a', 'usage': None}
{'id': 'chatcmpl-B2U0V706Tn3rZZ5xnfTncrqabyKQ8', 'choices': [{'delta': {'content': None, 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': '"}', 'name': None}, 'type': None}]}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931703, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_00428b782a', 'usage': None}
{'id': 'chatcmpl-B2U0V706Tn3rZZ5xnfTncrqabyKQ8', 'choices': [{'delta': {'content': None, 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': 'tool_calls', 'index': 0, 'logprobs': None}], 'created': 1739931703, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_00428b782a', 'usage': None}
{'id': 'chatcmpl-B2U0XSwogpBk6eVHaBHziF1HXaHRB', 'choices': [{'delta': {'content': '', 'function_call': None, 'refusal': None, 'role': 'assistant', 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931705, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_13eed4fce1', 'usage': None}
{'id': 'chatcmpl-B2U0XSwogpBk6eVHaBHziF1HXaHRB', 'choices': [{'delta': {'content': '現在', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931705, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_13eed4fce1', 'usage': None}
{'id': 'chatcmpl-B2U0XSwogpBk6eVHaBHziF1HXaHRB', 'choices': [{'delta': {'content': 'の', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931705, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_13eed4fce1', 'usage': None}
{'id': 'chatcmpl-B2U0XSwogpBk6eVHaBHziF1HXaHRB', 'choices': [{'delta': {'content': '東京', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931705, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_13eed4fce1', 'usage': None}
{'id': 'chatcmpl-B2U0XSwogpBk6eVHaBHziF1HXaHRB', 'choices': [{'delta': {'content': 'の', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931705, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_13eed4fce1', 'usage': None}
{'id': 'chatcmpl-B2U0XSwogpBk6eVHaBHziF1HXaHRB', 'choices': [{'delta': {'content': '天', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931705, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_13eed4fce1', 'usage': None}
{'id': 'chatcmpl-B2U0XSwogpBk6eVHaBHziF1HXaHRB', 'choices': [{'delta': {'content': '気', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931705, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_13eed4fce1', 'usage': None}
{'id': 'chatcmpl-B2U0XSwogpBk6eVHaBHziF1HXaHRB', 'choices': [{'delta': {'content': 'は', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931705, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_13eed4fce1', 'usage': None}
{'id': 'chatcmpl-B2U0XSwogpBk6eVHaBHziF1HXaHRB', 'choices': [{'delta': {'content': '以下', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931705, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_13eed4fce1', 'usage': None}
{'id': 'chatcmpl-B2U0XSwogpBk6eVHaBHziF1HXaHRB', 'choices': [{'delta': {'content': 'の', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931705, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_13eed4fce1', 'usage': None}
{'id': 'chatcmpl-B2U0XSwogpBk6eVHaBHziF1HXaHRB', 'choices': [{'delta': {'content': '通', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931705, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_13eed4fce1', 'usage': None}
{'id': 'chatcmpl-B2U0XSwogpBk6eVHaBHziF1HXaHRB', 'choices': [{'delta': {'content': 'り', 'function_call': None, 'refusal': None, 'role': None, 'tool_calls': None}, 'finish_reason': None, 'index': 0, 'logprobs': None}], 'created': 1739931705, 'model': 'gpt-4o-mini-2024-07-18', 'object': 'chat.completion.chunk', 'service_tier': 'default', 'system_fingerprint': 'fp_13eed4fce1', 'usage': None}
(以下略)
ToolCallResult イベントを受け取り、呼ばれたツール名・ツールに渡された引数・ツールのアウトプットの出力例
search_web
{'query': '東京 現在の天気'}
{'query': '東京 現在の天気', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': '東京, 東京都, 日本の現在の天気概況 | AccuWeather', 'url': 'https://www.accuweather.com/ja/jp/tokyo/226396/current-weather/226396', 'content': '東京, 東京都, 日本の現在の天気概況 | AccuWeather 東京, 東京都 ======= 46°F 東京 東京都 46° 今日 WinterCast ローカル{stormName}トラッカー 毎時 毎日 レーダー MinuteCast 月間 大気質 健康・アクティビティ 今日 毎時 毎日 レーダー MinuteCast 月間 大気質 健康・アクティビティ To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5video Video Player is loading. Current Time\xa00:00 Loaded: 0% Seek to live, currently behind liveLIVE descriptions off, selected Subtitles subtitles settings, opens subtitles settings dialog subtitles off, selected This is a modal window. Beginning of dialog window. Text BackgroundColorOpacity Font Size Font Family Close Modal DialogEnd of dialog window. RealFeel® 41° 41° 10 mph RealFeel® 52° 突風28 mph RealFeel® 25° 突風24 mph 51° 33° 48° 38° 52° 36° 世界 アジア 日本 東京都 東京', 'score': 0.8259413, 'raw_content': None}, {'title': '東京(東京)の天気 - Yahoo!天気・災害', 'url': 'https://weather.yahoo.co.jp/weather/jp/13/4410.html', 'content': 'パーソナル天気 現在位置: 天気・災害トップ > 関東・信越 > 東京都 > 東京(東京) 大雪に警戒\u3000交通影響や今後の見通し ・大雪への備え ・雨雪レーダー ・警報・注意報 ・運行情報 ・週間天気 今日明日の天気 2025年2月8日 10時00分発表 2月8日(土) 晴れ 9℃[-1] 1℃[0] 時間  0-6 6-12    12-18   18-24 降水  --- 10% 10% 10% 風: 北西の風日中やや強く23区西部でははじめ北西の風強く 波: 2メートル後1メートル 2月9日(日) 晴れ 11℃[+2] 3℃[+2] 時間  0-6 6-12    12-18   18-24 降水  0%  0%  10% 0% 風: 北西の風後北の風23区西部では北西の風やや強く 波: 1メートル 東京地方の警報・注意報 注意報 強風、波浪、乾燥 東京都の花粉情報 2025年2月8日 7時00分 発表 2/8(土) 少ない 2/9(日) 少ない 花粉の詳しい情報を確認 週間天気 2025年2月8日 8時00分発表 日付 2月10日 (月) 2月11日 (火) 2月12日 (水) 2月13日 (木) 2月14日 (金) 2月15日 (土) 降水 2025年2月8日 10時00分 発表 (C) Mapbox (C) OpenStreetMap (C) LY Corporation Yahoo!地図ガイドライン (C) Mapbox (C) OpenStreetMap (C) LY Corporation Yahoo!地図ガイドライン 2/8(土)9時\u3000寒波ピーク 新たな大雪災害に警戒\u3000あす朝にかけてさらに70〜80センチの降雪予想 Copyright (C) 2025 Weather Map Co., Ltd. All Rights Reserved. © LY Corporation', 'score': 0.8184036, 'raw_content': None}, {'title': '東京都の実況天気 - 日本気象協会 tenki.jp', 'url': 'https://tenki.jp/live/3/16/', 'content': '東京都の実況天気 - 日本気象協会 tenki.jp tenki.jpトップ| サイトマップ| ヘルプ tenki.jp PM2.5分布予測 雷(予報) 雷(実況) PM2.5分布予測 東京都  東京都の実況天気12日15:00現在 東京都 各地の実況天気12日15:00現在 | 名前 | 天気 | 気温 | 最高/最低気温 | 湿度 | 降水量(3時間) | | 東京都心(大手町) |  | 晴れ | 11.1℃ | 11.6℃\xa0/\xa00.4℃ | 58% | 0.0mm | | 三宅島 |  | 曇り | 12.0℃ | 13.6℃\xa0/\xa04.4℃ | 69% | 0.0mm | | 八丈島 |  | 曇り | 12.4℃ | 13.0℃\xa0/\xa05.9℃ | 69% | 0.0mm | 雨雲 PM2.5 アメダスランキング(東京都) 降水量 気温(高温順) 24時間 東北は今夜から雨のち暴風や吹雪 なだれや交通への影響に注意・警戒 02/12(水)17:06 13日は日本海側で猛吹雪のおそれ 週末は一時的に寒さ緩む 来週は再び強烈寒波 02/12(水)16:58 19日にかけて近畿は気圧・気温の変化が大きい 季節の変わり目の体調不良に注意 02/12(水)16:25 13日 北日本は暴風雪や大雪に警戒 太平洋側も北風が強まる 関東も夜は気温急降下 02/12(水)15:52 tenki.jp公式SNS tenki.jp公式アプリ tenki.jp tenki.jp 登山天気 tenki.jpトップ PM2.5分布予測 雷(予報) 雨雲(過去) 雷(実況) 花粉飛散情報(1~5月頃) 桜開花情報(2~5月頃) GWの天気(4~5月頃) ヒートショック(10~3月頃) Copyright (C) 2025 日本気象協会 All Rights Reserved. Copyright (C) 2025 ALiNKインターネット All Rights Reserved.', 'score': 0.78334314, 'raw_content': None}, {'title': '東京都の天気 - Yahoo!天気・災害', 'url': 'https://weather.yahoo.co.jp/weather/jp/13/', 'content': 'パーソナル天気 現在位置: 天気・災害トップ > 関東・信越 > 東京都 大雪に警戒\u3000交通影響や今後の見通し ・大雪への備え ・雨雪レーダー ・警報・注意報 ・運行情報 ・週間天気 東京都の天気 2月8日 10時00分発表 8(土) 9(日) 10(月) 11(火) 12(水) 13(木) 14(金) 15(土) 関東・信越 東京 大島 八丈島 父島 都道府県概況 日本付近は強い冬型の気圧配置となっています。 東京地方は、晴れています。 8日は、強い冬型の気圧配置となり、寒気の影響を受ける見込みです。このため、晴れ昼過ぎから夕方曇りとなるでしょう。 9日は、引き続き強い冬型の気圧配置となり、寒気の影響を受ける見込みです。このため、晴れ昼過ぎから時々曇りとなるでしょう。 【関東甲信地方】関東甲信地方は、晴れや曇りで、雪や雨の降っている所があります。 8日は、強い冬型の気圧配置となり、寒気の影響を受ける見込みです。このため、晴れや曇りで、長野県や関東地方北部の山沿いでは雷を伴い雪が降り、伊豆諸島では、雨や雪の降る所があるでしょう。 9日は、引き続き強い冬型の気圧配置となり、寒気の影響を受ける見込みです。このため、晴れや曇りで、長野県や関東地方北部の山沿いでは雪が降り、伊豆諸島では、雨や雪の降る所があるでしょう。 関東地方と伊豆諸島の海上では、うねりを伴い、8日は大しけとなり、9日はしける見込みです。船舶は高波に警戒してください。 雨雲レーダー (C) Mapbox (C) OpenStreetMap (C) LY Corporation Yahoo!地図ガイドライン 天気図 気象衛星 東京都エリアの情報 防災情報 警報・注意報 台風 土砂災害マップ 洪水マップ 河川水位 火山 地震 津波 避難情報 避難場所マップ 停電情報 災害カレンダー 防災手帳 防災速報 天気ガイド 天気予報 気象衛星 天気図 アメダス 雨雲レーダー 雷レーダー 週間天気 長期予報 波予測 風予測 潮汐情報 世界の天気 花粉情報 雨雪レーダー 過去の天気(外部サイト) 再生する 2/8(土)9時\u3000寒波ピーク 新たな大雪災害に警戒\u3000あす朝にかけてさらに70〜80センチの降雪予想 レジャー施設 空港 ゴルフ場 釣り・潮汐情報 キャンプ場 マリン 野球場 テーマパーク 競馬場 サッカー場・競技場 プライバシーポリシー - プライバシーセンター - 利用規約 - ご意見・ご要望 - 広告掲載について - ヘルプ・お問い合わせ Copyright (C) 2025 Weather Map Co., Ltd. All Rights Reserved. © LY Corporation', 'score': 0.7825466, 'raw_content': None}, {'title': '東京都の天気 - 日本気象協会 tenki.jp', 'url': 'https://tenki.jp/forecast/3/16/?id=685', 'content': '東京都の天気 - 日本気象協会 tenki.jp tenki.jpトップ| サイトマップ| ヘルプ tenki.jp 雨雲レーダー PM2.5分布予測 雷(予報) 雨雲レーダー(過去) 雷(実況) 気象予報士のポイント解説 雨雲レーダー PM2.5分布予測 06(木) 13(木) 06日15:00発表02月06日(木) 千代田区  10/0 20% 新宿区  9/-1 20% 世田谷区  9/-1 20% 三鷹市  9/-2 20% 八王子市  10/-2 10% 青梅市  8/-2 20% 大島町  8/2 0% 八丈町  9/3 30% 小笠原村  18/16 50% 練馬区  8/-2 20% 台東区  9/0 20% 品川区  9/-1 10% 町田市  8/-2 0% 立川市  9/-1 20% 気象予報士のポイント解説(日直予報士) 寒波はいつまで 02/06(木)13:46 大寒波の影響 6日正午の気温は東京8℃台 今夜も極寒 水道管・路面の凍結に注意 02/06(木)12:46 最強寒波 7日~8日は再び大雪のピーク 名古屋や大阪も雪か 厳しい寒さいつまで 02/06(木)11:50 千代田区 雨雲 PM2.5 天気予報アプリ-tenki.jp アプリで天気を確認!無料で48時間先までの雨雲レーダーが使える tenki.jpの公式X(旧Twitter) 最新の気象・防災情報や、生活に役立つ情報を毎日リアルタイムに配信中! 雨雲レーダー PM2.5 雷(予報) 雨雲レーダー(過去) 雷(実況) tenki.jp公式SNS tenki.jp公式アプリ tenki.jp tenki.jp 登山天気 tenki.jpトップ 雨雲レーダー PM2.5分布予測 雷(予報) 雨雲(過去) 雷(実況) 花粉飛散情報(1~5月頃) 桜開花情報(2~5月頃) GWの天気(4~5月頃) 梅雨入り・明け(5~7月頃) 熱中症情報(4~9月頃) 紅葉見頃情報(10~11月頃) ヒートショック(10~3月頃) スキー積雪情報(11~5月頃) 初日の出(12~1月頃) Copyright (C) 2025 日本気象協会 All Rights Reserved. Copyright (C) 2025 ALiNKインターネット All Rights Reserved.', 'score': 0.7038554, 'raw_content': None}], 'response_time': 1.8}

状態設定

過去の状態だけでなく、初期状態(initial_state)を設定し、初期状態とコンテキスト(Context)を介して、各エージェントのツールから共通のメモリにアクセスすることも可能です。

from llama_index.core.workflow import Context


async def set_name(ctx: Context, name: str) -> str:
    state = await ctx.get("state") # 状態の取得
    state["name"] = name # "name" に値を設定
    await ctx.set("state", state) # Contextに反映
    return f"Nameが{name}にセットされました。"


workflow = AgentWorkflow.from_tools_or_functions(
    [set_name],
    llm=llm,
    system_prompt="あなたは名前を設定できる有用なアシスタントです。",
    initial_state={"name": "unset"}, # 初期状態の設定
)

ctx = Context(workflow)

response = await workflow.run(user_msg="私の名前はmasakuriです。", ctx=ctx)
print(str(response))
# あなたの名前は「masakuri」に設定されました。

state = await ctx.get("state")
print(state)
# {'name': 'masakuri'} # "name" が "unset" から "masakuri" に更新。

Human in the Loop

重要な意思決定・実行を行う際、AIエージェントにすべてを任せることは危険が伴う可能性があります。
そこで、人間に実行の確認を求める機構を入れることは重要です。
AgentWorkflowを使って実現する方法を見ていきましょう。

from llama_index.core.workflow import (
    Context,
    InputRequiredEvent, # ユーザーからの入力を要求するイベント
    HumanResponseEvent, # ユーザーからの入力を受け取るイベント
)


async def dangerous_task(ctx: Context) -> str: # "危険なタスク"の定義
    """A dangerous task that requires human confirmation."""
    ctx.write_event_to_stream(
        InputRequiredEvent( # ユーザーの許可を得る。
            prefix="Are you sure you want to proceed?",
            user_name="Logan",
        )
    )

    response = await ctx.wait_for_event( # ユーザーからの入力を待つ。
        HumanResponseEvent, requirements={"user_name": "Logan"}
    )
    if response.response == "yes": # "yes"の返答なら実行
        return "Dangerous task completed successfully."
    else: # "yes"以外の返答なら実行されない。
        return "Dangerous task aborted."


workflow = AgentWorkflow.from_tools_or_functions( # `dangerous_task`を実行するAgentWorkflowの定義
    [dangerous_task], 
    llm=llm,
    system_prompt="You are a helpful assistant that can perform dangerous tasks.",
)

各行にコメントを記載しているので実装を見てみてください。
では、実行してみましょう[3]

handler = workflow.run(user_msg="I want to proceed with the dangerous task.")

async for event in handler.stream_events():
    if isinstance(event, InputRequiredEvent):
        response = input(event.prefix).strip().lower()
        handler.ctx.send_event(
            HumanResponseEvent(
                response=response,
                user_name=event.user_name,
            )
        )

response = await handler
print(str(response))
# Are you sure you want to proceed? yes("yes"の返答をした場合)
# The dangerous task has been completed successfully. If you need anything else, feel free to ask!

# Are you sure you want to proceed? no("no"の返答をした場合)
# The dangerous task has been aborted. If you would like to proceed with it, please confirm your decision.

応用: サーベイを行うマルチエージェントワークフローの構築

ここからは前節の基本を応用して、AgentWorkflowを用いたマルチエージェントワークフローの構築方法を見ていきましょう。
特定トピックの調査、レポート作成を行うエージェントを作成します[4]

システムデザイン

3つのエージェントからなるシステムを構築します。

  • ResearchAgent
    与えられたトピックに関してWeb検索を行い調査するエージェント
  • WriteAgent
    ResearchAgentが収集した情報を元にレポートを作成するエージェント
  • ReviewAgent
    WriteAgentが作成したレポートをレビューし、フィードバックを提供するエージェント

上記エージェントを作成する上で必要となるツールを今回は4つ定義します。

  • web_search
    与えられたトピックに関してWeb検索を行うツール
  • record_notes
    与えられたトピックに関するメモを残すツール
  • write_report
    ResearchAgentが収集した情報を元にレポートを作成するツール
  • review_report
    レポートをレビューし、フィードバックを提供するツール

ツール定義

ツールの定義は以下のとおりです。

from tavily import AsyncTavilyClient
from llama_index.core.workflow import Context
from google.colab import userdata
from llama_index.llms.openai import OpenAI

OPENAI_API_KEY = userdata.get('OPENAI_API_KEY')
TAVILY_API_KEY = userdata.get('TAVILY_API_KEY')

llm = OpenAI(model="gpt-4o-mini", api_key=OPENAI_API_KEY)

async def search_web(query: str) -> str: # Web検索を行うツールの定義
    """Useful for using the web to answer questions."""
    client = AsyncTavilyClient(api_key=TAVILY_API_KEY)
    return str(await client.search(query))


async def record_notes(ctx: Context, notes: str, notes_title: str) -> str: # メモを残すツールの定義
    """Useful for recording notes on a given topic. Your input should be notes with a title to save the notes under."""
    current_state = await ctx.get("state") # 現在の状態を取得
    if "research_notes" not in current_state: # 現在の状態が "research_notes" でなければ "research_notes" の状態を空に。
        current_state["research_notes"] = {}
    current_state["research_notes"][notes_title] = notes # メモ(notes)を現在の状態に反映
    await ctx.set("state", current_state) # Contextに反映
    return "Notes recorded."


async def write_report(ctx: Context, report_content: str) -> str: # レポートを作成するツール
    """Useful for writing a report on a given topic. Your input should be a markdown formatted report."""
    current_state = await ctx.get("state") # 現在の状態を取得
    current_state["report_content"] = report_content # レポート(report_content)を現在の状態に反映
    await ctx.set("state", current_state) # Contextに反映
    return "Report written."


async def review_report(ctx: Context, review: str) -> str: # レポートのレビューをするツール
    """Useful for reviewing a report and providing feedback. Your input should be a review of the report."""
    current_state = await ctx.get("state") # 現在の状態を取得
    current_state["review"] = review # レビュー(review)を現在の状態に反映
    await ctx.set("state", current_state) # Contextに反映
    return "Report reviewed."

エージェント定義

ツールの定義ができたので、エージェントを作成しましょう。
LlamaIndexではいくつかのエージェントクラスがありますが、LLMが関数呼び出し機能をサポートし利用する場合は FunctionAgent クラスを、利用しない場合は汎用的に利用可能な ReActAgent クラスを利用するとよいようです。

from llama_index.core.agent.workflow import FunctionAgent, ReActAgent

research_agent = FunctionAgent( # Web検索を行い調査するエージェントの定義
    name="ResearchAgent", # エージェント名
    description="Useful for searching the web for information on a given topic and recording notes on the topic.", # エージェントの説明
    system_prompt=( # エージェントのシステムプロンプト
        "You are the ResearchAgent that can search the web for information on a given topic and record notes on the topic. "
        "Once notes are recorded and you are satisfied, you should hand off control to the WriteAgent to write a report on the topic. "
        "You should have at least some notes on a topic before handing off control to the WriteAgent."
    ),
    llm=llm, # エージェントが使用するLLMの指定
    tools=[search_web, record_notes], # エージェントが使用可能なツールの指定
    can_handoff_to=["WriteAgent"], # エージェントが処理を引き渡せるエージェントの指定。デフォルトはすべてのエージェントとなる。
)

write_agent = FunctionAgent( # レポートを作成するエージェント
    name="WriteAgent",
    description="Useful for writing a report on a given topic.",
    system_prompt=(
        "You are the WriteAgent that can write a report on a given topic. "
        "Your report should be in a markdown format. The content should be grounded in the research notes. "
        "Once the report is written, you should get feedback at least once from the ReviewAgent."
    ),
    llm=llm,
    tools=[write_report],
    can_handoff_to=["ReviewAgent", "ResearchAgent"],
)

review_agent = FunctionAgent( # レビューを行うエージェント
    name="ReviewAgent",
    description="Useful for reviewing a report and providing feedback.",
    system_prompt=(
        "You are the ReviewAgent that can review the write report and provide feedback. "
        "Your review should either approve the current report or request changes for the WriteAgent to implement. "
        "If you have feedback that requires changes, you should hand off control to the WriteAgent to implement the changes after submitting the review."
    ),
    llm=llm,
    tools=[review_report],
    can_handoff_to=["WriteAgent"],
)

ワークフロー作成

エージェントが定義できたので、AgentWorkflow を作成しましょう。

from llama_index.core.agent.workflow import AgentWorkflow

agent_workflow = AgentWorkflow(
    agents=[research_agent, write_agent, review_agent], 
    root_agent=research_agent.name, # ルートエージェント(最初に呼び出されるエージェント)の指定
    initial_state={ # 初期状態の設定
        "research_notes": {},
        "report_content": "Not written yet.",
        "review": "Review required.",
    },
)

実行

では、実行してみましょう!
途中経過を確認するために、ストリーミングを使って逐次状況を確認します。

from llama_index.core.agent.workflow import (
    AgentInput,
    AgentOutput,
    ToolCall,
    ToolCallResult,
    AgentStream,
)

handler = agent_workflow.run(
    user_msg=(
        "インターネットの歴史について調査し、レポートを書いてください。インターネットの発展、ウェブの発展、21世紀のインターネットの発展など、インターネットの歴史についてまとめてください。"
    )
)

current_agent = None
current_tool_calls = ""
async for event in handler.stream_events():
    if (
        hasattr(event, "current_agent_name")
        and event.current_agent_name != current_agent
    ):
        current_agent = event.current_agent_name
        print(f"\n{'='*50}")
        print(f"🤖 Agent: {current_agent}")
        print(f"{'='*50}\n")

    # if isinstance(event, AgentStream):
    #     if event.delta:
    #         print(event.delta, end="", flush=True)
    # elif isinstance(event, AgentInput):
    #     print("📥 Input:", event.input)
    elif isinstance(event, AgentOutput):
        if event.response.content:
            print("📤 Output:", event.response.content)
        if event.tool_calls:
            print(
                "🛠️  Planning to use tools:",
                [call.tool_name for call in event.tool_calls],
            )
    elif isinstance(event, ToolCallResult):
        print(f"🔧 Tool Result ({event.tool_name}):")
        print(f"  Arguments: {event.tool_kwargs}")
        print(f"  Output: {event.tool_output}")
    elif isinstance(event, ToolCall):
        print(f"🔨 Calling Tool: {event.tool_name}")
        print(f"  With arguments: {event.tool_kwargs}")

実行結果は以下「実行結果」を展開してご確認ください。
3つのエージェント(ResearchAgent, WriteAgent, ReviewAgent)がそれぞれ管理するツールを実行し、調査・レポート作成・レビューを行ってくれました。

実行結果

==================================================
🤖 Agent: ResearchAgent
==================================================

🛠️  Planning to use tools: ['search_web']
🔨 Calling Tool: search_web
  With arguments: {'query': 'インターネットの歴史 発展 ウェブ 21世紀'}
🔧 Tool Result (search_web):
  Arguments: {'query': 'インターネットの歴史 発展 ウェブ 21世紀'}
  Output: {'query': 'インターネットの歴史 発展 ウェブ 21世紀', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': 'インターネットの簡単な歴史: 始まりから現在まで', 'url': 'https://informatecdigital.com/ja/インターネットの始まりから現在までの簡単な歴史/', 'content': 'ティム・バーナーズ=リーとワールド・ワイド・ウェブ. 変革的な発展 ... La インターネットの歴史 21 世紀は、前例のない技術の進歩と社会の変革を特徴とする新しい時代に入りました。このセクションでは、インターネットが日常生活のいたるところに見', 'score': 0.82391036, 'raw_content': None}, {'title': 'もうすぐ21世紀! インターネットの歴史を振り返る', 'url': 'https://internet.watch.impress.co.jp/www/article/2000/0105/history.htm', 'content': '21世紀まであと1年弱となった。そこで、20世紀半ばに始まるインターネットの歴史を振りかえってみよう。 ... 日本政府のWebサーバー「www.kantei.go', 'score': 0.6466616, 'raw_content': None}, {'title': 'インターネットの旅 誕生から未来へ紐解くデジタル革命 - Itとpcに関連する用語の解説', 'url': 'https://it-notes.stylemap.co.jp/webservice/インターネットの旅\u3000誕生から未来へ紐解くデジ/', 'content': 'Webの発展とブラウザの役割 ... モバイルインターネットの普及は、特に21世紀に入ってから顕著です。 ... このように、インターネットの歴史は情報のやり取りから始まり、未来には新たな技術やサービスによって私たちの生活を豊かにしていくでしょう。', 'score': 0.63723457, 'raw_content': None}, {'title': 'インターネットの歴史 | ホームページ制作会社nttタウンページ | デジタルリード', 'url': 'https://www.ntttp-dlead.com/homepage-sakusei-blog/homepage-knowledge/internet-history.html', 'content': 'インターネットの歴史は、半世紀に満たない短期間で急速な進化を遂げ、私たちの生活に深く根付いてきました。 ARPANETから始まったネットワークの発展は、通信速度の向上や利用環境の変化により、一般ユーザーにも普及していきました。', 'score': 0.59061456, 'raw_content': None}, {'title': 'インターネットの歴史をわかりやすく解説!世界と日本の通信事情まとめ | GeeklyMedia (ギークリーメディア) | Geekly ...', 'url': 'https://www.geekly.co.jp/column/cat-technology/1903_085/', 'content': 'インターネットの歴史をわかりやすく解説! ... このネットワークが現在のインターネットの元であり、その後の爆発的な発展のきっかけでもあります。 ... 21世紀に入るとitバブルというものが起こりました。', 'score': 0.5066652, 'raw_content': None}], 'response_time': 1.78}
🛠️  Planning to use tools: ['record_notes', 'record_notes']
🔨 Calling Tool: record_notes
  With arguments: {'notes': 'インターネットの歴史は、1960年代のARPANETから始まり、1980年代にTCP/IPプロトコルが導入され、1990年代にはワールド・ワイド・ウェブが登場しました。これにより、インターネットは一般ユーザーに普及し、情報の共有が容易になりました。21世紀に入ると、モバイルインターネットの普及やソーシャルメディアの発展が見られ、インターネットは私たちの生活の中心となりました。特に、ITバブルやクラウドコンピューティング、IoT(モノのインターネット)などの新しい技術が登場し、インターネットの利用方法が多様化しています。', 'notes_title': 'インターネットの歴史'}
🔧 Tool Result (record_notes):
  Arguments: {'notes': 'インターネットの歴史は、1960年代のARPANETから始まり、1980年代にTCP/IPプロトコルが導入され、1990年代にはワールド・ワイド・ウェブが登場しました。これにより、インターネットは一般ユーザーに普及し、情報の共有が容易になりました。21世紀に入ると、モバイルインターネットの普及やソーシャルメディアの発展が見られ、インターネットは私たちの生活の中心となりました。特に、ITバブルやクラウドコンピューティング、IoT(モノのインターネット)などの新しい技術が登場し、インターネットの利用方法が多様化しています。', 'notes_title': 'インターネットの歴史'}
  Output: Notes recorded.
🔨 Calling Tool: record_notes
  With arguments: {'notes': '1. ARPANETの誕生(1960年代)\n2. TCP/IPプロトコルの導入(1980年代)\n3. ワールド・ワイド・ウェブの登場(1990年代)\n4. モバイルインターネットの普及(21世紀)\n5. ソーシャルメディアの発展(21世紀)\n6. ITバブルとクラウドコンピューティング(21世紀)\n7. IoTの進展(21世紀)', 'notes_title': 'インターネットの発展の主要な出来事'}
🔧 Tool Result (record_notes):
  Arguments: {'notes': '1. ARPANETの誕生(1960年代)\n2. TCP/IPプロトコルの導入(1980年代)\n3. ワールド・ワイド・ウェブの登場(1990年代)\n4. モバイルインターネットの普及(21世紀)\n5. ソーシャルメディアの発展(21世紀)\n6. ITバブルとクラウドコンピューティング(21世紀)\n7. IoTの進展(21世紀)', 'notes_title': 'インターネットの発展の主要な出来事'}
  Output: Notes recorded.
🛠️  Planning to use tools: ['handoff']
🔨 Calling Tool: handoff
  With arguments: {'to_agent': 'WriteAgent', 'reason': 'レポートを書くための十分な情報が収集されました。'}
🔧 Tool Result (handoff):
  Arguments: {'to_agent': 'WriteAgent', 'reason': 'レポートを書くための十分な情報が収集されました。'}
  Output: Agent WriteAgent is now handling the request due to the following reason: レポートを書くための十分な情報が収集されました。.
Please continue with the current request.

==================================================
🤖 Agent: WriteAgent
==================================================

🛠️  Planning to use tools: ['write_report']
🔨 Calling Tool: write_report
  With arguments: {'report_content': '# インターネットの歴史\n\n## 1. ARPANETの誕生(1960年代)\nインターネットの歴史は、1960年代にアメリカ国防総省のARPANET(Advanced Research Projects Agency Network)から始まりました。このネットワークは、異なるコンピュータ間での情報共有を目的として開発されました。\n\n## 2. TCP/IPプロトコルの導入(1980年代)\n1980年代には、TCP/IP(Transmission Control Protocol/Internet Protocol)が導入され、異なるネットワークを接続するための標準プロトコルとなりました。これにより、インターネットは急速に拡大し、さまざまなネットワークが相互接続されるようになりました。\n\n## 3. ワールド・ワイド・ウェブの登場(1990年代)\n1990年代には、ティム・バーナーズ=リーによってワールド・ワイド・ウェブ(WWW)が開発され、インターネットの利用が一般ユーザーに広がりました。ウェブブラウザの登場により、情報の検索や共有が容易になりました。\n\n## 4. モバイルインターネットの普及(21世紀)\n21世紀に入ると、スマートフォンやタブレットの普及に伴い、モバイルインターネットが急速に発展しました。これにより、いつでもどこでもインターネットにアクセスできるようになり、生活様式が大きく変わりました。\n\n## 5. ソーシャルメディアの発展(21世紀)\nソーシャルメディアの登場により、人々は情報を共有し、コミュニケーションを取る方法が変わりました。FacebookやTwitterなどのプラットフォームは、個人や企業にとって重要な情報発信の場となっています。\n\n## 6. ITバブルとクラウドコンピューティング(21世紀)\n2000年代初頭にはITバブルが発生し、多くのインターネット関連企業が急成長しました。その後、クラウドコンピューティングの普及により、データの保存や処理がインターネット上で行えるようになりました。\n\n## 7. IoTの進展(21世紀)\n最近では、IoT(モノのインターネット)が注目されています。さまざまなデバイスがインターネットに接続され、データの収集や分析が行われることで、私たちの生活がさらに便利になっています。\n\n## 結論\nインターネットの歴史は、技術の進歩と社会の変革を反映しています。今後も新たな技術やサービスが登場し、私たちの生活に影響を与えることでしょう。'}
🔧 Tool Result (write_report):
  Arguments: {'report_content': '# インターネットの歴史\n\n## 1. ARPANETの誕生(1960年代)\nインターネットの歴史は、1960年代にアメリカ国防総省のARPANET(Advanced Research Projects Agency Network)から始まりました。このネットワークは、異なるコンピュータ間での情報共有を目的として開発されました。\n\n## 2. TCP/IPプロトコルの導入(1980年代)\n1980年代には、TCP/IP(Transmission Control Protocol/Internet Protocol)が導入され、異なるネットワークを接続するための標準プロトコルとなりました。これにより、インターネットは急速に拡大し、さまざまなネットワークが相互接続されるようになりました。\n\n## 3. ワールド・ワイド・ウェブの登場(1990年代)\n1990年代には、ティム・バーナーズ=リーによってワールド・ワイド・ウェブ(WWW)が開発され、インターネットの利用が一般ユーザーに広がりました。ウェブブラウザの登場により、情報の検索や共有が容易になりました。\n\n## 4. モバイルインターネットの普及(21世紀)\n21世紀に入ると、スマートフォンやタブレットの普及に伴い、モバイルインターネットが急速に発展しました。これにより、いつでもどこでもインターネットにアクセスできるようになり、生活様式が大きく変わりました。\n\n## 5. ソーシャルメディアの発展(21世紀)\nソーシャルメディアの登場により、人々は情報を共有し、コミュニケーションを取る方法が変わりました。FacebookやTwitterなどのプラットフォームは、個人や企業にとって重要な情報発信の場となっています。\n\n## 6. ITバブルとクラウドコンピューティング(21世紀)\n2000年代初頭にはITバブルが発生し、多くのインターネット関連企業が急成長しました。その後、クラウドコンピューティングの普及により、データの保存や処理がインターネット上で行えるようになりました。\n\n## 7. IoTの進展(21世紀)\n最近では、IoT(モノのインターネット)が注目されています。さまざまなデバイスがインターネットに接続され、データの収集や分析が行われることで、私たちの生活がさらに便利になっています。\n\n## 結論\nインターネットの歴史は、技術の進歩と社会の変革を反映しています。今後も新たな技術やサービスが登場し、私たちの生活に影響を与えることでしょう。'}
  Output: Report written.
🛠️  Planning to use tools: ['handoff']
🔨 Calling Tool: handoff
  With arguments: {'to_agent': 'ReviewAgent', 'reason': 'レポートが完成しましたので、レビューをお願いします。'}
🔧 Tool Result (handoff):
  Arguments: {'to_agent': 'ReviewAgent', 'reason': 'レポートが完成しましたので、レビューをお願いします。'}
  Output: Agent ReviewAgent is now handling the request due to the following reason: レポートが完成しましたので、レビューをお願いします。.
Please continue with the current request.

==================================================
🤖 Agent: ReviewAgent
==================================================

🛠️  Planning to use tools: ['review_report']
🔨 Calling Tool: review_report
  With arguments: {'review': 'レポートはインターネットの歴史に関する重要な出来事を網羅しており、構成も明確です。各セクションが論理的に繋がっており、情報も適切に整理されています。特に、21世紀の発展に関する部分が具体的で、現代のインターネットの状況をよく反映しています。全体的に非常に良いレポートです。特に修正は必要ありません。'}
🔧 Tool Result (review_report):
  Arguments: {'review': 'レポートはインターネットの歴史に関する重要な出来事を網羅しており、構成も明確です。各セクションが論理的に繋がっており、情報も適切に整理されています。特に、21世紀の発展に関する部分が具体的で、現代のインターネットの状況をよく反映しています。全体的に非常に良いレポートです。特に修正は必要ありません。'}
  Output: Report reviewed.
📤 Output: レポートは非常に良く書かれており、インターネットの歴史に関する重要な出来事を網羅しています。構成も明確で、情報が適切に整理されています。特に21世紀の発展に関する部分が具体的で、現代のインターネットの状況をよく反映しています。全体的に修正は必要ありません。承認します。

最後に最終レポートを確認しましょう。

state = await handler.ctx.get("state")
print(state["report_content"])
最終レポート
# インターネットの歴史

## 1. ARPANETの誕生(1960年代)
インターネットの歴史は、1960年代にアメリカ国防総省のARPANET(Advanced Research Projects Agency Network)から始まりました。このネットワークは、異なるコンピュータ間での情報共有を目的として開発されました。

## 2. TCP/IPプロトコルの導入(1980年代)
1980年代には、TCP/IP(Transmission Control Protocol/Internet Protocol)が導入され、異なるネットワークを接続するための標準プロトコルとなりました。これにより、インターネットは急速に拡大し、さまざまなネットワークが相互接続されるようになりました。

## 3. ワールド・ワイド・ウェブの登場(1990年代)
1990年代には、ティム・バーナーズ=リーによってワールド・ワイド・ウェブ(WWW)が開発され、インターネットの利用が一般ユーザーに広がりました。ウェブブラウザの登場により、情報の検索や共有が容易になりました。

## 4. モバイルインターネットの普及(21世紀)
21世紀に入ると、スマートフォンやタブレットの普及に伴い、モバイルインターネットが急速に発展しました。これにより、いつでもどこでもインターネットにアクセスできるようになり、生活様式が大きく変わりました。

## 5. ソーシャルメディアの発展(21世紀)
ソーシャルメディアの登場により、人々は情報を共有し、コミュニケーションを取る方法が変わりました。FacebookやTwitterなどのプラットフォームは、個人や企業にとって重要な情報発信の場となっています。

## 6. ITバブルとクラウドコンピューティング(21世紀)
2000年代初頭にはITバブルが発生し、多くのインターネット関連企業が急成長しました。その後、クラウドコンピューティングの普及により、データの保存や処理がインターネット上で行えるようになりました。

## 7. IoTの進展(21世紀)
最近では、IoT(モノのインターネット)が注目されています。さまざまなデバイスがインターネットに接続され、データの収集や分析が行われることで、私たちの生活がさらに便利になっています。

## 結論
インターネットの歴史は、技術の進歩と社会の変革を反映しています。今後も新たな技術やサービスが登場し、私たちの生活に影響を与えることでしょう。

以上、AgentWorkflowを用いたマルチエージェントワークフローの構築方法を見ていきました。
マルチエージェントのアプリケーションを構築する上で、とても使い勝手が良いと感じました。

お知らせ

少しでも弊社にご興味を持っていただけた方は、お気軽にご連絡頂けますと幸いです。まずはカジュアルにお話を、という形でも、副業を検討したいという形でも歓迎しています。
https://hrmos.co/pages/zenkigen/jobs?jobType=FULL
https://speakerdeck.com/zenkigenforrecruit/detailed-version-recruitment-materials-for-data-scientists

脚注
  1. 月1000回のAPIコールが無料で、サインアップ時にクレジットカード登録なども不要で比較的簡単にアカウント作成からAPIキー取得ができます。 ↩︎

  2. AgentWorkflow.run()メソッドは非同期関数で結果を返すため、awaitを付けて呼び出しています。 ↩︎

  3. メッセージ部分を日本語とするとユーザーからの入力待ちとならない場合が結構ありました。 ↩︎

  4. 今流行りの Deep Research とまではいきませんが、その簡易版と見ることもできるでしょう ↩︎

ZENKIGENテックブログ

Discussion