# PR #31484 完整报告

- 仓库：`sgl-project/sglang`
- 标题：Upgrade llguidance to 1.7.6
- 合并时间：2026-07-18 07:31
- 原文链接：http://prhub.com.cn/sgl-project/sglang/pull/31484

---

# 执行摘要

- 一句话：升级 llguidance 依赖至 1.7.6 并适配 API
- 推荐动作：该 PR 值得精读，尤其是 `llguidance_backend.py` 中的适配模式：如何应对上游 API 删除单一属性改用集合属性的变化。对于维护依赖升级的开发者有参考价值。

# 功能与动机

升级固定的 llguidance 依赖从 0.7.11 到 1.7.6，并适配 llguidance 语法后端以使用更新的 API。这是为了保持与上游版本的兼容性，并获得新版本的功能与修复。

# 实现拆解

**步骤 1：升级依赖版本约束 **- 在所有 pyproject.toml 变体中（python/pyproject.toml、pyproject_cpu.toml、pyproject_npu.toml、pyproject_other.toml、pyproject_xpu.toml 以及 3rdparty/amd/wheel/sglang/pyproject.toml）将 llguidance 版本约束从 `>=0.7.11,<0.8.0` 更新为 `>=1.7.6,<2.0.0`。

**步骤 2：适配 llguidance 后端 API**（python/sglang/srt/constrained/llguidance_backend.py） - 新增辅助函数 `_normalize_eos_token_ids`，用于将 eos_token_ids 参数统一为 int 或 list 类型。`GuidanceGrammar` 类：初始化时从 `self.llguidance_tokenizer.eos_tokens` 获取 EOS token 集合，替代已移除的 `eos_token` 属性。在 `accept_token` 方法中检查 `token in self.eos_tokens` 而非 `token == self.eos_token`。`GuidanceBackend` 类：新增 `eos_token_ids` 参数，并在调用 `from_tokenizer` 时通过 `eos_token` 参数传递标准化后的 EOS token 集合。

**步骤 3：适配调用方 **（python/sglang/srt/constrained/base_grammar_backend.py） - 在 `create_grammar_backend` 函数中，为 `GuidanceBackend` 构造时传递 `n_vocab=vocab_size` 和 `eos_token_ids=eos_token_ids` 参数。

**步骤 4：更新测试与工具脚本 **- `test_base_grammar_backend.py`：更新测试用例，验证 `GuidanceBackend` 构造传递了 `n_vocab` 和 `eos_token_ids`。`send_one.py`：将 JSON schema 占位符从 `$$ANY$$` 替换为 `{"type": "object"}`，因为 llguidance 1.7.6 不再支持 `$$ANY$$` 特殊字符串。

关键文件：
- `python/sglang/srt/constrained/llguidance_backend.py`（模块 约束解码；类别 source；类型 core-logic；符号 _normalize_eos_token_ids）: 核心逻辑，适配 llguidance 1.7.6 API 变更，包含 EOS token 属性替换和新参数传递。
- `python/sglang/srt/constrained/base_grammar_backend.py`（模块 约束解码；类别 source；类型 core-logic）: 传递新参数（n_vocab、eos_token_ids）给 GuidanceBackend，调用方适配。
- `test/registered/unit/constrained/test_base_grammar_backend.py`（模块 单元测试；类别 test；类型 test-coverage）: 更新测试用例以覆盖新的构造签名和参数传递。
- `python/sglang/test/send_one.py`（模块 测试工具；类别 test；类型 test-coverage）: 替换已移除的 $$ANY$$ 占位符为合法 JSON schema 对象。
- `python/pyproject.toml`（模块 构建配置；类别 config；类型 configuration）: 主项目的 llguidance 版本约束升级。
- `python/pyproject_cpu.toml`（模块 构建配置；类别 config；类型 configuration）: CPU 平台的 llguidance 版本约束升级。
- `python/pyproject_npu.toml`（模块 构建配置；类别 config；类型 configuration）: NPU 平台的 llguidance 版本约束升级。
- `python/pyproject_other.toml`（模块 构建配置；类别 config；类型 configuration）: 其他平台的 llguidance 版本约束升级。
- `python/pyproject_xpu.toml`（模块 构建配置；类别 config；类型 configuration）: XPU 平台的 llguidance 版本约束升级。
- `3rdparty/amd/wheel/sglang/pyproject.toml`（模块 构建配置；类别 config；类型 configuration）: AMD wheel 配置的 llguidance 版本约束升级。

