Hanah

Hanah

wow-agent-day02 手搓一个土得掉渣的Agent

Reference: DataWhale wow agent day02

准备阶段#

  1. 创建一个虚拟环境 agent, pip install openai python-dotenv。
  2. 要想用 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"
  1. 加载环境变量代码:
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 变量#

  1. ChatGPT 可以识别各种良好标记的层级结构内容。
  2. 将 prompt 的内容用结构化方式呈现,并设置标题即可方便的引用,修改,设置 prompt 内容。可以直接使用段落标题来指代大段内容,也可以告诉 ChatGPT 修改调整指定内容。这类似于编程中的变量,因此我们可以将这种标题当做变量使用。
  3. 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

  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 设计方法论#

  1. 数据准备。收集高质量的案例数据作为后续分析的基础。
  2. 模型选择。根据具体创作目的,选择合适的大语言模型。
  3. 提示词设计。结合案例数据,设计初版提示词;注意角色设置、背景描述、目标定义、约束条件等要点。
  4. 测试与迭代。将提示词输入 GPT 进行测试,分析结果;通过追问、深度交流、指出问题等方式与 GPT 进行交流,获取优化建议。
  5. 修正提示词。根据 GPT 提供的反馈,调整提示词的各个部分,强化有效因素,消除无效因素。
  6. 重复测试。输入经修正的提示词重新测试,比较结果,继续追问 GPT 并调整提示词。
  7. 循环迭代。重复上述测试 - 交流 - 修正过程,直到结果满意为止。
  8. 总结提炼。归纳提示词优化过程中获得的宝贵经验,形成设计提示词的最佳实践。
  9. 应用拓展。将掌握的方法论应用到其他创意内容的设计中,不断丰富提示词设计的技能。
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 的用户,请问您需要执行以下哪项操作?

  1. 用户注册 - 如果您想要注册新用户,请告诉我。
  2. 用户数据查询 - 如果您需要查询用户数据,请告诉我。
  3. 删除用户数据 - 如果您需要删除用户数据,请告诉我。

请根据您的需求选择相应的操作,我会调用相应的工人来协助您。
意图识别:好的,您需要查询 ID 1001 的用户数据。我将发送一个带有 "query workers" 的特殊令牌来调用处理用户数据查询的工人。请稍等片刻,我将通知您查询结果。
switch to
Assistant: 请提供您设置的密码。
意图识别:查询中,请稍等。 (模拟查询)

用户 ID 1001 的信息如下:

查询完成。

您还有其他问题需要咨询吗?如果没有,请回复 “customer service” 以结束任务。
switch to
Assistant: 查询中,请稍等。 (模拟查询)

用户 ID 1001 的信息如下:

  • 用户名:张三
    ...
    您还有其他问题需要咨询吗?如果没有,请回复 “customer service” 以结束任务。
    意图识别:您已经提供了查询用户 ID 和密码,现在我将发送一个带有 "query workers" 的特殊令牌来调用工人查询数据库。请稍等片刻。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。