【Bedrock開発入門 刊行後サポート】Amazon Novaモデルで動作させる方法

昨年出版した「[Amazon Bedrock 生成AIアプリ開発入門 [AWS深掘りガイド]]()」が、皆様にご支援いただき、なんと

**ITエンジニア本大賞2025 技術書部門ベスト10** に選出いただきました！！！！！

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/724de84a-e5c8-8558-4e02-17c81dc65d06.png)

https://www.shoeisha.co.jp/campaign/award/result

50音順ではありますが、位置が良いですね！

---

執筆段階から、「移り変わりがはやいテーマなので、出版後もできるだけアップデート情報を反映させていきたいね」と話していたのですが、、

**申し訳ございません！！**

ちょっとサボってました🙇‍♀️🙇‍♂️🙇

Amazon Novaで動かないという声を聞いたので、大急ぎで検証しました！

:::note
書籍サポートのGitHubにも更新版のコードを格納しておりますのでこちらも参照ください。

https://github.com/minorun365/bedrock-book/
:::


## InvokeModel APIの場合

InvokeModel APIを使用たNovaモデルの呼び出しは、書籍執筆時点の`1.34.87`でも動作します。

```shell
pip install boto3==1.34.87 botocore==1.34.87
```

:::note
boto3に`bedrock-runtime`が追加されたバージョン`1.28.57`でも、Novaモデルを呼び出すことは可能です。（ただし、ガードレールはクロスリージョン推論などの機能には対応していないので、可能であれば最新バージョンを使用したほうがいいと思います。
:::

### 通常呼び出し

