5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Strands Deciderハンズオン!

5
Last updated at Posted at 2026-10-07

このイベント用のハンズオン手順です。どなたでも好きなときに試せます!(環境構築を含めて1.5時間目安)

2026/10/1、AWSから発表されたStrands Deciderを動かすハンズオンです。

Strands Deciderは文章を生成せず、用意した選択肢から答えを選ぶだけの小さなモデルです。9月頃に出てきたJevと同じようなものですね。これをStrands Agentsに組み込んで使ってみましょう!

このハンズオンではこんな感じのことを試します。

  • Strands Deciderの3種類の質問(choice、noul、score)を試す
  • HTTPサーバーとして動かして、curlで呼ぶ
  • Strands Agentsの Intervention、ModelRouter、グラフ、HumanInTheLoop に組み込む

多少のAWS利用料(数十円〜の想定)がかかります。ご自身の責任で実施してください。

費用の大半はEC2です。東京リージョンの t4g.xlarge で、1時間あたり約0.17ドルです。終わったら、忘れずに「おかたづけ」でリソースを削除してください。

事前準備

AWSアカウントにサインインして、マネジメントコンソールを開きます。

リージョンを「東京」に設定します。
image.png

環境構築編

CloudFormationスタックを作成

AWSが公開している「AI Agent Development Code Server」のテンプレートを使います。EC2の上で動くVS Code(Code Server)を作って、そこで作業を進めていきましょう。

まず以下のページを開きます。

「AWS へのデプロイ」のリージョンを「東京」にして、「Deploy」ボタンを押します。
image.png

サインインしていないと、サインイン画面に飛ばされます。サインインしてから押し直すと、スタックの作成画面が開きます。
image.png

