Closed9

AWSのエージェントフレームワーク「Strands Agents」 を試す

kun432kun432

公式サイト

https://strandsagents.com/latest/

ドキュメント(ユーザガイド)

https://strandsagents.com/latest/documentation/docs/

Strands Agents SDK

Strands Agentsは、エージェントを構築するための、コードファーストで使いやすいフレームワークです。

機能

Strands Agentsは軽量でプロダクションに対応し、多くのモデルプロバイダーとデプロイメントターゲットをサポートします。

主な機能は次のとおりです:

  • 軽量で邪魔にならない: シンプルなエージェントループは動作するだけで、完全にカスタマイズ可能です。
  • プロダクション対応: エージェントを大規模に実行するための完全な観測可能性、トレース、デプロイメントオプション。
  • モデル、プロバイダ、デプロイメントにとらわれない: Strandsは、さまざまなプロバイダのさまざまなモデルをサポートしています。
  • 強力な組み込みツール: 幅広い機能を備えたツールで、すぐに使い始めることができます。
  • マルチエージェントと自律エージェント: エージェント チームや時間の経過とともに自己改善するエージェントなど、高度な技術を AI システムに適用します。
  • 会話型、非会話型、ストリーミング型、非ストリーミング型: さまざまなワークロードに対応するあらゆるタイプのエージェントをサポートします。
  • 安全性とセキュリティを優先: データを保護しながら、責任を持ってエージェントを実行します: データを保護しながら、責任を持ってエージェントを運営する。

Python SDK GitHubレポジトリ

https://github.com/strands-agents/sdk-python

kun432kun432

Quickstart

https://strandsagents.com/latest/documentation/docs/user-guide/quickstart/

ローカルのMacで試す。

uvでプロジェクト+Python仮想環境を作成

uv init -p 3.12 my-strands-agent && cd $_

パッケージインストール

uv add strands-agents
出力
 + strands-agents==1.0.1

上記以外に開発用パッケージが2つある

  • strands-agents-tools: エージェントに与えるサンプルのツールのパッケージ
  • strands-agents-builder: Strandsエージェントとツールの構築を支援するエージェントのパッケージ(?)

とりあえずこれらも追加する

uv add strands-agents-tools strands-agents-builder
出力
 + strands-agents-builder==0.1.7
 + strands-agents-tools==0.2.1

今回はLLMは公式に従ってBedrockを使うこととするので、以下を実施する必要がある。

  • クレデンシャルの設定
    • AWS CLIがセットアップ済み
  • Bedrockモデルの有効化
    • BedrockのモデルアクセスでClaude 4を利用可能にしておく
    • 自分はこのあとAgentCoreも試そうと思っているので、AgentCoreが提供されている以下のリージョンのAnthropicモデルを全部有効化した。
      • バージニア北部
      • オレゴン
      • シドニー
      • 東京(AgentCoreはまだ来ていないが一応)

クォータもいじる必要があるのかなぁ・・・以前にも申請した記憶はあるが、とりあえずそれは躓いてから考えることにする。

次にプロジェクト構成。uvのデフォルトだとこう。

tree
出力
.
├── README.md
├── main.py
├── pyproject.toml
└── uv.lock

Strands Agentsのサンプル構成は以下となっている

出力
my_agent/
├── __init__.py
├── agent.py
└── requirements.txt

必ずこれに従う必要があるのかどうかは現時点ではわからないが、少し寄せておくことにする。

mv main.py agent.py
touch __init__.py

ではファイルを修正

__init__.py
from . import agent
agent.py
from strands import Agent, tool
from strands_tools import calculator, current_time, python_repl

# @toolデコレータを使って、Python関数をカスタムツールとして定義
@tool
def letter_counter(word: str, letter: str) -> int:
    """
    単語内の特定の文字の出現回数を数える

    Args:
        word (str): 検索対象の単語
        letter (str): 数える特定の文字

    Returns:
        int: 単語内の特定の文字の出現回数
    """
    if not isinstance(word, str) or not isinstance(letter, str):
        return 0

    if len(letter) != 1:
        raise ValueError("'letter'パラメータは単一の文字である必要がある。")

    return word.lower().count(letter.lower())

