文章翻译自官网github.com/anthropics/…
利用工具强制生成 JSON
学习目标
-
理解如何使用工具来强制生成结构化响应
-
利用这种 “技巧” 生成结构化的 JSON
利用工具的一种更有趣的方式是强制 Claude 输出 JSON 等结构化内容。在很多场景下,我们可能希望从 Claude 那里获得标准化的 JSON 响应,例如:提取实体、汇总数据、分析情感等。
一种方法是直接要求 Claude 返回 JSON,但这可能需要额外的工作来从 Claude 返回的长字符串中提取 JSON,或者确保 JSON 符合我们想要的确切格式。
好消息是,每当 Claude 想要使用工具时,它已经会按照我们定义工具时指定的完美结构化格式进行响应。
在上一节课中,我们给了 Claude 一个计算器工具。当它想要使用这个工具时,会返回类似这样的内容:
json
{
'operand1': 1984135,
'operand2': 9343116,
'operation': 'multiply'
}
这看起来和 JSON 非常相似!
如果我们希望 Claude 生成结构化的 JSON,我们可以利用这一点。我们要做的就是定义一个描述特定 JSON 结构的工具,然后告诉 Claude 这个工具。就这样。Claude 会做出响应,它认为自己在 “调用工具”,但实际上我们只关心它给出的结构化响应。
概念概述
这与我们在上一节课中所做的有何不同?以下是上一节课的工作流程图:
在上一节课中,我们给了 Claude 使用工具的权限,当 Claude 想要调用它时,我们实际上会调用底层的工具函数。
在本节课中,我们将通过告诉 Claude 某个特定工具来 “欺骗” 它,但我们不需要实际调用底层的工具函数。我们使用该工具来强制生成特定结构的响应,如下图所示:
情感分析
让我们从一个简单的例子开始。假设我们希望 Claude 分析某些文本中的情感,并返回具有以下格式的 JSON 对象:
json
{
"negative_score": 0.6,
"neutral_score": 0.3,
"positive_score": 0.1
}
我们只需要定义一个使用 JSON Schema 来捕获这种结构的工具。以下是一个可能的实现:
python
运行
tools = [
{
"name": "print_sentiment_scores",
"description": "Prints the sentiment scores of a given text.",
"input_schema": {
"type": "object",
"properties": {
"positive_score": {"type": "number", "description": "The positive sentiment score, ranging from 0.0 to 1.0."},
"negative_score": {"type": "number", "description": "The negative sentiment score, ranging from 0.0 to 1.0."},
"neutral_score": {"type": "number", "description": "The neutral sentiment score, ranging from 0.0 to 1.0."}
},
"required": ["positive_score", "negative_score", "neutral_score"]
}
}
]
现在我们可以告诉 Claude 这个工具,并明确告诉 Claude 使用它,以确保它确实会使用。我们应该会得到一个响应,表明 Claude 想要使用一个工具。工具使用响应应该包含我们想要的确切格式的所有数据。
python
运行
from anthropic import Anthropic
from dotenv import load_dotenv
import json
load_dotenv()
client = Anthropic()
tweet = "I'm a HUGE hater of pickles. I actually despise pickles. They are garbage."
query = f"""
<text>
{tweet}
</text>
Only use the print_sentiment_scores tool.
"""
response = client.messages.create(
model="claude-3-sonnet-20240229",
max_tokens=4096,
tools=tools,
messages=[{"role": "user", "content": query}]
)
response
plaintext
ToolsBetaMessage(id='msg_01BhF4TkK8vDM6z5m4FNGRnB', content=[TextBlock(text='Here is the sentiment analysis for the given text:', type='text'), ToolUseBlock(id='toolu_01Mt1an3KHEz5RduZRUUuTWz', input={'positive_score': 0.0, 'negative_score': 0.791, 'neutral_score': 0.209}, name='print_sentiment_scores', type='tool_use')], model='claude-3-sonnet-20240229', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=Usage(input_tokens=374, output_tokens=112))
让我们看看我们从 Claude 那里得到的响应。我们将重要部分加粗:
plaintext
ToolsBetaMessage(id='msg_01BhF4TkK8vDM6z5m4FNGRnB', content=[TextBlock(text='Here is the sentiment analysis for the given text:', type='text'), ToolUseBlock(id='toolu_01Mt1an3KHEz5RduZRUUuTWz', input={'positive_score': 0.0, 'negative_score': 0.791, 'neutral_score': 0.209}, name='print_sentiment_scores', type='tool_use')], model='claude-3-sonnet-20240229', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=Usage(input_tokens=374, output_tokens=112))
Claude “认为” 它在调用一个将使用这种情感分析数据的工具,但实际上我们只是要提取这些数据并将其转换为 JSON:
python
运行
import json
json_sentiment = None
for content in response.content:
if content.type == "tool_use" and content.name == "print_sentiment_scores":
json_sentiment = content.input
break
if json_sentiment:
print("Sentiment Analysis (JSON):")
print(json.dumps(json_sentiment, indent=2))
else:
print("No sentiment analysis found in the response.")
plaintext
Sentiment Analysis (JSON):
{
"positive_score": 0.0,
"negative_score": 0.791,
"neutral_score": 0.209
}
它奏效了!现在让我们把它变成一个可重用的函数,该函数接收一条推文或一篇文章,然后以 JSON 格式打印或返回情感分析结果。
python
运行
def analyze_sentiment(content):
query = f"""
<text>
{content}
</text>
Only use the print_sentiment_scores tool.
"""
response = client.messages.create(
model="claude-3-sonnet-20240229",
max_tokens=4096,
tools=tools,
messages=[{"role": "user", "content": query}]
)
json_sentiment = None
for content in response.content:
if content.type == "tool_use" and content.name == "print_sentiment_scores":
json_sentiment = content.input
break
if json_sentiment:
print("Sentiment Analysis (JSON):")
print(json.dumps(json_sentiment, indent=2))
else:
print("No sentiment analysis found in the response.")
python
运行
analyze_sentiment("OMG I absolutely love taking bubble baths soooo much!!!!")
plaintext
Sentiment Analysis (JSON):
{
"positive_score": 0.8,
"negative_score": 0.0,
"neutral_score": 0.2
}
python
运行
analyze_sentiment("Honestly I have no opinion on taking baths")
plaintext
Sentiment Analysis (JSON):
{
"positive_score": 0.056,
"negative_score": 0.065,
"neutral_score": 0.879
}
使用 tool_choice 强制工具使用
目前,我们通过提示来 “强制” Claude 使用我们的 print_sentiment_scores 工具。在我们的提示中,我们写道 “Only use the print_sentiment_scores tool.(只使用 print_sentiment_scores 工具。)”,这通常是有效的,但有一种更好的方法!我们实际上可以使用 tool_choice 参数强制 Claude 使用特定的工具:
python
运行
tool_choice={"type": "tool", "name": "print_sentiment_scores"}
上面的代码告诉 Claude,它必须通过调用 print_sentiment_scores 工具来响应。让我们更新我们的函数以使用它:
python
运行
def analyze_sentiment(content):
query = f"""
<text>
{content}
</text>
Only use the print_sentiment_scores tool.
"""
response = client.messages.create(
model="claude-3-sonnet-20240229",
max_tokens=4096,
tools=tools,
tool_choice={"type": "tool", "name": "print_sentiment_scores"},
messages=[{"role": "user", "content": query}]
)
json_sentiment = None
for content in response.content:
if content.type == "tool_use" and content.name == "print_sentiment_scores":
json_sentiment = content.input
break
if json_sentiment:
print("Sentiment Analysis (JSON):")
print(json.dumps(json_sentiment, indent=2))
else:
print("No sentiment analysis found in the response.")
我们将在接下来的课程中更详细地介绍 tool_choice。
实体提取示例
让我们使用相同的方法让 Claude 生成格式良好的 JSON,其中包含从文本样本中提取的人员、组织和位置等实体:
python
运行
tools = [
{
"name": "print_entities",
"description": "Prints extract named entities.",
"input_schema": {
"type": "object",
"properties": {
"entities": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "The extracted entity name."},
"type": {"type": "string", "description": "The entity type (e.g., PERSON, ORGANIZATION, LOCATION)."},
"context": {"type": "string", "description": "The context in which the entity appears in the text."}
},
"required": ["name", "type", "context"]
}
}
},
"required": ["entities"]
}
}
]
text = "John works at Google in New York. He met with Sarah, the CEO of Acme Inc., last week in San Francisco."
query = f"""
<document>
{text}
</document>
Use the print_entities tool.
"""
response = client.messages.create(
model="claude-3-sonnet-20240229",
max_tokens=4096,
tools=tools,
messages=[{"role": "user", "content": query}]
)
json_entities = None
for content in response.content:
if content.type == "tool_use" and content.name == "print_entities":
json_entities = content.input
break
if json_entities:
print("Extracted Entities (JSON):")
print(json.dumps(json_entities, indent=2))
else:
print("No entities found in the response.")
plaintext
Extracted Entities (JSON):
{
"entities": [
{
"name": "John",
"type": "PERSON",
"context": "John works at Google in New York."
},
{
"name": "Google",
"type": "ORGANIZATION",
"context": "John works at Google in New York."
},
{
"name": "New York",
"type": "LOCATION",
"context": "John works at Google in New York."
},
{
"name": "Sarah",
"type": "PERSON",
"context": "He met with Sarah, the CEO of Acme Inc., last week in San Francisco."
},
{
"name": "Acme Inc.",
"type": "ORGANIZATION",
"context": "He met with Sarah, the CEO of Acme Inc., last week in San Francisco."
},
{
"name": "San Francisco",
"type": "LOCATION",
"context": "He met with Sarah, the CEO of Acme Inc., last week in San Francisco."
}
]
}
我们使用了与之前相同的 “技巧”。我们告诉 Claude 它可以使用一个工具,以此让 Claude 返回特定的数据格式。然后我们提取 Claude 返回的格式化数据,这样就大功告成了。
请记住,在这种用例中,明确告诉 Claude 我们希望它使用某个特定的工具会很有帮助:
Use the print_entities tool.(使用 print_entities 工具。)
写在中间
我最近正在使用一个超nice的Claude code镜像
真的非常好用,填写我的码「5OTTEB」还能获得额外额度,快来试试!
维基百科摘要示例(包含更复杂的数据)
让我们尝试另一个更复杂的例子。我们将使用 Python 的wikipedia包获取完整的维基百科页面文章,并将其传递给 Claude。我们会让 Claude 生成包含以下内容的响应:
-
文章的主题
-
文章的摘要
-
文章中提到的关键词和主题列表
-
文章的分类列表(娱乐、政治、商业等)以及分类分数(即主题属于该类别的强度)
如果我们向 Claude 传递关于华特・迪士尼的维基百科文章,可能会得到这样的结果:
json
{
"subject": "华特·迪士尼",
"summary": "华特·埃利亚斯·迪士尼是美国动画师、电影制片人和企业家。他是美国动画产业的先驱,在卡通制作方面推出了多项创新。他保持着个人获得奥斯卡奖和提名次数最多的纪录。他还参与了迪士尼乐园和其他主题公园以及电视节目的开发。",
"keywords": [
"华特·迪士尼",
"动画",
"电影制片人",
"企业家",
"迪士尼乐园",
"主题公园",
"电视"
],
"categories": [
{
"name": "娱乐",
"score": 0.9
},
{
"name": "商业",
"score": 0.7
},
{
"name": "科技",
"score": 0.6
}
]
}
下面是一个函数的示例实现,该函数接收一个维基百科页面主题,查找文章、下载内容、将其传递给 Claude,然后打印生成的 JSON 数据。我们使用相同的策略:通过定义工具来 “引导” Claude 的响应格式。
注意:如果你的电脑上没有安装wikipedia,请确保运行pip install wikipedia进行安装!
python
运行
import wikipedia
# 工具定义
tools = [
{
"name": "print_article_classification",
"description": "打印分类结果。",
"input_schema": {
"type": "object",
"properties": {
"subject": {
"type": "string",
"description": "文章的主题",
},
"summary": {
"type": "string",
"description": "文章的段落摘要"
},
"keywords": {
"type": "array",
"items": {
"type": "string",
"description": "文章中的关键词和主题列表"
}
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "类别的名称。"},
"score": {"type": "number", "description": "类别的分类分数,范围从0.0到1.0。"}
},
"required": ["name", "score"]
}
}
},
"required": ["subject", "summary", "keywords", "categories"]
}
}
]
# 为给定文章主题生成json的函数
def generate_json_for_article(subject):
page = wikipedia.page(subject, auto_suggest=True)
query = f"""
<document>
{page.content}
</document>
使用print_article_classification工具。示例类别包括政治、体育、科技、娱乐、商业。
"""
response = client.messages.create(
model="claude-3-haiku-20240307",
max_tokens=4096,
tools=tools,
messages=[{"role": "user", "content": query}]
)
json_classification = None
for content in response.content:
if content.type == "tool_use" and content.name == "print_article_classification":
json_classification = content.input
break
if json_classification:
print("文本分类(JSON):")
print(json.dumps(json_classification, indent=2))
else:
print("在响应中未找到文本分类。")
python
运行
generate_json_for_article("杰夫·高布伦")
plaintext
文本分类(JSON):
{
"subject": "杰夫·高布伦",
"summary": "杰弗里·林恩·高布伦是美国演员和音乐家,曾出演一些票房最高的电影,如《侏罗纪公园》和《独立日》。他在电影和电视领域有着漫长而成功的职业生涯,在众多电影和电视节目中担任角色。高布伦还是一位出色的爵士乐音乐家,与他的乐队The Mildred Snitzer Orchestra一起发行了多张专辑。",
"keywords": [
"演员",
"音乐家",
"《侏罗纪公园》",
"《独立日》",
"电影",
"电视",
"爵士乐"
],
"categories": [
{
"name": "娱乐",
"score": 0.9
}
]
}
python
运行
generate_json_for_article("章鱼")
plaintext
文本分类(JSON):
{
"subject": "章鱼",
"summary": "本文全面概述了章鱼,包括它们的解剖学、生理学、行为、生态学和进化历史。内容涵盖了它们复杂的神经系统、伪装和变色能力、智力以及与人类的关系等主题。",
"keywords": [
"章鱼",
"头足类",
"软体动物",
"海洋生物学",
"动物行为",
"进化"
],
"categories": [
{
"name": "科学",
"score": 0.9
},
{
"name": "自然",
"score": 0.8
}
]
}
python
运行
generate_json_for_article("赫伯特·胡佛")
plaintext
文本分类(JSON):
{
"subject": "赫伯特·胡佛",
"summary": "本文提供了美国第31任总统赫伯特·胡佛的全面传记。内容包括他的早年生活、作为采矿工程师和人道主义者的职业生涯、大萧条期间的总统任期以及他的总统卸任后的活动。",
"keywords": [
"赫伯特·胡佛",
"大萧条",
"共和党",
"美国总统",
"采矿工程师",
"比利时救济委员会",
"美国食品管理局",
"商务部长",
"斯穆特-霍利关税法",
"新政"
],
"categories": [
{
"name": "政治",
"score": 0.9
},
{
"name": "商业",
"score": 0.7
},
{
"name": "历史",
"score": 0.8
}
]
}
练习
使用上述策略编写一个名为translate的函数,该函数接收一个单词或短语,并生成结构化的 JSON 输出,包含英文原文以及西班牙语、法语、日语和阿拉伯语的翻译。
以下是函数的工作示例:
如果我们调用:
python
运行
translate("how much does this cost")
预期输出如下:
json
{
"english": "how much does this cost",
"spanish": "¿cuánto cuesta esto?",
"french": "combien ça coûte?",
"japanese": "これはいくらですか",
"arabic": "كم تكلفة هذا؟"
}
注意:如果你想打印结果,以下代码行将帮助你美观地输出:
python
运行
print(json.dumps(translations_from_claude, ensure_ascii=False, indent=2))