ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

VLLM 格式化LLM输出实战:用 guided_json、guided_regex 与 guided_choice 约束生成结果

VLLM 格式化LLM输出实战:用 guided_json、guided_regex 与 guided_choice 约束生成结果 1. 为什么下游解析总在崩从一次线上事故说起如果你正在用 VLLM 部署 OpenAI 兼容接口并且下游有程序要解析模型输出那你大概率遇到过这种场景模型明明回答得挺对但你的json.loads()就是报错因为它在 JSON 外面裹了一层 json 代码块或者干脆在末尾加了一句希望这个回答对你有帮助。这类问题在 VLLM 里有一个非常干净的解法就是格式化输出Guided Decoding核心参数有三个guided_json、guided_regex、guided_choice。它们能做什么简单说就是在解码阶段直接约束 token 的采样空间让模型物理上无法生成不符合格式的内容而不是靠 prompt 里写请务必返回 JSON这种软约束。适合谁适合所有在 VLLM 上跑结构化抽取、分类打标、字段填充、Agent 工具调用参数生成的开发者。我试过用 prompt 反复强调格式结果模型在 temperature0 时稳定一调高就开始飘。后来换成 guided 系列参数解析失败率直接归零。这篇就按能跟做的标准把三类参数的配置骨架、请求示例、验证方法和踩坑点全部铺开。文中接口地址统一用 TaoToken 的 OpenAI 兼容入口做演示你本地起 VLLM 服务时把 base_url 换成http://localhost:8000/v1即可参数写法完全一致。2. TaoToken 前置准备拿到可调用的 OpenAI 兼容入口VLLM 的 guided 参数是通过extra_body传的属于 OpenAI SDK 的扩展字段所以你需要一个支持透传 extra_body 的客户端环境。如果你本地已经有 VLLM 服务直接跳到第 3 节。如果你想先用一个稳定的 OpenAI 兼容端点验证参数行为可以走 TaoToken 的接入方式。第一步注册并登录控制台在 API Keys 页面创建一个密钥。这个 Key 就是后面api_key字段的值。地址是 https://taotoken.net/api-keys 创建后复制保存页面只显示一次。第二步确认 base_url。TaoToken 的 OpenAI 兼容入口是 https://taotoken.net/api 注意末尾不要带/v1SDK 会自动拼接。如果你用的是原生 requests 调用完整路径是https://taotoken.net/api/v1/chat/completions。第三步选模型。guided 参数是否生效取决于后端推理引擎是否支持。VLLM 默认的格式化解码后端是 outlines对guided_json、guided_choice、guided_regex支持都比较成熟。你在 TaoToken 上选一个指令跟随能力强的模型即可比如 Qwen 系列或 Llama 系列的中等尺寸版本小模型在强约束下更容易出现语义偏移。第四步装依赖。只需要两个包pip install openai pydanticpydantic是用来定义 JSON Schema 的比手写 dict 更省心字段描述也能直接带进去。环境准备好之后下面所有代码你都可以直接复制运行只改api_key和model两个变量。3. 可复制配置三类 guided 参数的 sampling_params 骨架先给一个统一的客户端初始化函数后面三个例子都复用它避免重复代码import json from openai import OpenAI from pydantic import BaseModel, Field API_KEY 你的 TaoToken API Key BASE_URL https://taotoken.net/api MODEL 你的模型名 client OpenAI(api_keyAPI_KEY, base_urlBASE_URL) def chat(system_prompt, user_prompt, extra_bodyNone): kwargs dict( modelMODEL, temperature0, messages[ {role: system, content: system_prompt}, {role: user, content: user_prompt}, ], ) if extra_body: kwargs[extra_body] extra_body completion client.chat.completions.create(**kwargs) return completion.choices[0].message.content注意temperature0不是必须的但做格式验证时建议先固定为 0排除随机性干扰。guided 参数本身在采样阶段就做了 mask温度高一点也不会破坏格式但语义质量会波动。3.1 guided_json用 Pydantic 定义 Schema字段级约束这是最常用的一类。定义一个 Pydantic 模型把model_json_schema()塞进extra_body[guided_json]class Topic(BaseModel): 问题: str Field(description用户提出的问题) 答案: str Field(description对问题的详细解答) system_prompt 你是一个严谨的技术助手。 user_prompt 请生成一对和 Python 虚拟环境相关的问题和答案。 resp chat( system_prompt, user_prompt, extra_body{guided_json: Topic.model_json_schema()}, ) print(resp) data json.loads(resp) print(data[问题])实测下来即使 prompt 里完全不提返回 JSON输出也会是干净的 JSON 对象没有代码块包裹没有多余解释。Field(description...)里的描述会作为 schema 的一部分传给解码器对字段内容的语义有引导作用建议写清楚。如果你不想用 Pydantic也可以直接手写 schema dict效果一样schema { type: object, properties: { name: {type: string}, age: {type: integer, minimum: 0, maximum: 150}, tags: {type: array, items: {type: string}}, }, required: [name, age], }required字段一定要显式声明否则模型可能省略某些键下游取值时又要写一堆.get()兜底。3.2 guided_choice分类打标只能从候选里选情感分类、意图识别、路由分发这类任务输出空间是有限枚举用guided_choice最合适resp chat( 你是一个情感分类器。, 判断这条评论的情感今天天气真不错好想出去吃大餐。, extra_body{guided_choice: [Positive, Negative, Neutral]}, ) print(resp) # 只会输出 Positive / Negative / Neutral 之一这里有个必须记住的限制guided_choice只能输出单个选项无法处理多标签任务。如果你需要模型同时打多个标签得改用guided_json把标签定义成数组字段并在 schema 里约束items的 enumclass MultiLabel(BaseModel): labels: list[str] Field( description从候选标签中选择所有适用的, json_schema_extra{items: {enum: [技术, 产品, 运营, 设计]}}, )3.3 guided_regex抽取固定模式比如 IP、日期、订单号当你要抽的东西有明确的正则形态guided_regex比 JSON 更轻量。比如抽 IPv4 地址ip_regex r((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?) resp chat( 你是一个信息抽取助手。, 请给出一个公网 DNS 服务器的 IPv4 地址。, extra_body{guided_regex: ip_regex}, ) print(resp)这里要提醒一个坑正则约束的是输出必须匹配这个模式但不保证语义正确。上面这个例子模型可能输出1.8.8.88这种格式合法但实际不存在的地址。格式约束解决的是解析问题不是事实性问题两者要分开治理。如果你既要格式对又要内容对建议用guided_json把字段拆细配合 description 引导再在业务层做校验。3.4 三类参数对照表参数适用场景输出空间多值支持典型用途guided_json结构化对象JSON Schema 定义支持数组/嵌套信息抽取、工具调用参数guided_choice单标签分类固定枚举列表不支持情感、意图、路由guided_regex固定模式串正则匹配集不支持IP、日期、编号抽取4. 验证请求怎么确认约束真的生效了写完请求不代表约束生效必须做对照验证。最直接的方法是同一 prompt 跑两次一次不带 guided 参数一次带对比输出形态。user_prompt 请生成一对和 Python 装饰器相关的问题和答案。 base_resp chat(你是一个技术助手。, user_prompt) print( 无约束 ) print(base_resp) guided_resp chat( 你是一个技术助手。, user_prompt, extra_body{guided_json: Topic.model_json_schema()}, ) print( guided_json ) print(guided_resp) # 关键验证能否直接解析 try: obj json.loads(guided_resp) assert set(obj.keys()) {问题, 答案} print(解析成功字段完整) except Exception as e: print(解析失败, e)无约束的输出通常会带 markdown 代码块、编号列表、结尾寒暄guided 输出则是纯 JSON。验证时不要只看像不像 JSON要用json.loads真解析一遍再断言字段名和类型。对于guided_choice验证方式是断言返回值落在候选集合内assert resp.strip() in {Positive, Negative, Neutral}对于guided_regex用re.fullmatch验证import re assert re.fullmatch(ip_regex, resp.strip()) is not None如果这些断言在批量请求里全部通过说明约束链路是通的。建议把验证逻辑封装成一个函数在 CI 里跑几十条样本比人工抽查靠谱。5. 本篇常见错排查guided 参数不生效的六个原因第一个参数位置放错。guided_json必须放在extra_body里不能直接作为create()的顶层参数否则 OpenAI SDK 会因为不认识这个字段而报错或静默丢弃。第二个base_url 末尾多了/v1。OpenAI SDK 会自动补/chat/completions如果你写成https://taotoken.net/api/v1最终路径会变成/api/v1/v1/chat/completions直接 404。第三个后端不支持。guided 参数依赖推理引擎的格式化解码能力VLLM 默认走 outlines支持较好。如果你换成了别的后端或者版本太老参数会被忽略输出退回自由生成。排查方法是看服务启动日志里有没有 outlines 相关的加载信息。第四个schema 写得太复杂。嵌套层级过深、oneOf/anyOf大量混用、递归引用都会让解码器的状态机爆炸表现为生成极慢或者直接超时。建议 schema 扁平化嵌套不超过三层。第五个正则写得太宽泛。像.*这种正则在 guided_regex 里等于没约束还会拖慢解码。正则要尽量精确锚定字符集和长度。第六个模型本身指令能力太弱。强约束下小模型容易为了满足格式而牺牲语义输出合法但答非所问。这种情况不是参数问题是模型选型问题换一个指令跟随更好的模型即可。注意guided 参数解决的是格式合法性不解决事实正确性。生产环境里格式校验和内容校验要分成两层别指望一个参数包打天下。6. 继续深入把 guided 输出接进你的业务链路格式约束跑通之后下一步是把它接进真实链路。如果你在做 Agent 或工具调用guided_json可以直接生成函数参数对象省掉一层解析容错代码。如果你在做批量数据标注guided_choice配合并发请求能把打标吞吐拉起来。如果你需要长期跑编码类任务、让模型稳定输出结构化补丁或配置可以了解一下 Coding Plan 的用法地址是 https://taotoken.net/coding-plan 它针对长会话和代码场景做了优化。想直接在线试模型对话、观察 guided 参数在不同模型上的表现差异可以走 https://taotoken.net/chat 。接入文档在 https://taotoken.net/doc 里面有完整的参数说明和示例。API Keys 管理入口还是 https://taotoken.net/api-keys 建议给不同项目建不同的 Key方便按项目排查调用问题。最后留一个实用习惯每次改完 schema 或正则先跑 20 条样本做断言验证再上批量。格式约束的收益在批量场景才明显但问题也往往在批量里才暴露。把验证脚本固化下来比事后翻日志省事得多。
返回列表