GPT编程和应用

2.3.2、实现上下文DST

在Prompt中加入上下文

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
instruction = """
你的任务是识别用户对手机流量套餐产品的选择条件。
每种流量套餐产品包含三个属性:名称(name),月费价格(price),月流量(data)。
根据对话上下文,识别用户在上述属性上的倾向。识别结果要包含整个对话的信息。
"""

# 输出描述
output_format = """
以JSON格式输出。
1. name字段的取值为string类型,取值必须为以下之一:经济套餐、畅游套餐、无限套餐、校园套餐 或 null;

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的字段。
"""
#DO NOT OUTPUT NULL-VALUED FIELD!

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="哪个便宜"
#input_text="无限量哪个多少钱"
#input_text="流量最大的多少钱"

context = f"""
客服:有什么可以帮您
用户:有什么100G以上的套餐推荐
客服:我们有畅游套餐和无限套餐,您有什么价格倾向吗
用户:{input_text}
"""

prompt = f"""
{instruction}

{output_format}

{examples}

{context}
"""

response = get_completion(prompt)
print(response)

实现NLG对话策略

我们先把刚才的能力串起来,构建一个简单的客服机器人

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# 加载环境变量
import openai
import os, json, copy

from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())  # 读取本地 .env 文件,里面定义了 OPENAI_API_KEY

openai.api_key = os.getenv('OPENAI_API_KEY')

instruction = """
你的任务是识别用户对手机流量套餐产品的选择条件。
每种流量套餐产品包含三个属性:名称(name),月费价格(price),月流量(data)。
根据用户输入,识别用户在上述三种属性上的倾向。
"""

# 输出描述
output_format = """
以JSON格式输出。
1. name字段的取值为string类型,取值必须为以下之一:经济套餐、畅游套餐、无限套餐、校园套餐 或 null;

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"字段存储待排序的字段

只输出中只包含用户提及的字段,不要猜测任何用户未直接提及的字段。
DO NOT OUTPUT NULL-VALUED FIELD! 确保输出能被json.loads加载。
"""

examples = """
便宜的套餐:{"sort":{"ordering"="ascend","value"="price"}}
有没有不限流量的:{"data":{"operator":"==","value":"无上限"}}
流量大的:{"sort":{"ordering"="descend","value"="data"}}
100G以上流量的套餐最便宜的是哪个:{"sort":{"ordering"="ascend","value"="price"},"data":{"operator":">=","value":100}}
月费不超过200的:{"price":{"operator":"<=","value":200}}
就要月费180那个套餐:{"price":{"operator":"==","value":180}}
经济套餐:{"name":"经济套餐"}
"""

class NLU:
    def __init__(self):
        self.prompt_template = f"{instruction}\n\n{output_format}\n\n{examples}\n\n用户输入:\n__INPUT__"
    def _get_completion(self, prompt, model="gpt-3.5-turbo"):
        messages = [{"role": "user", "content": prompt}]
        response = openai.ChatCompletion.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):
        if "name" in nlu_semantics:
            state.clear()
        if "sort" in nlu_semantics:
            slot = nlu_semantics["sort"]["value"]
            if slot in state and state[slot]["operator"] == "==":
                del state[slot]
        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":180,"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"]:
                if "status" not in kwargs or kwargs["status"]!=r["requirement"]:
                    continue
            for k, v in kwargs.items():
                if k == "sort":
                    continue
                if k == "data" and v["value"] == "无上限":
                    if r[k] != 1000:
                        select = False
                        break
                if "operator" in v:
                    if not eval(str(r[k])+v["operator"]+str(v["value"])):
                        select = False
                        break
                elif str(r[k])!=str(v):
                    select = False
                    break
            if select:
                records.append(r)
        if len(records) <= 1:
            return records
        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):
        if records:
            prompt = self.prompt_templates["recommand"].replace("__INPUT__",user_input)
            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)
            for k,v in self.state.items():
                if "operator" in v:
                    prompt = prompt.replace(f"__{k.upper()}__",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"):
        session = copy.deepcopy(self.session)
        session.append({"role": "user", "content": prompt})
        response = openai.ChatCompletion.create(
            model=model,
            messages=session,
            temperature=0,
        )
        return response.choices[0].message["content"]

    def run(self, user_input):
        #调用NLU获得语义解析
        semantics = self.nlu.parse(user_input)
        print("===semantics===")
        print(semantics)
        #调用DST更新多轮状态
        self.state = self.dst.update(self.state,semantics)
        print("===state===")
        print(self.state)
        #根据状态检索DB,获得满足条件的候选
        records = self.db.retrieve(**self.state)
        #拼装prompt调用chatgpt
        prompt_for_chatgpt = self._wrap(user_input, records)
        print("===gpt-prompt===")
        print(prompt_for_chatgpt)
        #调用chatgpt获得回复
        response = self._call_chatgpt(prompt_for_chatgpt)
        #将当前用户输入和系统回复维护入chatgpt的session
        self.session.append({"role": "user", "content": user_input})
        self.session.append({"role": "assistant", "content": response})
        return response

    

进阶技巧

3.1、思维链(Chain of Thoughts, CoT)

思维链,是大模型涌现出来的一种独特能力。

它是偶然被「发现」(对 OpenAI 的人在训练时没想过会这样)的。有人在提问时以「Let’s think step by step」开头,结果发现 AI 会自动把问题分解成多个步骤,然后逐步解决,使得输出的结果更加准确。

划重点:
  1. 思维链的原理,让AI生成更多相关的内容,构成更丰富的「上文」,从而提高「下文」生成更正确结果的概率
  2. 对涉及计算和逻辑推理的问题,尤为有效
  3. 用好思维链,复杂问题的结果更准确

换一个业务场景:客服质检

任务本质是检查客服与用户的对话是否有不合规的地方

  • 质检是电信运营商和金融券商大规模使用的一项技术
  • 每个涉及到服务合规的检查点称为一个质检项

我们选一个质检项来演示思维链的作用:

产品信息准确性

当向用户介绍流量套餐产品时,客服人员必须准确提及产品名称、月费价格、月流量总量、适用条件(如有)

上述信息缺失一项或多项,或信息与实时不符,都算信息不准确

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import openai
import os

from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())  # 读取本地 .env 文件,里面定义了 OPENAI_API_KEY

openai.api_key = os.getenv('OPENAI_API_KEY')

def get_completion(prompt, model="gpt-3.5-turbo"):
    messages = [{"role": "user", "content": prompt}]
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=0,  # 模型输出的随机性,0 表示随机性最小
    )
    return response.choices[0].message["content"]

instruction = """
给定一段用户与手机流量套餐客服的对话,
你的任务是判断客服介绍产品信息的准确性:

当向用户介绍流量套餐产品时,客服人员必须准确提及产品名称、月费价格和月流量总量 上述信息缺失一项或多项,或信息与实时不符,都算信息不准确

已知产品包括:

经济套餐:月费50元,月流量10G
畅游套餐:月费180元,月流量100G
无限套餐:月费300元,月流量1000G
校园套餐:月费150元,月流量200G,限在校学生办理
"""

# 输出描述
output_format = """
以JSON格式输出。
如果信息准确,输出:{"accurate":true}
如果信息不准确,输出:{"accurate":false}
"""

context = """
用户:你们有什么流量大的套餐
客服:您好,我们现在正在推广无限套餐,每月300元就可以享受1000G流量,您感兴趣吗
"""

context2 = """
用户:有什么便宜的流量套餐
客服:您好,我们有个经济型套餐,50元每月
"""

context3 = """
用户:流量大的套餐有什么
客服:我们推荐畅游套餐,180元每月,100G流量,大多数人都够用的
用户:学生有什么优惠吗
客服:如果是在校生的话,可以办校园套餐,150元每月,含200G流量,比非学生的畅游套餐便宜流量还多
"""

prompt = f"""
{instruction}

{output_format}

请一步一步分析以下对话

对话记录:
{context3}
"""

response = get_completion(prompt)
print(response)

根据对话记录,客服介绍产品信息的准确性可以分析如下:

  1. 客服介绍了畅游套餐,包括月费180元和月流量100G,这与实际产品信息相符,属于准确信息。

  2. 客服介绍了校园套餐,包括月费150元和月流量200G,并且提到了该套餐只限在校学生办理,这与实际产品信息相符,属于准确信息。

综上所述,客服介绍的产品信息是准确的。

因此,输出结果为:{“accurate”:true} 用户:流量大的套餐有什么 客服:我们推荐畅游套餐,180元每月,100G流量,大多数人都够用的 用户:学生有什么优惠吗 客服:如果是在校生的话,可以办校园套餐,150元每月,含200G流量

根据对话内容,客服介绍了两种套餐产品:畅游套餐和校园套餐。经济套餐和无限套餐没有被提及。

对于畅游套餐,客服提到了产品名称、月费价格和月流量总量,信息准确。

对于校园套餐,客服也提到了产品名称、月费价格和月流量总量,信息准确。

因此,客服介绍产品信息的准确性是正确的。

输出:{“accurate”:true} { “accurate”: false } {“accurate”:false} 根据对话内容,客服介绍的产品信息如下: …

因此,客服介绍产品信息是准确的。

输出结果为:{“accurate”:true} Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings…

3.2、自洽性(Self-Consistency)

  • 采样多个具有多样性的结果
  • 通过投票选出最终结果

doc