AWS Strands Agents SDK を触ってみた感想

に公開

概要

  • AWSが発表した新しいオープンソースのSDK
  • AIエージェント構築用のSDKで「モデル駆動型」のアプローチをすると表現している
    • 目的を達成するためにモデルとツールを使ってAgentがループし続けることに重点を置いている
    • このループを可能な限り手数を少なく実装するためにモデルの使用とツールの実装を抽象化していたり、Web検索などの組み込みのツールを用意していたりする
    • 将来的にA2A(Agent-to-Agent)サポートを予定しているとの記載もある

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

実装

とりあえず動かす

ローカルからBedrockを試すだけならたった3行で実行ができる。

from strands import Agent
agent = Agent()
agent("Tell me about agentic AI")

使用するモデルを変える

defaultはClaude 3.7 Sonnet in the US Oregon (us-west-2) なので、とりあえず安いnova-microに変える。

model = BedrockModel(
    model_id="apac.amazon.nova-micro-v1:0",
    region_name='ap-northeast-1',
)
agent = Agent(
    model=model
)

組み込みツール

コンストラクタに組み込みのツールを渡すことができる。
HTTPリクエスト、ファイル操作、AWSSDKをラップしたツールでリソースにアクセスさせるツール等が組み込まれているため、汎用的なツールは自作する必要はない。

# https://github.com/strands-agents/tools
agent = Agent(
    tools=[http_request, file_read, use_aws]
)

ツールを作る場合

LangChain同様、@toolデコレータを付けてメソッドを実装してあげる

@tool
def get_user_location() -> str:
    """Get the user's location
    """

    # Implement user location lookup logic here
    return "Seattle, USA"

ワークフローを作る場合

model = BedrockModel(
    model_id="apac.amazon.nova-micro-v1:0",
    region_name='ap-northeast-1',
    max_tokens=5000
)

researcher_agent = Agent(
    system_prompt=(
        "You are a Researcher Agent that gathers information from the web. "
        "1. Determine if the input is a research query or factual claim "
        "2. Use your research tools (http_request, retrieve) to find relevant information "
        "3. Include source URLs and keep findings under 1000 words"
    ),
    callback_handler=None,
    tools=[http_request],
    model=model
)

analyst_agent = Agent(
    callback_handler=None,
    system_prompt=(
        "You are an Analyst Agent that verifies information. "
        "1. For factual claims: Rate accuracy from 1-5 and correct if needed "
        "2. For research queries: Identify 3-5 key insights "
        "3. Evaluate source reliability and keep analysis under 200 words"
    ),
    model=model
)

writer_agent = Agent(
    system_prompt=(
        "You are a Writer Agent that creates clear reports. "
        "1. For fact-checks: State whether claims are true or false "
        "2. For research: Present key insights in a logical structure "
        "3. Keep reports under 200 words with brief source mentions"
    ),
    model=model
)

def run_research_workflow(user_input):
    researcher_response = researcher_agent(
        f"Research: '{user_input}'. Use your available tools to gather information from reliable sources.",
    )
    research_findings = str(researcher_response)
    analyst_response = analyst_agent(
        f"Analyze these findings about '{user_input}':\n\n{research_findings}",
    )
    analysis = str(analyst_response)
    final_report = writer_agent(
        f"Create a report on '{user_input}' based on this analysis:\n\n{analysis}"
    )
    return final_report

def handler(event, context):
    try:
        body_str = event.get('body')
        if body_str:
            body = json.loads(body_str)
            user_input = body.get('input')
        else:
            user_input = event.get('input')

        if not user_input:
            return {
                'statusCode': 400,
                'body': json.dumps({'error': 'Missing "input" in the request body or event.'})
            }

        result = run_research_workflow(user_input)

        return {
            'statusCode': 200,
            'body': json.dumps({'result': str(result)})
        }

    except json.JSONDecodeError as e:
        return {
            'statusCode': 400,
            'body': json.dumps({'error': f'Invalid JSON in body: {str(e)}'})
        }
    except Exception as e:
        return {
            'statusCode': 500,
            'body': json.dumps({'error': str(e)})
        }

CDKでデプロイしてみる

export class InfraStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const role = new Role(this, 'LambdaExecutionRole', {
      assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
    });

    const cris = CrossRegionInferenceProfile.fromConfig({
      geoRegion: CrossRegionInferenceProfileRegion.APAC,
      model: BedrockFoundationModel.AMAZON_NOVA_MICRO_V1
    })
    cris.grantInvoke(role);

    const logGroup = new LogGroup(this, 'LogGroup', {
      logGroupName: '/aws/lambda/practice',
      retention: RetentionDays.ONE_DAY,
      removalPolicy: RemovalPolicy.DESTROY
    });
    logGroup.grantWrite(role);

    new DockerImageFunction(this, 'DockerImageFunction', {
      code: DockerImageCode.fromImageAsset(path.join(__dirname, '../lambda')),
      environment: {
        TZ: 'Asia/Tokyo',
      },
      timeout: Duration.seconds(300),
      logGroup,
      role,
      tracing: Tracing.ACTIVE,
    });
  }
}

OTelを試してみる

AIエージェントのオブザーバービリティは大事。X-Rayではなく、OTelを標準としている。
Lambdaに環境変数を追加してあげるだけでトレースの確認ができる。

      environment: {
        TZ: 'Asia/Tokyo',
        OTEL_EXPORTER_OTLP_ENDPOINT: 'https://otlp.nr-data.net:4318/v1/traces',
        STRANDS_OTEL_ENABLE_CONSOLE_EXPORT: 'true',
        OTEL_EXPORTER_OTLP_HEADERS: `api-key=${this.node.tryGetContext('newrelicApiKey')}`,
      },

感想

GoogleのAI Development Kitと同様に、Strands SDKは抽象度が高く、簡潔な記述でAIエージェントを構築できることが分かった。実装のハードルを下げる点では優れていて、特にツール連携のしやすさや作りやすさは、LangChainとか先行ツールを意識した設計に見える。

AWSはBedrockやその周辺基盤の整備に注力してきた印象が強かったので、ここに踏み込んでくるのは意外だった。ただ公式ブログを見た感じ、Amazon Q DeveloperやAWS Glueとかのチームで既に数か月前から社内で実績を積んでいたっぽい。今回のリリースはその延長線上にあるだけで、公開しただけなのかな。

ただ、公開から数日経ってもGitHubのスターやIssueの動きは控えめで、現時点ではコミュニティの盛り上がりは限定的っぽい。AWSユーザー層の関心や期待が基盤サービスの拡充や統合に向いているのかとも思ったりする。

一方で、GoogleのADKは4月にv0.1.0が出た後、わずか1ヶ月強でv1.0.0まで到達しており、開発・改善の速度がすごい。

それからADKもそうだが、LangChainとかもBreaking Changesが多い中で各社どうやって運用しているのか気になったりもする。

段々脱線してきたので終わりにする。
最後にYoutubeに動画も挙がっていたので載せておく。
https://www.youtube.com/watch?v=Ausm87d5Ry8

Discussion