探索 prompt 编码范式:如何优雅构建测试代码生成提示词?

2023-11-02

从四月份到现在,我们持续为 AutoDev 编写了一系列的功能。尽管开发了三个多月,我们一直在持续思考、并重构我们管理 prompt 的方式。在即将发布的 AutoDev 0.8 里,我们进一下完善了现有的上下文构建方式,以模式化的方式重新思考并设计了新的上下文工程体系。

而测试生成正是我们的第一个新试点,以探索新的 prompt 模式是否更方便?如下是基于新 prompt 编码范式的视频:

详细代码见:https://github.com/unit-mesh/auto-dev

AutoDev 的 prompt 演进

在那篇《Prompt 编写模式:如何将思维框架赋予机器》,我总结了如何更好的编写 prmopt。于是,在 ArchGuard Co-mate 中,我们将这些模式代码化:

interface BaseTemplate {
    fun getRole(): String = ""
    fun getInstruction(): String = ""
    fun getRequirements(): String = ""
    fun getSample(): String = ""
    fun getExtendData(): String = ""
}

在开发 AutoDev 的过程中,我们发现它匹配我们所理解的编程范式:模式化。但是,在引入了规范化的代码生成之后,一部份 prompt 变成了配置,以支持不同团队配置自己的 prompt:

{
    "spec": {
    "controller": "- 在 Controller 中使用 BeanUtils.copyProperties 进行 DTO 转换 Entity\n- 禁止使用 Autowired\n-使用 Swagger Annotation 表明 API 含义\n-Controller 方法应该捕获并处理业务异常,不应该抛出系统异常。",
    "service": "- Service 层应该使用构造函数注入或者 setter 注入,不要使用 @Autowired 注解注入。",
    "entity": "- Entity 类应该使用 JPA 注解进行数据库映射\n- 实体类名应该与对应的数据库表名相同。实体类应该使用注解标记主键和表名,例如:@Id、@GeneratedValue、@Table 等。",
    "repository": "- Repository 接口应该继承 JpaRepository 接口,以获得基本的 CRUD 操作",
    "ddl": "-  字段应该使用 NOT NULL 约束,确保数据的完整性"
    }
}

而随着需求的进一步演进,我们又基于 import 与相似性,添加了所需要的代码上下文、技术框架等等。

诸如于:

when (action) {
  ...
    ChatActionType.CODE_COMPLETE -> {
        val codeComplete = customPromptConfig?.autoComplete
        if (codeComplete?.instruction?.isNotEmpty() == true) {
            prompt = codeComplete.instruction
        }
        when {
            MvcUtil.isController(fileName, lang) -> {
                val spec = CustomPromptConfig.load().spec["controller"]
                if (!spec.isNullOrEmpty()) {
                    additionContext = "requirements: \n$spec"
                }
                additionContext += mvcContextService.controllerPrompt(file)
            }
          ...
         }
    }
}

而随着,我们进一步地迭代我们的功能,类似于如上的代码会变得更得更多复杂。除此,随着我们对于多语言的支持情况越来越好,我们不能再根据一个个语言创建一个复杂的 prompt。所以,我们需要思考一些新的范式。

AutoDev 新 prompt 范式

在结合了先前参考的 JetBrains AI Assistant 的设计思想之后,在编写自动测试生成的 prompt 里,我们重新设计了一部分的 prompt。一个新的 prompt 将由以下几部分构成:

  • ChatActionType。即起始的指令(instruction),如编写测试、解释代码等。

  • 特定场景要求。基于特定场景下,如 MVC 下不同分层的编写模式。

  • 技术栈上下文。根据不同语言、技术栈,生成的特定 prompt。

  • 代码上下文。精炼代码信息,以注释方式生成。

  • <代码>

  • 指令起始提示词。即用来更明确的提示 AI,人类期待的返回格式。

由此,一个最终的 prompt 示例如下(【xxx】只用于解释):