このテンプレートには、スタックの作成が失敗することがある不具合があります(修正PR: https://github.com/aws-samples/sample-one-click-generative-ai-solutions/pull/98 )。失敗した場合は、ロールバックされたスタックを削除してから、同じ手順で作り直してください。

パラメータを入力

下記を変更します。それ以外はデフォルトでOKです。

  • UserEmail:自分のメールアドレス
  • InstanceType:t4g.xlarge
  • RepoUrl:https://github.com/har1101/strands-decider-handson.git
  • EnableAdministratorAccess:false

image.png

UserEmail には、完了通知とSNSの購読確認メールが届きます。

RepoUrl を入れておくと、ハンズオンの資料とサンプルコードがインストールされた状態でEC2が起動します。

InstanceType の既定値のt4g.largeはメモリ8GBですが、Strands Deciderはモデルの読み込み時に約12GBを使うので、8GBだとプロセスが強制終了されます。メモリ16GBの t4g.xlarge を選んでください。

EnableAdministratorAccess は、既定値のtrueだとインスタンスに管理者権限が付きます。ハンズオンで必要なのはBedrockを呼ぶ権限だけで、それは別途付与するので、falseでOKです。

これらを設定したら画面の一番下までスクロールして、IAMリソースの作成を承認するチェックを入れ、「スタックの作成」を押します。
image.png

デプロイ完了までは7分前後かかります。「イベント」タブで、リソースが順に作られていく様子を見られるので気になる方は覗いてみてください。
image.png

RunSSMDoc が CREATE_FAILED になって、スタック全体がロールバックされた場合は、前述の既知の不具合です。
image.png

URLとパスワードを確認

ステータスがCREATE_COMPLETEになったら、「出力」タブを開きます。
URLにアクセスして、Passwordを入力してログインします。
image.png

うまくいかない場合は修正版のテンプレートで作成

公式のテンプレートで失敗したときは、同じ内容を直したテンプレートを使います。直したのは、EC2のSSMへの登録を待ってからコマンドを送る部分だけで、できあがる環境は同じです。

先に、失敗したスタックが残っているなら、削除しておきます(削除の手順は、この記事の最後の「おかたづけ」と同じです)。

次のリンクから、テンプレートのファイルをダウンロードします。ページを開いたら、Ctrl + S(Macは Cmd + S)で保存してください。

保存するときのファイル名は、AIAgentDevelopmentCodeServerDeploymentStack.yaml にします。

CloudFormationのスタック一覧を開いて、「スタックの作成」から「新しいリソースを使用 (標準)」を選びます。

image.png

「テンプレートファイルのアップロード」を選んで、「ファイルの選択」からダウンロードしたファイルを指定します。ファイル名が表示されたら、画面の下にある「次へ」を押します。

image.png

この画面では、スタック名を入力する必要があります。strands-decider-handson など、好きな名前で構いません。パラメータは、公式のテンプレートのときと同じ4つを入れます。UserEmail、InstanceType、RepoUrl、EnableAdministratorAccessの値は、前の「パラメータを入れる」と同じです。入力したら、「次へ」を押します。

image.png

他は変更無しでOK。画面の下までスクロールして、IAMリソースの作成を承認するチェックを入れ、「次へ」を押します。最後の確認画面で、画面の一番下にある「送信」を押すとスタックの作成が始まります。

ここからは、公式のテンプレートのときと同じです。7分前後で CREATE_COMPLETE になったら、「出力」タブで URL と Password を確認して、次に進みます。

開発環境へアクセス

ログイン

出力タブのURLを開きます。Ctrl+Enterなどで別タブで起動する方が良さそうです。
そしてPasswordの値を入力して「SUBMIT」を押します。
image.png

するとEC2で起動しているVSCodeにアクセスできます。
左のエクスプローラーに、ハンズオンの資料とサンプルコードが並んでいると思います。
(画面右のBuild with Agentはペケボタンから消してOKです)
image.png

一応、HANDSON.mdをクリックしてもハンズオン手順書が出てきます。ただ私の雑なメモ書きみたいな部分もあるので、解説も見ながら進めたい方はこちらのブログをご参照ください。

image.png

ターミナルを開く

画面下のターミナルを開きます。左の「≡」マーク→「Terminal」→「New Terminal」を選んでください。
image.png

フォルダを信頼するか聞かれたら「Trust Folder & Continue」を押します。
image.png

ターミナルが開きました。ここから先のコマンドは、このターミナルに入力していきます。
image.png

Pythonの仮想環境を作成

Python 3.14の仮想環境を作ります。高速なパッケージ管理ツールの uv を使います。
(Strands Decider自体はPython 3.10から対応しているようです)

uv venv -p 3.14
source .venv/bin/activate

ターミナルの先頭に(workshop)と出ていれば、仮想環境に入れています。
image.png

続いてStrands Deciderをインストールします。

uv pip install strands-decider

関連リソースも一緒に入るので、数十秒かかると思います。
image.png

Strands Deciderを動かしてみよう

Strands DeciderはDecision Model(意思決定モデル)と言われるものです。普通のLLMのように文章を生成するものではなく、一定の選択肢から最適なものを高速で選んでくれるようなものです。使い所がこれまでのものとは違うって感じですね。

ベースは約19億パラメータのQwen3.5-2Bであると記載されています。もっと詳しく知りたい方は下記ブログなどをご参照ください。

Strands Deciderは3種類の使い方があります。そしてその判断の確信度(confidence)も見ることができます。

種類 何を答えるか
noul Yes/Noを0〜1の確率で返す(1に近いほどYes)
choice 選択肢から1つ選ぶ
score 順序のある選択肢で点数を付ける

Step.1 CLIで動かしてみる

まずはstrands-decider askというコマンドで動かしてみましょう。イメージとしては単発でLLMを動かす感じです。まず初めの動作確認みたいなものですね。このコマンドを使う時は、状況を表す文章(state)と質問・選択肢などを渡します。

まずはchoiceです。「助けて!請求金額が3日前からずっと間違っています!」というメッセージを、どのチームが対応すべきか選ばせます。ここではchoiceに続けて、質問とその選択肢を記載しています。

strands-decider ask StrandsAgents/strands-decider-2B-hobson-v19 \
  --state "助けて!請求金額が3日前からずっと間違っています!" \
  --choice "どのチームが対応すべきですか?=請求,営業,技術"

初回は、Hugging Faceからモデル(約4.4GB)がダウンロードされます。2回目以降はダウンロードされませんが、askは毎回モデルを読み込み直すので、50秒ほどかかります。

flash-linear-attention や causal_conv1d が入っていないという警告が出ることがあります。結果は正しく、最適化された部品を使っていないだけなので、無視してOKです。

image.png

請求が0.599、技術が0.265、営業が0.136で、請求が選ばれました。これらの数字は選択肢の中でどれが選ばれやすいかを表しているので、大体%と置き換えて理解して良さそうです。

confidence 0.398というのも出ていますが、これはこの答え自体がどのくらいの信頼度かを表しています。0.398だとそこまで高くはなさそうですね。
日本語と英語でも結果が変わるかもしれないので、是非試してみましょう!

次はnoulです。同じメッセージが、緊急性を感じさせるかをYes/Noで聞きます。stateで質問するのは変わらず、noulは質問だけを投げます。

strands-decider ask StrandsAgents/strands-decider-2B-hobson-v19 \
  --state "助けて!請求金額が3日前からずっと間違っています!" \
  --noul "緊急性を感じさせますか?"

image.png

ここでは0.789でした。1に近いほどYesなので、「かなり緊急」という判断ですね。

3つ目はscoreです。書き手がどれくらい苛立っているかを、3段階で採点させます。ここでもscoreに続けて、質問と選択肢を用意しています。

strands-decider ask StrandsAgents/strands-decider-2B-hobson-v19 \
  --state "助けて!請求金額が3日前からずっと間違っています!" \
  --score "書き手はどれくらい苛立っていますか?=落ち着いている,苛立っている,落ち込んでいる"

image.png

スコアは1.17(confidence0.516)でした。この数値の算出は各選択肢の番号(0, 1, 2)に確率を掛けて足した値で、0×0.139 + 1×0.552 + 2×0.310 = 1.17です。苛立ちの強さが中くらい、と読めるでしょうか。
本来ならもう少し連続した比較の方が良いかもしれませんね(落ち着いている、少し苛立っている、激昂しているみたいに)。

ちなみに、複数の質問を1回でまとめて聞くこともできます。ここまでで試したchoice, noul, scoreを全部一緒に聞いてみましょう。

strands-decider ask StrandsAgents/strands-decider-2B-hobson-v19 \
  --state "助けて!請求金額が3日前からずっと間違っています!" \
  --choice "どのチームが対応すべきですか?=請求,営業,技術" \
  --noul "緊急性を感じさせますか?" \
  --score "書き手はどれくらい苛立っていますか?=落ち着いている,苛立っている,落ち込んでいる"

image.png

Step.2 HTTPサーバーとして動かす

先程まで使っていたaskでの実行は毎回モデルを読み込み直す処理が入ります。そのため、アプリから何度も呼ぶには向きません。

そこで、HTTPサーバーを動かして質問を受け付ける形にしてみましょう。こうするとモデルの読み込みが最初の1回で済みます。

strands-decider serve StrandsAgents/strands-decider-2B-hobson-v19 --port 8000

Uvicorn running on http://127.0.0.1:8000 と出れば、起動完了です。

image.png

このターミナルはサーバーが使い続けます。ターミナルの右上にある「+」を押して、2つ目のターミナルを開きます。
2つ目のターミナルから、curlで質問を送ります。

curl -s localhost:8000/v1/systemone \
  -H 'content-type: application/json' \
  -d '{
    "state": "助けて!請求金額が3日前からずっと間違っています!",
    "questions": {
      "緊急か": {"type": "noul", "instructions": "緊急性を感じさせますか?"}
    }
  }'

