【愚公系列】《AIGC辅助软件开发》008-面向软件开发的提示工程:如何提问才能让ChatGPT更懂你

举报
愚公搬代码 发表于 2024/10/31 11:37:30 2024/10/31
264 0 0
【摘要】 🏆 作者简介,愚公搬代码🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。🏆《近期荣誉》:2022年度博客之星TOP2,2023年度博客之星TOP2,2022年华为云十佳博主,2023年华为云十佳博主...

🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。
🏆《近期荣誉》:2022年度博客之星TOP2,2023年度博客之星TOP2,2022年华为云十佳博主,2023年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏

🚀前言

随着人工智能技术的快速发展,越来越多的开发者开始利用像ChatGPT这样的智能助手来提升工作效率和解决问题。然而,如何有效地与这些智能工具进行沟通,尤其是在软件开发领域,往往决定了最终的结果和体验。这就引出了“提示工程”(Prompt Engineering)的概念——通过优化问题的表达方式,使AI更好地理解需求和上下文。

本文将深入探讨面向软件开发的提示工程,重点介绍如何构建高效的提问策略,以便让ChatGPT更准确地理解您的意图和需求。我们将分享一些实用的技巧和示例,帮助您明确问题、提供必要的上下文,并引导AI生成更符合期待的回答。

无论您是刚入门的开发者,还是希望提高与AI互动能力的资深工程师,这篇文章都将为您提供有价值的见解和实践建议。让我们一起探索如何通过有效的提问,让ChatGPT成为您软件开发过程中的得力助手,助力项目的顺利推进!

🚀一、如何提问才能让ChatGPT更懂你

OpenAI 提供了一些关于 Prompt 的指导性建议:

  1. 使你的 Prompt 更加明确。
  2. 指定你想要的答案格式。
  3. 让模型在确定答案之前逐步思考或辩论利弊。

此外,我们还可以提供案例,并尽可能提供足够的上下文,以帮助模型更好地理解问题的背景和意图。接下来将举一些例子,让读者有更直观的感受。

🔎1.提问清晰且明确

我们通过对比,再来感受下清晰、明确的 Prompt是怎样的。

先看一个粗略的 Prompt:

请用JavaScript 写一个贪吃蛇的游戏。

在这里插入图片描述
得到html文件和js文件如下:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贪吃蛇游戏</title>
<style>
  body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    margin: 0;
    background-color: #333;
  }
  canvas {
    background-color: #000;
  }
</style>
</head>
<body>
<canvas id="gameCanvas" width="400" height="400"></canvas>
<script src="snake.js"></script>
</body>
</html>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

const gridSize = 20;
const canvasSize = canvas.width;
const tileCount = canvasSize / gridSize;

let snake = [{ x: 10, y: 10 }];
let direction = { x: 0, y: 0 };
let food = { x: 15, y: 15 };
let score = 0;

function getRandomFoodPosition() {
  const x = Math.floor(Math.random() * tileCount);
  const y = Math.floor(Math.random() * tileCount);
  return { x, y };
}

function drawRect(x, y, color) {
  ctx.fillStyle = color;
  ctx.fillRect(x * gridSize, y * gridSize, gridSize, gridSize);
}

function update() {
  const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };

  if (head.x === food.x && head.y === food.y) {
    snake.unshift(head);
    score += 10;
    food = getRandomFoodPosition();
  } else {
    snake.pop();
    snake.unshift(head);
  }

  if (head.x < 0 || head.x >= tileCount || head.y < 0 || head.y >= tileCount || snake.slice(1).some(segment => segment.x === head.x && segment.y === head.y)) {
    resetGame();
  }
}

function draw() {
  ctx.clearRect(0, 0, canvasSize, canvasSize);

  snake.forEach(segment => drawRect(segment.x, segment.y, 'lime'));
  drawRect(food.x, food.y, 'red');

  ctx.fillStyle = 'white';
  ctx.font = '16px Arial';
  ctx.fillText(`Score: ${score}`, 10, 20);
}

function resetGame() {
  snake = [{ x: 10, y: 10 }];
  direction = { x: 0, y: 0 };
  score = 0;
  food = getRandomFoodPosition();
}

