目录
创建 Issue 后,总会忘记添加“优先级”和“领域”标签。这样的情况发生了几次,未分类的 Issue 就逐渐积压起来了。
在个人开发的仪表盘应用中,我用 GitHub Issue 管理待办事项。Issue 模板会自动添加变更类型标签,但处理优先级,以及后端、前端等工作领域,之前都需要手动选择。于是,我决定在 Issue 创建后,通过 GitHub Actions 将内容传给 Jev,再根据分类结果添加标签。
Jev 支持的分类方式#
Jev 是 TypeSafe 提供的一类专注于判断的模型。它不会针对问题生成文章,而是按预先指定的格式作答。这次使用的格式是 Choice 和 Noul。
Choice 用于从固定选项中选择一项的分类任务。它还会返回各选项的概率,因此除了知道“选了哪个答案”,还可以查看判断的置信度。它适合从 critical、high、medium、low 中选择优先级。
Noul 会返回问题答案为“是”的概率,取值范围为 0 到 1。针对每个领域询问“要解决这个 Issue,是否需要修改这个领域的内容”,就能处理涉及多个领域的 Issue。如果前端和后端都需要修改,对应的两个问题就都会得到 yes 的判断。
另一种格式 Score 会返回各等级的概率和期望值。这次也用它测试了优先级判断。各格式的详细规格可以查阅 TypeSafe 文档。
本次测试环境与使用规则#
功能验证使用了以下环境。
- 模型:jev-1.13
- Python SDK:typesafe-sdk 0.7.1
- Python:3.13
- GitHub Actions:ubuntu-latest
添加标签时,我制定了两条规则。如果已有优先级或领域标签,就不再修改对应维度的标签。如果判断的置信度不足,也不添加标签。具体来说,优先级的 Choice confidence 低于 0.5 时跳过;领域的 yes 概率低于 0.5 时跳过。
这样就不必强行给模型判断模糊的 Issue 分类。没有添加标签的 Issue,可以按需由人工确认。
用英语编写问题和说明#
TypeSafe 文档说明,Jev 主要使用英语进行训练,用英语时准确率最高。它也能处理中文、日语和韩语文本,但准确率不一定与英语相同。
因此,我用英语编写了问题和选项说明。Issue 的标题和正文则保留日语,直接传入。领域说明也由仓库中使用的日语定义翻译成英语。
这种配置的分类效果如何,需要用实际 Issue 验证。这次我将过去自己标注过的 Issue 作为标准答案,调整问题表述和判断方法,比较结果。
对领域的提问应关注“是否需要修改”,而非“是否提及”#
如果逐个领域询问“这个 Issue 是否与前端有关”,判定范围就会过宽。例如,即使界面显示了错误,如果原因在后端,也可能无需修改前端代码。但对于“是否有关”这样的问题,前端仍有可能被判为 yes。
因此,我将问题改为“这是否是解决该 Issue 必须修改的主要领域之一”。同时,在 no 的条件中加入说明:如果只是正文中提到了这个领域、仅受到间接影响,或只是问题出现的场景,都不算符合条件。
Noul(
instructions=f"Is {description} one of the main areas that must be changed to resolve this issue?",
criteria={
"true": "Substantial work in this area is required. A person doing this work would edit files or configuration belonging to this area.",
"false": "This area is only mentioned, only indirectly affected, or merely the context in which the problem appears. No substantial work happens here.",
},
)问题的措辞变了,判断标准也会随之改变。最好先用文字明确界定哪些情况算符合条件,再用已有 Issue 验证。
用 Choice 判断优先级#
我分别尝试了用 Choice 和 Score 判断优先级。使用历史 Issue 得到的测试结果如下。
- Choice,不设 confidence 门槛:标签添加率 100%,与标准答案一致的比例为 67%,偏差在 ±1 个等级以内的比例为 95%
- Choice,confidence 不低于 0.5:标签添加率 81%,与标准答案一致的比例为 73%,偏差在 ±1 个等级以内的比例为 95%
- Choice,confidence 不低于 0.7:标签添加率 59%,与标准答案一致的比例为 76%,偏差在 ±1 个等级以内的比例为 98%
- Score,confidence 不低于 0.5:标签添加率 92%,与标准答案一致的比例为 56%,偏差在 ±1 个等级以内的比例为 94%
在本次数据中,Choice 的一致率高于 Score。Score 有时会返回介于等级之间的值,例如返回 2.5 时,需要决定将其归入哪个等级。测试中采用了四舍五入。而 Choice 可以直接采用概率最高的选项。
将 confidence 门槛设为 0.5 的 Choice,标签添加率为 81%,与标准答案的一致率为 73%。提高到 0.7 后,一致率有所提升,但能添加标签的 Issue 比例降到了 59%。这次综合考虑标签添加率,采用了不低于 0.5 的门槛。
问题是“How urgently should this issue be addressed?”。每个选项的说明都由对应标签的定义翻译成英语。例如,critical 指生产环境故障、严重漏洞、数据损坏等需要立即处理的问题;low 则指有空余时间时再推进的小幅改进或整理工作。
通过 GitHub Actions 执行#
工作流由 Issue 的 opened 事件触发,同时支持手动执行。手动执行时输入 Issue 编号。所需权限限定为读取仓库内容和读写 Issue,并将 TypeSafe API 密钥以 TYPESAFE_API_KEY 的名称存入仓库的 Secrets。
name: Issue Triage
on:
issues:
types: [opened]
workflow_dispatch:
inputs:
issue_number:
description: 分類する issue 番号
required: true
permissions:
contents: read
issues: write
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
cache: pip
cache-dependency-path: scripts/requirements.txt
- run: pip install -r scripts/requirements.txt
- env:
TYPESAFE_API_KEY: ${{ secrets.TYPESAFE_API_KEY }}
GH_TOKEN: ${{ github.token }}
ISSUE_NUMBER: ${{ github.event.issue.number || inputs.issue_number }}
run: python scripts/issue_triage.py "$ISSUE_NUMBER"checkout 和 setup-python 使用提交 SHA 固定版本,而不是使用标签。这是固定工作流所引用 Action 版本的一种方法。依赖包则在 requirements.txt 中写为 typesafe-sdk~=0.7.1。
传给脚本的参数只有 Issue 编号。标题、正文和已有标签通过 gh issue view 获取。先检查已有标签:如果已有优先级标签,就跳过 Choice 问题;如果已有领域标签,就跳过 Noul 问题。整个流程只向 Jev 询问缺失的分类维度,并且只为达到设定阈值的结果添加标签。
issue = json.loads(
subprocess.run(
["gh", "issue", "view", issue_number, "--json", "title,body,labels"],
check=True,
capture_output=True,
text=True,
).stdout
)
existing_labels = [label["name"] for label in issue["labels"]]
if not any(label.startswith("priority:") for label in existing_labels):
questions["priority"] = Choice(
instructions="How urgently should this issue be addressed?",
criteria=PRIORITIES,
)
if not any(label.startswith("area:") for label in existing_labels):
for name, description in AREAS.items():
questions[f"area_{name}"] = Noul(
instructions=f"Is {description} one of the main areas that must be changed to resolve this issue?",
criteria={
"true": "Substantial work in this area is required.",
"false": "This area is only mentioned, indirectly affected, or the context where the problem appears.",
},
)这个示例只摘录了构建问题的部分。API 调用、概率提取和添加标签的处理,请根据 SDK 规范补充到脚本中。标签名称和 API 返回值也需要结合实际仓库进行确认。
先用历史 Issue 确定阈值#
本次配置是使用个人开发应用中已有的 Issue 测试得出的。如果 Issue 内容或标签定义不同,不一定能达到相同的一致率。
可以先选取一些历史 Issue 作为标准答案,调整问题表述和 confidence 阈值,比较结果,这样更容易选出适合实际使用的配置。如果准备引入这套流程,不妨先用历史 Issue 检查分类效果,再确定适合自己仓库的标准。
目录