image.png

JSONで答えが返ってきました。noulは0.7894で、Step.1とほぼ一致しています。usageの出力トークンが1なのは、文章を作らずに1回の計算で答えを出しているためです。

latency_msは約3000ミリ秒です。公式のGPUでの計測(115ミリ秒)より遅いのは、このインスタンスにGPUがないため致し方なしですね。

次のStep.3でポートを変えて起動し直すので、1つ目のターミナルに戻って Ctrl + C でサーバーを止めます。

image.png

サーバーを2つ同時に起動すると、メモリが16GBでも足りなくなります。必ず止めてから進めてください。

Step.3 Strands Agentに組み込む

ここまでは単発でStrands Deciderを動かすだけでしたが、ここからは実際にありそうなユースケースを試していきましょう。まずはStrands Agentと組み合わせて使ってみます。

Strands Agentには、ツールを呼ぶ直前などに処理を差し込めるInterventionという仕組みがあります。Claude CodeのHooksみたいなものです。
ここでStrands Deciderを動かして、エージェントに処理してもらうか、人間にエスカレするかを振り分けてもらいましょう。

まずはAWS公式が提供しているStrands Deciderリポジトリをcloneして、Strands Agentsと一緒にインストールします。ターミナルは1つ目のものを継続利用してください。

git clone https://github.com/strands-labs/strands-decider && cd strands-decider
uv pip install -e . strands-agents

