2

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

 1 year ago
source link: https://www.phodal.com/blog/how-to-encoding-prompt/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Posted by: Phodal Huang July 30, 2023, 8:11 p.m.

从四月份到现在,我们持续为 AutoDev 编写了一系列的功能。尽管开发了三个多月,我们却一直思考、并重构我们管理 prompt 的方式。

在即将发布的 AutoDev 0.8 里,我们进一下完善了现有的上下文构建方式,以模式化的方式重新思考并设计了新的上下文工程体系。

而测试生成正是我们的第一个新试点,以探索新的 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),如编写测试、解释代码等。
  • 基础要求。
  • 场景化要求。基于特定场景下,匹配新的提示词。
  • ChatContextProvider。根据不同语言、技术栈,生成的特定 prompt。
  • CodeContextProvider。精炼代码信息,以注释方式生成。
  • <代码>
  • 指令起始提示词。即用来更明确的提示 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

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

```kotlin
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的智能性和实用性。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK