代码采纳率从 22% 到 33%,通义灵码辅助数据库智能编码实践

作者:蒲松洋

通义灵码本质上是一个AI agent,它已经进行了大量的优化。然而,为了更完美或有效地调用模型的潜在能力,我们在使用时仍需掌握一些技巧。通常,大多数人在使用通义灵码时会直接上手,这是 AI agent 的一个优势,即 zero shot 使用,无需任何上下文即可直接使用通义灵码的能力。

例如,如果你熟悉通义灵码,@workspace 针对整个代码仓库提出问题时,通义灵码将立即帮助你总结。在这种情况下,我们通常将其称为 zero shot。例如,像"0帧起手"这样的称呼,实际上没有任何上下文,因此通义灵码所获取的信息也相对较少。在这种情况下,它更容易发散性地进行发挥。综上所述,使用 zero shot 时,我们大约只能提取到通义灵码的 70% 的能力,这类似于人类大脑中的快速思维和慢速思维。后面的内容将更详细地讨论如何优化提示词prompt,以最大限度地提取通义灵码的能力。

接手一个新项目,无从下手?

我以一个名为"Cline"的开源项目为例进行演示。它实质上是一个类似于通义灵码的AI插件。当我们面对一个开源仓库时,首要任务是对其有一个全面的理解,明确其技术栈以及整体架构。因此,我们首先需要明确这个代码仓库是做什么的。

css 复制代码
@workspace 这个代码仓库是做什么的?

然后大家可以看到通义灵码会提取本地代码仓库的一些信息,并进行本地化信息检索,将关键信息上传至通义灵码后台。接着,通义灵码将针对当前问题做出相应反应。

然而,通义灵码作为一个 AI agent,通过渐进式的增强,如学习用户的习惯不断优化其模型,能够迅速生成回复。它能告诉你当前这个代码仓库是名为 cline 的 VS code 扩展项目,并且能够提供一个AI助手。

在面对新项目时,我们常遇到对新起手项目一无所知的情况。使用IDE能够避免我们阅读大量源码来学习新项目,它为我们提供了很大的便利。向其他同学或对项目熟悉的同学请教相关知识,可能会打扰到他们,而且我们也不能保证他们是否能理解我们的水平和需求。

UnitTest

在许多项目中,通过利用通义灵码,我们能够实现跨站或跨领域的学习。在其他案例中,例如单元测试,我们也会利用单测作为指标性的事情去做。因此,我们会借助通义灵码的能力,比如对于一个从 0 到 1 的项目,如果没有既有的单元测试框架或解决方案,我们可以通过通义灵码来指导项目引入单元测试,搭建起项目中单测的基础框架脚手架,并逐步提高单测覆盖率。此外,我们还可以使用通义灵码来解释代码,即进行代码的解释性工作。

css 复制代码
@workspace 这个代码仓库如何引入单测?

针对具体的函数/类来生成单元测试:

协助重构历史代码

当我们面对历史悠久、难以理解的代码时,通常会感到无从下手。虽然这种情况比面对一个完全陌生的代码仓库要稍好一些,但使用通义灵码可以显著减少阅读源码的量。

通过将 renderRigthMenuButton 拆分成多个子函数使其变得更小和更容易阅读,注意,拆分不能改变原函数的逻辑和完整性。

排查错误

解释 TypeScript 编译器错误:TS2345: Argument of type '"loading" | { [key: string]: CompletionX; }' is not assignable to parameter of type '{ [key: string]: CompletionX; }'

当然,对于工程师而言,一旦熟悉了通义灵码,便能自然地将其应用于实践,例如编写浏览器扩展。然而,我今天更想强调的是关于使用技巧。

对于大部分人来说,灵码的许多方法是直觉性的,可以迅速上手。但若要最大限度地利用灵码的剩余生产力,对 Prompt 的 engineer 在工程上有所了解是必要的。

我之前看过很多的文章介绍,因为现在大家对大语言模型有一个普遍的看法,即大模型自身生成的回答往往会趋于平庸。这是因为平庸的回答方式类似于我们的大脑,如果我们要进行深入思考,需要消耗更多的脑力。对于大语言模型来说,若要进行深度思考,将需要更多的 GPU 资源和电力。因此,其回答会倾向于平庸。但要激发它的潜力,我们需要给予一些触发,引导它进行深度思考。所以在使用技巧上,有一些基本的策略。

使用技巧

why?:大模型的回答趋向平庸 70%,我们需要激发它的潜力 30%。

基本策略:专注(Focus);举例(One-Shot,Few-Shot);让模型思考(Chain-Of-Thought);角色扮演(Role Playing);

专注(Focus)

1. 使用明确、精准的语言

技巧:避免模糊、冗余含糊的表达,确保指令清晰、无歧义,尽量精准。

