基于LLM与GitHub Actions的AI代码审查机器人设计与实现

📅 2026/8/9 8:47:22
基于LLM与GitHub Actions的AI代码审查机器人设计与实现
1. 项目概述当AI化身“毒舌考官”最近在搞一个挺有意思的自动化项目我把它叫做“毒舌考官”。简单来说就是让一个AI模型7x24小时不间断地蹲守在代码仓库里每当有新的代码提交Pull Request简称PR进来它就立刻跳出来用一套极其严格、甚至有点“刻薄”的标准对每一行代码进行审查Code Review。这个AI考官不会因为深夜提交就放水也不会因为提交者是团队大佬就网开一面它唯一的准则就是预设的规则和代码质量本身。这个想法的源头是很多开发团队尤其是中小团队或开源项目都面临的一个共同痛点Code Review的人力瓶颈和标准不一。资深工程师时间宝贵不可能review所有代码而不同reviewer的标准和风格差异又可能导致代码质量参差不齐。更重要的是一些基础的、重复性的问题比如代码风格不一致、潜在的安全漏洞、明显的性能瑕疵如果能在提交环节就被自动拦截并给出修改建议能极大提升团队的交付效率和代码健康度。于是我决定利用现有的云服务和开源模型搭建一个完全自动化的、集成在GitHub工作流中的AI代码审查机器人。它不仅要能发现问题还要能用清晰、直接甚至带点“毒舌”幽默感的语言指出问题让开发者印象深刻从而主动改进。整个系统设计的目标是低成本、易部署、高定制、强提醒。接下来我就把这套系统的设计思路、核心实现、踩过的坑以及实际效果毫无保留地分享出来。2. 系统架构与核心组件选型要让AI成为全年无休的考官关键在于构建一个稳定、自动触发的流水线。整个系统的核心架构可以概括为事件驱动 无服务器函数 大语言模型LLM。2.1 整体工作流设计整个流程始于GitHub仓库的一个事件比如pull_request的opened或synchronize即更新了PR。我选择使用GitHub Actions作为整个自动化流程的编排引擎原因很简单它与GitHub原生集成无需额外认证配置相对直观并且有一定的免费额度非常适合个人项目或小型团队。事件触发当PR创建或更新时GitHub Actions被自动触发。代码获取与准备Action的工作流会拉取PR的代码差异diff这是审查的原材料。调用审查引擎将代码diff、PR描述、相关文件上下文等信息组装成一个清晰的提示词Prompt发送给后端的AI审查服务。AI分析与生成评论后端的AI服务通常是一个API接收请求基于LLM的能力分析代码生成包含问题、建议、严重等级和“毒舌”评语的审查报告。结果回传AI服务将生成的评论通过GitHub API以PR评论Comment或检查状态Check Status的形式反馈到对应的PR页面上。这个流程确保了从代码提交到获得AI反馈全程无需人工干预实现了真正的“无人值守”审查。2.2 核心组件深度解析2.2.1 LLM服务提供商的选择这是整个系统的“大脑”。选择时我主要权衡了以下几个维度能力、成本、速度、稳定性和易用性。OpenAI GPT系列能力最强尤其是代码理解方面GPT-4 Turbo表现非常出色。但成本相对较高且需要处理网络访问问题。对于追求极致审查质量且预算充足的项目它是首选。Anthropic Claude系列在长上下文和指令遵循方面表现优异适合审查大量代码变更。其安全机制也更严格。API价格与OpenAI接近。开源模型自部署如CodeLlama、DeepSeek-Coder等。这是控制成本、保障数据隐私的最佳路径。你可以使用像Ollama这样的工具在本地或自有服务器上运行模型或者使用Together AI、Replicate这类提供开源模型托管服务的平台。它们的成本通常远低于闭源API但需要一定的运维精力且模型能力可能稍逊于顶级闭源模型。国内大模型API如通义千问、文心一言、智谱GLM等。对于国内开发者这是网络最稳定、合规性最好的选择。它们的代码能力在快速进步且价格常有优势。我的选择与心得在项目初期验证阶段我使用了GPT-3.5 Turbo API因为它速度快、成本低足以验证流程。在正式部署时我转向了开源模型自部署方案具体是使用Together AI托管的DeepSeek-Coder-33B模型。它的代码专业能力很强价格仅为GPT-4的零头并且没有使用限制的担忧。对于“毒舌”风格这种需要高度定制化提示词的任务拥有一个稳定、可控的模型端点至关重要。2.2.2 无服务器函数与编排AI服务需要一个接收HTTP请求并返回结果的载体。这里我强烈推荐使用云函数Serverless。Vercel Serverless Functions / AWS Lambda / Google Cloud Functions这些服务可以让你只写一个简单的API接口比如用Python的FastAPI或Node.js的Express部署后得到一个HTTPS端点。它们按调用次数和资源使用量计费在流量不大时成本极低甚至免费。优势无需管理服务器自动扩缩容天然高可用。非常适合这种由外部事件触发、计算密集AI推理但执行时间不长的任务。在我的实现中我使用Vercel的Serverless Functions配合Python FastAPI框架来部署我的AI审查逻辑。GitHub Actions通过HTTP POST请求将代码diff等信息发送到这个Vercel函数地址函数内部调用Together AI的API处理后将结果返回。2.2.3 GitHub Actions 工作流配置这是系统的“触发器”和“执行器”。.github/workflows/ai-review.yml是这个项目的核心配置文件。name: AI Code Review on: pull_request: types: [opened, synchronize, reopened] jobs: review: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkoutv4 with: fetch-depth: 0 # 获取完整历史方便某些工具分析 - name: Run AI Code Review uses: actions/github-scriptv7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | // 1. 获取PR的详细信息diff, 文件列表等 const { data: pr } await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number, }); // 2. 获取PR的差异文件列表 const { data: files } await github.rest.pulls.listFiles({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number, }); // 3. 构建发送给AI服务的payload // 包括PR标题、描述、提交者、变更文件列表、每个文件的diff内容 const reviewPayload { pr_title: pr.title, pr_body: pr.body || , pr_author: pr.user.login, files: files.map(f ({ filename: f.filename, status: f.status, additions: f.additions, deletions: f.deletions, patch: f.patch, // 这就是核心的代码diff })) }; // 4. 调用我们部署的AI审查API const axios require(axios); const aiReviewEndpoint process.env.AI_REVIEW_API_URL; // 从仓库Secret读取 const aiReviewApiKey process.env.AI_REVIEW_API_KEY; // 从仓库Secret读取 try { const response await axios.post(aiReviewEndpoint, reviewPayload, { headers: { Authorization: Bearer ${aiReviewApiKey} } }); const aiComments response.data.comments; // 5. 将AI的评论逐条提交到PR for (const comment of aiComments) { await github.rest.pulls.createReviewComment({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number, commit_id: pr.head.sha, path: comment.filePath, line: comment.lineNumber, body: comment.body, // 这里就是AI生成的“毒舌”评论 }); } // 6. 可选创建一个总结性评论 if (response.data.summary) { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: ## AI 考官报告\n\n${response.data.summary} }); } } catch (error) { console.error(AI Review failed:, error); // 可以在这里添加失败通知比如发到Slack }这个工作流清晰地展示了从触发到完成评论的全过程。关键在于安全地处理API密钥使用GitHub Secrets和高效地处理可能很多的代码变更文件。3. “毒舌”提示词工程与审查策略让AI“毒舌”起来核心在于提示词Prompt工程。这不是简单地让AI骂人而是通过精心设计的指令让它以一种犀利、直接、略带幽默但又不失专业的方式指出问题目的是让开发者印象深刻并乐于改正。3.1 提示词的核心结构一个有效的审查提示词通常包含以下几个部分角色设定明确告诉AI它要扮演的角色。这是注入“性格”的关键。审查目标与范围明确要审查什么代码diff以及重点关注哪些方面。审查规则与标准给出具体的、可操作的审查清单。这是保证审查质量一致性的基础。输出格式要求强制AI以结构化如JSON或特定模板输出方便后续程序处理。风格与语气指令这里就是定义“毒舌”程度的地方。以下是我经过多次迭代后形成的一个相对稳定的提示词模板你是一位资深的、要求极其严格的、言辞犀利的首席技术官CTO正在对团队提交的代码进行最终审核。你的审核以“毒舌”著称一针见血不留情面但所有批评都必须基于事实和代码规范旨在帮助开发者成长而不是人身攻击。 【审查任务】 请仔细分析以下Pull Request中的代码变更diff并给出你的审查意见。 【PR信息】 - 标题{pr_title} - 描述{pr_body} - 提交者{pr_author} 【代码变更】 {formatted_diff} 【你的审查必须覆盖以下方面并按优先级排序】 1. **功能性错误**逻辑错误、边界条件缺失、潜在的崩溃风险如空指针、数组越界。 2. **安全漏洞**OWASP Top 10相关风险如注入、敏感信息泄露、不安全的反序列化等。 3. **代码坏味道**重复代码、过长的函数/类、复杂的条件判断、魔法数字、不清晰的命名。 4. **性能问题**低效的算法如O(n^2)的嵌套循环、不必要的资源创建如在循环内连接数据库、未使用索引等。 5. **可维护性与一致性**是否遵循了项目约定的代码风格如命名规范、缩进、注释是否清晰、架构是否合理。 【输出格式要求】 你必须以严格的JSON格式输出包含一个comments数组和一个summary字符串。 - 每个comment对象必须包含filePath文件路径 lineNumber行号基于diff severity严重等级BLOCKER, CRITICAL, MAJOR, MINOR, INFO category问题类别如“安全”、“性能” body评论正文。 - summary是对本次PR的总体评价。 【“毒舌”风格指南】 - 在body中请使用直接、犀利、略带讽刺或夸张幽默的语言。 - 可以引用一些经典的程序员梗或比喻。 - 对于低级错误可以表达“失望”或“震惊”。 - 对于好的实践也可以给予简洁的、酷酷的肯定。 - **绝对禁止**进行人身攻击、侮辱性词汇或与代码无关的评论。 示例评论风格 - 针对一个明显的空指针风险“哦看看这个一个优雅的NullPointerException孵化器。你是打算在用户点击这里的时候给他们表演一个程序崩溃的魔术吗赶紧加上空判断” - 针对重复代码“这段代码我好像在三个地方都见到了它的双胞胎兄弟。你们是在玩‘大家来找茬’吗建议抽离成一个公共函数除非你想在修改时玩‘打地鼠’游戏。” - 针对一个清晰的函数“嗯这个函数命名和结构居然能让人类看懂值得一个不情愿的点赞。继续保持。” 现在开始你的毒舌审查吧。3.2 审查策略与问题分类为了让审查更有条理我在后端逻辑中对问题进行了分类和优先级排序。这不仅仅是提示词的要求在收到AI的原始输出后我还会进行后处理严重性过滤我会设置一个阈值例如只将BLOCKER、CRITICAL和MAJOR级别的问题以评论形式发布到PR而MINOR和INFO级别的问题则汇总到最终的summary里。避免用太多琐碎问题“刷屏”引起开发者反感。同类项合并如果AI在同一个文件的相邻行或同一类问题上输出了多条相似评论后端逻辑会尝试将它们合并为一条更综合的评论提升阅读体验。提供修复建议光指出问题不够最好能给出修改方向。在提示词中我明确要求AI在评论中尽量包含“如何修改”的建议哪怕是伪代码。例如“这里用map查找是O(n)考虑改用Set时间复杂度可以降到O(1)。”3.3 处理长上下文与Token限制代码PR的diff可能很长很容易超过LLM的上下文窗口限制。我采取了以下策略分文件处理不将整个PR的diff一次性塞给AI。而是逐个文件处理为每个文件的diff单独构造提示词并调用AI。这样每个请求的上下文都在可控范围内。代价是API调用次数增加但对于按Token计费的模型总成本可能更低且稳定性更高。智能截断对于单个特别大的文件diff如果超过模型限制比如8000 Token我会优先截取变更行及其前后若干行例如前后各10行的上下文确保AI能理解变更的局部逻辑。同时在提示词中说明“这是部分截取请基于此分析”。使用长上下文模型如果预算允许直接选用像Claude-3-200k或GPT-4-128k这类支持超长上下文的模型可以一次性处理大多数PR。在我的实现中我选择了分文件处理策略。虽然调用次数多了但结合成本较低的开源模型总支出完全可控并且审查粒度更细对于大PR更稳定。4. 后端服务实现与部署细节后端服务是连接GitHub Actions和LLM的桥梁它需要完成接收请求、处理diff、调用AI、格式化输出、调用GitHub API等一系列工作。4.1 技术栈选择FastAPI Vercel我选择Python FastAPI来实现后端主要因为FastAPI现代、快速自动生成API文档异步支持好非常适合构建这类轻量级API。Python在AI和数据处理生态方面有巨大优势调用各种AI SDKopenai, anthropic, together等非常方便。部署到Vercel的Serverless Functions极其简单几乎零配置。4.2 核心代码解析以下是核心API端点的简化代码# main.py from fastapi import FastAPI, HTTPException, Depends, Header from pydantic import BaseModel from typing import List, Optional import os import together import asyncio from github import Github, GithubIntegration import httpx app FastAPI(titleAI毒舌考官) # 配置从环境变量读取 TOGETHER_API_KEY os.getenv(TOGETHER_API_KEY) GITHUB_APP_ID os.getenv(GITHUB_APP_ID) GITHUB_APP_PRIVATE_KEY os.getenv(GITHUB_APP_PRIVATE_KEY).replace(\\n, \n) MODEL_NAME deepseek-ai/DeepSeek-Coder-33B-Instruct together.api_key TOGETHER_API_KEY # 定义请求/响应模型 class FileDiff(BaseModel): filename: str status: str additions: int deletions: int patch: Optional[str] None class ReviewRequest(BaseModel): pr_title: str pr_body: Optional[str] pr_author: str files: List[FileDiff] class ReviewComment(BaseModel): filePath: str lineNumber: int severity: str # BLOCKER, CRITICAL, MAJOR, MINOR, INFO category: str body: str class ReviewResponse(BaseModel): comments: List[ReviewComment] summary: str def get_github_client(installation_id: int): 通过GitHub App的安装ID获取认证的Github客户端 integration GithubIntegration(GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY) access_token integration.get_access_token(installation_id) return Github(access_token.token) def construct_prompt_for_file(file_diff: FileDiff, pr_info: dict) - str: 为单个文件构建提示词 prompt_template f [角色与任务设定同上文此处省略...] 【PR信息】 - 标题{pr_info[title]} - 提交者{pr_info[author]} 【代码变更 - 文件{file_diff.filename}】 diff {file_diff.patch if file_diff.patch else 文件内容无变更或变更无法解析} [审查规则与输出格式要求同上文此处省略...] return prompt_template async def get_ai_review_for_file(prompt: str) - dict: 调用Together AI API获取单个文件的审查结果 try: response together.Complete.create( modelMODEL_NAME, promptprompt, max_tokens1500, temperature0.2, # 温度调低让输出更稳定、更“严厉” stop[] # 防止输出多余内容 ) raw_output response[choices][0][text].strip() # 尝试从输出中解析JSON这里需要健壮的解析逻辑 # 简化为示例实际需要处理解析失败的情况 import json # 假设AI返回的是纯JSON或者我们通过提示词约束其只输出JSON return json.loads(raw_output) except Exception as e: print(f调用AI API失败: {e}) return {comments: [], summary: fAI审查失败: {str(e)}} app.post(/review, response_modelReviewResponse) async def perform_review(request: ReviewRequest, x_github_event: Optional[str] Header(None)): 主审查端点。 GitHub Actions会POST到此端点。 all_comments [] pr_info {title: request.pr_title, author: request.pr_author} # 1. 过滤并处理有实际代码变更的文件 files_to_review [f for f in request.files if f.patch and f.status ! removed] if not files_to_review: return ReviewResponse(comments[], summary AI考官未发现可审查的代码变更。是提交了空PR吗) # 2. 并发处理每个文件的审查控制并发数避免速率限制 semaphore asyncio.Semaphore(3) # 限制并发为3 async def review_single_file(file): async with semaphore: prompt construct_prompt_for_file(file, pr_info) result await get_ai_review_for_file(prompt) # 为每个评论附加文件信息 for comment in result.get(comments, []): comment[filePath] file.filename # 可能需要根据diff调整行号映射略复杂此处简化 return result tasks [review_single_file(f) for f in files_to_review] file_results await asyncio.gather(*tasks, return_exceptionsTrue) # 3. 汇总结果 for res in file_results: if isinstance(res, Exception): print(f处理文件时出错: {res}) continue all_comments.extend(res.get(comments, [])) # 4. 根据严重性过滤和排序评论 severity_order {BLOCKER: 0, CRITICAL: 1, MAJOR: 2, MINOR: 3, INFO: 4} all_comments.sort(keylambda x: severity_order.get(x[severity], 5)) # 只返回严重程度较高的评论避免刷屏 comments_to_post [c for c in all_comments if c[severity] in [BLOCKER, CRITICAL, MAJOR]] # 5. 生成总结 summary_parts [] if comments_to_post: summary_parts.append(f 本次审查发现 **{len(comments_to_post)}** 个需要重点关注的问题。) if all_comments: summary_parts.append(f 问题分布{, .join([f{s}: {sum(1 for c in all_comments if c[\severity\]s)} for s in severity_order])}) summary .join(summary_parts) if summary_parts else ✅ AI考官本次变更看起来比较干净继续保持 return ReviewResponse(commentscomments_to_post, summarysummary) # 注意实际部署时将评论发布回GitHub的逻辑可以放在这个端点内 # 也可以由GitHub Actions在收到这个API的响应后自己去发布。 # 我选择了后者将逻辑放在GitHub Actions的脚本中这样后端更纯粹只负责AI分析。4.3 部署到Vercel部署过程非常简单将上述代码包含requirements.txt列出依赖fastapi,together,pygithub,httpx等推送到一个Git仓库。在Vercel控制台导入该仓库。在Vercel的项目设置Settings - Environment Variables中添加必要的环境变量TOGETHER_API_KEYGITHUB_APP_IDGITHUB_APP_PRIVATE_KEYAI_REVIEW_API_KEY用于认证GitHub Actions的调用。部署完成后Vercel会给你一个类似https://your-project.vercel.app的域名。你的API端点就是https://your-project.vercel.app/review。将这个URL和你在第3步设置的AI_REVIEW_API_KEY添加到你的GitHub仓库的Secrets中分别命名为AI_REVIEW_API_URL和AI_REVIEW_API_KEY。至此一个完整的、自动化的“毒舌考官”系统就搭建完成了。5. 实战效果、优化与避坑指南系统运行一段时间后我收集了一些反馈并针对出现的问题进行了优化。5.1 实际效果与团队反馈效率提升最明显的效果是那些常见的、模式化的代码问题如未使用的变量、简单的语法错误、不规范的命名几乎在提交瞬间就被指出来节省了人工Reviewer大量时间。质量门禁AI成功拦截了几次可能导致线上故障的严重Bug例如一个未处理的异步操作异常和一个可能的内存泄漏模式。这让团队对AI审查的信任度大增。“毒舌”风格的接受度出乎意料大多数开发者觉得这种风格“有趣”、“印象深刻”。一句“这段代码的复杂度让我想起了意大利面的内部结构”比干巴巴的“函数过于复杂”更能让人记住并想去重构。当然这非常依赖于团队文化。在更严肃的团队可以调整提示词让AI的语气变得专业而坚定即可。教育意义对于初级开发者AI的评论常常附带解释和最佳实践建议成了一个随时在线的、免费的代码教练。5.2 遇到的挑战与优化方案误报与噪音LLM有时会“过度解读”对完全合理的代码提出质疑或者误解了代码意图。优化引入白名单机制。对于某些被反复误报的、公认合理的模式比如特定的设计模式、框架约定用法可以在后处理阶段过滤掉。或者在提示词中更精确地描述项目特有的技术栈和约定。Token成本与速度初期使用GPT-4时审查大型PR成本高且速度慢。优化如前所述切换到DeepSeek-Coder这类性价比高的开源模型。同时分文件并发处理显著减少了整体等待时间。还可以设置PR变更行数的上限例如超过500行的PRAI只审查前N个文件或提示人工介入。行号映射问题AI评论中的行号是基于我们提供的diff行号但直接用它去GitHub上评论可能会偏移特别是当PR有多个Commit时。优化这是一个复杂问题。更可靠的做法是不使用AI提供的精确行号而是使用GitHub API的createReviewComment时指定position参数该参数表示此评论在diff中的位置从1开始。这需要更精细地解析diff结构。一个更简单的妥协是在评论中不指定行号而是说明“在文件XXX中关于YYY函数的修改部分”让开发者自行定位。速率限制与错误处理无论是GitHub API还是AI服务商API都有速率限制。优化在代码中实现指数退避重试机制。对于GitHub Actions可以使用actions/github-script的自动重试或自己实现重试逻辑。对于AI API调用务必用try...catch包裹并记录失败日志避免因单次失败导致整个审查流程中断。安全与隐私代码是核心资产。优化如果使用第三方AI API务必阅读其数据使用政策。对于敏感项目自托管开源模型是唯一选择。即使使用API也可以通过提示词要求AI“不要记忆或存储被审查的代码”。此外确保API密钥等机密信息完全通过GitHub Secrets和环境变量管理绝不硬编码。5.3 高级玩法与扩展思路这个基础框架可以玩出很多花样多模型投票同时调用两个不同的模型如GPT-4和Claude审查同一份代码然后对比或综合它们的意见可以提高准确率。与静态分析工具结合先使用SonarQube、CodeQL、ESLint、Pylint等传统静态分析工具扫描将它们的输出也作为上下文喂给AI。让AI专注于这些工具不擅长的逻辑、架构、可读性等“语义层面”的审查并可以解释静态分析工具报错的原因。学习团队模式将历史上人工Review通过的PR作为正面样本被拒绝的PR作为负面样本微调一个专属的审查模型让它更贴合团队的代码风格和标准。分级审查策略为不同等级的贡献者设置不同的审查严格度。例如对核心成员的PRAI只关注架构和安全等高级问题对新成员的PR则进行从代码风格到逻辑的全方位“毒舌”洗礼。集成到CI/CD不仅评论对于BLOCKER级别的问题可以让AI审查有权限请求更改Request Changes甚至在特定条件下阻止合并成为CI流水线中真正的一环。6. 总结与个人体会搭建并运行这个“AI毒舌考官”项目让我对AI在软件开发流程中的落地有了更深的体会。它不是一个要取代人类的“终结者”而是一个不知疲倦的超级辅助。它把开发者从重复、枯燥的代码检查中解放出来让他们能更专注于设计、架构和解决复杂问题。最大的收获有两点一是提示词工程是灵魂。你如何定义AI的角色、任务和输出直接决定了它的价值和体验。二是系统工程思维很重要。如何设计一个稳定、高效、可维护的自动化流水线如何处理错误、控制成本、保障安全这些和AI模型本身的能力同等重要。这个项目目前还在持续迭代中。下一步我计划引入更多开源静态分析工具的结果作为上下文并尝试用更轻量的模型如7B参数做第一轮快速过滤再用大模型做深度分析进一步优化成本和速度。如果你也在为团队代码质量发愁不妨试试亲手打造一个属于你们的AI考官它带来的效率和质量的提升可能会超乎你的想象。