ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

mistral.rs 中的 llguidance 受限生成:用 Lark + JSON Schema 文法强制「推理 + JSON」结构输出

2026/9/16 10:25:20 拓冰建站 浏览量
mistral.rs 中的 llguidance 受限生成:用 Lark + JSON Schema 文法强制「推理 + JSON」结构输出 mistral.rs 中的 llguidance 受限生成用 Lark JSON Schema 文法强制「推理 JSON」结构输出【免费下载链接】mistral.rsFast, flexible LLM inference项目地址: https://gitcode.com/GitHub_Trending/mi/mistral.rs本文围绕 mistral.rs 的官方示例文档 llguidance 展开完整讲解如何用 llguidance 文法对 LLM 生成过程施加约束以一段 Lark 文法包裹推理文本、再以myobj锚点引用一个 JSON Schema 子文法使模型输出必然形如Reasoning: …\nJSON: {answer: Yes|No}。读完本文你将掌握该示例的完整代码与运行方式并理解文法是如何经由Constraint枚举、ParserFactory与SequenceRecognizer在采样阶段逐 token 生效的底层链路。什么是 llguidance 受限生成受限生成constrained decoding / structured output的核心思想是在每一步采样时只允许符合某个文法的 token 通过 logits 屏蔽masking从而保证输出在形式上严格合法。mistral.rs 将该能力建立在 llguidance 之上——工作区根Cargo.toml固定使用llguidance 1.2.0default-features false启用lark特性并搭配toktrie_hf_tokenizers 1.2.0与toktrie 1.4.0两个依赖把 Hugging Face tokenizer 的字节级词表编译成 token 字节前缀树TokTrie供文法匹配器在字节层面精确对齐 token 边界。从 Constraint 枚举 可以看到 mistral.rs 支持的全部受限生成入口pub type LlguidanceGrammar llguidance::api::TopLevelGrammar; /// Control the constraint with llguidance. pub enum Constraint { Regex(String), Lark(String), JsonSchema(serde_json::Value), Llguidance(LlguidanceGrammar), None, }Regex/Lark/JsonSchema单一文法的便捷入口内部会各自转换成一个TopLevelGrammarLlguidance完整入口直接传入TopLevelGrammar即本文示例使用的形式允许携带多个文法片段并通过名字相互引用None不施加约束。LlguidanceGrammar是llguidance::api::TopLevelGrammar的类型别名其结构为grammars: VecGrammarWithLexer加一个可选的max_tokens这正是下面示例中vec![top, schema]的来源。示例代码逐段解析示例源码位于 mistralrs/examples/advanced/llguidance/main.rs在 mistralrs/Cargo.toml 中注册为name llguidance的 example运行命令为cargo run --release --example llguidance -p mistralrs完整代码如下use anyhow::Result; use mistralrs::{ llguidance::api::GrammarWithLexer, IsqBits, LlguidanceGrammar, ModelBuilder, PagedAttentionMetaBuilder, RequestBuilder, TextMessageRole, }; use serde_json::json; #[tokio::main] async fn main() - Result() { let model ModelBuilder::new(google/gemma-4-E4B-it) .with_auto_isq(IsqBits::Four) .with_logging() .with_paged_attn(PagedAttentionMetaBuilder::default().build()?) .build() .await?; let top GrammarWithLexer::from_lark(r#start: Reasoning: /./ \nJSON: myobj#.to_string()); let schema GrammarWithLexer { name: Some(myobj.to_string()), json_schema: Some(json!({ type: object, properties: { answer: {type: string, enum: [Yes, No]}, }, required: [answer], additionalProperties: false, })), ..Default::default() }; let request RequestBuilder::new() .set_constraint(mistralrs::Constraint::Llguidance(LlguidanceGrammar { grammars: vec![top, schema], max_tokens: None, })) .set_sampler_max_len(100) .add_message( TextMessageRole::User, If all dogs are mammals, and all mammals are animals, are dogs animals?, ); let response model.send_chat_request(request).await?; println!({}, response.choices[0].message.content.as_ref().unwrap()); Ok(()) }第一步加载模型ModelBuilder::new(google/gemma-4-E4B-it)指定模型此处为 Google 的 gemma-4-E4B-it后续链式方法配置推理环境.with_auto_isq(IsqBits::Four)启用自动 ISQIn-Situ Quantization在线 4-bit 量化无需预先量化权重即可降低显存占用.with_logging()开启运行日志.with_paged_attn(PagedAttentionMetaBuilder::default().build()?)启用 PagedAttention 分页注意力以优化 KV cache 内存管理.build().await?异步完成模型加载。模型加载本身与约束生成无关任何支持send_chat_request的文本模型都可以替换。第二步构造「Lark 外壳 JSON Schema 内核」双文法这是示例的核心也是 llguidanceTopLevelGrammar的典型用法——用两个GrammarWithLexer组合出一条完整的生成路径顶层 Lark 文法topGrammarWithLexer::from_lark(r#start: Reasoning: /./ \nJSON: myobj#.to_string());这段 Lark 文法规定了输出的骨架先输出字面量Reasoning:然后是任意非空文本/./推理过程换行后输出JSON:最后以myobj终结。myobj是 llguidance 的文法引用语法它指向grammars向量中name myobj的另一个文法片段解析时会被内联展开。JSON Schema 子文法schemaGrammarWithLexer { name: Some(myobj.to_string()), json_schema: Some(json!({ type: object, properties: { answer: {type: string, enum: [Yes, No]}, }, required: [answer], additionalProperties: false, })), ..Default::default() }该片段只声明name与json_schema两个字段其余字段如lark_grammar、regex、ebnf_grammar走Default把一份 JSON Schema 编译成受限生成器answer字段必填、取值只能是Yes或No且禁止额外属性。两段文法合起来保证了输出必然形如Reasoning: 自由推理文本 JSON: {answer: Yes}其中自由文本部分不受约束模型可自由发挥推理JSON 部分则被逐 token 强制合法。第三步组装请求并发送let request RequestBuilder::new() .set_constraint(mistralrs::Constraint::Llguidance(LlguidanceGrammar { grammars: vec![top, schema], max_tokens: None, })) .set_sampler_max_len(100) .add_message( TextMessageRole::User, If all dogs are mammals, and all mammals are animals, are dogs animals?, ); let response model.send_chat_request(request).await?;set_constraint(Constraint::Llguidance(...))把双文法挂到请求上max_tokens: None表示文法侧不额外限制 token 数set_sampler_max_len(100)采样器层面的最大生成长度 100 token作为硬性截断上限add_message(TextMessageRole::User, ...)追加一条用户消息一段三段论推理题send_chat_request返回Response示例直接打印choices[0].message.content。底层实现文法如何变成逐 token 的 Matcher理解示例的行为需要看 mistral.rs 内部的三条链路。词表 → 字节前缀树build_llg_factorypipeline/llg.rs 中的build_llg_factory在模型初始化时为每个 tokenizer 构建一个全局共享的ParserFactory先把 tokenizer 的 decoder 规整为单个ByteLeveldecoder保证文法与字节流对齐收集所有 special tokenat.special为真的 added tokens通过toktrie_hf_tokenizers::ByteTokenizer::from_tokenizer(tokenizer)得到 token 字节表token_bytes并对可能被漏标的 special token 手动补上SPECIAL_TOKEN_MARKER前缀源码注释说明这是针对 Tekken 类 tokenizer 的修复否则toktrie构建器可能不给特殊 token 打标记;最终TokTrie::from(info, token_bytes)ParserFactory::new_simple(env)产出ArcParserFactory缓存在 pipeline metadata 中metadata.llg_factory。文法 → Matcherllg.rs 的后两个函数 完成约束到匹配器的转换pub fn llg_grammar_from_constraint(constraint: Constraint) - ResultOptionTopLevelGrammar { let grm match constraint { Constraint::Regex(regex) TopLevelGrammar::from_regex(regex), Constraint::Lark(lark) TopLevelGrammar::from_lark(lark.clone()), Constraint::JsonSchema(value) TopLevelGrammar::from_json_schema(value.clone()), Constraint::Llguidance(value) value.clone(), Constraint::None return Ok(None), }; Ok(Some(grm)) } pub fn constraint_from_llg_grammar( factory: ParserFactory, grm: TopLevelGrammar, ) - Resultllguidance::Matcher { let parser factory.create_parser(grm)?; Ok(llguidance::Matcher::new(Ok(parser))) }也就是说示例中手写的Constraint::Llguidance(LlguidanceGrammar { grammars: vec![top, schema], .. })被原样克隆为TopLevelGrammarConstraint::Llguidance分支不做转换再由ParserFactory编译成解析器包进llguidance::Matcher。Matcher是有限状态机每消费一个 token 就步进一次采样阶段用它查询当前状态下哪些 token 合法从而在 logits 上屏蔽非法 token。每步采样中的挂载点sequence.rs 中的 SequenceRecognizer 表明每条推理序列携带的识别器只有两种形态pub enum SequenceRecognizer { Llguidance(Boxllguidance::Matcher), None, }在 pipeline/sampling.rs 中请求带着约束进入解码循环后会执行crate::pipeline::llg::constraint_from_llg_grammar(factory, grm)并赋值seq.recognizer SequenceRecognizer::Llguidance(Box::new(matcher))此后每一步采样都会先经过该 Matcher 过滤候选 token。同一套机制还服务于流式工具调用当检测到模型开始输出工具调用片段时tool_call_state会动态激活一个续文法continuation grammar——若构建失败或 llguidance 不可用源码会以tracing::warn!(Cannot force required tool call: llguidance is unavailable)降级为无约束继续。同一模式在仓库中的规模化应用示例里一个 Lark 外壳文法 引用的 JSON Schema 子文法的组合方式正是 mistral.rs 内置工具调用解析器共用的骨架。tools/grammar.rs 的build_json_format_grammar与示例结构一一对应pub(crate) fn build_json_format_grammar( lark: String, tools: [Tool], args_key: str, is_array: bool, ) - TopLevelGrammar { let top GrammarWithLexer::from_lark(lark); let schema json_body_schema(tools, args_key, is_array); let json_body GrammarWithLexer { name: Some(json_body.to_string()), json_schema: Some(schema), ..Default::default() }; TopLevelGrammar { grammars: vec![top, json_body], max_tokens: None, } }区别仅在于子文法名叫json_body、schema 由工具列表动态生成。该文件末尾的单元测试qwen_grammar_has_two_grammars、llama_uses_parameters_key、mistral_nemo_is_array等见 grammar.rs 测试模块验证了各模型格式Qwen、Llama、Mistral Nemo、Hunyuan、DeepSeek、Gemma4、Harmony、Liquid、Atem下外壳文法与子文法的组合形态可以直接作为阅读时确认文法结构的参照。相关资源与运行提示Rust 示例源码mistralrs/examples/advanced/llguidance/main.rs对应文档页由 docs/scripts/render_examples.py 从示例源码自动生成文档中亦有此说明修改行为应改示例源码而非文档Python 侧同款示例examples/python/llguidance.py 与 server 侧 examples/server/llguidance.py可在 Python API 与服务端场景复用同一套文法思想库再导出路径mistralrs门面 crate 通过 pub use mistralrs_core::llguidance 再导出llguidance因此示例中use mistralrs::llguidance::api::GrammarWithLexer可用更简单的入口如果只需要单段文法可直接用Constraint::Regex(....to_string())、Constraint::Lark(start: ....to_string())或Constraint::JsonSchema(json!(...))无需手工组装TopLevelGrammar需要多片段互相引用如本文的myobj时才必须使用Constraint::Llguidance。小结mistral.rs 的 llguidance 受限生成把结构化输出下沉到了采样层用户只需提供文法Lark 外壳 命名的 JSON Schema 子文法运行时经由Constraint::Llguidance→TopLevelGrammar→ParserFactory.create_parser→llguidance::Matcher的链路在每一步解码时屏蔽非法 token。本文示例展示了该链路的手工完整用法同一模式也支撑着仓库内所有内置工具调用格式的受限解析是理解 mistral.rs 结构化输出体系的切入点。【免费下载链接】mistral.rsFast, flexible LLM inference项目地址: https://gitcode.com/GitHub_Trending/mi/mistral.rs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考