
在当今的研发领域,人工智能正迅速地在代码补全、对话系统、文本到代码转换等多个应用场景中重新定义我们的工作方式。在 InfoQ 举办的 QCon 全球软件开发大会(北京站)上,蚂蚁集团技术专家牛俊龙分享了“智能代码助手 CodeFuse 的架构设计与实践”,他介绍了本地核心服务的设计策略,通过实际案例展示如何结合本地与远程数据资源,进一步优化智能代码助手的 AI 能力。此外,他还分享了产品在落地过程中的宝贵经验和教训,并展望 CodeFuse 的未来发展路径。
内容亮点
解密 CodeFuse 智能代码助手的设计理念与构建策略
详细剖析如何在多元化业务场景中实现高效数据处理的技术方案
将于 10 月 23 - 25 召开的 QCon 上海站设计了「AI4SE:软件研发提质增效实践」专题,将探讨结合需求管理工具,构建业务知识工程,并将业务知识贯穿到开发,测试和运维全流程;探讨如何设计和选择“人机协作”的辅助编码工具,评测并度量实际效果以及如何选择合适的运维场景,结合 MCP ,构建动态规划,持续学习的运维 AI Agent 等等话题。敬请关注。
以下是演讲实录(经 InfoQ 进行不改变原意的编辑整理)。
Al 在研发领域带来的变化
近期,我关注了国内外一些数据报告,其中两份报告引起了我的注意。第一份是《2024 年中国 AI 大模型产业发展与应用研究报告》。该报告数据丰富,从中可以提炼出两个关键信息。首先,从模型数量来看,近一两年发展迅猛,从 2023 年到 2024 年,模型数量增长了约 200%。其次,从价格方面来看,百万 token 的价格从 2023 年的 300 元降低到 2024 年的 1.5 元,价格更加趋于合理化,使得更多人能够使用。报告还提到,从发布和备案的大模型中,垂直领域的大模型按行业分类,排名前五的是互联网、金融、医疗、教育和政务,其中互联网行业位居第一。这一数据表明,在互联网行业更适合大模型的落地应用。
另一份国外报告的数据来源于 Dora Research 2024。Dora 是一个在研发效能领域具有影响力的研究团队。该报告提到,他们调研了 39,000 多名不同级别的开发者,基于 AI 的任务进行了排序,其中排名第一的是代码编写,其次是理解文档、代码解释等。在生产力方面,75% 的受访者表示 AI 能够提高他们的生产力,超过三分之一的人表示生产力提高了 50% 以上。从这些数据可以看出,AI 已不再是一个高不可攀的高科技产品,而是离我们越来越近,甚至在研发领域已成为不可或缺的工具。

接下来,我想介绍一下 CodeFuse 的落地现状。从产品矩阵来看,我们为开发者提供了 CodeFuse 智能助手,这是一款常见的 IDE 插件。此外,我们还为研发团队提供了 CodeFuse 研发助手的构建平台,并且由于其部署在内部,我们还为其他工具平台提供了 CodeFuse API,以支持其他平台的 AI 化和 AI 能力。因为主要面向开发者,CodeFuse 在开发的各个阶段都能提供多种能力,例如仓库问答、代码补全等。
近期,CodeFuse 也经历了一些新的变化。我整理了其中最主要的三个变化。首先是实时代码修改和预测功能,补全能力是抓住用户核心需求的关键。如果补全能力不佳,就难以吸引用户。实时代码修改和预测功能实际上是对现有补全能力的升级,它不仅支持在光标之后新增代码,还能支持光标前后一段代码的新增、修改和删除。
第二个新能力是 AI Partner,它被定位为智能编程搭档,能够根据需求描述完成编码任务,支持生成和修改多个文件。与补全功能不同,补全是在后台默默辅助用户,而 AI Partner 需要用户主动与其对话。例如,用户在编写代码时,可以通过右侧的自然语言描述,让 CodeFuse 插件生成相关代码。
最后一个新能力是 Text to Code。这一能力是基于用户在开发过程中提出的需求,能够一键改写指定代码,支持重构、简化和注释等功能。它与 AI Partner 也不太一样,因为 AI Partner 的焦点集中在编辑器右侧的对话框,可能会干扰用户的编程思路。为了解决这一问题,我们提供了 Text to Code 功能,用户在编写代码时无需转移视线,只需选中需要修改的代码,并在上方添加自然语言描述,即可根据需求完成代码的生成或修改。

