开启左侧

手把手教你完成用AI大模型做代码审查

[复制链接]
在线会员 0qCf 发表于 6 小时前 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题 |快速收录
尔思维中有个观念,能用AI年夜模子干的工作,能主动化的工作,便没有要让人干。那一类工作,人取AI年夜模子比拟,多少无劣势。AI年夜模子孜孜不倦,会加强进修,会愈来愈智慧,品质不竭进步。跟着手艺的进步,代码检查已经加入能够主动化工作之列,不竭皆很念进修一下怎样用AI年夜模子干代码检查,经常道即是不举措。此次下定决意,要拂拭各类托言战来由,辞别迟延症,现在咱们加入邪题。AI年夜模子检查的过程是:代码编辑者正在Gitlab UI界里倡议兼并代码恳求触收设置佳的gitlab webhook工作webhook将代码兼并疑息拉收给Jenkins正在Jenkins上运行AI代码检查支流程逻辑,将文献收收给年夜模子,将检查成果写进到Gitlab兼并 恳求批评区完毕步调

第一步 制作年夜模子镜像

正在代码检查圆里,上面 四个年夜模子表示绝对较佳1. GPT-OSS 系列

举荐指数:⭐⭐⭐⭐⭐模子:gpt-oss:20B(启源年夜模子)布置:Docker 容器,当地布置,撑持 Ollama 或者 vLLM劣势:华文代码理解才气强,多语言撑持(Python、Java、JS/TS 等罕见语言),高低文处置才气年夜(20B 模子约 32k token 高低文)特性:适宜代码检查、天生、劣化任务,特别正在庞大名目中能供给具体阐发2. CodeLlama 系列

举荐指数:⭐⭐⭐⭐模子:CodeLlama-7B/13B/34B-Instruct布置:撑持Docker,可用Ollama或者vLLM布置劣势:Meta启源,特地针对于代码劣化,撑持多种编程语言Python挪用:颠末OpenAI兼容API或者间接挪用3. DeepSeek Coder

举荐指数:⭐⭐⭐⭐模子:DeepSeek-Coder-6.7B/33B-Instruct布置:Docker + vLLM/Ollama劣势:代码才气强,华文撑持佳,拉理速率快特性:正在代码理解战天生圆里表示优良4. StarCoder2

举荐指数:⭐⭐⭐模子:StarCoder2-7B/15B布置:Docker + Transformers/vLLM劣势:BigCode名目,锻炼数据品质下特性:撑持80+编程语言刚刚开端挑选年夜模子挑选的是Meta的 CodeLlama-7B-Instruct,因为它的占用资本比力高,前面发明高低文少度偏偏高,不敷用,以下图所示

