Ruby SDKを活用してBedrockのStructured Outputsにアプローチする

先日、Bedrockで提供されている一部モデルにてStructured outputsに対応したとのニュースを目にしました。

aws.amazon.com

ものは試しと、OpenAI GPT OSSに対してRuby SDKを使って実装してみることにしました。ただ、今回私が試した範囲では必ずJSON形式で結果が返ってくるとは限りませんでした。

実装は以下のような感じになります。

require "aws-sdk-bedrockruntime"
require "json"

CSV_PATH = "mydata.csv"
MODEL_ID = "openai.gpt-oss-120b-1:0"
REGION   = "ap-northeast-1"

def read_csv_as_text(path)
  File.read(path, encoding: "UTF-8")
end

def build_prompt(csv_text)
  <<~PROMPT
    以下はCSVデータです。
    カラムは「日付, 項目, 金額」です。

    このCSVを項目ごとに金額を合計してください。

    CSV:
    #{csv_text}
  PROMPT
end

def aggregate_by_bedrock(csv_text)
  client = Aws::BedrockRuntime::Client.new(region: REGION)

  prompt = build_prompt(csv_text)

  schema = {
    "type": "object",
    "properties": {
      "total_cost": {
        "type": "integer",
        "description": "総費用"
      },
      "by_item": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "item": {
              "type": "string",
              "description": "項目"
            },
            "cost": {
              "type": "integer",
              "description": "金額"
            }
          }
        }
      },
    },
    "required": ["total_cost", "by_item"],
    "additionalProperties": false
  }

  response = client.converse(
    model_id: MODEL_ID,
    messages: [
      {
        role: "user",
        content: [
          {
            text: prompt
          }
        ]
      }
    ],
    output_config: {
      text_format: {
        type: "json_schema",
        structure: {
          json_schema: {
            schema: JSON.dump(schema),
            name: "sample_schema"
          }
        }
      }
    }
  )

  full_text = response.output.message.content
  .select { |block| block.text }
  .map { |block| block.text }
  .join("")

  puts full_text

end

def main
  csv_text = read_csv_as_text(CSV_PATH)
  aggregate_by_bedrock(csv_text)
end

main

いくつか補足します。

まず、変数schemaの内容です。

  schema = {
    "type": "object",
    "properties": {
      "total_cost": {
        "type": "integer",
        "description": "総費用"
      },
      "by_item": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "item": {
              "type": "string",
              "description": "項目"
            },
            "cost": {
              "type": "integer",
              "description": "金額"
            }
          }
        }
      },
    },
    "required": ["total_cost", "by_item"],
    "additionalProperties": false
  }

出力させたいJSONの書式をJSON Schemaという形で表現しています。

次にモデルの呼び出しです。output_configというものを指定します。

    output_config: {
      text_format: {
        type: "json_schema",
        structure: {
          json_schema: {
            schema: JSON.dump(schema),
            name: "sample_schema"
          }
        }
      }
    }

Structured output固有の実装は以上です。

なおOpen AI GPT OSSの特徴かどうかはわからないのですが、結果を確認する際、以下の実装では結果がnilとなってしまいました。

p response.output.message.content[0].text

最終的な結果がresponse.output.message.content[1].textに入っていたので、response.output.message.contentの中身を結合する以下の処理を行い、結果として出力するようにしましt。あ

  full_text = response.output.message.content
  .select { |block| block.text }
  .map { |block| block.text }
  .join("")

  puts full_text

これで実行してみた結果、以下のようにJSON形式で結果が返ってきました(値は間違っています)。

{ "total_cost" :

    0          ,       "by_item" : [
   {
   "item" : "食費",
   "cost" : 2000
   } ,
   {
   "item" : "交通費",
   "cost" : 500
   }
   ]
}

ただ、何回か繰り返し実行してみると、以下のような形のようにJSON形式で結果が返ってこないことが多くなりました。

**{ "total_cost" :

    2500        ,       "by_item" : [
   {
   "item" : "食費",
   "cost" : 2000
   } ,
   {
   "item" : "交通費",
   "cost" : 500
   }
   ]
}

今回、デバッグまでの時間が取れなかったので今回はここまでの結果となりますが、いつか追加検証を行いたいと思います。