Step FunctionsでBedrockのクロスリージョン推論を使いたい！

# やりたいこと
Step Functions(以下SFn)のワークフロー内でBedrockを呼び出したい。

でも東京リージョンのコンソール上で確認すると、利用可能なモデルがなんだか少ない…
せっかくなら最新版のClaude Sonnet 4など、より優れたモデルを使いたいですよね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/9377ae3e-20fd-4639-a8c3-b4c623b0d68e.png)

## 表示されるモデルが少ない原因
これの原因は、恐らく**東京リージョンのみで使用可能な基盤モデルが表示されているため**です。

### 東京リージョンのみで利用可能なBedrock
まず、東京リージョンでのみ利用可能なBedrockを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/52cbbce4-9e13-4d77-9cc0-72c179169f9f.png)

Cross-region inferenceの記載があるものは、**クロスリージョン推論でしか利用できないモデル**となっています。

以下ドキュメントにおいても、「一部のモデルは、一部のリージョンではクロスリージョン推論を通じてのみアクセスできます」と記載されています。

https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html

したがって、Anthropicのモデルで東京リージョンのみに閉じて利用できるのは
- Claude 3.5 Sonnet
- Claude3 Haiku
- Claude 2.1
- Claude Instant

の4つのみとなります。

冒頭のSFnを再度確認すると、同じように表示されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/9377ae3e-20fd-4639-a8c3-b4c623b0d68e.png)

### SFnの仕様
また、SFnの仕様として**クロスリージョンリソースアクセスはできません**。
>現在、クロスリージョン AWS SDK 統合とクロスリージョン AWS リソースアクセスは Step Functions では使用できません。

https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/concepts-access-cross-acct-resources.html

したがって、クロスリージョンにリクエストを送信することになるモデルたちは、SFn上で選択できない・利用できないものだと思っていました。

# クロスリージョン推論でしか使えないモデルを使う方法
と思い込んでいたのですが、意外とできました！それも結構簡単に！

## やりかた
BedrockのInvokeModelをワークフロー上へ持ってきておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/b68bdb93-c3a9-40d4-9677-4c6387c111dd.png)

:::note warn
2025/05/24現在、boto3などのAWS SDKでBedrockの基盤モデルを呼び出したい場合は、**Converse API**というのを用いるのが主流だと思います。

ただ、現状[SFnではInvokeModel APIしか使うことはできない](https://docs.aws.amazon.com/step-functions/latest/dg/connect-bedrock.html)ようです。
:::

モデルは仮として、Claude 3.5 Sonnetを選択しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/ab0b7d49-bd50-4bd9-ae65-7f824eb75bba.png)

コンソールにおいて「デザイン」ではなく「コード」を選択すると、先程のワークフローをコードで表現したものが出現します。
これは「Amazon States Language(以下ASL)」というJSONベースの構造化言語が用いられています。
```bedrock.asl.json
{
  "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:ap-northeast-1::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0",
        "Body": {
          "anthropic_version": "bedrock-2023-05-31",
          "max_tokens": 200,
          "messages": [
            {
              "role": "user",
              "content": [
                {
                  "type": "text",
                  "text": "string"
                }
              ]
            }
          ]
        }
      },
      "End": true
    }
  },
  "QueryLanguage": "JSONata"
}
```

この`ModelId`の部分を、クロスリージョン対応のモデルIDに変えてあげます。
今回は最新版であるClaude Sonnet 4を指定してみましょう。

Bedrockコンソールの「Cross-region inference」ページを参照し、Inference profile IDもしくはARNをコピーし、先程の`ModelId`の値を置き換えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/f101dfb4-7fd3-4587-a826-02d2e5694ffe.png)

or
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/d16406db-8885-469a-b5ea-f7720d79d8ed.png)
↓
```diff_json:bedrock.asl.json
- "ModelId": "arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0"
// ↓どちらか一方だけでOKです
+ "ModelId": "apac.anthropic.claude-sonnet-4-20250514-v1:0"
+ "ModelId": "arn:aws:bedrock:ap-northeast-1:<アカウントID>:inference-profile/apac.anthropic.claude-sonnet-4-20250514-v1:0"
```

また、プロンプトを入力できるようにするための変更を行います。デフォルトのままだと「string」が毎回入力されるだけになっているためです。

JSONata構文 `"{% <JSONata expression> %}"` を用いて、入力値 `prompt` の値を渡せるようにしてみましょう。
以下のように修正します。
```diff_json:bedrock.asl.json
"content": [
    {
        "type": "text",
-       "text": "string"
+       "text": "{% $states.input.prompt %}"
    }
]
```

