第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-1〜16-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_modelsのbase_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_search、MCPClient をそのまま 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 をそのまま親 Agent の tools=[...] に渡す(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_evals の Case/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の最新ドキュメントを参照して...")
| 行 | やってること | なぜ |
|---|---|---|
① @tool(file: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_ng(file:40-46) |
直前ノード decision の実行結果文字列に "OK"/"NG" が含まれるかを判定する関数を定義 |
add_edge の condition に渡すことで、次にどのノードへ進むかを実行時の結果に応じて動的に決める |
② research→analysis/fact_check(file:57-58) |
無条件の並列分岐を定義 | research が終わったら analysis と fact_check の両方を並行して進められる構造にする |
③ all_dependencies_complete(...)(file:28-36, 59-64) |
analysis と fact_check の両方が Status.COMPLETED になるまで decision に進ませない |
ファンイン(複数ノードの合流)を条件関数で表現している。片方だけ終わった時点で先走らないようにするため |
④ decision→report if OK(file:65) |
判定がOKなら最終レポート作成ノードへ進む | 正常系の終端 |
⑤ decision→research 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.py は research_results = research(task) → analysis(f"...{research_results}") → report(f"...{analysis_results}") という素の Python 変数受け渡しで直列に繋いでいるだけで、分岐・並列・ループ・状態管理はすべて手書きが必要。14_graph.py の GraphBuilder はそれをノード・エッジ・条件関数として宣言的に表現し、並列実行・ファンイン待ち合わせ・ループをフレームワーク側が管理する。
マルチエージェント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-1〜16-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.pyはstrands.agent.a2a_agent.A2AAgentを使い、カード情報の取得だけを行う(軽量な疎通確認用と推測)。16-3_a2a_client.pyはstrands_tools.a2a_client.A2AClientToolProviderを使い、リモートエージェントを丸ごとツール群として local なAgentに注入する(実務で使う本線はこちら)。
学んだこと(要点)¶
Agentはコンストラクタ引数を増やすだけで機能が積み上がる設計(モデル・プロンプト・ツール・セッション・フック・履歴管理・構造化出力・実行方式)。第1章のような手続き的な追記が要らない。- ストリーミングの受け取り方が3通りある:①
callback_handler=None+resultから最終テキストだけ取得(03-1)、②callback_handlerにカスタム関数(同期・イベント駆動)、③agent.stream_async()をasync for(非同期ジェネレータ)。用途に応じて選べる。 conversation_manager(履歴の間引き=メモリ内ロジック)とsession_manager(永続化=ディスク保存)は明確に別の関心事として分離されている。前者はcontext_basicsのトリミング戦略のフレームワーク内蔵版、後者は第1章にはなかった「プロセスをまたいだ会話継続」を可能にする。structured_output_model=にPydanticモデルを渡すだけで型付き結果が取れる(内部でどうJSON Schema制約やツール呼び出しに変換しているかはサンプルからは読めないため断定しない)。Agentのtools=には「関数(@tool)」「既製ツール(strands_tools)」「MCPクライアントオブジェクト」「別のAgentオブジェクト」の4種類すべてを同じリストに混在させて渡せる、統一的なインターフェースになっている。BeforeToolCallEventフックは「ツールを呼ぶ前に横から検査して止める」ための拡張点で、guardrails_basicsの@wrap_tool_callallow/ask/deny と同系(手続き的=確率的ではなく構造的な防御)。ただし本章のフックはエージェント実装側の機構であり、第9章 Policy(Cedar) のゲートウェイ側での宣言的強制とはレイヤが異なる。
落とし穴・現代版に移植するなら¶
- バージョン pin:
strands-agents==1.38.0、strands-agents-tools==0.5.1、strands-agents-evals==0.1.15、boto3[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.pyのapi_key="<api key>"はプレースホルダー。実行するには OpenAI の実キーに置換が必須(README にも明記)。05_prompt.pyはカレントディレクトリのimage.jpgを読み込む前提(章フォルダ直下に同梱されている)。16-1_a2a_server.pyと16-3_a2a_client.py(+確認用の16-2)は別ターミナルでペア起動が必要。サーバを先に立ち上げないとクライアント側が接続エラーになる。07_tool_executor.pyのSequentialToolExecutorは「デフォルトの実行方式は何か」がこの1ファイルだけからは分からない(デフォルトが並列だから明示的に直列化している、と推測されるが未確認)。18_evals.pyの評価はLLM-as-judge(OutputEvaluator(rubric=...))であり、決定論的な正解チェックではない。評価結果が実行のたびに揺れうる点はeval_basicsのjudgeバイアスの議論と地続き。- 依存関係の非対称に注意: 章全体の
pyproject.tomlはopenai,a2aextra やevalsパッケージまで含むフルセットだが、handson/pyproject.tomlはハンズオンで使わない機能(OpenAI・A2A・評価)を含まない最小構成。読者が誤って章ルートの依存を見て「ハンズオンにもopenai extraが要る」と誤解しないよう留意。
記事参照¶
- 書籍 第3章「Strands Agents入門」。関連:
../../lectures/agentcore_basics/STUDY_NOTES.md(「2. Strands Agents 入門」に主要機能の早見表、「5. 既存 lectureとの対応」にマルチエージェントの対応表) - マルチエージェントの手組み版比較:
../../lectures/multi_agent/README.md(Supervisor/Swarm/Command(goto=)をLangGraphで手組みした版)
作成: 2026-07-17 / 最終更新: 2026-07-17