【ChatActionType】
Write unit test for following code.
【特定场景要求】
You MUST return code only, not explain.
You MUST use given-when-then style.
You MUST use should_xx style for test method name.
When testing controller, you MUST use MockMvc and test API only.
【技术栈上下文】
You are working on a project that uses Spring MVC,Spring WebFlux,JDBC to build RESTful APIs.
【代码上下文】
// class name: BookMeetingRoomRequest
// class fields: meetingRoomId
// ...
// class name: BookMeetingRoomResponse
// class fields: bookingId meetingRoomId userId startTime endTime
// ...
【代码】
```java
@PostMapping("/{meetingRoomId}/book")
    public ResponseEntity<BookMeetingRoomResponse> bookMeetingRoom(@PathVariable String meetingRoomId, @RequestBody BookMeetingRoomRequest request) {
        BookMeetingRoomResponse response = new BookMeetingRoomResponse();
        return new ResponseEntity<>(response, HttpStatus.CREATED);
    }
```
【指令起始提示词】
Start with `import` syntax here

简单来说,一个复杂的 prompt,将通过一系列的依赖注入(Intellij 扩展点)来完善上下文。

ChatActionType

即根据用户的意图而创建的指令,如解释代码、重构代码、编写测试等等:

when (this) {
    EXPLAIN -> "Explain selected $lang code"
    REVIEW -> "Code Review for following $lang code"
    REFACTOR -> "Refactor the following $lang code"
    CODE_COMPLETE -> "Complete $lang  code, return rest code, no explaining"
    WRITE_TEST -> "Write unit test for following $lang code"
    FIX_ISSUE -> "Help me fix this issue"
    GEN_COMMIT_MESSAGE -> """suggest 10 commit messages based on the following diff:..."""
    CREATE_DDL -> "create ddl"
    CREATE_CHANGELOG -> "generate release note"
    CHAT -> ""
}

随后,根据不同的类型,添加对应的指令要求,诸如于 MVC 分层下,代码应该如何编写、命令风格等等。

特定场景要求

再根据不同的场景要求,诸如于在编写 Java 的 Controller 测试时,我们期望以 MockMVC 作为 API 测试框架来生成,而在编写 Service 测试时,我们期望以 Mockito 作为 Mock 的框架来生成测试。于是乎,一个对应的代码便是:

class JavaTestContextProvider : ChatContextProvider {
override fun isApplicable(project: Project, creationContext: ChatCreationContext): Boolean {
    return creationContext.action == ChatActionType.WRITE_TEST
}
override fun collect(project: Project, creationContext: ChatCreationContext): List<ChatContextItem> {
   ...
}

随后,再根据不同的类型,如 Controller、Service 提供对应的 prompt。

技术栈上下文

相似的,为了生成这一句 prompt You are working on a project that uses Spring MVC,Spring WebFlux,JDBC to build RESTful APIs. ,我们需要从依赖管理工具/构建工具(如 Gradle、Package.json)中获取项目框架,并只列出关键的技术栈,以生成符合项目技术栈的代码。也就是上述 prompt 中的: Spring MVC,Spring WebFlux,JDBC 。

所以,代码实现起来便类似于:

override fun isApplicable(project: Project, creationContext: ChatCreationContext): Boolean {
    return hasProjectLibraries(project)
}
private fun hasProjectLibraries(project: Project): Boolean {
    prepareLibraryData(project)?.forEach {
        if (it.groupId?.contains("org.springframework") == true) {
            return true
        }
    }
    return false
}

详细见相关的代码。

代码上下文

即与当前代码相关的代码,为了降低无用 prompt 的影响,我们并没有完全采用与 GitHub Copilot 一致的 Jaccard Similarity 方式来构建,而是通过两种策略。

  • 在关键场景下,如 CRUD 代码场景,通过 import + 选择代码的引用代码来呈现,并使用类 UML 的方式来减少上下文。

