把 GitHub Issue 的第一轮脏活交给 AI:做一个 Issue 分诊助手

把 GitHub Issue 的第一轮脏活交给 AI:做一个 Issue 分诊助手

GitHub Issue 多了以后,最先让人头疼的往往不是写代码,而是每天要先看一堆 Issue:这是 Bug、功能请求还是咨询?优先级到底多高?应该让哪个方向的开发者处理?回复用户时又该怎么说?

这次我做了一个比较实际的小项目:GitHub Issue 分诊助手。

它相当于代替了开发者最烦恼的且繁琐的工作:issue solution。

它不负责替开发者关闭 Issue,也不直接替开发者修改代码,而是把最前面的"脏活"先做掉:读取 Issue → 分析问题 → 判断类型和优先级 → 推荐标签 → 给出回复建议。

模型部分使用蓝耘元生代 MaaS 提供的模型服务。


一、为什么要做这个东西?

如果你长期维护一个开源项目,应该很容易遇到这样的场景。

项目刚发布的时候,Issue 可能只有几十条。

这时候一个个看完全没问题。

但项目慢慢有了用户以后,Issue 会越来越多:

text 复制代码
Bug
Feature Request
Question
Documentation
Performance
Compatibility
Duplicate
Invalid
Security

真正麻烦的是,很多 Issue 并不是一句话就能判断。也就是说,问题的根源并不能直接判断出来,而是要花很多时间去复盘和排查。

例如:

"升级 VS Code 之后 MCP 服务没了。"

这到底是什么?

可能是:

  • VS Code 的 Bug
  • MCP 配置格式变化
  • 用户配置错误
  • 第三方 MCP Server 不兼容
  • 某个扩展的问题

太多了。问题的可能性总会从四面八方攻击过来。

如果是维护者,每个 Issue 都需要打开、阅读正文、看环境信息、判断影响范围,然后再决定标签和优先级。这个工作很繁琐,而且人眼看起来并不会比机器快,但是决策的工作又需要维护者来把关。

所以这个项目的定位非常明确:

AI 不替维护者做最终决定,而是完成第一轮信息整理。

最终决定仍然由人来做。


二、真实 Issue 测试

我们可以先来做一个简单的小测试。

测试对象:

Microsoft VS Code

Issue:

#339955 --- MCP migration writes type: "local" to workspace .mcp.json, which VS Code can't read, so the servers disappear

这个 Issue 创建于 2026 年 10 月 6 日 ,标签中已经有 ai-customizations、bug 和 mcp。作者提供了 VS Code 版本、操作系统、复现步骤以及实际结果。

查看 VS Code Issue #339955

这个 Issue 非常适合拿来做测试。

它描述的问题是:

text 复制代码
原来的 MCP Server 可以正常显示

        ↓

执行 MCP Migration

        ↓

服务器从 Workspace MCP 列表消失

        ↓

Agent session 中也无法使用

更关键的是,Issue 作者已经进一步定位到:

json 复制代码
{
  "mcpServers": {
    "single-keep": {
      "type": "local"
    }
  }
}

而 VS Code 的 Workspace MCP 配置解析器只接受 stdio / http 类型,因此迁移后写入 type: "local" 导致 Server 被忽略。

作者还验证了一个非常有价值的现象:

text 复制代码
local → stdio

手动修改后,MCP Server 就重新出现了。

所以这不是一个"为了演示 AI 随便写出来的 Bug"。

它是一个真实存在、能够复现、而且带有明确技术上下文的 Issue。


三、我希望这个 AI 助手完成什么?

先把需求限定清楚。

输入:

text 复制代码
GitHub Repository
+
Issue Number

例如:

text 复制代码
Repository:
microsoft/vscode

Issue:
339955

程序自动调用 GitHub API 获取:

text 复制代码
标题
正文
Labels
创建时间
作者
Issue URL

然后交给蓝耘 MaaS。

AI 最终输出:

