学习目标 #
- 了解如何使用工具强制结构化响应
- 利用此“技巧”生成结构化 JSON
利用工具的更有趣的方法之一是强制 Claude 使用 JSON 等结构化内容进行响应。在许多情况下,我们可能希望从 Claude 获得标准化的 JSON 响应:提取实体、汇总数据、分析情绪等。
这样做的一种方法是简单地要求 Claude 使用 JSON 进行响应,但这可能需要额外的工作来实际从我们从 Claude 返回的大字符串中提取 JSON,或者确保 JSON 遵循我们想要的确切格式。
好消息是,每当 Claude 想要使用工具时,它都会使用我们在定义工具时告诉它使用的完美结构化格式进行响应。
在上一课中,我们给了 Claude 一个计算器工具。当它想要使用该工具时,它会以如下内容进行响应:
{
‘operand1’: 1984135,
‘operand2’: 9343116,
‘operation’: ‘multiply’
}
这看起来与 JSON 非常相似!
如果我们想让 Claude 生成结构化的 JSON,我们可以利用这一点。我们所要做的就是定义一个描述特定 JSON 结构的工具,然后告诉 Claude。就是这样。Claude 会做出回应,认为它是“调用工具”,但我们真正关心的是它给我们的结构化响应。
概念概述 #
这与上一课中所做的有何不同?以下是上一课的工作流程图:
在上一课中,我们让 Claude 访问一个工具,Claude 想要调用它,然后我们实际调用了底层工具函数。
在本课中,我们将通过告诉 Claude 有关特定工具的信息来“欺骗”它,但我们不需要实际调用底层工具函数。我们使用该工具作为强制特定响应结构的一种方式,如下图所示:
情感分析 #
让我们从一个简单的例子开始。假设我们希望 Claude 分析某些文本中的情感,并使用遵循以下形状的 JSON 对象进行响应:
{
“negative_score”:0.6,
“neutral_score”:0.3,
“positive_score”:0.1
}
我们所要做的就是定义一个使用 JSON Schema 捕获此形状的工具。以下是可能的实现:
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 想要使用一个工具。工具使用响应应该包含我们想要的确切格式的所有数据。
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
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 的回复。我们已将重要部分加粗:
Claude“认为”它正在调用一个将使用这种情绪分析数据的工具,但实际上我们只是要提取数据并将其转换为 JSON:
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.")
Sentiment Analysis (JSON):
{
"positive_score": 0.0,
"negative_score": 0.791,
"neutral_score": 0.209
}
成功了!现在让我们将其转换为可重复使用的函数,该函数接受推文或文章,然后打印或返回 JSON 格式的情绪分析。
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.")
analyze_sentiment("OMG I absolutely love taking bubble baths soooo much!!!!")
Sentiment Analysis (JSON):
{
"positive_score": 0.8,
"negative_score": 0.0,
"neutral_score": 0.2
}
analyze_sentiment("Honestly I have no opinion on taking baths")
Sentiment Analysis (JSON):
{
"positive_score": 0.056,
"negative_score": 0.065,
"neutral_score": 0.879
}
使用 tool_choice 强制使用工具 #
目前,我们通过提示“强制”Claude 使用我们的 print_sentiment_scores 工具。在我们的提示中,我们写下仅使用 print_sentiment_scores 工具。这通常有效,但还有更好的方法!我们实际上可以使用 tool_choice 参数强制 Claude 使用特定工具:
tool_choice={“type”: “tool”, “name”: “print_sentiment_scores”}
上面的代码告诉 Claude 它必须通过调用 print_sentiment_scores 工具来响应。让我们更新我们的函数以使用它:
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,其中包含从文本示例中提取的人员、组织和位置等实体:
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.")
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 我们希望它使用给定工具会有所帮助:使用 print_entities 工具。
提取维基百科页面文章摘要示例 #
让我们尝试另一个稍微复杂一点的示例。我们将使用 Python wikipedia 包获取整个维基百科页面文章并将其传递给 Claude。我们将使用 Claude 生成包含以下内容的响应:
- 文章的主要主题
- 文章摘要
- 文章中提到的关键字和主题列表
- 文章的类别分类列表(娱乐、政治、商业等)以及分类分数(即主题属于该类别的程度)
如果我们将有关沃尔特·迪士尼的维基百科文章传递给 Claude,我们可能会得到这样的结果:
{
“主题”:“沃尔特·迪士尼”,
“摘要”:“沃尔特·埃利亚斯·迪士尼是美国动画师、电影制片人和企业家。他是美国动画行业的先驱,并在动画片制作中引入了多项发展。他保持着个人获得奥斯卡奖和提名次数最多的记录。他还参与了迪士尼乐园和其他主题公园以及电视节目的开发。”,
“关键词”:[
“沃尔特·迪士尼”,
“动画”,
“电影制片人”,
“企业家”,
“迪士尼乐园”,
“主题公园”,
“电视”
],
“类别”: [
{
“名称”: “娱乐”,
“得分”: 0.9
},
{
“名称”: “商业”,
“得分”: 0.7
},
{
“名称”: “技术”,
“得分”: 0.6
}
]
}
这是一个函数的示例实现,该函数需要 Wikipedia 页面主题,查找文章,下载内容,将其传递给 Claude,然后打印出生成的 JSON 数据。我们使用相同的策略来定义一个工具来“指导”Claude 响应的形状。
注意:如果您的机器上没有 pip install wikipedia,请确保安装它!
import wikipedia
#tool definition
tools = [
{
"name": "print_article_classification",
"description": "Prints the classification results.",
"input_schema": {
"type": "object",
"properties": {
"subject": {
"type": "string",
"description": "The overall subject of the article",
},
"summary": {
"type": "string",
"description": "A paragaph summary of the article"
},
"keywords": {
"type": "array",
"items": {
"type": "string",
"description": "List of keywords and topics in the article"
}
},
"categories": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "The category name."},
"score": {"type": "number", "description": "The classification score for the category, ranging from 0.0 to 1.0."}
},
"required": ["name", "score"]
}
}
},
"required": ["subject","summary", "keywords", "categories"]
}
}
]
#The function that generates the json for a given article subject
def generate_json_for_article(subject):
page = wikipedia.page(subject, auto_suggest=True)
query = f"""
<document>
{page.content}
</document>
Use the print_article_classification tool. Example categories are Politics, Sports, Technology, Entertainment, Business.
"""
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("Text Classification (JSON):")
print(json.dumps(json_classification, indent=2))
else:
print("No text classification found in the response.")
generate_json_for_article("Jeff Goldblum")
Text Classification (JSON):
{
"subject": "Jeff Goldblum",
"summary": "Jeffrey Lynn Goldblum is an American actor and musician who has starred in some of the highest-grossing films, such as Jurassic Park and Independence Day. He has had a long and successful career in both film and television, with roles in a wide range of movies and TV shows. Goldblum is also an accomplished jazz musician and has released several albums with his band, The Mildred Snitzer Orchestra.",
"keywords": [
"actor",
"musician",
"Jurassic Park",
"Independence Day",
"film",
"television",
"jazz"
],
"categories": [
{
"name": "Entertainment",
"score": 0.9
}
]
}
generate_json_for_article("Octopus")
Text Classification (JSON):
{
"subject": "Octopus",
"summary": "This article provides a comprehensive overview of octopuses, including their anatomy, physiology, behavior, ecology, and evolutionary history. It covers topics such as their complex nervous systems, camouflage and color-changing abilities, intelligence, and relationships with humans.",
"keywords": [
"octopus",
"cephalopod",
"mollusc",
"marine biology",
"animal behavior",
"evolution"
],
"categories": [
{
"name": "Science",
"score": 0.9
},
{
"name": "Nature",
"score": 0.8
}
]
}
generate_json_for_article("Herbert Hoover")
Text Classification (JSON):
{
"subject": "Herbert Hoover",
"summary": "The article provides a comprehensive biography of Herbert Hoover, the 31st President of the United States. It covers his early life, career as a mining engineer and humanitarian, his presidency during the Great Depression, and his post-presidency activities.",
"keywords": [
"Herbert Hoover",
"Great Depression",
"Republican Party",
"U.S. President",
"mining engineer",
"Commission for Relief in Belgium",
"U.S. Food Administration",
"Secretary of Commerce",
"Smoot\u2013Hawley Tariff Act",
"New Deal"
],
"categories": [
{
"name": "Politics",
"score": 0.9
},
{
"name": "Business",
"score": 0.7
},
{
"name": "History",
"score": 0.8
}
]
}
练习 #
使用上述策略编写一个名为“translate”的函数,该函数接受一个单词或短语并生成一个结构化的 JSON 输出,其中包括原始的英语短语和翻译后的西班牙语、法语、日语和阿拉伯语短语。
下面是该函数应如何工作的示例:
如果我们调用此函数:
translate(“这要花多少钱”)
我们期望得到如下输出:
{
“english”:“这要花多少钱”,
“spanish”:“¿cuánto cuesta esto?”,
“french”:“combien ça coûte?”,
“japanese”:“これはいくらですか”,
“arabic”:“كم تكلفة هذا؟”
}
注意:如果您想打印结果,这行代码将帮助您很好地打印出来:
print(json.dumps(translations_from_claude, Ensure_ascii=False, Indent=2))