目录
- 0 背景
- 1 环境配置
- 1.1 下载包
- 1.2 配置密钥
- 1.3 测试模型
- 2 解决问题
- 2.1 获取数据
- 2.2 设计Prompt
- 2.2 设计处理函数
- 2.3 开始提取
- 附全流程代码
0 背景
Datawhale AI夏令营第二期开始啦,去年有幸参与过第一期,收获很多,这次也立马参与了第二期,这一期主要是关于大模型微调实战的,之前一直想接触大模型,但是忙于毕业一直没有行动,抓住这次机会行动起来!
在当今数字化时代,企业积累了丰富的对话数据,这些数据不仅是客户与企业之间交流的记录,更是隐藏着宝贵信息的宝库。在这个背景下,群聊对话分角色要素提取成为了企业营销和服务的一项重要策略。
群聊对话分角色要素提取的理念是基于企业对话数据的深度分析和挖掘。通过对群聊对话数据进行分析,企业可以更好地理解客户的需求、兴趣和行为模式,从而精准地把握客户的需求和心理,提供更加个性化和优质的服务。这不仅有助于企业更好地满足客户的需求,提升客户满意度,还可以为企业带来更多的商业价值和竞争优势。
群聊对话分角色要素提取的研究,将企业对话数据转化为可用的信息和智能的洞察,为企业营销和服务提供了新的思路和方法。通过挖掘对话数据中隐藏的客户行为特征和趋势,企业可以更加精准地进行客户定位、推广营销和产品服务,实现营销效果的最大化和客户价值的最大化。这将为企业带来更广阔的发展空间和更持续的竞争优势。
相关链接:
基于星火大模型的群聊对话分角色要素提取挑战赛
零基础入门大模型技术竞赛-速通学习手册
1 环境配置
1.1 下载包
本项目是基于windows环境,pycharm编译器,星火认知大模型Spark3.5 Max,首先安装软件包
pip install --upgrade spark_ai_python # 这里相较于baseline版本去掉-q
国内使用:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple spark_ai_python
如果清华源版本不可用,请使用一下命令升级到最新版本:
pip install -i https://repo.model.xfyun.cn/api/packages/administrator/pypi/simple spark_ai_python --upgrade
或者,开那个解决。(最好用)
我在安装的时候出现了报错,经分析是网络问题,开那个解决了。
注:项目仅支持 Python3.8+
1.2 配置密钥
这里密钥从讯飞开发者平台申请,Datawhale还帮我们申请了200w的token,随便花!
from sparkai.llm.llm import ChatSparkLLM, ChunkPrintHandler
from sparkai.core.messages import ChatMessage
import json
#星火认知大模型Spark3.5 Max的URL值,其他版本大模型URL值请前往文档(https://www.xfyun.cn/doc/spark/Web.html)查看
SPARKAI_URL = 'wss://spark-api.xf-yun.com/v3.5/chat'
#星火认知大模型调用秘钥信息,请前往讯飞开放平台控制台(https://console.xfyun.cn/services/bm35)查看
SPARKAI_APP_ID = '' # 替换成自己的
SPARKAI_API_SECRET = '' # 替换成自己的
SPARKAI_API_KEY = '' # 替换成自己的
#星火认知大模型Spark3.5 Max的domain值,其他版本大模型domain值请前往文档(https://www.xfyun.cn/doc/spark/Web.html)查看
SPARKAI_DOMAIN = 'generalv3.5'
1.3 测试模型
baseline提供了一个函数用于测试环境以及API是否配置合理,直接运行即可
def get_completions(text):
messages = [ChatMessage(
role="user",
content=text
)]
spark = ChatSparkLLM(
spark_api_url=SPARKAI_URL,
spark_app_id=SPARKAI_APP_ID,
spark_api_key=SPARKAI_API_KEY,
spark_api_secret=SPARKAI_API_SECRET,
spark_llm_domain=SPARKAI_DOMAIN,
streaming=False,
)
handler = ChunkPrintHandler()
a = spark.generate([messages], callbacks=[handler])
return a.generations[0][0].text
# 测试模型配置是否正确
text = "你是谁?"
print(get_completions(text)) # 注意这里要添加一个print函数
注意:相较于baseline,最后一行添加了print函数,因为百度在线平台会直接打印以及输出图片。
- 直接调用了
ChatMessage()
用于获取用户输入的字符串,其中role
参数:system
用于设置对话背景,user
表示是用户的问题,assistant
表示AI的回复。content
是用户和AI的对话内容。 ChatSparkLLM()
构造了星火模型,其中streaming
参数指的是采用一次性返回结果(非流式)还是采用流式返回结果。这里简单测试,采用False
。
详细的SDK说明可以在星火的Github查看。
2 解决问题
2.1 获取数据
def read_json(json_file_path):
"""读取json文件"""
with open(json_file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
def write_json(json_file_path, data):
"""写入json文件"""
with open(json_file_path, 'w', encoding='utf-8') 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")
这里就是很普通的实现了Json文件的读取函数和写入函数。注意win环境下在读取和写入的时候要添加, encoding='utf-8'
,否则会读取失败。在许多中文Windows系统中,默认编码是gbk,而不是utf-8。
2.2 设计Prompt
baseline提供的Prompt:
# prompt 设计
PROMPT_EXTRACT = """
你将获得一段群聊对话记录。你的任务是根据给定的表单格式从对话记录中提取结构化信息。在提取信息时,请确保它与类型信息完全匹配,不要添加任何没有出现在下面模式中的属性。
表单格式如下:
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格式输出。
不要添加任何澄清信息。
输出必须遵循上面的模式。
不要添加任何没有出现在模式中的附加字段。
不要随意删除字段。
**输出:**
'''
[{{
"基本信息-姓名": "姓名",
"基本信息-手机号码": "手机号码",
"基本信息-邮箱": "邮箱",
"基本信息-地区": "地区",
"基本信息-详细地址": "详细地址",
"基本信息-性别": "性别",
"基本信息-年龄": "年龄",
"基本信息-生日": "生日",
"咨询类型": ["咨询类型"],
"意向产品": ["意向产品"],
"购买异议点": ["购买异议点"],
"客户预算-预算是否充足": "充足或不充足",
"客户预算-总体预算金额": "总体预算金额",
"客户预算-预算明细": "预算明细",
"竞品信息": "竞品信息",
"客户是否有意向": "有意向或无意向",
"客户是否有卡点": "有卡点或无卡点",
"客户购买阶段": "购买阶段",
"下一步跟进计划-参与人": ["跟进计划参与人"],
"下一步跟进计划-时间点": "跟进计划时间点",
"下一步跟进计划-具体事项": "跟进计划具体事项"
}}, ...]
'''
"""
2.2 设计处理函数
import json
class JsonFormatError(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
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
# 查看对话标签
def print_json_format(data):
"""格式化输出json格式"""
print(json.dumps(data, indent=4, ensure_ascii=False))
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
- JsonFormatError 类: 这是一个自定义异常类,继承自Python内置的Exception类。当遇到JSON格式错误时,这个异常会被抛出。它接收一个消息参数,并将其存储在message属性中。
- convert_all_json_in_text_to_dict 函数: 这个函数接受一个字符串参数text,然后扫描这个字符串,寻找JSON对象,并将它们转换为Python字典。它使用一个栈来处理嵌套的JSON对象,并只在找到匹配的括号对时才尝试解析JSON。
- print_json_format 函数: 这个函数接受一个Python字典data作为参数,并将其转换为格式化的JSON字符串,然后打印出来。它使用json.dumps函数来实现这个转换,其中indent=4用于美化输出,ensure_ascii=False允许打印非ASCII字符。
- check_and_complete_json_format 函数: 这个函数用于检查一个列表中的每个字典是否包含一组特定的键,并且这些键对应的值的类型也是正确的。
2.3 开始提取
from tqdm import tqdm
retry_count = 5 # 重试次数
result = []
error_data = []
for index, data in tqdm(enumerate(test_data)):
index += 1
is_success = False
for i in range(retry_count):
try:
res = get_completions(PROMPT_EXTRACT.format(content=data["chat_text"]))
infos = convert_all_json_in_text_to_dict(res)
infos = check_and_complete_json_format(infos)
result.append({
"infos": infos,
"index": index
})
is_success = True
break
except Exception as e:
print("index:", index, ", error:", e)
continue
if not is_success:
data["index"] = index
error_data.append(data)
write_json("output.json", result)
附全流程代码
配置好虚拟环境,下载两个json文件到dataset文件夹下即可直接使用。
"""
==========================================
@author: Seaton
@Time: 2024/6/29:下午7:19
@IDE: PyCharm
@Summary:Task01:baseline实现
==========================================
"""
from sparkai.llm.llm import ChatSparkLLM, ChunkPrintHandler
from sparkai.core.messages import ChatMessage
import json
from tqdm import tqdm
# 星火认知大模型Spark3.5 Max的URL值,其他版本大模型URL值请前往文档(https://www.xfyun.cn/doc/spark/Web.html)查看
SPARKAI_URL = 'wss://spark-api.xf-yun.com/v3.5/chat'
# 星火认知大模型调用秘钥信息,请前往讯飞开放平台控制台(https://console.xfyun.cn/services/bm35)查看
SPARKAI_APP_ID = ''
SPARKAI_API_SECRET = ''
SPARKAI_API_KEY = ''
# 星火认知大模型Spark3.5 Max的domain值,其他版本大模型domain值请前往文档(https://www.xfyun.cn/doc/spark/Web.html)查看
SPARKAI_DOMAIN = 'generalv3.5'
def get_completions(text):
messages = [ChatMessage(
role="user",
content=text
)]
spark = ChatSparkLLM(
spark_api_url=SPARKAI_URL,
spark_app_id=SPARKAI_APP_ID,
spark_api_key=SPARKAI_API_KEY,
spark_api_secret=SPARKAI_API_SECRET,
spark_llm_domain=SPARKAI_DOMAIN,
streaming=False,
)
handler = ChunkPrintHandler()
a = spark.generate([messages], callbacks=[handler])
return a.generations[0][0].text
# # 测试模型配置是否正确
# text = "你是谁?"
# print(get_completions(text)) # 注意这里要添加一个print函数
def read_json(json_file_path):
"""读取json文件"""
with open(json_file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
def write_json(json_file_path, data):
"""写入json文件"""
with open(json_file_path, 'w', encoding='utf-8') 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")
# prompt 设计
PROMPT_EXTRACT = """
你将获得一段群聊对话记录。你的任务是根据给定的表单格式从对话记录中提取结构化信息。在提取信息时,请确保它与类型信息完全匹配,不要添加任何没有出现在下面模式中的属性。
表单格式如下:
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格式输出。
不要添加任何澄清信息。
输出必须遵循上面的模式。
不要添加任何没有出现在模式中的附加字段。
不要随意删除字段。
**输出:**
'''
[{{
"基本信息-姓名": "姓名",
"基本信息-手机号码": "手机号码",
"基本信息-邮箱": "邮箱",
"基本信息-地区": "地区",
"基本信息-详细地址": "详细地址",
"基本信息-性别": "性别",
"基本信息-年龄": "年龄",
"基本信息-生日": "生日",
"咨询类型": ["咨询类型"],
"意向产品": ["意向产品"],
"购买异议点": ["购买异议点"],
"客户预算-预算是否充足": "充足或不充足",
"客户预算-总体预算金额": "总体预算金额",
"客户预算-预算明细": "预算明细",
"竞品信息": "竞品信息",
"客户是否有意向": "有意向或无意向",
"客户是否有卡点": "有卡点或无卡点",
"客户购买阶段": "购买阶段",
"下一步跟进计划-参与人": ["跟进计划参与人"],
"下一步跟进计划-时间点": "跟进计划时间点",
"下一步跟进计划-具体事项": "跟进计划具体事项"
}}, ...]
'''
"""
class JsonFormatError(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
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
# 查看对话标签
def print_json_format(data):
"""格式化输出json格式"""
print(json.dumps(data, indent=4, ensure_ascii=False))
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
retry_count = 5 # 重试次数
result = []
error_data = []
for index, data in tqdm(enumerate(test_data)):
index += 1
is_success = False
for i in range(retry_count):
try:
res = get_completions(PROMPT_EXTRACT.format(content=data["chat_text"]))
infos = convert_all_json_in_text_to_dict(res)
infos = check_and_complete_json_format(infos)
result.append({
"infos": infos,
"index": index
})
is_success = True
break
except Exception as e:
print("index:", index, ", error:", e)
continue
if not is_success:
data["index"] = index
error_data.append(data)
write_json("output.json", result)