text 复制代码
问题类型
优先级
推荐标签
问题摘要
关键证据
可能原因
维护者应该关注的内容
回复建议

整体流程:

text 复制代码
GitHub Issue
      │
      ↓
GitHub REST API
      │
      ↓
Issue 数据清洗
      │
      ↓
Prompt 构造
      │
      ↓
蓝耘元生代 MaaS
      │
      ↓
大模型
      │
      ↓
结构化 JSON
      │
      ├── 问题类型
      ├── 优先级
      ├── 标签建议
      ├── 原因分析
      └── 回复草稿

GitHub 官方提供了 Repository Issues REST API,可以直接获取仓库 Issue;对于公开仓库,读取 Issue 并不要求身份验证。需要注意的是,GitHub 的 Issues API 同时可能返回 Pull Request,因此程序中最好检查返回数据里的 pull_request 字段。


四、模型选择

对于一个 AI 项目来说,真正需要考虑的是:

模型服务能不能稳定接入,以及以后换模型的时候要不要重写业务代码。

蓝耘元生代 MaaS 对开发者比较友好的一个地方,是提供 OpenAI 兼容接口。

也就是说,我们可以继续使用熟悉的 Python OpenAI SDK:

python 复制代码
from openai import OpenAI

然后把模型服务地址指向蓝耘。

蓝耘目前也提供模型服务、统一 API 和智能路由能力;智能路由可以通过统一 API 在多个模型之间进行调度。

对于这个项目,我暂时没有把智能路由也塞进来。

原因很简单:第一版先把固定模型跑通,等项目真正需要控制成本或者根据任务复杂度选择不同模型时,再考虑智能路由。


五、准备蓝耘 MaaS

首先进入蓝耘元生代 MaaS 平台。

蓝耘元生代 MaaS

进入控制台以后创建 API Key。

然后进入模型列表,选择一个当前账号可用的文本模型。

本文代码以:

text 复制代码
/maas/deepseek-ai/DeepSeek-V3.2

作为示例。

蓝耘的 OpenAI 兼容 Base URL:

text 复制代码
https://maas-api.lanyun.net/v1

也就是说,使用 OpenAI SDK 时:

python 复制代码
client = OpenAI(
    api_key=os.environ["LANYUN_API_KEY"],
    base_url="https://maas-api.lanyun.net/v1"
)

模型 ID 则填写:

python 复制代码
model="/maas/deepseek-ai/DeepSeek-V3.2"

这个模型 ID 应该以你实际进入蓝耘模型详情页后看到的 API 调用模型名为准,不要机械复制我的文章,因为每个人模型选择上也会不同嘛。蓝耘当前的模型列表会变化,模型名称也可能调整。


六、创建项目

接下来就是创建项目。我们这里使用 Python。

目录结构:

text 复制代码
github-issue-triage/
│
├── app.py
├── github_client.py
├── lanyun_client.py
├── analyzer.py
├── requirements.txt
├── .env
└── README.md

安装依赖:

bash 复制代码
python -m venv .venv

Windows:

powershell 复制代码
.venv\Scripts\activate

Linux/macOS:

bash 复制代码
source .venv/bin/activate

安装:

bash 复制代码
pip install openai requests python-dotenv streamlit

requirements.txt:

text 复制代码
openai
requests
python-dotenv
streamlit

创建 .env:

env 复制代码
LANYUN_API_KEY=你的蓝耘API_KEY

LANYUN_BASE_URL=https://maas-api.lanyun.net/v1

LANYUN_MODEL=/maas/deepseek-ai/DeepSeek-V3.2

注意:

text 复制代码
.env

不要提交到 GitHub。

.gitignore:

text 复制代码
.env
.venv/
__pycache__/

七、第一步:从 GitHub 把真实 Issue 拿回来

创建 github_client.py:

python 复制代码
import requests


GITHUB_API = "https://api.github.com"


