AWS Strands Agents SDK を触ってみた感想
概要
- AWSが発表した新しいオープンソースのSDK
- AIエージェント構築用のSDKで「モデル駆動型」のアプローチをすると表現している
- 目的を達成するためにモデルとツールを使ってAgentがループし続けることに重点を置いている
- このループを可能な限り手数を少なく実装するためにモデルの使用とツールの実装を抽象化していたり、Web検索などの組み込みのツールを用意していたりする
- 将来的にA2A(Agent-to-Agent)サポートを予定しているとの記載もある
実装
とりあえず動かす
ローカルから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に動画も挙がっていたので載せておく。
Discussion