CodeFuse 插件技术架构
在深入介绍 CodeFuse 技术架构之前,我想先与大家探讨一下我们在设计和开发过程中所秉持的理念。因为正是这些理念,为我们后续的系统设计和开发提供了明确的方向和目标。
首先,我们思考的是 CodeFuse 插件的定位。它是一款 AI 产品,而 AI 产品本质上首先是产品,因此产品体验至关重要。如果界面设计不佳,或者功能设计违背用户习惯,那么这款产品肯定无法吸引用户。而作为一款 AI 产品,其核心竞争力在于 AI 能力。我们坚信,AI 能力越强大,就越能吸引用户。那么,如何提升 AI 能力呢?我们从两个方面入手:一是模型能力,二是上下文质量。
模型能力是大家最为熟悉的,它决定了 AI 插件的智能水平和功能的强大程度。然而,仅有强大的模型是不够的。如果输入的上下文质量不高,生成的结果也难以令人满意。因此,上下文质量同样是影响 AI 能力的关键因素。高质量的上下文能够帮助模型更好地理解用户需求,从而生成更符合预期的结果。
基于以上思考,我们在设计 CodeFuse 插件时,将其整体划分为四个模块。第一个模块是 CodeFuse 插件本身。这是用户最直接接触的部分,具备 AI 补全、对话等功能。第二个模块是本地核心服务,它是一个可执行文件。在 Mac 或 Linux 系统上,它是一个二进制文件;在 Windows 系统上,则是一个可执行的 .exe 文件。这个服务的主要职责是提升上下文质量。它会分析本地仓库的数据以及用户在 IDE 中的行为习惯,为 AI 能力提供检索能力。这些检索到的数据会被整合到当前 AI 请求的上下文中。为了实现这一功能,背后有一个索引库,它会实时更新,以确保数据的准确性和时效性。
当一个 AI 请求经过插件和本地核心服务后,并不会直接发送到模型,而是会发送到远程的服务端。服务端会对接收到的 AI 请求进行数据的前后处理,包括数据的拼装和模型的调度。同时,服务端还会进行各种 A/B 实验。服务端背后也有一个索引,但与本地索引不同的是,本地索引专注于当前仓库的数据,而远程索引则用于跨仓库检索。例如,当用户新建一个应用并提出需求时,如果本地仓库中没有相关数据可供检索,服务端会查找与当前仓库主题相关的其他应用中的数据,作为参考并整合到上下文中。

