初めに
LLMを使った実装で、こんなことをしていませんか?
- 「こういうJSON形式で返して」とプロンプトに書く
- レスポンスを文字列で受け取り
json.loads()でパースする
正直、業務コードに組み込むには怖いと思っていました。
理由は以下の通りです。
- フォーマットが微妙に崩れて
json.loads()が落ちる - LLMの気分でキー名が変わる
- 型が保証されないので後続処理が書きづらい
with_structured_output とは
with_structured_output とは、LLMの出力の形式を制限できるLangChainメソッドです。
これを使うと、LLMの出力を「型(クラス)」で受け取れます。
「文字列を頑張って解釈する」必要が無くなります!
⚠️ with_structured_output は便利ですが、すべてのLMで使えるわけではないようです。
具体例:with_structured_output を使わない場合
from langchain_aws import ChatBedrock
llm = ChatBedrock(
region_name="ap-northeast-1",
model_id="global.anthropic.claude-sonnet-4-5-20250929-v1:0",
)
response = llm.invoke(
"""
20代の日本人男性のユーザーを1人作ってください。
次のJSON形式で返してください。
{
"name": "string",
"age": number
}
"""
)
print(response.content)
出力(理想)👇
{
"name": "田中太郎",
"age": 25
}
これをパースします。
data = json.loads(response.content)
name = data["name"]
age = data["age"]
問題点
- JSONで返ってくる 保証はない
- キー名が変わる / 欠けることがある
- 型が違っても実行時まで分からない
結果として LLMの出力に業務コードが引きずられる構造 になりがちです。
具体例:with_structured_output を使う場合
from langchain_aws import ChatBedrock
from pydantic import BaseModel, Field
class User(BaseModel):
name: str = Field(description="ユーザーの名前")
age: int = Field(description="ユーザーの年齢")
llm = ChatBedrock(
region_name="ap-northeast-1",
model_id="global.anthropic.claude-sonnet-4-5-20250929-v1:0",
)
structured_llm = llm.with_structured_output(User)
result = structured_llm.invoke(
"20代の日本人男性のユーザーを1人作ってください。"
)
print(result)
print(type(result))
出力👇
name='田中太郎' age=25
<class '__main__.User'>
ポイント
- 返り値は 最初から
User型 - JSONパース処理が必要ない
- フィールド欠損・型違いは即エラー
まとめ
with_structured_output を使うと
- LLMの返り値を 「壊れやすい文字列」 ではなく 「普通の関数の戻り値」 として扱える
- JSONパース処理が不要になる
- 業務コードに安心して組み込める
便利な機能なので積極的に活用していこうと思います!