领域专家通过文字描述进行 AI 驱动的编程:真的能行吗?

图片

作者 | Markus Voelter
译者 | 马可薇
策划 | Tina
摘要
  • 通过大语言模型(LLM)的描述生成代码方面的最新进展让非程序员也能通过“文字描述进行编程”,从而生成实际可用的复杂程序,这是计算机科学家与领域专家长久以来的梦想

  • 假设代码和结果的可解释性仍然重要,那么代码的测试将仍需采用更为传统的形式。因此,非程序员必须理解测试和用例覆盖的概念

  • 程序的理解、可视化、探索,以及模拟将在未来更为重要,以便向领域内专家解释生成的程序的用处

  • 高级编程语言和领域特定语言(DSL)之间相似性很高,因为需要生成的程序越是短小(越是不易出错),与执行语义之间的联系就越是直接(也更容易理解)

  • 作者认为,这种方式的可用范围到底有多大,以及利用大语言模型的“文字描述魔法”和更为传统的计算方式之间集成的前景如何,仍是一个悬而未决的问题。作者将利用一个在 JetBrains MPS 中实现的开源演示程序对此进行说明

    前    言

    人工智能、机器学习、神经网络,尤其是像 ChatGPT 这类大语言模型(LLM)的出现,让人们对编程的未来有了两方面的讨论,其中之一是人工智能如何协助开发者更高效地编程。我们或许都让 ChatGPT 根据文字描述生成过几行代码片段,再把这些代码复制粘贴到自己开发的大型应用程序里,或者是直接在集成开发环境中使用 GitHub Copilot。

    这套方法很好用,因为程序员只用看一眼代码或者是让代码在“安全”的环境下运行,就能验证代码的合理性,最后甚至还能提前写好测试以验证这些生成的代码能够在所有相关的场景下正常工作。这些 AI 生成的代码甚至不用完全正确,只要达到八成的正确率,对开发人员而言就是有用的。这就和我们在 Stack Overflow 上查资料一样,这些都是我们最终动手完成工作的灵感、大纲、指引,或者提示。在作者看来,这种 AI 的使用方式无疑为开发者们带来了价值。

    探讨的第二方面则在于 AI 是否能让非程序员对计算机下达指示。换句话说,就是让人们仅通过提示的编写,就能让 AI 生成代码,让机器做事。这与前一种的主要不同点在于防范机器胡言乱语的保护机制的缺乏,至少明面上是没有的。

    非程序员不一定能看出代码是否合理,他们也不一定能将生成的 80% 准确率的解决方案变成 100%,他们也不一定会编写验证测试。那么,这种方法到底可行吗?语言和工具是否需要有多大的改动才能实现?这两个问题将是本文的重点。

    为什么不直接使用 AI?

    你或许想问,为什么不直接生成程序呢?为什么不直接让通用 AI 做事,而是要生成代码然后再做事呢?假设我们现在要计算税款,最终目标是一个可以根据收入、支出,以及生活开销等数据,计算出任何居民的税务负担。

    一种方式是让居民将数据输入某种表格,然后再把这个表格数据(比如 JSON)交给 AI(通用大语言模型或税款计算专用模型),让 AI 直接计算税款。其中没有任何 AI 生成或手写程序的参与,除了数据收集、转换为 JSON 格式并交给 AI 这部分。多数情况下这种方法可能都不够好,原因有下:

    • 基于 AI 的软件都不怎么擅长数学计算 [1]这不能算是税务计算领域的独有问题,毕竟现实世界中大多领域中都有对数字的计算

    • 如果一个 AI 只能达到 99% 的准确率,那剩下 1% 的错误往往会让人大失所望。

    • 无论结果如何,这都没法向最终用户结束或“证明”(后面还会再细讲这个话题)

    • 用神经网络运行固定算法的计算,这在计算能力和附带产生的能源与水资源消耗方面利用率都很低。

    • 算法一旦变动,就必须重新训练神经网络,因此会需要更高的计算成本

    为解决这些问题,我们可以换一种方法,让非程序员的领域专家(比如税务顾问)对 AI 描述税款计算的逻辑,让 AI 生成一个可以在居民数据上反复运行的经典且确定的算法。假设这套生成的程序没问题,那么上述的所有问题都将消失:

    • 可以按需计算数字精度

    • 通过跟踪计算的算法,我们可以解释并证明结果的正确性(后文中会详细说明)

    • 在所有情况下,计算结果都是正确的(假设生成的程序正确)

    • 计算的能效不亚于当今任何程序

    • 生成的代码可随需求变化进行增量调整

    注意我们这里假定所有相关情况下的正确性和可解释性都很重要,如果你不认可这些假定,那文章后面就不用再读了,因为你大概认为人工智能将多少取代所有的传统编程软件。作者坚决不认可这种观点,至少在未来五至七年内是不认可的。

    正确性和创造性

    根据人们对大语言模型写文章或 Midjourney 之流的图像生成体验,我们认可这些 AI 是具备创造能力的。在对“创造力”没有确切定义的情况下,作者认为这是在相同或略有不同的提示词下,生成结果在一定程度上的可变性。这要归功于单词预测的工作原理,以及这些工具的随机性结果生成,Stephen Wolfram 在他的文章中对这点有很好的解释。神经网络通常不具备精准的确定性,但人们却将这一缺陷视作了优点。

    只需一个简单的实验,让图像生成 AI 渲染一个具备一定“正确性”的技术相关主题,比如飞机或起重机,你就会得到两侧机翼长度不一致,或者一侧机翼上有两架引擎但另一侧只有一架引擎的喷气式客机。这些结果通常不尽如人意。但如果你想生成“在下雨的森林里奔跑的幻影狗”,人们对结果中的不准确和多变性的容忍度则会更高,甚至还会将其解释为“创造力”。应用程序的生成更类似飞机图片的生成,而非幻影狗。在这种人工智能的用例下,创造性不在其特征范围内。

    可解释性

    先看看可解释性的概念。再回到我们的税款计算问题,假设你在某段时间内需要支付 15,323 欧元,但自己估算了下,这好像有点多,于是你问:“为什么是 15,323 欧?”如果是 AI 直接生成的结果,那它是没办法解释的。AI 可能会(形象地)回复说这是根据自己所有内部神经元的权重值、阈值和内部神经元的激活水平得出的结果。不过这些对人类而言毫无意义。人工智能对税款计算逻辑的联系充其量也是非常间接的,或许 AI 还会告诉你这是因为你的情况与其他 250 个例子很相近,那么出于某种原因,你的税款就一定是 15323 欧。训练过的神经网络本质上只是一个极其严密的曲线拟合,其中包含大量的参数。这是一种“经验编程”的形式,是对已有数据和推断的强行复制。

    就像是科学一样,要想解释拟合数据的含义,就得把它和科学理论相结合,比如“将曲线和已知的物理量相拟合”。与科学理论大致相对的是根据“有意义”的算法计算结果的“传统”程序,用户可以检查其中间值、查看程序选择的分支路径和决定所依据的标准等等。这是对“为什么”问题合理回答的第一阶段,尤其是在程序是通过抽象概念、结构和名称来表达的,而这些税务这一语境下是有意义的 [2]

    我们可以很轻松地从结构良好的程序中,追溯支撑其中某段程序代码的规则或规范。通过与领域相符的合理抽象表达的程序状态,以及对“要求”(在税款计算场景下是法律)的联系,这二者相结合是对“为什么”问题的很好回答。尽管目前也有对可解释 AI 的研究,但我认为目前的深度学习方法在短期内还做不到这一点,ChatGPT 提供的解释往往空洞且肤浅。试着多问问它“为什么”,你很快会发现它其实解释不了很多东西。

    特定领域下的工具和语言

    领域内专家通过 prompt 编程是否能行,这个问题的答案有一部分在领域特定语言(DSL)中。DSL 是一种为特定类型问题量身定做的软件语言,这些问题可能包括税款计算及必要的数据结构,或者用于诊断失眠或药物滥用情况的医疗调查问卷。DSL 是由特定领域内的中小型企业共同开发,且依赖于领域内熟知的抽象概念和符号。因此,如果让 AI 生成 DSL 代码,领域内的专家便可以阅读代码,从而一眼看出代码的正确性。

    这里我一定要特意注明语法的问题。众所周知,大语言模型是与文本打交道的,因此在与大语言模型交互时我们也得使用 DSL 的文本语法。但这不意味着中小型企业在验证或其他操作时也得用这种文法,面向用户的语法可以是任何有意义的东西:图形、表格、符号、代码块,或文本。虽然图形表示的经典编程语言往往效果不佳,但如果语言在设计之初就能考虑到这两种语法的存在,那么效果往往会更好。DSL 社区在这方面可谓是经验颇丰。

    更笼统地说,如果代码是由 AI 编写,人类只负责审核或稍加修改,那么可写性和可读性这个横亘多年的权衡将会更为偏向可读性。作者认为这种权衡往往都是偏向于可读性,因为一段代码的阅读次数要远比编写次数多太多,更别说 IDE 在协助编码方面也越来越有用。但不管怎么说,如果把编码工作交给 AI,那这场争论将迎来句号。

    生成 DSL 这类高级编程语言的第二点优势在于,AI 更不容易出错。大语言模型本质就是单词预测机器,限制词汇量并简化语法能降低预测错误的风险,句子中非必要的变量减少,从而更可能生成正确的代码。我们应该保证编程语言善于分离关注点,别把中小企业关注的业务逻辑和“技术内容”混为一谈。

    通往正确性的第一道关隘就是编译器(或者是解释型语言中的语法或类型检查器)。任何生成程序只要没通过类型或编译器检查可被直接拒绝,AI 会自动生成另一段代码。这又是高级语言的另一优势:类型系统的构建更为容易,与语法结构相结合便能更有意义地在当前领域内约束程序。同样,语言中(不必要)的自由度越少,便能更容易地分析程序的有趣属性。举例来说,状态机模型的模型检查比 C 程序更容易,从结果中抽象“解释”也会更容易,中小型企业学会通过代码阅读或是某种模拟器、调试程序进行的验证也会更轻松。化繁为简,也会简化所有人和所有工具的生活。

    这种方式有不少例子。Mathematica 中的 Chat Notebooks 允许用户编写 prompt,再由 ChatGPT 生成可被 Mathematica 执行的相应 Wolfram 语言代码。类似的方法展示还有阿帕奇 Spark 及 itemis CREATE(一种状态机建模工具)。作者将在文章下一节中详细讲解演示程序。

    DSL 代码生成也有一个缺点,可供大语言模型学习的特定语言下的代码示例在互联网上并不算多。不过事实证明,“教” ChatGPT 学语言效果还不错。作者认为原因应该有两个:一是因为尽管 DSL 是针对特定领域的,但其中的表达式等部分通常与传统编程语言非常类似;二是因为 DSL 在该领域内相对来说是更高级的,所以语法通常也会更加“大白话”,像是“用我在上文中解释的 DSL 风格”来表达对 AI 而言并不难理解。

    能教会大语言模型的语言内容受限于大语言模型的“工作记忆”,但可以料想,工作记忆在未来只会越来越大,进而允许更为复杂的 DSL。作者也相信,未来还将开发出更多针对结构化文本优化的其他模型,这些模型将遵循一种形式化模式,而非英文的语言结构。

    演   示

    作者实现了一套将 DSL 和大语言模型相结合的系统,演示程序是基于 JetBrains 的 MPS 和 ChatGPT,代码已分享至 GitHub。演示的语言侧重于带有字段和数值计算的表单,更为复杂的表格版本可能会用于医疗保健类的评估之类。下图是表单的示例:

    图片

    除了表单之外,该语言同样支持表达测试,这些都可直接在 MPS 中通过解释器执行。

    图片

    在这段视频中,作者展示了如何利用 ChatGPT 3.5-turbo 根据文字描述生成有意义的表格不否认,这种语言很简单,而现实世界中用到的 DSL 往往都要更复杂。作者也用其他更为复杂的语言做过实验,效果还算不错。正如作者所说,大语言模型在未来完成这项任务时会更好且更为优化。此外,多数 DSL 都有各自的视角或侧重点,用户往往只需要生成模型的一小部分,对大语言模型而言,相当于是更小的语言。

    关于这份演示技术部分实现的简要说明,可见 GitHub 中的 README 。

    理解并测试生成的代码

    要想理解代码的用处,光靠看是不够的,运行后观察其行为是更好的方式。在先前的税款计算的例子中,我们或许会先检查居民应缴的税款是否符合规定,或者在医疗类表格的例子中先验证计算出的数值是否符合预期。对于现实生活中的复杂程序来说,程序行为存在很多变量,也有许多独特的情况(税款计算这个例子显然如此,医疗类算法也是一样),因此我们需要编写测试用于验证所有相关的情况。

    这一步不会因为代码是 AI 生成的而消失,相反测试甚至会变得更为重要。如果我们没能在描述中准确表达需求,就算 AI 没有在胡说八道,生成的代码也很可能是不完整或不正确的。假设我们想通过对话形式一步步地得出正确代码,那么我们就需要通过回归测试验证先前生成的那些可运行代码没有被 AI 对代码的“优化”搞砸。因此,如果我们想让 AI 生成程序,非程序员的领域内专家必须控制一个具备合理且完善覆盖率的回归测试组件。

    通过文字描述指示 AI 修改代码中的所有东西,即使是对中小型企业而言,大概也是低效率的。随着时间的推移,人们会逐渐掌握这门语言,并开始直接在代码上进行修改。作者在上文中描述的应用程序示例可允许用户修改生成的表格,在用户指示大语言模型做更进一步的修改时,模型会从用户修改后的状态开始。用户和大语言模型间可以实现真正的协作。这项工具也支持“撤销”,如果 AI 对代码的修改弊大于利,人们会希望能有回滚的功能。作者搭建的演示会在 MPS 中以节点列表的形式保留 {prompt-reply} 对,只需删除列表尾部节点就能实现逐步的回滚。

    那么中小型企业要怎样才能实现所需的测试呢?如果测试的语言足够简单(根据作者的经验,DSL 通常都是这种情况),则完全可以手动编写测试。在上文中的演示系统中,有一种情况下测试只是对字段数值和计算结果的断言。让大语言模型根据冗长的描述生成测试效率很低,尤其是在有优秀工具支持的情况下。正如在演示例子中一样,字段和计算结果的列表已经预填充到测试里了。编写测试的另一种方法是在用户“操作”生成的工具时记录测试。虽然在演示中没有展示,但作者已经在一个类似健康评估的实际项目的 DSL 中实现了这套方法,用户可以在一个完全渲染过的表单中输入数值,并对显示的计算后数值表示“赞同”或“不赞同”。

    即使如此,用户仍然需要思考所有相关的测试场景,仍然需要继续编写测试直到场景覆盖指标变为绿色。第三种方法则是利用现有的测试用例生成工具。根据对程序的分析,这些工具能给出一系列覆盖率优秀的测试。通常情况下,用户仍需要为所有自动生成的输入组手动提供预期的输出数值,或更为笼统地说是断言的行为。对部分系统来说,这些测试用例生成器虽然能生成正确的断言,但中小企业的用户最少也要对这些结果进行全面的审核,生成的程序出错的话,结果也会出问题。从技术上来讲,测试用例生成器只能用来验证程序,不能用来确认程序。

    变异测试(自动修改程序以识别不影响测试结果的部分)可以很好地检验场景覆盖中的漏网之鱼。这种方式的优点在于它不依赖于花里胡哨的程序分析、容易实现,并且能适用于用户自己特定领域中的语言。事实上,演示中的 DSL 所基于的 MPS 基础设施既支持覆盖率分析(根据运行测试的解释器),又支持原型程序 mutator。

    我们也可以考虑让 AI 来生成测试。当然,这么做就会冒着“自我实现的预言”的风险,如果 AI“误解”了描述中的要求,那么它会就生成错误的程序和测试,然后错误地验证了错误的程序。为解决这一问题,我们可以让不同的 AI 分别生成程序和测试,至少也得要打开两个分别的 ChatGPT 对话窗口。根据作者的实验,ChatGPT 没办法生成正确的表单计算预期值,没办法“执行”它之前生成的表单表达式。我们可以为验证工具(比如模型检查器)生成属性 [3] 而非测试,与生成测试相比,生成属性具备更高的可信度。重要的是,即使是靠传统的测试生成器或 AI 生成了测试或属性,这些测试至少也要通过人工的验证。测试或基于工具的程序验证的成功率只有在验证对象正确时才有用。

    此外,我们还需考虑调试的问题。生成的代码在特定情况下不起作用怎么办?仅仅给出“代码在某某情况下没用,请修复”这样的提示是低效率的,作者在演示程序中验证了这一点。到最后,还是直接采用生成的代码更有效。所以说,代码必须能被理解和“调试”。优秀的领域相关语言(包含模拟器、调试器及其他将源码与程序行为相关联的手段)即使是对中小型企业来说,也是能发挥很大作用的。程序追踪、可视化执行、实时编程和集成开发环境领域内,程序与其执行之间的关联很小,这些才是我们这里讨论的重点。对于没有明显图形表示的程序来说,仍需要更多的研究和开发,这让作者联想到了最初实时编程演示中的弹跳球。

    除此之外还有一个问题,作者称之为“失败的魔法”。如果中小型的企业习惯了靠 AI 提示词描述让一切运行,并且几乎不接触源码或者是说自己生成的程序是如何运作的话,他们将很难深入到源码中对一些问题进行修复。在源码到行为的这条路上,其中加入的“魔法”越多,任何的用户在调试时就越难从行为返溯到程序。人们会需要相当花哨的调试器,并且这些调试器的构建成本大概也不会低。这也是多年来在没有 AI 协助下使用 DSL 所得到的另一个教训。

    总   结

    让我们回顾一下中小型企业需要具备怎样的技能,才能在特定的领域内稳定使用 AI 进行“编程”。除了编写 prompt 的能力,人们还需要学会怎样审查、编写和记录测试,理解测试的用例覆盖范围,辨别缺失的测试场景以及何时才算是有足够的测试量。人们还必须要了解代码生成的“范式”和结构,才能清楚解释并逐步做出修改。要想在实践中发挥作用,组织内的软件工程师也必须调整自己所用的语言和工具,并将其作为 AI 代码生成的目标:

    • 更小且与领域相关度更高的语言更有可能生成正确的代码,也更容易为中小型企业所理解,这其中也包括测试编写所用的语言

    • 我们需要程序的可视化、动画、模拟器、调试器,以及其他能缩小程序与其执行之间差距的工具

    • 最后,任何测试用例生成器、程序分析等手段都将非常有用

    因此,AI 让人类与计算机之间以自然语言进行沟通的预想在一定程度上成为现实。虽然我们可以将预期行为以提示描述的形式表达,人类仍然需要验证 AI 生成的程序在所有相关场景下的正确性。作者不认为仅仅通过提示的对话界面就能有多大用,“怎样与计算机沟通”这类的培训在一定程度上还是必要的。在 AI 已经问世的现在,除了计算机科学学科外,大多数的领域仍然极度匮乏这方面的教育。

    当然,随着 AI 的进入,事情也会有所变化,尤其是在将基于规则的经典 AI 与大语言模型相结合这一突破性的创意提出后。或许人工验证已经不再必要,因为 AI 在某种程度上已经足够优秀,它们总能生成正确的程序,但作者认为这在未来五到七年内都不会发生。预测未来很难,所以作者不会去做。

    致    谢

    感谢 Sruthi Radhakrishnan、Dennis Albrecht、Torsten Görg、Meite Boersma 和 Eugen Schindler 对本文先前版本的反馈意见。

    还要感谢 Srini Penchikala 和 Maureen Spencer 对本文的审阅和校对。

    [1] 未来大语言模型可能会和 Mathematica 等数学引擎相集成,到时候这点将不再是问题 

    [2] 如果用 C 程序来做同样的计算,而全局的整数变量命名是 i1 到 i500,就算程序能百分比生成正确结果,程序执行情况的检查或自动生成的报告并不能让人类知道到底发生了什么,抽象和命名方式非常重要! 

    [3] Properties are generalized statements about the behavior of a system that verification tools try to prove or try to find counterexamples for. 属性是验证工具试图证明或试图找到反例的关于系统行为的概括性陈述。 

    查看英文原文:

    声明:本文为 InfoQ 翻译,未经许可禁止转载。