def get_issue(owner: str, repo: str, issue_number: int):
    url = (
        f"{GITHUB_API}/repos/"
        f"{owner}/{repo}/issues/{issue_number}"
    )

    response = requests.get(
        url,
        headers={
            "Accept": "application/vnd.github+json"
        },
        timeout=20
    )

    response.raise_for_status()

    data = response.json()

    # GitHub Issues API 也可能返回 Pull Request
    if "pull_request" in data:
        raise ValueError(
            f"#{issue_number} 是 Pull Request,不是普通 Issue"
        )

    return {
        "number": data["number"],
        "title": data["title"],
        "body": data.get("body") or "",
        "labels": [
            label["name"]
            for label in data.get("labels", [])
        ],
        "author": data["user"]["login"],
        "state": data["state"],
        "created_at": data["created_at"],
        "url": data["html_url"],
    }

这里没有做复杂封装。

因为这个项目第一版最重要的是:

保证拿到的数据和 GitHub 页面上的内容一致。

例如运行:

python 复制代码
issue = get_issue(
    "microsoft",
    "vscode",
    339955
)

print(issue)

就能拿到真实 Issue。


八、第二步:接入蓝耘元生代

创建:

text 复制代码
lanyun_client.py

代码:

python 复制代码
import os
from openai import OpenAI


client = OpenAI(
    api_key=os.environ["LANYUN_API_KEY"],
    base_url=os.getenv(
        "LANYUN_BASE_URL",
        "https://maas-api.lanyun.net/v1"
    )
)


def chat(messages):
    response = client.chat.completions.create(
        model=os.environ["LANYUN_MODEL"],
        messages=messages,
        temperature=0.2,
        max_tokens=1200,
        stream=False
    )

    return response

这里实际上就是整个项目与蓝耘之间的"连接层"。

以后如果换模型:

env 复制代码
LANYUN_MODEL=新的模型ID

业务代码基本不用动。

这也是我比较看重 MaaS 的地方。

模型属于基础设施,Issue 分诊属于业务逻辑。

两者最好不要写死在一起。


九、第三步:真正决定项目效果的 Prompt

接下来才是整个项目最核心的一部分。

创建:

text 复制代码
analyzer.py

先定义系统提示词:

python 复制代码
SYSTEM_PROMPT = """
你是一名开源项目维护者助手。

你的任务不是替维护者做最终决定,而是完成 GitHub Issue
的第一轮技术分诊。

请严格根据 Issue 中提供的信息进行判断。

你需要输出:

1. category
   - bug
   - feature_request
   - question
   - documentation
   - other

2. priority
   - critical
   - high
   - medium
   - low

3. labels
   给出最多 5 个建议标签。

4. summary
   用中文概括问题。

5. evidence
   列出支持判断的原始信息。
   不允许编造 Issue 中不存在的内容。

6. possible_cause
   分析可能原因。
   如果 Issue 作者已经提供了原因,需要明确区分
   "作者已经确认"与"模型推测"。

7. maintainer_action
   给维护者下一步建议。

8. reply
   给出一段适合维护者发布到 GitHub Issue
   的回复草稿。

特别要求:

- 不要声称问题已经修复。
- 不要虚构测试结果。
- 不要把模型推测写成确定事实。
- 如果信息不足,明确写出缺失信息。
- 回复要专业、简洁。
"""

然后构造用户输入:

python 复制代码
def build_prompt(issue):
    return f"""
请分析下面这个 GitHub Issue。

Repository:
{issue["repository"]}

Issue Number:
#{issue["number"]}

Title:
{issue["title"]}

Current Labels:
{issue["labels"]}

Author:
{issue["author"]}

Created At:
{issue["created_at"]}

Issue Body:
{issue["body"]}
"""

最后调用:

python 复制代码
from lanyun_client import chat


def analyze(issue):
    response = chat([
        {
            "role": "system",
            "content": SYSTEM_PROMPT
        },
        {
            "role": "user",
            "content": build_prompt(issue)
        }
    ])

    return response.choices[0].message.content

