コンテンツにスキップ

第3章 Strands Agents入門 — 学習メモ

書籍「Amazon Bedrock AgentCore実践入門」第3章のサンプルコード(このフォルダ)を読み解いた個人学習メモ。 AgentCore 全体像は ../../lectures/agentcore_basics/STUDY_NOTES.md 参照(本メモはその第2節「Strands Agents 入門」を chapter3 の全24ファイル単位でさらに詳細化したもの)。 実行検証は伴わない(コードの構造から解説)。コードに現れた API 名だけ断定し、読めない挙動は「〜と推測」で明示する。

一言で

この章は「第1章で自分で書いた while ループが、Agent() というオブジェクト1つに畳み込まれる」章。 Agent(tools=[...]) を作って agent("...") と呼ぶだけで、ツール要求の検出・実行・履歴追記・再呼び出しが内部で自動化される。掴むべき対比は2つ: ①第1章の生 Bedrock(手続き型・自前ループ)↔ 本章の Strands(宣言型・Agent の引数に部品を渡すだけ)、②LangGraph(StateGraph/ノード/エッジを明示的に組む)↔ Strands(Agent() の生成だけで ReAct ループが隠蔽される、より薄いフレームワーク)。

全体像

24ファイルは README の節構成(3.2 基本 → 3.3 構成要素 → 3.4 ハンズオン → 3.6 マルチエージェント → 3.7 トレーシング/評価)に沿って並ぶ。各ファイルは基本単体実行できるが、16-116-3(A2A)だけはサーバ/クライアントのペア起動が必要。

flowchart TB
    subgraph Basic["3.2 最小構成"]
        A01["01_simple.py<br>Agent() だけ"]
    end
    subgraph Parts["3.3 構成要素(Agentの引数に足していく)"]
        A02["02_agent.py<br>model/system_prompt/tools"]
        A03["03-1〜3_invoke.py<br>callback_handler/stream_async"]
        A04["04-1〜3_model.py<br>BedrockModel/OpenAIModel"]
        A05["05_prompt.py<br>マルチモーダル入力"]
        A06["06_tools.py<br>tool デコレータ/MCPClient"]
        A07["07_tool_executor.py<br>SequentialToolExecutor"]
        A08["08_session.py<br>FileSessionManager"]
        A09["09_hook.py<br>BeforeToolCallEvent"]
        A10["10_conversation.py<br>SlidingWindowConversationManager"]
        A11["11_structured_output.py<br>Pydantic構造化出力"]
    end
    subgraph Handson["3.4 ハンズオン"]
        H["handson/main.py<br>model+tools全部乗せ"]
    end
    subgraph Multi["3.6 マルチエージェント"]
        A12["12_agents_as_tools.py"]
        A13["13_workflow.py"]
        A14["14_graph.py"]
        A15["15_swarm.py"]
        A16["16-1〜3_a2a_*.py"]
    end
    subgraph Obs["3.7 トレーシングと評価"]
        A17["17_tracing.py"]
        A18["18_evals.py"]
    end
    Basic --> Parts --> Handson --> Multi --> Obs
  • 3.2/3.3 は「1つの Agent に何を渡せるか」のカタログ(モデル・プロンプト・ツール・セッション・フック・履歴管理・構造化出力)。
  • 3.4 ハンズオンはそのカタログの実戦投入(BedrockModel + system_prompt + 2つの組み込みツール)。
  • 3.6 は「複数の Agent をどう連携させるか」の5パターン(Agents-as-Tools/Workflow/Graph/Swarm/A2A)。
  • 3.7 は「動かした結果をどう可視化・採点するか」(トレーシングと評価)で、AgentCore の Observability(第11章)・Evaluation(第12章)の手前にある Strands 単体の機能。

使用ライブラリ・原理

  • Agent オブジェクト: Agent(model=, system_prompt=, tools=[...], ...) で生成し、agent("...") という呼び出し可能オブジェクトとして使う。内部で「モデルに問い合わせ→ツール要求があれば実行→結果を履歴に追加→再問い合わせ」という第1章で手書きした while ループ相当(ReAct ループ)を自動で回す。
  • @tool デコレータ: 関数に付けるとその関数がエージェントの呼び出せるツールになる。docstring の Args: セクションからパラメータの説明を読み取り、型ヒントと合わせて JSON Schema を自動生成する(第1章で手書きしていた tool_spec の dict がコードから自動導出される)。
  • strands_tools: http_request / tavily_search / calculator / current_time など、よく使うツールの既製実装パッケージ(strands-agents-tools)。
  • モデル抽象化: model= には文字列(モデルID)か BedrockModel(...) / OpenAIModel(...) のようなモデルオブジェクトを渡せる。「モデルは差し替え可能な部品」という設計lectures/local_modelsbase_url 差し替えと同じ思想の、フレームワークレベル版)。
  • Callback / Hook: callback_handler はストリーミングイベントを受け取る関数(None にすると標準出力への逐次表示を無効化できる)。hooks.add_callback(EventClass, fn) はツール実行前後など特定タイミングに割り込むイベント駆動の拡張点。
  • Conversation / Session の分離: conversation_manager(履歴をどう間引くか=メモリ内のロジック)と session_manager(会話をどこに永続化するか=ファイル等への保存)は別の関心事として分離されている。
  • マルチエージェント5方式: Agents-as-Tools(子 Agent をツール扱い)/Workflow(素の Python で直列に手渡し)/Graph(ノード・条件付きエッジを明示)/Swarm(エージェントのリストを渡すだけで協調は自動)/A2A(別プロセス・別サーバのエージェントを HTTP 経由で呼ぶ)。下に行くほど「エージェント間の結合が疎」になる

