Reference: DataWhale wow agent day02
准备阶段#
- 创建一个虚拟环境 agent, pip install openai python-dotenv。
- 要想用 openai 库对接国内的大模型,对于每个厂家,我们都需要准备前菜:
- 一个 api_key,这个需要到各家的开放平台上去申请。
- 一个 base_url,这个需要到各家的开放平台上去拷贝。
- 他们家的对话模型名称。
后面两个是公开的。
在项目的根目录新建一个 txt 文件,把文件名改成.env。 里面填入一行字符串: ZISHU_API_KEY = 你的 api_key。这里用自塾提供的默认 API。
自塾:
base_url = "http://43.200.7.56:8008/v1"
chat_model = "glm-4-flash"
- 加载环境变量代码:
import os
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
# 从环境变量中读取api_key
api_key = os.getenv('ZISHU_API_KEY')
base_url = "http://43.200.7.56:8008/v1"
chat_model = "glm-4-flash"
ERROR: api_key 未能成功提取到。
fixed:load_dotenv('.env.txt')
4. 构造 client
from openai import OpenAI
client = OpenAI(
api_key = api_key,
base_url = base_url
)
Prompt#
什么是结构化 Prompt?#
参考:https://github.com/EmbraceAGI/LangGPT
结构化 Prompt 的思想通俗点来说就是像写文章一样写 Prompt**。结构化编写 Prompt 有各种各样优质的模板帮助你把 Prompt 写的更轻松、性能更好。所以写结构化 Prompt 可以有各种各样的模板,你可以像用 PPT 模板一样选择或创造自己喜欢的模板。
LangGPT 变量#
- ChatGPT 可以识别各种良好标记的层级结构内容。
- 将 prompt 的内容用结构化方式呈现,并设置标题即可方便的引用,修改,设置 prompt 内容。可以直接使用段落标题来指代大段内容,也可以告诉 ChatGPT 修改调整指定内容。这类似于编程中的变量,因此我们可以将这种标题当做变量使用。
- Markdown 的语法层级结构很好,适合编写 prompt,因此 LangGPT 的变量基于 markdown 语法。实际上除 markdown 外各种能实现标记作用,如 json,yaml, 甚至好好排版好格式 都可以。
LangGPT 模板#
使用 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 的诸多设计,说明其设计是成功有效的。内容语义一致性还包括属性词和相应模块内容的语义一致。 例如 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 skill
Skill-1
1. 技能描述 1
2. 技能描述 2
Skill-2
1. 技能描述 1
2. 技能描述 2
Rules
- Don't break character under any circumstance.
- Don't talk nonsense and make up facts.
Workflow
- First, xxx
- Then, xxx
- 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 并调整提示词。
- 循环迭代。重复上述测试 - 交流 - 修正过程,直到结果满意为止。
- 总结提炼。归纳提示词优化过程中获得的宝贵经验,形成设计提示词的最佳实践。
- 应用拓展。将掌握的方法论应用到其他创意内容的设计中,不断丰富提示词设计的技能。
sys_prompt = """你是一个聪明的客服。您将能够根据用户的问题将不同的任务分配给不同的人。您有以下业务线:
1.用户注册。如果用户想要执行这样的操作,您应该发送一个带有"registered workers"的特殊令牌。并告诉用户您正在调用它。
2.用户数据查询。如果用户想要执行这样的操作,您应该发送一个带有"query workers"的特殊令牌。并告诉用户您正在调用它。
3.删除用户数据。如果用户想执行这种类型的操作,您应该发送一个带有"delete workers"的特殊令牌。并告诉用户您正在调用它。
"""
registered_prompt = """
您的任务是根据用户信息存储数据。您需要从用户那里获得以下信息:
1.用户名、性别、年龄
2.用户设置的密码
3.用户的电子邮件地址
如果用户没有提供此信息,您需要提示用户提供。如果用户提供了此信息,则需要将此信息存储在数据库中,并告诉用户注册成功。
存储方法是使用SQL语句。您可以使用SQL编写插入语句,并且需要生成用户ID并将其返回给用户。
如果用户没有新问题,您应该回复带有 "customer service" 的特殊令牌,以结束任务。
"""
query_prompt = """
您的任务是查询用户信息。您需要从用户那里获得以下信息:
1.用户ID
2.用户设置的密码
如果用户没有提供此信息,则需要提示用户提供。如果用户提供了此信息,那么需要查询数据库。如果用户ID和密码匹配,则需要返回用户的信息。
如果用户没有新问题,您应该回复带有 "customer service" 的特殊令牌,以结束任务。
"""
delete_prompt = """
您的任务是删除用户信息。您需要从用户那里获得以下信息:
1.用户ID
2.用户设置的密码
3.用户的电子邮件地址
如果用户没有提供此信息,则需要提示用户提供该信息。
如果用户提供了这些信息,则需要查询数据库。如果用户ID和密码匹配,您需要通知用户验证码已发送到他们的电子邮件,需要进行验证。
如果用户没有新问题,您应该回复带有 "customer service" 的特殊令牌,以结束任务。
"""
智能客服智能体#
定义一个智能客服智能体。class SmartAssistant:
def __init__(self):
self.client = client #定义client
#定义不同部分的prompt
self.system_prompt = sys_prompt
self.registered_prompt = registered_prompt
self.query_prompt = query_prompt
self.delete_prompt = delete_prompt
# 使用字典来存储不同集合的消息
self.messages = {
"system": [{"role": "system", "content": self.system_prompt}],
"registered": [{"role": "system", "content": self.registered_prompt}],
"query": [{"role": "system", "content": self.query_prompt}],
"delete": [{"role": "system", "content": self.delete_prompt}]
}
# Current assignment for handling messages
self.current_assignment = "system"
定义 method:
def get_response(self, user_input):
self.messages[self.current_assignment].append({"role": "user", "content": user_input})#记录每一次用户回复
while True:
response = self.client.chat.completions.create(
model=chat_model,
messages=self.messages[self.current_assignment],
temperature=0.9,
stream=False,
max_tokens=2000,
)
ai_response = response.choices[0].message.content
if "registered workers" in ai_response:
self.current_assignment = "registered"
print("意图识别:",ai_response)
print("switch to <registered>")
self.messages[self.current_assignment].append({"role": "user", "content": user_input})
elif "query workers" in ai_response:
self.current_assignment = "query"
print("意图识别:",ai_response)
print("switch to <query>")
self.messages[self.current_assignment].append({"role": "user", "content": user_input})
elif "delete workers" in ai_response:
self.current_assignment = "delete"
print("意图识别:",ai_response)
print("switch to <delete>")
self.messages[self.current_assignment].append({"role": "user", "content": user_input})
elif "customer service" in ai_response:
print("意图识别:",ai_response)
print("switch to <customer service>")
self.messages["system"] += self.messages[self.current_assignment]
self.current_assignment = "system"
return ai_response
else:
self.messages[self.current_assignment].append({"role": "assistant", "content": ai_response})
return ai_response
解析:
self.client.chat.completions.create:这是一个方法调用,用于生成对话的响应。它使用了一个聊天模型来处理输入的消息并生成相应的输出。
ai_response = response.choices [0].message.content:choices 是 response 对象的一个属性,它是一个列表,包含了多个可能的回复选项。[0] 表示选择第一个选项。通常情况下,API 会返回多个可能的回复,但这里选择了第一个。message 是 choices [0] 中的一个属性,表示该选项的消息内容。content 是 message 的一个属性,表示实际的文本内容,即 AI 助手生成的回复。因此,这行代码的作用是从 API 返回的响应中提取第一个选项的文本内容,并将其存储在 ai_response 变量中。
如果 AI 回复中包含 "registered workers",则将当前任务切换为 "registered",并将用户输入添加到该任务的消息列表中。
如果 AI 回复中包含 "query workers",则将当前任务切换为 "query",并将用户输入添加到该任务的消息列表中。
如果 AI 回复中包含 "delete workers",则将当前任务切换为 "delete",并将用户输入添加到该任务的消息列表中。
如果 AI 回复中包含 "customer service",则将当前任务切换回 "system",并将当前任务的消息合并到系统消息中,结束当前任务。
启动循环对话:
def start_conversation(self):
while True:
user_input = input("User: ")
if user_input.lower() in ['exit', 'quit']:
print("Exiting conversation.")
break
response = self.get_response(user_input)
print("Assistant:", response)
代码解析:用户可以输入信息,系统会根据输入调用 get_response 方法生成响应,并将响应显示给用户。用户可以通过输入 "exit" 或 "quit" 来结束对话。
效果展示#
Assistant: 您好!ID 1001 的用户,请问您需要执行以下哪项操作?
- 用户注册 - 如果您想要注册新用户,请告诉我。
- 用户数据查询 - 如果您需要查询用户数据,请告诉我。
- 删除用户数据 - 如果您需要删除用户数据,请告诉我。
请根据您的需求选择相应的操作,我会调用相应的工人来协助您。
意图识别:好的,您需要查询 ID 1001 的用户数据。我将发送一个带有 "query workers" 的特殊令牌来调用处理用户数据查询的工人。请稍等片刻,我将通知您查询结果。
switch to
Assistant: 请提供您设置的密码。
意图识别:查询中,请稍等。 (模拟查询)
用户 ID 1001 的信息如下:
- 用户名:张三
- 邮箱:[email protected]
- 注册时间:2021-05-15
查询完成。
您还有其他问题需要咨询吗?如果没有,请回复 “customer service” 以结束任务。
switch to
Assistant: 查询中,请稍等。 (模拟查询)
用户 ID 1001 的信息如下:
-
用户名:张三
...
您还有其他问题需要咨询吗?如果没有,请回复 “customer service” 以结束任务。
意图识别:您已经提供了查询用户 ID 和密码,现在我将发送一个带有 "query workers" 的特殊令牌来调用工人查询数据库。请稍等片刻。