Datawhale | AI+X AI 夏令营2024 Task2 笔记

182 阅读9分钟

#AI夏令营 #Datawhale #夏令营

对 baseline 代码的解读

数据处理

首先原始数据是类似这样的 json

[
    {
        "chat_text": "王勇7:哈喽吴娜8,企微那边关于外部联系人上回说的沟通对接有什么反馈嘛\n王勇7:价格有没有调整\n吴娜8:目前应该是没有 具体的方案还没出来 我估计对方是突然不需要了就不搞了吧\n王勇7:[图片]\n王勇7:会话存档又有新动作了\n吴娜8:企微好友这块还没明确\n吴娜8:[图片]\n吴娜8:不知道到底啥情况hhh\n王勇7:他们腾讯内部针对代理商售卖折扣做了调整,可能只是渠道这一类\n王勇7:现在他们有很多腾讯代理商也是企微服务商,做底走单子\n王勇7:[图片]\n王勇7:看样子这条路也堵住了\n王勇7:太难了\n吴娜8:真不容易hhh\n王勇7:哎对了吴娜8,咨询你个事\n王勇7:今天辉煌有限公司这个公司咨询我们工具\n吴娜8:也是元保的 子公司\n王勇7:我记得这个公司是之前元保对接下过会话存档,他是和元保啥关系\n王勇7:哦哦\n王勇7:原来如此\n王勇7:okok\n王勇7:懂了\n吴娜8:就是一个子公司hhh\n王勇7:付款还是元保付嘛\n王勇7:还是他们自己也可以走对接\n吴娜8:都是同一批人吧 最终还是采购部采购 \n吴娜8:是不是换人了\n王勇7:对 换人了\n王勇7:那我找元保的采购老师问问吧\n吴娜8:嗯嗯 采购部新招了好几个人 \n吴娜8:其实都是一个部门的  分工有调整\n吴娜8:或者业务先咨询 后面转采购也是有可能的\n王勇7:get 还得是你呀\n王勇7:最近我们公司有一批腾讯龙年的企鹅公仔\n王勇7:给你寄一个吧\n王勇7:2024年的新款\n吴娜8:之前元保采购就两个人 我和领导 \n吴娜8:我走了之后变成了 六七个人 哈哈哈哈哈\n王勇7:[图片]\n王勇7:[图片]\n王勇7:这是一条引用/回复消息:\n"吴娜8:\n我走了之后变成了 六七个人 哈哈哈哈哈"\n------\n这说明你一个人顶六七个人,牛蛙牛蛙\n吴娜8:一把辛酸泪hhh    不用给我寄啦哈哈~别客气\n王勇7:[表情]\n王勇7:行吧行吧\n吴娜8:[表情]\n王勇7:后面企微有新动静都帮你关注,第一时间跟你说~\n吴娜8:好嘞~\n王勇7:哦了",
        "infos": [
            {
                "基本信息-姓名": "吴娜8",
                "基本信息-手机号码": "",
                "基本信息-邮箱": "",
                "基本信息-地区": "",
                "基本信息-详细地址": "",
                "基本信息-性别": "",
                "基本信息-年龄": "",
                "基本信息-生日": "",
                "咨询类型": [
                    "答疑"
                ],
                "意向产品": [],
                "购买异议点": [
                    "客户内部问题"
                ],
                "客户预算-预算是否充足": "",
                "客户预算-总体预算金额": "",
                "客户预算-预算明细": "",
                "竞品信息": "",
                "客户是否有意向": "无意向",
                "客户是否有卡点": "无卡点",
                "客户购买阶段": "项目搁置",
                "下一步跟进计划-参与人": [],
                "下一步跟进计划-时间点": "",
                "下一步跟进计划-具体事项": ""
            }
        ]
    },
    ...
]

对于测试集就是把下面的 "infos" 设置为空。"chat_text" 里面就是群聊信息,完全混杂在一起,需要分开。

昨天跑完 baseline 得到的输出结果是类似这样的 json

image.png

