技术速递|使用 Copilot SDK 构建 AI 驱动的 GitHub Issue 分类系统

张开发
2026/4/11 11:40:32 15 分钟阅读

分享文章

技术速递|使用 Copilot SDK 构建 AI 驱动的 GitHub Issue 分类系统
作者Andrea Griffiths排版Alan Wang学习如何将 Copilot SDK 集成到 React Native 应用中以生成由 AI 驱动的 issue 摘要并采用适用于生产环境的优雅降级和缓存模式。Copilot SDK 让你可以将驱动 Copilot Chat 的同款 AI 添加到你自己的应用中。我想看看它在实际中的效果如何于是我构建了一个名为 IssueCrush 的 issue 分类应用。下面是我的一些经验以及你可以如何开始。如果你曾维护过开源项目或者在一个拥有活跃仓库的团队中工作过你一定熟悉这种感觉。你打开 GitHub看到通知角标显示47 个 issues。其中一些是 bug一些是功能请求一些其实应该放到讨论区还有一些是三年前就已出现的重复 issue。对 issue 进行分类所带来的精力消耗是真实存在的。每一个 issue 都需要上下文切换阅读标题、浏览描述、检查标签、思考优先级、决定如何处理。当多个仓库中有数十个 issue 需要逐一处理时你的大脑很快就会不堪重负。我想让这个过程变得更高效。借助 GitHub Copilot SDK我找到了实现的方法。Copilot Chathttps://youtu.be/c67GaAkf1BE?sigQm38vCKp0S8y66C/?wt.mc_id3reg_webpage_reactorIssueCrush 登场右滑即处理IssueCrush 将你的 GitHub issues 以可滑动卡片的形式展示。向左滑表示关闭向右滑表示保留。当你点击 “Get AI Summary” 时Copilot 会读取该 issue并告诉你它的内容以及应该如何处理。这样一来维护者无需逐条阅读冗长的描述就能获得即时、可操作的上下文从而更快地做出分类决策。下面是我如何集成 GitHub Copilot SDK 来实现这一点的。架构挑战第一个技术决策是弄清楚在哪里运行 Copilot SDK。React Native 应用无法直接使用 Node.js 包而 Copilot SDK 需要 Node.js 运行时。在内部SDK 会管理一个本地的 Copilot CLI 进程并通过 JSON-RPC 与其通信。由于依赖 CLI 可执行文件和 Node 环境这种集成必须在服务端运行而不能直接在 React Native 应用中运行。这意味着服务器必须安装 Copilot CLI并且该命令需要在系统的 PATH 中可用。我最终选择了一种服务端集成模式以下是这种架构之所以有效的原因在所有客户端之间共享单个 SDK 实例这样就不需要为每个移动客户端都创建一个新的连接。服务器为所有请求管理一个实例。开销更低、认证握手更少、清理也更简单。在服务器端存储 Copilot 认证密钥以确保凭证安全。你的 API token 永远不会接触客户端。它们保存在服务器上——这是它们应在的位置而不是放在一个可以被反编译的 React Native 包中。当 AI 不可用时具备优雅降级能力这样即使 Copilot 服务宕机或超时你仍然可以对 issues 进行分类。应用会回退到一个基础摘要。AI 可以让分类更快但不应该成为单点故障。对请求进行日志记录以便调试和监控因为每一次提示和响应都会经过你的服务器。你可以跟踪延迟、捕获失败并调试提示问题而无需在移动客户端中额外添加监控代码。在构建类似系统之前你需要在你的服务器上安装 Copilot CLI。GitHub Copilot 订阅服务或使用自有 API 密钥的 BYOK 配置。完成 Copilot CLI 的认证。在服务器上运行copilot auth或者设置COPILOT_GITHUB_TOKEN环境变量。Copilot CLIhttps://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli/?wt.mc_id3reg_webpage_reactorBYOK 配置https://github.com/github/copilot-sdk/blob/main/docs/auth/byok.md/?wt.mc_id3reg_webpage_reactor如何实现 Copilot SDK 集成Copilot SDK 使用基于会话的模型。你需要先启动一个客户端这会拉起 CLI 进程创建一个会话发送消息然后进行清理。const { CopilotClient, approveAll } await import(github/copilot-sdk); let client null; let session null; try { // 1. Initialize the client (spawns Copilot CLI in server mode) client new CopilotClient(); await client.start(); // 2. Create a session with your preferred model session await client.createSession({ model: gpt-4.1, onPermissionRequest: approveAll,}); // 3. Send your prompt and wait for response const response await session.sendAndWait({ prompt }); // 4. Extract the content if (response response.data response.data.content) { const summary response.data.content; // Use the summary... } } finally { // 5. Always clean up if (session) await session.disconnect().catch(() {}); if (client) await client.stop().catch(() {}); }关键 SDK 模式生命周期管理SDK 遵循严格的生命周期start() → createSession() → sendAndWait() → disconnect() → stop()这里有一个我吃过亏才学到的教训如果没有正确清理会话会导致资源泄漏。我花了两个小时排查内存问题最后才发现自己忘了调用disconnect()。因此务必将所有会话交互都包裹在 try/finally 语句中。在清理操作上使用.catch(() {})可以避免清理过程中的错误覆盖原始错误。用于分类的提示工程提示词结构为模型提供了充足的上下文信息使其能够完成任务。我会提供与 issue 相关的结构化信息而非直接堆砌原始文本const prompt You are analyzing a GitHub issue to help a developer quickly understand it and decide how to handle it. Issue Details: - Title: ${issue.title} - Number: #${issue.number} - Repository: ${issue.repository?.full_name || Unknown} - State: ${issue.state} - Labels: ${issue.labels?.length ? issue.labels.map(l l.name).join(, ) : None} - Created: ${issue.created_at} - Author: ${issue.user?.login || Unknown} Issue Body: ${issue.body || No description provided.} Provide a concise 2-3 sentence summary that: 1. Explains what the issue is about 2. Identifies the key problem or request 3.Suggests a recommended action (e.g., needs investigation, ready to implement, assign to backend team, close as duplicate) Keep it clear, actionable, and helpful for quick triage. No markdown formatting.;标签和作者相关信息的重要性远超你的想象。首次贡献者提出的 issue其处理方式与核心维护者提出的 issue 截然不同而 AI 会利用这些信息来调整其生成的摘要内容。响应处理sendAndWait()方法会在会话进入空闲状态后返回助手的响应。在访问嵌套属性之前务必验证响应链是否存在。const response await session.sendAndWait({ prompt }, 30000); // 30 second timeout let summary; if (response response.data response.data.content) { summary response.data.content; } else { thrownew Error(No content received from Copilot); }sendAndWait()的第二个参数是超时时间毫秒。我们需要将其设置得足够长以处理复杂 issue但又不能太长以免用户一直盯着加载状态。我见过太多 “undefined is not an object” 的错误所以我可以肯定永远不要省略对响应链的空值校验。客户端服务层在 React Native 端我将 API 调用封装在一个服务类中用于处理初始化和错误状态// src/lib/copilotService.ts import type { GitHubIssue } from../api/github; import { getToken } from./tokenStorage; export interfaceSummaryResult { summary: string; fallback?: boolean; requiresCopilot?: boolean; } export classCopilotService { private backendUrl process.env.EXPO_PUBLIC_API_URL || http://localhost:3000; asyncinitialize(): Promise{ copilotMode: string } { try { const response await fetch(${this.backendUrl}/health); const data await response.json(); console.log(Backend health check:, data); return { copilotMode: data.copilotMode || unknown }; } catch (error) { console.error(Failed to connect to backend:, error); thrownew Error(Backend server not available); } } asyncsummarizeIssue(issue: GitHubIssue): PromiseSummaryResult { try { const token await getToken(); if (!token) { thrownew Error(No GitHub token available); } const response await fetch(${this.backendUrl}/api/ai-summary, { method: POST, headers: { Content-Type: application/json, }, body: JSON.stringify({ issue, token }), }); const data await response.json(); if (!response.ok) { if (response.status 403 data.requiresCopilot) { return { summary: data.message || AI summaries require a GitHub Copilot subscription., requiresCopilot: true, }; } thrownew Error(data.error || Failed to generate summary); } return { summary: data.summary || Unable to generate summary, fallback: data.fallback || false, }; } catch (error) { console.error(Copilot summarization error:, error); throw error; } } } export const copilotService new CopilotService();React Native 集成UI 使用的是非常直接的 React 状态管理。点击按钮调用服务并缓存结果const [loadingAiSummary, setLoadingAiSummary] useState(false); const handleGetAiSummary async () { const issue issues[currentIndex]; if (!issue || issue.aiSummary) return; setLoadingAiSummary(true); try { const result await copilotService.summarizeIssue(issue); setIssues(prevIssues prevIssues.map((item, index) index currentIndex ? { ...item, aiSummary: result.summary } : item ) ); } catch (error) { console.error(AI Summary error:, error); } finally { setLoadingAiSummary(false); } };一旦 issue 对象上存在摘要卡片就会将按钮替换为摘要文本。如果用户滑走后再回来缓存的版本会立即渲染出来无需再次发起 API 请求。优雅降级AI 服务可能会失败。网络问题、速率限制以及服务中断都会发生。服务器处理两种失败模式订阅错误会返回 403 状态码从而让客户端展示清晰的提示信息而其他所有情况都会回退到基于 issue 元数据生成的摘要。} catch (error) { // Clean up on error try { if (session) await session.disconnect().catch(() {}); if (client) await client.stop().catch(() {}); } catch (cleanupError) { // Ignore cleanup errors } const errorMessage error.message.toLowerCase(); // Copilot subscription errors get a clear 403 if (errorMessage.includes(unauthorized) || errorMessage.includes(forbidden) || errorMessage.includes(copilot) || errorMessage.includes(subscription)) { return res.status(403).json({ error: Copilot access required, message: AI summaries require a GitHub Copilot subscription., requiresCopilot: true }); } // Everything else falls back to a metadata-based summary const fallbackSummary generateFallbackSummary(issue); res.json({ summary: fallbackSummary, fallback: true }); }回退机制会基于我们已有的信息生成一个有用的摘要function generateFallbackSummary(issue) { const parts [issue.title]; if (issue.labels?.length) { parts.push(\nLabels: ${issue.labels.map(l l.name).join(, )}); } if (issue.body) { const firstSentence issue.body.split(/[.!?]\s/)[0]; if (firstSentence firstSentence.length 200) { parts.push(\n\n${firstSentence}.); } } parts.push(\n\nReview the full issue details to determine next steps.); return parts.join(); }其他值得注意的模式服务器提供了一个/health端点用于指示 AI 是否可用。客户端在启动时会检查该端点如果后端无法支持 AI 功能就会直接隐藏摘要按钮避免出现不可用的按钮。摘要是按需生成的而不是预先生成的。这样可以降低 API 成本并避免用户只是快速滑过 issue 时产生不必要的调用浪费。SDK 使用await import(github/copilot-sdk)动态加载而不是在顶层使用require。这样即使 SDK 本身存在问题服务器也能正常启动从而让部署和调试更加顺畅。依赖项{ dependencies: { github/copilot-sdk: ^0.1.14, express: ^5.2.1 } }SDK 通过 JSON-RPC 与 Copilot CLI 进程进行通信。你需要在系统中安装 Copilot CLI并确保它可以在 PATH 环境变量中被访问。请查看 SDK 的包依赖要求以确认所需的最低 Node.js 版本。Copilot CLIhttps://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli/?wt.mc_id3reg_webpage_reactorSDK 的包依赖要求https://github.com/github/copilot-sdk/?wt.mc_id3reg_webpage_reactor我在构建这个项目时学到的服务端是正确的选择。SDK 依赖 Copilot CLI 二进制文件而你不可能在手机上安装它。把它运行在服务端可以将 AI 逻辑集中管理简化移动端客户端同时确保凭证永远不会离开后端。提示词结构比提示词长度更重要。向模型提供结构化的元数据例如标题、标签、作者比直接把整个 issue 内容当作原始文本效果更好。给模型合适的输入它才能返回更有价值的结果。永远要有回退机制。AI 服务可能会宕机也可能遇到速率限制。从一开始就设计优雅降级机制。即使 AI 部分不可用用户仍然应该能够继续处理 issue。一定要清理会话。SDK 要求显式清理先disconnect()再stop()。我曾经漏掉一次disconnect()结果花了两个小时排查内存泄漏。每次都要用try/finally。缓存结果。一旦生成摘要就把它存到 issue 对象上。如果用户滑走再回来可以立即显示缓存内容。没有第二次 API 调用没有额外成本也没有延迟。AI 可以让维护工作变得可持续。issue 分类是一种“隐形工作”非常消耗精力。它不会被感谢但会不断堆积。如果能把处理 50 个 issue 的时间减半就意味着可以把时间投入到代码审查、指导他人或者至少不用再害怕通知数量。Copilot SDK 只是一个工具但更重要的是这个思路找出那些让你疲惫的维护工作然后问问 AI 是否可以先做第一步处理。尝试一下github/copilot-sdk为构建智能开发者工具提供了真正的可能性。结合 React Native 的跨平台能力你可以以一种原生且流畅的方式将 AI 工作流带到移动端。如果你正在构建类似的东西可以从我这里介绍的服务端模式开始。这是最简单的可行路径而且可以随着应用规模扩展。源代码已发布在 GitHubAndreaGriffiths11/IssueCrush。开始使用 Copilot SDK看看你还能构建什么。入门指南会带你用大约五行代码完成第一次集成。如果你有反馈或想法可以加入 SDK 讨论区一起交流。AndreaGriffiths11/IssueCrushhttps://github.com/AndreaGriffiths11/IssueCrush/?wt.mc_id3reg_webpage_reactor开始使用 Copilot SDKhttps://github.com/github/copilot-sdk/?wt.mc_id3reg_webpage_reactor入门指南https://github.com/github/copilot-sdk#getting-started/?wt.mc_id3reg_webpage_reactorSDK 讨论区https://github.com/github/copilot-sdk/discussions/?wt.mc_id3reg_webpage_reactor

更多文章