一、智能研发工具的发展
首先来看一下智能研发工具的发展历程和方向。
1. 智能化的发展背景与落地诉求
早期的智能化工具,如 GitHub 的 Copilot 工具,大约在两年半前推出。最初,Copilot 的主要功能是在开发者编写代码时提供自动补全建议。随着时间的发展,其功能逐渐丰富,现在的 Copilot chat 插件支持语言转换、单元测试等多种功能。
随着大模型的发展,所有相关产品都在迅速升级进化。从最初的辅助编码开始,到现在能够与开发者协同工作,我们甚至可以将部分任务完全交给 AI 完成。由代码补全转变为代码生成,不仅是在代码基础上生成代码,更多是在需求的基础上去生成代码,从单纯的写代码转变为服务于整个工程。例如,最近 Copilot 推出的帮用户搭建单元测试环境的功能,已经超越了单纯的代码编写层面。
接下来,将从代码生成的体量、工具职责的变化、效果的优化、能力的提升以及用户体验交互方式上的创新等几个方面来介绍这一工具近期发生的变化。
2. Go Fat:更大规模的代码补全
首先是“Go Fat”,也就是代码补全的规模在不断变大。代码补全是最经典的智能开发形式。在编写代码过程中,自动出现一行灰色的提示,这并非通过函数定义等方式自动生成,而是基于大模型推测出一段话。这种形式对于开发者来说最容易理解和使用,因为它将敲几个字符的时间简化为按下 Tab 键的时间。在此基础上,所有工具都希望在符合用户习惯的前提下进一步升级。
从单行代码的补全,逐步扩展到代码块,甚至基于注释生成整个方法或函数,从而扩大补全的范围。
然而,随着模型生成的代码量的增加,错误率也会相应上升。尤其是当一个工具或模型在不了解具体业务需求的情况下自动生成代码时,错误率往往更高。一旦错误率达到一定比例,比如超过一半,那么人工修正这些错误所花费的时间将抵消甚至超过工具带来的效率提升。因此,对于工具而言,必须通过结合模型的编程现场、项目需求以及用户的操作历史等多维度信息来提升准确率,使其达到一个既准确又高效的水平。
所有工具都需要解决四个核心问题:触发时机、推理逻辑、结束位置和检查质量。
触发时机的核心在于判断何时能够补全一块代码,何时应仅补全一行代码。显然,这取决于代码中重复性和规律性的特点。例如,在存在 if-else,for 或 switch 结构时,代码往往具有明显的规律性,很多 switch 的 case 中的代码都非常相似,因此许多工具会在这些场景下触发多行代码的补全。在其他场景下,工具可能还是会退回到单行补全。另一种特征是注释,当你已经写了一行注释时,工具可以根据该注释进一步推理代码,从而获得更完整的上下文信息,提高准确性。触发时机是通过产品层面的语法树解析来解决的,以达到高质量、低错误率的目标。
第二是推理逻辑,将什么东西输入给模型,模型才能更好的推理。最简单的情况是将光标位置上下的两段代码作为输入,模型在中间插入新代码。除此之外,模型还可以利用文件依赖关系和 import 的内容来更好地进行推断。例如,当你编写代码时,通常会打开其他文件以查看可用的功能或变量,然后返回继续编写。因此,你最近打开的文件在一定程度上可能会被用到。对此,在后文中还将进行更详细的说明。
第三是结束位置。由于模型本质上是一个续写工具,它根据前文生成后文,但若不断续写最后一定是错误的。并且代码过长可能会无法使用,少量代码行则可能更为实用。因此,如何适时地结束代码生成是关键问题。然而,正因为代码是多行的,没法仅凭换行符作为 stop sequence 的结束标志,我们需要通过程序方式检测模型输出的内容是否应结束。这涉及多种策略,例如:当触发时机为一个代码块时,我们可以在遇到匹配的右括号时结束;如果是 if 里面还有个 if,那等外层的 if 结束时结束;如果触发时机是一个注释,那么连续的换行或下一个注释的出现可能是合适的结束信号;如果代码块之间存在空行时,这通常意味着不同的逻辑块的开始,因此可以在此结束。这些策略与触发时机密切相关,旨在确保代码块的适时结束。
接下来再看检查质量。在使用模型时,我们偶尔会遇到一些问题,例如模型生成完全重复的代码片段,这是一解决个显而易见且需要的问题,但对于代码质量而言,还存在其他明显的不符合标准的情况。例如,在训练过程中可能会遇到数据污染,导致模型生成包含韩文字符的注释。显然,无论是面向客户还是我们自己使用,这种情况都是不可接受的。因此,我们的首要任务是通过策略来屏蔽这些错误内容的输出,第二就是追加模型训练。除此之外,还有一类模型很容易出现的问题就是代码过度拟合,生成的代码与现有代码过度相似,甚至完全相同,导致无法直接使用,这类问题也要解决掉。最终,通过质量检查和过滤措施,将高质量的代码呈现到用户面前。
通过上述工作,多行代码补全的正确率或者用户采纳率,都可以达到平均值以上。
3. Be Rich:丰富的生成能力
除了协助补全代码,我们还需探索模型在更多场景中的应用潜力。一个显著的例子便是代码优化。在日常业务开发中,写面条代码是再平常不过的事。然而,当业务已经十分繁忙时,再要求他们回头审视代码,进行重构与优化,无疑是一种难以接受的工作模式。如果将这类任务交给模型,能够得到怎样的帮助呢?
首先,模型会指出代码中存在重复部分,并建议将这些重复的内容抽象化,通过创建一个函数来封装它们。其次,模型会指出代码中的硬编码问题,如果未来需要修改这些编码,而其他地方未同步更新,可能会导致问题。因此,它建议提取一些常量,以确保所有相关部分都能通过修改常量来同步更新。第三,它会指出代码中存在的类型安全问题,比如某个类型应该是从其他部分获取的,但代码中没有进行相应的判断。因此,模型会建议修改语法,采用问号点等方式来增强类型安全性。第四,模型会指出代码中的命名不规范问题,并提供一段经过优化的代码作为示例,经过评测,这段代码是可靠的。当然,在将这段代码应用到原始代码之前,我们进行了一些工程化处理,以确保它们能够对接。这样,你可以直接采纳这段代码,从而自然地实现一些常见的优化,而这些优化既不影响业务逻辑,又是相对可靠和正确的。因此,在编码过程中,我们可以将一部分工作,特别是事后的优化和检查,交给模型来完成,让它与我们协同开发。这与以前模型中只能单纯编写一两行代码的情况相比,已经有了截然不同的效果。
再来看另一个场景,即函数代码量过大的情况。当我们不小心编写了一个 500 行的函数时,作为工程师的第一反应通常是认为这个函数需要拆分。然而,对于忙碌的工程师来说,可能会因为时间紧张而无法立即进行拆分。那么,在这个场景下,模型能做些什么呢?
以前端场景为例,假设我们有一个展示在界面上的组件或者元素。模型首先会在这个组件中抽取一些公共的功能,并将它们封装成一个函数,使得这个函数可以在多个地方复用。其次,模型会在函数的基础上再做一层封装,这在 React 框架中被称为 hook,这种封装依然保持了代码的可复用性。接下来,模型会进行第三层的拆分,将一个大组件拆分成多个小组件。比如,一个包含框、标题和内容的组件,模型会将其拆分成独立的标题、内容和布局(框)组件。这样的拆分有助于从代码实践的角度将组件分离成不同的层次,然后再通过组合的方式将它们重新整合在一起。当然,这并不是通过简单的 prompt 就能实现的。这背后其实是我们平时大量实践经验的积累,需要将这些经验转化为复杂的 prompt,并转化为一系列类似于 One-shot 或 Few-shots 的例子,放入模型的上下文中。
这是我非常期望的开发类工具所能达到的效果,将我们具有丰富经验的工程师的实践融入到产品中,让所有人都能享受到这些经验所带来的好处。
4. Seek Deep:更深度地了解全库
接下来,来看看产品的第三个进度,这主要体现在它对整个代码库的了解程度上。最初的工具对代码库没有任何感知,只是根据光标前后的内容凭感觉生成代码,这在实际业务中很容易出错。因此,后来的工具都发展出了全库代码索引的概念。简单来说,全库代码索引是通过向量化 embedding 技术,将代码库中的所有代码进行索引,然后根据用户的需求将其转化为向量,并进行向量的相似度计算,从而找到相关的代码。这个过程虽然复杂,但效果却比较明显。
例如,当你询问一个方法的作用时,如果工具没有了解整个代码库,它可能只会告诉你这个方法看起来像是一个用于对话的方法,并逐行解释代码。然而,这对于大多数工程师来说并没有太大的价值。但如果工具了解整个代码库,它就能为你梳理出整条业务流程,并生成一个流程图。这个流程图并不是简单的函数调用关系,而是真正业务层面上的流程。
当你需要启动一个会话时,工具会分情况根据你调用的方法,分析出下游做了哪些复杂的流程,并分析出整个业务流程。特别是当你刚接触一个代码库,还不熟悉其业务,但又急需完成一个需求或修复一个bug时,这种效果会非常明显。
5. Create By Trust:值得信赖的工作
再审视我们当前所使用的这些工具,还存在一个问题,即其生成的结果常常会产生幻觉。一旦这种现象发生,用户的信任度便会大幅下降。那么,我们应如何解决这一问题呢?在特定情境下,例如执行某个命令时,该命令可能会在运行过程中突然报错。面对此类错误,我们可以将其提交给模型,并结合一系列工程调优手段进行修复。模型能够精确地指出错误所在,具体到文件的某一行或某几行代码,这是因为我们拥有调用栈(step trace)信息。在修复问题后,我们可以清晰地观察到代码的变化。
这种做法的优势在于,当你再次执行该命令时,若看到命令成功运行,显示为绿色,你便能自然而然地确认问题已被解决。在这种可验证的场合下使用模型,我们可以以极低的成本迅速判断模型输出的正确性,若发现错误,则将其撤回即可。在这种情境下,我们与模型之间的协同配合,其效率远高于人工逐行审查模型输出的代码。
6. AT Your Hand:更贴合现场的交互
接下来,我们探讨一下交互方面的问题。当前的众多工具,包括我们自主开发的以及其他大模型应用,一个最显著的特征就是在界面边缘添加一个侧边栏,内置对话框以供聊天,这已成为大模型的主要使用方式。然而,对话框在编写代码时却带来了一个显著问题:它会打断编写者的思路。当我们全神贯注于编写代码时,视线始终聚焦于代码区域。如果此时需要查看对话框,就必须将视线从代码上移开,转至侧边栏,这无疑会打断我们的编程节奏。对于编程人员而言,专注力至关重要。因此,现有的许多工具已开始尝试将对话框融入代码区域中,我们称之为“行间对话(inline chat)”。在行间对话模式下,你可以直接在代码光标处与工具进行交互。例如,当你需要编写某段代码时,只需简单说明需求,大模型便会根据需求调用并返回相应的代码。
此外,还可以选择一段代码,请求工具根据需求进行修改,如添加边界条件检测、判断返回值是否正确,甚至进行代码优化等,工具会在你的代码基础上进行编辑,并以红绿块的形式展示修改内容。若你满意修改结果,点击确认即可保留;若不满意,则点击取消,修改内容将被撤销。这种方案能够让你在专注编写代码的同时,通过自然语言与大模型进行交互,从而更加高效地完成编程任务。
以上就是我们产品在交互方面的发展。
二、企业落地智能研发经验
1. 在企业中踏实落地开发智能化
从企业和团队的角度来看,大模型无疑是一个极具潜力的工具,这一点我们都普遍认同。然而,当我们将工具下放至每个个体时,却会发现大家的接受程度参差不齐。有些人认为大模型对他们的帮助极大,工作效率有了百分之五六十甚至翻倍的提升,他们非常乐意接受并使用这一工具。而另一些人则可能觉得不习惯,偶尔的错误会让他们感到困扰,觉得需要花费额外的时间去校对和修正,因此对大模型持保留态度。还有一些人可能习惯于原有的工作模式,不愿意因为引入大模型而做出改变,他们更倾向于等待,直到大模型成为主流后再考虑使用。
面对这种差异,如果团队希望大模型能够尽快被大家接受并发挥作用,那么我们需要做两件事情:
第一,建立大家对大模型的心智认知。让大家在日常工作的方方面面都能感受到大模型的存在,使他们在遇到问题时能够自然而然地想到“或许我可以试试用大模型来解决”,这种心智的建立需要时间和持续的引导。
第二,增强大家对大模型效果的信心。虽然大模型在使用过程中可能会偶尔出现错误,但是不是两次三次,实际上可能是一百次才可能出错两次,这种错误是相对较少的。只有当大家真正相信大模型即高效又准确时,大家才能愿意去接受它。
针对这两件事情,我们的处理方式如下:首先,我们思考的是,在何种情况下能让大家真正感受到大模型始终存在。若仅将大模型的应用局限于编写代码的过程中,这显然是不充分的。因为在编写代码时,大模型无非充当了一个 IDE 插件的角色,一旦关闭,其存在感便荡然无存。此后,我们也可能忘记启动大模型以辅助其他工作。
2. 感知存在:融合 Devops 必经链路
对于工程师而言,有一个不可或缺的环节,即整个 Devops 链路。从需求提出,通过需求平台,到编写代码,再到代码评审、文档查阅、测试、构建、编译验证,直至最后的部署,这些步骤都是不可或缺的。代码提交后,必须查看流水线状态;测试结果出来后,必须进行分析;部署上线后,还需确认线上运行情况及上线进度。我们希望在这些环节中融入大模型的应用,即便不期望大模型能带来显著的帮助,也希望能通过频繁的接触,使工程师们在面对问题时,能够自然而然地想到大模型。
因此,我们在产品中尝试引入了以下几个功能。
首要的是智能化的基于大模型的代码评审。代码评审对我们来说是一个必不可少的环节,所有代码必须经过评审才能入库。在这个环节,我们引入了大模型来分析提交的代码差异,并为大家提供一些推荐。具体推荐的内容其实并不是最重要的,关键在于大模型能够发现一些典型的问题。将这一功能应用到我们公司内部后,目前的结果是,在所有的代码评审环节的评论中,有 20% 是由大模型生成的。其中,有 15% 被大家认为确实是有用的,是一个很好的功能,而非废话。这个数字对我们来说其实并不高,因为 15% 的正确率,如果放在正常产品中,通常会引起质疑,担心会有错误。但我们的主要目标并不是追求极高的正确率,而是让大家感受到大模型在协助他们工作。
因此,我们并未将该功能作为产品下线,而是持续保留在第二个环节,即流水线编译失败的阶段。当编译失败时,大模型会自动运行,分析失败的原因,查看日志,提出可能的问题,并建议开发者通过哪些关键词搜索网络,或者检查代码中可能存在的问题。这个功能并不直接产生任何代码。
如果从大模型编写代码的角度来看,这个功能产生的代码量为零,但它仍然能够让开发者随时随地感受到大模型的存在。因此,我们一直将该功能保留在线上,帮助大家进行最基础、最简单的分析。通过这样不断地向开发者推送大模型的能力,我们公司内部目前已有超过 80% 的工程师在开发阶段持续使用大模型相关的工具。
3. 效果信心:利用 CICD 确保效果
接下来,要探讨的是如何让工程师对大模型生成的结果有信心。在这方面,我们有一个非常有效的做法。我们所有的正规项目都配备了 CICD 流水线,它本质上就是检查代码的正确性,确保代码能够成功编译和运行。这个流水线为我们提供了一个最基础、最可靠的信心保障,即我们提供的代码至少是编译通过的,能够运行的。
在这个场景中,我们最深入的一个应用就是使用大模型来编写单元测试(UT)。这并不是简单地让大模型根据重要代码生成单元测试用例,而是有一系列的处理流程。首先,大模型会删除不需要测试的代码,因为保留这些代码会干扰大模型。同时受限于大模型的上下文长度和成本,它会通过代码依赖关系添加更多的相关代码,以确保大模型能够生成有效的单元测试。在生成单元测试后,大模型并不会直接将其交给用户。因为此时并不知道这些代码是否正确,所以它会运行这些单元测试,并生成一个测试报告,根据这个报告大模型会进行翻译,从最基础的 Json 中获取关键信息,例如如果我们用的是国内的模型,它就会把英文改成中文。
这个报告通常会反映两类问题。第一类问题是单元测试是否通过。如果单元测试失败,可能是单元测试本身写错了,也可能是业务逻辑有问题。我们会通过另一个模型来判断是哪种情况,并相应地修正单元测试或业务逻辑。修正单元测试可能包括修改断言、删除错误的单元测试或让大模型修复错误的单元测试。第二类问题是覆盖率不够。如果大模型生成的单元测试覆盖率太低,我们会让模型有针对性地去提升覆盖率。我们会从覆盖率报告中找出哪些行没有被覆盖,然后分析这些行是在哪些分支条件下,让大模型解释这个未覆盖的分支是什么判断条件,再将这个解释吐回给大模型,让大模型注意到这个情况,再让大模型根据情况补充更多的单元测试。
通过不断提升单元测试的正确性和覆盖率,我们最终会得到一组既正确又全面的单元测试。然后,我们会通过增删用例,将这些单元测试组合起来,生成最终的单元测试代码交给用户。
通过使用文心的小模型来进行不同复杂度的单元测试,我们能够在模型规模几乎小了 10 倍的情况下,达到与 GPT-4 基本相同的覆盖率效果。当然,这是在实验室环境下可以多轮运行的结果。然而在实际用户使用时,我们不能接受长时间的运行,因为那会影响用户体验。所以我们对时间上会有要求。
我们最终产品化的效果:首先,生成正确率要达到 100%,因为只要代码能运行,那它一定是正确的;其次,行覆盖率要达到 30% 以上;最后,整个过程的耗时要小于 30 秒。对于大模型一个比较大的生成任务来说,30 秒已经是一个相对较短的时间了,而且在这个时间内,我们能够提供 30% 的覆盖率,这是我们产品化的最终结果。
对于用户来说,这个能力几乎是一个纯收益的事情。他们不再需要管理代码,只需要试错、生成并保存即可。这在我们内部提供了大量的大模型代码生成量,也是用户非常喜欢的一个能力。
由于该过程本就正确无误,无需用户手动保存文件,因此我们将其融入到代码管理之中。当提交代码后,大模型会自动基于你的代码生成单元测试,并再次提交一份包含单元测试的代码补丁。对于这份代码,你几乎无需进行额外的审查,直接合并即可,因为其正确性已经得到了保证。基于这样的设计,我们可以让用户在全新的开发阶段也无需关注单元测试的编写,同时仍然能够获得具有高覆盖率和正确性的单元测试代码。
三、开发者智能工具使用实践
1. 形式变革:智能时代理念
作为个体,在使用这些工具时应该采取哪些实践方法以更有效地发挥工具的作用呢?
未来,AI 与我们的关系绝不仅仅是工具与人之间的简单关联,因此我们不会仅仅停留于使用 AI 的阶段,而是致力于与 AI 实现真正的协同工作。所谓协同,意指在某些情况下,我们会将任务分配给 AI 完成,而在其他情况下,则亲自处理。并非指在一项任务中,我仅负责前半部分而 AI 负责后半部分。基于此,我在实践中形成了一些个人的准则。
2. 作为开发者拥抱智能化实践
首先,关于代码检索这一任务,我认为让 AI 来完成是一个极为出色的选择。其次,当我利用 AI 来处理事务时,如何加强上下文信息,从而提升 AI 的准确率。第三,我会分享如何管理自己的知识,使其能够与 AI 有效关联。最后,我们将讨论如何与 AI 进行协同分工。
3. 一种全新的找代码方法
我们先来探讨代码检索这一任务,这是我认为在代码编写领域中,AI 有可能真正超越人类的一项能力。人通常是如何寻找代码的呢?我们往往会根据所需代码的大致要求,先在脑海中构想在代码里面的方法名或者变量名,然后利用全局搜索功能进行查找。如果搜不到就换个名字继续搜,如果找到了,就进一步检查该函数被哪些其他函数调用,以及函数内部调用了哪些其他函数,即我们常说的“Go to Definition,Go to Reference”。通过这一系列的步骤,我们最终在脑海中汇总这些信息,得出一个结果。
然而如果整个代码库都具备 embedding 的向量能力,那么我们的模型就有办法准确地定位到代码库中的任意代码片段。其次,模型天生就具备总结和解释的能力,这使得它能够在代码检索方面发挥出色。因此,我们设想了一个未来的场景:在寻找代码时,我们可以直接使用自然语言进行搜索,而无需再纠结于具体的变量名或方法名。
在此,我有两个实践建议:第一,我们应该描述要找的功能或需求,而不是局限于描述自己的代码。第二,如果我们寻找的目标不仅仅是代码本身,而是对代码进行整理和总结后的结论,那么我们可以直接让模型为我们生成这个结论。
这里有两个典型的例子来说明这一点。左边第一个例子是要找代码,关于在应用程序中查找创建应用表单的实现位置。如果我自己去找,可能会尝试使用与“创建应用”相关的词汇,如“create”等,进行搜索,但很可能无法找到。因为这个代码库的命名方式较为特殊,它将创建应用的功能命名为“fork”。我自己很难想到这个词,但模型却能够帮我找到正确的位置。它指出在“fork”目录下有“Info”和“UI”两个文件,这两个文件共同实现了创建应用的功能。整个过程模型只用了大约二十秒,而我自己可能需要花费五分钟甚至更长时间来尝试不同的关键词进行搜索。
第二个例子是关于总结系统暴露的路由数量。我并不是在寻找具体的代码,而是希望模型能够帮我总结在某个目录下系统暴露了多少个路由,并给出它们的详细信息。模型同样出色地完成了这项任务。它列出了所有的路由,包括它们的请求方法(如 POST、GET 等)、目标地址以及所在的文件位置。而我如果要自己去做这件事情,可能需要逐个打开 Controller 文件,查找其中的注解来识别路由,然后再将这些信息记录下来。这同样是一个非常耗时的过程,但模型只用了十几秒就完成了。
因此,在代码检索这件事情上,模型的表现确实非常出色和可靠。
4. 让相关代码先走一步
接下来关注的是,我们在实际应用中如何提升模型的表现,以优化输出结果。这主要依赖于对大模型 prompt 的编写,因为提示词的质量直接影响模型的表现。然而在代码检索或相关应用中,用户往往无法直接输入提示词,而是依赖于产品内置的逻辑。这些内置逻辑主要基于以下几个核心原则。
第一,关于编写 import 语句,建议尽早进行。我了解到许多开发者并不习惯首先编写 import 语句,因为 IDE(集成开发环境)通常会在使用时自动补全。然而,如果你能够预见到将会使用到某些模块或库,并提前将它们写入 import 语句中,大模型就能提前知道。因为你的开发工具会根据 import 语句自动查找并补全所需的依赖,从而增强模型的准确性。
第二,如果你正在处理与当前任务相关的文件,建议将它们打开并放置在旁边。例如,当你在编写一个 Controller 时,它可能会依赖于 Service 层,而 Service 层又可能依赖于 DAO(数据访问对象)。将这些相关的文件都打开并放置在视野内,开发工具都会读取这些当前打开的文件,并从中提取有用的信息提供给模型。
比如需要补充一个名为“APPID”的信息,如果仅依赖模型进行单独补充,它可能会生成一个毫无意义的字符串,如“134567”,这样的结果显然不符合你的期望。然而,如果你已经打开了一个包含环境变量的文件,并且该文件中有关于“APPID”的环境变量设置,那么模型或开发工具就能自动读取这个环境变量,并将其准确地补充到所需位置。这种能够智能识别并利用当前环境中已有信息的细节处理非常出色。
关于 import 语句,在导入了某个模块或库后,模型或开发工具就能自动在这个文件或库中寻找你所需的方法。例如,如果你要调用一个名为 getPrice 的方法,并且该方法接受一个参数,那么如果你已经 import 了这个方法,模型就能准确地为你补全这段代码,包括方法名和参数传递。然而,如果你没有导入相应的模块,模型在尝试补全代码时可能会遇到困难,因为它无法确定你应该调用的方法的确切名称。在这种情况下,模型可能会给出一些与你的意图不符的建议,比如 getTotal、getAmount 或其他任何可能的方法名。因此,import 语句的使用,对于确保模型能够准确补全代码,效果非常好。
再来看之前提到的代码补全问题,如果将其置于对话模式下进行考虑。这里举一个最简单的对话例子来说明,有一个普通的需求,即编写一个组件,该组件的功能是向后端接口发送请求,并接收返回列表数据,同时在表格中展示这些数据。这是我们在日常工作中经常会遇到的任务。
如果我直接对模型表述这个需求,模型可能会根据当前社区中最流行的技术方案来为我生成代码。例如,如果模型认为 React 框架很流行,且通常使用 Axios 来发送请求,那么它可能会按照这种方式来编写代码。然而,这样的代码在我们的具体业务中是否真正有价值呢?这并不一定。因为我们的业务可能会采用不同的社区框架,如果生成的代码没有基于我们实际使用的框架,那么这些代码对我们来说就可能是无用的。
那我应该怎么做呢?在这个对话之前,我引用了一个在前端的项目文件 package.json,这个文件记录了项目中安装的所有第三方依赖的声明。我仅仅做了这一件事情,其他的内容都没有改变。模型似乎注意项目中使用了 antd 和 react-query 这两个库,因此它决定使用这两个库来为我编写代码。在发送请求时,它选择了使用 react-query 的 useQuery,接下来是 antd 的表格,并声明了表格的列(columns)属性。
这样生成的代码与我的实际需求匹配度高,而且在我的实际业务场景中可以直接使用,几乎不需要进行任何修改。因此我们在编写对话时,通过引用几个非常核心的文件作为参考,可以立即将代码的质量从 0 分提升到七八十分的效果。这是我们在对话实践中得出的一个经验。
而且,这个方法不仅适用于前端 JavaScript 项目中的 package.json,还适用于其他编程语言和框架。比如 Java 项目中的 pom.xml 和 gradle 文件,PHP 项目中的 composer.json 文件,以及 Python 项目中的 requirements.txt 文件。我认为在任何需要生成有用代码的对话中,都可以无脑地将这些文件提供给模型作为参考,这样生成的代码就是有用的。
5. 你就惯着她吧
最后,我们来探讨一些更理论层面的内容。模型就摆在那里,它的能力如何,是否优秀,这都是既定的。那么作为使用者,应该如何去适应模型呢?适应的程度应该达到这样的程度,即了解模型执行的时间。比如,当我输入一个“if”时,我知道模型后续不会只生成一行代码,而是会展开成四五行代码。因此,我会耐心地等待 2 秒,因为我觉得生成这些代码可能需要这个时间。同样地,如果我知道在某个情境下,模型只会生成一行代码,并且这个过程可能非常迅速,比如 500 毫秒,那么我就会相应地调整我的等待时间。如果模型在预定的时间内没有给出反馈,那么我就会停止等待。这是一种适应。
另外,我还需要学会在何时给模型提供更好的推理上下文。之前也讨论过,模型在某些情况下会表现得更加准确,而在其他情况下则可能不尽如人意。比如,当我在处理与业务高度相关的逻辑时,如果模型不了解具体的业务需求,那么它的预测结果很可能就不准确。在这种情况下,我就会选择自己编写代码,而不是等待模型的反馈。相反,如果编写的是一段通用的算法模型,并且模型能够通过函数名来推断出我的需求,那么即使模型需要 3 秒、5 秒,我也会耐心地等待,因为模型生成的代码通常会比我自己编写的更加高效和准确。
此外,还需要认识到模型并不是完美的,因此我们需要充分发挥模型的优势,同时避免它的缺点。有些任务,比如找代码、解释代码、读代码以及局部的重构和改写等,模型通常能够比我更加高效地完成。而有些任务,模型则可能无法胜任,这时就需要亲自上阵。通过与模型的长期磨合,我们逐渐积累了这种经验。
总结起来,人和模型都有自己的擅长领域,而人擅长技术选型、任务规划以及创造型的工作。
6. Focus, Let AI Run The Errands
那 AI 擅长什么呢?AI 模型擅长记忆和对细节的处理。它们能够快速地记住大量的信息,几个 T 的 Token 都是模型的记忆。此外,AI 还擅长处理一些琐碎且细节丰富的工作,比如局部的代码优化、探索型的数据搜索、互联网信息的搜集以及归纳总结等。
所以人类并不应该与 AI 在这些方面竞争。相反,我们应该充分发挥自己的优势,比如做规划、进行创造性思考等,将重复劳动、琐碎的事情交给 AI,这个称之为 Focus,专注。“Let AI run the errands”,即让 AI 处理那些杂七杂八的事情。这是我认为当真正改变了工作流以后,与AI协作的最好的关系。
7. 内容总结
上图中对我们的工具、企业和个人在 AI 发展上做的事情进行了总结,在此不做赘述。
从未来的视角来看,我认为将无人驾驶作为一个比较对象是十分恰当的。目前,无人驾驶技术处于 L2 与 L3 之间的发展阶段,而我们的智能研发则尚处于 L0 与 L1 之间,未来一定会继续发展,越往后“Transfer of responsibility”即它的职责将愈发向 AI 领域偏移。AI 将承担越来越多的工作,而人类所需完成的事务则会相应减少。我认为这是一个极为美好的前景,人类将得以投身于更多富有创新性的工作之中。因此,我们可以满怀期待地憧憬未来,随着我们不断向右前行,最终人类将能够专注于更具价值的事业。
四、问答环节
Q1:分享中提到的了解全库,将代码逻辑绘制成图形是纯粹依赖于代码生成,还是结合了业务上的需求文档等?按案例中解读的效果还是比较好的,如果单从代码的逻辑流出发,很可能得不出那样比较细致的关系。
A1:我们现在的实现不需要依赖需求文档,因为大部分产品的需求文档质量的不太高,还有可能是过时的。所以,首先我们是通过代码之间的固定调用关系,通过语法解析生成代码的知识图谱,其次在代码层面之上用大模型做代码的解释和增加注释等工作。这些工作将建立自然语言与代码之间的联系,将两者融合后,运用一系列综合算法进行处理。例如,当找到一个代码块时,会提取该代码块的解释,并同时获取其调用关系的图谱以及图谱的解释。将这些信息整合后,再借助大型模型来生成该代码块整体流程的摘要。最终,将生成一个类似于之前所展示的图,实际上这是一个通过 Mermaid 方法生成的图,它将以一段 Mermaid 代码的形式展现。
Q2:代码关系抽取等工作是实时做的,还是已经基于代码库离线处理好了?
A2:这些工作肯定是提前处理好的,代码库第一次打开时可能需要几分钟处理一遍,后续代码修改则增量更新。
Q3:全项目进行向量化是在云端做的吗?如何保障代码的安全?
A3:这一环节确实是在云端完成的,首先它是安全的。我们将其分为两个部分:第一部分是本地代码传输到云端过程中的网络安全性,这一安全主要通过 https 协议来保障。若需进一步增强安全性,我们可以实施 https 协议的证书 Auth 校验,以此防止通过私有证书进行劫持的风险。这是传输部分的安全性。第二部分则是存储环节的安全性。我们的方案是不直接存储任何源代码内容,存下来的是 embedding 向量对应的源代码文件名和文件对应的范围,即代码起始和结束的行号与列号,具体的代码内容我们并不存储。在实际使用时,我们需要这段代码时,会向客户端发送一个请求,让客户端根据提供的信息找到相应的代码段。并且我们会通过一个哈希值进行校验,以确认代码内容是否已被修改。如果校验通过,再将这段代码取回并供给模型使用。因此在存储方面,我们通过不执行任何落盘存储操作来确保这些代码本身不会因我们的安全风险而泄露到外部。