はじめに
AWS Step Functionsを使うと簡単にワークフロージョブを作成することができます。
今回は最近流行りの生成AIサービスBedrockをワークフローの間に噛ませて、Bedrockで生成したテキストをSlackにメッセージ送信するまでを作ってみました。
ポイント
- Step FunctionsからAWSサービスの直接呼び出し
- JSONataを使った値の参照・抽出と変数の使用方法
- Amazon SNSとAWS ChatBotを使ったメッセージ送信(ほぼ別記事参照)
がつかめる記事になっているはずです。
準備・前提
- リージョン:バージニア北部
- Slackのワークスペース・チャンネル作成済み
- ChatBotとSNSの設定済み
のもと、進めていきます。
ChatBotとSNSについては、下記のサイトを参考にして作成できます。
ChatBotのコンソール上のテストメッセージを送信
を押下し、Slackにメッセージが届いていれば準備は完了です。
Step Functions
ここからStep Functionsの解説です。
今回はStep Functionsのステートマシン実行入力で、翻訳させたいテキストtext
と翻訳言語language
を渡してあげて、Slackに「元テキスト」・「翻訳言語」・「翻訳後テキスト」をメッセージで送信するワークフローにしました。
{
"text": "こんにちは",
"language": "イタリア語"
}
ワークフローは2ステップの簡単なものです。
最終的なステートマシンのコードはこちらです。
{
"QueryLanguage": "JSONata",
"Comment": "A description of my state machine",
"StartAt": "Bedrock InvokeModel",
"States": {
"Bedrock InvokeModel": {
"Type": "Task",
"Resource": "arn:aws:states:::bedrock:invokeModel",
"Arguments": {
"ModelId": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0",
"Body": {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 200,
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "{% $states.input.text & 'を' & $states.input.language & 'に翻訳して出力して。出力は翻訳した文だけにしてください。' %}"
}
]
}
]
}
},
"Output": {
"text": "{% $states.result.Body.content[0].text %}"
},
"Next": "SNS Publish",
"Assign": {
"inputText": "{% $states.input.text %}",
"inputLanguage": "{% $states.input.language %}"
}
},
"SNS Publish": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"End": true,
"Arguments": {
"TopicArn": "arn:aws:sns:us-east-1:654654550185:BedrockStepFunctionTestTopic",
"Message": {
"version": "1.0",
"source": "custom",
"content": {
"description": "{% '原文:' & $inputText & '\n\n' & '言語:' & $inputLanguage & '\n\n' & '翻訳結果:' & $states.input.text %}"
}
}
}
}
}
}
Step FunctionsからBedrockをinvokeするIAM RoleとSNS PublishするIAM Roleも作成が必要です。
Bedrock invoke部分
入力からモデルパラメータへの設定まで
Bedock InvokeModelをワークフローに追加し、anthropic.claude-3-sonnet-20240229-v1:0"
のモデルを選んだ際のBedrock モデルパラメータは下記のJSONになっています。
{
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 200,
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "string"
}
]
}
]
}
まずはお試しでcontentの配列のオブジェクトtext
のvalueである"string"
部分を書き換えて、
"こんにちはを英語で翻訳してください"
と入力し、保存。
この状態で動かすだけでもBedrock invokeされて翻訳結果がステートの結果として出力できるはずです。
ただ今回はステートマシンの入力で動的にvalue部分を書き換えて、毎回違う言葉を違う言語に翻訳できるようにしてみたいです。
{
"text": "こんにちは",
"language": "イタリア語"
}
そこでJSONataで入力値参照を行います。
{
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 200,
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "{% $states.input.text & 'を' & $states.input.language & 'に翻訳して出力して。' %}"
}
]
}
]
}
Step FunctionにおけるJSONata記法では、下記のように外側を"{% %}"
で囲って、その中で値の参照や演算を行うことが可能です。
"{% $here.JSONata.expression %}"
また、ステートに渡される入力は
$states.input.hoge
で参照することができます。今回の場合、text
というKeyをステートマシンの入力で渡すので、$states.input.text
で参照をしています。
JSONataではテンプレートリテラル的な変数との結合を行うこともできます。
$states.input.text & 'を'
のように、
$states.input.text
を
を、&
を用いて結合しています。
{
"text": "こんにちは",
"language": "イタリア語"
}
の実行入力の場合、Bedrock invokeに渡される"text"のvalueは、最終的にこんにちはをイタリア語に翻訳して出力して。
となります。
Bedrock invoke結果の出力
Bedrock invokeで得られる出力はデフォルトだと下記のようになっていました。
{
"Body": {
"id": "msg_bdrk_01FYBMe2qw3UA1DCvUdGjeem",
"type": "message",
"role": "assistant",
"model": "claude-3-sonnet-20240229",
"content": [
{
"type": "text",
"text": "こんにちはのスペイン語訳は:\n\nBuenas tardes\n\nです。"
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 28,
"output_tokens": 27
}
},
"ContentType": "application/json"
}
ただ、今後のステートで、使われたモデル情報やid等は不要であり、翻訳の内容だけ得たいためその部分だけを次のSNSステートに出力するよう、出力内容を加工してみます。
上記のデータから、欲しいのは、Body→content→配列の最初のオブジェクト→text
という階層を確認しておきます。
それをJSONataで表現すると、
"{% $states.result.Body.content[0].text %}"
になります。$states.result
でステートの出力にアクセスでき、その後はBody
、content
のindexが0
のtext
と、順に参照をしていく形になります。
次のSNSステートには分かりやすいよう下記のようにtextのvalueとして、渡してあげます。
{
"text": "{% $states.result.Body.content[0].text %}"
}
SNS部分
毎回固定のメッセージをSlackに送る場合、下記のようにcontent.description
の値に任意の文字列を設定すると実現できます。
{
"version": "1.0",
"source": "custom",
"content": {
"description": "こんにちは!"
}
}
今回はBedrockで翻訳されたテキストを動的に送りたいため、JSONataを用いて値の参照を行います。
そして、2024年秋に新しく追加された"変数"機能についても今回扱うようにしてみました。
完成したメッセージの設定が下記です。
{
"version": "1.0",
"source": "custom",
"content": {
"description": "{% '原文:' & $inputText & '\n\n' & '言語:' & $inputLanguage & '\n\n' & '翻訳結果:' & $states.input.text %}"
}
}
description部分の解説をします。
"{% '原文:' & $inputText & '\n\n' & '言語:' & $inputLanguage & '\n\n' & '翻訳結果:' & $states.input.text %}"
いきなりですが、$inputText
と$inputLanguage
が変数の参照です。これは前段のBedrock invokeの設定内に記述していたものになります。
"Assign": {
"inputText": "{% $states.input.text %}",
"inputLanguage": "{% $states.input.language %}"
}
変数の設定で、ステートマシン入力値のtext
とlanguage
を参照し、それぞれinputText
という変数と、inputLanguage
という変数を設定していました。
後述のステートで、この変数は$inputText
・$inputLanguage
という形で参照が可能になります。
"{% '原文:' & $inputText & '\n\n' & '言語:' & $inputLanguage & '\n\n' & '翻訳結果:' & $states.input.text %}"
description部分のその他はステートへの入力値の参照$states.input.text
と結合&
になっています。
ステートマシンの解説はここまでで、実行結果を見てみましょう。
ステートマシンから実行を開始
を押下し、入力モーダルを開きます。
下記をフォームに入力し、実行を開始
を押してみます。
{
"text": "今年ももう終わるよ、一年あっという間だったね。",
"language": "英語"
}
するとSlackに新規メッセージが届きました。原文と言語も入力値が出力できていることが
確認できました。
英語で試してみたので、次はフランス語にして実行してみましょう。
{
"text": "今年ももう終わるよ、一年あっという間だったね。",
"language": "フランス語"
}
するとフランス語で結果が帰ってきました。
意図通り、内容を翻訳→出力してメッセージングできていますね!
おわりに
今回はStep FunctionsからBedrockでテキスト生成し、その結果をSlack通知するまでを実施しました。
Step FunctionsからBedrockをこんなに簡単に実行できるのかという驚きがまずありました。
Lambdaを使わなくてもStep FunctionsでAWSサービスを簡単に呼び出して使用することができるのでとても便利ですね。
また、Bedrockのinvoke結果をフロント(画面やチャットツールなど)に出力する流れはJSONataでデータをゴニョゴニョ操作しがいがあるので、JSONataの勉強になった&楽しかったです!