# カスタムな letter_counter ツールと同様に、
# strands-toolsパッケージのサンプルツールを指定して、
# エージェントを作成
agent = Agent(tools=[
    calculator,
    current_time,
    python_repl,
    letter_counter
])

# 利用できるツールを使って答えれる質問をエージェントに行う
message = """
4つのお願いがあります:

1. 今何時ですか?
2. 3111696 / 74088 を計算して。
3. "strawberry" という単語に 文字 "r" はいくつありますか?
4. 私が今話したことを実行するスクリプトを出力して。
   出力する前にそのスクリプトが正しく動作することを確認して。
"""
agent(message)

では実行

uv run agent.py
出力
botocore.errorfactory.ValidationException: An error occurred (ValidationException) when calling the ConverseStream operation: The provided model identifier is invalid.
└ Bedrock region: ap-northeast-1
└ Model id: us.anthropic.claude-sonnet-4-20250514-v1:0

自分のAWS CLIの設定はap-northeast-1がデフォルトになっているのだけど、どうもStrands AgentのデフォルトはUSになっている?とりあえずAWS_DEFAULT_REGIONを設定して再実行。

export AWS_DEFAULT_REGION="us-east-1"
uv run agent.py

出力された。どうやらPythonコード実行には確認が行われるみたい。あぁ、コンテナで動かせばよかったな。とりあえず y で進める。

出力
4つのお願いにお答えします。まず最初の3つから始めましょう。
Tool #1: current_time

Tool #2: calculator

