文章翻译自官网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 Anthropicfrom dotenv import load_dotenvimport jsonload_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}])responseplaintext
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 jsonjson_sentiment = Nonefor content in response.content: if content.type == "tool_use" and content.name == "print_sentiment_scores": json_sentiment = content.input breakif 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 = Nonefor content in response.content: if content.type == "tool_use" and content.name == "print_entities": json_entities = content.input breakif 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))
