启用 JSON 模式

确保 Claude 输出一致的 JSON 格式

🌐 查看英文原文 | 源码 Notebook


通过 Prompt 让 Claude 实现”JSON 模式”

Claude 没有通过受约束采样实现的正式”JSON 模式”。但不用担心——你仍然可以从 Claude 获得可靠的 JSON 输出!本教程将向你展示如何做到这一点。

首先,让我们看看 Claude 的默认行为。

%pip install anthropic
import json
import re
from pprint import pprint

from anthropic import Anthropic
client = Anthropic()
MODEL_NAME = "claude-opus-4-1"
message = (
    client.messages.create(
        model=MODEL_NAME,
        max_tokens=1024,
        messages=[
            {
                "role": "user",
                "content": "Give me a JSON dict with names of famous athletes & their sports.",
            },
        ],
    )
    .content[0]
    .text
)
print(message)
Here is a JSON dictionary with names of famous athletes and their respective sports:

{
  "athletes": [
    {
      "name": "Usain Bolt",
      "sport": "Track and Field"
    },
    {
      "name": "Michael Phelps",
      "sport": "Swimming"
    },
    {
      "name": "Serena Williams",
      "sport": "Tennis"
    },
    {
      "name": "LeBron James",
      "sport": "Basketball"
    },
    {
      "name": "Lionel Messi",
      "sport": "Soccer"
    },
    {
      "name": "Simone Biles",
      "sport": "Gymnastics"
    },
    {
      "name": "Tom Brady",
      "sport": "American Football"
    },
    {
      "name": "Muhammad Ali",
      "sport": "Boxing"
    },
    {
      "name": "Nadia Comaneci",
      "sport": "Gymnastics"
    },
    {
      "name": "Michael Jordan",
      "sport": "Basketball"
    },
    {
      "name": "Pelé",
      "sport": "Soccer"
    },
    {
      "name": "Roger Federer",
      "sport": "Tennis"
    }
  ]
}

Claude 遵循了指令并输出了一个漂亮的字典,我们可以用代码来提取它:

def extract_json(response):
    json_start = response.index("{")
    json_end = response.rfind("}")
    return json.loads(response[json_start : json_end + 1])


extract_json(message)
{'athletes': [{'name': 'Usain Bolt', 'sport': 'Track and Field'},
  {'name': 'Michael Phelps', 'sport': 'Swimming'},
  {'name': 'Serena Williams', 'sport': 'Tennis'},
  {'name': 'LeBron James', 'sport': 'Basketball'},
  {'name': 'Lionel Messi', 'sport': 'Soccer'},
  {'name': 'Simone Biles', 'sport': 'Gymnastics'},
  {'name': 'Tom Brady', 'sport': 'American Football'},
  {'name': 'Muhammad Ali', 'sport': 'Boxing'},
  {'name': 'Nadia Comaneci', 'sport': 'Gymnastics'},
  {'name': 'Michael Jordan', 'sport': 'Basketball'},
  {'name': 'Pelé', 'sport': 'Soccer'},
  {'name': 'Roger Federer', 'sport': 'Tennis'}]}

但如果我们想让 Claude 跳过前导文字直接输出 JSON 呢?一个简单的方法是预填充 Claude 的回复,并包含一个”{” 字符。

message = (
    client.messages.create(
        model=MODEL_NAME,
        max_tokens=1024,
        messages=[
            {
                "role": "user",
                "content": "Give me a JSON dict with names of famous athletes & their sports.",
            },
            {"role": "assistant", "content": "Here is the JSON requested:\n{"},
        ],
    )
    .content[0]
    .text
)
print(message)

   "athletes":[
      {
         "name":"Michael Jordan",
         "sport":"Basketball"
      },
      {
         "name":"Babe Ruth",
         "sport":"Baseball"
      },
      {
         "name":"Muhammad Ali",
         "sport":"Boxing"
      },
      {
         "name":"Serena Williams",
         "sport":"Tennis"
      },
      {
         "name":"Wayne Gretzky",
         "sport":"Hockey"
      },
      {
         "name":"Michael Phelps",
         "sport":"Swimming"
      },
      {
         "name":"Usain Bolt",
         "sport":"Track and Field"
      },
      {
         "name":"Mia Hamm",
         "sport":"Soccer"
      },
      {
         "name":"Michael Schumacher",
         "sport":"Formula 1 Racing"
      },
      {
         "name":"Simone Biles",
         "sport":"Gymnastics"
      }
   ]
}