ファイル別の役割

ファイル 役割
01_simple.py 最小構成。Agent() を作って呼ぶだけ
02_agent.py model / system_prompt / tools を渡す基本形
03-1_invoke.py callback_handler=None でストリーミング表示を止め、result.message["content"][-1]["text"] で最終回答だけ取得
03-2_invoke.py callback_handler にカスタム関数を渡し、ストリーミングイベントの生 kwargs を観察
03-3_invoke.py agent.stream_async()async for で回す非同期ストリーミング。イベント種別(message/result/data)で分岐
04-1_model.py model 未指定時はデフォルトの BedrockModel(執筆時点で Claude Sonnet 4.6)が使われる
04-2_model.py model= に文字列(モデルID)を渡す方法と、BedrockModel(model_id=, region_name=, temperature=) を明示生成する方法の2通り
04-3_model.py strands.models.openai.OpenAIModel で他社(OpenAI)モデルに差し替え
05_prompt.py system_prompt の指定と、画像+テキストのリストをそのまま agent([...]) に渡すマルチモーダル入力
06_tools.py @tool デコレータでの自作ツール、strands_tools.tavily_searchMCPClient をそのまま tools= に渡すリモートツール接続
07_tool_executor.py SequentialToolExecutor でツールの実行方式を直列に指定
08_session.py FileSessionManager で会話をファイルシステムに永続化
09_hook.py BeforeToolCallEvent フックでツール実行前にURLを検査し event.cancel_tool で遮断
10_conversation.py SlidingWindowConversationManager(window_size=10) で履歴を直近N件に制限
11_structured_output.py Pydantic モデルを structured_output_model= に渡し result.structured_output で型付き結果を取得
12_agents_as_tools.py Agent をそのまま親 Agenttools=[...] に渡す(Agents-as-Tools)
13_workflow.py 3つの Agent を素の Python で直列に手渡し(Workflow、フレームワーク機構は使わない)
14_graph.py GraphBuilder でノード・条件付きエッジ・ループバックを持つグラフを構築(Graph)
15_swarm.py Swarm([...]) に名前付き Agent のリストを渡すだけで協調させる(Swarm)
16-1_a2a_server.py A2AServer(agent).serve() でエージェントをA2Aサーバ化(デフォルト9000番ポート)
16-2_a2a_card.py A2AAgent(endpoint=...).get_agent_card() でリモートエージェントの能力(Agent Card)を取得
16-3_a2a_client.py A2AClientToolProvider(known_agent_urls=[...]) でリモートエージェントをツール化し、ローカル Agent から呼ぶ
17_tracing.py StrandsTelemetry().setup_console_exporter() でOTelトレースをコンソールに出力
18_evals.py strands_evalsCase/Experiment/OutputEvaluator(rubric=) でLLM-as-judge評価
handson/main.py 3.4節ハンズオン本体。BedrockModel + system_prompt + calculator/current_time の全部乗せ
pyproject.toml strands-agents[openai,a2a]==1.38.0 等、章全体のスニペット検証用の依存一式
handson/pyproject.toml ハンズオン用の最小依存(openai/a2a/evals extra は不要)

中心コードの読み解き

06_tools.py — ツール定義の自動化とMCPクライアントの直接接続

@tool                                              # ①
def get_weather(location: str):
    """locationの天気を取得

    Args:
        location: 都市名                           # ②
    """
    return f"{location}の天気は晴れで、気温は25℃です。"

agent = Agent(tools=[get_weather])                 # ③
agent("東京の天気を教えて")