"infos" 和之前一样,每一条需要加一个 index

我们直接看原始数据是看不出什么的,可以先简单处理一下

import json

def read_json(json_file_path):
    """读取json文件"""
    with open(json_file_path, 'r') as f:
        data = json.load(f)
    return data

def write_json(json_file_path, data):
    """写入json文件"""
    with open(json_file_path, 'w') as f:
        json.dump(data, f, ensure_ascii=False, indent=4)

# 读取数据
train_data = read_json("dataset/train.json")
test_data = read_json("dataset/test_data.json")

# 查看对话数据
print(train_data[100]['chat_text'])

处理完就得到了更容易看清用户每一条对话的形式

image.png

prompt 拆解

之前把完整的 prompt 粘贴过来的时候会发现 makrdown 格式错乱,多半和 prompt 里面三个反引号包起来的代码段有关系,要显示正常可以把最外层的三个反引号改成四个反引号。这次直接拆成一段一段来看这个 prompt 到底在干什么。

  • 任务目标
  • 抽取数据定义
  • 抽取内容引入
  • 抽取规则强调

任务目标

你将获得一段群聊对话记录。你的任务是根据给定的表单格式从对话记录中提取结构化信息。在提取信息时,请确保它与类型信息完全匹配,不要添加任何没有出现在下面模式中的属性。

抽取数据定义

表单格式如下:
info: Array<Dict(
    "基本信息-姓名": string | "",  // 客户的姓名。
    "基本信息-手机号码": string | "",  // 客户的手机号码。
    "基本信息-邮箱": string | "",  // 客户的电子邮箱地址。
    "基本信息-地区": string | "",  // 客户所在的地区或城市。
    "基本信息-详细地址": string | "",  // 客户的详细地址。
    "基本信息-性别": string | "",  // 客户的性别。
    "基本信息-年龄": string | "",  // 客户的年龄。
    "基本信息-生日": string | "",  // 客户的生日。
    "咨询类型": string[] | [],  // 客户的咨询类型,如询价、答疑等。
    "意向产品": string[] | [],  // 客户感兴趣的产品。
    "购买异议点": string[] | [],  // 客户在购买过程中提出的异议或问题。
    "客户预算-预算是否充足": string | "",  // 客户的预算是否充足。示例:充足, 不充足
    "客户预算-总体预算金额": string | "",  // 客户的总体预算金额。
    "客户预算-预算明细": string | "",  // 客户预算的具体明细。
    "竞品信息": string | "",  // 竞争对手的信息。
    "客户是否有意向": string | "",  // 客户是否有购买意向。示例:有意向, 无意向
    "客户是否有卡点": string | "",  // 客户在购买过程中是否遇到阻碍或卡点。示例:有卡点, 无卡点
    "客户购买阶段": string | "",  // 客户当前的购买阶段,如合同中、方案交流等。
    "下一步跟进计划-参与人": string[] | [],  // 下一步跟进计划中涉及的人员(客服人员)。
    "下一步跟进计划-时间点": string | "",  // 下一步跟进的时间点。
    "下一步跟进计划-具体事项": string | ""  // 下一步需要进行的具体事项。
)>

抽取内容引入

请分析以下群聊对话记录,并根据上述格式提取信息:

**对话记录:**
```
{content}
```

抽取规则强调

请将提取的信息以JSON格式输出。
不要添加任何澄清信息。
输出必须遵循上面的模式。
不要添加任何没有出现在模式中的附加字段。
不要随意删除字段。

