1. 为什么我决定把 Issue 分诊这件事交给机器
维护过开源项目的人大概都有同感:真正消耗精力的往往不是写代码,而是每天打开仓库时面对的那一长串新 Issue。标题五花八门,有的只写一句"用不了",有的把日志贴了三百行,有的其实是功能建议却挂在了 Bug 分类下,还有的干脆是来问安装问题的。你一条条点开、读、判断、打标签、回复"请提供复现步骤",一套流程走下来,半小时就没了,而真正需要你亲自处理的核心问题可能只有两三个。
我维护的几个中小型仓库,平均每周新增 Issue 在 30 到 60 条之间。有段时间我统计过,其中大约 40% 属于信息不全需要追问、25% 属于重复或已有答案、15% 属于用错模板,真正需要我深入排查的不到 20%。也就是说,我每天花在"第一轮脏活"上的时间,绝大部分是在做机械的归类和信息补全,而不是解决问题本身。这种重复劳动做久了,人的判断力反而会下降,容易漏掉真正重要的 Issue。
于是我开始琢磨:能不能做一个Issue 分诊助手,把第一轮筛选、分类、追问、去重这些活儿交给 AI,让它先过一遍,我只处理它标记为"需要人工介入"的部分?这个想法落地之后,我把它做成了一个可复用的工具,核心思路是:用规则做粗筛,用大模型做语义理解和回复草稿生成,用人工做最终把关。整套流程跑下来,我处理 Issue 的时间大概压缩了六成以上,而且漏判率控制得还不错。
这篇文章我会把整个设计和实现过程拆开讲清楚,包括为什么要这么分层、每一层具体怎么落地、提示词怎么写、哪些坑我踩过、以及怎么保证 AI 不瞎打标签。适合正在维护开源项目、内部工单系统、或者任何需要处理大量用户反馈的读者参考。哪怕你只是想给自己的小仓库加个自动回复机器人,里面的思路也能直接拿去用。
2. 分诊助手到底要解决哪几件事
在动手写代码之前,我先把"分诊"这个动作拆解成了几个独立的能力。很多人一上来就想让大模型"读一下 Issue 然后告诉我怎么办",结果发现输出极不稳定,因为任务边界太模糊。正确的做法是把一个大任务拆成几个职责单一的小任务,每个任务单独设计输入输出。
2.1 把"分诊"拆成四个可独立验证的子任务
我最终确定的四个子任务是:分类、信息完整性检查、重复检测、回复草稿生成。这四个任务彼此独立,可以分别评估准确率,出问题也容易定位。
分类负责判断这条 Issue 属于 Bug、功能请求、使用咨询、文档问题还是其他。信息完整性检查负责判断这条 Issue 是否包含了复现所需的关键要素,比如版本号、操作系统、复现步骤、报错日志。重复检测负责在历史 Issue 里找相似项,避免同一个问题被反复提。回复草稿生成则根据前面的判断,生成一段得体的回复,要么追问缺失信息,要么引导到已有 Issue,要么确认已收到。
把这四件事分开的好处是,每一件都可以用不同的技术手段处理。分类和完整性检查适合用大模型,重复检测适合用向量检索加规则,回复草稿适合用模板加模型润色。如果全塞给一个模型一次性输出,你既没法评估它哪一步错了,也没法针对性地优化。
2.2 为什么不能让模型直接决定"关不关"
这里有个很重要的原则:AI 只做建议,不做裁决。我见过一些团队直接让机器人自动关闭 Issue,结果误关了用户的真实反馈,社区口碑一下就崩了。分诊助手的定位应该是"给维护者减负",而不是"替维护者做决定"。
所以我的设计里,所有 AI 的输出都只是标签和草稿,最终是否打标签、是否关闭、是否回复,都要经过人工确认,或者至少经过一层规则校验。比如模型说"这是重复 Issue",我不会直接关,而是自动贴一条评论"这条看起来和 #123 相似,如果是同一个问题请到那边补充信息",然后打上possible-duplicate标签,等人工确认。
这个边界划清楚之后,整个系统的风险就小了很多。哪怕模型判断错了,最坏的结果也只是多了一条待确认的标签,不会造成不可逆的伤害。
2.3 一个真实的分诊流程长什么样
我把整个流程画成了一条流水线,每一步都有明确的输入输出和失败处理:
| 步骤 | 输入 | 处理方式 | 输出 | 失败兜底 |
|---|---|---|---|---|
| 1. 拉取新 Issue | 仓库事件 | 定时轮询或 Webhook | Issue 原始数据 | 记录日志,下轮重试 |
| 2. 规则粗筛 | Issue 标题正文 | 关键词匹配 | 初步分类 | 归入"待人工" |
| 3. 模型分类 | 清洗后文本 | 大模型调用 | 类别 + 置信度 | 低置信度转人工 |
| 4. 完整性检查 | 文本 + 模板 | 大模型 + 字段校验 | 缺失项列表 | 默认要求补全 |
| 5. 重复检测 | 文本向量 | 向量检索 | 相似 Issue 列表 | 无结果则跳过 |
| 6. 生成草稿 | 以上结果 | 模板 + 模型 | 回复文本 | 用纯模板兜底 |
| 7. 人工确认 | 全部结果 | 人工 | 最终标签和回复 | — |
这条流水线里,第 2 步的规则粗筛经常被忽略,但它其实很关键。比如标题里带"崩溃""报错""异常"的,大概率是 Bug;带"希望""建议""能不能"的,大概率是功能请求。用规则先分个大概,可以大幅降低后面模型调用的成本和误判率。
3. 规则层:先让机器做最便宜的判断
很多人做 AI 工具时容易犯一个错:什么都想交给模型。但模型调用有成本、有延迟、还不稳定。能用规则解决的,绝对不要上模型。规则层是整个分诊助手的第一道闸门,它的目标是"用最低成本过滤掉最容易判断的部分"。
3.1 关键词表怎么建才不误伤
我一开始的关键词表写得很随意,结果发现"崩溃"这个词在功能请求里也会出现,比如"希望增加崩溃日志导出功能"。所以关键词匹配不能只看单词,要看上下文。我的做法是给每个关键词配上位置权重和否定词检测。
具体来说,标题里出现的关键词权重高于正文,正文前 200 字出现的权重高于后面。同时维护一个否定词表,比如"希望""建议""能否"出现在关键词附近时,降低 Bug 类别的权重。这套逻辑用简单的打分就能实现,不需要模型。
# 规则打分示例(简化版) BUG_KEYWORDS = {"崩溃": 3, "报错": 3, "异常": 2, "失败": 2, "无法": 2} FEATURE_KEYWORDS = {"希望": 3, "建议": 3, "能否": 2, "增加": 2, "支持": 1} NEGATION = {"希望", "建议", "能否", "想要"} def rule_score(title, body): text = title + " " + body[:200] bug_score = sum(w for k, w in BUG_KEYWORDS.items() if k in text) feature_score = sum(w for k, w in FEATURE_KEYWORDS.items() if k in text) # 如果否定词出现在 Bug 关键词附近,削弱 Bug 分 for neg in NEGATION: if neg in text: bug_score *= 0.5 break return {"bug": bug_score, "feature": feature_score}这段代码很粗糙,但实测下来能把大约 60% 的 Issue 分到正确的粗类别里,剩下的 40% 交给模型。这就省下了一大半的模型调用。
3.2 模板匹配:识别用户有没有按规矩来
大部分仓库都会提供 Issue 模板,但用户经常不按模板填。我的做法是检查正文里是否包含模板要求的关键字段,比如"复现步骤""期望行为""实际行为""版本信息"。如果这些字段缺失,直接标记为"信息不全",甚至不需要模型介入。
这里有个细节:不同模板的字段名不一样,所以我会把模板解析成字段列表,然后逐个检查。检查方式不是精确匹配,而是模糊匹配,因为用户可能写"重现步骤"而不是"复现步骤"。用简单的编辑距离或者包含关系判断就够了。
提示:模板字段检查不要做得太严格。有些用户虽然没写"复现步骤"这四个字,但把步骤写得很清楚。所以字段检查只作为"信息不全"的辅助信号,最终判断还是要结合模型对内容的理解。
3.3 规则层的边界在哪里
规则层能处理的是"模式明显"的情况,一旦遇到语义模糊、需要理解上下文的,就必须交给模型。我给自己定了一条线:如果规则打分的前两名差距小于阈值,或者所有类别得分都很低,就转模型处理。这样既保证了效率,又不会因为规则太死而误判。
实测下来,规则层能独立处理大约 55% 到 65% 的 Issue,剩下的进入模型层。这个比例会随着关键词表的完善而提高,但我不建议追求 100%,因为规则越复杂越难维护,而且容易过拟合到历史数据上。
4. 模型层:提示词设计才是真正的难点
规则层过滤完之后,剩下的 Issue 就要交给大模型了。这一步是整个分诊助手的核心,也是最容易做砸的地方。我前后改了七八版提示词,才把输出稳定下来。下面把我踩过的坑和最终方案讲清楚。
4.1 为什么一次性让模型输出所有结果会翻车
我最初的提示词是这样的:"请分析这条 Issue,判断它的类型、是否信息完整、是否重复,并生成回复。"结果模型经常顾此失彼,分类对了但完整性判断错了,或者回复里编造了不存在的版本号。原因很简单:一个提示词里塞太多任务,模型的注意力会被分散。
后来我改成每个任务单独调用一次模型,虽然调用次数多了,但每次输出都稳定可控。分类任务只输出类别和置信度,完整性任务只输出缺失字段,回复任务只输出回复文本。这样每个任务的提示词都可以针对性优化,评估也方便。
4.2 分类提示词:给模型划死选项,别让它自由发挥
分类任务最大的问题是模型喜欢自创类别。你让它分类,它可能给你输出"疑似环境问题""可能是配置错误"这种你根本没定义的类别。解决办法是在提示词里明确列出所有可选类别,并要求只输出其中之一。
我的分类提示词大致是这样的:
你是一个开源项目的 Issue 分诊助手。请将下面的 Issue 分类到以下类别之一: - bug:用户报告功能异常、崩溃、报错 - feature:用户请求新功能或改进 - question:用户询问使用方法、配置方式 - docs:用户指出文档错误或缺失 - other:以上都不属于 只输出类别名称,不要输出任何解释。 如果无法确定,输出 uncertain。 Issue 标题:{title} Issue 正文:{body}关键点有三个:一是类别定义要写清楚,每个类别给一两个例子;二是要求只输出类别名,方便程序解析;三是提供uncertain选项,让模型在不确定时能诚实表达,而不是硬猜。
4.3 完整性检查:把"缺什么"变成结构化输出
完整性检查的难点在于,模型很容易过度要求。比如用户已经写了操作系统和版本号,模型还要求提供"详细环境信息"。我的做法是把检查项固定成一个列表,让模型逐项判断"有/没有/不适用",而不是让它自由发挥。
请检查下面的 Issue 是否包含以下信息,对每一项输出 yes、no 或 na: - 版本号 - 操作系统 - 复现步骤 - 期望行为 - 实际行为 - 报错日志 以 JSON 格式输出,键为检查项,值为判断结果。 Issue 正文:{body}用 JSON 输出是为了方便程序解析。这里有个坑:模型有时候会输出带注释的 JSON,或者用中文的"是/否"。所以我在提示词里明确要求"只输出合法 JSON,不要有注释",并且在解析时做了容错,比如把"是"映射成 yes。
4.4 重复检测:向量检索比模型更靠谱
重复检测我一开始也想用模型做,让模型判断"这条 Issue 和历史上哪条相似"。但很快发现两个问题:一是模型不知道历史 Issue 的内容,你得把几百条历史 Issue 塞进上下文,成本高还不现实;二是模型对"相似"的判断标准不稳定。
后来我改用向量检索:把所有历史 Issue 的标题和正文做嵌入,存进向量库,新 Issue 来了之后做相似度检索,取 Top 5 相似项,再用一个轻量模型判断"是否真的是同一个问题"。这样既解决了上下文长度问题,又提高了准确率。
| 方案 | 准确率 | 成本 | 可维护性 |
|---|---|---|---|
| 纯模型判断 | 低 | 高 | 差 |
| 纯向量检索 | 中 | 低 | 好 |
| 向量检索 + 模型复核 | 高 | 中 | 好 |
实测下来,向量检索的 Top 5 里通常能命中真正的重复项,模型复核只是用来排除"看起来像但实际不同"的情况。这个组合的准确率比纯模型高不少。
4.5 回复草稿:模板打底,模型润色
回复草稿我坚持一个原则:模板负责结构,模型负责语气。纯模型生成的回复容易跑偏,比如过度承诺、编造信息、语气生硬。纯模板又太机械,用户看着不舒服。
我的做法是先根据分诊结果选一个模板,比如"信息不全"对应追问模板,"重复"对应引导模板,然后把模板和 Issue 内容一起给模型,让它"在保持模板结构的前提下,用更自然的语气重写"。这样既保证了回复的完整性,又让语气更友好。
下面是一段回复模板,请根据 Issue 内容把它改写得自然一些, 但不要改变模板的结构和核心信息,不要添加模板里没有的承诺。 模板:{template} Issue 内容:{body}注意:一定要在提示词里强调"不要添加模板里没有的承诺"。我踩过这个坑,模型自作主张写了"我们会在 24 小时内修复",结果根本没这回事,用户等了两天又来催。
5. 把整条流水线串起来:工程实现里的那些细节
前面讲的是每一层怎么做,这一节讲怎么把它们串成一个能跑的系统。这部分看起来是纯工程,但其实有很多影响最终效果的细节,处理不好会让前面的努力白费。
5.1 触发方式:轮询还是 Webhook
最开始的版本我用的是定时轮询,每 10 分钟拉一次新 Issue。优点是实现简单,不依赖仓库平台的 Webhook 配置;缺点是延迟高,而且频繁调用 API 可能触发限流。后来我改成了 Webhook 触发,新 Issue 一创建就推过来,实时性好了很多。
但 Webhook 也有坑:网络抖动会导致事件丢失,所以我在 Webhook 之外还保留了一个低频的兜底轮询,比如每小时拉一次,用来补漏。两者结合,既保证了实时性,又不会漏掉事件。
5.2 状态管理:怎么避免重复处理同一条 Issue
分诊助手必须记录每条 Issue 的处理状态,否则同一条 Issue 可能被反复处理,重复打标签、重复回复。我用一个简单的状态表来管理,字段包括 Issue ID、处理阶段、处理结果、时间戳。
| 字段 | 说明 |
|---|---|
| issue_id | Issue 唯一标识 |
| stage | 当前处理阶段(rule/model/done) |
| result | 分类、完整性等结果 |
| updated_at | 最后更新时间 |
| retry_count | 重试次数 |
每次处理前先查状态,已经处理过的直接跳过。如果某一步失败了,记录 retry_count,超过阈值就转人工,不再自动重试。这个机制看起来简单,但没有它整个系统会乱套。
5.3 失败兜底:模型调用失败时怎么办
模型调用失败是常态,网络超时、限流、返回格式错误都可能发生。我的处理原则是:任何一步失败,都不能让整条流水线卡死。具体做法是每一步都有超时和重试,重试失败后降级处理。
比如分类模型调用失败,就退回规则层的判断结果;完整性检查失败,就默认标记为"信息可能不全",让人工看一眼;回复生成失败,就直接用纯模板回复。这样即使模型服务挂了,分诊助手依然能提供基础的分诊能力,不会完全瘫痪。
5.4 日志与可观测性:出了问题怎么查
我强烈建议在早期就把日志做好。每条 Issue 的处理过程都要记录:规则层打了多少分、模型返回了什么、最终决策是什么、耗时多少。这样当用户反馈"我的 Issue 被误判了"时,你能快速定位是哪一步出了问题。
我的日志里会记录每次模型调用的原始输入和输出,虽然占空间,但排查问题时非常有用。有一次模型把一条功能请求判成了 Bug,我翻日志发现是提示词里"崩溃"这个词的权重被规则层放大了,导致模型被误导。没有日志的话,这种问题很难发现。
6. 实测效果与那些让我头疼的边界情况
系统跑起来之后,我持续观察了两周,记录了一些数据,也遇到了不少边界情况。这一节把真实效果和踩过的坑都摊开讲,方便你评估这套方案是否适合自己。
6.1 准确率到底怎么样
两周内系统处理了大约 90 条 Issue,我人工复核了每一条的判断结果。分类准确率大约 85%,完整性检查准确率约 80%,重复检测准确率约 75%,回复草稿的可用率(稍微改改就能发)约 70%。这个数字不算惊艳,但考虑到它省下了我大量的初筛时间,我觉得是划算的。
分类错误主要集中在"功能请求"和"使用咨询"之间,因为有些用户问"怎么实现某个效果",既像咨询又像功能请求。完整性检查的错误主要是过度要求,模型有时候会要求用户提供其实不必要的信息。重复检测的错误主要是把相似但不同的问题判成了重复。
6.2 那些让模型犯难的 Issue
有几类 Issue 特别容易让模型翻车。第一类是中英文混杂的,用户标题写英文、正文写中文,模型有时候会只读一半。第二类是超长 Issue,正文几千字,模型容易抓不住重点。第三类是带大量代码块的,模型有时候会把代码里的报错信息当成正文描述。
针对这些情况,我做了预处理:中英文混杂的先做语言检测,统一翻译成一种语言再处理;超长 Issue 先做摘要,把摘要和原文一起给模型;代码块单独提取出来,只把代码外的文本给模型做分类。这些预处理看起来麻烦,但能显著提升准确率。
6.3 用户对自动回复的反应
我原本担心用户反感自动回复,但实测下来反应还不错,前提是回复要有用且诚实。比如"感谢反馈,为了更快定位问题,能否补充一下版本号和复现步骤"这种回复,用户基本都会配合。但如果回复是"您的问题已收到,我们会尽快处理"这种空话,用户反而会更烦躁。
所以我的回复草稿里,尽量做到三点:一是明确告诉用户下一步要做什么,二是说明为什么要这么做,三是给一个预期,比如"补充信息后我们会尽快查看"。避免空洞的客套话,也避免过度承诺。
6.4 成本核算:这套方案到底花多少钱
很多人关心成本。我用的模型 API 按调用次数计费,每条 Issue 平均调用 3 到 4 次模型(分类、完整性、复核、润色),加上向量检索的费用,单条 Issue 的成本大概在几分钱到一毛钱之间。对于每周几十条 Issue 的仓库来说,一个月成本也就几块钱到十几块钱,完全可以接受。
如果预算更紧,可以进一步优化:把分类和完整性检查合并成一次调用,用更小的模型做分类,只在重复检测和回复润色时用大模型。我试过用轻量模型做分类,准确率只降了两三个百分点,但成本降了一半。
7. 如果你也想做一个,我的几点实操建议
写到这里,核心的设计和实现基本讲完了。最后分享几点我在这个项目里总结出来的经验,都是踩过坑之后才明白的,希望能帮你少走弯路。
第一,先跑通最小闭环,再优化准确率。我一开始就想把每个环节都做到最好,结果拖了很久才上线。后来我改成先用最简单的规则加一次模型调用跑通全流程,上线之后再逐步优化每个环节,效率高很多。分诊助手这种工具,早一天上线就早一天省时间。
第二,人工确认环节不能省。哪怕你的模型准确率很高,也要保留人工确认。这不仅是风险控制,也是收集反馈、持续优化模型的数据来源。我每次人工修正模型的判断,都会记录下来,定期用来调整提示词和规则。
第三,提示词要版本化管理。提示词是这套系统的核心资产,改一版可能效果差很多。我用一个简单的文件管理提示词,每次修改都记录版本和对应的准确率,这样能清楚地知道哪一版效果最好,也方便回滚。
第四,别追求全自动。分诊助手的价值是"减负",不是"替代"。把目标定在"帮维护者省下 60% 的初筛时间",比定在"完全自动处理"要现实得多,也安全得多。我现在每天打开仓库,先看 AI 分好类的结果,只处理它标记为需要人工的部分,剩下的它已经帮我打好了标签、写好了回复草稿,我确认一下就行。
这套东西我还在持续迭代,最近在尝试把用户的历史行为也纳入判断,比如老用户提的 Issue 优先级更高、经常提无效 Issue 的用户需要更严格的模板校验。这些优化能不能提升效果,等我跑一段时间再回来分享。如果你也在维护仓库,被 Issue 淹没,不妨从最简单的规则层开始试试,哪怕只是自动打个标签,也能省下不少时间。