在模型进行推理的同时,插件服务端会对每一个 AI 请求进行大量埋点。这些埋点数据会在后台进行离线计算,分析出正样本和负样本。这些样本会与内部数据结合,用于模型的迭代和训练,以推动模型的进一步发展。新训练出的模型并不会直接上线,而是会经过 CodeFuse 的评测服务。评测服务的主要目的是评估模型能力,为当前的 AI 功能打分。例如,对于补全功能,评测服务会使用从线上梳理出的评测集进行评估,只有当分数超过 60 分时,模型才被允许上线。低于 60 分的模型则需要继续训练。
上线后的模型会通过服务端的各种 A/B 实验进行逐步推广。最初,可能会分配 5% 或 10% 的流量进行测试,每天都会关注模型的效果。如果效果良好,流量会逐步增加;反之,效果不佳的模型则会被下线。通过这种方式,我们确保了 CodeFuse 插件在提供强大 AI 能力的同时,能够持续优化和提升用户体验。
本地核心服务模块详解
补全过程详解
重点介绍一下本地核心服务的设计与实现,这部分的核心目标是提升上下文的质量。要实现这一目标,我们需要从三个关键维度进行考量:数据来源、检索策略以及提示工程。数据来源决定了我们能够为 AI 请求提供哪些补充信息,这些信息可能来自代码仓库、知识库或用户行为记录。检索策略则涉及如何高效地从海量数据中提取相关信息,我们采用了多种检索方式,包括 BM25、向量检索和图检索,同时支持单轮或多轮检索。而提示工程则关乎如何将检索到的数据进行有效拼装,以何种方式呈现给模型,这直接关系到最终的用户体验和效果。这三个维度缺一不可,每一部分都对上下文质量有着显著影响。
本地核心服务的构建基于索引机制。由于服务运行在本地,它主要处理本地仓库的数据,而不涉及跨仓库的内容。具体来说,本地核心服务会遍历整个代码仓库,并将代码拆解为四类信息。首先是仓库级信息,例如仓库的 Git 地址、README 文件内容以及仓库的 Wiki 概要等。其次,对于仓库中的每个文件,我们会记录其功能描述和注释信息。这些信息构成了文件级索引。然而,仅凭文件级索引是不够的,因为在对话或代码补全等场景中,我们通常需要检索代码块级别的内容。因此,我们进一步对文件进行了切分,目前采用两个维度:函数级别和片段级别(trunk level)。函数级别切分能够提供更精准的代码结构信息,适用于大多数编程语言;而片段级别切分则主要用于应对一些非主流语言或脚本语言,这些语言可能无法进行函数级别的解析。此外,片段级别索引还可以作为兜底方案,确保即使在函数级别索引无法满足需求时,仍能提供一定的检索能力。例如,对于 SQL 脚本或执行脚本,片段级别索引能够发挥重要作用。