Bad Case:尝试创建一个程序,能处理一些数字计算任务。

Good Case:编写一个Python程序,实现对两个浮点数进行加、减、乘、除四则运算,并提供用户友好的命令行界面。

2. 明确数据类型和格式要求

技巧:详细说明输入参数、返回值以及中间结果的数据类型、结构和格式。

Bad Case:编写一个函数,处理一串数字。

Good Case:使用 TypeScript 编写一个名为sum_of_even_numbers的函数,接受一个类型为number[]的参数numbers,返回列表中所有偶数之和。返回值类型为number类型。

使用例子(One-Shot,Few-Shot)

1. 提供具体的输入/输出实例

技巧:在与通义灵码交互时,务必提供实际的输入数据和预期输出结果,以帮助通义灵码直观理解你的测试用例需求,确保其生成的代码能准确覆盖所需测试场景。

Bad Case:编写一个测试用例,验证一个反转字符串函数的行为。

Good Case:编写一个测试用例,验证反转字符串函数 reverse_string 的行为:

  • 测试函数名称:test_reverse_string
  • 输入数据:待反转字符串"Hello, World!"
  • 预期输出:函数返回结果"!dlroW ,olleH"

2. 描述异常情况及处理方式

技巧:指出可能遇到的异常情况及期望的错误处理策略。

Bad Case:编写一个函数,读取指定路径下的 CSV 文件,并将其内容转换成 string[][]

Good Case:使用 TypeScript 编写一个名为read_csv_to_tabular的函数,接受一个类型为string的参数filePath,表示要读取的CSV文件路径。函数尝试打开并解析该文件,将其内容转换为二维字符串数组并返回。在处理过程中,考虑并妥善处理以下以后情况:

  • 文件不存在:当指定路径的文件不存在时,抛出 FileNotFoundError 错误,并附带提示信息
  • 无权限打开文件:如果用户没有权限访问或读取文件,抛出 PermissionError 错误,并附带提示信息
  • CSV 解析错误:在解析 CSV 文件时,如果遇到不符合CSV格式规范的情况,抛出 ParseError 错误,并附带提示信息
  • 对其他错误,抛出自定义错误 CSVReadError,并附带原始错误信息
scala 复制代码
// read_csv_to_tabular.ts
import fs from 'fs';
import parse from 'csv-parse';
import { CSVReadError, FileNotFoundError, PermissionError, ParseError } from './errors';

interface CSVRow {
  [key: string]: string;
}

export async function read_csv_to_tabular(filePath: string): Promise<string[][]> {
  return new Promise((resolve, reject) => {
    fs.access(filePath, fs.constants.R_OK, (err) => {
      if (err) {
        if (err.code === 'ENOENT') {
          reject(new FileNotFoundError(`File not found: ${filePath}`));
        } else if (err.code === 'EACCES') {
          reject(new PermissionError(`Permission denied when accessing file: ${filePath}`));
        } else {
          reject(new CSVReadError(`Failed to open file: ${err.message}`, err));
        }
        return;
      }

      fs.createReadStream(filePath)
        .pipe(parse({ delimiter: ',' }))
        .on('data', (row: CSVRow) => {
          resolve(row);
        })
        .on('error', (parseError) => {
          reject(new ParseError(`Failed to parse CSV: ${parseError.message}`, parseError));
        })
        .on('end', () => {
          resolve([]);
        });
    });
  });
}

// errors.ts
class CSVReadError extends Error {}
class FileNotFoundError extends CSVReadError {}
class PermissionError extends CSVReadError {}
class ParseError extends CSVReadError {}

3. 增加强调词和强调符号

技巧:使用强调词汇或突出关键指令,确保灵码充分重视并准确执行。

Bad Case:编写一个测试脚本,使用 Jest 测试框架和 React 组件进行单元测试。

Good Case:请务必 编写一个详尽的测试脚本,利用业界领先的 Jest 测试框架核心 React 组件进行全面的单元测试。遵循一下强调要点:

  • 务必覆盖 所有公共方法(包括生命周期方法、自定义时间处理器)和重要的私有辅助方法,确保高代码覆盖率 (至少达到95%)
  • 正确模拟 组件的依赖项(如 Redux store、API 调用、第三方库等),使用合适的 Jest Mocking 工具 (如jest.fn()jest.mock())和测试双射(Test Doubles)。
  • 严格执行 React 组件的渲染输出验证,利用 Jest 的 Snapshot Testing 功能或 Enzyme/Jest-React 的快照测试,确保 UI 结构与样式的一致性。
  • 全面检查 组件的交互行为,通过触发事件、模拟异步响应等方式,验证状态变更、回调触发、路由跳转等预期行为。
  • 遵循最佳实践,保持测试用例的原子性、独立性和可读性,合理组织测试文件结构,使用 describe()it() 块划分测试。
  • 确保用例稳定性,避免因外部以来变化导致的测试失败,定期更新并维护测试数据及 Mock 数据。