这样,一个完整的 AI 分析链就完成了。


十、一定要要求 AI 给出"证据"

这是这个项目里我比较在意的一点。

如果只让 AI 输出:

text 复制代码
这是一个 Bug。
优先级很高。
原因是 MCP 配置错误。

其实没什么意义。

因为你不知道它是不是胡猜的。

所以我在 Prompt 中专门增加了:

text 复制代码
evidence

要求 AI 找出支持判断的原始证据。

例如针对我们选择的 VS Code #339955,Issue 本身已经提供:

text 复制代码
Expected:
迁移后的 MCP Server 应该继续出现在 Workspace。

Actual:
Workspace (0),服务器消失。

Configuration:
type: "local"

Cause:
Workspace reader 只接受 stdio/http。

这些内容都可以回到原始 Issue 中验证。

这样 AI 的角色就从:

"给我猜一个答案。"

变成:

"帮我整理这个 Issue 已经提供的证据。"

这对技术类 AI 应用非常重要。

现在基本大功告成!来看下命令行的实现效果:


十一、做一个简单的 Web 界面

为了让它不只是一个命令行 Demo,我使用 Streamlit 做一个很简单的前端。

app.py:

python 复制代码
import streamlit as st

from github_client import get_issue
from analyzer import analyze


st.set_page_config(
    page_title="GitHub Issue 分诊助手",
    layout="wide"
)

st.title("GitHub Issue 分诊助手")

col1, col2 = st.columns(2)

with col1:
    owner = st.text_input(
        "Repository Owner",
        value="microsoft"
    )

with col2:
    repo = st.text_input(
        "Repository",
        value="vscode"
    )

issue_number = st.number_input(
    "Issue Number",
    min_value=1,
    value=339955
)

if st.button("开始分析", type="primary"):

    with st.spinner("正在获取 GitHub Issue..."):

        issue = get_issue(
            owner,
            repo,
            issue_number
        )

    st.subheader(
        f"#{issue['number']} {issue['title']}"
    )

    with st.expander("查看原始 Issue"):
        st.write(issue["body"])

    with st.spinner("正在调用蓝耘 MaaS..."):

        issue["repository"] = (
            f"{owner}/{repo}"
        )

        result = analyze(issue)

    st.subheader("AI 分诊结果")

    st.markdown(result)

启动:

bash 复制代码
streamlit run app.py

然后浏览器访问:

text 复制代码
http://localhost:8501

十二、第一次完整运行

现在输入:

text 复制代码
Owner:
microsoft

Repository:
vscode

Issue:
339955

点击:

text 复制代码
开始分析

整个程序会经历:

text 复制代码
① 请求 GitHub API

        ↓

② 获取 Issue #339955

        ↓

③ 提取标题、正文、标签、作者等信息

        ↓

④ 构造 Prompt

        ↓

⑤ 请求蓝耘 MaaS

        ↓

⑥ 大模型进行 Issue 分诊

        ↓

⑦ 返回分析结果

        ↓

⑧ Streamlit 页面展示

效果不错!蓝耘控制台上也有相应的信息。


十三、针对真实 Issue,AI 应该分析出什么?

对于:

text 复制代码
microsoft/vscode#339955

合理的分诊结果应该接近:

text 复制代码
问题类型:
Bug

优先级:
High

建议标签:
bug
mcp
ai-customizations

问题摘要:

VS Code MCP Server 在迁移过程中被写入
type: "local",而 Workspace MCP 配置解析器
无法识别该类型,因此迁移后的 MCP Server
从 Workspace 和 Agent Session 中消失。

关键证据:

1. 原配置使用 stdio Server。
2. Migration 后 .mcp.json 中出现 type: "local"。
3. Workspace reader 仅接受 stdio/http。
4. 手动将 local 修改为 stdio 后,Server
   可以立即恢复。

可能原因:

MCP Server Migration 在生成 workspace
.mcp.json 时使用了 Copilot CLI 格式中的
type: local,而 VS Code Workspace MCP
解析器尚未将 local 映射为 stdio。

维护者建议:

检查 mcpServerCustomizationMigration.ts
中对 Copilot MCP Server 配置的转换逻辑,
确认 stdio Server 是否应该转换为 type: stdio。

回复建议:

感谢提供详细的复现步骤和根因分析。

从目前的信息来看,问题似乎发生在 MCP
迁移过程中生成的配置类型与 Workspace
MCP 配置解析器支持的类型不一致。

特别是迁移后的 type: "local" 与 Workspace
解析器支持的 stdio/http 类型之间存在差异。

我们会进一步检查迁移逻辑,并验证将
type 从 local 修改为 stdio 后恢复正常的行为。

这类输出有一个特点:不准不懂装懂。

比如 Issue 作者已经明确给出了:

text 复制代码
type: local

以及:

text 复制代码
改成 type: stdio 后恢复

那么 AI 就应该把这些内容当成证据,而不是自己重新发明一个原因。


十四、一些问题:Base URL 差一点就写错

接入 MaaS 时有一个非常容易踩的坑。

完整请求地址是:

text 复制代码
https://maas-api.lanyun.net/v1/chat/completions

但是如果你使用 OpenAI SDK:

python 复制代码
OpenAI(
    base_url="..."
)

这里应该填写:

text 复制代码
https://maas-api.lanyun.net/v1

而不是:

text 复制代码
https://maas-api.lanyun.net/v1/chat/completions

原因是 SDK 自己还会继续拼接:

text 复制代码
/chat/completions

如果把完整接口地址也填进去,就可能变成:

text 复制代码
/v1/chat/completions/chat/completions

最终得到 404。

蓝耘当前的 OpenAI 兼容接入资料也采用 Base URL 与具体 /chat/completions 路径分开的方式。

所以这里建议直接记住:

python 复制代码
# SDK
base_url="https://maas-api.lanyun.net/v1"

真正 HTTP 请求才是:

text 复制代码
POST
https://maas-api.lanyun.net/v1/chat/completions

这个问题看起来很小,但实际开发的时候非常容易浪费时间。


十五、再加一个保护:不要把整个 GitHub 世界都塞给模型

最开始写的时候,很容易想到:

"既然 AI 越多信息越好,那我把 Issue 的所有东西都给它。"

其实不太合适。

Issue 可能非常长。

还可能包含:

text 复制代码
日志
代码
截图说明
评论
Stack Trace
大量重复信息

所以正式版本最好增加输入控制。

例如:

python 复制代码
MAX_BODY_LENGTH = 30000


body = issue["body"]

if len(body) > MAX_BODY_LENGTH:
    body = body[:MAX_BODY_LENGTH] + (
        "\n\n[Issue 正文过长,后续内容已截断]"
    )

更进一步,可以把:

text 复制代码
Issue 正文
最新评论
相关 Issue
Pull Request
仓库 README

分别处理。

而不是无脑全部拼成一个 Prompt。

这样后面才能继续扩展成真正的 Agent。


十六、为什么蓝耘在这个项目里不是"换了个 API 地址"?

如果项目只有:

python 复制代码
client = OpenAI(
    base_url="蓝耘地址"
)

然后:

python 复制代码
client.chat.completions.create(...)

确实没什么技术含量。

真正的项目链路应该是:

text 复制代码
                GitHub
                  │
                  │ REST API
                  ↓
          ┌───────────────┐
          │ Issue 获取层  │
          └───────┬───────┘
                  │
                  ↓
          ┌───────────────┐
          │ 数据清洗层    │
          └───────┬───────┘
                  │
                  ↓
          ┌───────────────┐
          │ Prompt 构造   │
          └───────┬───────┘
                  │
                  ↓
          ┌───────────────┐
          │ 蓝耘 MaaS     │
          │ 模型服务层    │
          └───────┬───────┘
                  │
                  ↓
          ┌───────────────┐
          │ AI 分诊结果   │
          └───────┬───────┘
                  │
          ┌───────┴────────┐
          ↓                ↓
       维护者查看       回复草稿

