目录
Step1:下载相关库
!pip install --upgrade -q spark_ai_python tqdm
Step2:配置导入
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'
Step3:模型测试
一个名为
ChatSparkLLM
的大语言模型接口进行交互,通过给定的输入text
来生成模型的输出。这个代码片段的核心功能是接受用户输入的文本,然后通过模型生成对应的回复。
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 = "你好"
get_completions(text)
- 初始化
ChatSparkLLM
模型的实例。参数包括 API URL、应用 ID、API 密钥和秘密、域名等,这些都是与模型通信所需的配置信息。 - 创建了一个
ChunkPrintHandler
对象,这通常用于处理流式或分块输出。在这里,它可能负责处理生成结果的回调,但具体功能取决于ChunkPrintHandler
的定义。 - 使用
spark.generate
方法生成回复。messages
作为输入消息列表传递给模型;callbacks
列表包含handler
,它处理生成过程中的回调。 - 返回生成的第一个回复文本。假设
a.generations
是一个嵌套列表,访问[0][0].text
得到第一个生成的文本。 - 最后是测试代码:这部分代码是用来测试整个配置是否正确。它调用
get_completions
函数,并传入一个简单的中文文本 “你好”。这个调用将会触发模型生成回复,如果配置正确,应该会返回相应的中文回复。
Step4:数据读取
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")
read_json
函数:读取指定路径的 JSON 文件,并将其内容解析为 Python 字典或列表。- 使用
open
函数以只读模式 ('r'
) 打开 JSON 文件;使用json.load(f)
函数将文件内容解析成 Python 数据结构;返回解析后的数据。 write_json
函数:将 Python 数据结构写入到指定路径的 JSON 文件中。- 使用
open
函数以写模式 ('w'
) 打开 JSON 文件;使用json.dump
函数将data
序列化为 JSON 格式,并写入文件。 ensure_ascii=False
确保 JSON 文件中的非 ASCII 字符不会被转义。indent=4
使输出的 JSON 文件具有 4 个空格的缩进,以提高可读性。
Step5:Prompt设计
Prompt设计是大模型(如 GPT 系列)的核心技术之一。Prompt 本质上是一个模板或指导语,它告诉模型应该如何理解和处理输入,并生成合适的输出。在大模型的开发和应用中,精心设计 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格式输出。
不要添加任何澄清信息。
输出必须遵循上面的模式。
不要添加任何没有出现在模式中的附加字段。
不要随意删除字段。
**输出:**
```
[{{
"基本信息-姓名": "姓名",
"基本信息-手机号码": "手机号码",
"基本信息-邮箱": "邮箱",
"基本信息-地区": "地区",
"基本信息-详细地址": "详细地址",
"基本信息-性别": "性别",
"基本信息-年龄": "年龄",
"基本信息-生日": "生日",
"咨询类型": ["咨询类型"],
"意向产品": ["意向产品"],
"购买异议点": ["购买异议点"],
"客户预算-预算是否充足": "充足或不充足",
"客户预算-总体预算金额": "总体预算金额",
"客户预算-预算明细": "预算明细",
"竞品信息": "竞品信息",
"客户是否有意向": "有意向或无意向",
"客户是否有卡点": "有卡点或无卡点",
"客户购买阶段": "购买阶段",
"下一步跟进计划-参与人": ["跟进计划参与人"],
"下一步跟进计划-时间点": "跟进计划时间点",
"下一步跟进计划-具体事项": "跟进计划具体事项"
}}, ...]
```
"""
- 上述Prompt 清晰地指示了模型的任务、输出的结构化格式和需要提取的具体信息类型。是一个为从群聊对话记录中提取结构化信息而设计的 Prompt。
题外话之如何简单理解 Prompt 设计?
- Prompt 是模型操作的指令: 就像我们告诉一个人要做什么,Prompt 是我们告诉模型要做什么的指令。
- 包含输入和期望输出的格式: Prompt 通常包括示例输入和我们期望的输出格式,这有助于模型理解任务。
- 需要清晰明确: 好的 Prompt 应该非常清晰、具体,这样模型才知道如何执行任务。
Prompt 设计在大模型的应用中扮演着关键角色,它决定了模型能否正确理解任务并生成预期的输出。通过仔细设计 Prompt,可以极大地提升模型的性能和输出质量。
Step6:主函数启动(分三段解释)
下面这部分是用于处理和验证 LLM(如大语言模型)输出中的 JSON 格式的数据,确保提取到的 JSON 数据完整且符合预定义的结构。
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:
这是一个自定义异常类,用于处理 JSON 格式错误。它继承自 Python 的Exception
类,接收一个message
参数,用于描述错误信息。 - 函数
convert_all_json_in_text_to_dict:
该函数从给定的文本中提取所有的 JSON 字符串并将其转换为 Python 字典。stack
用于跟踪 JSON 对象的开始和结束位置。当stack
为空时,表示找到了一个完整的 JSON 对象,然后使用json.loads
将其解析为字典并添加到dicts
列表中。 - 函数
print_json_format:
这个函数格式化输出 JSON 数据,便于阅读。json.dumps
方法用于将 Python 对象转换为 JSON 字符串,indent=4
使输出格式更加美观,ensure_ascii= False
保证非 ASCII 字符能够正常显示。 - 函数
check_and_complete_json_format:
该函数检查和补全 JSON 数据中的字段,确保其符合预期的格式。required_keys
定义了 JSON 数据中每个字段的名称和类型。函数首先检查输入的data
是否是一个列表,列表中的每个元素是否是字典。然后,逐个检查和补全字典中的字段,如果某个字段缺失,则按默认值(空字符串或空列表)补全。还会检查字段的类型是否正确,对于类型为列表的字段,确保其中的每个元素都是字符串。
下面这段代码的功能是逐条处理
test_data
中的聊天记录,提取并格式化其中的信息。在处理过程中,如果发生异常,它会尝试多次重试以获取正确的结果。
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): #对于每条记录,尝试 retry_count 次(最多 5 次)来处理。
try:
res = get_completions(PROMPT_EXTRACT.format(content=data["chat_text"]))
# 使用 get_completions 函数根据 PROMPT_EXTRACT 模板生成结果。
infos = convert_all_json_in_text_to_dict(res)
# 通过 convert_all_json_in_text_to_dict 函数将结果中的 JSON 提取并转换为 Python 字典。
infos = check_and_complete_json_format(infos)
# 使用 check_and_complete_json_format 函数验证并补全提取到的 JSON 数据。
result.append({
"infos": infos,
"index": index
})
is_success = True
# 如果发生异常(例如 JSON 格式错误、网络问题等),打印错误信息并继续重试
break
except Exception as e:
print("index:", index, ", error:", e)
# 如果发生异常(例如 JSON 格式错误、网络问题等),打印错误信息并继续重试
continue
if not is_success:
data["index"] = index
error_data.append(data)
# 如果在 retry_count 次重试后仍未成功处理当前记录,则将该记录添加到 error_data 列表中,以便后续处理。
tqdm
是一个非常实用的库,用于在循环中显示进度条。它能帮助开发者了解当前处理进度,非常适合长时间运行的任务。- 使用
tqdm
库显示处理进度条,遍历test_data
中的每条记录。enumerate(test_data)
提供每条记录的索引和内容,将索引值index
加 1 以便从 1 开始计数(通常用于表示人类可读的序号)。 - (这段代码详细解释见注释)
- 在处理外部 API 或复杂逻辑时,异常是不可避免的。重试机制允许在遇到临时错误时进行多次尝试,以提高成功率。
下面这段代码是处理之前处理失败的数据
error_data
,并进行重新处理和整理。
# 故障数据处理
if error_data:
retry_count = 10 # 重试次数
# 将重试次数设为 10,相比之前的 5 次,增加了重试次数以提高成功率。
error_data_temp = []
while True:
if error_data_temp:
error_data = error_data_temp
error_data_temp = []
for data in tqdm(error_data):
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": data["index"]
})
is_success = True
break
except Exception as e:
print("index:", index, ", error:", e)
continue
if not is_success:
error_data_temp.append(data)
if not error_data_temp:
break
result = sorted(result, key=lambda x: x["index"])
- 将重试次数设为 10,相比之前的 5 次,增加了重试次数以提高成功率。
1. 循环处理错误数据:
error_data_temp
用于存储当前轮次处理失败的数据,以便下一轮重新处理。while True:
开始一个无限循环,直到所有的错误数据都被成功处理或超出重试次数为止。- 在每轮循环开始时,将
error_data_temp
赋值给error_data
,并清空error_data_temp
。
2. 重试逻辑:
- 对于每条数据
data
,使用retry_count
次重试机会来处理数据。 - 如果成功处理数据,则将提取的信息存入
result
列表中,包括索引信息。 - 如果处理失败,则将该数据添加到
error_data_temp
列表中,等待下一轮重新处理。
3. 循环终止条件:当 error_data_temp
列表为空时,即所有错误数据都被成功处理或达到最大重试次数时,退出循环。
4. 结果排序:最后,对 result
列表按照 index
字段进行排序,以便后续处理或输出时保持正确顺序。
Step7:生成提交文件并下载上传平台可查看分数
# 保存输出
write_json("output.json", result)