ENECHANGE Developer Blog

ENECHANGE開発者ブログ

AIエージェント「ブログほめ太郎」におけるStrands Agentsの活用

こんにちは、VPoTの岩本 (iwamot) です。

前回の石橋さんの記事では、手動コーディングを禁止する「LLMチャレンジウィーク」の取り組みが紹介されていました。特に印象に残ったのは「洗濯機がある時代に洗濯板で洗濯するメリットは何もない」という参加者のコメントです。ユニークな表現ですよね。

今回は、ぼくが作った「ブログほめ太郎」をご紹介します。ENECHANGE Developer Blogの新着記事をレビューし、Slackに投稿してくれるAIエージェントです。Strands Agentsを活用することで、プログラミングレス*1で実現できています。

ブログほめ太郎によるレビュー例

まず、実際のレビュー例をご覧ください。

このようなレビューを、月曜~金曜の15時に届けてくれます。「3時のおやつに、ほめ太郎」と親しんでもらえるよう意識しました。

せっかく記事を書いても、反応がないとさびしいですよね。でも、ほめ太郎がいれば大丈夫です。ほめるだけでなく深掘りの提案もしてくれます。

Lambda関数のソースコード

ブログほめ太郎は、AWSのLambda関数として実装されています。以下がソースコードの全文です。

import os

from strands import Agent
from strands.models import BedrockModel

# /var/task のままだと、slackツールのimport時にディレクトリ作成エラーになる
# https://github.com/strands-agents/tools/issues/199
os.chdir("/tmp")

from strands_tools import http_request, rss, slack, use_aws

PROMPT = f"""
あなたは技術ブログの記事を読んで、筆者を励ますレビューを書くAIエージェントです。
下記の手順でレビューし、結果をSlackに投稿してください。

1. use_awsツールを使い、最後にレビューした記事のURLをパラメータストアから取得する
  - リージョン:{os.environ['AWS_REGION']}
  - パラメータ名:{os.environ['BHT_LAST_REVIEWED_ARTICLE_PARAM']}
  - パラメータが見つからないこともある

2. RSSフィード {os.environ['BHT_RSS_URL']} を参照する
  - 最後にレビューした記事より後に公開された記事を取得する
  - ステップ1でパラメータが見つからなかった場合は、最新記事1件を取得する

3. 対象記事がなければ、レビューせずに終了する

4. 以下、公開日時の古い記事から繰り返す
  4-1. http_requestツールで当該記事の全文を参照する
  4-2. 日本語400字程度で、技術的に価値のあるレビューメッセージを生成する
    - 「<!channel> 新着記事レビューです!」から始める
    - 記事タイトルとURLを紹介する
    - 投稿に感謝する。筆者名が分かれば名前で呼びかける(会社名やチーム名は不要)
    - 絵文字や *アスタリスク1つ* を使って適度に強調する
      - 太字にしたい場合は、 *アスタリスク1つだけ* で囲むこと
      - いかなる場合も、アスタリスクの前後には必ずスペースを入れること
      - アスタリスク2つ(`**`)は絶対に使わないこと
  4-3. Slackチャンネル {os.environ['BHT_SLACK_CHANNEL_ID']} に投稿する

5. use_awsツールで、最後にレビューした記事のURLを更新する

技術的レビューの観点例:
- 読者が得られる学び
- 技術的な新規性や独自性
- さらに深掘りできる内容の提案
"""

def lambda_handler(_event, _context):
    agent = Agent(
        model=BedrockModel(cache_tools="default"),
        tools=[http_request, rss, slack, use_aws],
    )
    response = agent(PROMPT)
    return str(response)

ご覧のとおり、ほとんどプロンプトだけのシンプルな内容です。4つのツールを許可したエージェントにプロンプトを渡しているだけ。これだけで、新着記事の参照・レビューの生成・Slackへの投稿が実現できています。

なぜプログラミングレスで実現できたのか

このようにプログラミングレスでAIエージェントが作れたのは、Strands Agentsのおかげです。

