Outlines 输出类型(Output Types)完全指南:用 Python 类型约束 LLM 结构化生成
【免费下载链接】outlinesStructured Outputs项目地址: https://gitcode.com/GitHub_Trending/ou/outlines
Outlines 以“输出类型”作为结构化生成的统一入口:向模型传入一个普通 Python 类型(如int、Literal、Pydantic 模型)或 Outlines 专用类型(Choice、JsonSchema、Regex、CFG),即可让文本生成严格服从该类型的语法约束。本文将完整梳理各类输出类型的用法、参数细节与底层编译原理,帮助你把这套类型体系直接应用到分类、信息抽取、JSON 生成与格式校验等实战场景中。
Overview:一次调用,一个输出类型
Outlines 模型在被调用时接收两个核心参数:一个prompt(提示词)和一个output type(输出类型),除此之外的任何推理关键字参数(如max_new_tokens、temperature)都会被原样转发给底层模型:
model("How many minutes are there in one hour", int) # "60" model("Pizza or burger", Literal["pizza", "burger"]) # "pizza" model("Create a character", Character, max_new_tokens=100) # '{"name": "James", ...}'输出类型可以来自整个 Python 类型生态:
- 大多数 Python 原生类型,例如
int、str; typing模块中的类型,例如Literal、List、Dict、Enum等;- 流行的第三方库类型,例如 Pydantic、GenSON。
Outlines 还针对特定输出结构提供了四个专用类型(下文逐一详解):
- 多选一:
Choice - JSON Schema:
JsonSchema - 正则表达式:
Regex - 上下文无关文法:
CFG
核心心智模型:输出类型 = 函数返回值的类型提示
使用 Outlines 时,你只需要把“你希望函数返回什么类型”作为输出类型传给模型即可。例如下面这些函数签名:
from datetime import date from typing import Dict, List, Literal, Union from pydantic import BaseModel class Character(BaseModel): name: str birth_date: date skills: Union[Dict, List[str]] def give_int() -> int: ... def pizza_or_burger() -> Literal["pizza", "burger"]: ... def create_character() -> Character: ...用 Outlines 模型生成时,把同样的类型直接作为输出类型传入,就能得到符合该类型约束的文本:
model("How many minutes are there in one hour", int) # "60" model("Pizza or burger", Literal["pizza", "burger"]) # "pizza" model("Create a character", Character, max_new_tokens=100) # '{"name": "James", "birth_date": "1980-05-10", "skills": ["archery", "negotiation"]}'与函数类型提示的关键区别:返回的一律是字符串
Outlines 生成器永远返回字符串,这是它与普通函数类型提示最重要的差异。你需要自己把响应 cast 成目标类型:
result = model("Create a character", Character, max_new_tokens=100) casted_result = Character.model_validate_json(result) print(result) # '{"name": "Aurora", "birth_date": "1990-06-15", "skills": ["Stealth", "Diplomacy"]}' print(casted_result) # name=Aurora birth_date=datetime.date(1990, 6, 15) skills=['Stealth', 'Diplomacy']在src/outlines/generator.py的BlackBoxGenerator与SteerableGenerator实现中,__call__返回的都是self.model.generate(...)的结果——约束只作用于生成过程,生成完成后不会替你解析字符串,因此类型转换(如上面的model_validate_json)必须由调用方完成。
输出类型分类
根据使用场景,输出类型可以划分为以下几类。其中大部分来自 Python 原生或知名第三方库,而JsonSchema、Regex、CFG是 Outlines 特有的三个类型。
基本 Python 类型
最直接的结构化生成方式是让回答符合某个基本类型,例如int或 Python 列表。可以使用原生基本类型和typing库中的类型:
from typing import Dict output_type = float # 合法输出示例:"0.05" output_type = bool # 合法输出示例:"True" output_type = Dict[int, str] # 合法输出示例:"{1: 'hello', 2: 'there'}"通过组合集合类型与Union、Optional,可以构建更复杂的响应格式。例如下面这个用于表示半结构化数据的输出类型:
from typing import Dict, List, Optional, Tuple, Union output_type = Dict[str, Union[int, str, List[Tuple[str, Optional[float]]]]]该类型对应的值是:键为字符串的字典,值可以是整数、字符串,或由“字符串 + 浮点数/None”组成的二元组列表。合法的生成响应示例(包含在字符串内):
{ "name": "Alice", "age": 30, "metrics": [("engagement", 0.85), ("satisfaction", None)] }源码层面的细节:在 src/outlines/types/dsl.py 中,python_types_to_terms负责把 Python 类型逐层转换成 DSL 的Term对象。例如int映射为types.integer(正则[+-]?(0|[1-9][0-9]*)),float映射为types.number,bool映射为Regex("(True|False)"),原生dict则直接映射为内置 JSON 文法CFG(grammars.json)。容器类型的处理尤为精细:
_handle_list要求List必须恰好一个类型参数(只支持同构列表),否则抛出TypeError;_handle_dict要求Dict恰好两个类型参数,并强制给键加 JSON 双引号——即使键类型是int(如Dict[int, str]),因为 JSON 对象的键必须是被引号包裹的字符串;_handle_union会先剥离None成员,再把None映射为Regex("None"),因此Union[int, str, None]与Optional[Union[int, str]]都能正确处理,而不是像旧实现那样对NoneType报 “not supported”;- 转换过程有 10 层递归深度上限,超出会抛
RecursionError,用于防止递归类型定义导致无限递归。
多选一(Multiple Choices)
Outlines 通过Literal或Enum输出类型支持多选一分类。例如:
from enum import Enum from typing import Literal class PizzaOrBurger(Enum): pizza = "pizza" burger = "burger" # 两种等价的多选输出类型 output_type = Literal["pizza", "burger"] output_type = PizzaOrBurger此外,还可以使用 Outlines 特有的Choice类型,它接收一个list作为参数,特别适合选项列表是动态生成的场景:
from outlines.types import Choice def get_multiple_choices() -> list: # 这里可以包含复杂逻辑 return ["pizza", "burger"] output_type = Choice(get_multiple_choices())源码层面的细节:Choice在 src/outlines/types/dsl.py 中定义为@dataclass,字段为items: List[Any]。to_regex对每个 item 递归调用python_types_to_terms后以|连接,例如Choice(["a", "b", "c"])会编译为正则(a|b|c)(见 tests/types/test_dsl.py 与 tests/types/test_to_regex.py 中的验证)。Enum类型则被转换为Alternatives:成员逐一转换后并列,且_get_enum_members会额外识别“以裸函数作为成员值”的枚举成员(通过__qualname__前缀区分普通方法与被赋值的函数),因此“函数枚举”这类特殊用法也能被正确约束。
JSON Schema
很多常见的 Python 类型存储的信息本质上等价于一份 JSON Schema。Outlines 中可用于生成符合 JSON Schema 文本的类型包括:
- Pydantic 类
- Dataclass
- TypedDict
- GenSON 的
SchemaBuilder - Callable(函数的参数被转换为 JSON 键,类型注解用于定义值的类型)
例如:
from dataclasses import dataclass @dataclass class Character: name: str age: int output_type = Character def character(name: str, age: int): return None output_type = character其中“Callable”路径由 src/outlines/types/utils.py 的get_schema_from_signature实现:通过inspect.signature读取每个参数的类型注解,未注解的参数会抛出ValueError;无默认值的参数在 schema 中为必填,带默认值的参数变为可选;随后用create_model动态构造 Pydantic 模型并导出model_json_schema()。
另外两种 JSON Schema 格式——schema 字符串和schema 字典——必须使用 Outlines 特有的JsonSchema类包装。原因是它们只是普通的字符串/字典,直接传入会产生歧义,无法与str、dict等基本类型区分:
from outlines.types import JsonSchema schema_string = '{"type": "object", "properties": {"answer": {"type": "number"}}}' output_type = JsonSchema(schema_string) schema_dict = { "type": "object", "properties": { "answer": {"type": "number"} } } output_type = JsonSchema(schema_dict)JsonSchema接受两个可选参数:
whitespace_pattern(默认None):指定 JSON 语法空白符的匹配模式;不提供时使用默认的宽松 JSON 空白规则。该参数只由outlines_core后端应用(见 src/outlines/backends/outlines_core.py 中build_regex_from_schema(json_schema, whitespace_pattern)的调用);llguidance与xgrammar后端不支持它,设置后会产生错误(llguidance后端的 docstring 明确标注 “Not supported by the llguidance backend; must beNone”,见 src/outlines/backends/llguidance.py)。ensure_ascii(默认True):对应json.dumps方法的ensure_ascii参数。设为False时,schema 中的非 ASCII 字符会被转换为 Unicode 形式。
源码层面的细节:JsonSchema的构造函数在 src/outlines/types/dsl.py 中会按输入类型分派:字典走json.dumps(schema, ensure_ascii=...),Pydantic 模型走model_json_schema(),TypedDict/Dataclass 走TypeAdapter(...).json_schema(),GenSON builder 走to_json(ensure_ascii=...);最后用jsonschema.Draft7Validator.check_schema校验 schema 合法性,非法输入会抛ValueError。它还提供了两个实用的类方法:JsonSchema.from_file(path)可直接从.json文件加载 schema;JsonSchema.convert_to(schema, target_types)可以在"str"、"dict"、"pydantic"、"typeddict"、"dataclass"、"genson"之间互相转换。
正则表达式(Regex Patterns)
Outlines 支持用正则表达式约束文本生成。由于正则本质上是字符串字面量,直接传入会有歧义,因此必须用outlines.types.Regex对象包装:
from outlines.types import Regex regex = r"[0-9]{3}" output_type = Regex(regex)outlines.types模块内置了一批常用的正则模式(定义在 src/outlines/types/init.py),可以直接 import 作为输出类型使用,例如句子、邮箱、ISBN 等:
from outlines.types import sentence print(type(sentence)) # outlines.types.dsl.Regex print(sentence.pattern) # [A-Z].*\s*[.!?]仓库源码中的内置模式远不止这三个,按__all__的导出清单可归纳为:
- Python 风格类型:
string、integer、boolean、number、date、time、datetime; - 基本正则类型:
digit、char、newline、whitespace、hex_str、uuid4、ipv4、ipv6、semver、mac_address、hex_color、slug、credit_card; - 文档类类型:
sentence([A-Z].*\s*[.!?])、paragraph(一个或多个句子后跟换行)、email(RFC 5322 兼容)、isbn。
这些模式大多有严谨的注解,例如ipv6覆盖八组冒号十六进制、::零压缩、IPv4 映射等完整形式;credit_card按发卡机构前缀与长度匹配 Visa、Mastercard、Amex、Diners Club、Discover、JCB、Maestro、UnionPay 等卡号格式(不校验 Luhn 校验和)。
需要自己构建复杂正则时,可以使用 Outlines 的正则 DSL:它把正则表达式组织成Term对象树,支持String/Regex两种叶子节点,以及exactly、optional、one_or_more、zero_or_more、between、at_least、at_most等量词方法,还可用+拼接、either()做或运算,并能把自定义类型直接嵌入 Pydantic 模型做字段校验。
上下文无关文法(Context-Free Grammars)
Outlines 允许生成符合上下文无关文法语法的文本。文法使用 Lark 语言定义。由于文法以字符串表达,因此必须用outlines.types.CFG对象包装:
from outlines.types import CFG grammar_string = """ start: expr expr: "{" expr "}" | "[" expr "]" | """ output_type = CFG(grammar_string)源码层面的细节:CFG是@dataclass,字段为definition: str,并且重载了__eq__(按文法字符串相等比较)。它还提供了CFG.from_file(path)类方法,可以从文件直接读取文法定义。仓库在 src/outlines/grammars.py 中预置了几个常用 Lark 文法,通过read_grammar从 src/outlines/grammars/ 目录加载——包括arithmetic(算数文法)与json(JSON 文法,python_types_to_terms转换原生dict时使用的就是它),对应的.lark源文件位于 src/outlines/grammars/arithmetic.lark 和 src/outlines/grammars/json.lark,并在 tests/cfg_samples/ 下有对应的解析测试样本。
输出类型的编译原理:从 Python 类型到 logits processor
理解输出类型如何在底层生效,有助于你判断哪些类型适合哪个后端。在 src/outlines/generator.py 中,Generator工厂函数按模型类型分派:
SteerableModel(本地可控模型):SteerableGenerator在初始化时会把输出类型编译成logits processor(该过程可能较昂贵,因此只构建一次并复用)。编译路径为:python_types_to_terms(output_type)先把任意 Python 类型转为 DSLTerm,然后按Term的类型分派到后端工厂函数——CFG走get_cfg_logits_processor,JsonSchema走get_json_schema_logits_processor(同时传入whitespace_pattern),其余全部走to_regex得到正则字符串后交给get_regex_logits_processor。每次调用__call__/batch/stream前都会先logits_processor.reset()再传给model.generate。BlackBoxModel/AsyncBlackBoxModel(远程 API 模型):输出类型不会编译为 logits processor,而是原样传给模型,由模型方自行处理约束(对应BlackBoxGenerator的 docstring 所述)。- 若同时传入
output_type与processor,Generator会抛出ValueError(两者互斥);processor参数仅对SteerableModel开放,供高级用户直接注入已构建好的 logits processor。
从源码结构看,可推断出以下关键结论:to_regex(见 src/outlines/types/dsl.py 的to_regex函数)把String、Regex、JsonSchema(经build_regex_from_schema)、Choice、KleeneStar/KleenePlus、Optional、Alternatives、Sequence、四种Quantify*统一递归编译为正则字符串,因此绝大多数输出类型最终都落在“正则约束”这一能力上;而JsonSchema与CFG则分别依赖后端的 JSON schema 与文法编译能力。
输出类型的可用性
上文介绍的各输出类型并非对所有模型都可用——部分模型只提供有限的结构化输出支持。具体支持范围取决于你使用的模型与后端(如transformers、vllm、ollama、llamacpp、openai等),请查阅对应模型文档(参见 docs/features/models/ 目录与 结构化生成后端)确认其支持哪些输出类型。实操时建议遵循两条原则:本地可导向模型(SteerableModel)优先选择与后端匹配的输出类型(注意whitespace_pattern仅outlines_core支持);远程 API 模型则将输出类型直接透传给服务方能力支持的形式。
【免费下载链接】outlinesStructured Outputs项目地址: https://gitcode.com/GitHub_Trending/ou/outlines
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考