Loading...

文章背景图

Guidance:大模型结构化提示生成引擎指南

2026-05-22
0
- 字
- 分钟
|

Guidance:大模型结构化提示生成引擎指南

guidance-ai/guidance 是微软研究院发起的开源项目,实时 Star 数约 21.8k,是一套「引导大模型输出」的编程范式。本文基于官方 README 完整翻译整理,从项目定位、安装方式、Pythonic 接口、约束生成、自定义函数、JSON 生成到离线调试逐一说明。

Guidance 的核心思想是:用 Python 代码直接控制大模型输出的结构。它允许你约束生成(正则、CFG 上下文无关文法),也允许把控制流(条件、循环、工具调用)和文本生成无缝交织在一起。相比传统的提示工程或微调,Guidance 在保证输出质量的同时,还能降低延迟和成本——因为很多 token 在语法上是确定的,不需要模型再算一遍。

对做应用的开发者来说,Guidance 解决的是一个非常实际的痛点:你让大模型输出 JSON,它经常给你多一个逗号、少一个引号、或者夹带几句解释;你让它输出 A/B/C/D,它经常给你输出「我觉得答案是 C」。传统做法是写一大堆后处理正则去修,修不好就重试。Guidance 的做法是从生成源头就把这些非法路径堵死,模型只能在你规定的语法空间里选 token,既省了后处理的麻烦,也省了重试浪费的 token。

这个项目最初由微软研究院发起,后来成立了独立的 guidance-ai 组织维护。它和同样来自微软的 Semantic Kernel、以及学术界的 Outlines、Textual Inversion 等项目一起,构成了「结构化大模型输出」这条技术路线的主要工具链。对于想把大模型真正用在生产环境、而不是只在 Demo 里聊天的团队来说,这一类工具是必学的。

一、项目官方信息

项目 信息
GitHub 仓库 guidance-ai/guidance
联系邮箱 guidanceai@microsoft.com
社区 Discord
Star 数 约 21.8k
开源协议 MIT
主要语言 Jupyter Notebook / Python
支持后端 Transformers、llama.cpp、OpenAI 等多种

二、官方图片

封面取 GitHub 仓库官方图,本文不重复贴图。项目 Logo 见 docs/figures/guidance_logo_blue.svg。

三、环境要求

Guidance 通过 PyPI 分发,支持多种后端。只要你已经装好所需模型的后端(比如 Transformers 后端需要 PyTorch,llama.cpp 后端需要本地 GGUF 权重),就可以直接安装 Guidance 本体。Python 版本要求 3.9+,建议配合 Jupyter Notebook 使用,因为 Guidance 提供了交互式 widget,能在浏览器里实时高亮生成过程。

硬件方面,如果你用 Transformers 后端跑 7B 级模型,建议 16GB 显存;跑 70B 建议 80GB 或多卡。如果你用 llama.cpp 后端跑 GGUF 量化模型,8GB 显存甚至 CPU 也能跑小模型。如果你只用 OpenAI 后端,则本地不需要 GPU,只需要网络能访问 OpenAI API。

四、完整部署步骤

1. 安装 Guidance

pip install guidance

2. 安装后端(按需)

  • Transformers 后端:pip install transformers torch,然后用 Transformers("microsoft/Phi-4-mini-instruct") 加载。
  • llama.cpp 后端:下载 GGUF 权重,用 LlamaCpp("path/to/model.gguf") 加载。
  • OpenAI 后端:设置 OPENAI_API_KEY 环境变量,用 OpenAI("gpt-4o") 加载。

Guidance 的设计是「后端无关」的:同一份 Prompt 模板和约束文法,换一个后端就能跑不同模型,不需要改业务代码。这种设计在做模型对比实验时特别方便——你可以用同一套 Prompt 模板分别跑 Phi、Llama、Qwen,看哪个模型在你的任务上表现最好,而不用为每个模型重写一遍 Prompt。

