开源项目 Issue 自动打标:用 LLM 分类器替代人工 Triage 的工程实践
开源项目 Issue 自动打标用 LLM 分类器替代人工 Triage 的工程实践一、Issue 管理的熵增定律当每天新增 20 个 Issue 时人工分类已成为瓶颈开源项目的 Issue 列表是最真实的需求入口。但随着项目热度增长Issue 的数量开始膨胀。Bug Report、Feature Request、Question、Duplicate——每个 Issue 都需要维护者手动分类打标。对于一个活跃的项目每天新增 10-20 个 Issue 是很正常的速度。每个 Issue 的人工分类需要 30-60 秒阅读、理解、打标、评论引导。按日均 15 个计算维护者每天需要投入 10-15 分钟在纯粹的分类工作上。这看起来不多但这是重复性的、低价值的劳动。你的时间本可以用在 Review PR、修复 Bug、回复有价值的问题上——而不是反复判断这个 Issue 应该打bug还是enhancement标签。LLM 分类器可以接管这个环节。它不需要完美只需要比人工分类快 10 倍且准确率达到 80% 以上。剩余 20% 的模糊 case 再转人工处理。graph TD A[新 Issue 创建] -- B[GitHub Webhook] B -- C[LLM 分类器] C -- D1{分类结果} D1 --|Bug| E1[auto: bug needs-triage] D1 --|Feature| E2[auto: enhancement] D1 --|Question| E3[auto: question 引导模板评论] D1 --|Duplicate| E4[auto: potential-duplicate 建议关闭] E1 -- F1{置信度} E2 -- F1 E3 -- F1 E4 -- F1 F1 --| 0.8| G[自动打标完成] F1 --| 0.8| H[标记 needs-review 转人工] style G fill:#51cf66,color:#fff style H fill:#ffd43b,color:#000本文将实现一个基于 LLM 的 Issue 自动分类系统通过 GitHub Actions 集成到 CI 中。二、分类器的设计原则小而精确而非大而全Issue 分类不是无限标签体系。对于大多数开源项目以下 5 个分类已经覆盖 90% 的场景标签含义自动化动作bug缺陷报告要求补充复现步骤enhancement功能建议引导到 Discussionquestion使用问题自动回复 FAQ 链接documentation文档问题标记 good first issueduplicate疑似重复建议搜索已有 Issue标签粒度不宜过细。如果标签超过 10 个LLM 的分类准确率会下降——因为在相似标签间如bugvsbug:criticalvsbug:minor做区分需要更深的上下文。Prompt 设计中要明确对于不确定的 case标记为needs-review而不是强行分类。一个分类错误给维护者带来的困扰大于没有分类。三、GitHub Actions 集成实现Workflow 配置# .github/workflows/issue-triage.yml name: Issue Triage on: issues: types: [opened] jobs: triage: runs-on: ubuntu-latest permissions: issues: write steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 20 - name: Install dependencies run: | npm init -y npm install actions/github actions/core openai - name: Classify Issue env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: node scripts/issue-classifier.js分类脚本// scripts/issue-classifier.js const github require(actions/github); const core require(actions/core); const OpenAI require(openai); const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const octokit github.getOctokit(process.env.GITHUB_TOKEN); const context github.context; const CLASSIFICATION_PROMPT 你是一个开源项目的 Issue 分类助手。请根据 Issue 内容判断其类型。 分类选项及标准 - bug: 报告了程序错误、崩溃、异常行为需要修复代码 - enhancement: 提出了新功能建议或改进方案 - question: 询问使用方法、配置方式或如何做 - documentation: 文档错误、缺失或改进建议 - duplicate: 明确提到与已有 Issue 相同的问题 规则 1. 如果 Issue 中缺少关键信息无版本号、无复现步骤、描述不清标记为 needs-more-info置信度 0.3 2. 最可能的分类只有一个 3. 输出 JSON 格式不要输出其他内容 输出格式 {label: bug|enhancement|question|documentation|duplicate|needs-more-info, confidence: 0.0-1.0, reason: 一句话理由} ; const AUTO_REPLIES { bug: 感谢提交 Bug 报告已自动标记为 \bug\。请在下面补充 - 使用的版本号 - 复现步骤 - 期望行为和实际行为, enhancement: 感谢你的功能建议已标记为 \enhancement\。如果方便请到 Discussions 区进一步讨论。, question: 这看起来是一个使用问题。建议先查看文档或 FAQ。如果仍无法解决请提供更多上下文。, documentation: 感谢指出文档问题已标记为 \documentation\欢迎提交 PR 修复。, duplicate: 此 Issue 可能已有相似的讨论。请搜索已有 Issue 确认是否重复。如确认重复建议关闭此 Issue。, needs-more-info: 感谢提交但需要更多信息才能判断问题。请补充 - 使用的版本 - 问题的具体表现 - 相关的日志/截图, }; async function classifyIssue() { const issue context.payload.issue; if (!issue) { core.setFailed(No issue in payload); return; } const title issue.title || ; const body issue.body || ; // 合并标题和正文用于分类 const text 标题: ${title}\n正文: ${body.substring(0, 3000)}; try { const response await openai.chat.completions.create({ model: gpt-4o-mini, messages: [ { role: system, content: CLASSIFICATION_PROMPT }, { role: user, content: text }, ], temperature: 0.1, response_format: { type: json_object }, max_tokens: 200, }); const result JSON.parse(response.choices[0].message.content); const { label, confidence, reason } result; console.log(分类结果: ${label} (置信度: ${confidence.toFixed(2)})); console.log(理由: ${reason}); // 高置信度自动打标 if (confidence 0.8) { await octokit.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, labels: [label, auto-classified], }); const reply AUTO_REPLIES[label]; if (reply) { await octokit.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, body: 自动分类: **${label}** (置信度: ${(confidence * 100).toFixed(0)}%)\n\n${reason}\n\n---\n\n${reply}, }); } } else { // 低置信度标记为需要人工审查 await octokit.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, labels: [needs-review, auto-classified], }); await octokit.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, body: 自动分类不确定。推测: **${label}** (置信度: ${(confidence * 100).toFixed(0)}%)\n\n已标记为人工审查。, }); } } catch (err) { console.error(分类失败:, err); // 分类失败不应该阻塞 Issue 创建 core.warning(Issue classification failed: ${err.message}); } } classifyIssue();四、准确率与成本的工程平衡准确率分析基于实际项目测试500 Issue 样本LLM 分类准确率约 82-88%。bug 和 enhancement 的分类准确率最高 90%question 和 duplicate 的准确率较低~75%因为这两类更容易混淆。误分类的影响低风险标签修正只需几秒高风险duplicate 误判为 bug浪费维护者时间。建议对 duplicate 类降低自动打标阈值到 0.9。金钱成本每次分类约 500-1000 Token使用 gpt-4o-mini 约 $0.0001。月均 300 个 Issue总成本不到 $0.05。不适用场景Issue 量 5/天人工分类负担可以接受项目有严格的安全 Issue 处理流程安全漏洞的分类需要人工判断多语言混合的 IssueLLM 对中英混合、代码片段中的问题容易误判五、总结Issue 自动分类系统的核心价值是用近乎零成本的方式帮维护者过滤掉 80% 的重复分类劳动。落地路径先在项目中运行一段时间静默模式只分类不打标对比 LLM 分类和人工分类的差异计算准确率后设定自动打标阈值最后开放自动评论功能。每一步都在降低风险渐进式地交付价值。工具的意义不是替代人而是让人把精力集中在最重要的决策上。少即是多——让 LLM 处理重复性的分类工作让维护者专注于代码和社区。