JSONataのパラメータの渡し方は以下の感じみたいです。詳しくはドキュメントを見てみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/1cea167c-6ea9-4ec9-a541-e014a0791e43.png)

https://docs.aws.amazon.com/step-functions/latest/dg/transforming-data.html

最終的に完成したASLは以下の感じです。
```bedrock.asl.json
{
  "Comment": "A description of my state machine",
  "StartAt": "Bedrock InvokeModel",
  "States": {
    "Bedrock InvokeModel": {
      "Type": "Task",
      "Resource": "arn:aws:states:::bedrock:invokeModel",
      "Arguments": {
        "ModelId": "apac.anthropic.claude-sonnet-4-20250514-v1:0",
        "Body": {
          "anthropic_version": "bedrock-2023-05-31",
          "max_tokens": 200,
          "messages": [
            {
              "role": "user",
              "content": [
                {
                  "type": "text",
                  "text": "{% $states.input.prompt %}"
                }
              ]
            }
          ]
        }
      },
      "End": true
    }
  },
  "QueryLanguage": "JSONata"
}
```

これで、クロスリージョン推論を通じてClaude Sonnet 4へプロンプトを投げることができるようになりました！👏

(余談：`Arguments` の中身は、Claudeが提供するMessages APIの形式に則っています。詳しくは以下をご参照ください。)

https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html


## 権限設定
上記ステートマシンを作成すると、SFn用のIAMロールと、Bedrock(とX-rayも)へのアクセス権限の設定されたIAMポリシーが作成されます。
```bedrock-iampolicy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Sid": "InvokeModel1",
            "Action": [
                "bedrock:InvokeModel"
            ],
            "Resource": [
                "arn:aws:bedrock:ap-northeast-1::foundation-model/apac.anthropic.claude-sonnet-4-20250514-v1:0"
            ]
        }
    ]
}
```

ただ、これだけで実行するとアクセス権限エラーが出ます。
```json:エラー内容
{
  "cause": "User: <ロール名> is not authorized to perform: bedrock:InvokeModel on resource: arn:aws:bedrock:ap-northeast-1:<アカウントID>:inference-profile/apac.anthropic.claude-sonnet-4-20250514-v1:0 because no identity-based policy allows the bedrock:InvokeModel action (Service: BedrockRuntime, Status Code: 403, Request ID: hogehoge) (SDK Attempt Count: 1)",
  "error": "Bedrock.AccessDeniedException",
  "resource": "invokeModel",
  "resourceType": "bedrock"
}
```
要するに、「arn:aws:bedrock:ap-northeast-1:<アカウントID>:inference-profile/apac.anthropic.claude-sonnet-4-20250514-v1:0」への実行権限がないぞ、と怒られています。

自動作成されたIAMロールに付与されていたのは、東京リージョンのSonnet 4に対する権限だけだったのですが、クロスリージョン推論を使用する場合は**推論プロファイルへのアクセス権限を追加**してあげる必要があります。

https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-prereq.html

また、デフォルトで設定されていた東京リージョンのモデルARNもちょっと書き方がおかしいです。「apac.」が余計ですね。こちらも修正します。
```diff_json:bedrock-iampolicy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Sid": "InvokeModel1",
            "Action": [
                "bedrock:InvokeModel"
            ],
            "Resource": [
-               "arn:aws:bedrock:ap-northeast-1::foundation-model/apac.anthropic.claude-sonnet-4-20250514-v1:0"
+               "arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0"
+               "arn:aws:bedrock:ap-northeast-1:<アカウントID>:inference-profile/apac.anthropic.claude-sonnet-4-20250514-v1:0"
            ]
        }
    ]
}
```

これでOK…な時もあるのですが、まだ完了ではありません。
クロスリージョン推論を用いる場合は、**ルーティングされるすべてのリージョンにおけるSonnet 4へのアクセス権限を付与**してあげる必要があります。

つまり東京リージョンで実行する場合は、アジアパシフィックに所属し、かつSonnet 4を利用できるすべてのリージョンに対する権限を付与する必要があります。
```diff_json:bedrock-iampolicy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Sid": "InvokeModel1",
            "Action": [
                "bedrock:InvokeModel"
            ],
            "Resource": [
                "arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0",
+               "arn:aws:bedrock:ap-northeast-2::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0",
+               "arn:aws:bedrock:ap-northeast-3::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0",
+               (省略、合計8リージョン分指定)
                "arn:aws:bedrock:ap-northeast-1:<アカウントID>:inference-profile/apac.anthropic.claude-sonnet-4-20250514-v1:0"
            ]
        }
    ]
}
```

