把 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 版本、操作系统、复现步骤以及实际结果。

这个 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 平台。
进入控制台以后创建 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"的完整工作流。