用 Jev 给 PR 加一层语义门禁

先说结论

  • 这是一个我自己的 CI 方案,不是 TypeSafe 官方给出的 PR 门禁模板;官方 cookbook 只提供可复用的判断能力。
  • 变更类型判断可以借用 Function calling 的 closed-set/Choice 思路;敏感路径判断可以借用 Guardrails for LLMs 的 Noul/Score 思路。
  • Confidence 官方文档写明:Choice/Score 有 confidence,Noul 没有。因此 Noul 判断只能读概率,不能读 confidence
  • 成本是我根据官方首页输入价推算:Jev 输入 $42/十亿 token,单 PR 的输入侧成本约 $0.000084~$0.00042。
  • 落地时 state 只放摘要、路径、变更统计,不要放密钥和完整源码;阈值必须拿历史 PR 回测。

链路设计:把“这个 PR 有没有风险”拆成可决策问题

CI 里最贵的步骤,往往不是测试跑多久,而是人看到 PR 后判断:这改动会不会碰坏核心链路?要不要我现在就 review?

我的方案是把人工判断拆成三个问题:

  1. 变更类型:这是一个 feature、refactor、bugfix、hotfix,还是 release 变更?
  2. 是否触及敏感路径:认证、授权、支付、密钥、数据库迁移、CI/CD 等区域有没有被碰到?
  3. 风险等级:如果直接合并,危险程度属于哪一档?

我的做法是:先把 diff 脱敏成 state,再一次请求里提交 ChoiceNoulScore 三个问题;然后按 confidence 和 Noul 的概率,决定 CI 走 pass、加标签还是阻塞。这是工程实现选择,不是官方推荐写法。

官方 Function calling cookbook 给了选动作的基础能力。它原文是这样写的:

When you order a “large iced oat latte, no sweetener,” the barista does not write your sentence down. They mark four options on a cup. This cookbook does the same thing for a trading API: a sentence goes in, and out comes a function name and its arguments as evaluated enums, each with a confidence.

这句话的用处不是“PR 分类”,而是它点出了一个关键:把自然语言压缩到有限选项,并且每个选项带置信度。对 PR 门禁来说,change_type 如果让模型自由生成,结果可能不稳定;如果做成 Choice,让模型从固定集合里选,CI 后续分支就更好写。

官方可依据的部分:用 Choice 选动作

Function calling cookbook 里,参数值如果来自固定列表,就被视为 closed set。官方文档写道:

An argument whose values come from a fixed list is a closed set.

我把 PR 类型同样做成 closed set,而不是开放式文本生成。这样不论模型输出的是 bugfix 还是 feature,都会落在 CI 预设分支里。官方示例里的 Choice 可以返回 confidence,这正好用来决定“要不要自动分流”。

这里要说明:官方 cookbook 的示例是交易 API,不是 CI;官方文档没有说“请这样给 PR 做语义门禁”。我在这里是把官方展示的 Choice + confidence + closed set 能力移植到 PR 场景,这是我的方案,不是官方场景。

官方可依据的部分:用 Noul 和 Score 卡风险

Guardrails cookbook 的做法更接近我需要的“风险门禁”。它原文说:

Screen every message going into and out of an LLM app with one TypeSafe request, thresholding hazard probabilities and severity to pass, review, block, or route.

我的 PR 场景里,“消息进出”模型可以类比为“变更进入代码库”,所以同样可以用概率和严重度来分流:通过、打标签、阻塞或人工 review。

Guardrails cookbook 还把风险拆成多个 Noul 问题,并搭配一个 Score 问题:

A battery of Noul questions hands you the probability that each hazard holds, and a Score question rates how much harm complying would do.

这个拆法在 PR 门禁里很有用:sensitive_touch 用 Noul 判断“是否触及敏感路径”,risk_level 用 Score 判断“合并后可能伤得多重”。需要注意的是,这仍然是我把官方 guardrails 能力迁移到 CI;官方 cookbook 面对的是 LLM 输入输出安全,并非代码评审。

Confidence 怎么变成 CI 分支

官方 Confidence 文档解释了 confidence 的本质:

The answer’s confidence property collapses that shape into a single number from 0 to 1, so you can threshold on it without doing the math yourself. (Noul answers don’t carry one.)

也就是说,ChoiceScore 适合用 confidence 做门槛,Noul 不适合。官方给的三档路径还写明:

Low confidence: Do not act. Route to a human, request clarification, or fall back to a different system.

因此,我的 CI 分支里,低 confidence 不自动通过,而是直接要求人工 review。这里有一个容易踩的坑:Noul 没有 confidence,如果你对 Noul 结果调 sensitive.confidence,那不是官方文档支持的做法。官方只告诉你 Noul 给概率,不给你算好的 confidence。

可改的伪代码

下面是我整理的骨架。它读取 diff、脱敏组装 state、调用 TypeSafe、再按结果决定 CI 分支。这不是可直接运行的 SDK 代码,字段和阈值也需要替换成你的实际环境。