除了代码相关数据,本地核心服务还会记录用户的行为数据,例如代码补全的历史记录、Git 日志以及 IDE 操作记录等。这些行为数据有助于将用户的行为习惯融入上下文,从而使 AI 能力更加贴近用户的实际需求。
在代码补全场景中,上下文内容主要由相关性、相似性和历史行为三部分构成。相关性是指在补全时提供与当前代码片段直接相关的结构信息。例如,当用户输入“userService”时,如果能明确告知模型对应的“UserService.java”的具体结构,如其中包含“addUser”方法,那么模型就能更准确地进行补全,而不是随意猜测出“saveUser”或“insertUser”等可能并不准确的内容。相似性的作用则是学习用户之前编写代码的逻辑。例如,如果用户在仓库中多次编写了“new User()”后紧接着调用“user.setId”方法,那么在新的代码文件中再次编写“new User()”时,模型会根据之前的行为模式推测用户下一步可能需要调用“user.setId()”方法。历史行为则是基于用户在 IDE 中的操作习惯,例如某些用户可能习惯先编写方法名,然后通过快捷键补全前面的代码。这些行为记录会被 CodeFuse 捕捉到,并在后续的补全中自动应用,从而提高补全的效率和准确性。
在实现这些功能的过程中,我们面临的一个重要挑战是如何在短时间内完成大量的计算任务,以确保补全的响应时间足够短。一般来说,一次常规的补全操作整体耗时控制在 500 毫秒以内完成,超过这个时间,用户就会感受到明显的延迟,从而降低补全的可用性。目前,我们已经将大部分补全操作的响应时间控制在 200 到 400 毫秒之间。整个补全链路中,模型推理通常会占用较多时间,一般需要上百毫秒。为了优化这一过程,我们采用了动态延迟策略。当用户输入字符时,并非每次输入都立即触发补全请求,而是设置了一个延迟时间。这个延迟时间是动态调整的,根据不同用户对时间的感知能力进行优化。例如,有些用户可能在 100 毫秒的延迟下有较高的采纳率,而有些用户则在 30 毫秒的延迟下表现更好。通过动态调整延迟时间,我们能够在保证补全效果的同时,尽量减少用户的等待时间。
除了动态延迟策略,我们还对数据检索部分进行了优化。在第一次尝试时,我们将目标设定为 50 毫秒以内完成检索任务,但实际上我们已经能够将这一时间进一步缩短。在补全的上下文中,相关性检索并非简单地提取导入语句,而是根据光标位置找到与当前代码片段最相关的代码文件。例如,如果光标前有一个“user.setId”语句,我们需要找到“user”所代表的对象,如“User.java”,并提取“User.java”的结构信息。同时,我们还需要对光标前后一定范围内的代码片段与全仓库的代码片段进行相似度对比。然而,这一过程的耗时可能会非常惊人。以一个包含约 2,000 个 Java 文件的仓库为例,按照我们之前提到的索引切分方式,可以切分出约 5~6 万个代码片段。即使是这样规模的仓库,一次相似度计算的耗时也可能超过 100 毫秒,更不用说那些规模更大的仓库了。这曾是我们面临的一个重大难题。
为了解决这一问题,我们从两个维度进行了优化:数量和编码。从数量上,我们结合了用户编写代码的习惯,通过定制规则筛选出与当前代码片段最相似的文件。例如,在编写“UserService.java”时,我们会查找与“UserService.java”路径相似或 import 语句相似的代码文件,并对其进行排序,取前 50 个文件。然后,从这 50 个文件中提取对应的索引片段,并将这些片段放入一个固定长度的集合中,例如 5,000 个片段。在每次补全时,我们只需将光标前后提取的代码片段与这 5,000 个片段进行相似度对比,而不是与整个仓库的所有片段对比。这样可以显著减少数据量,从而提高检索速度。
从编码角度,我们对代码片段进行了进一步优化。由于相似度计算本质上是字符串对比,其速度和资源消耗相对较高,我们将代码片段文本编码为数字数组,通过对比数字数组来加速相似度计算。
在本地核心服务中,我们充分利用了用户端的 CPU 资源。在补全的瞬间,我们会将 5,000 个代码片段的相似度对比任务分配到不同的 CPU 核心上进行并行计算,然后将结果存储在二叉堆中,从中提取前几名的数据用于后续处理。在这个过程中,一旦某个 CPU 完成当前所有相似度对比任务队列,它会尝试从其他 CPU 任务队列中获取新的任务,从而确保 CPU 资源得到充分利用。尽管在用户本地端上计算相似度时对 CPU 资源的消耗非常敏感,但我们在实际运行中发现,整个相似度计算过程的耗时非常短,通常在 20 毫秒以内。在这个时间内,用户端上几乎无法察觉到任何延迟。落地实际效果,CPU 消耗通常小于 3%。

对话流程详解
之前提到的 AI Partner,其实可以简单理解为一个智能编程助手。举个例子,假设我在代码中实现了一个快速排序算法,然后我让 AI Partner 帮我写一个调用这个快速排序的函数。AI Partner 能很快的帮我写出来。当然,这只是个简单的例子,实际的推理过程会更复杂一些,推理完成后会生成一个可直接运行的代码片段。
CodeFuse 的对话和 AI Partner 的实现流程可以分为三个主要步骤:查询处理、信息检索和快速应用。首先,当用户提出一个需求,比如“创建一个用户接口”,查询处理阶段会提取关键词,如“用户”“创建”等,并识别其中的实体,例如“UserService”。经过这一阶段,用户的原始需求会被转化为两份数据:一份是中英文关键词集合,另一份是中英文查询集合。关键词会通过 BM25 算法进行检索,而查询集合则通过向量检索进行处理。检索完成后,会对数据进行去重、重新排序等加工操作,然后将这些数据输入模型进行推理。推理完成后,模型会生成一个相关的函数,比如“createUser”函数,而不是生成整个文件。