安装完成后,建议先跑一个最简单的 Hello World 例子验证环境是否正常,比如用 Transformers("microsoft/Phi-4-mini-instruct") 加载一个小模型,跑一次 gen(max_tokens=20),能正常出结果就说明 Guidance 和后端都装好了。第一次运行会自动从 Hugging Face 下载 Phi-4-mini 权重,国内用户建议先配好 Hugging Face 镜像端点再跑,否则第一次下载权重可能会卡住,影响调试心情。

五、使用方法

1. Pythonic 接口:像写普通 Python 一样写 Prompt

Guidance 用 with system()、with user()、with assistant() 上下文管理器来组织对话角色,用 gen() 标记模型生成的位置:

from guidance import system, user, assistant, gen
from guidance.models import Transformers

# 也可以用 LlamaCpp 或其他后端
phi_lm = Transformers("microsoft/Phi-4-mini-instruct")

# Model 对象是不可变的,这是一个副本
lm = phi_lm

with system():
    lm += "You are a helpful assistant"

with user():
    lm += "Hello. What is your name?"

with assistant():
    lm += gen(max_tokens=20)

print(lm)

命令行运行会输出拼接好的完整对话;在 Jupyter Notebook 里,Guidance 会渲染一个交互式 widget,高亮显示哪部分是模型生成的、哪部分是你写的。

这种写法和写普通 Python 几乎一样:用 with 上下文管理角色,用 += 拼接文本,用 gen() 标记模型生成的位置。你不需要学一套新的 DSL,也不需要写复杂的提示模板字符串,Python 的缩进、条件、循环都可以直接用。这也是 Guidance 相比其他提示工程工具最大的差异点——它把 Prompt 变成了代码,而不是把代码变成了 Prompt。

捕获生成的文本也很简单,给 gen() 起个名字就行:

lm = phi_lm

with system():
    lm += "You are a helpful assistant"

with user():
    lm += "Hello. What is your name?"

with assistant():
    lm += gen(name="lm_response", max_tokens=20)

print(f"{lm['lm_response']=}")

2. 约束生成:保证输出语法

Guidance 最强大的能力是约束输出。比如你要求模型只输出一个数字:

lm = phi_lm

with system():
    lm += "You are a teenager"

with user():
    lm += "How old are you?"

with assistant():
    lm += gen("lm_age", regex=r"\d+", temperature=0.8)

print(f"The language model is {lm['lm_age']} years old")

模型被强制只能输出数字,不管 temperature 多高都不会冒出别的字符。

如果输出必须是预设列表里的某一项,用 select():

from guidance import select

lm = phi_lm

with system():
    lm += "You are a geography expert"

with user():
    lm += """What is the capital of Sweden? Answer with the correct letter.

    A) Helsinki
    B) Reykjavík
    C) Stockholm
    D) Oslo
    """

with assistant():
    lm += select(["A", "B", "C", "D"], name="model_selection")

print(f"The model selected {lm['model_selection']}")

这比让模型自由生成再自己做正则校验高效得多——模型从一开始就被限制在合法选项里,不会浪费 token。

Guidance 的约束系统底层是上下文无关文法(CFG),理论上可以约束任何能用 CFG 描述的输出格式。只要后端模型完整支持 Guidance 的约束机制(即在推理时能动态修改 logits mask),你就能写出任意复杂的文法约束,比如「只允许输出符合国家邮政编码格式的字符串」「只允许输出符合 SQL SELECT 语法的查询」「只允许输出符合特定 XML 标签嵌套规则的文档」。这种能力在做结构化输出、做工具调用、做代码生成时特别有用。

3. 离线调试文法(不调模型 API)

迭代约束规则时,可以用 Mock 模型在本地验证文法是否正确,不需要真的调用大模型:

from guidance import gen
from guidance.models import Mock

grammar = "expr=" + gen(regex=r"\d+([+*]\d+)*", name="expr")

# 1) 直接校验字符串是否符合文法
assert grammar.match("expr=12+7*3") is not None
assert grammar.match("expr=12+*3") is None

# 2) 用本地 Mock 模型跑一遍完整流程
lm = Mock(b"<s>expr=12+7*3")
lm += grammar
print(lm["expr"])  # 12+7*3