[書籍](https://www.sbcr.jp/product/4815626440/)の「2.11.2 AWS SDKを用いて各モデルのAPIへリクエストを行う方法」のP.92に記載のコードを元に解説します。

InvokeMode APIはリクエストの形式がモデルごとに異なるのでAmazon Novaの形式に合わせる必要があります。

```python:Anthropic Claude 3 Sonnetの場合（書籍から引用）
# リクエストボディを定義
body = json.dumps(
    {
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 1000,
        "messages": [
            {
                "role": "user",
                "content": "Bedrockってどういう意味？",
            }
        ],
    }
)

# モデルを定義（Claude 3 Sonnet）
modelId = "anthropic.claude-3-sonnet-20240229-v1:0"
```

```python:Amazon Novaの場合
# リクエストボディを定義
body = json.dumps(
    {
        "messages": [
            {
                "role": "user",
                "content": [{"text": "Bedrockってどういう意味？"}],
            }
        ],
        "inferenceConfig": {"max_new_tokens": 1000},
    }
)

# モデルを定義（Amazon Nova Pro）
modelId = "amazon.nova-pro-v1:0"
```

レスポンスの形式もモデルごとに異なります。

```python:Anthropic Claude 3 Sonnetの場合（書籍から引用）
answer = response_body["content"][0]["text"]
```

```python:Amazon Novaの場合
answer = response_body["output"]["message"]["content"][0]["text"]
```

実行方法は変わりません。

```shell:
python 2_invoke-model.py
```

### ストリーム呼び出し

[書籍](https://www.sbcr.jp/product/4815626440/)の「2.11.2 AWS SDKを用いて各モデルのAPIへリクエストを行う方法」のP.97に記載のコードを元に解説します。

ストリームの場合、リクエストの形式は先ほどで解説したものと変わりませんが、レスポンスの形式が以下のように変わります。

```python:Anthropic Claude 3 Sonnetの場合（書籍から引用）
# ストリーミング出力
for event in response.get("body"):
    chunk = json.loads(event["chunk"]["bytes"])
    if (
        chunk["type"] == "content_block_delta"
        and chunk["delta"]["type"] == "text_delta"
    ):
        print(chunk["delta"]["text"], end="")
```

```python:Amazon Novaの場合
# ストリーミング出力
for event in response.get("body"):
    chunk = json.loads(event["chunk"]["bytes"])
    
    if "contentBlockDelta" in chunk:
        print(chunk["contentBlockDelta"]["delta"]["text"], end="")
```


## Converse APIの場合

:::note
Converse APIが校了ギリギリのタイミングでリリースだったので、実行例を書籍に含めることができませんでした。以下にコード例を用意していますので、こちらをご確認ください。

https://github.com/minorun365/bedrock-book/blob/main/chapter2/3-1_streaming-converse.py
:::

Converse APIはBoto3の`1.34.116`で追加されました。このバージョン以降であれば、Converse APIを使用してNovaモデルを呼び出し可能です。

```shell
pip install boto3==1.34.116 botocore==1.34.116
```

Converse APIの特徴として、 **「モデルが変わってもリクエストとレスポンスの形式が同一」** というものがあるので、モデル名を変更するだけでNovaモデルを利用できます。

```python:Anthropic Claude 3 Sonnetの場合（上記GitHubから引用）
# モデルを定義（Claude 3 Sonnet）
model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
```

```python:Amazon Novaの場合
# モデルを定義（Amazon Nova Pro）
model_id = "amazon.nova-pro-v1:0"
```

実行方法

```shell
python 2-1_invoke-converse.py
```

## LangChainの場合

LangChainを使用する場合は、Boto3だけでなくlangchain-awsが対応している必要があります。langchain-awsのNova対応は`0.2.9`のリリースに含まれています。

langchain-awsのバージョン0.2.9では依存ライブラリーとして、boto3の`1.35.74`以降が必要ですので同時にインストールされます。

:::note
今回調べて発覚したのですが、Amazon Novaが発表されたその日のうちにlangchain-awsのバージョン0.2.9がリリースされていました！！はやい！！！
:::

```
pip install langchain-aws==0.2.9 boto3==1.35.74 botocore==1.35.74
```

### Invoke呼び出し

[書籍](https://www.sbcr.jp/product/4815626440/)の「3.5.2 【ステップ１】 LangChainの実装」のP.144に記載のコードを元に解説します。

ライブラリーのバージョンが対応していれば、モデル名を変更するだけNovaモデルを呼び出し可能です。

```python:Anthropic Claude 3 Sonnetの場合（上記GitHubから引用）
# ChatBedrockを生成
chat = ChatBedrock(
    model_id="anthropic.claude-3-sonnet-20240229-v1:0",
    model_kwargs={"max_tokens": 1000},
)
```

```python:Amazon Novaの場合
# ChatBedrockを生成
chat = ChatBedrock(
    model_id="amazon.nova-pro-v1:0",
    model_kwargs={"max_tokens": 1000},
)
```

実行方法

```shell
python 1_langchain.py
```

### Stream呼び出し

[書籍](https://www.sbcr.jp/product/4815626440/)の「3.5.3 【ステップ２】ストリーム出力」のP.150に記載のコードを元に解説します。

stream呼び出しの場合、同じコードで動作はするのですが、出力部分の動作が異なります。

以下のコードを用いてClaudeとNovaの出力を比較します。

```python:Stream形式でモデルを呼び出す（書籍から引用）
# Stream形式でモデル呼び出し
for chunk in chat.stream(messages):
    print(chunk.content, end="", flush=True)

print("")
```


```text:Anthropic Claude 3 Sonnetの出力
空が青く見える主な理由は、太陽の光が地球の大気中の分子と相互作用するためです。

具体的には以下の理由があげられます。

1. 空気中の窒素や酸素の分子が、太陽光の短い波長(青色)の光を mehr散乱させるため。この散乱を"レーリー散乱"と呼びます。

2. 長い波長(赤色)の光は空気分子にあまり散乱されないため、太陽からの光は青白く見えます。

3. 太陽光は大気圏の上部で最も強く散乱され、そのため空頂上付近は深い青色に見えます。地平線に近づくにつれ、散乱される光の経路が長くなり、青みが薄れていきます。

4. 大気中の水蒸気や浮遊粒子なども一部光を散乱させますが、レーリー散乱が空の青色の主な原因です。

このように、太陽光と大気中の分子との相互作用により、青色光が優先的に散乱され、それが空の青色の印象を与えているのです。
```

```text:Amazon Novaの出力
[][{'type': 'text', 'text': '', 'index': 0}][{'type': 'text', 'text': '空', 'index': 0}][{'type': 'text', 'text': 'が', 'index': 0}][{'type': 'text', 'text': '青', 'index': 0}][{'type': 'text', 'text': 'く', 'index': 0}][{'type': 'text', 'text': '見', 'index': 0}][{'type': 'text', 'text': 'える', 'index': 0}][{'type': 'text', 'text': '理由', 'index': 0}][{'type': 'text', 'text': 'は', 'index': 0}][{'type': 'text', 'text': '、', 'index': 0}][{'type': 'text', 'text': 'レイ', 'index': 0}][{'type': 'text', 'text': 'リー', 'index': 0}][{'type': 'text', 'text': '散', 'index': 0}][{'type': 'text', 'text': '乱', 'index': 0}][{'type': 'text', 'text': 'と呼ばれる', 'index': 0}][{'type': 'text', 'text': '現', 'index': 0}][{'type': 'text', 'text': '象', 'index': 0}][{'type': 'text', 'text': 'による', 'index': 0}][{'type': 'text', 'text': 'ものです', 'index': 0}][{'type': 'text', 'text': '。', 'index': 0}]
...以下略
```

Novaモデルを使用すると、JSONで出力されました。

この出力は`ChatBedrock`ではなく`ChatBedrockConverse`を使用した場合の動作と同じです。

```python:ChatBedrockConverseを使用したコード
# Pyhton外部モジュールのインポート
from langchain_aws import ChatBedrockConverse
from langchain_core.messages import HumanMessage, SystemMessage

# ChatBedrockを生成
chat = ChatBedrockConverse(
    model="anthropic.claude-3-sonnet-20240229-v1:0",
    max_tokens=1000,
)

# メッセージを定義
messages = [
    SystemMessage(content="あなたのタスクはユーザーの質問に明確に答えることです。"),
    HumanMessage(content="空が青いのはなぜですか？"),
]

# Stream形式でモデル呼び出し
for chunk in chat.stream(messages):
    print(chunk.content, end="", flush=True)

print("")
```

```text:ChatBedrockConverseを使用した場合のAnthropic Claude 3 Sonnetの出力
[][{'type': 'text', 'text': '空', 'index': 0}][{'type': 'text', 'text': 'が', 'index': 0}][{'type': 'text', 'text': '青', 'index': 0}][{'type': 'text', 'text': 'く', 'index': 0}][{'type': 'text', 'text': '見', 'index': 0}][{'type': 'text', 'text': 'える', 'index': 0}][{'type': 'text', 'text': '主', 'index': 0}][{'type': 'text', 'text': 'な', 'index': 0}][{'type': 'text', 'text': '理', 'index': 0}][{'type': 'text', 'text': '由', 'index': 0}][{'type': 'text', 'text': 'は', 'index': 0}][{'type': 'text', 'text': '、', 'index': 0}][{'type': 'text', 'text': '太', 'index': 0}][{'type': 'text', 'text': '陽', 'index': 0}][{'type': 'text', 'text': '光', 'index': 0}][{'type': 'text', 'text': 'の', 'index': 0}][{'type': 'text', 'text': '中', 'index': 0}][{'type': 'text', 'text': 'の', 'index': 0}][{'type': 'text', 'text': '短', 'index': 0}][{'type': 'text', 'text': 'い', 'index': 0}][{'type': 'text', 'text': '波', 'index': 0}][{'type': 'text', 'text': '長', 'index': 0}][{'type': 'text', 'text': 'の', 'index': 0}][{'type': 'text', 'text': '光', 'index': 0}][{'type': 'text', 'text': '(', 'index': 0}][{'type': 'text', 'text': '青', 'index': 0}]
...以下略
```

langchain-awsには、InvokeModel APIを使用する「`ChatBedrock`」とConverse APIを使用する「`ChatBedrockConverse`」の２つの実装があります。Novaモデルの場合はどうも **「ChatBedrockを使用しても内部でConverse APIを使用する」** という動作をしているようです。

Novaモデルを使用した場合の出力をClaudeの場合に合わせるには、StrOutputParserを使用します。

```python:ChatBedrockでAmazon Novaを使用し、Claude 3と出力を合わせる場合
# Pyhton外部モジュールのインポート
from langchain_aws import ChatBedrock
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.output_parsers import StrOutputParser

# ChatBedrockを生成
chat = ChatBedrock(
    model_id="amazon.nova-pro-v1:0",
    model_kwargs={"max_tokens": 1000},
    streaming=True,
)
# Nova特別対応：出力が生成したテキストになるようにStrOutputParserを追加
chat = chat | StrOutputParser()

# メッセージを定義
messages = [
    SystemMessage(content="あなたのタスクはユーザーの質問に明確に答えることです。"),
    HumanMessage(content="空が青いのはなぜですか？"),
]

# Stream形式でモデル呼び出し
for chunk in chat.stream(messages):
    print(chunk, end="", flush=True)

print("")
```

```text:Amazon Novaの出力
空が青く見える理由は、レイリー散乱と呼ばれる現象によるものです。太陽光は、赤外線、可視光線、紫外線を含むさまざまな波長の光で構成されています。地球の大気に入ると、これらの光は空気中の窒素や酸素などの小さな分子によって散乱されます。

レイリー散乱では、光の波長が短いほど散乱が強くなります。可視光線の中で、青色の光は波長が比較的短いため、赤色やオレンジ色の光よりも強く散乱されます。このため、青色の光が大気中であらゆる方向に散乱され、私たちは空を青く見ることになるのです。

夕焼けや日の出の際に空が赤やオレンジに見えるのも、同じ原理に基づいています。これらの時間帯には、太陽が地平線近くにあるため、光はより長い距離を大気中を通過します。これにより、青色や緑色の光がより強く散乱され、残る光は主に赤やオレンジの波長になるため、空が赤やオレンジに見えることになります。
```

:::note
今回のコードでは問題ありませんが、`chunk`の内容がテキストのみの置き換わっているため、chunk.content以外のchunk.response_metadataなどはなくなってしまいますので、用途に合わせて修正してください。
:::

## まとめ

今後もできるだけアップデートに追随していきたいと思いますので、引き続きよろしくお願いいたします！