Strands Agentsは、AWSがオープンソースで公開したAIエージェントのSDKです。ご覧いただいたような短いコードでAIエージェントが作れます。AWS自身の本番サービスでも使われているそうです。

aws.amazon.com

便利なツールが簡単に組み込める点も、Strands Agentsの大きな特長です。ブログほめ太郎では、下記の4つを利用しています。

ツール ブログほめ太郎での用途
http_request 記事全文の取得
rss RSSフィードの参照
slack Slackへのレビュー投稿
use_aws パラメータストアの参照・更新

これら以外にも便利なツールが多数あります。ぜひリポジトリをご確認ください。

github.com

Lambda関数を動かす設定

Lambda関数を動かすための、おもな設定内容もご紹介します。

実行ロール

Lambda関数の実行ロールには、以下のようなポリシーを設定しました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Effect": "Allow",
            "Resource": "arn:aws:logs:us-west-2:123456789012:log-group:/aws/lambda/blog-home-taro:*"
        },
        {
            "Action": [
                "bedrock:InvokeModelWithResponseStream",
                "bedrock:InvokeModel"
            ],
            "Effect": "Allow",
            "Resource": "arn:aws:bedrock:us-west-2:123456789012:inference-profile/us.anthropic.claude-sonnet-4-20250514-v1:0"
        },
        {
            "Action": [
                "bedrock:InvokeModelWithResponseStream",
                "bedrock:InvokeModel"
            ],
            "Condition": {
                "StringLike": {
                    "bedrock:InferenceProfileArn": "arn:aws:bedrock:us-west-2:123456789012:inference-profile/us.anthropic.claude-sonnet-4-20250514-v1:0"
                }
            },
            "Effect": "Allow",
            "Resource": [
                "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0",
                "arn:aws:bedrock:us-east-2::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0",
                "arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0"
            ]
        },
        {
            "Action": [
                "ssm:GetParameter",
                "ssm:PutParameter"
            ],
            "Effect": "Allow",
            "Resource": "arn:aws:ssm:us-west-2:123456789012:parameter/blog-home-taro/last-reviewed-article"
        }
    ]
}

CloudWatchログの出力・Claude Sonnet 4の呼び出し・パラメータストアの読み書きのみ許可しています。万一 use_aws ツールが暴走しても、できることは限られるので安心です。

Lambdaレイヤー

今回のLambda関数では、Lambdaレイヤーを使いました。Lambdaレイヤーは、Lambda関数の依存するライブラリなどをパッケージ化する機能です。レイヤーを使うと、関数のデプロイパッケージが小さく保てます。

ブログほめ太郎で使うレイヤーには、別途インストールの必要な rss ツールを含めてStrands Agentsをパッケージ化しました。具体的にはCodeBuildというサービスで下記のように作成しています。

version: 0.2
phases:
  install:
    runtime-versions:
      python: 3.13
  build:
    commands:
      - mkdir python
      - echo "strands-agents-tools[rss]==$STRANDS_AGENTS_VERSION" > requirements.txt
      - pip install --no-cache-dir -r requirements.txt -t python
      - find python -type d -name '__pycache__' -exec rm -rf {} +
      - zip -r layer.zip python
  post_build:
    commands:
      - aws lambda publish-layer-version
          --layer-name strands-agents-rss
          --description "Strands Agents $STRANDS_AGENTS_VERSION with RSS tool"
          --compatible-runtimes python3.13
          --compatible-architectures arm64
          --zip-file fileb://layer.zip

環境変数

Lambda関数の環境変数には、以下を設定しました。

キー
BHT_LAST_REVIEWED_ARTICLE_PARAM /blog-home-taro/last-reviewed-article
BHT_RSS_URL https://tech.enechange.co.jp/feed
BHT_SLACK_CHANNEL_ID C**********
BYPASS_TOOL_CONSENT true
SLACK_APP_TOKEN xapp-**********
SLACK_BOT_TOKEN xoxb-**********