window.addEventListener('keydown', (e) => {
  switch (e.key) {
    case 'ArrowUp':
      if (direction.y === 0) direction = { x: 0, y: -1 };
      break;
    case 'ArrowDown':
      if (direction.y === 0) direction = { x: 0, y: 1 };
      break;
    case 'ArrowLeft':
      if (direction.x === 0) direction = { x: -1, y: 0 };
      break;
    case 'ArrowRight':
      if (direction.x === 0) direction = { x: 1, y: 0 };
      break;
  }
});

function gameLoop() {
  update();
  draw();
}

resetGame();
setInterval(gameLoop, 100);

在这里插入图片描述
感兴趣的读者可以试试,ChatGPT立刻就能生成一个可运行的贪吃蛇游戏的代码。原因很简单,贪吃蛇是一个大众熟知的游戏,ChatGPT已经把相关源代码都存储在它的知识库了。

那么如何让 ChatGPT 输出一个定制化的贪吃蛇游戏的代码呢?我们举一个例子:

请用JavaScript 写一个贪吃蛇游戏,要求如下:
1.点击方向键时游戏开始,程序会选择一个随机的方向作为蛇的移动方向。可以用方向键控制贪吃蛇移动。
2.开始时,贪吃蛇用一个绿色的方格表示,食物用红色方格表示,食物与贪吃蛇间隔一段距离。
3.贪吃蛇每吃一点食物就增加一个坐标的长度,增加部分的色与蛇的颜色一样。
4.游戏每一局有一个积分、开始时积分为0。贪吃蛇每吃一个食物、增加2的幂次方积分。比如第一次增加1分,第二次增加2分,第五次增加16分。
5.游戏在贪吃蛇撞墙或撞到自己时结束,游戏结束时,在游戏区域显示最终的积分。
6.游戏区域是一个400x400像素的矩形区域,由20x20个方格组成。
7.所有CSS和JavaSenpt代码都写在同一个HTML文件中。

在这里插入图片描述

得到代码:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>贪吃蛇游戏</title>
    <style>
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
        }
        #gameArea {
            width: 400px;
            height: 400px;
            background-color: #000;
            display: grid;
            grid-template-columns: repeat(20, 20px);
            grid-template-rows: repeat(20, 20px);
            position: relative;
        }
        .snake {
            background-color: green;
            grid-column-start: 1;
            grid-row-start: 1;
        }
        .food {
            background-color: red;
        }
        #score {
            position: absolute;
            top: 10px;
            left: 10px;
            color: white;
            font-size: 16px;
        }
        #gameOver {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: white;
            font-size: 24px;
            display: none;
        }
    </style>
</head>
<body>
    <div id="gameArea">
        <div id="score">积分: 0</div>
        <div id="gameOver">游戏结束!<br>最终积分: <span id="finalScore"></span></div>
    </div>

    <script>
        const gameArea = document.getElementById('gameArea');
        const scoreDisplay = document.getElementById('score');
        const gameOverDisplay = document.getElementById('gameOver');
        const finalScoreDisplay = document.getElementById('finalScore');
        const gridSize = 20;
        const gridCount = 20;

        let snake = [{ x: 10, y: 10 }];
        let food = { x: 15, y: 15 };
        let direction = { x: 0, y: 0 };
        let nextDirection = { x: 0, y: 0 };
        let score = 0;
        let moveInterval;
        let moveSpeed = 200;
        let power = 1;

        document.addEventListener('keydown', startGame);

        function startGame(event) {
            if (direction.x === 0 && direction.y === 0) {
                switch (event.key) {
                    case 'ArrowUp':
                        nextDirection = { x: 0, y: -1 };
                        break;
                    case 'ArrowDown':
                        nextDirection = { x: 0, y: 1 };
                        break;
                    case 'ArrowLeft':
                        nextDirection = { x: -1, y: 0 };
                        break;
                    case 'ArrowRight':
                        nextDirection = { x: 1, y: 0 };
                        break;
                }
                direction = { ...nextDirection };
                moveInterval = setInterval(moveSnake, moveSpeed);
                document.removeEventListener('keydown', startGame);
                document.addEventListener('keydown', changeDirection);
            }
        }

        function changeDirection(event) {
            switch (event.key) {
                case 'ArrowUp':
                    if (direction.y === 0) nextDirection = { x: 0, y: -1 };
                    break;
                case 'ArrowDown':
                    if (direction.y === 0) nextDirection = { x: 0, y: 1 };
                    break;
                case 'ArrowLeft':
                    if (direction.x === 0) nextDirection = { x: -1, y: 0 };
                    break;
                case 'ArrowRight':
                    if (direction.x === 0) nextDirection = { x: 1, y: 0 };
                    break;
            }
        }

        function moveSnake() {
            direction = { ...nextDirection };
            const newHead = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };
            
            if (newHead.x < 0 || newHead.x >= gridCount || newHead.y < 0 || newHead.y >= gridCount || isCollision(newHead)) {
                endGame();
                return;
            }

            snake.unshift(newHead);

            if (newHead.x === food.x && newHead.y === food.y) {
                score += Math.pow(2, power);
                power++;
                scoreDisplay.textContent = '积分: ' + score;
                generateFood();
            } else {
                snake.pop();
            }

            drawGame();
        }

        function isCollision(position) {
            return snake.some(segment => segment.x === position.x && segment.y === position.y);
        }

        function generateFood() {
            let newFoodPosition;
            do {
                newFoodPosition = {
                    x: Math.floor(Math.random() * gridCount),
                    y: Math.floor(Math.random() * gridCount)
                };
            } while (isCollision(newFoodPosition));
            food = newFoodPosition;
        }

        function drawGame() {
            gameArea.innerHTML = `<div id="score">积分: ${score}</div><div id="gameOver">游戏结束!<br>最终积分: <span id="finalScore"></span></div>`;
            snake.forEach(segment => {
                const snakeElement = document.createElement('div');
                snakeElement.style.gridColumnStart = segment.x + 1;
                snakeElement.style.gridRowStart = segment.y + 1;
                snakeElement.classList.add('snake');
                gameArea.appendChild(snakeElement);
            });

            const foodElement = document.createElement('div');
            foodElement.style.gridColumnStart = food.x + 1;
            foodElement.style.gridRowStart = food.y + 1;
            foodElement.classList.add('food');
            gameArea.appendChild(foodElement);
        }

        function endGame() {
            clearInterval(moveInterval);
            finalScoreDisplay.textContent = score;
            gameOverDisplay.style.display = 'block';
        }

        drawGame();
    </script>
