大模型方案之初尝试--基于RAG检测工程代码问题
由于大模型自身固有的制约因素,大模型方案核心在于解决如何筛选工程中的多线程代码,然后让大模型扫描这些筛选出来的代码,分析其中的多线程问题。
初期尝试是采用RAG,尝试筛出工程中的多线程代码。
一、RAG概述
RAG是Retrieval Augmented Generation的英文缩写,中文名为检索增强生成,是一种为Prompt提供相应上下文的检索技术,包含构建知识库和查询两个阶段,图1描述了RAG的技术原理。

图1 RAG原理
l 构建知识库
知识库用于存储一个任务所需要了解的背景知识,比如需要了解Spring、MyBatis、Redis、Hibernate等相关框架的知识,因为不是每个大模型在训练时,都会学习过这些框架的充足知识,在不对大模型进行微调的情况下,可通过RAG技术增强大模型对这些框架的了解。
构建知识库的方法,将承载知识的文件(Document),以一定的规则切分(Splitter)为一个一个的小段(Chunk),然后将每一个小段(Chunk)经由嵌入(Embedding)模型,映射为一个向量(Vector),随后将(Chunk, Vector)组对存入向量数据库,这样就完成了知识库的构建。
注意,RAG的核心技术,或者说是前提假设,向量能够反应对应文本的语义,相似的的文本,它们对应的向量间的距离是相近的,相反不同语义的文本,其所对应的向量间的距离是比较大的。
l 查询
查询阶段,用户输入一个query,RAG会将该query文本经由嵌入(Embedding)模型,映射为一个向量,该向量反映query文本的语义,随后该向量会去匹配知识库里的所有知识,匹配方法是计算每条知识的向量,与该查询向量的相似度,然后返回相似度最高的前k条知识的文本,这k个文本会被组装到最终喂给大模型的Prompt,作为其上下文存在,提供给该query所需要的相关背景知识,从而提高大模型生成结果的质量。
二、RAG筛选多线程相关代码
第一节已经说明了为什么要筛选多线程相关代码,RAG只不过是一种代码筛选的方式,图2展示了RAG筛选相关源码方案的主要流程。

图2 RAG筛选相关源码方案
l 构建知识库
将要扫描多线程问题的工程代码,作为任务的背景知识,以一定规则将工程源码切分为一个一个的chunk,再将chunk经由嵌入模型映射为向量vector,并将(vector, chunk)存入向量数据库,以此完成知识库的构建。
如何切分源码和使用何种嵌入模型,是构建知识库的核心,另外,由于算力短缺,只能在其他团队部署好的Dify平台尝试本方案,所以也受到Dify平台参数配置的制约。
-
- 切分粒度
本方案考虑的出发点是从工程总体角度出发,去发现多线程问题,这意味着更多地是要从文件与文件、类与类、多个函数之间去发现问题,所以本方案对工程代码文件的切分粒度,尽量保持在类(Class)这
个级别,Chunk的最大长度为4000,4000为Dify平台限制的最大值。
-
- 分隔符
由于使用Dify平台做RAG,少了非常多的可定制性,所以采用一种非常简单的策略,采用“--------------------- chunk seperator ---------------------”作为每个chunk的分隔符,编写脚本将该分隔符自动追加在每个
代码文件的末尾,这样Dify就可以根据、该分隔符自动切分代码。
-
- 嵌入模型选择
采用智源的bge-m3模型。
l 查询
查询阶段,将用户query直接与向量数据库进行相似度匹配,筛选出相似度最高的前k个代码段(chunk),该阶段的所有流程,均由Dify平台自动完成,我们所需要做的是配置好相关参数参数。
-
- k值
该参数告诉RAG系统,返回匹配度最高的前k个代码段(chunk),受Dify平台制约,k值最大取值为10,本方案设置k值为10。
-
- Rerank重排序
本方案采用对RAG结果进行重排序,以提高RAG返回结果的质量,负责对RAG结果重排的大模型为bge-reranker-v2-m3。
-
- 推理大模型选择
采用DeepSeek蒸馏过的Qwen-32B模型DeepSeek-R1-Distill-Qwen-32B。
三、本方案的效果
采用一个包含3个java文件的demo进行效果展示,demo构建的知识库如图3所示。

