Files
FastDeploy/docs/zh/features/structured_outputs.md
2025-06-29 23:29:37 +00:00

333 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Structured Outputs
## 概述
Structured Outputs 是指通过预定义格式约束使大模型生成内容严格遵循指定结构。该功能可显著提升生成结果的可控性适用于需要精确格式输出的场景如API调用、数据解析、代码生成等同时支持动态语法扩展平衡灵活性与规范性。
FastDeploy 支持使用 [XGrammar](https://xgrammar.mlc.ai/docs/) 后端生成结构化输出。
支持输出格式
- `json格式`: 输出内容为标准的 JSON 格式
- `正则表达式格式regex`: 精确控制文本模式(如日期、邮箱、身份证号等),支持复杂正则表达式语法
- `选项格式choice`: 输出严格限定在预定义选项中
- `语法格式grammar`: 使用扩展 BNF 语法定义复杂结构,支持字段名、类型及逻辑关系的联合约束
- `结构标签格式structural_tag`: 通过标签树定义结构化输出,支持嵌套标签、属性校验及混合约束
## 使用方式
服务启动时,可以通过 `--guided-decoding-backend` 参数指定期望使用的后端,如果指定 `auto`, FastDeploy 会自动选择合适的后端。
### OpenAI 接口
FastDeploy 支持 OpenAI 的 [Completions](https://platform.openai.com/docs/api-reference/completions) 和 [Chat Completions](https://platform.openai.com/docs/api-reference/chat) APIOpenAI 通过 `response_format` 参数指定 Structured Outputs 的输出格式。
FastDeploy 支持通过指定 `json_object` 来保证输出为json格式如果想指定 JSON 内部具体参数名、类型等信息,可以通过 `json_schema` 来指定详细信息,详细使用方法可以参考示例。
```python
import openai
port = "8170"
client = openai.Client(base_url=f"http://127.0.0.1:{port}/v1", api_key="null")
completion = client.chat.completions.create(
model="null",
messages=[
{
"role": "user",
"content": "生成一个包含以下信息的JSON对象中国四大发明名称、发明朝代及简要描述每项不超过50字",
}
],
response_format={"type": "json_object"}
)
print(completion.choices[0].message.content)
print("\n")
```
输出
```
{"inventions": [{"name": "造纸术", "dynasty": "东汉", "description": "蔡伦改进造纸术,用树皮、麻头等为原料,成本低廉,推动文化传播。"}, {"name": "印刷术", "dynasty": "唐朝", "description": "雕版印刷术在唐朝出现,后毕昇发明活字印刷术,提高印刷效率。"}, {"name": "火药", "dynasty": "唐朝", "description": "古代炼丹家偶然发明,后用于军事,改变了战争方式和格局。"}, {"name": "指南针", "dynasty": "战国", "description": "最初称司南,后发展为指南针,为航海等提供方向指引。"}]}
```
以下示例要求模型返回一个 book 信息的 json 数据json 数据中必须包含`author、title、genre`三个字段,且`genre`必须在给定 `BookType` 中。
```python
import openai
from pydantic import BaseModel
from enum import Enum
class BookType(str, Enum):
romance = "Romance"
historical = "Historical"
adventure = "Adventure"
mystery = "Mystery"
dystopian = "Dystopian"
class BookDescription(BaseModel):
author: str
title: str
genre: BookType
port = "8170"
client = openai.Client(base_url=f"http://127.0.0.1:{port}/v1", api_key="null")
completion = client.chat.completions.create(
model="null",
messages=[
{
"role": "user",
"content": "生成一个JSON描述一本中国的著作要包含作者、标题和书籍类型。",
}
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "book-description",
"schema": BookDescription.model_json_schema()
},
},
)
print(completion.choices[0].message.content)
print("\n")
```
输出
```
{"author": "曹雪芹", "title": "红楼梦", "genre": "Historical"}
```
`structural_tag` 可以提取输入内容中的重点信息,并调用指定方法,返回预定义的结构化输出。以下示例展示了通过 `structural_tag` 获取指定时区并输出格式化结果。
```python
import openai
port = "8170"
client = openai.Client(base_url=f"http://127.0.0.1:{port}/v1", api_key="null")
content_str = """
你有以下函数可以调用:
{
"name": "get_current_date",
"description": "根据给定的时区获取当前日期和时间",
"parameters": {
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "获取当前日期和时间的时区, 例如: Asia/Shanghai",
}
},
"required": ["timezone"],
}
}
如果你选择只调用函数,请按照以下格式回复:
<{start_tag}={function_name}>{parameters}{end_tag}
其中
start_tag => `<function`
parameters => 一个JSON字典其中函数参数名为键函数参数值为值。
end_tag => `</function>`
这是一个示例,
<function=example_function_name>{"example_name": "example_value"}</function>
注意:
- 函数调用必须遵循指定的格式
- 必须指定所需参数
- 一次只能调用一个函数
- 将整个函数调用回复放在一行上
你是一个人工智能助理,请回答下列问题。
"""
completion = client.chat.completions.create(
model="null",
messages=[
{
"role": "system",
"content": content_str,
},
{
"role": "user",
"content": "你今天去上海出差",
}
],
response_format={
"type": "structural_tag",
"structures": [
{
"begin": "<function=get_current_date>",
"schema": {
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "获取当前日期和时间的时区, 例如: Asia/Shanghai",
}
},
"required": ["timezone"],
},
"end": "</function>",
}
],
"triggers": ["<function="],
},
)
print(completion.choices[0].message.content)
print("\n")
```
输出
```
<function=get_current_date>{"timezone": "Asia/Shanghai"}</function>
```
对于 `choice、grammar、regex`格式FastDeploy 使用 `extra_body` 参数支持,`choice` 较为简单,指定后模型会在预定义选项中选择一个最优项。在 FastDeploy 中,可以通过 `guided_choice` 指定一组与定义选项,示例如下
```python
import openai
port = "8170"
client = openai.Client(base_url=f"http://127.0.0.1:{port}/v1", api_key="null")
completion = client.chat.completions.create(
model="null",
messages=[
{"role": "user", "content": "深圳的地标建筑是什么?"}
],
extra_body={"guided_choice": ["深圳平安国际金融中心", "中国华润大厦(春笋大厦)", "深圳京基100", "深圳地王大厦"]},
)
print(completion.choices[0].message.content)
print("\n")
```
输出
```
深圳地王大厦
```
`regex` 允许用户通过正则表达式限制输出格式,通常用于需要精确控制文本格式的场景。以下示例展示了通过模型生成一个标准格式的网络地址的过程。
```python
import openai
port = "8170"
client = openai.Client(base_url=f"http://127.0.0.1:{port}/v1", api_key="null")
completion = client.chat.completions.create(
model="null",
messages=[
{
"role": "user",
"content": "生成一个标准格式的网络地址,包括协议、域名。\n",
}
],
extra_body={"guided_regex": r"^https:\/\/www\.[a-zA-Z]+\.com\/?$\n"},
)
print(completion.choices[0].message.content)
print("\n")
```
输出
```
https://www.example.com/
```
`grammar` 允许用户通过 EBNF(Extended Backus-Naur Form) 语法描述一个限制规则,以下示例展示了通过 `guided_grammar` 限制模型输出一段html代码。
```python
import openai
port = "8170"
client = openai.Client(base_url=f"http://127.0.0.1:{port}/v1", api_key="null")
html_h1_grammar = """
root ::= html_statement
html_statement ::= "<h1" style_attribute? ">" text "</h1>"
style_attribute ::= " style=" dq style_value dq
style_value ::= (font_style ("; " font_weight)?) | (font_weight ("; " font_style)?)
font_style ::= "font-family: '" font_name "'"
font_weight ::= "font-weight: " weight_value
font_name ::= "Arial" | "Times New Roman" | "Courier New"
weight_value ::= "normal" | "bold"
text ::= [A-Za-z0-9 ]+
dq ::= ["]
"""
completion = client.chat.completions.create(
model="null",
messages=[
{
"role": "user",
"content": "生成一段html的代码对以下标题加粗、Times New Roman字体。标题ERNIE Bot",
}
],
extra_body={"guided_grammar": html_h1_grammar},
)
print(completion.choices[0].message.content)
print("\n")
```
输出
```
<h1 style="font-family: 'Times New Roman'; font-weight: bold">ERNIE Bot</h1>
```
### OpenAI beta 接口
在 OpenAI Client 1.54.4 版本之后,提供了 `client.beta.chat.completions.parse` 接口,通过该接口,实现了与 Python 原生类型更好的支持。以下示例展示了使用该接口直接获取 `pydantic` 类型的结构化输出。
```python
from pydantic import BaseModel
import openai
class Info(BaseModel):
addr: str
height: int
port = "8170"
client = openai.Client(base_url=f"http://127.0.0.1:{port}/v1", api_key="null")
completion = client.beta.chat.completions.parse(
model="null",
messages=[
{"role": "user", "content": "上海东方明珠在上海市浦东新区世纪大道1号高度为468米请问上海东方明珠的地址和高度是多少"},
],
response_format=Info,
)
message = completion.choices[0].message
print(message)
print("\n")
assert message.parsed
print("地址:", message.parsed.addr)
print("高度:", message.parsed.height)
```
输出
```
ParsedChatCompletionMessage[Info](content='{"addr": "上海市浦东新区世纪大道1号", "height": 468}', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None, parsed=Info(addr='上海市浦东新区世纪大道1号', height=468), reasoning_content=None)
地址: 上海市浦东新区世纪大道1号
高度: 468
```