news 2026/9/8 22:52:55

LocalAI 受限生成实战:用 BNF 语法(grammar)精确约束 LLM 输出格式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LocalAI 受限生成实战:用 BNF 语法(grammar)精确约束 LLM 输出格式

LocalAI 受限生成实战:用 BNF 语法(grammar)精确约束 LLM 输出格式

【免费下载链接】LocalAILocalAI is the open-source AI engine. Run any model - LLMs, vision, voice, image, video - on any hardware. No GPU required.项目地址: https://gitcode.com/GitHub_Trending/lo/LocalAI

LocalAI 的chat端点支持grammar参数,允许以 Backus-Naur Form(BNF)描述文法来"锁死"大语言模型的输出格式——无论是 JSON、YAML,还是只允许 "yes"/"no" 二选一。读完本文,你将掌握如何在请求中传入自定义 BNF 文法、理解文法在 LocalAI 源码中从 HTTP 请求到 llama.cpp 后端的完整传递链路,并了解response_format、函数调用等功能如何与受限生成协同工作。

一、功能概述:grammar 参数与 BNF 文法

chat端点支持grammar请求字段,其值是一段 BNF 文法。该特性使 LLM 只能生成严格符合用户自定义 schema 的输出,例如JSONYAML或任何其他可以用 BNF 定义的格式。

在请求结构中,该字段的定义位于 OpenAI 兼容 Schema:

// A grammar to constrain the LLM output Grammar string `json:"grammar,omitempty" yaml:"grammar"` JSONFunctionGrammarObject *functions.JSONFunctionStructure `json:"grammar_json_functions,omitempty" yaml:"grammar_json_functions"`

可以看到grammar是一个可选的字符串字段(omitempty),因此不会出现在转发给上游严格 Provider 的请求体中——这与 LocalAI 对 LocalAI 私有字段"零值不泄漏"的设计一致。

兼容性说明:该特性目前仅由使用 llama.cpp 后端的模型支持(受限于底层解码器对文法约束的实现)。完整兼容模型列表可参见 模型兼容性参考页。该能力源自 llama.cpp 项目对 grammar/logits 过滤的支持。

二、文法语法速览:BNF 规则支持的语法元素

对于更复杂的文法,可以定义多行 BNF 规则。语法解析支持以下元素:

语法元素符号说明
选择(Alternation)\|在多个候选中任选其一
重复*+*表示零次或多次,+表示一次或多次
可选?元素可出现零次或一次
字符类[a-z]匹配某一类字符
字符串字面量"text"精确匹配指定文本
规则定义rule ::= ...命名规则,root为入口规则

文法以root规则为起点;请求体中可以通过\n换行符拼接多条规则。

三、实战示例(完整可复制)

示例 1:二值响应约束(yes / no)

以下示例将模型输出严格限制为 "yes" 或 "no",适用于需要严格控制回答格式的场景:

curl http://localhost:8080/v1/chat/completions -H "Content-Type: application/json" -d '{ "model": "gpt-4", "messages": [{"role": "user", "content": "Do you like apples?"}], "grammar": "root ::= (\"yes\" | \"no\")" }'

grammar参数被设置为 "yes"/"no" 的简单选择,无论上下文如何,模型响应都只能是这两个选项之一。

示例 2:强制 JSON 输出

也可以用文法强制 JSON 输出格式:

curl http://localhost:8080/v1/chat/completions -H "Content-Type: application/json" -d '{ "model": "gpt-4", "messages": [{"role": "user", "content": "Generate a person object with name and age"}], "grammar": "root ::= \"{\" \"\\\"name\\\":\" string \",\\\"age\\\":\" number \"}\"\nstring ::= \"\\\"\" [a-z]+ \"\\\"\"\nnumber ::= [0-9]+" }'

注意请求体中\\\"的转义层级:外层是 JSON 字符串的转义,内层是 BNF 字符串字面量中的引号,最终传给文法解析器的是"\"name\":"这样的字面量规则。

示例 3:强制 YAML 输出

同理,可以用文法强制 YAML 格式(此处为 fruits 列表):

curl http://localhost:8080/v1/chat/completions -H "Content-Type: application/json" -d '{ "model": "gpt-4", "messages": [{"role": "user", "content": "Generate a YAML list of fruits"}], "grammar": "root ::= \"fruits:\" newline (\" - \" string newline)+\nstring ::= [a-z]+\nnewline ::= \"\\n\"" }'

三个示例的共同点:root规则定义了输出的"骨架",子规则(stringnumbernewline)负责可复用的片段。文法只约束输出 token 序列,不改变提示词模板的生成过程。

四、源码纵深:grammar 从请求到后端的完整链路

4.1 入口:中间件读取请求字段

用户传入的grammar字段首先由请求中间件写入运行配置,见 请求中间件:

if input.Grammar != "" { config.Grammar = input.Grammar }

4.2 chat 端点:文法的多个来源与优先级

chat 端点 中文法并非只能来自用户手写,实际存在多条生成路径,按请求内容触发:

  1. response_format自动转文法:当请求携带response_format时,端点会将其转换为文法:
    • type: "json_object"→ 直接套用内置的functions.JSONBNF(完整合法 JSON 文法);
    • type: "json_schema"→ 将 schema 包装为JSONFunctionStructure,调用fs.Grammar(...)即时生成 BNF。
  2. 函数调用(Functions/Tools)自动生成文法:当请求需要走函数调用且未禁用文法(NoGrammar为 false)或处于strict模式时,端点会把函数列表转换为 JSON 结构并调用jsStruct.Grammar(...)生成约束文法;非 strict 模式下还会追加一个answer(可通过NoActionFunctionName配置改名)"无动作"函数,允许模型直接回答而不调用工具。
  3. grammar_json_functions字段:请求可直接携带 LocalAI 私有的JSONFunctionGrammarObject(一个 JSON Schema 结构),端点同样调用其Grammar()方法生成 BNF。

