生成AIでJSON「だけ」の出力を強制させる（Claude 3.5 / Amazon Nova対応）

生成AIでJSONを生成させたいことってありますよね。

`Claude.ai`で試してみます。

```
JSONのサンプルデータを作成してください。
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/1fff8cbf-1947-aa4a-8b55-f2b25901134f.png)

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/58c33c95-bb78-a83c-d089-e8c5fb6cb29e.png)

上手に生成してくれました。
ただ、プログラム内部で使用したい場合、JSONの前後の文字が余計ですよね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/46c305e5-1b50-0881-1e55-6c5f20010328.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/7a337cdb-6503-aaeb-78a9-4e2b95d4722f.png)

余計な出力を行わず、JSON「だけ」を生成させる方法を紹介します。

## アシスタントプロンプトに「事前入力」

生成AIのモデルには、ユーザープロンプトだけでなく、アシスタントプロンプトを任意で指定できるモデルがあります。Claude 3.5やAmazon Novaはこれらに対応しています。

JSON形式で出力したい場合、JSONの先頭文字の「`{`」をアシスタントプロンプトとして指定することで、生成AIが生成する文字列を制御します。

```python:Amazon Bedrockで実行する例（事前入力しない場合）
import boto3

client = boto3.client("bedrock-runtime")

response = client.converse(
    modelId="us.anthropic.claude-3-5-haiku-20241022-v1:0",
    messages=[
        {
            "role": "user",
            "content": [{"text": "JSONのサンプルデータを作成してください。"}],
        }
    ],
)
```

<details><summary>出力</summary>

> 以下に、さまざまな種類のJSONサンプルデータを提示します：
> 
> 1. 基本的なユーザー情報
> ```json
> {
>   "id": 1,
>   "name": "山田太郎",
>   "age": 30,
>   "email": "yamada@example.com",
>   "isActive": true
> }
> ```
> 
> 2. ネストされた配列を含むデータ
> ```json
> {
>   "company": "株式会社サンプル",
>   "employees": [
>     {
>       "id": 1,
>       "name": "鈴木一郎",
>       "department": "営業"
>     },
>     {
>       "id": 2, 
>       "name": "佐藤花子",
>       "department": "開発"
>     }
>   ]
> }
> ```
> 
> 3. 複雑な構造のデータ
> ```json
> {
>   "project": {
>     "name": "新製品開発",
>     "budget": 10000000,
>     "team": {
>       "leader": {
>         "name": "田中次郎",
>         "role": "プロジェクトマネージャー"
>       },
>       "members": [
>         {"name": "山本三郎", "position": "デザイナー"},
>         {"name": "中村四郎", "position": "エンジニア"}
>       ]
>     },
>     "timeline": {
>       "start": "2023-07-01",
>       "end": "2024-06-30"
>     }
>   }
> }
> ```
> 
> 4. 商品情報
> ```json
> {
>   "products": [
>     {
>       "id": 101,
>       "name": "スマートフォン",
>       "price": 59800,
>       "specs": {
>         "color": ["ブラック", "ホワイト"],
>         "storage": ["128GB", "256GB"],
>         "camera": "1200万画素"
>       }
>     },
>     {
>       "id": 102,
>       "name": "ノートパソコン",
>       "price": 89000,
>       "specs": {
>         "cpu": "Core i7",
>         "memory": "16GB",
>         "display": "15.6インチ"
>       }
>     }
>   ]
> }
> ```
> 
> 5. 天気情報
> ```json
> {
>   "location": "東京",
>   "current": {
>     "temperature": 25.5,
>     "humidity": 70,
>     "windSpeed": 5.2,
>     "condition": "曇り"
>   },
>   "forecast": [
>     {
>       "date": "2023-07-15",
>       "highTemp": 28,
>       "lowTemp": 22,
>       "description": "晴れ"
>     },
>     {
>       "date": "2023-07-16",
>       "highTemp": 26,
>       "lowTemp": 20,
>       "description": "一時雨"
>     }
>   ]
> }
> ```
> 
> これらのサンプルは、JSONの様々な使用方法と構造を示しています。用途や必要に応じて、適切なJSONデータを選択または参考にしてください。

</details>

```python:Amazon Bedrockで実行する例（事前入力する場合）
import boto3

client = boto3.client("bedrock-runtime")

response = client.converse(
    modelId="us.anthropic.claude-3-5-haiku-20241022-v1:0",
    messages=[
        {
            "role": "user",
            "content": [{"text": "JSONのサンプルデータを作成してください。"}],
        },
        {
            "role": "assistant",           # アシスタントプロンプト
            "content": [{"text": "{"}],    # `{`とだけ入力
        }
    ],
)
```

出力

```

  "users": [
    {
      "id": 1,
      "name": "山田太郎",
      "age": 35,
      "email": "yamada.taro@example.com",
      "address": {
        "city": "東京",
        "street": "渋谷区恵比寿",
        "zipCode": "150-0013"
      },
      "hobbies": ["読書", "旅行", "料理"],
      "isActive": true
    },
    {
      "id": 2, 
      "name": "鈴木花子",
      "age": 28,
      "email": "suzuki.hanako@example.com",
      "address": {
        "city": "大阪",
        "street": "梅田",
        "zipCode": "530-0001"
      },
      "hobbies": ["音楽", "ダンス"],
      "isActive": false
    }
  ],
  "company": {
    "name": "テクノロジー株式会社",
    "foundedYear": 2010,
    "departments": [
      "開発部",
      "営業部",
      "人事部"
    ],
    "totalEmployees": 250
  },
  "products": [
    {
      "id": "A001",
      "name": "スマートフォン",
      "price": 59800,
      "features": {
        "color": ["黒", "白", "青"],
        "storage": [64, 128, 256]
      }
    }
  ]
}
```

いい感じですね。

:::note
事前入力した「`{`」は、生成した文字列には含まれないので、注意
:::

## 「事前入力」と「停止シーケンス」を使う

「事前入力」だけでもおおよそうまくいくのですが、もう少し汎用性のある指定方法が、Amazon Novaのドキュメントにありました。

参考：https://docs.aws.amazon.com/nova/latest/userguide/prompting-structured-output.html

「事前入力」に加え、「停止シーケンス」も使用する方法です。

:::note
停止シーケンスとは、生成AIに「この文字を出力したら生成を停止して」と伝えるための設定です。
:::

「事前入力」で生成する文字列の先頭を決め、「停止シーケンス」で生成する文字列の終端を決めます。それぞれに指定する値は以下のとおりです。

* **事前入力：** ```json
* **停止シーケンス：** ```

そうです。Qiitaでもおなじみの「Markdown記法でJSONを生成させる」のです。

```python
import boto3

client = boto3.client("bedrock-runtime")

response = client.converse(
    modelId="us.anthropic.claude-3-5-haiku-20241022-v1:0",
    messages=[
        {
            "role": "user",
            "content": [{"text": "JSONのサンプルデータを作成してください。"}],
        },
        {
            "role": "assistant",                # アシスタントプロンプト
            "content": [{"text": "```json"}],   # 「```json」を指定
        },
    ],
    inferenceConfig={"stopSequences": ["```"]},   # 停止シーケンスを指定
)
```

出力

```
{
    "users": [
        {
            "id": 1,
            "name": "山田太郎",
            "age": 35,
            "email": "yamada@example.com",
            "isActive": true,
            "address": {
                "street": "東京都渋谷区",
                "zipCode": "150-0001"
            },
            "hobbies": ["読書", "映画鑑賞", "音楽"]
        },
        {
            "id": 2,
            "name": "鈴木花子", 
            "age": 28,
            "email": "suzuki@example.com",
            "isActive": false,
            "address": {
                "street": "大阪市北区",
                "zipCode": "530-0001"
            },
            "hobbies": ["料理", "旅行"]
        }
    ],
    "company": {
        "name": "サンプル株式会社",
        "founded": 2010,
        "departments": ["営業", "開発", "人事"]
    }
}
```

この方法の場合は、生成AIの出力がJSONそのものになるので、先頭の「`{`」がかけることもありません。

:::note
Amazon Novaモデルの場合、どうも「停止シーケンスの文字列まで出力に含める」仕様のようです。
そのため、「```」が生成AIの出力に含まれるので取り除いて使用する必要があります。
:::

この方法であれば、JSON出力だけでなく、Pythonプログラムの生成などにも応用が可能ですね。