Guidance约束生成框架Skill guidance

Guidance 是 Microsoft Research 开发的框架,用于约束大语言模型的输出生成。它支持通过正则表达式和上下文无关语法控制输出格式,确保生成的内容如 JSON、XML 等符合指定结构,并可用于构建复杂的多步骤工作流程。关键词:Guidance, LLM, 约束生成, JSON 生成, 正则表达式, 语法, 微软研究, AIGC

AIGC 0 次安装 0 次浏览 更新于 3/21/2026

名称: guidance 描述: 控制 LLM 输出与正则表达式和语法,保证有效的 JSON/XML/代码生成,强制执行结构化格式,并使用 Guidance - Microsoft Research 的约束生成框架构建多步骤工作流程 版本: 1.0.0 作者: Orchestra Research 许可证: MIT 标签: [提示工程, Guidance, 约束生成, 结构化输出, JSON 验证, 语法, 微软研究, 格式强制执行, 多步骤工作流程] 依赖项: [guidance, transformers]

Guidance: 约束 LLM 生成

何时使用此技能

使用 Guidance 当您需要:

  • 控制 LLM 输出语法 通过正则表达式或语法
  • 保证有效的 JSON/XML/代码 生成
  • 降低延迟 相比传统提示方法
  • 强制执行结构化格式(日期、电子邮件、ID 等)
  • 构建多步骤工作流程 使用 Pythonic 控制流
  • 防止无效输出 通过语法约束

GitHub Stars: 18,000+ | 来源: Microsoft Research

安装

# 基础安装
pip install guidance

# 使用特定后端
pip install guidance[transformers]  # Hugging Face 模型
pip install guidance[llama_cpp]     # llama.cpp 模型

快速开始

基础示例:结构化生成

from guidance import models, gen

# 加载模型(支持 OpenAI、Transformers、llama.cpp)
lm = models.OpenAI("gpt-4")

# 使用约束生成
result = lm + "法国的首都是 " + gen("capital", max_tokens=5)

print(result["capital"])  # "Paris"

使用 Anthropic Claude

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

# 配置 Claude
lm = models.Anthropic("claude-sonnet-4-5-20250929")

# 使用上下文管理器进行聊天格式
with system():
    lm += "你是一个有用的助手。"

with user():
    lm += "法国的首都是什么?"

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

核心概念

1. 上下文管理器

Guidance 使用 Pythonic 上下文管理器进行聊天式交互。

from guidance import system, user, assistant, gen

lm = models.Anthropic("claude-sonnet-4-5-20250929")

# 系统消息
with system():
    lm += "你是 JSON 生成专家。"

# 用户消息
with user():
    lm += "生成一个包含姓名和年龄的人员对象。"

# 助手响应
with assistant():
    lm += gen("response", max_tokens=100)

print(lm["response"])

好处:

  • 自然聊天流程
  • 清晰的角色分离
  • 易于阅读和维护

2. 约束生成

Guidance 确保输出匹配指定的模式,使用正则表达式或语法。

正则表达式约束

from guidance import models, gen

lm = models.Anthropic("claude-sonnet-4-5-20250929")