mcp_client = MCPClient(                            # ④
    lambda: streamable_http_client("https://knowledge-mcp.global.api.aws")
)
agent = Agent(tools=[mcp_client])                  # ⑤
agent("AWSの最新ドキュメントを参照して...")
やってること なぜ
@toolfile:7 関数をエージェントが呼べるツールとして登録 第1章では tool_spec という dict を手書きしていたが、ここでは関数定義そのものがツール定義になる
② docstring の Args:file:11-12 パラメータ location の説明を記述 型ヒント(str)とこの説明文からJSON Schemaが自動生成されると推測(第1章の inputSchema.json を手で組んでいた作業に相当)
Agent(tools=[get_weather])file:17 自作ツールをエージェントに渡す リストに関数を並べるだけでツール登録が完了する、宣言的なAPI
MCPClient(lambda: streamable_http_client(...))file:25-28 外部のMCPサーバー(AWS公式ドキュメント検索)への接続を作る ラムダで遅延接続にしている(呼ばれるまでHTTP接続を張らない)と推測
Agent(tools=[mcp_client])file:30 MCPクライアントオブジェクトそのものtools に渡す MCPサーバーが公開する全ツールが自動的にエージェントから使えるようになる。個々のツールを1つずつ登録する必要がない点が @tool 単体登録との違い

このMCP接続はクライアント側の直接利用(Agentのプロセス内でMCPサーバーに繋ぐ)であり、第8章 Gateway(複数ツールをAWS側で集約しIAM認証を挟む)とは別レイヤの話である点に注意。

14_graph.py — 条件付きエッジとループバックを持つグラフ

def is_ok(state: GraphState) -> bool:              # ①
    return "OK" in str(state.results["decision"].result)

def is_ng(state: GraphState) -> bool:
    return "NG" in str(state.results["decision"].result)

builder.add_edge("research", "analysis")           # ②
builder.add_edge("research", "fact_check")
builder.add_edge("analysis", "decision",
    condition=all_dependencies_complete(["analysis", "fact_check"]))  # ③
builder.add_edge("fact_check", "decision",
    condition=all_dependencies_complete(["analysis", "fact_check"]))
