作者:蒲松洋
通义灵码本质上是一个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 能力,结合团队特定的领域知识和文档,进一步提高开发效率。这一策略需要团队领导者进行细分领域的工作,以及对团队成员的针对性支持。