Tool #3: letter_counter
それでは4つ目のお願いに答えます。あなたが今話したことを実行するスクリプトを作成し、動作確認をします。
Tool #4: python_repl
Do you want to proceed with Python code execution? [y/*]

さらに確認。こちらも yで。

出力
pytzモジュールがないようなので、標準ライブラリのみを使用してスクリプトを修正します。
Tool #5: python_repl
Do you want to proceed with Python code execution? [y/*] y

最終結果

出力
4つのお願いの実行結果:
==================================================
1. 現在時刻: 2025-07-22T19:59:15.634013
2. 3111696 / 74088 = 42.0
3. "strawberry" に文字 "r" は 3 個含まれています
4. このスクリプトが上記の処理を実行しました
4つのお願いの実行結果:
==================================================
1. 現在時刻: 2025-07-22T19:59:15.634063
2. 3111696 / 74088 = 42.0
3. "strawberry" に文字 "r" は 3 個含まれています
4. このスクリプトが上記の処理を実行しました
素晴らしい!スクリプトが正しく動作することを確認できました。

## 4つのお願いの回答:

1. **現在時刻**: 2025-07-22T10:57:41.954982+00:00 (UTC)

2. **計算結果**: 3111696 ÷ 74088 = **42**

3. **文字カウント**: "strawberry" という単語に文字 "r" は **3個** 含まれています

4. **実行スクリプト**:
```python
# 4つのお願いを実行するスクリプト(標準ライブラリのみ使用)
from datetime import datetime

def main():
    print("4つのお願いの実行結果:")
    print("=" * 50)

    # 1. 現在時刻を取得
    current_time = datetime.now()
    print(f"1. 現在時刻: {current_time.isoformat()}")

    # 2. 計算を実行
    result = 3111696 / 74088
    print(f"2. 3111696 / 74088 = {result}")

    # 3. 文字数をカウント
    word = "strawberry"
    letter = "r"
    count = word.count(letter)
    print(f'3. "{word}" に文字 "{letter}" は {count} 個含まれています')

    # 4. スクリプト自体の情報
    print("4. このスクリプトが上記の処理を実行しました")

if __name__ == "__main__":
    main()
```

このスクリプトは実際に実行して動作確認済みです。すべてのお願いが正しく処理されることを確認しています。

エージェントの推論ループについては以下に詳細がある
https://strandsagents.com/latest/documentation/docs/user-guide/concepts/agents/agent-loop/

kun432kun432

デバッグログを有効化するには strandsロガーを設定する

debug_log.py
import logging
from strands import Agent

# Strandsのデバッグログレベルを有効化
logging.getLogger("strands").setLevel(logging.DEBUG)

# ログフォーマットを設定して、標準エラー出力に出力する
logging.basicConfig(
    format="%(levelname)s | %(name)s | %(message)s",
    handlers=[logging.StreamHandler()]
)

agent = Agent()

agent("こんにちは!")
uv run debug_log.py
出力
DEBUG | strands.models.bedrock | config=<{'model_id': 'us.anthropic.claude-sonnet-4-20250514-v1:0'}> | initializing
DEBUG | strands.models.bedrock | region=<us-east-1> | bedrock client created
DEBUG | strands.tools.registry | tools_dir=</Users/kun432/work/my-strands-agent/tools> | tools directory not found
DEBUG | strands.tools.registry | tool_modules=<[]> | discovered
DEBUG | strands.tools.registry | tool_count=<0>, success_count=<0> | finished loading tools
DEBUG | strands.tools.registry | getting tool configurations
DEBUG | strands.tools.registry | tool_count=<0> | tools configured
INFO | strands.telemetry.metrics | Creating Strands MetricsClient
DEBUG | strands.tools.registry | getting tool configurations
DEBUG | strands.tools.registry | tool_count=<0> | tools configured
DEBUG | strands.event_loop.streaming | model=<<strands.models.bedrock.BedrockModel object at 0x104064bf0>> | streaming messages
DEBUG | strands.models.bedrock | formatting request
DEBUG | strands.models.bedrock | request=<{'modelId': 'us.anthropic.claude-sonnet-4-20250514-v1:0', 'messages': [{'role': 'user', 'content': [{'text': 'こんにちは!'}]}], 'system': [], 'inferenceConfig': {}}>
DEBUG | strands.models.bedrock | invoking model
DEBUG | strands.models.bedrock | got response from model
DEBUG | strands.models.bedrock | finished streaming response from model
DEBUG | strands.agent.conversation_manager.sliding_window_conversation_manager | message_count=<2>, window_size=<40> | skipping context reduction
こんにちは!お元気ですか?何かお手伝いできることがあれば、お気軽にお声かけください。
kun432kun432

モデルプロバイダを設定する。デフォルトはBedrockのClaude 4 Sonnetになっている。エージェントが使用するモデルを確認するには model.config を使う。

get_model_config.py
from strands import Agent

agent = Agent()

print(agent.model.config)
出力
{'model_id': 'us.anthropic.claude-sonnet-4-20250514-v1:0'}

モデルを変更するには以下の2つの方法がある。

  1. エージェントのコンストラクタにモデルID文字列を直接渡す
  2. 任意の設定でモデルプロバイダのインスタンスを作成する

1の場合はこう。Claude 3.5 Haikuにしてみた。自分は全然知らなかったけど、クロスリージョン推論プロファイルで指定するのね。

set_model_1.py
from strands import Agent

# モデルID文字列で特定のモデルを指定してエージェントを作成
agent = Agent(model="us.anthropic.claude-3-5-haiku-20241022-v1:0")

agent("こんにちは!")
出力
こんにちは!今日はどんなお手伝いができますか?何か楽しいことや興味のあることについて、お話ししましょう。

2の場合。こちらは細いパラメータなんかも指定してる。

set_model_2.py
import boto3
from strands import Agent
from strands.models import BedrockModel

# BedrockModelを作成
bedrock_model = BedrockModel(
    model_id="apac.anthropic.claude-sonnet-4-20250514-v1:0",
    region_name="ap-northeast-1",
    temperature=0.3,
)

agent = Agent(model=bedrock_model)

agent("こんにちは!")
出力
こんにちは!お元気ですか?何かお手伝いできることがあれば、お気軽にお声かけください。

一応Bedrock以外のモデルも使えるということで、ここではOpenAIを試してみる。

https://strandsagents.com/latest/documentation/docs/user-guide/concepts/model-providers/openai/

パッケージを更新

uv add -U 'strands-agents[openai]'

OpenAI APIキーをセット

export OPENAI_API_KEY=XXXXX
set_model_openai.py
from strands import Agent
from strands.models.openai import OpenAIModel
from strands_tools import calculator
import os

model = OpenAIModel(
    client_args={
        "api_key": os.environ["OPENAI_API_KEY"]
    },
    # **model_config
    model_id="gpt-4o",
    params={
        "max_tokens": 1000,
        "temperature": 0.7,
    }
)

agent = Agent(model=model, tools=[calculator])
agent("2 + 2 は?")
出力
Tool #1: calculator
\(2 + 2\) は \(4\) です。

詳細は各プロバイダごとにドキュメントが用意されているのでそちらを参照

kun432kun432

ストリーミングは、非同期イテレータとコールバックの2つの方法がある。

非同期イテレータの場合。FastAPI や Django Channels のような非同期フレームワーク、非同期アプリケーションの場合にはこちら。

import asyncio
from strands import Agent
from strands_tools import calculator

# コールバックハンドラを無効化してエージェントを初期化
agent = Agent(
    tools=[calculator],
    callback_handler=None  # デフォルトのコールバックハンドラを無効化
)

# 非同期関数でストリームされたエージェントイベントを反復処理
async def process_streaming_response():
    prompt = "25 * 48 は?計算過程も説明して。"

    # エージェントのレスポンスストリームを非同期イテレータとして取得
    agent_stream = agent.stream_async(prompt)

    # イベントが到着したら処理
    async for event in agent_stream:
        if "data" in event:
            # 生成されたテキストチャンクを出力
            print(event["data"], end="\n", flush=True)  # 実際は `end=""` だが説明のため改行
        elif "current_tool_use" in event and event["current_tool_use"].get("name"):
            # ツール使用情報の出力
            print(f"\n[部分的なツール使用情報: {event['current_tool_use']['name']}]")

# 非同期イベント処理でエージェントを実行
asyncio.run(process_streaming_response())
出力
25 ×
48 の計算を
行います。計算過程も
含めて説
明いたします。

[部分的なツール使用情報: calculator]

[部分的なツール使用情報: calculator]

[部分的なツール使用情報: calculator]

[部分的なツール使用情報: calculator]

[部分的なツール使用情報: calculator]
**
答え:
 1200**


**計算過程
の説明:**


25 × 48
 = 1
200 です
が、この
計算をい
くつかの
方法で説
明できます:

**
方法1:
筆算による計
算**
```

    48

  ×
25

----

240  (48
 × 5
)
  960
   (48
× 20)

  ----

1200
```


**方法
2: 分
解
による
計算**

-
 25 =
 5 ×
 5
なので

- 25
 × 48
 = 5
 × 5
 × 48
 = 5
 × (
5 ×
48) = 5
 × 240
 = 1
200

**方
法3:
100の
倍数を
利用**
-
 25 × 4
 = 100
 なので
-
48 = 4
 × 12

- 25
× 48 = 25
 × (
4 ×
12) = (25 ×
4) × 12 =
100 ×
12 = 1200

ど
の方法でも答
えは **1200** にな
ります。

コールバックハンドラの場合。

import logging
from strands import Agent
from strands_tools import calculator

# ロギングを設定
logging.basicConfig(
    format="%(levelname)s | %(name)s | %(message)s",
    handlers=[logging.StreamHandler()]
)

logger = logging.getLogger("my_agent")
logger.setLevel(logging.INFO)

# 出力の代わりにロギング用のシンプルなコールバックハンドラを定義
tool_use_ids = []
def callback_handler(**kwargs):
    if "data" in kwargs:
        # ストリームされたデータチャンクをログに出力
        logger.info(kwargs["data"])
    elif "current_tool_use" in kwargs:
        tool = kwargs["current_tool_use"]
        if tool["toolUseId"] not in tool_use_ids:
            # ツール使用情報をログに出力
            logger.info(f"\n[ツール使用: {tool.get('name')}]")
            tool_use_ids.append(tool["toolUseId"])

# コールバックハンドラを指定してエージェントを作成
agent = Agent(
    tools=[calculator],
    callback_handler=callback_handler
)

# エージェントに質問
result = agent("25 * 48 は?計算過程も説明して。")

# 最後の応答のみを出力
print(result.message)
出力
INFO | my_agent | 25 × 48の
INFO | my_agent | 計算を行います。計
INFO | my_agent | 算過程も含めて説
INFO | my_agent | 明しますね。
INFO | my_agent |
[ツール使用: calculator]
INFO | my_agent | **
INFO | my_agent | 答え: 1200**
INFO | my_agent |

**計算過程
INFO | my_agent | の説明:**
INFO | my_agent |

25 × 48
INFO | my_agent |  = 1200

この
INFO | my_agent | 計算は以下の
INFO | my_agent | ように考えること
INFO | my_agent | ができます:
INFO | my_agent |

1. **分解して
INFO | my_agent | 計算する方法:**

INFO | my_agent | - 25 × 48
INFO | my_agent | = 25 × (50
INFO | my_agent | - 2)
INFO | my_agent |
   - =
INFO | my_agent |  25 ×
INFO | my_agent |  50 -
INFO | my_agent |  25 × 2

INFO | my_agent | - = 1
INFO | my_agent | 250 -
INFO | my_agent | 50
INFO | my_agent |
   - = 1200

2
INFO | my_agent | . **別
INFO | my_agent | の分解方法:**
INFO | my_agent |
   -
INFO | my_agent | 25 × 48 = (
INFO | my_agent | 20 + 5) ×
INFO | my_agent | 48
   - = 20
INFO | my_agent | × 48 + 5
INFO | my_agent | × 48
   - =
INFO | my_agent | 960 +
INFO | my_agent | 240
   - = 1200
INFO | my_agent |

3. **
INFO | my_agent | 25の特
INFO | my_agent | 性を
INFO | my_agent | 利用した方法:**
INFO | my_agent |
   - 25
INFO | my_agent |  = 100
INFO | my_agent |  ÷
INFO | my_agent | 4なので
INFO | my_agent |
   - 25
INFO | my_agent |  × 48
INFO | my_agent |  = (
INFO | my_agent | 100 ÷ 4
INFO | my_agent | ) × 48 = 100
INFO | my_agent |  × 48
INFO | my_agent |  ÷
INFO | my_agent |  4
INFO | my_agent | = 4
INFO | my_agent | 800 ÷
INFO | my_agent |  4
INFO | my_agent | = 1200
INFO | my_agent |

ど
INFO | my_agent | の方法で
INFO | my_agent | も答えは**
INFO | my_agent | 1200**にな
INFO | my_agent | ります。
{'role': 'assistant', 'content': [{'text': '**答え: 1200**\n\n**計算過程の説明:**\n\n25 × 48 = 1200\n\nこの計算は以下のように考えることができます:\n\n1. **分解して計算する方法:**\n   - 25 × 48 = 25 × (50 - 2)\n   - = 25 × 50 - 25 × 2\n   - = 1250 - 50\n   - = 1200\n\n2. **別の分解方法:**\n   - 25 × 48 = (20 + 5) × 48\n   - = 20 × 48 + 5 × 48\n   - = 960 + 240\n   - = 1200\n\n3. **25の特性を利用した方法:**\n   - 25 = 100 ÷ 4なので\n   - 25 × 48 = (100 ÷ 4) × 48 = 100 × 48 ÷ 4 = 4800 ÷ 4 = 1200\n\nどの方法でも答えは**1200**になります。'}]}

それぞれの詳細は以下

https://strandsagents.com/latest/documentation/docs/user-guide/concepts/streaming/async-iterators/

https://strandsagents.com/latest/documentation/docs/user-guide/concepts/streaming/callback-handlers/

kun432kun432

ここまでの所感

Quickstartはほんとにシンプルなものなので、まだなんともかんとも。Human-in-the-loopが標準っぽく動いてるのは良さそうというのはあるけど。とりあえずドキュメントで各コンポーネントの中味を確認していく必要がありそう。

kun432kun432

そういえば、以前もマルチエージェントフレームワークあったよね

https://zenn.dev/kun432/scraps/bbe09fbf313632

何やら名称が変わってた。

https://github.com/awslabs/agent-squad

まあ上のはAWSLabsだし、Strands Agentsはv1.0だし(GAってことなのかな?)、ってことを踏まえると、立ち位置がどう違うのか?わからないけど、とりあえずはStrands Agents触っておけば良さそうな気はしてる。

このスクラップは2ヶ月前にクローズされました