image.png

もう一度サーバーを起動します。今度はポート8099にします。

strands-decider serve StrandsAgents/strands-decider-2B-hobson-v19 --port 8099

image.png

2つ目のターミナルに切り替えて、仮想環境に入り直し、cloneしたディレクトリに移動します。

source /workshop/.venv/bin/activate
cd /workshop/strands-decider

ここでファイルを少しだけ修正します。エクスプローラーから strands-decider → examples → strands → tool_call_intervention.py を開き、Ctrl + G で195行目に飛ぶと、モデルIDが書かれています。

image.png

先頭の us を jp に書き換えて、保存します。

image.png

そして再実行してみましょう。
image.png

このサンプルだけは公式のコードなので、Deciderへの質問文が英語です。Step 4以降は、日本語に直したサンプルを使います。

このサンプルでは、「天気を教えて」とだけ頼まれたエージェントが、都市を勝手に推測して天気ツールを呼ぼうとします。その直前に、Deciderが次の2つをYes/Noで判定します。

  • ツールの引数は、ユーザーが実際に言ったことに基づいているか
  • ユーザーに確認する前にツールを呼ぶのは、早すぎないか

結果は、「引数が根拠に基づいている確率」が0.16、「早すぎる確率」が0.65でした。エージェントは天気を答える代わりに、「どの都市の天気を知りたいですか?」と聞き返しています。

Interventionは、判断の結果として、次の4種類のどれかを返します。今回はGuideが返りました。

返り値 意味
Proceed ツールをそのまま実行する
Deny ツール呼び出しを拒否する
Confirm 止めて人間に確認する
Guide フィードバックを付けて、モデルに考え直させる

ここでClaudeが有効化されていない、みたいなエラーが出た際には下記を参考にコンソールから申請してください。すぐに使えるようになると思います。

Step.4 LLMと人間のルーティング判定

ここでは、サポートへの問い合わせを、AIが答えるか、人間の担当者に回すかを、LLMを呼ぶ前に判定します。
2つ目のターミナルで、ひとつ上のディレクトリに戻ります。

cd /workshop

Strands Agentsのbefore_invocationフックを使います。エージェントが依頼を処理し始める前、つまりLLMを呼ぶ前に走る処理です。ここでDeciderに「この問い合わせは人間に回すべきか」を聞いて、回すと判断したらDenyを返し、LLMを一度も呼ばずに終わらせます。

Deciderへの質問は、次のchoiceが1つだけです。エディタに開いているのが、このコードです。

QUESTIONS = {
    "route": Decider.choice(
        "このサポートへの問い合わせは、誰が対応すべきですか?",
        {
            "AIアシスタント": "製品ドキュメントをもとに、AIアシスタントだけで十分に答えられる一般的な質問。使い方、機能、プラン、...",
            "担当者": "人間のスタッフが必要な問い合わせ。返金、請求のトラブル、補償、アカウントのセキュリティ事故、...",
        },
    ),
}

選択肢の説明文が、そのままDeciderへの指示になります。「担当者」を選ぶ確率が0.7以上なら、人間に回します。このしきい値は、コード側で決めます。

実行します。

python step4_escalation_gate.py

出力が長いので、マウスホイールでスクロールして確認します。冒頭は、AIが答えたケースです。「データをCSVで書き出すにはどうすればいいですか?」は、人間に回す確率(p_human)が0.106と低く、そのままLLMが答えました。

image.png
image.png

その次の「パスワードを忘れました。どうすればいいですか?」は、p_human が0.523で、しきい値の0.7に届かないので、LLMが答えています。ただし、確信度は0.046とかなり低く、きわどい判定だとわかります。

出力の末尾は、人間に回したケースです。

image.png