接下来会遇到一个问题:这个函数应该插入到当前仓库的哪个位置?为此,我们训练了一个专门的模型,用于判断生成的函数在目标文件中的合适位置。通过这一系列流程,可以完成一些简单的需求。然而,大家也能感受到,对于复杂的业务需求,这种流程还远远不够。例如,如果让用户直接将生成的代码应用于线上环境,大家肯定不敢这么做。那么,问题出在哪里?是模型的问题,还是检索环节出了问题?我们深入思考后发现,人解决这类问题的方式或许可以给我们一些启示。
假设你让一个默认开发者编写一个创建用户接口,他写出来的代码能直接用在你的业务中吗?答案是不能。即使加上一些背景信息,比如告诉他有一个“UserService”,让他基于这个服务编写接口,结果依然不能直接使用。其实,这种表现和模型的现状很相似。既然人也很难解决这个问题,那为什么还要让模型去直接解决呢?我们进一步思考,现在开发者是如何编写出可用代码的呢?
想象一下,当你招聘一个人到公司,让他编写一个创建用户接口,他一开始肯定写不出来,因为他对业务背景和应用基础信息一无所知。如果告诉他这个接口用于一个内部系统,每天只有十几个人使用,他可能会直接实现一个简单的数据库 CRUD 操作。但如果告诉他这个接口将用于一个面向消费者的电商平台,他肯定不会只做简单的 CRUD,而是会考虑是否需要限流、权限校验等其他功能。这说明,人在编写代码时,会基于多维度的信息进行判断,比如业务背景、应用信息等。此外,人还会进行多步骤的规划,而不是拿到需求就直接开始写代码。他会先了解各种信息,然后打开仓库,找到相关的接口和领域对象,思考如何复用现有代码,哪些地方需要更新,最后找到入口函数开始编写代码。这个过程与我们之前提到的流程有明显的区别。

我们发现,人解决问题的方式有两个关键特点:一是多维度的信息收集,二是多步骤的规划能力。基于这些特点,我们尝试设计一个具有自主思考能力的检索系统,以提升 AI 代码生成的能力。
从宏观角度看,当用户提出一个需求时,我们首先让系统进行“思考”,将任务拆解为三个步骤:思考、行动和决策。思考阶段进一步细化,让模型先判断第一步做什么,比如先了解应用的背景信息,然后基于这个信息拆解出多个子查询,如读取应用的 README 或远程检索仓库的 Wiki 等。这些子查询会根据不同的检索条件走到不同的检索流程,然后获取解决子查询问题所需要的数据。

获取数据后,系统会进行决策,判断这些数据是否能满足子查询的需求。如果不能满足,继续检索;如果能满足,将所有子查询的结果汇总,进行简单总结,然后询问模型是否需要下一步操作。如果有下一步,整个流程会重复;如果没有,则结束。相比之前的流程,这个新流程有三个显著特点:一是检索方式从单轮变为多轮;二是从单维度变为多维度;三是将原本单纯的检索流程转变为一个模型与检索相结合的流程。这个流程参考了人类解决问题的方式,让 AI Partner 具备了自主思考的检索能力。
经验与总结
全链路的 A/B 实验
在 CodeFuse 的落地过程中,我们始终致力于提升 AI 效果,这些努力主要通过技术手段来实现。然而,技术手段的优化只是第一步,更重要的是如何判断这些优化是否真正有效。在我们看来,A/B 实验是验证效果的关键环节。我们开展的 A/B 实验不仅局限于模型层面,而是贯穿于整个产品链路,包括插件侧和服务端等多个环节。