蓝耘这类云平台承担的是:

模型服务层。

GitHub API 是:

数据来源。

Python / Streamlit 是:

应用层。

Prompt 和规则是:

业务逻辑。

将它单独分层,这样充分地体现高内聚低耦合。


十七、问题来了:为什么不直接部署在本地呢?

这个项目其实完全可以本地部署一个开源模型。

但是对于 Issue 分诊这种场景,我认为第一阶段没有必要。

因为项目真正需要的是:

text 复制代码
输入一个 Issue
↓
快速得到一个稳定的分析结果

而不是研究:

text 复制代码
CUDA
显存
量化
TensorRT
vLLM
模型并行
GPU 调度

如果我的目标是研究推理部署,那我当然会选择自己部署模型。

但它终归只是一个分析的助手,它不能直接解决项目内部的issue,那么也就没必要部署在本地了。

MaaS 可以把模型基础设施这一层抽出来。

蓝耘元生代本身也不只是 MaaS API,它的产品体系覆盖模型服务、开发环境、算力和部署等环节;其官方资料将元生代定位为覆盖 AI 开发、训练、推理部署的基础平台。

所以对于这次项目,我没有为了"展示技术"而强行自己部署模型。

能省掉的基础设施工作,就先省掉,把时间留给真正的业务逻辑。


十八、最终效果


十九、简单复盘

一个开源项目每天可能会收到几十个 Issue。

其中很多工作并不需要维护者一开始就亲自处理:

text 复制代码
这是什么问题?
属于哪个类别?
严重程度怎么样?
有没有提供环境信息?
应该贴什么标签?
回复用户的时候应该先问什么?

这些事情看起来琐碎,但每天积累下来就是大量时间。

所以这次做的 Issue 分诊助手让 AI 先把维护者每天都要做的第一轮信息整理工作完成。

在这个项目里,GitHub 负责提供真实的问题数据,Python 负责业务逻辑,Streamlit 负责交互,而蓝耘元生代 MaaS 负责提供模型能力。

每一层都有自己的职责。

项目技术栈

text 复制代码
Python 3.x
        │
        ├── Streamlit
        ├── Requests
        ├── OpenAI SDK
        │
        ↓
GitHub REST API
        │
        ↓
蓝耘元生代 MaaS
        │
        ↓
DeepSeek / 其他可用模型

GitHub 官方 Issues API 支持读取 Issue、标签、正文等信息,并提供 Issue 评论等后续操作接口,因此这个项目后续可以自然扩展成"读取 → 分诊 → 人工确认 → 回写 GitHub"的完整工作流。

相关推荐
具身AGI2 小时前
物理AI 空间理解:空间物理 信息的三条注入路线
人工智能·深度学习
飘尘2 小时前
从"算子"到"AI Infra":大模型背后看不见的那群人在忙什么
人工智能·算法·面试
AOI小白新手上路2 小时前
anomalib 缺陷检测复现笔记:从跑通库到 EfficientAD 落地
人工智能·笔记·机器学习
中伟视界2 小时前
危化罐区“双预警“方案解析:AI气体监测布控球+AI布控球,罐区作业怎么管?
人工智能
kaixin_啊啊2 小时前
【零基础学AI】第 3 章课后练习与答案
人工智能
Maynor9962 小时前
Claude Opus 5.5 最强可视化案例合集:114 精选 + 1000+ 完整清单(含提示词)
人工智能·开源·claude
u1301302 小时前
AI 日报(2026年10月6日)
人工智能
黑妹天下第一乖2 小时前
第 08 讲:阿加犀 AidGenSE 端侧大模型服务化与 OpenAI 兼容接口
人工智能·嵌入式硬件·深度学习·机器人·iot