前言
大模型的多轮问答
难点就是在于如何精确识别用户最新的提问的真实意图
,而在常见的使用大模型进行多轮对话方式中,我接触到的只有两种方式
:
- 一种是简单地直接使用
user
和assistant
两个角色将一问一答的会话内容喂给大模型,让它能够结合最新的问题靠自己去理解用户的最新的问题的含义。 - 另外一种方式是在会话过程中将历史的问题进行维护,再使用另外一个大模型结合最新的问题去理解用户当前的意图。
两种方式都可以,但是在我目前的业务上我目前使用的是后者
,因为比较容易实现,效果也不错。
第一种方式
这是使用的是 qwen
的多轮问答 api ,要使用这一种方式,需要维护一个相当长的历史会话记录 messages
,而且要保证 messages
中的 user/assistant
消息交替出现,这是一个必须要遵循的条件,如果是碰到异常,必须要对 messages 中最后的无效对话进行清理。这里就是将理解用户意图和解决用户的问题都混在了一块,对于我要做的业务,回答内容的不确定性太高,而且实现成本也高,需要在会话中加入大量业务代码,所以果断放弃了。
这里的代码主要实现了一个简易地关于烹饪的对话,只有两轮,实现逻辑比较简单,写的比较粗糙,理解意思即可。
def multi_round():
messages = [{'role': 'system', 'content': '你是一个绝佳的烹饪助手'},
{'role': 'user', 'content': '如何做西红柿炖牛腩?'}]
response = Generation.call(model="qwen-turbo", messages=messages, result_format='message')
if response.status_code == HTTPStatus.OK:
print(response)
messages.append({'role': response.output.choices[0]['message']['role'],
'content': response.output.choices[0]['message']['content']}) # 将assistant的回复添加到messages列表中
else:
print(response.message)
messages = messages[:-1] # 如果响应失败,将最后一条user message从messages列表里删除,确保 user/assistant 消息交替出现
messages.append({'role': 'user', 'content': '不放糖可以吗?'}) # 将新一轮的user问题添加到messages列表中
response = Generation.call(model="qwen-turbo", messages=messages, result_format='message', )
if response.status_code == HTTPStatus.OK:
print(response)
messages.append({'role': response.output.choices[0]['message']['role'],
'content': response.output.choices[0]['message']['content']}) # 将第二轮的assistant的回复添加到messages列表中
else:
print(response.message)
messages = messages[:-1] # 如果响应失败,将最后一条user message从messages列表里删除,确保 user/assistant 消息交替出现
第二种方式
在我所做地业务中,对于 assistant
的回复不关心,主要关心的是用户的问题
,所以我只关注 user
的历史提问,在实现的时候只需要维护一个列表 history
,始终将最新的用户提问追加即可,为了保证列表信息的有效性,我始终只维护最后 10
个问题。我这里使用 qwen-max
模型对历史提问进行总结,并且按照我要求的方式进行输出。也就是说这个模型只负责总结历史问题,对于业务问题的回答是其他大模型干的事情,任务分工明确就减少了不确定性。
@app.route('/get_last_question', methods=["POST"])
def get_last_question():
global user_data
logging.info("-"*20)
data = request.get_json()
if 'question' not in data or not data['question'] or 'userId' not in data or not data['userId']:
return "无法理解或者无法解决,请重新输入问题"
question = data['question']
userId = data['userId']
try:
user_data = load_data(config) # 加载用户数据
if userId not in user_data:
user_data[userId] = []
user_data[userId].append(question) # 获取 userId 对应的历史对话记录
user_data[userId] = user_data[userId][-10:] # 只保留历史上 10 个对话记录
logging.info(f"正在解析用户 【{userId}】 意图,问题历史是 {user_data[userId]}...\n\n")
history = user_data[userId]
history_str = ""
if len(history)>1:
history_str = "历史上我依次提问了以下问题:\n"
for i,h in enumerate(history[:-1]):
history_str += f"时间 10:06:0{i+1} ,问题是: {h}\n"
else:
history_str += "目前暂无用户提问历史记录。"
messages = [
{'role': 'system',
'content': '您是一名善于从历史提问中分析用户的最新意图的助手,请根据提问历史记录,分析并总结用户的最新问题的完整意图。'},
{'role': 'user',
'content': f"根据提问历史记录,分析并总结用户的最新问题的完整意图。不要做冗余的解释或者赘述。如果用户提出的问题语义模糊不清无法识别,可以直接返回空字符串。答案的模板必循遵循“【{{我的最新的问题描述}}】”,总结出来的问题还必须要满足下面的要求:\n"
f"1、如果用户的问题查询的是“杭州市”或者“杭州”范围的数据一律使用“全市”进行替换,因为业务数据范围默认就是全杭州市的数据,所以无需重复再提起,但是我们不对包含“杭州市”或者”杭州“字符串的单位名称进行任何处理,因为单位名称具有独特的含义。\n"
f"2、用户的简短问题或者意图模糊的提问(如‘2024年呢’等)通常是对之前历史问题的追问或者补充,请根据历史问题记录推断出完整的问题。\n"
f"例子:\n"
f"输入的历史问题列表是:\n "
f"时间 2024-6-6 ,问题是:升序统计2023年各项目类型下管线项目计划数和计划投资金额\n "
f"我最新的问题是:统计杭州市2023年管线和管廊建设计划的执行率\n"
f"经过分析历史问题列表发现最新的问题和前面的问题关系不大,所以直接最后总结出来的问题是 “统计杭州市2023年管线和管廊建设计划的执行率”, 从问题中可以看出要查询杭州市范围的数据,按照要求我们知道默认数据范围就是全杭州市,所以要用”全市“进行替换,所以输出结果为“【统计全市2023年管线和管廊建设计划的执行率】”。\n"
f"例子:\n"
f"输入的历史问题列表是: \n"
f"时间 2024-6-6,问题是:升序统计杭州市2023年各项目类型下管线项目计划数和计划投资金额\n"
f"我最新的问题是:2024年呢\n"
f"经过分析发现列表中最新的问题和前面的问题关系有联系,所以经过分析最后总结出来的问题是 “升序统计杭州市2024年各项目类型下管线项目计划数和计划投资金额”, 从问题中可以看出要查询杭州市范围的数据,按照要求我们知道默认数据范围就是全杭州市,所以要用”全市“进行替换,所以输出结果为“【升序统计全市2024年各项目类型下管线项目计划数和计划投资金额】”。\n"
f"例子:\n"
f"输入的历史问题列表是: \n"
f"时间 2024-6-6,问题是:升序查询杭州市2023年权属单位是杭州市政府的管线信息\n"
f"我最新的问题是,问题是:2024年呢\n"
f"经过分析发现列表中最新的问题和前面的问题关系有联系,所以经过分析最后总结出来的问题是 “升序查询杭州市2024年权属单位是杭州市政府的管线信息”, 从问题中可以看出要查询杭州市范围的数据,按照要求我们知道默认数据范围就是全杭州市,所以要用”全市“进行替换,所以输出结果为“【升序查询全市2024年权属单位是杭州市政府的管线信息】”,我们不对包含“杭州市”或者”杭州“字符串的单位名称进行任何处理。\n"
f"\n{history_str}\n,现在我的最新的问题是 “{history[-1]}” ,请严格遵守上述要求并总结出用户的最新问题并给出完整的意图,并简要介绍思考过程。"}
]
logging.info(f"总结用户最新意图 prompt :{messages}")
response = Generation.call(model="qwen-max-0428", messages=messages, result_format='message')
resp = response.output.choices[0]['message']['content']
logging.info(f"用户最新意图是:{resp}")
g = re.search(r"【.*】", resp)
if g:
resp = g.group().replace("【", "").replace("】", "")
save_data(user_data, config)
return resp
return ""
except Exception as e:
logging.info("提取总结最新的问题过程中报错")
logging.error(e)
return ""