以插件侧为例,我们曾对动态延迟时间进行优化。动态延迟时间的调整是通过一个算法小模型实现的,而我们开发了多种算法小模型,并通过 A/B 实验来比较它们的效果,最终选择表现最佳的模型。在服务端,我们也进行了大量 A/B 实验,涉及 prompt 的拼装、任务阶段的组装等多个方面。本地核心服务部分的实验更为复杂,例如代码切分方式和检索策略等。我们曾针对相似度检索进行实验,通常情况下,我们会以光标前后的内容为基础进行检索。然而,通过 A/B 实验,我们发现对于某些编程语言,仅使用光标之前的内容进行检索,然后将检索结果下移一定行数(例如 10 行或 20 行),这种简单的策略调整竟然使采纳率提高了 2%。这充分说明了 A/B 实验在优化检索策略方面的价值。
业务定制化方案
在与业务方合作的过程中,我们也遇到了一些挑战。一方面,同一种能力在不同业务背景下表现存在差异。以代码补全为例,虽然我们内部测试的采纳率可能达到 “80%”,但在与业务方沟通时,有些团队反馈他们的实际采纳率可能只有 “70%” 或 “60%”,而有些团队甚至能达到 “90%”(这里所提及的数字均为模拟案例,不代表真实业务数据)。这种差异的原因在于不同业务场景(如终端开发、数据研发或业务代码编写)的逻辑可能截然不同。另一方面,有些团队有定制化的对话诉求,例如他们需要检索自己业务平台上的数据,而我们的通用检索方案可能无法完全覆盖这些需求。
为了解决这些问题,我们采取了以下措施。首先,针对定制化的检索需求,我们开放了本地核心服务的定制能力。本地核心服务是一个用 Rust 编写的可执行文件,其代码结构分为四层:基座层负责将代码打包成可执行文件;SPI 接口层定义了通用接口,例如代码补全和对话检索的接口;数据层提供数据索引和检索能力;中间层则是 CodeFuse 默认实现的一套检索和索引构建能力。当业务方有定制需求时,我们可以将这些定制能力开放给他们。例如,终端团队如果希望提高 Android 或 iOS 语言的采纳率,他们可以基于自己的业务需求进行检索定制。这种定制化的检索方案能够显著提升上下文质量,从而更好地满足业务需求。

其次,针对用户有定制化检索诉求的情况,我们支持用户创建自定义的助手。用户可以选择合适的模型、系统提示词以及工具(这些工具背后会处理文档切分和检索等任务)。用户还可以上传 PDF 文档或其他文档链接。完成自定义助手的创建后,用户可以在平台上设置权限并发布。这样,内部用户就可以通过 @ 提及的方式,使用这些定制化的检索和对话策略。通过这种方式,我们不仅满足了用户的个性化需求,还进一步提升了 CodeFuse 在不同业务场景中的适用性和灵活性。

未来展望
下图左侧是 OpenAI 提出的 AI 发展方向,我认为其观点确实具有启发性。OpenAI 的理念更多是基于通用场景展开的,而我们团队在思考如何将这些理念具体应用到研发领域,尤其是研发助手或 IDE 插件方面时,提出了一个分阶段的发展思路。

在我看来,研发领域的 AI 应用可以分为三个阶段:AI 辅助、AI 协同和 AI 驱动。在 AI 辅助阶段,我们看到的主要是像代码补全这样的功能。这种能力为开发者提供了便利,但仍然只是辅助性质的,开发者是主导,AI 只是提供一些可能的代码片段或建议。
接下来是 AI 协同阶段,这正是我们目前 CodeFuse 所处的阶段。在这个阶段,AI 不再仅仅是被动地提供帮助,而是像一个“小机器人”一样,在背后默默地支持开发者。当开发者有简单的任务时,可以直接分配给 AI,让它协助完成。例如,AI Partner 就是这种能力的体现,它可以根据开发者的需求生成代码片段,甚至完成一些简单的编码任务。
最后是 AI 驱动阶段,这是我们的长远目标。在这个阶段,AI 将具备一定的自主思考能力,能够像一个独立的开发者一样工作。例如,当提出一个需求时,AI 不仅能够生成代码,还能自己进行测试,并在测试完成后自动提交代码。这种能力将极大地提高开发效率,甚至可能改变软件开发的模式。
目前,CodeFuse 正在努力完善 AI 协同阶段的能力,我们希望通过不断优化和提升这一阶段的功能,为未来迈向 AI 驱动阶段奠定坚实的基础。
嘉宾介绍
牛俊龙,蚂蚁集团技术专家。多年互联网系统研发经验,参与过多个亿级流量的重点系统设计和研发。目前就职于蚂蚁集团 CIO 技术部,致力于研发效能领域的 AI 落地方向,从 0 到 1 设计 CodeFuse IDE 插件的整体系统架构,将 AI 能力运用到补全,对话,TextToCode 等多个功能,为研发人员提供更智能,更便捷的研发体验。
评论