# 约束为有效电子邮件格式
lm += "电子邮件:" + gen("email", regex=r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")

# 约束为日期格式 (YYYY-MM-DD)
lm += "日期:" + gen("date", regex=r"\d{4}-\d{2}-\d{2}")

# 约束为电话号码
lm += "电话:" + gen("phone", regex=r"\d{3}-\d{3}-\d{4}")

print(lm["email"])  # 保证有效电子邮件
print(lm["date"])   # 保证 YYYY-MM-DD 格式

工作原理:

  • 正则表达式在令牌级别转换为语法
  • 无效令牌在生成过程中过滤
  • 模型只能生成匹配的输出

选择约束

from guidance import models, gen, select

lm = models.Anthropic("claude-sonnet-4-5-20250929")

# 约束为特定选择
lm += "情感:" + select(["积极", "消极", "中性"], name="sentiment")

# 多项选择选择
lm += "最佳答案:" + select(
    ["A) 巴黎", "B) 伦敦", "C) 柏林", "D) 马德里"],
    name="answer"
)

print(lm["sentiment"])  # 其中之一:积极、消极、中性
print(lm["answer"])     # 其中之一:A、B、C 或 D

3. 令牌修复

Guidance 自动在提示和生成之间“修复”令牌边界。

问题: 令牌化创建不自然的边界。

# 没有令牌修复
prompt = "法国的首都是 "
# 最后一个令牌:" 是 "
# 第一个生成的令牌可能是 " 巴黎"(带前导空格)
# 结果:"法国的首都是  巴黎"(双空格!)

解决方案: Guidance 回退一个令牌并重新生成。

from guidance import models, gen

lm = models.Anthropic("claude-sonnet-4-5-20250929")

# 令牌修复默认启用
lm += "法国的首都是 " + gen("capital", max_tokens=5)
# 结果:"法国的首都是巴黎"(正确间距)

好处:

  • 自然文本边界
  • 没有尴尬间距问题
  • 更好的模型性能(看到自然令牌序列)

4. 基于语法的生成

使用上下文无关语法定义复杂结构。

from guidance import models, gen

lm = models.Anthropic("claude-sonnet-4-5-20250929")

# JSON 语法(简化)
json_grammar = """
{
    "name": <gen name regex="[A-Za-z ]+" max_tokens=20>,
    "age": <gen age regex="[0-9]+" max_tokens=3>,
    "email": <gen email regex="[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}" max_tokens=50>
}
"""

# 生成有效 JSON
lm += gen("person", grammar=json_grammar)

print(lm["person"])  # 保证有效 JSON 结构

使用场景:

  • 复杂结构化输出
  • 嵌套数据结构
  • 编程语言语法
  • 领域特定语言

5. Guidance 函数

使用 @guidance 装饰器创建可重用的生成模式。

from guidance import guidance, gen, models

@guidance
def generate_person(lm):
    """生成一个包含姓名和年龄的人员。"""
    lm += "姓名:" + gen("name", max_tokens=20, stop="
")
    lm += "
年龄:" + gen("age", regex=r"[0-9]+", max_tokens=3)
    return lm

# 使用函数
lm = models.Anthropic("claude-sonnet-4-5-20250929")
lm = generate_person(lm)

print(lm["name"])
print(lm["age"])

有状态函数:

@guidance(stateless=False)
def react_agent(lm, question, tools, max_rounds=5):
    """使用工具的反应代理。"""
    lm += f"问题:{question}

"

    for i in range(max_rounds):
        # 思考
        lm += f"思考 {i+1}:" + gen("thought", stop="
")

        # 动作
        lm += "
动作:" + select(list(tools.keys()), name="action")

        # 执行工具
        tool_result = tools[lm["action"]]()
        lm += f"
观察:{tool_result}

"

        # 检查是否完成
        lm += "完成?" + select(["是", "否"], name="done")
        if lm["done"] == "是":
            break

    # 最终答案
    lm += "
最终答案:" + gen("answer", max_tokens=100)
    return lm

后端配置

Anthropic Claude

from guidance import models

lm = models.Anthropic(
    model="claude-sonnet-4-5-20250929",
    api_key="your-api-key"  # 或设置 ANTHROPIC_API_KEY 环境变量
)

OpenAI

lm = models.OpenAI(
    model="gpt-4o-mini",
    api_key="your-api-key"  # 或设置 OPENAI_API_KEY 环境变量
)

本地模型 (Transformers)

from guidance.models import Transformers

lm = Transformers(
    "microsoft/Phi-4-mini-instruct",
    device="cuda"  # 或 "cpu"
)

本地模型 (llama.cpp)

from guidance.models import LlamaCpp

lm = LlamaCpp(
    model_path="/path/to/model.gguf",
    n_ctx=4096,
    n_gpu_layers=35
)

常见模式

模式 1: JSON 生成

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

lm = models.Anthropic("claude-sonnet-4-5-20250929")

with system():
    lm += "你生成有效的 JSON。"

with user():
    lm += "生成一个包含姓名、年龄和电子邮件的用户资料。"

with assistant():
    lm += """{
    "name": """ + gen("name", regex=r'"[A-Za-z ]+"', max_tokens=30) + """,
    "age": """ + gen("age", regex=r"[0-9]+", max_tokens=3) + """,
    "email": """ + gen("email", regex=r'"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"', max_tokens=50) + """
}"""

print(lm)  # 保证有效 JSON

模式 2: 分类

from guidance import models, gen, select

lm = models.Anthropic("claude-sonnet-4-5-20250929")

text = "这个产品太棒了!我很喜欢它。"

lm += f"文本:{text}
"
lm += "情感:" + select(["积极", "消极", "中性"], name="sentiment")
lm += "
置信度:" + gen("confidence", regex=r"[0-9]+", max_tokens=3) + "%"

print(f"情感:{lm['sentiment']}")
print(f"置信度:{lm['confidence']}%")

模式 3: 多步推理

from guidance import models, gen, guidance

@guidance
def chain_of_thought(lm, question):
    """生成带逐步推理的答案。"""
    lm += f"问题:{question}

"

    # 生成多个推理步骤
    for i in range(3):
        lm += f"步骤 {i+1}:" + gen(f"step_{i+1}", stop="
", max_tokens=100) + "
"

    # 最终答案
    lm += "
因此,答案是:" + gen("answer", max_tokens=50)

    return lm

lm = models.Anthropic("claude-sonnet-4-5-20250929")
lm = chain_of_thought(lm, "200 的 15% 是多少?")

print(lm["answer"])

模式 4: ReAct 代理

from guidance import models, gen, select, guidance

@guidance(stateless=False)
def react_agent(lm, question):
    """使用工具的 ReAct 代理。"""
    tools = {
        "calculator": lambda expr: eval(expr),
        "search": lambda query: f"搜索查询结果:{query}",
    }

    lm += f"问题:{question}

"

    for round in range(5):
        # 思考
        lm += f"思考:" + gen("thought", stop="
") + "
"

        # 动作选择
        lm += "动作:" + select(["calculator", "search", "answer"], name="action")

        if lm["action"] == "answer":
            lm += "
最终答案:" + gen("answer", max_tokens=100)
            break

        # 动作输入
        lm += "
动作输入:" + gen("action_input", stop="
") + "
"

        # 执行工具
        if lm["action"] in tools:
            result = tools[lm["action"]](lm["action_input"])
            lm += f"观察:{result}

"

    return lm

lm = models.Anthropic("claude-sonnet-4-5-20250929")
lm = react_agent(lm, "25 * 4 + 10 是多少?")
print(lm["answer"])

模式 5: 数据提取

from guidance import models, gen, guidance

@guidance
def extract_entities(lm, text):
    """从文本中提取结构化实体。"""
    lm += f"文本:{text}

"

    # 提取人员
    lm += "人员:" + gen("person", stop="
", max_tokens=30) + "
"

    # 提取组织
    lm += "组织:" + gen("organization", stop="
", max_tokens=30) + "
"

    # 提取日期
    lm += "日期:" + gen("date", regex=r"\d{4}-\d{2}-\d{2}", max_tokens=10) + "
"

    # 提取地点
    lm += "地点:" + gen("location", stop="
", max_tokens=30) + "
"

    return lm

text = "蒂姆·库克于 2024-09-15 在库比蒂诺的 Apple Park 宣布。"

lm = models.Anthropic("claude-sonnet-4-5-20250929")
lm = extract_entities(lm, text)

print(f"人员:{lm['person']}")
print(f"组织:{lm['organization']}")
print(f"日期:{lm['date']}")
print(f"地点:{lm['location']}")

最佳实践

1. 使用正则表达式进行格式验证

# ✅ 好:正则表达式确保有效格式
lm += "电子邮件:" + gen("email", regex=r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")

# ❌ 坏:自由生成可能产生无效电子邮件
lm += "电子邮件:" + gen("email", max_tokens=50)

2. 使用 select() 进行固定类别选择

# ✅ 好:保证有效类别
lm += "状态:" + select(["待处理", "已批准", "已拒绝"], name="status")

# ❌ 坏:可能生成拼写错误或无效值
lm += "状态:" + gen("status", max_tokens=20)

3. 利用令牌修复

# 令牌修复默认启用
# 无需特殊操作 - 自然连接即可
lm += "首都是 " + gen("capital")  # 自动修复

4. 使用停止序列

# ✅ 好:在换行处停止以进行单行输出
lm += "姓名:" + gen("name", stop="
")

# ❌ 坏:可能生成多行
lm += "姓名:" + gen("name", max_tokens=50)

5. 创建可重用函数

# ✅ 好:可重用模式
@guidance
def generate_person(lm):
    lm += "姓名:" + gen("name", stop="
")
    lm += "
年龄:" + gen("age", regex=r"[0-9]+")
    return lm

# 多次使用
lm = generate_person(lm)
lm += "

"
lm = generate_person(lm)

6. 平衡约束

# ✅ 好:合理约束
lm += gen("name", regex=r"[A-Za-z ]+", max_tokens=30)

# ❌ 太严格:可能失败或非常慢
lm += gen("name", regex=r"^(John|Jane)$", max_tokens=10)

与替代方案的比较

特性 Guidance Instructor Outlines LMQL
正则表达式约束 ✅ 是 ❌ 否 ✅ 是 ✅ 是
语法支持 ✅ CFG ❌ 否 ✅ CFG ✅ CFG
Pydantic 验证 ❌ 否 ✅ 是 ✅ 是 ❌ 否
令牌修复 ✅ 是 ❌ 否 ✅ 是 ❌ 否
本地模型 ✅ 是 ⚠️ 有限 ✅ 是 ✅ 是
API 模型 ✅ 是 ✅ 是 ⚠️ 有限 ✅ 是
Pythonic 语法 ✅ 是 ✅ 是 ✅ 是 ❌ SQL 类
学习曲线 中等

何时选择 Guidance:

  • 需要正则表达式/语法约束
  • 想要令牌修复
  • 使用控制流构建复杂工作流程
  • 使用本地模型(Transformers、llama.cpp)
  • 偏好 Pythonic 语法

何时选择替代方案:

  • Instructor: 需要带自动重试的 Pydantic 验证
  • Outlines: 需要 JSON 模式验证
  • LMQL: 偏好声明式查询语法

性能特征

延迟减少:

  • 比传统提示方法快 30-50% 用于约束输出
  • 令牌修复减少不必要的重新生成
  • 语法约束防止无效令牌生成

内存使用:

  • 与无约束生成相比开销最小
  • 语法编译在首次使用后缓存
  • 在推理时高效令牌过滤

令牌效率:

  • 防止无效输出的令牌浪费
  • 无需重试循环
  • 直接路径到有效输出

资源

另请参阅

  • references/constraints.md - 全面的正则表达式和语法模式
  • references/backends.md - 后端特定配置
  • references/examples.md - 生产就绪示例