builder.add_edge("decision", "report", condition=is_ok)   # ④
builder.add_edge("decision", "research", condition=is_ng) # ⑤
やってること なぜ
is_ok/is_ngfile:40-46 直前ノード decision の実行結果文字列に "OK"/"NG" が含まれるかを判定する関数を定義 add_edgecondition に渡すことで、次にどのノードへ進むかを実行時の結果に応じて動的に決める
researchanalysis/fact_checkfile:57-58 無条件の並列分岐を定義 research が終わったら analysisfact_check の両方を並行して進められる構造にする
all_dependencies_complete(...)file:28-36, 59-64 analysisfact_check両方Status.COMPLETED になるまで decision に進ませない ファンイン(複数ノードの合流)を条件関数で表現している。片方だけ終わった時点で先走らないようにするため
decisionreport if OK(file:65 判定がOKなら最終レポート作成ノードへ進む 正常系の終端
decisionresearch if NG(file:66 判定がNGなら最初の research ノードに戻る グラフが単純なDAG(有向非巡回グラフ)ではなく循環(ループ)も許容することを示す。品質が基準を満たすまでリサーチをやり直す設計

このグラフ構造をMermaidにすると次のようになる(ノード名は実コードの add_node 第2引数、条件は要約)。

flowchart TD
    research["research<br>リサーチ"] --> analysis["analysis<br>分析"]
    research --> factCheck["fact_check<br>ファクトチェック"]
    analysis -->|"analysis&fact_check<br>両方完了"| decision["decision<br>OK/NG判定"]
    factCheck -->|"analysis&fact_check<br>両方完了"| decision
    decision -->|"OK"| report["report<br>レポート作成"]
    decision -->|"NG"| research

Workflow(13_workflow.py)との対比: 13_workflow.pyresearch_results = research(task)analysis(f"...{research_results}")report(f"...{analysis_results}") という素の Python 変数受け渡しで直列に繋いでいるだけで、分岐・並列・ループ・状態管理はすべて手書きが必要。14_graph.pyGraphBuilder はそれをノード・エッジ・条件関数として宣言的に表現し、並列実行・ファンイン待ち合わせ・ループをフレームワーク側が管理する

マルチエージェント5方式の対比

方式 結合の強さ 実装のイメージ ファイル
Agents-as-Tools 強い(親が子を直接所有) Agent をそのまま tools=[research_agent] に入れる 12_agents_as_tools.py
Workflow 中(呼び出し順は手書き) 変数に結果を代入してリレー式に次のAgentへ渡す。フレームワーク機構なし 13_workflow.py
Graph 中(構造は明示、実行はフレームワーク任せ) GraphBuilder:ノード+条件付きエッジ+ループ 14_graph.py
Swarm 弱い(協調ロジックはフレームワークに委譲) 名前付き Agent のリストを Swarm([...]) に渡すだけ。誰が次に動くかはフレームワークが決めると推測 15_swarm.py
A2A 最も弱い(プロセス・サーバも別) HTTP経由。A2AServerで公開、A2AClientToolProviderでクライアント側からツール化 16-116-3

A2Aの3ファイル関係(16-2は16-3とは別クラスを使っている点に注意。lecture側の要約表には現れない細部):

sequenceDiagram
    participant Client as クライアントAgent
    participant Card as A2AAgent(card取得専用)
    participant Server as A2AServer(Calculator Agent)

    Note over Server: 16-1_a2a_server.py がserve()で待受開始
    Client->>Card: get_agent_card()
    Card->>Server: エージェントカードを問い合わせ
    Server-->>Card: name/description/skills
    Card-->>Client: AgentCardを返す
    Note over Client: 16-3ではA2AClientToolProviderが<br>known_agent_urlsからツールを自動生成
    Client->>Server: invoke_async("12x34を計算して")
    Server-->>Client: 計算結果テキスト
  • 16-2_a2a_card.pystrands.agent.a2a_agent.A2AAgent を使い、カード情報の取得だけを行う(軽量な疎通確認用と推測)。
  • 16-3_a2a_client.pystrands_tools.a2a_client.A2AClientToolProvider を使い、リモートエージェントを丸ごとツール群として local な Agent に注入する(実務で使う本線はこちら)。

学んだこと(要点)

  • Agent はコンストラクタ引数を増やすだけで機能が積み上がる設計(モデル・プロンプト・ツール・セッション・フック・履歴管理・構造化出力・実行方式)。第1章のような手続き的な追記が要らない。
  • ストリーミングの受け取り方が3通りある:①callback_handler=Noneresultから最終テキストだけ取得(03-1)、②callback_handlerにカスタム関数(同期・イベント駆動)、③agent.stream_async()async for(非同期ジェネレータ)。用途に応じて選べる。
  • conversation_manager(履歴の間引き=メモリ内ロジック)と session_manager(永続化=ディスク保存)は明確に別の関心事として分離されている。前者は context_basics のトリミング戦略のフレームワーク内蔵版、後者は第1章にはなかった「プロセスをまたいだ会話継続」を可能にする。
  • structured_output_model= にPydanticモデルを渡すだけで型付き結果が取れる(内部でどうJSON Schema制約やツール呼び出しに変換しているかはサンプルからは読めないため断定しない)。
  • Agenttools= には「関数(@tool)」「既製ツール(strands_tools)」「MCPクライアントオブジェクト」「別の Agent オブジェクト」の4種類すべてを同じリストに混在させて渡せる、統一的なインターフェースになっている。
  • BeforeToolCallEvent フックは「ツールを呼ぶ前に横から検査して止める」ための拡張点で、guardrails_basics@wrap_tool_call allow/ask/deny と同系(手続き的=確率的ではなく構造的な防御)。ただし本章のフックはエージェント実装側の機構であり、第9章 Policy(Cedar) のゲートウェイ側での宣言的強制とはレイヤが異なる。

落とし穴・現代版に移植するなら

  • バージョン pin: strands-agents==1.38.0strands-agents-tools==0.5.1strands-agents-evals==0.1.15boto3[crt]==1.42.96。Python は >=3.14
  • extras は機能ごとに個別追加が必要(uv add の対象が細かく分かれる):OpenAI連携は strands-agents[openai]、A2Aは strands-agents[a2a]、トレーシングは strands-agents[otel]、RSS/Tavily系ツールは strands-agents-tools[rss]。評価(strands-agents-evals)はさらに別パッケージ。
  • 04-3_model.pyapi_key="<api key>" はプレースホルダー。実行するには OpenAI の実キーに置換が必須(README にも明記)。
  • 05_prompt.py はカレントディレクトリの image.jpg を読み込む前提(章フォルダ直下に同梱されている)。
  • 16-1_a2a_server.py16-3_a2a_client.py(+確認用の 16-2)は別ターミナルでペア起動が必要。サーバを先に立ち上げないとクライアント側が接続エラーになる。
  • 07_tool_executor.pySequentialToolExecutor は「デフォルトの実行方式は何か」がこの1ファイルだけからは分からない(デフォルトが並列だから明示的に直列化している、と推測されるが未確認)。
  • 18_evals.py の評価はLLM-as-judge(OutputEvaluator(rubric=...))であり、決定論的な正解チェックではない。評価結果が実行のたびに揺れうる点は eval_basics のjudgeバイアスの議論と地続き。
  • 依存関係の非対称に注意: 章全体の pyproject.tomlopenai,a2a extra や evals パッケージまで含むフルセットだが、handson/pyproject.toml はハンズオンで使わない機能(OpenAI・A2A・評価)を含まない最小構成。読者が誤って章ルートの依存を見て「ハンズオンにもopenai extraが要る」と誤解しないよう留意。

記事参照


作成: 2026-07-17 / 最終更新: 2026-07-17