**输出:**
```
[{{
    "基本信息-姓名": "姓名",
    "基本信息-手机号码": "手机号码",
    "基本信息-邮箱": "邮箱",
    "基本信息-地区": "地区",
    "基本信息-详细地址": "详细地址",
    "基本信息-性别": "性别",
    "基本信息-年龄": "年龄",
    "基本信息-生日": "生日",
    "咨询类型": ["咨询类型"],
    "意向产品": ["意向产品"],
    "购买异议点": ["购买异议点"],
    "客户预算-预算是否充足": "充足或不充足",
    "客户预算-总体预算金额": "总体预算金额",
    "客户预算-预算明细": "预算明细",
    "竞品信息": "竞品信息",
    "客户是否有意向": "有意向或无意向",
    "客户是否有卡点": "有卡点或无卡点",
    "客户购买阶段": "购买阶段",
    "下一步跟进计划-参与人": ["跟进计划参与人"],
    "下一步跟进计划-时间点": "跟进计划时间点",
    "下一步跟进计划-具体事项": "跟进计划具体事项"
}}, ...]
```

数据抽取

这里加上了这么多格式,之后大模型输出的抽取结果还是不能稳定输出格式完整的 json。

```json
[    {        "基本信息-姓名": "张三",        "基本信息-手机号码": "12345678901",        "基本信息-邮箱": "zhangsan@example.com",        "基本信息-地区": "北京市",        "基本信息-详细地址": "朝阳区某街道",        "基本信息-性别": "男",        "基本信息-年龄": "30",        "基本信息-生日": "1990-01-01",        "咨询类型": ["询价"],
        "意向产品": ["产品A"],
        "购买异议点": ["价格高"],
        "客户预算-预算是否充足": "充足",
        "客户预算-总体预算金额": "10000",
        "客户预算-预算明细": "详细预算内容",
        "竞品信息": "竞争对手B",
        "客户是否有意向": "有意向",
        "客户是否有卡点": "无卡点",
        "客户购买阶段": "合同中",
        "下一步跟进计划-参与人": ["客服A"],
        "下一步跟进计划-时间点": "2024-07-01",
        "下一步跟进计划-具体事项": "沟通具体事项"
    }
]
```

使用函数 convert_all_json_in_text_to_dict 对 json 数据进行提取

def convert_all_json_in_text_to_dict(text):
    """提取LLM输出文本中的json字符串"""
    dicts, stack = [], []
    for i in range(len(text)):
        if text[i] == '{':
            stack.append(i)
        elif text[i] == '}':
            begin = stack.pop()
            if not stack:
                dicts.append(json.loads(text[begin:i+1]))
    return dicts

有时候输出还会缺少字段,在大模型分析对话内容的过程中发现某些字段提取内容是空,于是直接连字段名都不输出了,下面的代码里 check_and_complete_json_format 功能是检查字段格式,并补全字段名

import json

class JsonFormatError(Exception):
    def __init__(self, message):
        self.message = message
        super().__init__(self.message)

def check_and_complete_json_format(data):
    required_keys = {
        "基本信息-姓名": str,
        "基本信息-手机号码": str,
        "基本信息-邮箱": str,
        "基本信息-地区": str,
        "基本信息-详细地址": str,
        "基本信息-性别": str,
        "基本信息-年龄": str,
        "基本信息-生日": str,
        "咨询类型": list,
        "意向产品": list,
        "购买异议点": list,
        "客户预算-预算是否充足": str,
        "客户预算-总体预算金额": str,
        "客户预算-预算明细": str,
        "竞品信息": str,
        "客户是否有意向": str,
        "客户是否有卡点": str,
        "客户购买阶段": str,
        "下一步跟进计划-参与人": list,
        "下一步跟进计划-时间点": str,
        "下一步跟进计划-具体事项": str
    }

    if not isinstance(data, list):
        raise JsonFormatError("Data is not a list")

    for item in data:
        if not isinstance(item, dict):
            raise JsonFormatError("Item is not a dictionary")
        for key, value_type in required_keys.items():
            if key not in item:
                item[key] = [] if value_type == list else ""
            if not isinstance(item[key], value_type):
                raise JsonFormatError(f"Key '{key}' is not of type {value_type.__name__}")
            if value_type == list and not all(isinstance(i, str) for i in item[key]):
                raise JsonFormatError(f"Key '{key}' does not contain all strings in the list")

    return data

理解了整个 baseline 代码,明天开始学习并应用 prompt 技巧改进这一部分。