4. 撰写模块化的Prompt

技巧:将 Prompt 分为明确的模块,如任务描述、输入数据、预期输出、约束条件等,提升代码可读性和交互效率。

Bad Case:编写一个用户登录功能的界面

Good Case:实现一个使用用邮件和密码的登录界面,登录的后端接口为/api/users/login,接受的JSON请求:

perl 复制代码
{
  "email": "[email protected]",
  "password": "secret"
}

接口返回:

  • 成功(HTTP 200):返回 access_token
  • 失败(HTTP 401):返回错误信息

约束:

  • 验证:检查 email 格式,验证非空密码
  • token 保存:登录成功后,检查 cookie 中是否设置相同的 token

让模型思考(Chain-Of-Thought)

基于代码上下文来提问

技巧:在与通义灵码交互时,附带相关代码片段、报错堆栈或项目相关背景信息,有助于模型更好的理解任务需求,生成符合现在代码结构和项目逻辑的代码,甚至可以一键帮助你排查问题并输出相关优化代码

Bad Case:仅提供孤立的编程任务要求,如"实现一个函数,计算两个矩阵的乘法"

Good Case:在以下矩阵操作类中,添加一个名为 matrix_multiply 的方法,实现两个同类型矩阵的乘法运算。请确保与已有的 add、 substract 方法保持一致的接口风格。并解释一下你的思路。

角色扮演(Role Playing)

在文件头设定一个整体和概述的目标

Bad Case: 新建空白文件,直接使用灵码生成深入细节的函数

Good Case:在新建空白文件中,先在文件头使用注释来描述背景、整体期望完成的目标、大体逻辑伪代码等;然后再深入具体的细节来生成代码。在文件头设定一个整体的目标描述,有助于灵码在生成具体细节代码时有整体基调背景作为上下文。

总结

关于前面所提到的 zero shot 的一些 prompt 提示词,在了解了如何提高通义灵码回复质量的技巧后,大家也可以尝试对 zero shot 的内容进行修改,以获得更高质量的结果。在此 unit test 中,也欢迎大家介绍自己的背景。

例如:我是一名前端新人,基于这个背景我如何给这个代码仓库如何引入单测,也请你解释一下你的思考过程?此外,在使用这些技巧时,通义灵码也具备上下文记忆能力。这意味着你可以连续提问,这样它就能结合上下文给出更好的回答,或者说是能够根据上下文进行推演。

另外,我想提醒大家注意的是,在模型生成回答之后,还有一个"Retry"选项。许多用户可能倾向于忽略这个按钮,然而,与通义灵码的开发人员交流时,他们告诉我,通过点击"Retry",模型能够进行升级。或者说,当前的回答可能是基于较浅层的输入。如果希望得到更深入或更慢的回答,或者调度一个参数量更大的模型,可以通过点击"Retry"来实现。尽管更大的模型并不一定总是能返回更好的结果,但它们的思维过程和开放性通常会更高,因为它们拥有更多的参数。

我使用通义灵码的过程分为三个阶段:首先确保团队所有成员都开始使用以提高渗透率,利用其代码自动生成和智能补全功能提升效率;其次,通过经验和技巧的积累,尤其是针对前端外包团队的培训,持续提升采纳率和代码生成占比;最后,利用通义灵码的 RAG 能力,结合团队特定的领域知识和文档,进一步提高开发效率。这一策略需要团队领导者进行细分领域的工作,以及对团队成员的针对性支持。

相关推荐
海鸥816 小时前
在K8S中挂载 Secret 到 Pod
云原生·容器·kubernetes
海鸥817 小时前
K8S中若要挂载其他命名空间中的 Secret
云原生·容器·kubernetes
云上艺旅9 小时前
K8S学习之基础三十六:node-exporter部署
学习·云原生·贪心算法·kubernetes·prometheus
终端行者11 小时前
k8s自动弹性伸缩之HPA实践
云原生·容器·kubernetes
杰瑞学AI11 小时前
Devops之Docker:Docker入门
运维·nginx·spring cloud·docker·云原生·容器·devops
容器魔方11 小时前
Karmada v1.13 版本发布!新增应用优先级调度能力
云原生·容器·云计算
执键行天涯11 小时前
【云原生之K8s】k8s中某个服务的节点pods的什么时候会自动扩容? 扩容是指什么,加一个pods节点吗,扩容的形式有哪些?
云原生·容器·kubernetes
桂月二二13 小时前
云原生分布式存储:数据洪流中的时空折叠艺术
分布式·云原生
波格斯特13 小时前
Bilve 搭建手册
云原生·eureka