AWS re:Invent 2024でキャッチアップを早くするために AWS What's new をBedrockで要約してSlackに定期的に通知する構成を作ってみた
はじめに
こんにちは、リテールアプリ共創部の塚本です。
現在、AWS re:Invent 2024にラスベガスの現地から参加しています。
AWS re:Inventでは毎年新しいサービスや機能が多数発表されます。
今回は現地参加に備えて、What's New Feedの内容をBedrockで要約し、Slack通知できる構成を作ってみました。
AWS CDKで実装しており簡単にデプロイできるため、試せる方は自身のAWS環境でお試しください。
実現できたこと
以下の内容で、Bedrockからの出力を受け取ってslackに通知しています。
## 日本語
[ここに入力テキストの完全な日本語訳を提供してください]
## 要約
[ブログ記事の主要ポイントを簡潔に要約してください]
## ユースケース
[ブログ記事に記載機能のユースケースを検討して記載してください]
## ブログを書くためにおすすめのアクション
[このトピックについて開発者がブログを書く際に、注目すべき3〜5個の推奨アクションまたは重要ポイントをリストアップしてください]
実装
実装の紹介をします。
構成図
AWS構成を紹介します。
EventBridgeで1時間ごとにLambdaを実行し、1時間以内のFeedを取得します。
Bedrockで要約とブログ執筆に役立つ内容を出力してもらい、Slackに投稿します。
SlackのWebhook用エンドポイントURLを取得する
以下URLを参考に、SlackのWebhook用エンドポイントURLを取得します。
インフラの実装(AWS CDK)
AWS CDK(Typescript)の実装を紹介します。
主なライブラリ
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import * as dotenv from 'dotenv';
dotenv.config();
export class RssSummaryStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// EventBridgeのスケジュール実行間隔とFeedの取得間隔に使用
const FEED_FETCH_INTERVAL_HOUR = 1 as const;
// Lambda(Node.js)の定義
/**
* Note:
* Bedrockのライブラリがbundlingできない箇所でライブラリインポートをしており、Layerを利用しないと
* 使えなかったため、Layerを利用。
**/
const layer = new lambda.LayerVersion(this, 'RssSummaryLayer', {
code: lambda.Code.fromAsset('layer'),
compatibleRuntimes: [lambda.Runtime.NODEJS_20_X],
description: 'BedrockのLayer',
});
// Lambdaの定義
const rssSummaryLambda = new NodejsFunction(this, 'RssSummaryLambda', {
runtime: lambda.Runtime.NODEJS_20_X,
entry: 'src/rss-summary.ts',
environment: {
FEED_FETCH_INTERVAL_HOUR: FEED_FETCH_INTERVAL_HOUR.toString(),
BEDROCK_AWS_REGION: process.env.BEDROCK_AWS_REGION || 'ap-northeast-1',
BEDROCK_AWS_SECRET_ACCESS_KEY:
process.env.BEDROCK_AWS_SECRET_ACCESS_KEY || '',
BEDROCK_AWS_ACCESS_KEY_ID: process.env.BEDROCK_AWS_ACCESS_KEY_ID || '',
SLACK_URL: process.env.SLACK_URL || '',
},
memorySize: 512,
timeout: cdk.Duration.minutes(5),
architecture: lambda.Architecture.ARM_64,
tracing: lambda.Tracing.ACTIVE,
layers: [layer],
});
// eventBridgeの定義
const rule = new events.Rule(this, 'Rule', {
schedule: events.Schedule.rate(
cdk.Duration.hours(FEED_FETCH_INTERVAL_HOUR),
),
enabled: true,
});
rule.addTarget(new targets.LambdaFunction(rssSummaryLambda));
}
}
EventBridgeとLambdaのシンプルな構成です。
1点注意点として、使う必要のないLayerを利用しています。
const layer = new lambda.LayerVersion(this, 'RssSummaryLayer', {
code: lambda.Code.fromAsset('layer'),
compatibleRuntimes: [lambda.Runtime.NODEJS_20_X],
description: 'BedrockのLayer',
});
こちらはlangchain関連のライブラリが、esbuildでうまくbundlingできなかったため使用しています。
以下のようなエラーが発生します。
ERROR Uncaught Exception {"errorType":"Runtime.ImportModuleError","errorMessage":"Error: Cannot find module '@smithy/signature-v4'\nRequire stack:\n- /var/task/index.js\n- /var/runtime/index.mjs","stack":["Runtime.ImportModuleError: Error: Cannot find module '@smithy/signature-v4'","Require stack:","- /var/task/index.js","- /var/runtime/index.mjs"," at _loadUserApp (file:///var/runtime/index.mjs:1087:17)"," at async UserFunction.js.module.exports.load (file:///var/runtime/index.mjs:1119:21)"," at async start (file:///var/runtime/index.mjs:1282:23)"," at async file:///var/runtime/index.mjs:1288:1"]}
Lambdaの実装(Node.js)
実装を一部紹介します。
主なライブラリ
- Bedrock関連
- RSS関連
- その他汎用系
BedrockChatクラスのインスタンスを保持するためのクラスです。
export class BedrockChatClient {
bedrockChat: BedrockChat;
constructor() {
this.bedrockChat = new BedrockChat({
model: 'anthropic.claude-3-haiku-20240307-v1:0',
region: process.env.BEDROCK_AWS_REGION,
credentials: {
accessKeyId: process.env.BEDROCK_AWS_ACCESS_KEY_ID || '',
secretAccessKey: process.env.BEDROCK_AWS_SECRET_ACCESS_KEY || '',
},
// NOTE: 課金が多くなるのを防ぐため、最大トークンを設定しておく
maxTokens: 100000,
});
}
}
model: 'anthropic.claude-3-haiku-20240307-v1:0',
モデルは『Claude 3 Haiku』を利用します。
『Claude 3.5 Sonnet』を使いたかったのですが、料金節約のために今回は利用しませんでした。
コストは以下のようになっています。(2024/12/01時点)
- 入力トークン 1,000 あたりの料金: 0.00025 USD
- 出力トークン 1,000 あたりの料金: 0.00125 USD
maxTokens: 100000,
maxTokensを制限することで、過剰な課金が起こらないようにしておきます。
以下の実装は、AWS Bedrockを使用してブログ記事の分析と要約を行う関数 summaryByBedrock を定義しています。
英語のAWSブログ記事内容を入力として受け取り、それを日本語に変換し、要約し、ユースケースを分析し、さらにブログ執筆のためのアクションを提案しています。
export async function summaryByBedrock(
bedrockChatClient: BedrockChatClient,
input: string,
) {
const prompt = ChatPromptTemplate.fromMessages([
[
'system',
`あなたはAWSのブログ記事を分析する優秀なアシスタント。あなたの任務は、{input_language}から日本語に内容を翻訳し、要約し、そして記事に基づいてブログを書くためのアクションを提案すること。以下の形式で回答して:
## 日本語
[ここに入力テキストの完全な日本語訳を提供してください]
## 要約
[ブログ記事の主要ポイントを簡潔に要約してください]
## ユースケース
[ブログ記事に記載機能のユースケースを検討して記載してください]
## ブログを書くためにおすすめのアクション
[このトピックについて開発者がブログを書く際に、注目すべき3〜5個の推奨アクションまたは重要ポイントをリストアップしてください]
すべての出力を日本語で行ってください。`,
],
['human', '{input}'],
]);
const chain = prompt.pipe(bedrockChatClient.bedrockChat);
const result = await chain.invoke({
input_language: 'English',
input,
});
logger.info('Bedrock実行結果', { result });
logger.info('Bedrockの使用状況', {
inputTokens: result.response_metadata.usage.input_tokens,
outputTokens: result.response_metadata.usage.output_tokens,
});
// DIRTY: 本来はバリデーションを入れたほうが良い
return result.content as string;
}
ここでは、langchain.jsを利用しています。
トークン量を確認したかったため、ログで出力するようにしています。
logger.info('Bedrockの使用状況', {
inputTokens: result.response_metadata.usage.input_tokens,
outputTokens: result.response_metadata.usage.output_tokens,
});
トークン使用量の目安
22個のRSS記事に対して今回のツールを実行したところ、トークン使用量は以下のようになりました。
合計は以下のようになります。
- 入力トークン(13826): 約0.0035USD
- 出力トークン(23734): 約0.030USD
料金面が心配だったのですが、安心して利用し続けられる程度の課金でした。
おわりに
今回、初めてBedrockを利用したツールを作成してみました。
langchain.jsを使うことで実装が楽になったのですが、bundlingのあたりでエラーが発生したので、解決に時間を使ってしまいました。
また、今回のRSSフィードは取得できる情報のなかにリンク先の記事内容が入っていたので、それをBedrockに渡すだけで実現できました。
RSSで取得できる情報にSummaryしか書いていない場合、リンク先のHTMLをマークダウンに変換した後にBedrockに渡すような処理が必要になりそうです。
今後も最新情報を素早くキャッチアップするため、様々なRSSフィードの要約を行えるツールを作ってみようと思います!