这个能力对做工程化特别重要:你可以在 CI 里跑文法测试,不需要花 GPU 钱。

传统做 Prompt 开发的流程是:写一个 Prompt → 调模型跑一遍 → 看输出对不对 → 改 Prompt → 再跑一遍。这个循环每一步都要花钱、花时间。Guidance 让你可以把「文法设计」和「模型调用」解耦:先用 Mock 模型在本地把文法调试到正确,确认文法本身没问题,再接真实模型跑端到端测试。这样能把调试成本降一个数量级。

4. 自定义 Guidance 函数

用 @guidance 装饰器可以把复杂 Prompt 封装成可复用函数,比如封装一个零样本选择题:

import guidance

from guidance.models import Model

ASCII_OFFSET = ord("a")

@guidance
def zero_shot_multiple_choice(
    language_model: Model,
    question: str,
    choices: list[str],
):
    with user():
        language_model += question + "\n"
        for i, choice in enumerate(choices):
            language_model += f"{chr(i+ASCII_OFFSET)} : {choice}\n"

    with assistant():
        language_model += select(
            [chr(i + ASCII_OFFSET) for i in range(len(choices))], name="string_choice"
        )

    return language_model

然后像 gen() 一样调用它,language_model 参数会自动传入。这种封装方式的好处是:你可以把复杂的业务 Prompt 模板(比如「先给一段背景说明,再列选项,最后让模型选一个字母」)封装成一个函数,以后在任何地方复用,不用每次都复制粘贴一大段 Prompt 字符串。Guidance 函数可以层层嵌套、组合,最终拼成一棵完整的 Prompt 文法树。

lm = phi_lm

with system():
    lm += "You are a student taking a multiple choice test."

for mcq in questions:
    lm_temp = lm + zero_shot_multiple_choice(question=mcq["question"], choices=mcq["choices"])
    converted_answer = ord(lm_temp["string_choice"]) - ASCII_OFFSET
    print(lm_temp)
    print(f"LM Answer: {converted_answer},  Correct Answer: {mcq['answer']}")

5. 组合文法:生成合法 HTML

多个 Guidance 函数可以组合成完整的上下文无关文法。官方示例演示了如何用这种方式让模型生成结构合法的 HTML 页面(标签闭合、嵌套正确):

  • 先用 @guidance(stateless=True) 定义无状态的原子函数,比如 _gen_text 生成不含标签的文本、_gen_text_in_tag 生成被某个标签包裹的文本。
  • 再逐层组合:_gen_header 生成 head、_gen_heading 生成 h1/h2/h3、_gen_para 生成 p 标签、_gen_body 生成 body、_gen_html 生成完整页面。
  • 最后用 make_html 包装,加 capture 捕获结果,加 with_temperature 控制采样温度。

这种方式生成的 HTML 标签一定闭合、结构一定合法,因为文法层面就禁止了非法输出。

这个例子虽然只是演示用的简化版 HTML 文法(不是完整的 HTML 规范实现),但它展示了 Guidance 组合文法的思路:从最小的原子函数开始,一层层往上拼,每层只负责自己那一小块语法规则,最后拼成一个完整的语言模型输出文法。这种思路和写编程语言的编译器是一样的——词法分析、语法分析、语义分析,每一层都有明确的边界。

6. 快速前递(Token Fast-Forwarding)

Guidance 的另一个核心优化是快速前递:当文法约束意味着下一个 token 是确定的(比如刚生成了 <h1>,后面必然跟 </h1>),Guidance 不需要模型再做一次前向计算,直接把这个 token 插进去。这在生成 JSON、HTML 这类结构化内容时能省掉大量 GPU 调用,显著降低延迟和成本。

举个具体例子:生成一个 100 个 token 的 JSON 字符串,其中可能有 30 个 token 是确定的(比如引号、冒号、逗号、括号),Guidance 可以直接跳过这 30 次模型前向计算,相当于把延迟降低了 30%。这也是为什么官方 README 说 Guidance 「相比传统提示工程或微调,能在降低延迟和成本的同时保持输出质量」——不是靠模型变小,而是靠少算不必要的 token。