def pr_semantic_gate(diff_meta):
    # diff_meta 已经脱敏:只有摘要、路径、变更统计,不含完整源码
    state = {
        "summary": diff_meta["summary"][:1800],
        "paths": diff_meta["changed_paths"][:80],
        "deletions": diff_meta["deletions"],
        "tests_touched": diff_meta["tests_touched"],
        "environment": diff_meta.get("ci_environment", "PR"),
    }

    resp = client.system_one(
        state=state,
        questions={
            "change_type": Choice(
                instructions="这个 PR 主要属于哪一类?",
                criteria={
                    "feature": "新增功能,但向后兼容",
                    "refactor": "结构整理,无功能变化",
                    "bugfix": "修复缺陷",
                    "hotfix": "紧急修复或安全问题",
                    "release": "版本、依赖或发布配置变更",
                },
            ),
            "sensitive_touch": Noul(
                instructions="这个变更是否触及敏感路径?",
                criteria=(
                    "触及认证、授权、支付、密钥、"
                    "数据库迁移、CI/CD 或生产配置"
                ),
            ),
            "risk_level": Score(
                instructions="如果按当前 diff 合并,风险有多高?",
                criteria=["none", "low", "medium", "high", "critical"],  # 官方字段名是 criteria(有序数组),不是 levels
            ),
        },
    )

    change = resp.answers["change_type"]
    sensitive = resp.answers["sensitive_touch"]
    risk = resp.answers["risk_level"]

    # Noul 没有 confidence,只读概率;这是官方 Confidence 页明确过的。
    sensitive_p = getattr(sensitive, "probability", None)
    if sensitive_p is None:
        return "need_review", resp

    if change.confidence < 0.5 or risk.confidence < 0.5:
        return "need_review", resp

    if sensitive_p >= 0.7 or risk.level in {"high", "critical"}:
        return "block_or_label", resp

    return "pass", resp

这里的 0.50.7 是示例阈值。官方 Confidence 示例里出现过 0.50.9,但官方也明确说:

The correct threshold values depend on your domain and the performance of the model for your use case.

所以这些值要拿你自己历史 PR 回测后再定。

落地注意事项

我的清单如下:

  1. state 脱敏:不放入完整 diff、仓库 token、CI 密钥。只放摘要、路径列表、变更统计。这样既能控制成本,也能避免把敏感信息送进外部模型。
  2. 日志可回溯:把 model 字段、置信度、概率、最终分支写进 CI 日志。一旦误判,至少能复算当时的输入和判定。
  3. 阈值回测:先把门禁跑在历史 PR 上,看 passblock_or_labelneed_review 的分布;不要一上来就阻塞合并。
  4. 先软门禁再硬门禁:建议先只加评论或打标签,稳定后再阻塞 CI。
  5. Noul 只读概率:不要给 Noul 结果读 confidence,否则代码容易崩或做出错误判断。

成本量级

成本按输入 token 估算。官方首页写明:

Jev.Cost $42 Per Billion input tokens.

这是一个官方数据。下面的成本范围是我自己推算,不是官方报价。

按输入 token 计算(自己推算):
单 PR 成本 = state_input_tokens / 1_000_000_000 × 42 USD

2K token: 2000 / 1e9 × 42 = 0.000084 USD
10K token: 10000 / 1e9 × 42 = 0.00042 USD

假设每个 PR 的 state 在 2K~10K 输入 token 量级,单次 PR 门禁成本约为 $0.000084~$0.00042(推算:按官方 $42/十亿输入 token 折算)。如果一天 200 个 PR,最低成本约 $0.0168/天(推算:200 × $0.000084);按 30 天 6000 个 PR 算,约 $0.504/月(推算:6000 × $0.000084)。但这是简化推算,实际还取决于请求次数、模型版本、SDK 打包方式、是否批量请求等。

官方首页还有一个 System One workflow 对比示例:Cost $0.000081、Completed in 0.114s,对比 LLM $0.0138808.566s。这是官方页面给出的示例,不是 PR 门禁承诺,只能说明量级上 System One 任务可能更便宜。

这次没核实的

  • Jev 输出 token 定价未在首页明确给出,我只核实到输入价为 $42/十亿 token。上面成本估算只覆盖输入侧,实际总成本需要确认输出 token 价格和单次请求计费规则。
  • TypeSafe 是否官方支持“CI/PR 语义门禁”这个场景,我未能核实。官方文档展示的场景是交易 function calling 和 LLM 护栏,本文的 CI 方案是我的设计。
  • 官方 SDK 中 Noul 返回字段的完整命名,我未在给定页面里逐字核实;因此伪代码里用 getattr(sensitive, "probability", None) 表示“读概率”,实际字段以 SDK 为准。
  • 0.7 这个 Noul 概率阈值是我在本例中提出的示例,不是官方推荐值。

参考来源

评论区

0 条评论

登录后可评论。

小猫跳舞 80 阅读