AI Codereview 到 Codereview Agent 的再次升级
背景
在之前的原创笔记《Gitlab 代码发布中的 AI Codereview 探索》已经介绍了在发布流程中接入了 AI Codereview 的探索实践,在使用 AI Codereview 的过程中取得了一定的效果,收到了不少好评,但是也接受到一些建议和问题反馈。其中反馈最多的就是有时候的审核意见和代码变更不相符,为什么会出现这种现象呢?其实原因很简单,就是chatGPT是无状态的,它并不会记忆用户提交的内容,同时在没有上下文的时候,它会根据你提交的内容以及自身训练数据去假设一些上下文,最终得出它的回复。如何去解决这个问题,让Codereview的质量再次提升是本文接下来讨论的重点。
回顾
审核效果
目前 AI Codereview 给出【变更总结】、【代码BUG】、【安全漏洞】、【代码评价】、【代码优化】五个方面的意见。
整体架构
效果差异分析
之前的原创笔记中我们提到`上下文的填充`是代码Codereview质量好坏的关键,本文开头也提到chatGPT在上下文不足的时候会给出一些假设性的回应。因此如何让变更代码的上下文更加准确是提升Codereview质量的关键。
左边的代码块因为变更的代码定义和使用都在同一次的提交中,所以代码上下文充分能够获得较好的审核效果。右边的代码块因为只有调用函数的代码,但是却没有这个函数的定义,在提交给chatGPT审核时,大模型将会根据函数名进行一些假设,导致最终的结果和提交的代码不相符的问题出现。
核心关键
解决以上问题的核心关键就是:如何获得变化代码块的上下文,并且将他们一起提供给chatGPT进行代码审核。想要解决这个问题,需要先了解整体审核的过程:
作为使用方,需要通过role: system设置chatGPT的角色,然后再role:user中设置代码审核的要求,然后将组装好的内容通过API提交给chatGPT,最终会得到审核的结果。增加代码上下文后,整体的审核过程如下图所示:
获取代码上下文的方式:
将变更代码块涉及的文件内容全部作为上下文,但是会导致token消耗太多,无效的代码太多效果反而不好,同时并没有办法提供函数的定义处的代码块。
建立索引树 + 语义分析的办法,类似实现代码编辑器的Go definition,但是实现难度太大,同时每种语言的实现方式还不一样。
代码库即是知识库 + Embedding,将仓库中的代码作为知识库,将代码做Embedding,然后用提交的代码去匹配相似的内容,获取函数的定义模块。
代码块Embedding
要对代码块进行Embedding,首先需要对代码进行切割,通过测试使用 RecursiveCharacterTextSplitter 进行分割,同时设置 chunk_size=500, chunk_overlap=100 能够获得比较好的分割效果。
将分割后的代码块进行Embedding的计算,本文中使用的是用 AzureOpenAIEmbeddings 的 text-embedding-ada-002 模型进行代码块的Embedding,最终将计算后的代码块放入向量数据库 Chroma 中。
测试用例
为了测试上述的方法是否能真的找到相关联的代码上下文,我们使用一个测试用例来测试这部分的功能。
首先在300行代码文件中选取两个函数 TestBanPrefix ,并且在 TestBanPrefix 中埋下一个坑,当 loginAccount = "test" 的时候会导致panic。再看右边的代码,简单来说根据提供的代码在向量数据库中检索相似的代码,k=3 表示检索最相似的3个代码块。
Embedding 效果
上图显示的就是检索的结果,效果真的很好,完全将 TestBanPrefix 函数的定义找到了。
基于代码Embedding的审核效果
上面代码中我们构建了一个简单的代码审核Prompt,让chatGPT去审核提供的那3行代码块。请看第1点的审核结果,它发现了函数中埋的坑,并且提醒我们不能传入loginAccount = "test",否则会导致panic,完美!
如何给 chatGPT 自主权
通过以上Embedding代码块的然后进行检索方式已经实现了代码审核的RAG流程,但是这样存在一个问题,就是每次提交的代码默认都会进行一次向量数据库检索获取关联的代码块。但是对于新增代码比较多的情况,变更的代码提供的上下文已经足够多了,没有必要再去向量数据库中进行检索了。因此,如何给chatGPT自主选择权,让它自己选择哪些代码需要获取上下文,哪些代码不需要获取上下文,会让Codereview 的整体流程更加高效。
使用Function Calling
为了给 chatGPT 自主权,可以使用 Function calling 的模式。简单来说就是封装好一些函数作为工具,然后提供给chatGPT,让它决定是否要执行这些函数,但是切记这些函数不是chatGPT自己执行,而是它根据你的描述给你提供相应的函数参数,然后用户自己根据参数本地执行工具函数,再将结果返回给chatGPT,完成整个流程。基于我们的Codereview场景,流程如下:
由上图可知,我们自己封装了一个 search_code_relavance 的函数,参数是 code_chunk 也就是代码块。直接上代码:
def search_code_relevance(code_chunk):res = db.similarity_search(code_chunk, k=1)codes = "\n".join([r.page_content for r in res])return codesclass SearchCodeRelevanceArgsSchema(BaseModel):code_chunk: strsearch_code_relevance_tool = StructuredTool.from_function(name="search_code_relevance",description="Search code relevance. Use this tool when you need to search for code relevance.",func=search_code_relevance,args_schema=SearchCodeRelevanceArgsSchema,)
有了这个 search_code_relavance 的函数以及 Function calling 的调用模式,我们就能将原本的 AI Codereview 工具升级成 Codereview Agent,它会根据提供的代码自主选择是否要检索相关代码。
简单构建一个 Agent 来测试下这个效果,代码如下:
重点关注红框的三个部分,这里构建了一个agent,并完成代码的Codereview过程。同时记录了整个调用链的效果,如下图:
可以从上图的调用链中发现,chatGPT发起了2次调用search_code_relavance函数的要求,并且都返回了相应的函数定义,并且非常准确。基于准确的代码上下文,chatGPT也给出了符合预期的审核意见。
总结
本文从 Codereview 场景出发,介绍了从最简单的直接调用 API 到使用 RAG 增加代码块的上下文,最终到使用 Function Calling 构建 Codereview Agent,希望给需要构建自己 Agent 的同事提供参考和借鉴。
三七互娱技术团队
扫码关注 了解更多