「二重に請求されています。返金してください。」のような、返金や不正アクセスの相談は、p_human が0.76〜0.86と高く、人間に回されました。llm_input_tokens=0 と出ていて、LLMには一度も問い合わせていません。

Decider1回の判定は、CPUのこの環境で5秒前後かかります。本来ならもっと高速に返してくれる環境でホストするべきです。

Step.5 LLMのルーティング判定

ここでは、依頼の難しさに応じて安いモデルと賢いモデルを使い分けます。
簡単な依頼にはClaude Haiku 4.5、込み入った依頼にはClaude Sonnet 4.6を使います。

Strands Agentsには、候補のモデルから1つを選んで使うModelRouterがあります。
どのモデルにするかを決める部分(RoutingStrategy)を自作できるので、そこでDeciderに判定してもらいましょう。

QUESTIONS = {
    "model": Decider.choice(
        "この依頼は、どのモデルが対応すべきですか?",
        {
            "軽量": "小さくて速いモデルで十分な依頼。あいさつ、短い返答、フレーズの翻訳、...",
            "高性能": "推論力の高いモデルが必要な依頼。複数の手順にわたる分析、設計、デバッグ、計画、...",
        },
    ),
}

実行します。

python step5_model_router.py

「『おはよう』をフランス語に訳して」は、確信度0.976で軽量に振られて、Haikuが答えました。「『承知しました』を丁寧なビジネスメールの一文に言い換えて」も軽量で、確信度は0.958です。

image.png

末尾の2件は、非同期処理のデッドロックの原因調査(確信度0.966)と、新規事業の市場規模のフェルミ推定(確信度0.944)です。どちらも高性能に振られて、Sonnet 4.6が答えました。

image.png
image.png

Step.6 担当エージェントへのルーティング

ここでは、問い合わせの内容に応じて請求・技術・営業のどの担当エージェントに渡すかを決めます。

まず受付のエージェントが、問い合わせを1文の日本語のチケットにまとめます。そのチケットをDeciderが読んで、担当を選びます。
Strands Agentsのグラフ(複数のエージェントをつなぐ仕組み)では、ノード同士をつなぐ線(エッジ)に条件を付けられます。この条件の判定に、Deciderを使います。

for team in TEAMS:
    builder.add_edge("受付", team, condition=routed_to(team))

流れ自体はコードで固定して、分かれ道の判断だけをDeciderに任せます。

python step6_graph_routing.py

image.png

3件の問い合わせが、請求(確信度0.671)、技術(0.993)、営業(0.938)の担当に、それぞれ振り分けられました。

Step.7 ツールを実行してよいかを決める

ここでは、エージェントがツールを呼ぶときに人間の承認が要るかどうかをDeciderに判断させます。ファイルの一覧や読み取りは自動で実行して、メール送信やファイル削除のような変更を伴う操作だけ、人間に確認します。

Strands AgentsのHumanInTheLoopは、ツールの実行前に人間の承認を挟む仕組みです。承認が要るかの判定を、Deciderに任せます。

agent = Agent(
    tools=[list_files, read_file, send_email, delete_file],
    interventions=[HumanInTheLoop(classifier=decider_classifier, ask="stdio")],
)

実行します。

python step7_tool_approval.py

ファイルの確認(list_files、read_file)は、承認が要る確率が0.06と0.07と低く、自動(auto)で実行されます。
一方、メール送信(send_email)は0.74で、ターミナルで承認を聞かれます。

image.png

ここではyと答えます。

次に、ファイル削除(delete_file)も0.78で承認を聞かれるので、こちらはnと答えます。

image.png

すると最終結果が表示されます。メールは送られて、ファイル削除は実行されませんでした。最後のremaining filesにtmp/old.logが残ってますよね。

image.png

もちろん質問の書き方で結果は変わるので、色々試してみてください!

おかたづけ

放っておくと、リソースの料金がかかり続けます。削除することを推奨します。

CloudFormationでスタックを開いて、右上の「スタックを削除」を押します。
image.png

確認のダイアログが出ます。入力欄にスタック名を入力すると、「スタックを削除」ボタンが押せるようになります。
image.png

ステータスが DELETE_IN_PROGRESS になって、数分で消えます。
image.png

EC2、EBS、CloudFrontなど、テンプレートが作ったものがまとめて消えます。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?