  • 在其它场景下,参考(复制) JetBrains AI Assistant 的相似 chunk 实现,以作为代码的一部分。

这部分大家都比较熟悉了。唯一差异的点是,如何支持多语言,这一点可以自己去看代码中的实现。

指令起始提示词

在针对于大的新特性时,Java 程序员通常会创建一个新的 Controller 等等的方式实现;而针对于功能点优化时,我们通常会修改现有函数,或者添加新的函数。

所以,在 AutoDev 的自动测试生成里,为了让 LLM 更能理解,我们添加了一个尾提示词。

if (!testContext.isNewFile) {
    "Start test code with `@Test` syntax here:  \n"
} else {
    "Start ${testContext.testClassName} with `import` syntax here:  \n"
}

简单来说,就是让 GPT 知道接下来应该做什么,避免在过程中因为各种 prompt 失焦。

总结

由 ChatGPT 总结

AutoDev 0.8项目在持续开发中不断演进,特别关注prompt的优化与重构。通过模式化的方式重新设计了新的上下文工程体系,实现了更智能、更人性化的代码提示与生成。新的prompt范式结合了ChatActionType、特定场景要求、技术栈上下文、代码上下文和指令起始提示词等元素,使得AutoDev能够更好地理解开发者的意图,根据具体场景和技术栈生成符合期望的代码。该改进不仅提高了开发效率,降低了误差,还增强了AutoDev的智能性和实用性。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

探索 prompt 编码范式:如何优雅构建测试代码生成提示词? 的相关文章

随机推荐

  • 左手坐标系和右手坐标系以及Unity中的世界坐标系和本地坐标系

    一 左手坐标系和右手坐标系 左手坐标系 伸开我们的左手 掌心向外 大拇指与食指成90度 中指 无名指和小指弯曲 大拇指指向的方向就是X轴正方向 食指指向的方向就是Y轴正方向 中指 无名指和小指指向的方向就是Z轴正方向 右手坐标系 伸开我们的
  • 快速突破面试算法之双指针篇

    前言 什么是双指针 砸门用大白话来说 就是两个定位装置 那么这两个定位装置有什么用呢 那肯定去定位撒 而且更高级的是这装置上面还有摄像头 可以看见当前所在位置的情况 现在我们给两个装置各自赋予动力 先赋予相同的动力 及移动速度一样 现在这两
  • 实现 vue2 中使用 vue-i18n 实现中英文切换功能

    1 下载包 版本要对应 2的版本8可以 vue3要用到9 npm install vue i18n 8 S 2 创建i18n js文件 import Vue from vue import Element from element ui i
  • 使用dd命令制作U盘启动盘

    1 插入U盘 df h查看U盘文件系统挂载情况 然后使用umount dev sdb 卸载U盘文件系统 2 执行命令 sudo mkfs vfat I dev sdb格式化U盘为FAT格式 3 dd if iso of dev sdb bs
  • vmware ubuntu与windows共享文件夹目录不显示的一种解决方法

    问题 mnt文件夹中没有共享的文件夹目录 甚至hgfs文件夹也没有 解决方法 1 我自己摸索出来的 在上图的界面 文件夹共享 下的选项选择 已禁用 点击最下方 确认 图中没有显示 被遮住了 再打开这个虚拟机设置界面 vmware界面上方工具
  • MAC强制卸载软件 如遇“不能修改或删除“*”,因为macOS需要它”

    连上小飞机后不知道怎么就下了个 流氓软件 疯狂卸载也卸载不掉 找了一个小时的攻略终于解决了 就是下面这个 很多人推荐用AppDelete这个应用卸载 尝试了一下并没有成功 用命令 sudo rm rf 应用名称 也没有卸载掉 尝试了一下解除
  • UE4 回合游戏项目 13- 生成敌人

    在上一篇 UE4 回合游戏项目 12 添加敌人受到攻击的动画 的基础上继续完成生成敌人的功能 效果 步骤 1 打开battleScenario 战斗场景 2 创建从类生成AI这个节点 现在我们需要获取到敌人的引用 以及敌人的数量 3 创建一
  • python内装饰器