上述逻辑执行完毕后,config.Grammar被赋值,并在调试日志中输出(xlog.Debug("Grammar", ...)),便于排查文法是否生效。

4.3 后端投递:透传给 llama.cpp

运行配置中的Grammar最终被打包进发给后端的PredictOptions,见 后端选项组装:

pbOpts := &pb.PredictOptions{ ... Grammar: c.Grammar, ... }

由于 llama.cpp 后端以独立 gRPC worker 进程运行,文法字符串经 backend.proto 定义的PredictOptions协议传递到 C++ 侧,由 llama.cpp 的采样器在逐 token 解码时进行 logits 过滤,从而保证每个输出的 token 都满足文法约束。这也解释了该特性的兼容边界:只有实现了 grammar 解码路径的 llama.cpp 系后端可用,其他后端会忽略该字段。

4.4 JSON Schema → BNF 的内部实现

自动路径中文法生成能力位于 pkg/functions/grammars 目录。其基础构件是 BNF 原始规则表:

PRIMITIVE_RULES = map[string]string{ "boolean": `("true" | "false") space`, "number": `("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space`, "integer": `("-"? ([0-9] | [1-9] [0-9]*)) space`, "string": `"\"" ([^"\\] | "\\" (["\\/bfnrt] | "u" ...))* "\"" space`, "null": `"null" space`, }

这解释了为什么示例 2 中的stringnumber规则与手写版本行为不同:内置转换会生成处理转义、负数、指数形式的严格规则;源码注释中还特别说明,freestring规则若不允许\"\\会产生歧义、实测效果显著变差——这类细节正是手写文法时容易踩坑的地方。

五、受限生成在 LocalAI 其他功能中的体现

grammar并非孤立特性,它还是多个 OpenAI 兼容能力的底层机制:

  • OpenAI Functions / 函数调用:OpenAI Functions 文档 描述的 structured outputs 正是通过上文的"函数列表 → JSON 结构 → BNF 文法"路径实现的,模型输出被约束为合法的函数调用 JSON。
  • Moderation 端点/v1/moderations的实现会先生成对应 moderation 响应 schema 的文法再约束输出(见 moderations 端点 处cfg.Grammar = grammar的赋值),保证返回内容严格符合 moderation schema。
  • Completion 端点/v1/completions同样读取grammar字段,并在response_format为 JSON 时套用JSONBNF(见 completion 端点)。

从源码结构看,文法约束在模板层面还有配合逻辑:模板求值器 中config.Grammar != ""会参与函数调用模板的分支判断,即"是否携带文法"会影响最终 prompt 模板的形态。

六、使用限制与注意事项

  • 后端限制:仅 llama.cpp 后端模型支持grammar约束,使用前请确认模型的 backend 字段;
  • 文法正确性自负:手写 BNF 若有歧义或无法被解析,服务端会记录Failed generating grammar错误日志并可能退化为无约束输出,建议先用简单文法验证再复杂化;
  • response_formatfunctions的交互:三者都可能最终落到config.Grammar上,若同时传入grammar与函数定义,按 chat 端点的分支逻辑,函数生成的文法可能覆盖此前由response_format设置的值,使用时应避免混用;
  • 转义层级:curl 请求体中\n\"需要按"JSON 字符串 → BNF 字面量"两层转义书写,是实际调试中最常见的报错来源。

相关功能

  • OpenAI Functions — 函数调用与结构化输出
  • 文本生成 — 通用文本生成能力
  • Moderation — OpenAI 兼容的安全分类,其响应被约束到 moderation schema
  • 模型兼容性参考 — 各后端能力对照表

【免费下载链接】LocalAILocalAI is the open-source AI engine. Run any model - LLMs, vision, voice, image, video - on any hardware. No GPU required.项目地址: https://gitcode.com/GitHub_Trending/lo/LocalAI

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 22:50:30

BMC固件工程师:服务器健康系统的底层调度者

1. BMC固件工程师不是“写BIOS的”,而是服务器健康系统的总调度员很多人第一次听说BMC(Baseboard Management Controller),下意识会把它和主板BIOS划等号——毕竟都跑在板子上、都带“固件”俩字、都能进底层。但这种类比就像把消…

作者头像 李华
网站建设 2026/9/8 22:48:53

OpenClaw 2.0 实战:模块化配置与AI Agent部署指南

1. 从“全家桶”到“够用就好”:OpenClaw 2.0 到底减掉了什么 我接触 OpenClaw 的时间不算短,从早期版本一路跟过来,最大的感受就是:这个平台以前太贪心了。什么功能都想塞进去,什么接口都想兼容,结果就是安…

作者头像 李华
网站建设 2026/9/8 22:46:56

计算机毕业设计之jsp图书座位预约系统

“互联网”的战略实施后,很多行业的信息化水平都有了很大的提升。但是目前很多图书馆日常业务仍是通过人工管理的方式进行,需要在图书座位预约投入大量的人力进行很多重复性工作,这样就浪费了许多的人力物力,工作效率较低&#xf…

作者头像 李华