关键符号：_normalize_eos_token_ids, GuidanceGrammar.accept_token, GuidanceGrammar.__init__, GuidanceBackend.__init__

## 关键源码片段

### `python/sglang/srt/constrained/llguidance_backend.py`

核心逻辑，适配 llguidance 1.7.6 API 变更，包含 EOS token 属性替换和新参数传递。

```python
# 辅助函数：统一 EOS token ID 格式，接受单个 int 或可迭代 int
def _normalize_eos_token_ids(
    eos_token_ids: Optional[Union[int, Iterable[int]]],
) -> Optional[Union[int, List[int]]]:
    if eos_token_ids is None or isinstance(eos_token_ids, int):
        return eos_token_ids
    return list(eos_token_ids)

class GuidanceGrammar(BaseGrammarObject):
    def __init__(self, llguidance_tokenizer: LLTokenizer, serialized_grammar: str):
        super().__init__()
        self.llguidance_tokenizer = llguidance_tokenizer
        self.serialized_grammar = serialized_grammar
        self.ll_matcher = LLMatcher(
            self.llguidance_tokenizer,
            self.serialized_grammar,
            log_level=int(os.environ.get("LLGUIDANCE_LOG_LEVEL", "1")),
        )
        self._check_err()
        # 使用新 API 的 eos_tokens 属性（返回 set）
        self.eos_tokens = set(self.llguidance_tokenizer.eos_tokens)

    def accept_token(self, token: int):
        if self.finished:
            return
        # 检查 token 是否在 EOS token 集合中
        if self.ll_matcher.is_stopped() and token in self.eos_tokens:
            self.finished = True
            return
        self.ll_matcher.consume_token(token)
        self._check_err()

class GuidanceBackend(BaseGrammarBackend):
    def __init__(
        self,
        tokenizer,
        any_whitespace: bool = True,
        whitespace_pattern: Optional[str] = None,
        n_vocab: Optional[int] = None,
        eos_token_ids: Optional[Union[int, Iterable[int]]] = None,
    ):
        super().__init__()
        self.tokenizer = tokenizer
        self.any_whitespace = any_whitespace
        self.whitespace_pattern = whitespace_pattern
        # 将 eos_token_ids 标准化后传给 from_tokenizer
        self.llguidance_tokenizer = from_tokenizer(
            self.tokenizer,
            n_vocab,
            eos_token=_normalize_eos_token_ids(eos_token_ids),
        )

```

# 评论区精华

本 PR 未产生实质性讨论，仅有自动化 CI 触发和重跑命令。主要挑战在于适配上游 API 变化，特别是 `eos_token` 到 `eos_tokens` 的变更，已在代码中通过 set membership 适配。

- 暂无高价值评论线程

# 风险与影响

- 风险：
 1. **兼容性风险**：llguidance 从 0.7.11 升级到 1.7.6 是跨越主版本升级，可能引入行为变化。虽已适配 eos_tokens 属性，但仍可能存在其他未暴露的 API 变更。
 2. **回归风险**：constrained decoding 功能可能因 API 误用而失效，尤其是多 EOS token 的场景。已有单元测试覆盖基本逻辑，但未覆盖所有边界情况。
 3. **依赖冲突**：新版本可能与项目中的其他依赖存在版本冲突，但已通过版本约束 `<2.0.0` 缓解。
 - 影响：对用户而言，使用 llguidance 后端的约束解码功能将正常工作，且可能获得上游的 bug 修复和性能改进。安装时需要 llguidance >=1.7.6，否则会因版本约束报错。对开发者，该 PR 提供了一个清晰的依赖升级适配模式，可作为后续库升级的参考。对系统无性能或安全风险。
 - 风险标记：依赖大版本升级 , 约束解码核心路径

# 关联脉络

- 暂无明显关联 PR