ただ、アジアパシフィックにおけるClaude Sonnet 4のクロスリージョン推論対応リージョンは東京含めて8つあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/ffe79d24-13c9-4438-b24e-b0b974e37b46.png)

面倒くさい場合は以下のようにすれば大丈夫です。
`ap-*` としておけば、アジアパシフィック全リージョンのSonnet 4に対してアクセス許可を設定できます。
```diff_json:bedrock-iampolicy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Sid": "InvokeModel1",
            "Action": [
                "bedrock:InvokeModel"
            ],
            "Resource": [
-               "arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0",
+               "arn:aws:bedrock:ap-*::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0",
                "arn:aws:bedrock:ap-northeast-1:<アカウントID>:inference-profile/apac.anthropic.claude-sonnet-4-20250514-v1:0"
            ]
        }
    ]
}
```

# いざ実行！
コンソール上から実行してみましょう。「実行を開始」ボタンから、以下のJSONを入力します。
```json: 入力を実行
{
  "prompt": "こんにちは"
}
```

以下の感じの回答が返ってくるはずです。
```json:回答(一部抜粋)
"model": "claude-sonnet-4-20250514",
"content": [
    {
        "type": "text",
        "text": "こんにちは！お元気ですか？何かお手伝いできることがあれば、お気軽にお声かけください。"
    }
]
```

# なぜクロスリージョン推論はSFnから利用可能なのか？
前述の通り、SFnはリージョン跨ぎのリソースアクセスはできません。
にも関わらず、なぜクロスリージョン推論が可能なのでしょうか。

**呼び出している推論プロファイルが東京リージョンにあるため**だと私は推測しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/489eab1f-92d8-4b7b-a92e-9c5cfa4c54a1.png)

https://pages.awscloud.com/rs/112-TZM-766/images/AWS-Black-Belt_2024_Amazon-Bedrock-Model-Inference-b_0909_v1.pdf

推論プロファイル(Inference Profile)とは、特定のモデルごとに使用可能なリージョンを定義したものです。

推論プロファイルのIDとしては、`apac.anthropic.claude-sonnet-4-20250514-v1:0` や `us.anthropic.claude-sonnet-4-20250514-v1:0` など、
`<地域名>.<モデルプロバイダ名>.<モデル名>` といった形で表現されます。

Sonnet 4のクロスリージョン推論はアジアパシフィックだと8つのリージョンに対応していると書きましたが、それは推論プロファイルで予め定義されているためです。

この推論プロファイルを指定してAPIリクエストを送信するリージョン(ソースリージョンと言います)が、ここでは東京です。
SFnを東京リージョンで作成し、そのワークフロー内でAPIリクエストを送信しているためですね。

このソースリージョンが東京リージョンのため、問題なく実行できるのかなと考えています。

SFnから見ると、以下のイメージでしょうか。
- 推論プロファイル用のエンドポイントが東京にあり、そこにリクエストを送信する
- 東京リージョンの推論プロファイル用エンドポイントから回答が返ってくる

つまり、東京リージョンのBedrock基盤モデルとやり取りしている(ように、SFn視点では見えている)のではないかと。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/5730b783-b4e7-49f8-8a3e-2276f3c8314f.png)


しかし、実際のところは以下のイメージです。
- 推論プロファイル用のエンドポイントが東京にあり、そこにリクエストを送信する(同じ)
- エンドポイントの裏側で、トラフィックが混雑していないどこかのリージョンへ、リクエストが適切に送信される
- 振り分けられたリージョンのBedrock基盤モデルへリクエストをコールし、レスポンスを得る
- エンドポイントへレスポンスが返ってくる
- 東京リージョンの推論プロファイル用エンドポイントから回答が返ってくる(同じ)

実態としてクロスリージョンなリソースアクセスが行われていますが、それはSFnの知らないところで行われている、な感じなのかなと。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/0cfcc168-535d-4016-a7d0-8a9ce13006f0.png)

間違ってたらご指摘ください！

# まとめ
Step Functionsでもクロスリージョン推論な基盤モデルを使用可能です！
ワークフローの中にBedrockを組み込む際は、ぜひご一考してみてはいかがでしょうか？