AI_COMPLETE 结构化输出¶
AI_COMPLETE 结构化输出允许您提供完成响应必须遵循的 JSON 架构。这减少了 AI 数据管道中的后处理需求,并实现了与需要确定性响应的系统的无缝集成。AI_COMPLETE 根据您的 JSON 架构验证每个生成的令牌,以确保响应符合提供的架构。
AI_COMPLETE 支持的每个模型都支持结构化输出,但最强大的模型通常会产生更高质量的响应。
使用 AI_COMPLETE 结构化输出¶
要获得采用结构化格式的响应,请指定一个对象,将 JSON 架构 (https://json-schema.org/) 表示为 response_format
实参。提供的 JSON 架构对象定义了生成文本必须遵循的结构、数据类型和约束条件,包括必填字段。对于简单任务,您无需指定输出格式的任何细节,甚至无需指示模型“以 JSON 格式响应”。对于更复杂的任务,提示模型以 JSON 格式响应可以提高准确性;详见 优化 JSON 依从性准确性。
ai_complete(
...
response_format => {
'type': 'json',
'schema': {
'type': 'object',
'properties': {
'property_name': {
'type': 'string'
},
...
},
'required': ['property_name', ...]
}
}
SQL 示例¶
以下示例演示如何使用 response_format
实参来指定响应的 JSON 架构。
SELECT AI_COMPLETE(
model => 'mistral-large2',
prompt => 'Return the customer sentiment for the following review: New kid on the block, this pizza joint! The pie arrived neither in a flash nor a snail\'s pace, but the taste? Divine! Like a symphony of Italian flavors, it was a party in my mouth. But alas, the party was a tad pricey for my humble abode\'s standards. A mixed bag, I\'d say!',
response_format => {
'type':'json',
'schema':{'type' : 'object','properties' : {'sentiment_categories':{'type':'array','items':{'type':'object','properties':
{'food_quality' : {'type' : 'string'},'food_taste': {'type':'string'}, 'wait_time': {'type':'string'}, 'food_cost': {'type':'string'}},'required':['food_quality','food_taste' ,'wait_time','food_cost']}}}}
}
);
响应:
{
"sentiment_categories": [
{
"food_cost": "negative",
"food_quality": "positive",
"food_taste": "positive",
"wait_time": "neutral"
}
]
}
以下示例演示如何使用 response_format
实参为响应指定 JSON 架构,以及如何使用 show_details
实参返回推理元数据。
SELECT AI_COMPLETE(
model => 'mistral-large2',
prompt => 'Return the customer sentiment for the following review: New kid on the block, this pizza joint! The pie arrived neither in a flash nor a snail\'s pace, but the taste? Divine! Like a symphony of Italian flavors, it was a party in my mouth. But alas, the party was a tad pricey for my humble abode\'s standards. A mixed bag, I\'d say!',
response_format => {
'type':'json',
'schema':{'type' : 'object','properties' : {'sentiment_categories':{'type':'array','items':{'type':'object','properties':
{'food_quality' : {'type' : 'string'},'food_taste': {'type':'string'}, 'wait_time': {'type':'string'}, 'food_cost': {'type':'string'}},'required':['food_quality','food_taste' ,'wait_time','food_cost']}}}}
},
show_details => TRUE
);
响应:
{
"created": 1738683744,
"model": "mistral-large2",
"structured_output": [
{
"raw_message": {
"sentiment_categories": [
{
"food_cost": "negative",
"food_quality": "positive",
"food_taste": "positive",
"wait_time": "neutral"
}
]
},
"type": "json"
}
],
"usage": {
"completion_tokens": 60,
"prompt_tokens": 94,
"total_tokens": 154
}
}
Python 示例¶
备注
结构化输出功能在 snowflake-ml-python
版本 1.8.0 及更高版本中受支持。
以下示例演示如何使用 response_format
实参来指定响应的 JSON 架构。
from snowflake.cortex import complete, CompleteOptions
response_format = {
"type": "json",
"schema": {
"type": "object",
"properties": {
"people": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "number"},
},
"required": ["name", "age"],
},
}
},
"required": ["people"],
},
}
prompt = [{
"role": "user",
"content": "Please prepare me a data set of 5 ppl and their age",
}]
options = CompleteOptions(
max_tokens=4096,
temperature=0.7,
top_p=1,
guardrails=False,
response_format=response_format
)
result = complete(
model="claude-3-5-sonnet",
prompt=prompt,
session={session_object}, # session created via connector
stream=True,
options=options,
)
output = "".join(result)
print(output)
响应:
{"people": [{"name":"John Smith","age":32},{"name":"Sarah Johnson","age":28},
{"name":"Michael Chen","age":45},{"name":"Emily Davis","age":19},{"name":"Robert Wilson","age":56}]}
Pydantic 示例¶
Pydantic 是适用于 Python 的数据验证和设置管理库。此示例使用 Pydantic 定义响应格式的架构。该代码执行以下步骤:
使用 Pydantic 定义架构
使用
model_json_schema
方法将 Pydantic 模型转换为 JSON 架构将 JSON 架构作为
response_format
实参传递给complete
函数
备注
此示例旨在在 Snowsight Python 工作表中运行,该工作表已经与 Snowflake 建立了连接。要在不同的环境中运行该工作表,您可能需要使用 Snowflake Connector for Python 建立与 Snowflake 的连接。
from pydantic import BaseModel, Field
import json
from snowflake.cortex import complete, CompleteOptions
from snowflake.snowpark.context import get_active_session
class Person(BaseModel):
age: int = Field(description="Person age")
name: str = Field(description="Person name")
people: list[Person] = Field(description="People list")
ppl = Person.model_json_schema()
'''
This is the ppl object, keep in mind there's a '$defs' key used
{'$defs': {'Person': {'properties': {'age': {'description': 'Person age', 'title': 'Age', 'type': 'integer'}, 'name': {'description': 'Person name', 'title': 'Name', 'type': 'string'}}, 'required': ['age', 'name'], 'title': 'Person', 'type': 'object'}}, 'properties': {'people': {'description': 'People list', 'items': {'$ref': '#/$defs/Person'}, 'title': 'People', 'type': 'array'}}, 'required': ['people'], 'title': 'People', 'type': 'object'}
'''
response_format_pydantic={
"type": "json",
"schema": ppl,
}
prompt=[{"role": "user", "content": "Please prepare me a data set of 5 ppl and their age"}]
options_pydantic = CompleteOptions( # random params
max_tokens=4096,
temperature=0.7,
top_p=1,
guardrails=False,
response_format=response_format_pydantic
)
model_name = "claude-3-5-sonnet"
session = get_active_session()
try:
result_pydantic = complete(
model=model_name,
prompt=prompt,
session=session,
stream=True,
options=options_pydantic,
)
except Exception as err:
result_pydantic = (chunk for chunk in err.response.text) # making sure it's generator, similar to the valid response
output_pydantic = "".join(result_pydantic)
print(output_pydantic)
响应:
{"people": [{"name":"John Smith","age":32},{"name":"Sarah Johnson","age":45},
{"name":"Mike Chen","age":28},{"name":"Emma Wilson","age":19},{"name":"Robert Brown","age":56}]}
REST API 示例¶
您可以使用 Snowflake Cortex LLM REST API 调用 COMPLETE 与您选择的 LLM。下面是使用 Cortex LLM REST API 提供架构的示例:
curl --location --request POST 'https://<account_identifier>.snowflakecomputing.cn/api/v2/cortex/inference:complete'
--header 'Authorization: Bearer <jwt>' \
--header 'Accept: application/json, text/event-stream' \
--header 'Content-Type: application/json' \
--data-raw '{
"model": "claude-3-5-sonnet",
"messages": [{
"role": "user",
"content": "Order a pizza for a hungry space traveler heading to the planet Zorgon. Make sure to include a special instruction to avoid any intergalactic allergens."
}],
"max_tokens": 1000,
"response_format": {
"type": "json",
"schema":
{
"type": "object",
"properties":
{
"crust":
{
"type": "string",
"enum":
[
"thin",
"thick",
"gluten-free",
"Rigellian fungus-based"
]
},
"toppings":
{
"type": "array",
"items":
{
"type": "string",
"enum":
[
"Gnorchian sausage",
"Andromedian mushrooms",
"Quasar cheese"
]
}
},
"delivery_planet":
{
"type": "string"
},
"special_instructions":
{
"type": "string"
}
},
"required":
[
"crust",
"toppings",
"delivery_planet"
]
}
}
}
}'
响应:
data: {"id":"4d62e41a-d2d7-4568-871a-48de1463ed2a","model":"claude-3-5-sonnet","choices":[{"delta":{"content":"{\"crust\":","content_list":[{"type":"text","text":"{\"crust\":"}]}}],"usage":{}}
data: {"id":"4d62e41a-d2d7-4568-871a-48de1463ed2a","model":"claude-3-5-sonnet","choices":[{"delta":{"content":" \"thin\"","content_list":[{"type":"text","text":" \"thin\""}]}}],"usage":{}}
data: {"id":"4d62e41a-d2d7-4568-871a-48de1463ed2a","model":"claude-3-5-sonnet","choices":[{"delta":{"content":", \"topping","content_list":[{"type":"text","text":", \"topping"}]}}],"usage":{}}
data: {"id":"4d62e41a-d2d7-4568-871a-48de1463ed2a","model":"claude-3-5-sonnet","choices":[{"delta":{"content":"s\": [\"Quasar","content_list":[{"type":"text","text":"s\": [\"Quasar"}]}}],"usage":{}}
创建一个 JSON 架构定义¶
为了获得 COMPLETE 结构化输出的最佳准确度,请遵循以下指导原则:
使用架构中的“required”字段 来指定必填字段。如果无法提取必填字段,COMPLETE 会报错。
在以下示例中,架构指示 COMPLETE 查找文档中提到的人员。
people
字段被标记为必填项,以确保人员被识别。{ 'type': 'object', 'properties': { 'dataset_name': { 'type': 'string' }, 'created_at': { 'type': 'string' }, 'people': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'name': { 'type': 'string' }, 'age': { 'type': 'number' }, 'isAdult': { 'type': 'boolean' } } } } }, 'required': [ 'dataset_name', 'created_at', 'people' ] }
响应:
{ "dataset_name": "name", "created_at": "date", "people": [ { "name": "Andrew", "isAdult": true } ] }
提供要提取的字段的详细描述,以便模型能够更准确地识别它们。例如,以下架构包括每个
people
字段的描述:name
、age
和isAdult
。{ 'type': 'object', 'properties': { 'dataset_name': { 'type': 'string' }, 'created_at': { 'type': 'string' }, 'people': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'name': { 'type': 'string', 'description': 'name should be between 9 to 10 characters' }, 'age': { 'type': 'number', 'description': 'Should be a value between 0 and 200' }, 'isAdult': { 'type': 'boolean', 'description': 'Persons is older than 18' } } } } } }
使用 JSON 引用¶
架构引用解决了使用 Cortex COMPLETE 结构化输出时的实际问题。使用以 $ref
表示的引用,您可以一次定义地址或价格等常用对象,然后在整个架构中引用它们。这样,当您需要更新验证逻辑或添加字段时,可以在一个地方(而不是在多个位置)进行更改,从而减少出错的可能性。
使用引用可以减少因实现不一致而导致的错误,并简化代码审查。引用的组件可以创建更简洁的层次结构,更好地表示数据模型中的实体关系。随着项目变得越来越复杂,这种模块化方法可以帮助您在保持架构完整性的同时管理技术债务。
Pydantic 之类的第三方库在 Python 中原生支持引用机制,从而简化了代码中架构的使用。
以下准则适用于 JSON 架构中引用的使用:
作用域限制:
$ref
机制仅限于用户的架构;不支持外部架构引用(例如 HTTPURLs)。定义位置: 对象定义应放置在架构的顶层,特别是在定义或
$defs
键下方。强制执行: 虽然 JSON 架构规范建议使用
$defs
键进行定义,但 Snowflake 的验证机制严格执行该结构。以下是有效$defs
对象的示例:
{
'$defs': {
'person':{'type':'object','properties':{'name' : {'type' : 'string'},'age': {'type':'number'}}, 'required':['name','age']}},
'type': 'object',
'properties': {'title':{'type':'string'},'people':{'type':'array','items':{'$ref':'#/$defs/person'}}}
}
使用 JSON 引用的示例¶
此 SQL 示例演示了 JSON 架构中引用的使用。
select ai_complete(
model => 'claude-3-5-sonnet',
prompt => 'Extract structured data from this customer interaction note: Customer Sarah Jones complained about the mobile app crashing during checkout. She tried to purchase 3 items: a red XL jacket ($89.99), blue running shoes ($129.50), and a fitness tracker ($199.00). The app crashed after she entered her shipping address at 123 Main St, Portland OR, 97201. She has been a premium member since January 2024.',
'response_format' => {
'type': 'json',
'schema': {
'type': 'object',
'$defs': {
'price': {
'type': 'object',
'properties': {
'amount': {'type': 'number'},
'currency': {'type': 'string'}
},
'required': ['amount']
},
'address': {
'type': 'object',
'properties': {
'street': {'type': 'string'},
'city': {'type': 'string'},
'state': {'type': 'string'},
'zip': {'type': 'string'},
'country': {'type': 'string'}
},
'required': ['street', 'city', 'state']
},
'product': {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'category': {'type': 'string'},
'color': {'type': 'string'},
'size': {'type': 'string'},
'price': {'$ref': '#/$defs/price'}
},
'required': ['name', 'price']
}
},
'properties': {
'customer': {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'membership': {
'type': 'object',
'properties': {
'type': {'type': 'string'},
'since': {'type': 'string'}
}
},
'shipping_address': {'$ref': '#/$defs/address'}
},
'required': ['name']
},
'issue': {
'type': 'object',
'properties': {
'type': {'type': 'string'},
'platform': {'type': 'string'},
'stage': {'type': 'string'},
'severity': {'type': 'string', 'enum': ['low', 'medium', 'high', 'critical']}
},
'required': ['type', 'platform']
},
'cart': {
'type': 'object',
'properties': {
'items': {
'type': 'array',
'items': {'$ref': '#/$defs/product'}
},
'total': {'$ref': '#/$defs/price'},
'item_count': {'type': 'integer'}
}
},
'recommended_actions': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'department': {'type': 'string'},
'action': {'type': 'string'},
'priority': {'type': 'string', 'enum': ['low', 'medium', 'high', 'urgent']}
}
}
}
},
'required': ['customer', 'issue','cart']
}
}
}
);
响应:
{
"created": 1747313083,
"model": "claude-3-5-sonnet",
"structured_output": [
{
"raw_message": {
"cart": {
"item_count": 3,
"items": [
{
"color": "red",
"name": "jacket",
"price": {
"amount": 89.99,
"currency": "USD"
},
"size": "XL"
},
{
"color": "blue",
"name": "running shoes",
"price": {
"amount": 129.5,
"currency": "USD"
}
},
{
"name": "fitness tracker",
"price": {
"amount": 199,
"currency": "USD"
}
}
],
"total": {
"amount": 418.49,
"currency": "USD"
}
},
"customer": {
"membership": {
"since": "2024-01",
"type": "premium"
},
"name": "Sarah Jones",
"shipping_address": {
"city": "Portland",
"state": "OR",
"street": "123 Main St",
"zip": "97201"
}
},
"issue": {
"platform": "mobile",
"severity": "high",
"stage": "checkout",
"type": "app_crash"
}
},
"type": "json"
}
],
"usage": {
"completion_tokens": 57,
"prompt_tokens": 945,
"total_tokens": 1002
}
}
优化 JSON 依从性准确性¶
COMPLETE 结构化输出通常不需要提示;它已经明白它的响应应该符合您指定的架构。然而,任务复杂度会显著影响 LLMs 遵循 JSON 响应格式的能力。任务越复杂,指定提示就越能提高结果的准确性。
简单任务,例如文本分类、实体提取、释义和摘要任务,这些任务不需要复杂的推理,通常不需要额外的提示。对于智能程度较低的小型模型,仅使用结构化输出功能就能显著提高 JSON 的格式遵循准确度,因为它会忽略模型生成的、与提供架构无关的任何文本。
中等复杂度任务 包括任何要求模型进行额外推理的简单任务,例如为分类决策提供理由。对于这些用例,我们建议添加“以 JSON 响应”来优化性能。
复杂推理任务 会要求模型执行更具开放性和模糊性的任务,例如根据回答的相关性、专业性和忠实度来评估和评分通话质量。对于这些用例,我们建议使用最强大的模型,例如 Anthropic 的
claude-3-5-sonnet
或 Mistral AI 的mistral-large2
,并添加“以 JSON 响应”,以及有关您想要在提示中生成的架构的详细信息。
为了获得最一致的结果,请在调用 COMPLETE 时将 temperature
选项设置为 0,无论任务或模型如何。
小技巧
要处理模型可能引发的错误,请使用 TRY_COMPLETE 而不是 COMPLETE。
成本注意事项¶
Cortex COMPLETE 结构化输出功能根据处理的词元数量计算成本,但不会因对照 JSON 架构验证每个词元而产生额外计算成本。然而,处理(和计费)的词元数量会随着架构复杂性的增加而增加。一般来说,提供的架构越大越复杂,消耗的输入和输出词元就越多。深度嵌套的高度结构化响应(例如分层数据)比简单架构消耗更多词元。
限制¶
您不能在架构的键中使用空格。
属性名称允许使用的字符包括:字母、数字、连字符和下划线。名称的最大长度为 64 个字符。
您不能使用
$ref
或者$dynamicRef
引用外部架构。
不支持以下约束关键字。使用不受支持的约束关键字会导致错误。
类型 |
关键字 |
---|---|
整数 |
|
数字 |
|
字符串 |
|
数组 |
|
对象 |
|
这些限制可能会在未来的版本中得到解决。
错误条件¶
情况 |
示例消息 |
HTTP 状态代码 |
---|---|---|
请求验证失败。由于模型无法生成有效的响应,查询被取消。这可能是由格式错误的请求引起。 |
|
400 |
输入架构验证失败。由于模型无法生成有效的响应,查询被取消。这可能是由于请求负载中缺失必需的属性,或者使用了不受支持的 json 架构功能(例如约束),或者不当使用 $ref 机制(例如,超出架构范围) |
|
400 |
模型输出验证失败。模型无法生成与架构匹配的响应。 |
|
422 |