图3 方案demo知识库
demo共计3个样本,其中1个非问题,2个是问题,如表1所示。
表1 测试样本

图4为测试效果截图,2个是问题的样本,大模型回基本正确,1个非问题的分析有点出入。

图4 测试效果截图
图4对应的完整回答文本如下:
|
好,我现在需要分析用户提供的代码,找出其中的多线程问题。首先,我会逐一查看每个类,寻找与多线程相关的关键词和潜在问题。 XXXXServiceImpl类:
使用了CompletableFuture和Executor来执行异步任务。 在executeReassign方法中,使用CompletableFuture.runAsync来异步执行reassignLotService.reassignLotByLock方法。这里需要注意的是,如果reassignLotService的方法内部没有正确处理并发,可能会导致数据不一致。 在waitAllReassignComplete方法中,遍历所有CompletableFuture并调用join()。如果其中一个任务失败,join()会抛出异常,但当前代码只是捕获异常并记录日志,没有重试或回滚机制,可能导致部分任务执行失败但整体返回成功,引发不一致。 checkDifferentUpgradeTags方法中,多个线程可能同时修改reassignRes列表,但由于使用的是CopyOnWriteArrayList,所以线程安全,没有问题。 YYYYOpenController类:
使用了CompletableFuture.runAsync来异步执行批量Hold操作,但没有处理可能的并发问题,比如多个线程同时修改相同的数据,可能会导致竞态条件。 batchHoldByAsync方法中,异步任务直接提交到executor,但没有控制任务的数量和顺序,可能导致资源耗尽或处理顺序混乱。 HandlerManager类:
没有明显的多线程问题,主要是注册和处理逻辑,但handlerMap的访问没有同步机制,如果多个线程同时注册或处理,可能会导致不一致。 总结一下,主要的多线程问题集中在异步任务的处理和资源管理上,特别是在结果处理和异常管理方面可能存在问题。 在分析提供的代码后,发现了以下几个潜在的多线程问题:
1. XXXXServiceImpl 类 问题:在 executeReassign 方法中使用了 CompletableFuture.runAsync 来执行异步任务。虽然每个任务都提交到线程池中,但如果 reassignLotService.reassignLotByLock 方法内部没有正确处理并发,可能会导致数据不一致。 分析:reassignLotService.reassignLotByLock 方法的实现未在上下文中提供,但如果没有正确的锁机制或事务管理,可能会导致多个线程同时修改相同的数据。 2. YYYYOpenController 类 问题:在 batchHoldByAsync 方法中,使用了 CompletableFuture.runAsync 来异步执行批量Hold操作。然而,该方法没有处理可能的并发问题,例如多个线程同时处理相同的数据,可能会导致竞态条件。 分析:异步任务直接提交到 executor,但没有控制任务的数量和顺序,可能导致资源耗尽或处理顺序混乱。 3. HandlerManager 类 问题:handlerMap 的访问没有同步机制。如果多个线程同时调用 register 或 handle 方法,可能会导致 handlerMap 的状态不一致。 分析:在 register 和 handle 方法中,handlerMap 的访问是无锁的,这在多线程环境下可能导致竞态条件。 总结 以上代码中存在潜在的多线程问题,特别是与异步任务的处理和共享资源的访问有关。建议在多线程环境下使用适当的锁机制或同步工具来确保数据的一致性和线程安全。
引用 XXXXServiceImpl.java.txt YYYYController.java.txt HandlerManager.java.txt |
四、本方案的问题
经过一系列尝试,本方案的问题主要体现在代码段召回率低,简单说就是用户要查询代码中的多线程问题,返回的代码段相当一部分与多线程无关,其与用户指令文本的相似度也偏低,分值普遍在0.5以下,相似度取值[0, 1],分值越高表示越相似,反之则越不相似。
尝试过中文提示词、英文提示词,简单提示词,带约束增强、FewShot的提示词,代码段召回效果均不理想。
目前推测有2个原因导致代码段召回效果不好:
l 嵌入模型选择不当
bge-reranker-v2-m3对代码语义的理解或许不适合。
l 代码文件切分策略
由于切分最大长度为4000 token,导致较长的文件,会被简单拆分为多个段(chunk),或许会将某些完整的多线程场景拆成2个以上的段(chunk),导致多线程场景不完整。

浙公网安备 33010602011771号