</body>
</html>

在这里插入图片描述

我们罗列了一些具体的需求,包括游戏玩法规则、展示的样式、输出的形式等。ChatGPT生成代码时,会将这些需求都考虑进去。

一开始,我们对游戏的运行过程可能不是很清楚,对ChatGPT生成的效果也不好判断,那么我们该怎么把问题描述清楚呢?通常我们用递进的方式,一边与ChatGPT交互,一边根据结果不断地调整Prompt、直到符合我们的预期。需求描述越清晰,ChatGPT的表现就越好。

🔎2.提供可参考的格式和案例说明

有时候,你的目标可能在你心中有明确的图景,但不容易用命令式的语言描述出来,这时就可以提供一些案例让ChatGPT学习。

比如,你要让ChatGPT帮助做命名实体识别,从文本中提取出人名、公司名等作为文本标签,可以用案例的方式告诉ChatGPT你想要的结果是怎样的。

阅读下面的文本,提取2种实体类型:公司名、人名
期望格式:
公司名:<逗号分隔的公司名称列表>
人名:<逗号分隔的人名列表>
##
案例文本:华为宣布突破ERP系统封锁 任正非孟晚舟发声
公司名:华为
人名:任正非,孟晚舟
##
文本:特斯拉的创始人兼CEO埃隆·马斯克(Elon Musk)也因此成为世界上最富有人,他的个人财富达到了近3000亿美元,超过了亚马逊杰夫·贝索斯(Jeff Bezos)和微软的比尔·盖茨(Bill Gates

在这里插入图片描述

ChatGPT学得很到位,它不仅理解了“公司名”和“人名”的含义,还学到了输出格式。

🔎3.提供上下文

ChatGPT的背后是大语言模型(LargeLanguage Model,LLM),它学习并吸收了全世界的知识,形成了自己的智慧。但它不了解你,包括你的想法、你的目的和你的要求。回想一下,我们在工作中给别人交代任务时,也需要考虑对方是否了解这个任务的背景如果对方不了解,你就需要把背景介绍清楚。ChatGPT也需要尽可能详细地描述你交给它的任务背景。

在写程序时,我们经常遇到错误或者运行结果不符合预期,这时候可以让ChatGPT帮忙进行分析。

🦋3.1 结果不符合预期

比如,有个简单的使用指针交换两个数的代码,运行结果不符合预期,想要ChatGPT帮忙看看。我们可以把代码复制上来,并把问题描述清楚。

我正在学习C语言,写了以下程序,想要实现a和b的交换:
但是在运行结果中,a和b并没有交换,请问为什么?
```c
#include<stdio.h>
void swap(int *a, int *b)
{
	int *k;
	k= a;a = b; b = k;
}

int main()
{
	int a=3,b=6,*x=&a,*y= &b;
	swap( x,y);
	print£( "%d,%d ",a,b);
}
```c

在这里插入图片描述
得到正确的代码

#include<stdio.h>

void swap(int *a, int *b)
{
    int k;
    k = *a;   // 将 a 指向的值存入 k
    *a = *b;  // 将 b 指向的值存入 a 指向的位置
    *b = k;   // 将 k 的值存入 b 指向的位置
}

int main()
{
    int a = 3, b = 6;
    swap(&a, &b);  // 传递 a 和 b 的地址
    printf("%d, %d\n", a, b);  // 输出交换后的值
    return 0;
}

🦋3.2 修改 bug

我故意将刚才的代码改错,让它编译不通过,我们可以这样问ChatGPT。

我正在学习C语言,我写了以下程序,想要实现a和b的交换。
但是编译报错了,错误信息是:
error: no matching function for call to 'swap'
程序代码是:
```c
#include<stdio.h>
void swap(int *a, int *b)
{
	int *k;
	k= a;a = b; b = k;
}

int main()
{
	int a=3,b=6;
	swap( a,b);
	print£( "%d,%d ",a,b);
}
请帮我看看问题在哪,如何修改?
```c

注意看,我将上面的描述分成了4段:第一段描述问题背景;第二段给出编译报错信息;第三段给出源代码,用```c把代码包裹起来,表示代码是用C语言写的;第四段向ChatGPT发问。
在这里插入图片描述

得到代码

#include<stdio.h>

void swap(int *a, int *b)
{
    int k;
    k = *a;   // 将 a 指向的值存入 k
    *a = *b;  // 将 b 指向的值存入 a 指向的位置
    *b = k;   // 将 k 的值存入 b 指向的位置
}

int main()
{
    int a = 3, b = 6;
    swap(&a, &b);  // 传递 a 和 b 的地址
    printf("%d, %d\n", a, b);  // 输出交换后的值
    return 0;
}

以上分析得很好,编译问题修改正确。更进一步地,ChatGPT认为我的期望是交换两个数,因此把不能正确交换的问题也一起修改了,表现超出预期。

🦋3.3 分析代码并加上注释

我们经常会看一些别人写的代码,比如开源代码,或者公司交接的历史项目。有的代码写得清晰明了,注释也丰富,让人如沐春风。但更多看到的是没有注释,甚至注释与代码对不上的情况。

在尝试让ChatGPT帮我分析代码之后,我发现代码阅读效率提高了,心情也愉悦了对于很长的代码文件,为了不超过 token 数量的限制,我们可以按函数接口来划分。

请分析下这段代码的功能,并加上注释:
```c
void InitDaemon()
{
	pid_t pid;
	if((pid=fork())!=0)
	{
		exit( 0);
    }

	setsid();
	signal(SIGHUP,SIG_IGN);
	signal(SIGPIPE,SIG_IGN);
	signal(SIGTTOU,SIG_IGN);
	signal(SIGTTIN,SIG_IGN);
	signal(SIGCHLD,SIG_IGN);
	struct sigaction sig;
	sig.sa_handler =SIG_IGN;
	sig,sa_flags =0;
	sigemptyset(&sig.sa_mask);
	sigaction( SGHUP,&sig,NULL);
	if((pid=fork())!=0)
	{
		exit(0);
	}
	exit(0);
	umask(0);
	setpgrp();
}
```c

在这里插入图片描述
得到代码

void InitDaemon()
{
    pid_t pid;
    
    // 第一次fork,创建子进程,父进程退出
    if((pid = fork()) != 0)
    {
        exit(0);  // 父进程退出,子进程继续执行
    }

    // 创建新会话,使子进程成为会话首进程,脱离控制终端
    setsid();

    // 忽略一些信号
    signal(SIGHUP, SIG_IGN);   // 忽略挂起信号,防止进程被终止
    signal(SIGPIPE, SIG_IGN);  // 忽略管道破裂信号
    signal(SIGTTOU, SIG_IGN);  // 忽略后台写终端信号
    signal(SIGTTIN, SIG_IGN);  // 忽略后台读终端信号
    signal(SIGCHLD, SIG_IGN);  // 忽略子进程退出信号,防止僵尸进程

    // 设置忽略信号的处理方式
    struct sigaction sig;
    sig.sa_handler = SIG_IGN;  // 设置处理程序为忽略
    sig.sa_flags = 0;  // 无附加标志
    sigemptyset(&sig.sa_mask);  // 清空信号集
    sigaction(SIGHUP, &sig, NULL);  // 忽略挂起信号
    
    // 第二次fork,创建孙子进程,子进程退出
    if((pid = fork()) != 0)
    {
        exit(0);  // 子进程退出,孙子进程继续执行
    }

    // 终止初始化进程,正式进入守护进程状态
    exit(0);

    // 将文件权限掩码设为0,以确保守护进程创建的文件具有最大的权限
    umask(0);

    // 将进程组ID设置为当前进程的PID,独立于任何终端
    setpgrp();
}

🔎4.8个OpenAl推荐的最佳实践

在使用模型时,以下8个最佳实践已被反复验证,建议在使用时遵循。当然,也鼓励根据自己的任务需求进行探索,找到适合自己的 Prompt。

  1. 使用最新的模型。

  2. 在提示开始处放置说明,并使用###或"""分隔说明和上下文。

    • 不够好:
      将下面的文本总结为最重要的要点的项目符号列表。{你的文本}

    • 比较好:
      将下面的文本总结为最重要的要点的项目符号列表。文本:"""{你的文本}"""

  3. 尽可能详细地描述所需上下文、结果、长度、格式、风格等。

    • 不够好:
      写一首关于 OpenAI的诗。

    • 比较好:
      用一位{著名诗人}的风格,写一首关于OpenAI的短诗,重点关注最近DALL-E产品的推出(DALL-E是一种文本到图像的机器学习模型),让人感到鼓舞。

  4. 给出示例以说明需求:展示所需内容的示例,这样模型更容易理解。

    • 不够好:
      提取以下文本中提到的实体。提取以下4种实体类型:公司名称、人名、具体主题和总体主题。文本:{text}

    • 比较好:
      提取下面文本中提到的重要实体。首先提取所有公司名称,然后提取所有人名,接着提取适合内容的特定主题,最后提取一般的总体主题。期望格式:
      公司名称:<逗号分隔的公司名称列表>
      人名:-||-
      具体主题:-||-
      总体主题:-||-
      文本:<text>

      当你提供特定的格式要求时,模型的响应更好。这也使得编程解析多个输出更加可靠。

  5. 先尝试让模型0样本生成,不行的话再给出少量样本试试,若还不行,再去做大量样本的训练。

    • Zero-shot (0样本):
      从下面的文本中提取关键词。文本:{text} 关键词:

    • Few-shot (少量样本):
      提供少量样本
      样例1文本:Stripe提供API,供Web开发人员使用,将付款处理集成到他们的网站和移动应用程序中。
      样例1关键词:Stripe、付款处理、API、Web开发人员、网站、移动应用程序
      ##
      样例2文本:OpenAI已经培训了先进的语言模型,非常擅长理解和生成文本。我们的API提供访问这些模型,并可用于解决几乎涉及处理语言的任何任务。
      样例2关键词:OpenAI、语言模型、文本处理、API。
      ##
      文本3:{text}
      关键词3:

      微调: 微调就是给模型更多的样本进行学习训练,这里就不举例了。

  6. 在说明中使用清晰、精确的语言。

    • 不够好:
      此产品的描述应该相当简短,仅几句话,不要太多。

    • 比较好:
      使用3~5句话来描述此产品。

  7. 不要只说不要做什么,而要说该怎么做。

    • 不够好:
      你是一个客服,当用户向你咨询网站登录问题时,请不要询问用户名和密码。

    • 比较好:
      你是一个客服,当用户向你咨询网站登录问题时,不要询问用户名和密码,请将用户转到登录帮助页面:http://www.samplewebsite.com/help/faq。

  8. 使用具体的词汇来帮助模型生成正确的代码。

    • 不够好:
      #写一个简单的Python函数#1.询问我英里数#2.将英里转换为公里

    • 比较好:
      #写一个简单的 Python 函数#1.询问我英里数#2.将英里转换为公里
      import

      在上面的代码示例中,添加“import”提示,告诉模型应该使用Python编写(类似用SELECT作为SQL语句的开头提示)。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。