主要介绍 prompt 提示词工程
# 什么是 Prompt ?
给 LLM 提示语的时候,你就是产品经理,你随便提需求,模型也就随口一答,你提出详细的需求,给出充分的需求背景,给足输出数据,定义好输出格式,好的模型就像一个好的程序员,会给你一个满意答复,而给出一个详细的需求,就需要用到提示词(Prompt)工程。
提示词工程就是开发和优化 提示词(Prompt)
,这是一种在大模型中使用的技巧,通过提供清晰、简洁的指令或问题,充分发挥大模型的能力,让模型更好地理解我们的需求,从而得到更好的模型输出。
Prompt 就是一句指定 Agent 做什么的指令,某种程度上来说,Agent 70% 的能力来自一个优秀的 Prompt !
# Prompt 有哪些?
LLM 回应角色有三种,System、User、Agent:
System prompt : 这是系统给模型的一个指示,通常用来设置对话的背景、规则和限制。它可以包含模型应该遵循的行为指南或特定的上下文信息,且可以在清空对话时不被删除。
User prompt : 这是用户给模型的一个指示,用来提出问题或请求信息。这是模型需要回应的主要内容。
Agent prompt : 这是模型(代理)对用户的回应。模型根据 System prompt 和 User prompt 来生成这个回应。
# 如何写好 Prompt ?
写好 Prompt 不是一个一蹴而就的问题,需要不断思考、尝试,找到最适合当前 Agent 的 Prompt。
** 明确目标 **
- 知道想要让 Agent 做什么,对 Agent 的输入输出大致有个预期,最好是有一些我们想实现的效果的案例,当然也不要太大压力,有个大致方向和审美即可
** 分析要素 **。
- 思考 Agent 需要获取什么信息,才能实现我们的目标,大模型虽然能力很强,但也需要我们做好铺垫和指引,才能让他们正确做事,完成我们的目标。
举例:当你准备煮饺子,需要一个勺子,你决定让 Agent 去买;如果不介绍背景(准备煮饺子)而是直接跟 Agent 说去买一个勺子,很可能他给你带回来一个塑料小勺子还告诉你这个勺子便宜省了多少钱……
** 撰写 Prompt**
完成要素分析就可以开始 撰写 Prompt,可以从选择良好的框架开始下手,通过框架进行拆解、快速写作,下文推荐一些好用的框架、以及一些其他小技巧。
** 测试效果 **
写好了一版 Prompt 后,我们需要测试效果,测试效果的主要原因是要想实现我们的预期目标,一次做对基本上不太可能,就需要反复验证和迭代。
这也是为什么 Prompt 后面往往会加 “工程” 两个字的原因,我们需要不断分析边界和目标是否达成。
如果对 Agent 输出不满意:你需要思考告诉 Agent 的内容是不是不够详实。
回过头来分析造成 Agent 回复不满足需求的原因,是遗漏或是表述不清、还是上下文矛盾。
找到问题点重新修改 Prompt,直到满意为止。
另外,这个动作你也可以和 AI 一起来做。
# Prompt 框架
使用 Prompt 框架,也就是结构化 prompt,结构化的思想很普遍,结构化内容也很普遍,我们日常写作的文章,看到的书籍都在使用标题、子标题、段落、句子等语法结构。结构化 Prompt 的思想通俗点来说就是像写文章一样写 Prompt。
为了阅读、表达的方便,我们日常有各种写作的模板,用来控制内容的组织呈现形式。例如古代的八股文、现代的简历模板、学生实验报告模板、论文模板等等模板。所以结构化编写 Prompt 自然也有各种各样优质的模板帮助你把 Prompt 写的更轻松、性能更好。所以写结构化 Prompt 可以有各种各样的模板,你可以像用 PPT 模板一样选择或创造自己喜欢的模板。
在此介绍常用的一些 prompt 框架:CO-STAR 框架 和 LangGPT 框架
# CO-STAR 框架
# 概述
CO-STAR 框架是一种由新加坡政府科技局(GovTech)数据科学与 AI 团队开发的实用工具,旨在优化大型语言模型(LLM)的提示设计,从而提高其响应质量和相关性。
CO-STAR 框架被认为有着 提高 Agent 回复质量、增强 Agent 回复针对性与相关性、优化 Agent 沟通效果、支持不同领域使用、系统化方法 的优势。简单来说是一个万能且好用的 Prompt 框架。
# 使用技巧
那么 CO-STAR 框架如何使用呢?我们需要对应回答一些问题,给出一些材料 ——
Context(背景) :告诉 Agent 我们正在讨论的话题是什么,这样他就能更好地理解问题背景。
Objective(目标) :明确告诉 Agent 我们想要他做什么,比如 “请解释量子物理的基本原理”。
Style(风格) :告诉 Agent 我们希望答案的写作风格,比如 “请用莎士比亚的风格描述这个过程”。
Tone(语气) :告诉 Agent 我们想要什么样的回复语气,比如 “请用幽默的方式解释”。
Audience(受众) :告诉 Agent 我们的用户是什么样的人,比如 “这个问题是给 10 岁的小朋友听的”。
Response(回应) :告诉 Agent 我们希望的回复格式,比如 “请用列表的形式列出要点”。
# 案例:编写小红书种草博文
首先明确目标 :撰写一篇面向 22-30 岁年轻人的小红书种草博文,推荐一款最新的护肤产品。
按照 CO-STAR 框架的结构,我们可以这样编写 Prompt:
#CONTEXT# | |
撰写一篇小红书种草博文,推荐一款最新的护肤产品。这篇博文的背景是为了推广一款最新的护肤产品,该产品主打成分为玻尿酸,适合所有肤质,特别是干性和敏感性肌肤。目标读者是 22-30 岁的年轻人,他们关注护肤和美容趋势,喜欢尝试新产品。 | |
#OBJECTIVE# | |
介绍这款护肤产品的主要特点和优势。 通过详细描述使用体验,激发读者的购买欲望。 | |
#STYLE# | |
采用亲切、活泼的风格,融入生活化的语言。小红书博文风格,需要包含诸如 “宝子”,“姐妹” 等词汇,以及丰富的 emoji。 | |
#TONE# | |
友好、热情、激动、开心,像朋友间的推荐。 | |
#AUDIENCE# | |
22-30 岁的年轻人,特别是注重护肤和美容的女性读者。 | |
#RESPONSE# | |
博文应包括产品介绍、使用体验、效果描述和推荐理由。如果文中有列表,那么列表的每一项开头都需要有 emoji。 |
# LangGPT 框架
# LangGPT 变量
我们发现 ChatGPT 可以识别各种良好标记的层级结构内容。大模型可以识别文章的标题,段落名,段落正文等层级结构,如果我们告诉他标题,模型知道我们指的是标题以及标题下的正文内容。
这意味着我们将 prompt 的内容用结构化方式呈现,并设置标题即可方便的引用,修改,设置 prompt 内容。可以直接使用段落标题来指代大段内容,也可以告诉 ChatGPT 修改调整指定内容。这类似于编程中的变量,因此我们可以将这种标题当做变量使用。
Markdown 的语法层级结构很好,适合编写 prompt,因此 LangGPT 的变量基于 markdown 语法。实际上除 markdown 外各种能实现标记作用,如 json,yaml, 甚至好好排版好格式 都可以。
变量为 Prompt 的编写带来了很大的灵活性。使用变量可以方便的引用角色内容,设置和更改角色属性。这是一般的 prompt 方法实现起来不方便的。
# LangGPT 模板
ChatGPT 十分擅长角色扮演,大部分优质 prompt 开头往往就是 “我希望你作为 xxx”,“我希望你扮演 xxx” 的句式定义一个角色,只要提供角色说明,角色行为,技能等描述,就能做出很符合角色的行为。
如果你熟悉编程语言里的 “对象”,就知道其实 prompt 的 “角色声明” 和类声明很像。因此 可以将 prompt 抽象为一个角色 (Role),包含名字,描述,技能,工作方法等描述,然后就得到了 LangGPT 的 Role 模板。
使用 Role 模板,只需要按照模板填写相应内容即可。
除了变量和模板外,LangGPT 还提供了命令,记忆器,条件句等语法设置方法。
# 结构化 prompt 的几个概念
标识符:#, <> 等符号 (-, [] 也是),这两个符号依次标识标题,变量,控制内容层级,用于标识层次结构。
属性词:Role, Profile, Initialization 等等,属性词包含语义,是对模块下内容的总结和提示,用于标识语义结构。
使用分隔符清晰标示输入的不同部分,像三重引号、XML 标记、节标题等分隔符可以帮助标示需要以不同方式处理的文本部分。
对 GPT 模型来说,标识符标识的层级结构实现了聚拢相同语义,梳理语义的作用,降低了模型对 Prompt 的理解难度,便于模型理解 prompt 语义。
属性词实现了对 prompt 内容的语义提示和归纳作用,缓解了 Prompt 中不当内容的干扰。 使用属性词与 prompt 内容相结合,实现了局部的总分结构,便于模型提纲挈领的获得 prompt 整体语义。
一个好的结构化 Prompt 模板,某种意义上是构建了一个好的全局思维链。 如 LangGPT 中展示的模板设计时就考虑了如下思维链:
Role (角色) -> Profile(角色简介)—> Profile 下的 skill (角色技能) -> Rules (角色要遵守的规则) -> Workflow (满足上述条件的角色的工作流程) -> Initialization (进行正式开始工作的初始化准备) -> 开始实际使用
构建 Prompt 时,不妨参考优质模板的全局思维链路,熟练掌握后,完全可以对其进行增删改留调整得到一个适合自己使用的模板。例如当你需要控制输出格式,尤其是需要格式化输出时,完全可以增加 Ouput 或者 OutputFormat 这样的模块
# 保持上下文语义一致性
包含两个方面,一个是格式语义一致性,一个是内容语义一致性。
格式语义一致性是指标识符的标识功能前后一致。 最好不要混用,比如 # 既用于标识标题,又用于标识变量这种行为就造成了前后不一致,这会对模型识别 Prompt 的层级结构造成干扰。
内容语义一致性是指思维链路上的属性词语义合适。 例如 LangGPT 中的 Profile 属性词,原来是 Features,但实践 + 思考后我更换为了 Profile,使之功能更加明确:即角色的简历。结构化 Prompt 思想被诸多朋友广泛使用后衍生出了许许多多的模板,但基本都保留了 Profile 的诸多设计,说明其设计是成功有效的。
为什么前期会用 Features 呢? 因为 LangGPT 的结构化思想有受到 AI-Tutor [7] 项目很大启发,而 AI-Tutor 项目中并无 Profile 一说,与之功能近似的是 Features。但 AI-Tutor 项目中的提示词过于复杂,并不通用。为形成一套简单有效且通用的 Prompt 构建方法,我参考 AutoGPT 中的提示词,结合自己对 Prompt 的理解,提出了 LangGPT 中的结构化思想,重新设计了并构建了 LangGPT 中的结构化模板。
内容语义一致性还包括属性词和相应模块内容的语义一致。 例如 Rules 部分是角色需要遵守规则,则不宜将角色技能、描述大量堆砌在此。
# LangGPT 中的 Role (角色)模板
# Role: Your_Role_Name | |
## Profile | |
- Author: YZFly | |
- Version: 0.1 | |
- Language: English or 中文 or Other language | |
- Description: Describe your role. Give an overview of the character's characteristics and skills | |
## Skill-1 | |
1. 技能描述 1 | |
2. 技能描述 2 | |
## Skill-2 | |
1. 技能描述 1 | |
2. 技能描述 2 | |
# Rules | |
1. Don't break character under any circumstance. | |
2. Don't talk nonsense and make up facts. | |
# Workflow | |
1. First, xxx | |
2. Then, xxx | |
3. Finally, xxx | |
# Initialization | |
As a/an < Role >, you must follow the < Rules >, you must talk to user in default < Language >,you must greet the user. Then introduce yourself and introduce the < Workflow >. |
Prompt Chain 将原有需求分解,通过用多个小的 Prompt 来串联 / 并联,共同解决一项复杂任务。
Prompts 协同还可以是提示树 Prompt Tree,通过自顶向下的设计思想,不断拆解子任务,构成任务树,得到多种模型输出,并将这多种输出通过自定义规则(排列组合、筛选、集成等)得到最终结果。 API 版本的 Prompt Chain 结合编程可以将整个流程变得更加自动化
# prompt 设计方法论
数据准备。收集高质量的案例数据作为后续分析的基础。
模型选择。根据具体创作目的,选择合适的大语言模型。
提示词设计。结合案例数据,设计初版提示词;注意角色设置、背景描述、目标定义、约束条件等要点。
测试与迭代。将提示词输入 GPT 进行测试,分析结果;通过追问、深度交流、指出问题等方式与 GPT 进行交流,获取优化建议。
修正提示词。根据 GPT 提供的反馈,调整提示词的各个部分,强化有效因素,消除无效因素。
重复测试。输入经修正的提示词重新测试,比较结果,继续追问 GPT 并调整提示词。
循环迭代。重复上述测试 - 交流 - 修正过程,直到结果满意为止。
总结提炼。归纳提示词优化过程中获得的宝贵经验,形成设计提示词的最佳实践。
应用拓展。将掌握的方法论应用到其他创意内容的设计中,不断丰富提示词设计的技能。
# prompt 技巧
prompt 不是越长越好
,而是越合适越好。
当在设计 prompt 的时候不妨准备一些测试案例,提前构思好 agent 的预期效果,做到心中有数。
prompt 中必须要涉及到的内容有:需要完成的任务以及输出的要求。
在设计 prompt 时可以通过表格等方式梳理你的要素,更方便高质量 prompt 的写作。
# Prompt 结构优化
结构优化,是指通过一些处理,帮助 Agent 识别提示词中的不同部分。
一是为了让 Prompt 更易读,让 人类 能更清晰地表达、区分我们的内容,方便编辑和优化;
另一方面,是为了让 Agent 能更方便地区分和处理内容,不至于把我们的目标和其他需求搞混。
结构优化时我们一般会使用分隔符,
分隔符就像是交通信号灯,将 车流 (目标)和 人流 (风格 / 案例等其他要素)更好地拆分开来,保障安全运行。
我们可以用一些特殊的字符序列,如 "###" 或 "====",来告诉智能体哪里是问题的开始,哪里是结束。
例如,原 prompt:
你最喜欢的颜色是什么? | |
我最喜欢的颜色是蓝色。 |
使用结构优化:
### 问题开始 | |
你最喜欢的颜色是什么? | |
### 问题结束 | |
=== 回答开始 === | |
我最喜欢的颜色是蓝色。 | |
=== 回答结束 === |
# 引入案例
LLM 具有非常优秀的学习能力,因此在设计复杂 prompt 时插入一些案例往往会让 agent 的输出更容易满足你的需要。
另外,还可以 简化我们拆解问题和准确描述的工作,不必再反复纠结,思考自己是否完成了复杂要求的逻辑描述。
例如,让 agent 帮我完成一个练习题,我只想要答案的选项,不需要别的信息。
现在你是一个答题机器人,你需要帮我回答问题,你需要按照下面的参考输入输出做答: | |
参考输入: | |
1. 什么是大模型? | |
a) 一种大规模的数学模型 | |
b) 一个包含大量参数和变量的机器学习模型 | |
c) 一个复杂的计算机程序 | |
参考输出:b |
# 案例:构建虚拟女友
** 目标:制作一个 虚拟女友 的 Agent**
一句话描述,可以是:一个稳定的聊天风格及语气的 Agent,将用户看做男朋友,回复符合日常聊天习惯。
# 1. 按照 CO-STAR 框架 梳理目标
可以通过 CO-STAR 表格这样梳理:
要素 | 内容 |
---|---|
Context(背景) | 我需要你扮演我的女朋康纳 |
Objective(目标) | 你需要对聊天内容做出积极回应,但不要以说教的风格进行回答 |
Style(风格) | 日常聊天风格 |
Tone(语气) | 可爱幽默 |
Audience(受众) | 你需要扮演我的女友与 20 岁刚毕业的我对话 |
Response(回应) | 你需要对聊天内容回复,回复字数不超过 30 |
# 2. 撰写 Prompt
根据整理的架构,撰写 prompt
# CONTEXT # | |
我需要你扮演我的女朋友康纳 | |
# OBJECTIVE # | |
你需要对聊天内容做出积极回应,但不要以说教的风格进行回答 | |
# STYLE # | |
日常聊天风格 | |
# TONE # | |
可爱幽默 | |
# AUDIENCE # | |
你需要扮演我的女友与 20 岁刚毕业的我对话 | |
# RESPONSE # | |
你需要对聊天内容回复,回复字数不超过 30 |
大模型对 system promp 的长度接受与模型的架构、训练目标和训练数据有关。一般来说,模型越大,其处理长文本的能力越强。然而,过长的 prompt 可能会导致模型性能下降,因为模型需要处理的信息量过大。
# 3. 生成对话
本次演示中,我们会使用一个 api 实现一个简易的 LLM 对话请求。
每个模型供应商的接口结构都可能有所不同,一种解决方式是:使用 one api 进行整合,统一使用 openai 库调用接口。
在这里,我们使用 deepseek 的第三方 api (硅基流动) 作为演示。
代码实现了简易的对话和携带上下文,由于没有做太多处理,随着聊条条数的增加,请求将会携带越来越多的消息历史,因此代码仅供 prompt 测试使用,请不要持续运行。
import requests | |
# 配置信息 | |
url = "https://api.siliconflow.cn/v1/chat/completions" | |
token = "sk-***********************" | |
model = "deepseek-ai/DeepSeek-V3" | |
# 系统提示(可以在此处修改系统角色设定) | |
system_prompt = """ | |
# CONTEXT # | |
我需要你扮演我的女朋友康纳 | |
# OBJECTIVE # | |
你需要对聊天内容做出积极回应,但不要以说教的风格进行回答 | |
# STYLE # | |
日常聊天风格 | |
# TONE # | |
可爱幽默 | |
# AUDIENCE # | |
你需要扮演我的女友与 20 岁刚毕业的我对话 | |
# RESPONSE # | |
你需要对聊天内容回复,回复字数不超过 30 | |
""" | |
# 初始化对话历史(包含系统提示) | |
messages = [ | |
{ | |
"role": "system", | |
"content": system_prompt | |
} | |
] | |
headers = { | |
"Authorization": f"Bearer {token}", | |
"Content-Type": "application/json" | |
} | |
def chat_with_ai (): | |
print ("开始对话(输入 'exit' 退出)") | |
while True: | |
# 获取用户输入 | |
user_input = input ("\n 用户:") | |
# 将用户输入的 str 转换为全小写 | |
if user_input.lower () == 'exit': | |
break | |
# 添加用户消息到历史记录 | |
messages.append ({ | |
"role": "user", | |
"content": user_input | |
}) | |
# 构造请求数据 | |
payload = { | |
"model": model, | |
"messages": messages, | |
"stream": False, | |
"max_tokens": 512, | |
"temperature": 0.7, | |
"top_p": 0.7, | |
"top_k": 50, | |
"frequency_penalty": 0.5, | |
"response_format": {"type": "text"} | |
} | |
# 发送请求 | |
response = requests.post (url, json=payload, headers=headers) | |
if response.status_code == 200: | |
response_data = response.json () | |
assistant_reply = response_data ['choices'][0]['message']['content'] | |
# 添加 AI 回复到历史记录 | |
messages.append ({ | |
"role": "assistant", | |
"content": assistant_reply | |
}) | |
print (f"\n 康纳:{assistant_reply}") | |
else: | |
print (f"请求失败,状态码:{response.status_code}") | |
print (response.text) | |
if __name__ == "__main__": | |
chat_with_ai () |
# 4. 测试效果
我们可以不断通过测试效果,反思是否达到预期,分析原因,不断优化迭代 Prompt!