现在我们只需要把我们预填充的”{“加回去,就可以提取 JSON 了。

output_json = json.loads("{" + message[: message.rfind("}") + 1])
output_json
{'athletes': [{'name': 'Michael Jordan', 'sport': 'Basketball'},
  {'name': 'Babe Ruth', 'sport': 'Baseball'},
  {'name': 'Muhammad Ali', 'sport': 'Boxing'},
  {'name': 'Serena Williams', 'sport': 'Tennis'},
  {'name': 'Wayne Gretzky', 'sport': 'Hockey'},
  {'name': 'Michael Phelps', 'sport': 'Swimming'},
  {'name': 'Usain Bolt', 'sport': 'Track and Field'},
  {'name': 'Mia Hamm', 'sport': 'Soccer'},
  {'name': 'Michael Schumacher', 'sport': 'Formula 1 Racing'},
  {'name': 'Simone Biles', 'sport': 'Gymnastics'}]}

对于非常长且复杂的 prompt,其中包含多个 JSON 输出,仅通过搜索”{“和”}“字符串可能无法奏效,你也可以让 Claude 将每个 JSON 项输出在指定的标签中,以便后续提取。

message = (
    client.messages.create(
        model=MODEL_NAME,
        max_tokens=1024,
        messages=[
            {
                "role": "user",
                "content": """Give me a JSON dict with the names of 5 famous athletes & their sports.
Put this dictionary in <athlete_sports> tags.

Then, for each athlete, output an additional JSON dictionary. In each of these additional dictionaries:
- Include two keys: the athlete's first name and last name.
- For the values, list three words that start with the same letter as that name.
Put each of these additional dictionaries in separate <athlete_name> tags.""",
            },
            {"role": "assistant", "content": "Here is the JSON requested:"},
        ],
    )
    .content[0]
    .text
)
print(message)
 

<athlete_sports>
{
  "Michael Jordan": "Basketball",
  "Serena Williams": "Tennis",
  "Lionel Messi": "Soccer", 
  "Usain Bolt": "Track and Field",
  "Michael Phelps": "Swimming"
}
</athlete_sports>

<athlete_name>
{
  "first": ["Magnificent", "Motivating", "Memorable"],
  "last": ["Joyful", "Jumping", "Jocular"]
}
</athlete_name>

<athlete_name>
{
  "first": ["Skillful", "Strong", "Superstar"],
  "last": ["Winning", "Willful", "Wise"]
}
</athlete_name>

<athlete_name>
{
  "first": ["Legendary", "Lively", "Leaping"],
  "last": ["Magical", "Marvelous", "Masterful"]  
}
</athlete_name>

<athlete_name>
{
  "first": ["Unbeatable", "Unbelievable", "Unstoppable"],
  "last": ["Brave", "Bold", "Brilliant"]
}
</athlete_name>

<athlete_name>
{
  "first": ["Marvelous", "Methodical", "Medalist"],
  "last": ["Powerful", "Persevering", "Precise"]
}
</athlete_name>

现在,我们可以使用正则表达式提取来获取所有字典。

import re


def extract_between_tags(tag: str, string: str, strip: bool = False) -> list[str]:
    ext_list = re.findall(f"<{tag}>(.+?)</{tag}>", string, re.DOTALL)
    if strip:
        ext_list = [e.strip() for e in ext_list]
    return ext_list


athlete_sports_dict = json.loads(extract_between_tags("athlete_sports", message)[0])
athlete_name_dicts = [json.loads(d) for d in extract_between_tags("athlete_name", message)]
pprint(athlete_sports_dict)
{'Lionel Messi': 'Soccer',
 'Michael Jordan': 'Basketball',
 'Michael Phelps': 'Swimming',
 'Serena Williams': 'Tennis',
 'Usain Bolt': 'Track and Field'}
pprint(athlete_name_dicts, width=1)
[{'first': ['Magnificent',
            'Motivating',
            'Memorable'],
  'last': ['Joyful',
           'Jumping',
           'Jocular']},
 {'first': ['Skillful',
            'Strong',
            'Superstar'],
  'last': ['Winning',
           'Willful',
           'Wise']},
 {'first': ['Legendary',
            'Lively',
            'Leaping'],
  'last': ['Magical',
           'Marvelous',
           'Masterful']},
 {'first': ['Unbeatable',
            'Unbelievable',
            'Unstoppable'],
  'last': ['Brave',
           'Bold',
           'Brilliant']},
 {'first': ['Marvelous',
            'Methodical',
            'Medalist'],
  'last': ['Powerful',
           'Persevering',
           'Precise']}]

总结一下:

  • 你可以使用字符串解析来提取”json"和"“之间的文本,从而获取 JSON。
  • 你可以通过部分 Assistant 消息来移除 JSON 之前的前导文字。(但是,这会失去让 Claude 在开始输出 JSON 之前进行”思维链”推理以提升智能的机会。)
  • 你可以使用停止序列来移除 JSON 之后的文本。
  • 你可以指示 Claude 将 JSON 输出在 XML 标签中,这样对于更复杂的 prompt,后续收集起来会更方便。

Tip

中文读者提示:本章节的代码和输出为英文原文。如需理解具体实现细节,可参考上方中文导读和代码注释。如有疑问,欢迎在 GitHub Issues 讨论。