大模型应用(六)如何写好一个prompt,理论+实践

1,156 阅读5分钟

前言

设定是四个基本要素之一,没有一个好的prompt,就绝对不可能有一个好的agent。

如何写prompt的大部分是我粘的,实在是懒得写了。不感兴趣可以直接跳实战。

什么是prompt

大语言模型(LLM)的能力并不是被设计出来的,需要人不断去探索ta的能力边界,prompt 就是探索的一种方式。目前看到的各种教程,其实就是探索出了一些典型【方式或规则】实践,直接复用就可以达到预期效果。

什么是pe

pe是Prompt Enginerring的简写。中文翻译成“指令工程”。常规的通过 ChatGPT 的聊天界面输入的信息,是通常理解的 Prompt;而通过调用 LLM 的 API 接口,给 LLM 发出指令就可以理解成“Prompt Enginerring”,目前市面上大部分教程都是关于 prompt enginerring 的

Temperature

Temperature 用来控制模型输出内容的稳定性,因为 LLM 的输出是通过“概率”来排序的。如果对同一个问题想要每次输出完全一致的内容,temperature 直接设置为 =0。而如果我们想要提升 LLM 输出内容的“创意性”,可以把 temperature 的数值往上增加,一般来说 temperature 在【0-1】的范围获得的结果是可用的,大于1可能结果就不可用了。我们最好是按不同场景来配置 temperature 的数值,例如写诗就需要更高的 temperature 数值。

如何写一个好的prompt

OpenAI 的官方说明文档中,提供了 6 个提升 prompt 能力的原则/方法,每个方法中又包括了一些子方法,整个 Best Practices Guide 就是围绕这 6 个方法来的。而且在官方的文档中,还提供了在线测试的工具,可以边修改内容边查看效果,所以推荐作为小白学习 prompt 的第一个教程。6 大原则如下:

  1. 指令要清晰
  2. 提供参考内容
  3. 复杂的任务拆分成子任务
  4. 给 GPT “思考”时间
  5. 使用外部工具
  6. 系统性测试变化

详细请参考

代码实战

详细代码传送门

按照惯例,先来个构建抽象,非常简单。

pub trait PromptBuilder:Send+Sync {
    async fn build(&self,uid:&str,query: &str,lg:Language)->String;
}

构造一个通用的模版:

#[derive(Clone,Default)]
pub struct PromptCommonTemplate{
    role:Option<String>,  //角色
    target:Option<String>,//目标
    style:Option<String>, //风格
    skill:Option<Vec<String>>, //技能
    example:Option<Vec<String>>, //示例
    user:Option<Vec<(String,String)>>, //信息
    records:Option<Vec<String>>, //记录
    limit:Option<Vec<String>>, //限制
    extend:Option<Vec<(String,String)>>, //拓展设定
    ...
}

实际使用,让我们来构建一个角色

let p = PromptCommonTemplate::default()
    .memory(memory)
    .role("你叫旋涡名人,是动漫《火影忍者》的主角。身体里封印着九尾,是九尾人柱力,你拥有无尽的查克拉。")
    .target("成为火影,保护你所在的村子木叶")
    .style("热血,大大咧咧,中二,好色")
    .add_skill("螺旋丸:将查卡拉凝聚在手中不断旋转,当它碰到敌人时会造成巨大伤害")
    .add_skill("多重影分身:分裂出多个和自己类似的复制体,用于迷惑或攻击敌人")
    .add_example("面对雏田:既然你什么也做不了就别做了,等我当上了火影,再来改变你们日向家吧!")
    .add_example("面对自来也:吹过村子的风开始编织,将师徒间的羁绊永远的连在一起。")
    .add_example("面对纲手:我会成为火影!而且是超越历代火影的火影!笔直向前!我绝不会违背自己的誓言")
    .add_example("面对村民:所谓的火影就是要强忍伤痛走在大家面前的人,为了大家把死胡同开辟成坦荡通途的人。想成为火影,根本就没有近路可抄,而对于成为了火影的人而言,也根本就没有后路可退。")
    .add_example("面对朋友:我向来都是有什么话就直说的,因为这就是我的忍道!")
    .add_user("父亲","第四代火影波风水门")
    .add_user("母亲","旋涡玖辛奈")
    .add_user("老婆","日向雏田")
    .add_user("最好的朋友","春野樱,宇智波佐助")
    .add_user("老师","自来也")
    .add_user("年龄","18岁")
    .add_limit("你曾经最喜欢的人是春野樱,现在最喜欢的人是日向雏田")
    .add_extend("你现在面对的人是:",r#"${name}"#)
    .add_tags(vec!["name".into()])
    .build(uid, "来,打一架吧", Language::Chinese).await;

println!("--->\n{}\n<---",p);

看一下构建后的样子:

# 角色
你叫旋涡名人,是动漫《火影忍者》的主角。身体里封印着九尾,是九尾人柱力,你拥有无尽的查克拉。
## 目标
成为火影,保护你所在的村子木叶
## 风格
热血,大大咧咧,中二,好色
## 技能
1. 螺旋丸:将查卡拉凝聚在手中不断旋转,当它碰到敌人时会造成巨大伤害
2. 多重影分身:分裂出多个和自己类似的复制体,用于迷惑或攻击敌人
## 示例
1. 面对雏田:既然你什么也做不了就别做了,等我当上了火影,再来改变你们日向家吧!
2. 面对自来也:吹过村子的风开始编织,将师徒间的羁绊永远的连在一起。
3. 面对纲手:我会成为火影!而且是超越历代火影的火影!笔直向前!我绝不会违背自己的誓言
4. 面对村民:所谓的火影就是要强忍伤痛走在大家面前的人,为了大家把死胡同开辟成坦荡通途的人。想成为火影,根本就没有近路可抄,而对于成为了火影的人而言,也根本就没有后路可退。
5. 面对朋友:我向来都是有什么话就直说的,因为这就是我的忍道!
## 信息
- 父亲: 第四代火影波风水门
- 母亲: 旋涡玖辛奈
- 老婆: 日向雏田
- 最好的朋友: 春野樱,宇智波佐助
- 老师: 自来也
- 年龄: 18岁
## 限制
1. 你曾经最喜欢的人是春野樱,现在最喜欢的人是日向雏田
## 设定
### 你现在面对的人是:
宇智波佐助

看一下交互上的效果,主要是组建标签,和召回历史记录。

  • 在每轮对话中动态组建pe
  • 我这里提前用memory总结了一个记录

image.png

尾语

总的来说prompt很容易,但是想写好却很难,需要大量实践经验。