    一 内置装饰器 内置装饰器 含义 classmethod 类方法 staticmethod 静态方法 二 普通方法 回顾 定义 第一个参数为self 代表 实例本身 调用 要有实例化的过程 通过 实例对象 方法名 调用 1 定义类 clas
  • 跳跃表原理

    跳跃表原理 最近看了一种数据结构叫做skipList redis和levelDB都是用了它 Skip List是在有序链表的基础上进行了扩展 解决了有序链表结构查找特定值困难的问题 查找特定值的时间复杂度为O logn 他是一种可以代替平衡
  • MySQL sku 数据批量导入_求node.js中mysql商品sku批量更新解决方案

    前提 不使用循环 1 sku具备添加 删除 修改功能 绑定数据sku sku goods id 1MJlrjS17jQ id 103 inventory 10 price 588 sku id 15617774918 sku name 黑色
  • Vue 富文本编辑器:vue-quill-editor粘贴图片上传服务器

    Vue 富文本编辑器 vue quill editor粘贴图片上传服务器 粘贴图片 vue quill editor复制图片实际上会以 base64 形式的进行一个上传 写进数据库后会导致数据一些加载问题 解决方法 将复制的图片先上传到服务
  • matlab产生随机数或随机矩阵

    Matlab中随机数生成器主要有 betarnd 贝塔分布的随机数生成器 binornd 二项分布的随机数生成器 chi2rnd 卡方分布的随机数生成器 exprnd 指数分布的随机数生成器 frnd f分布的随机数生成器 gamrnd 伽
  • Oracle中自动生成id的函数以及注意事项

    Oracle中自动生成id的函数 sys guid SELECT sys guid FROM aTable a 注意 上面这个是可以自动生成id 但是很多时候自动生成的id会出现乱码 原因 SYS GUID 以16位RAW类型值形式返回一个
  • arcgis中显示的图元根据投影坐标系确定单位

    这个迷糊了 如果是地理坐标系 则图元为经纬度 如果是投影坐标系 则图元为米
  • ESP8266使用邮件客户端 Arduino 库发送邮件(兼容ESP32)

    使用 ESP8266 发送邮件可以方便地处理各种事情 可以让 ESP8266 直接访问您的邮件服务器 例如 Gmail Hotmail Outlook 并通过它发送邮件 但许多邮件服务器会拒绝从不同于邮件服务器的域 您的 ip 发送的邮件
  • JS中Set去重

    Set类似于数组 但成员均是唯一的 没有重复值 Set本身是一个构造函数 用来生成Set数据结构 数组去重 const arr 2 2 3 4 4 const uniqe new Set arr 2 3 4 Array from方法可以将S
  • java实现姓名、手机号和银行卡中间用*号代替

    package com util date Description 模拟各大网站充值时的数据显示 author ShengLiu date 2018 7 4 public class TestUtil 定义所有常量 public stati
  • 关于Java中ArrayList最大容量Integer.MAX_VALUE-8中的8所代表的意义

    关于为何要减8这个问题 可以从出现何种错误的角度去看 你可以在程序中试图直接分配Integer MAX VALUE大小的ArrayList ArrayList
  • Scrapy源码分析-Spiders爬虫中文文档(一)

    Spider类定义了如何爬取某个 或某些 网站 包括了爬取的动作 例如 是否跟进链接 以及如何从网页的内容中提取结构化数据 爬取item 换句话说 Spider就是您定义爬取的动作及分析某个网页 或者是有些网页 的地方 对spider来说
  • 探索 prompt 编码范式:如何优雅构建测试代码生成提示词?

    从四月份到现在 我们持续为 AutoDev 编写了一系列的功能 尽管开发了三个多月 我们一直在持续思考 并重构我们管理 prompt 的方式 在即将发布的 AutoDev 0 8 里 我们进一下完善了现有的上下文构建方式 以模式化的方式重新