特に BYPASS_TOOL_CONSENT が重要です。デフォルトの false のままだと、パラメータストア更新許可の入力待ちで止まってしまいます。

The operation 'put_parameter' is potentially mutative. Do you want to proceed? [y/*]

定期実行

Lambda関数を定期実行するEventBridgeスケジューラも設定しました。前述のとおり、月曜~金曜の15時に実行されるようにしています。

cron(0 15 ? * 2-6 *)

料金の目安

さて、気になるお値段です。Lambda関数の呼び出し1回につき、20円ほどになりました。レビュー対象となる記事の数や長さによって、少し増減します。

料金のほとんどは、Bedrockの入出力トークンやプロンプトキャッシュにかかる分です。Lambdaなど他のサービス分は月に数円レベルなので、無視できますね。

以下が実際の料金例です。

タイプ トークン数 1,000トークンあたりの料金 料金
入力トークン 30,430 $0.003 $0.091
出力トークン 1,794 $0.015 $0.027
プロンプトキャッシュ書き込み 3,878 $0.00375 $0.015
プロンプトキャッシュ読み込み 19,390 $0.0003 $0.006
合計 $0.139

cache_tools="default"プロンプトキャッシュを有効にしているため、シンプルに入出力するよりは安くなっています。

プログラミングレスでよいのか

率直なところ、このプログラミングレスな実装は技術的な興味から始めたものでした。「できそうなのでやってみた。やってみたらできた」という感じです。

もし料金を抑えることが重要なら、ツールを使うかわりに自分でプログラミングすべきです。たとえば下記のように実装すると、入出力トークンやプロンプトキャッシュを減らせます。

  • RSSフィードや記事の参照にHTTPクライアントを使う
  • レビューの生成にAIエージェントを使う(入出力トークンやプロンプトキャッシュを消費するのはここだけ)
  • メッセージの投稿にSlack APIを使う
  • パラメータストアの参照・更新にAWS SDK (Boto3) を使う

しかし、ブログほめ太郎にかかる料金は週に100円くらいです。前述のとおり1回約20円、週5回しか実行されません。

今回は費用が十分安いのだから、むしろユニークな例としてプログラミングレスな実装を維持すべきと考えました。AIエージェントが簡単に作れると示すのに、うってつけのコードになっているのですから。

その他、実装での気づき

今回の実装を通じ、いくつか気づいたこともありました。あくまで現時点の動作や仕様に関してですが、参考のためにまとめておきます。

  • slack ツールをimportするだけで、データ用のディレクトリが作られる。作業ディレクトリが読み取り専用だと、作れずにエラーになる
  • rss ツールで記事の内容を参照させようとすると、他の記事の内容まで見てしまうことがある(今回は http_request ツールで参照させた)
  • Bedrock AgentCoreでもAIエージェントを簡単にデプロイできるが、まだEventBridgeスケジューラからは呼び出しづらい(それで今回はLambdaを選んだ)
  • LLMは強調したい文字列を ** で囲みがち。Slackでは * で囲む必要がある(フォーマット用のツールを実装すると便利そう)

まとめ

以上、ぼくが実装した「ブログほめ太郎」についてご紹介しました。Strands Agentsを活用すれば、プログラミングレスでも実用的なAIエージェントが作れます。

このくらいの勢いでエージェントを作ってしまい、長く使いたくなったらツールへの依存を減らしていくスタイルも悪くないのではないでしょうか。

ローカル環境で動かすだけなら、Lambdaレイヤーを用意する必要もありません。依存ライブラリを pip install strands-agents-tools[rss] のようにインストールすれば、すぐに開発を始められます。

プログラミングレスなAIエージェント、ぜひ作ってみてください。


AIエージェント活用リレーブログ、次回の予定は馬場さんによる「WebディレクターがAIエージェントでROI分析を効率化した話」です。お楽しみに。

*1:この記事では「ほとんどプログラミングしないスタイルでの実装」を「プログラミングレス」と表現します。シュガーレスとノンシュガーの違いを意識したものです。