7. 生成 JSON

JSON Schema 本质上就是一种上下文无关文法,Guidance 对它做了专门支持。基于 Pydantic 模型一行代码就能生成合法 JSON:

import json
from pydantic import BaseModel, Field

from guidance import json as gen_json

class BloodPressure(BaseModel):
    systolic: int = Field(gt=300, le=400)
    diastolic: int = Field(gt=0, le=20)
    location: str = Field(max_length=50)
    model_config = dict(extra="forbid")

lm = phi_lm

with system():
    lm += "You are a doctor taking a patient's blood pressure taken from their arm"

with user():
    lm += "Report the blood pressure"

with assistant():
    lm += gen_json(name="bp", schema=BloodPressure)

print(f"{lm['bp']=}")

生成的 JSON 一定符合 Pydantic 模型的约束(字段类型、取值范围、禁止额外字段),可以直接用 BloodPressure.model_validate_json() 反序列化。

这比传统的「让模型输出 JSON,然后 try-except json.loads,失败就重试」方案优雅得多。传统方案里,模型可能输出半截 JSON、多余文字、或者不符合 schema 的字段,你需要写一大堆容错逻辑;Guidance 从生成时就把 schema 翻译成文法约束,模型只能输出合法 JSON,一次成功,不需要重试。

六、FAQ

  • Guidance 和 Outlines、Function Calling 有什么区别? Guidance 是微软出的,主打「Python 代码即 Prompt 模板」,把控制流和生成交织在一起;Outlines 是另一个类似项目;Function Calling 是 OpenAI 提供的专有接口。Guidance 的优势是后端无关、本地可跑、文法表达力强。
  • 为什么要用约束生成而不是后处理校验? 后处理校验意味着模型可能生成非法输出,你需要重试或兜底;约束生成从源头就不让模型输出非法内容,既省 token 又省重试成本。
  • 支持哪些模型后端? Transformers、llama.cpp、OpenAI 是官方 README 明确提到的;理论上任何能提供 logits 或补全接口的后端都可以适配。
  • Jupyter widget 怎么用? 在 Notebook 里正常运行代码即可,Guidance 会自动检测环境并渲染交互式 widget,高亮显示生成过程。
  • 遇到问题去哪问? Discord 社区 或邮件 guidanceai@microsoft.com。
  • 和传统提示工程比,学习成本高吗? 不高。如果你已经会写 Python、会用上下文管理器,Guidance 的 API 基本一看就懂;新学的概念主要是「约束文法」和「快速前递」这两个,但理解了之后收益很大。
  • 生产环境能用吗? 可以,但要注意后端是否完整支持 Guidance 的约束机制(即能否在推理时动态修改 logits)。OpenAI 等闭源 API 因为不开放 logits 修改,约束能力会受限;本地开源模型通过 Transformers 或 llama.cpp 后端则可以完整支持。
  • 适合做什么场景? 结构化输出(JSON、XML、SQL)、工具调用、代码生成、选择题/分类任务、任何需要严格控制输出格式的场景。
  • 和微调比有什么优势? 微调是改模型权重,成本高、周期长;Guidance 是在推理时加约束,不改权重、即插即用。对于「只要输出格式正确」的需求,Guidance 比微调轻得多。
  • 能不能和 Unsloth、Transformers 一起用? 可以。Guidance 是推理时的约束层,不影响微调;Unsloth 微调完的模型,可以直接用 Guidance 加载做推理约束。

七、跳转链接

本文所有命令与版本号均与官方 README 一致,不做任何臆造。Guidance 是微软系大模型工程化工具链的重要一环,适合做结构化输出、工具调用和代码生成场景,建议在本地小模型上先跑通示例再迁移到生产环境,能省掉大量后处理和重试成本,是生产级 LLM 应用的必备工具之一,值得每个大模型应用开发者认真学习和使用。

评论交流

文章目录