4.prompt
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.contentdemo1(简单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;各个环节严谨同时应多维度进行考虑
