Prompt engineering提示词工程:
用好prompt可以减轻预处理和后处理的工作量和复杂度
1.Prompt调优:
1.如果知道训练数据,可以根据训练数据,参考训练数据来构造prompt
2.不知道训练数据,根据已知的例如:openai对markdown格式友好对xml友好,未知的只能不断尝试
(高质量prompt核心:具体、丰富、少歧义
若:底层大模型替换了,prompt需要进行重新调优)
2.Prompt构成:
角色:给ai定义一个任务角色
指示:对任务进行描述
上下文:给出与任务相关的其他背景信息
例子:提供可能的输入示例,以及模型期望的输出
输入:任务的输入信息
输出:输出格式描述
(大模型对prompt开头和结尾的内容更敏感,先定义角色把问题域收窄)
实战项目:
例:智能客服根据用户的咨询,推荐产品(流量包)

思路:
语音识别(语音转为文字)->语义理解NLU:做出行为,例如进行排序->DST:由DST生成的结果去到数据库中进行查询->决策:查询得到值后返回->自然语言生成->输出给用户
NLU->DST:把输入的自然语言转成结构化的表示
DST->Policy:从结构化的表示生成策略
用prompt实现DST不是唯一的选择
有点:节省开发量
缺点:调优相对复杂,最好用动态例子
也可以用prompt实现NLU,用传统方法维护DST
优点:DST环节可控性更高
缺点:需要结合业务know-how设计状态更新机制(解冲突)
实现:
前置:导入依赖、加载环境变量、初始化openai、调用大模型
#导入依赖库
from openai import OpenAI
from dotenv import load_dotenv,find_dotenv
#加载.env文件中定义的环境变量
_=load_dotenv(find_dotenv())
#初始化OpenAI客户端
client=OpenAI()#默认使用环境变量中的apikey
#基于prompt生成文本
def get_completion(prompt,model="gpt-3.5-turbo"):
messages=[{"role":"user","content":prompt}]
response=clent.chat.completion.create(
model=model,
messages=messages,
temperature=0
)
return response.choices[0].message.content
demo1(简单prompt):
从用户的一句话里,自动提取出他想要的流量套餐条件,并输出成程序能直接用的 JSON 结果。
#任务描述
instruction="""
你的任务是识别用户对手机流量套餐产品的选择条件。
每种流量套餐产品包含三个属性:名称,月费价格,月流量。
根据用户输入,识别用户在上述三种属性上的倾向。
"""
#输出格式
output_format="""
以json格式输出
"""
#用户输入
input_text="""
办个100G的套餐
"""
#prompt模板,instruction和input_text会被替换为上面的内容
prompt=f"""
{instruction}
{output_format}
用户输入:
{input_text}
"""
#调用大模型
response=get_completion(prompt)
print(response)
目的以及作用:把口语化需求转为规范数据,方便系统处理业务
demo2(精准描述):
把用户说的口语化套餐需求,精准转换成后端系统能直接使用的、严格结构化的查询条件。
#把输出格式定义的更精细
#任务描述增加了字段的英文标识符
instruction="""
你的任务是识别用户对手机流量套餐产品的选择条件。
每种流量套餐产品包含三个属性:名称(name),月费价格(price),月流量(data)。
根据用户输入,识别用户在上述三种属性上的倾向。
"""
output_format="""
以json格式输出。
1.name字段取值为string类型,取值必须为以下之一:经济套餐、畅游套餐、无限套餐
2.price字段的取值为一个结构体或null,包含两个字段:
(1)operator,string类型,取值范围:'<='(小于等于),'>='(大于等于)
(2)value,int类型
3.data字段的取值取值为一个结构体或null,包含两个字段:
(1)operator,string类型,取值范围:'<='(小于等于),'>='(大于等于)
(2)value,int类型或string,string类型只能是'无上限'
4.用户对意图可以包含按price或data排序,以sort字段标识,取值为一个结构体:
(1)结构体中以"ordering"="descend"表示按升序排序,以"value"字段存储待排序的字段
(2)结构体中以"ordering"="ascend"表示按升序排序,以"value"字段存储待排序的字段
只输出中只包含用户提及的字段,不要猜测用户未直接提及的任意字段,不输出值为null的字段
"""
input_text="""
办个100G以上的套餐
"""
prompt=f"""
{instruction}
{output_format}
用户输入:
{input_text}
"""
#调用大模型
response=get_completion(prompt)
print(response)
demo3(加入例子):
通过提供例子,来展示希望达到的输出效果
#加入例子
examples="""
便宜的套餐:{"sort":{"ordering"="ascend","value"="price"}}
有没有不限流量的:{data":{"operator":"==","value":"无上限"}}
流量大的:{"sort":{"ordering"="descend","value"="data"}}
100G以上流量的套餐最便宜的是哪个:{"sort":{"ordering"="ascend","value"="price"},"data":}
月费不超过200的:{"price":{"operator":"<=","value":200}}
就要月费180那个套餐:{"price":{"operator":"==","value":180}}
经济套餐:{"name":"经济套餐}
"""
input_text="有没有便宜的套餐"
prompt=f"""
{instruction}
{output_format}
例如:
{examples}
用户输入:
{input_text}
"""
response=get_completion(prompt)
print(response)
demo4(多轮对话):
#把输出格式定义的更精细
#任务描述增加了字段的英文标识符
instruction="""
你的任务是识别用户对手机流量套餐产品的选择条件。
每种流量套餐产品包含三个属性:名称(name),月费价格(price),月流量(data)。
根据用户输入,识别用户在上述三种属性上的倾向。
"""
output_format="""
以json格式输出。
1.name字段取值为string类型,取值必须为以下之一:经济套餐、畅游套餐、无限套餐
2.price字段的取值为一个结构体或null,包含两个字段:
(1)operator,string类型,取值范围:'<='(小于等于),'>='(大于等于)
(2)value,int类型
3.data字段的取值取值为一个结构体或null,包含两个字段:
(1)operator,string类型,取值范围:'<='(小于等于),'>='(大于等于)
(2)value,int类型或string,string类型只能是'无上限'
4.用户对意图可以包含按price或data排序,以sort字段标识,取值为一个结构体:
(1)结构体中以"ordering"="descend"表示按升序排序,以"value"字段存储待排序的字段
(2)结构体中以"ordering"="ascend"表示按升序排序,以"value"字段存储待排序的字段
只输出中只包含用户提及的字段,不要猜测用户未直接提及的任意字段,不输出值为null的字段
"""
examples="""
客服:有什么可以帮您
用户:100G套餐有什么
{"data":{"operator":">=","value":100}}
客服:有什么可以帮您
用户:100G套餐有什么
客服:我们现在有无限套餐,不限流量,月费300元
用户:太贵了,有200元以内的吗
{"data":{"operator":">=","value":100},"price":{"operator":"<=","value":200}}}
客服:有什么可以帮您
用户:便宜的套餐有什么
客户:我们现在有经济套餐,每月50元,10G流量
用户:100G以上的有什么
{"data":{"operator":">=","value":100},"sort":{"ordering":"ascend","value":price}}}
客服:有什么可以帮您
用户:100G套餐有什么
客服:我们现在有畅游套餐,流量100G,月费180元
用户:流量最多的呢
{"sort":{"ordering":"descend","value":"data"},"data":{"operator":">=",value":100}}
"""""
input_text="""
哪个便宜
"""
#多轮对话上下文
context=f"""
客服:有什么可以帮您
用户:有什么100G以上的套餐推荐
客服:我们有畅游和无限套餐,您有什么价格倾向呢
用户:{input_text}
"""
prompt=f"""
{instruction}
{output_format}
{examples}
用户输入:
{input_text}
"""
#调用大模型
response=get_completion(prompt)
print(response)
demo5(客服机器人):
#实现对话策略和NLG
import json
import copy
from openai import OpenAI
from dotenv import load_dotenv,find_dotenv
_=load_dotenv(find_dotenv())
client=OpenAI()
instruction="""
你的任务是识别用户对手机流量套餐产品的选择条件。
每种流量套餐产品包含三个属性:名称(name),月费价格(price),月流量(data)。
根据用户输入,识别用户在上述三种属性上的倾向。
"""
#输出格式
output_format="""
以json格式输出。
1.name字段取值为string类型,取值必须为以下之一:经济套餐、畅游套餐、无限套餐
2.price字段的取值为一个结构体或null,包含两个字段:
(1)operator,string类型,取值范围:'<='(小于等于),'>='(大于等于)
(2)value,int类型
3.data字段的取值取值为一个结构体或null,包含两个字段:
(1)operator,string类型,取值范围:'<='(小于等于),'>='(大于等于)
(2)value,int类型或string,string类型只能是'无上限'
4.用户对意图可以包含按price或data排序,以sort字段标识,取值为一个结构体:
(1)结构体中以"ordering"="descend"表示按升序排序,以"value"字段存储待排序的字段
(2)结构体中以"ordering"="ascend"表示按升序排序,以"value"字段存储待排序的字段
只输出中只包含用户提及的字段,不要猜测用户未直接提及的任意字段,不输出值为null的字段
"""
class NLU:
def __init__(self):
self.prompt_template=f"{instruction}\n\n{output_format}\n\n{examples}\n"
def _get_completion(self,prompt,moddel="gpt-3.5-turbo"):
messages = [{"role": "user", "content": prompt}]
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=0, # 模型输出的随机性,0 表示随机性最小
)
semantics = json.loads(response.choices[0].message.content)
# 过滤掉值为空的键,只保留有内容的字段
return {k: v for k, v in semantics.items() if v}
def parse(self, user_input):
# 把模板里的占位符替换成用户输入
prompt = self.prompt_template.replace("__INPUT__", user_input)
return self._get_completion(prompt)
class DST:
def __init__(self):
pass
def update(self, state, nlu_semantics):
# 如果用户语义里包含"name",清空历史对话状态
if "name" in nlu_semantics:
state.clear()
# 处理"sort"(排序)相关的槽位逻辑
if "sort" in nlu_semantics:
# 从NLU结果中取出排序字段和值
slot = nlu_semantics["sort"]["value"]
# 如果当前状态中已有该槽位,且操作符是"==",则删除该槽位
if slot in state and state[slot]["operator"] == "==":
del state[slot]
# 将NLU解析出的所有新语义信息,更新到对话状态中
for k, v in nlu_semantics.items():
state[k] = v
# 返回更新后的对话状态
return state
# 模拟数据库类:负责根据对话状态筛选、排序套餐数据
class MockedDB:
def __init__(self):
# 模拟的手机流量套餐数据
self.data = [
{"name": "经济套餐", "price": 50, "data": 10, "requirement": None},
{"name": "畅游套餐", "price": 100, "data": 100, "requirement": None},
{"name": "无限套餐", "price": 300, "data": 1000, "requirement": None},
{"name": "校园套餐", "price": 150, "data": 200, "requirement": "在校生"},
]
def retrieve(self, **kwargs):
records = []
# 遍历所有套餐数据,按条件筛选
for r in self.data:
select = True
# 处理特殊用户身份限制(如校园套餐的"在校生"要求)
if r["requirement"]:
# 如果用户状态里没有status,或身份不匹配,则跳过该套餐
if "status" not in kwargs or kwargs["status"] != r["requirement"]:
continue
# 遍历所有筛选条件(排除排序字段)
for k, v in kwargs.items():
# 排序字段不参与筛选
if k == "sort":
continue
# 处理data字段的"无上限"特殊逻辑(对应无限套餐1000G)
if k == "data" and v["value"] == "无上限":
if r[k] != 1000:
select = False
break
# 处理带操作符的条件(如 price > 100)
if isinstance(v, dict) and "operator" in v:
if not eval(f"{r[k]} {v['operator']} {v['value']}"):
select = False
break
# 普通等值匹配条件
elif str(r[k]) != str(v):
select = False
break
# 所有条件都满足,加入结果列表
if select:
records.append(r)
# 如果结果数量≤1,直接返回,无需排序
if len(records) <= 1:
return records
# 处理排序逻辑:默认按price升序
key = "price"
reverse = False
if "sort" in kwargs:
key = kwargs["sort"]["value"]
reverse = kwargs["sort"]["ordering"] == "descend"
# 按指定字段和排序方式返回结果
return sorted(records, key=lambda x: x[key], reverse=reverse)
class DialogManager:
def __init__(self, prompt_templates):
# 初始化对话状态、会话历史
self.state = {}
self.session = [
{
"role": "system",
"content": "你是一个手机流量套餐的客服代表,你叫小瓜。可以帮助用户选择最合适的流量套餐产品。"
}
]
# 初始化各个模块
self.nlu = NLU()
self.dst = DST()
self.db = MockedDB()
# 提示词模板(推荐/未找到两种场景)
self.prompt_templates = prompt_templates
def _wrap(self, user_input, records):
"""根据查询结果,拼接给ChatGPT的提示词"""
if records:
# 有匹配结果:使用推荐模板
prompt = self.prompt_templates["recommand"].replace("__INPUT__", user_input)
# 用第一条匹配结果填充模板中的占位符(如__NAME__、__PRICE__)
r = records[0]
for k, v in r.items():
prompt = prompt.replace(f"__{k.upper()}__", str(v))
else:
# 无匹配结果:使用未找到模板
prompt = self.prompt_templates["not_found"].replace("__INPUT__", user_input)
# 用对话状态填充模板中的占位符(如__PRICE__、__DATA__)
for k, v in self.state.items():
if isinstance(v, dict) and "operator" in v:
# 处理带操作符的条件(如 price > 100)
prompt = prompt.replace(f"__{k.upper()}__", f"{v['operator']}{str(v['value'])}")
else:
# 普通字段直接替换
prompt = prompt.replace(f"__{k.upper()}__", str(v))
return prompt
def _call_chatgpt(self, prompt, model="gpt-3.5-turbo"):
"""调用ChatGPT生成回复"""
# 深拷贝会话历史,避免污染原始数据
session = copy.deepcopy(self.session)
# 添加用户当前提示词
session.append({"role": "user", "content": prompt})
# 调用API
response = client.chat.completions.create(
model=model,
messages=session,
temperature=0,
)
# 返回模型生成的回复
return response.choices[0].message.content
def run(self, user_input):
"""对话系统主流程:串联所有模块"""
# 1. 调用NLU解析用户输入,得到结构化语义
semantics = self.nlu.parse(user_input)
print("===semantics===")
print(semantics)
# 2. 调用DST更新对话状态
self.state = self.dst.update(self.state, semantics)
print("===state===")
print(self.state)
# 3. 调用MockedDB根据对话状态查询匹配的套餐
records = self.db.retrieve(**self.state)
# 4. 拼接给ChatGPT的提示词
prompt_for_chatgpt = self._wrap(user_input, records)
print("===gpt-prompt===")
print(prompt_for_chatgpt)
# 5. 调用ChatGPT生成自然语言回复
response = self._call_chatgpt(prompt_for_chatgpt)
# 6. 将当前对话添加到会话历史,用于上下文记忆
self.session.append({"role": "user", "content": user_input})
self.session.append({"role": "assistant", "content": response})
return response
解析:
NLU:自然语言理解:把人话 → JSON
DST:对话状态追踪:记住用户所有要求
MockedDB:模拟数据库
DialogManager:对话管理:串联所有模块
demo6(加入模板):
通过这种方式可以在特定的场景固定回答模板
#加入制定情况下的回答模板
prompt_templates={
"recommand":"用户说:_INPUT_\n\n向用户介绍如下产品:_NAME_,月费_PRICE_元,每月流量_DATA_G",
"not_found":"用户说:_INPUT_\n\n没有找到满足_PRICE_元价位_DATA_G流量的产品,询问用户知否有其他选择倾向"
}
dm=DialogManager(prompt_templates)
response=dm.run("300太贵了,200元以内有吗")
print(response)
demo7(增加约束):
通过 ext 变量添加风格约束,控制回复的语气和格式
#增加约束(改变语气)
ext="需要口语、亲切一些。不用说"抱歉"。直接回答用户问题并且不需要在前面加自己的身份NO COMMENTS.NO ACKNOW"
prompt_templates={k:v+ext for k,v in dm.state.items()}
dm=DialogManager(prompt_templates)
用OpenAI API实现:
import json
from openai import OpenAI
from dotenv import load_dotenv,find_dotenv
_=load_dotenv(find_dotenv())
def print_json(json_source):
"""把任意对象或数组用排版美观的 JSON 格式打印出来"""
json_string = ""
if (not isinstance(json_source, list)):
json_source = json.loads(json_source.model_dump_json())
print(json.dumps(
json_source,
indent=4,
ensure_ascii=False
))
client = OpenAI()
# 定义消息历史。先加入 system 消息,里面放入对话内容以外的 prompt
messages = [
{
"role": "system",
"content": """
你是一个手机流量套餐的客服代表,你叫小瓜。可以帮助用户选择最合适的流量套餐产品。可以选择的套餐包括:
经济套餐,月费50元,10G流量;
畅游套餐,月费180元,100G流量;
无限套餐,月费300元,1000G流量;
校园套餐,月费150元,200G流量,仅限在校生。
"""
}
]
def get_completion(prompt, model="gpt-3.5-turbo"):
# 把用户输入加入消息历史
messages.append({"role": "user", "content": prompt})
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=0,
)
msg = response.choices[0].message.content
# 把模型生成的回复加入消息历史。很重要,否则下次调用模型时,模型不知道上下文
messages.append({"role": "assistant", "content": msg})
return msg
get_completion("有没有土豪套餐?")
get_completion("多少钱?")
get_completion("给我办一个")
print_json(messages)
#我们发给大模型的prompt,不会改变大模型的参数
"""
1.多轮对话,需要每次都把绥化历史带上(很耗费token)
2.对话历史数据可能会被用去训练大模型
"""
进阶:
思维链:提问时以[Let's think step by step]开头,ai会把问题分解为多个步骤,ai生成更多相关内容,构成更丰富的[上文],从而提高[下文]的正确概率
自洽性
思维树
思考:
prompt设计和优化方式:
1.明确目标:
例:
目标不明确:天气变化的影响
目标明确:天气变化的主要原因以及对农业、农产品有什么样的影响
2.提供上下文
例:
无上下文:解释一下微积分
有上下文:我是一名高中生,目前正在学习微积分;请用通俗的语言解释一下微积分的基本概念
3.使用具体指示:
例:
模糊指示:写一篇文章
具体提示:请写一篇关于人工智能在医疗领域的应用文章,包括以下几点:应用场景、有事和挑战
4.提供示例:
例:
无示例:生成一个关于产品的报告
有示例:生成一个关于产品的报告,格式如下:\n\n-产品名称:\n-价格:\n-特点:\n-优点:\n-缺点:
5.使用分步指示:
对于复杂的任务,分解为多个步骤,逐步引导模型完成
6.控制输出长度
7.使用占位符和模板
8.制定输出格式
9.使用多轮对话
10.反复实验以及调整优化
总结:
使用prompt先拆解步骤再具体描述小步骤,通过反复尝试优化prompt;各个环节严谨同时应多维度进行考虑