决定升级模子参数到CodeLlama-13B-Instruct,CodeLlama-13B-Instruct模子撑持的输出少度是4096  token,比CodeLlama-7B-Instruct模子多了一倍,  满意年夜大都场景。查了一下CodeLlama-13B-Instruct模子的软件请求:最少32GB RAM,举荐RTX  4090或者A100,公司的GPU是A100,契合 那一条,跑起去以后发明没有撑持华文,因而只可没法抛却。交着将年夜模子又换成DeepSeek-Coder-6.7B-Instruct,  发明异常的文献, 评审品质没有如CodeLlama系列,  最初换成gpt-oss:20b,发明照应速率比CodeLlama战DeepSeek-Coder皆要快一点儿, 并且代码评审品质也很下。终归该使用Docker镜像布置年夜模子佳,仍是使用裸机布置年夜模子佳,那个要瞅场景关于年夜大都场景,举荐从Docker开端:快速考证可止性成立尺度化过程积聚运维经历按照功用瓶颈决定可否迁徙到裸机功用枢纽场景才情索裸机:已经大白功用瓶颈正在容器层有充足的运维才气对于功用的请求超越了便利性资本充沛,寻求极致劣化AI代码考核,比拟功用,布置便利性,情况不合性,扩大机能够才是更应存眷的圆里。以是那里挑选了Docker镜像布置计划。写一个制作年夜模子镜像的Jenkins任务,过程是:树立情况变质战凭据。加入年夜模子镜像 Dockerfile 地点目次查抄近程 Docker Registry 可否已经有该镜像,假设 存留,跳过建立战拉收,假设 没有存留,建立镜像、拉收到近程 Docker Registry、革新 Kubernetes deployment
脚把脚学您完毕用AI年夜模子干代码检查w2.jpg
pipeline {  agent { label 'jenkins-runner-1' }  environment {    REGISTRY_HOST = 'reg.xxx.com:9088'    MODEL_NAME = 'gpt-oss-20b'    CONTAINER_NAME = 'codellama-review'  }    stage('制作年夜模子效劳镜像') {      steps {        script {          try {            withCredentials([              usernamePassword(                  credentialsId: 'REGISTRY',                  usernameVariable: 'REGISTRY_USERNAME',                  passwordVariable: 'REGISTRY_PASSWORD'              )            ]) {              env.IMAGE_TAG_MODEL = "base-images/ai-code-review:${env.MODEL_NAME}"              dir("co妹妹on-tools/ai-code-review/${env.MODEL_NAME}") {                def imageModelExists = sh(                  script: "curl -s -o /dev/null -w \"%{http_code}\" -u $REGISTRY_USERNAME:$REGISTRY_PASSWORD https://$REGISTRY_HOST/v2/${IMAGE_TAG_MODEL.split(':')[0]}/manifests/${IMAGE_TAG_MODEL.split(':')[1]}",                  returnStdout: true                ).trim() == '200'                //假设 镜像没有存留,则建立并拉收,并启用容器,运行镜像效劳                if (!imageModelExists) {                  sh """                    set -e                    echo "\$REGISTRY_PASSWORD" | docker login "$REGISTRY_HOST" -u "\$REGISTRY_USERNAME" --password-stdin                    docker build -f Dockerfile -t $REGISTRY_HOST/$IMAGE_TAG_MODEL .                    docker push $REGISTRY_HOST/$IMAGE_TAG_MODEL                    kubectl  --kubeconfig=/etc/deploy/kubegpu set image deployment/$CONTAINER_NAME $CONTAINER_NAME=$REGISTRY_HOST/$IMAGE_TAG_MODEL                    kubectl  --kubeconfig=/etc/deploy/kubegpu rollout status deployment/$CONTAINER_NAME                  """                }              }            }          } catch (Exception e) {            throw e          }        }      }    }  }}建立镜像时使用的Dockerfile文献如图所示,引用 的根底镜像ollama-with-gpt-oss的制作办法拜见笔者的那篇文章下载体会了一下OpenAI号称脚机也能运行起去的启源年夜模子gpt-oss-20b。FROM reg.xxx.com:9088/base-images/ai-code-review/ollama-with-gpt-oss:latestCMD ["serve"]第两步编辑代码检查年夜模子法式

简朴道一下AI代码检查支流程的施行逻辑:step1 初初化取设置导进模块:requests, argparse, os, sys, time, base64, json, functools 等。沉写 print 函数,使其主动革新输出。设置GitLab API:设置Ollama 当地模子:界说体系提醒 SYSTEM_PROMPT,用于指导模子施行代码检查。step2:  剖析启用号令通报的Gitlab兼并工作参数那些参数去自于gitlab webhook拉收的兼并工作,前面盘问兼并恳求变动文献的时候要用到。使用 argparse 获得号令止参数:--project-id:GitLab 名目 ID--project-name:GitLab 名目称呼--mr-iid:Merge Request IIDstep3 获得 MR变卦 文献挪用 GitLab API /merge_requests/{mr_iid}/changes 获得 MR 的文献变动列表。过滤文献, 以下那些文献没有会被检查:前去契合前提的文献列表。step4 获得变动文献实质挪用 GitLab API /merge_requests/{mr_iid} 获得源分收名,用于后绝获得兼并源分收改动文献残破实质。挪用 GitLab API /repository/files/{file_path} 获得窜改文献残破实质。文献实质为 Base64 编码,函数解码成 UTF-8 字符串前去。step 5 挪用 gpt-oss:20b 模子评审代码建立 messages:挪用 Ollama 当地模子交心 chat.completions.create 天生检查定见。前去模子输出文原。step6 提接 MR 批评挪用 GitLab API /merge_requests/{mr_iid}/notes 提接批评。将 AI 模子天生的检查成果动作 MR 批评公布。import requestsimport argparseimport osimport sysimport timeimport base64from openai import OpenAIimport jsonimport functoolsprint = functools.partial(print, flush=True)
==== GitLab API 设置 ====GITLAB_API = "https://git.xxx.com/api/v4"PRIVATE_TOKEN = "gitlab-token"# ==== Ollama 当地 API 设置 ====OLLAMA_MODEL = "gpt-oss:20b"OLLAMA_CLIENT = OpenAI(    api_key="ollama",    base_url="http://年夜模子效劳对于中表露的IP:端心/v1")SYSTEM_PROMPT = """请您饰演一个资深的华文代码检查大师,晓得 Web 前端、Python 战 Java 编程语言,请用助尔阐发上面代码中可以存留的逻辑毛病、功用成就或者欠好的编码气势派头。假设有成就,请用以下的格局输出检查成果:#####成果 1-成果 描绘: xxx- 改正倡议: xxx#####成果 2-成果 描绘: xxx- 改正倡议: xxx...请求:-不断 使用华文答复,假设一个成就也查抄没有进去,请复兴 "nice" 字样,没有要输出所有过剩的字符-成果 描绘战改正倡议要冗长,只管掌握正在 100 字之内。-假设 有多个成就,请根据成就严峻水平从下到高排序。"""# ==== GitLab API 启拆 ====def get_mr_changes(project_id, mr_iid,project_name):    print(f"📂 获得名目 {project_name}  MR-{mr_iid} 的变动文献...")    url = f"{GITLAB_API}/projects/{project_id}/merge_requests/{mr_iid}/changes"    headers = {"PRIVATE-TOKEN": PRIVATE_TOKEN}    resp = requests.get(url, headers=headers)    resp.raise_for_status()    changes = resp.json()["changes"]    # 许可检查的文献范例    allowed_extensions = (        '.vue', '.tsx', '.ts', '.js', '.css', '.less', '.scss',         '.py', '.pyi', '.ipynb', '.yaml', '.yml', '.ini', '.toml', '.json', '.env',        '.java', '.kt', '.xml', '.properties', '.gradle'    )    def is_only_whitespace_changes(diff_text: str) -> bool:        """鉴别 diff 可否只包罗空格、缩退、空止或者正文变更"""        if not diff_text.strip():            return True        diff_lines = [            line[1:]  # 来失落前缀 '+' 或者 '-'            for line in diff_text.splitlines()            if line.startswith(('+', '-')) and not line.startswith(('+++', '---'))        ]        for line in diff_lines:            stripped = line.strip()            #假设 有非空、非正文的代码,则前去 False            if stripped and not (stripped.startswith("//") or stripped.startswith("#") or stripped.startswith("/") or stripped.startswith("") or stripped.startswith("/")):                return False        return True    filtered_changes = []    for change in changes:        file_path = change.get("new_path") or change.get("old_path", "已知文献")        normalized_path = file_path.replace("\", "/")        # 1. 简略文献        if change.get("deleted_file", False):            print(f"跳过简略文献: {file_path}")            continue        # 2. 主动天生文献        if change.get("generated_file", False):            print(f"跳过主动天生文献: {file_path}")            continue        # 3. 沉定名但是实质出变        if change.get("renamed_file", False) and not change.get("diff", "").strip():            print(f"跳过仅沉定名无实质变更的文献: {file_path}")            continue        # 4. 非许可扩大名        if not file_path.endswith(allowed_extensions):            print(f"跳过非代码文献: {file_path}")            continue        # 5. 前端文献需正在 src/ 下        if file_path.endswith(('.ts', '.tsx', '.js', '.css', '.less', '.scss', '.vue')):            if not normalized_path.startswith("src/"):                print(f"跳过 src 目次中的前端文献: {file_path}")                continue        # 6. Python 文献需正在 app/ 下        elif file_path.endswith(('.py', '.pyi', '.ipynb')):            top_level_dir = normalized_path.split(os.sep)[0]            if top_level_dir in {"tests", "scripts"}:                print(f"跳过顶层目次 {top_level_dir} 中的 Python 文献: {file_path}")                continue        # 7. 只改空格/缩退的改正        if is_only_whitespace_changes(change.get("diff", "")):            print(f"跳过仅格局调解的文献: {file_path}")            continue        # 8.疏忽 年夜文献,那里假定 diff 字符串少度超越 10KB 的文献被觉得是年夜文献        if change.get("diff") and len(change["diff"].encode("utf-8")) > 10  1024:            print(f"跳过年夜文献(>{10}KB): {file_path}")            continue        filtered_changes.append(change)    return filtered_changesdef get_mr_source_branch(project_id, mr_iid):    # print(f"🔍 获得名目 {project_id} 的 MR {mr_iid} 的源分收...")    url = f"{GITLAB_API}/projects/{project_id}/merge_requests/{mr_iid}"    headers = {"PRIVATE-TOKEN": PRIVATE_TOKEN}    resp = requests.get(url, headers=headers)    resp.raise_for_status()    return resp.json()["source_branch"]def get_file_content(project_id, file_path, ref,file_total,file_index):    print(f"\n\n📄 获得第 {file_index}/{file_total} 文献 {file_path} 的实质 ...")    url = f"{GITLAB_API}/projects/{project_id}/repository/files/{requests.utils.quote(file_path, safe='')}"    headers = {"PRIVATE-TOKEN": PRIVATE_TOKEN}    params = {"ref": ref}    resp = requests.get(url, headers=headers, params=params)    if resp.status_code == 200:        file_info = resp.json()        content = base64.b64decode(file_info["content"]).decode("utf-8", errors="ignore")        print(f"✅ 胜利获得第 {file_index}/{file_total} 文献 {file_path} 文献实质,巨细: {len(content)} 字符")        return content    else:        raise Exception(f"获得文献实质失利: {file_path} (status={resp.status_code})")def post_mr_co妹妹ent(project_id, mr_iid, body):    url = f"{GITLAB_API}/projects/{project_id}/merge_requests/{mr_iid}/notes"    headers = {"PRIVATE-TOKEN": PRIVATE_TOKEN, "Content-Type": "application/json"}    resp = requests.post(url, headers=headers, json={"body": body})    resp.raise_for_status()    return resp.json()# ==== 间接挪用 Ollama 模子 ====def call_ollama_model(code_text, last_review=None):    print("🤖 开端挪用 Ollama 模子截至代码检查...")    messages = [{"role": "system", "content": SYSTEM_PROMPT.strip()}]    if last_review:        messages.append({            "role": "user",            "content": f"上一次代码检查的成果反应是:\n{last_review}\n请按照那些反应改良此次代码检查。"        })    messages.append({        "role": "user",        "content": f"如下是待评审代码实质: \n{code_text} \n请给出代码检查成果。"    })    # print("开端代码评审, messages 实质以下:")    # print(json.dumps(messages, ensure_ascii=False, indent=2))    try:        print("⏳ 在等候模子照应...")        response = OLLAMA_CLIENT.chat.completions.create(            model=OLLAMA_MODEL,            messages=messages,            max_tokens=8192,            temperature=0.3        )        print("✅ 支到模子照应")        # print("交心残破照应:", response)        # print(f"评审定见...{response.choices[0].message.content}")        return response.choices[0].message.content.strip()    except Exception as e:        print(f"挪用模子非常: {e}")        return f"模子照应失利: {e}"# ==== 支流程 ====def main():    parser = argparse.ArgumentParser(description="AI 代码评审 for GitLab MR(Ollama曲连版)")    parser.add_argument("--project-id", required=True, help="GitLab 名目ID")    parser.add_argument("--project-name", required=True, help="GitLab 名目称呼")    parser.add_argument("--mr-iid", required=True, help="Merge Request IID")    args = parser.parse_args()    print(f"🎯顺序 启用, {args.project_name} 名目开端施行代码检查任务...")    # print(f"📋 参数设置 - 名目ID: {args.project_id}, MR IID: {args.mr_iid}")    if not PRIVATE_TOKEN:        print("请树立情况变质 GITLAB_PRIVATE_TOKEN", file=sys.stderr)        sys.exit(1)    try:        changes = get_mr_changes(args.project_id, args.mr_iid,args.project_name)        file_total=len(changes)        print(f"📋 同找到{file_total} 个契合检查前提的变动文献")        source_branch = get_mr_source_branch(args.project_id, args.mr_iid)    except Exception as e:        print(f"获得疑息失利: {e}", file=sys.stderr)        sys.exit(1)    if not changes:        print("无变动实质,参加。")        sys.exit(0)    for file_index, change in enumerate(changes, 1):        file_path = change.get("new_path", "已知文献")        try:            full_content = get_file_content(args.project_id, file_path, source_branch,file_total,file_index)        except Exception as e:            print(f"获得残破实质失利: {e}", file=sys.stderr)            continue        prompt = f"{file_path} 文献的残破代码实质以下:\n\n{full_content}"        review = call_ollama_model(prompt)        if not review.strip() or review.strip() == "nice" or review.startswith("模子照应失利"):            continue        co妹妹ent_body = f"🔍 AI 检查倡议 - 文献 </span><span class="code-snippet__string"><span class="code-snippet__subst">{file_path}</span></span><span class="code-snippet__string">\n\n{review}"        try:            post_mr_co妹妹ent(args.project_id, args.mr_iid, co妹妹ent_body)            print(f"✅ 已经胜利写进 第 {file_index}/{file_total} 文献 {file_path} 的评审批评。")            time.sleep(1)        except Exception as e:            print(f"写批评失利: {e}", file=sys.stderr)    print("统统文献评审完毕。")if name == "main":    main()那里重心道一下挪用当地年夜模子的进参战照应字段寄义,因为那一齐常识平常打仗的比力少。挪用当地年夜模子进参数分析:
脚把脚学您完毕用AI年夜模子干代码检查w3.jpg
messages字段分析
脚把脚学您完毕用AI年夜模子干代码检查w4.jpg
照应字段分析:
脚把脚学您完毕用AI年夜模子干代码检查w5.jpg
代码检查类任务:举荐 temperature=0.1~0.3,更松散、更少梦想;自由天生任务:能够调下 temperature 战 top_p;第三步 编辑Jenkins Job运行年夜模子逻辑

Stage1:制作代码检查支流程镜像

目标:建立并拉收 AI 代码检查支流程的 Docker 镜像。施行过程:使用 withCredentials 获得 Docker 堆栈登录账号战暗码。树立镜像标签:REVIEW_IMAGE_TAG = "base-images/ai-code-review:review-${CODE_VERSION}"。加入名目目次 co妹妹on-tools/ai-code-review/ai-review-main。查抄镜像可否已经存留:用 curl 恳求 Docker Registry 的 manifests API,HTTP 前去码 200 暗示镜像存留。假设镜像没有存留:(1) 登录 Docker Registry; (2) 建立镜像 Dockerfile.review; (3)拉收镜像到堆栈。Stage 2:AI 当地年夜模子代码检查

目标:运行 AI 代码检查剧本对于 MR 实质截至主动检查。施行过程:使用 docker.image(...).inside 启用容器情况,运行以前建立的 REVIEW_IMAGE_TAG 镜像。正在容器内乱施行剧本: 参数从 webhook JSON 中获得。    cd /app    python ai_review.py --project-id=xxx --project-name=xxx --mr-iid=xxx3.获得剧本输出 result,输出 到 Jenkins 掌握台。 4.简朴检测毛病:pipeline {  agent { label 'jenkins-runner-1' }  environment {    REGISTRY_HOST = 'reg.xxx.com:9088'    CONTAINER_NAME = 'codellama-review'  }    stage('制作代码检查支流程镜像') {      steps {        script {          try {            withCredentials([              usernamePassword(                  credentialsId: 'REGISTRY',                  usernameVariable: 'REGISTRY_USERNAME',                  passwordVariable: 'REGISTRY_PASSWORD'              )            ]) {              env.REVIEW_IMAGE_TAG = "base-images/ai-code-review:review-${env.CODE_VERSION}"              dir('co妹妹on-tools/ai-code-review/ai-review-main') {                def imageReviewExists = sh(                  script: "curl -s -o /dev/null -w \"%{http_code}\" -u $REGISTRY_USERNAME:$REGISTRY_PASSWORD https://$REGISTRY_HOST/v2/${REVIEW_IMAGE_TAG.split(':')[0]}/manifests/${REVIEW_IMAGE_TAG.split(':')[1]}",                  returnStdout: true                ).trim() == '200'                //假设 镜像没有存留,则建立并拉收                if (!imageReviewExists) {                  sh """                    echo "\$REGISTRY_PASSWORD" | docker login "$REGISTRY_HOST" -u "\$REGISTRY_USERNAME" --password-stdin                    docker build -f Dockerfile.review -t $REGISTRY_HOST/$REVIEW_IMAGE_TAG .                    docker push $REGISTRY_HOST/$REVIEW_IMAGE_TAG                  """                }              }            }          } catch (Exception e) {            throw e          }        }      }    }    stage('AI当地年夜模子代码检查') {      // when() {      //   expression { return env.WEBHOOK_JSON_object_attributes_iid == '218' }      // }      steps {        script {          docker.image("${REGISTRY_HOST}/${REVIEW_IMAGE_TAG}").inside {            script {              env.PROJECT_NAME = env.WEBHOOK_JSON_project_http_url.split('/')[-1].replace('.git', '')              def exitCode = sh(script: """                  cd /app                  python ai_review.py \                    --project-id=${WEBHOOK_JSON_project_id} \                    --project-name=${PROJECT_NAME} \                    --mr-iid=${WEBHOOK_JSON_object_attributes_iid}              """, returnStatus: true)              if (exitCode != 0) {                error('AI Review 剧本施行非常,检测到毛病疑息')              }            }          }        }      }    }  }}步调1使用的Dockerfile.review界说以下所示:主要是装置Python,和ai_review.py所用到的依靠,复造主文献到容器,  那里要留神一下誊写挨次, 因为前三步没有会变,第四步经常变革,  假设把第四步写正在第三步前面,会构成屡屡ai_review.py实质有变动时,皆从头下载Python名目依靠。
根底镜像:Python 3.10 粗简版FROM python:3.10-slim# 树立事情目次WORKDIR /app# 先装置依靠,使用慢存RUN pip install --no-cache-dir openai requests# 复造名目文献到镜像COPY ai_review.py /app/
四步 设置Webhook

要共时正在Jenkins Job战Gitlab中设置webhook。Gitlab中的webhook会将各类git操纵工作数据拉收过去, Jenkins Job会对于拉收过去的工作截至监听处置正在Jenkins中树立webhook重心配:Post content parameters变质merge_state,WEBHOOK_JSONToken变质,那里设置成名目名Optional filter 对于webhook工作截至过滤
脚把脚学您完毕用AI年夜模子干代码检查w6.jpg
正在Gitlab中设置webhook只以是要先正在Jenkins Job中设置webhook,是因为正在Gitlab中设置webhook需要的二个参数:url战Secret 令牌,去自于Jenkins Job中的树立。url 挖写http://JENKINS_URL/generic-webhook-trigger/invokeSecret 令牌 挖写正在Jenkins Job webhook中设置的token
脚把脚学您完毕用AI年夜模子干代码检查w7.jpg
第五步 运行尝试

尝试办法: 鉴于dev分收创立一个feat分收,成心 复造一个函数, 改个名字,正在Gitlab 界里倡议背dev分收的兼并恳求,瞅瞅年夜模子可否评审出代码重复。成果使人趁心,年夜模子公然查抄出了重复的代码。尔瞅了一下年夜模子的评审倡议, 比年夜大都野生检查更专科,更详尽。

最初

使用年夜模子干了一件能发生代价的工作, 心里颇有充分感,那时间花的很值。假设您的公司也筹算完毕AI年夜模子代码检查功用, 这您瞅尔的文章, 可算是找对于人了,能让您少走一点儿直路,特别是年夜模子的挑选取下载这一齐。出事多走走挖金,启卷无益。转载::https://juejin.cn/post/7537975830226862095
您需要登录后才可以回帖 登录 | 立即注册 qq_login

本版积分规则

发布主题
阅读排行更多+
用专业创造成效
400-778-7781
周一至周五 9:00-18:00
意见反馈:server@mailiao.group
紧急联系:181-67184787
ftqrcode

扫一扫关注我们

Powered by 职贝云数A新零售门户 X3.5© 2004-2025 职贝云数 Inc.( 蜀ICP备2024104722号 )