From b2738705db5aef5a6d82d257c82356646a0d05dc Mon Sep 17 00:00:00 2001 From: weiweiw <14335254+weiweiw22@user.noreply.gitee.com> Date: Tue, 20 May 2025 15:31:00 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=87=E7=94=A8=E8=87=AA=E7=84=B6=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E7=BC=96=E7=A8=8B=E6=8A=80=E6=9C=AF=E6=8F=90=E5=8D=87?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/constants.py | 12 ++ api/main.py | 236 ++++++++++++++++++--------------- api/main_temp.py | 257 +++++++++++++++++++++--------------- api/slotRecognition.py | 21 ++- api/utils.py | 18 ++- generated_data/generated.py | 8 ++ 6 files changed, 326 insertions(+), 226 deletions(-) diff --git a/api/constants.py b/api/constants.py index 9b4dc08..7a723fc 100644 --- a/api/constants.py +++ b/api/constants.py @@ -44,3 +44,15 @@ TEAM_NAME = "teamName" PAGE = "page" PROGRAM_NAVIGATION = "programNavigation" + + +# 意图识别和槽位抽取服务返回的关键提示语列表 +SLOT_KEYWORDS = [ + "未找到匹配的", + "请提供更准确的公司名信息", + "请提供更准确的信息", + "请确认您要选择哪一个", + "请确认风险等级后再次提问", + "请补充该项目部所属的分公司名称", + "请问你想查询什么时间的" +] \ No newline at end of file diff --git a/api/main.py b/api/main.py index f61c921..258c9ee 100644 --- a/api/main.py +++ b/api/main.py @@ -10,7 +10,7 @@ from pydantic import ValidationError from logger_util import setup_logger from intentRecognition import IntentRecognition from slotRecognition import SlotRecognition -from utils import CheckResult, check_standard_name_slot_probability, check_lost +from utils import CheckResult, check_standard_name_slot_probability, check_lost, process_msg_content from config import * from globalData import GlobalData from apscheduler.schedulers.background import BackgroundScheduler @@ -272,128 +272,144 @@ def agent(): return jsonify({"error": str(e)}), 500 # 捕捉其他错误并返回 +def format_chat_history(history_messages): + reset_keywords = ["当前", "今天", "昨天", "本周", "下周", "明天", "今日", "打开","工程进度"] + keep_index = 0 + + # Step 1: 查找最靠近当前的 user 消息,若其包含关键词,则记录其索引 + for i in reversed(range(len(history_messages))): + msg = history_messages[i] + if msg.role == "user" and any(kw in msg.content and len(msg.content) > 5 for kw in reset_keywords): + keep_index = i + break + + # Step 2: 截取需要保留的历史消息 + filtered_messages = history_messages[keep_index:] + + # Step 3: 构建格式化历史 + formatted_history = "" + + for i, msg in enumerate(filtered_messages): + formatted_history += f"\n\n" + formatted_history += f"{msg.role}\n" + formatted_history += f"{process_msg_content(msg.content)}\n" + formatted_history += "\n" + + return formatted_history + + + def extract_multi_chat(messages): from openai import OpenAI client = OpenAI(base_url=api_base_url, api_key=api_key) - latest_message = messages[-1] - latest_user_question = latest_message.content if latest_message.role == "user" else "" + history_messages = messages[-7:] if len(messages) >= 7 else messages + chat_history = format_chat_history(history_messages) + logger.info(f"chat_history:{chat_history}") - time_prefixes = ["当前","今天", "昨天", "本周", "下周", "明天", "今日","打开"] - history_messages = [] if any(prefix in latest_user_question and len(latest_user_question) > 3 for prefix in time_prefixes) else messages[:-1] - - logger.info(f"len(history_messages):{len(history_messages)}") - - #最新问题的上一个问题里如果含有时间,则清空最老的历史对话 - last_two_messages = history_messages[-2:] - has_time_prefix = any( - msg.role == "user" and any(prefix in msg.content and len(msg.content) > 3 for prefix in time_prefixes) - for msg in last_two_messages - ) - last_chat_history = "\n".join([f"{msg.role}: {msg.content}" for msg in last_two_messages]) - oldest_chat_history = "" if has_time_prefix else "\n".join([f"{msg.role}: {msg.content}" for msg in history_messages[:2]]) - - logger.info(f"last_chat_history:{last_chat_history}") - logger.info(f"oldest_chat_history:{oldest_chat_history}") prompt = f''' - 你是一个意图识别与补全助手,你的任务是根据用户的最新问题判断是否需要补全,如果不需要补全,则原样返回用户的最新问题,否则需要结合最新对话历史和最老对话历史补全用户的最新问题,并只返回最终的完整问题。 - 请严格按照如下逻辑判断并执行: + 你是一个多轮对话理解和还原专家,擅长从复杂的上下文中提取关键信息,理清语义逻辑,最终还原出用户的真实意图。请你以"自然语言编程"的方式逐步思考并处理以下任务,最终生成完整明确的用户查询。 + + 请严格按照以下步骤执行: + + ## 第一步:初始化变量 + + 初始化以下变量(用于逻辑推理,请勿输出): + - 当前_用户问题 = "" # 表示最终需要还原的完整问题 + - 当前_实体 = "" # 表示用户所提及的公司、项目部、工程名称等具体业务实体 + - 当前_时间 = "" # 表示与查询相关的时间点或时间段 + - 下一步_操作 = "" # 表示当前对话中模型需要用户补充的信息类型 + - 下一步_选择列表 = "" # 表示需要从中选择具体内容的候选项列表 + + ## 第二步:逐轮解析对话历史 + + 从最早的对话轮次开始,依次处理每一轮对话。 + + ### 轮次 X(X从1开始递增) + + ### 如果当前角色为user: + + 请根据之前 assistant 的引导语,结合当前用户输入,判断是否需要进行补全操作,并按以下规则处理: + + - 如果 `下一步_操作 == "补充时间"`:调用函数 `补全时间(当前_用户问题, 用户输入)` 并更新 `当前_用户问题` + - 否则 如果 `下一步_操作 == "补充分公司"`:调用函数 `补全分公司(当前_用户问题, 用户输入)` 并更新 `当前_用户问题` + - 否则 如果 `下一步_操作 == "选择列表"`:调用函数 `替换序号为实体(当前_用户问题, 用户输入, 下一步_选择列表)` 并更新 `当前_用户问题` + - 否则 如果 有完整的句意(用户输入):将当前用户输入作为 `当前_用户问题` + - 否则 如果 包含模糊表达(用户输入):调用函数 `补全模糊表达(当前_用户问题, 用户输入)`并更新 `当前_用户问题` + - 否则 如果 是查询新属性(用户输入):调用函数 `替换新属性(当前_用户问题, 用户输入)`并更新 `当前_用户问题` + - 否则 将当前用户输入作为 `当前_用户问题` + 处理完成后请清空 `下一步_操作` 和`下一步_选择列表` + + ### 如果当前角色为 assistant: + + 请根据 assistant 的输出内容,判断接下来用户是否被引导进行补全,并更新 `下一步_操作`: + + - 如果 assistant 的回复中包含"请问你想查询什么时间的"或类似引导时间的内容:设定 `下一步_操作 = "补充时间"` + - 否则 如果包含"请补充该项目部所属的分公司名称":设定 `下一步_操作 = "补充分公司"` + - 否则 如果包含"请确认您要选择哪一个":设定 `下一步_操作 = "选择列表"`,并将当前 assistant 回复中列出的选项存入 `下一步_选择列表` + - 否则 什么都不做 + + ### 重复以上解析过程 + 请逐条处理每轮对话,直到所有历史对话处理完毕,然后进入第三步。 + + ## 第三步:输出还原后的最终用户问题 + 在完成全部历史消息处理后,请输出变量 `当前_用户问题`,它即为根据上下文补全后的完整查询。 --- - - 【规则判断与补全流程】 - - 第一步:用户最新问题是否存在指代词?→ 结合用户最新问题和历史用户问题进行补全 - - 若用户最新问题中出现模糊表达,如“具体是哪些项”、“是哪两个”、“作业计划分别是什么”、“合肥中心变工程呢”、“具体是哪20项”、“考勤人数呢”等,请根据需要结合最新对话历史和最老对话历史的补全问题信息; - - 需要补全的信息包括时间、主语、查询对象等; - - **特别地,若最新用户问题中未提及时间,但最新对话历史中的用户问题中出现“今天”“昨日”“本周”等时间词,则需要将其补全进最终问题中;** - - **特别地,若最新用户问题中未提及时间,但最老对话历史中的用户问题中出现“今天”“昨日”“本周”等时间词,则需要将其补全进最终问题中;** - - 示例1: - - 用户最新问题:“具体的作业内容是什么” - - 最新对话历史的用户问题:“送一分公司第一项目部今天有多少项作业计划” - - 最新对话历史的AI回答:“送电一分公司第一项目管理部有21项作业计划” - - 则最终提问应为: - “今天送电一分公司第一项目管理部的21项作业计划分别是什么” - - - 示例2: - - 用户最新问题:“2项作业计划是什么” - - 最老对话历史的用户问题:“宋1分公司第一项目部今天有多少作业计划” - - 最老对话历史的AI回答:“未匹配到您说的分公司名:宋1分公司,请提供更准确的分公司名” - - 最新对话历史的用户问题:“送一分公司” - - 最新对话历史的AI回答:“送电一分公司第一项目管理部有2项作业计划” - - 则最终提问应为: - “今天送电一分公司第一项目管理部的2项作业计划是什么” - - - 示例3: - - 用户最新问题:“具体的班组详情” - - 最新对话历史的用户问题:“送一分公司第一项目部今天有多少班组” - - 最新对话历史的AI回答:“送电一分公司第一项目部(金上)有20个班组” - - 则最终提问应为: - “送电一分公司第一项目部(金上)今天具体的20个班组详情” - - - 第二步:用户最新问题是否为序号指代(第一个/第2个)?→ 用完整工程/项目/公司名替换补全 - - 精确提取用户所指的序号(如“第3个”指第3个工程名、公司名或项目部名); - - 将该工程、公司或项目部的完整名称(包括括号中的编号)提取出来; - - 用完整工程、公司或项目部的名称替换掉最新对话历史的用户问题中出现的简称或模糊表达,但保留用户问题中的其余所有内容,包括操作动词(如“打开”“查一下”)、时间和目标对象(如“摄像头”、“视频”、"进度情况""作业计划""作业内容")等,不得遗漏; - - 禁止添加用户原始问题中未出现的动作性动词(例如:“打开”“查看”“查询”“显示”“展示”等); - - 最终问题必须贴近原始提问语气和意图,只替换模糊对象的全称,不改变句式和动作行为表达; - - 示例1: - - 用户最新问题:"第二个" 或"第2个" - - 最新对话历史的用户问题:"2025年南苑调相机检修(PROJ-2023-0179)今天有多少作业计划"" - - 最新对话历史的AI回答:你说的工程名可能是,第一个:检修公司调相机一二次设备检修维护和改造服务框架-2025年南苑调相机检修(PROJ-2023-0179),第二个:黄阳-仙河110kV线路工程(PROJ-2024-0047),请确认您要选择哪一个? - - 则最终提问应为: - "黄阳-仙河110kV线路工程(PROJ-2024-0047))今天有多少作业计划" - - - 示例2: - - 用户最新问题:"第2个" 或"第二个" - - 最新对话历史的用户问题:"请帮我查一下今天芦集变电站的进度情况" - - 最新对话历史的AI回答:你说的工程名可能是,第1个:芦集-古沟π入潘集变电站220kV线路工程(PROJ-2024-0189),第二个:淮南芦集220千伏变电站220千伏配电装置改造工程(PROJ-2024-0265),请确认您要选择哪一个? - - 则最终提问应为: - "请帮我查一下今天淮南芦集220千伏变电站220千伏配电装置改造工程(PROJ-2024-0265)的进度情况" - - - 示例3: - - 用户最新问题:"第2个" - - 最新对话历史的用户问题:"宏源电力公司第三项目部今天有多少项作业计划" - - 最新对话历史的AI回答:您说的实施组织名可能是,第1个:安徽宏源电力建设有限公司(线路),第2个:安徽宏源电力建设有限公司(变电),请选择哪一个 - - 则最终提问应为: - "安徽宏源电力建设有限公司(变电)第三项目部今天有多少项作业计划" - - - 示例4: - - 用户最新问题:"第2个" - - 最新对话历史的用户问题:"打开中心变摄像头" - - 最新对话历史的AI回答:您说的工程名可能是,第1个:锦绣-常青π入中心变电站220kV架空线路工程(PROJ-2024-1206),第2个:合肥中心变B包(PROJ-2024-0176),请选择哪一个 - - 则最终提问应为: - "打开合肥中心变B包(PROJ-2024-0176)摄像头" - - - 示例5: - - 最新对话历史的用户问题:“今天循环园工程有多少项作业计划” - - 最新对话历史的AI回答:“您说的工程名可能是:第1个... 第2个... 第3个:黄山巷联-水西220kV线路工程(PROJ-2024-0220),请确认您要选择哪一个?” - - 用户最新问题:“第3个” - - 错误输出(不要这样):“打开黄山巷联-水西220kV线路工程(PROJ-2024-0220)今天有多少项作业计划” - - 正确输出(你应该输出):“黄山巷联-水西220kV线路工程(PROJ-2024-0220)今天有多少项作业计划” - - 第三步:输出最终问题 - - 直接输出最终问题(无解释、无多余前缀或后缀) - - 保持句式自然清晰 - - --- - - 最老对话历史: - {oldest_chat_history} - 最新对话历史: - {last_chat_history} - - 用户最新问题: - {latest_user_question} - - 请输出最终问题:''' + 辅助函数说明: + 函数 补全时间(文本, 时间词): + 支持识别如"2025-5-15"、"昨天""等模糊时间 + 从时间词里提取时间 + 在文本开头添加提取到的时间并返回,保持其他内容不变 + 如果没有提取到时间则直接返回原文本内容 + 示例:补全时间(""第一项目部有多少作业计划", "今天的") 返回 "今天第一项目部有多少作业计划" + + 函数 补全分公司(文本, 分公司词): + 从分公司词里提取分公司 + 在文本里的时间词之后添加提取到的分公司并返回,保持其他内容不变 + 如果没有提取到分公司信息则直接返回原文本内容 + 示例:补全分公司("今天第一项目有多少作业计划", "送二分公司") 返回 "今天送二分公司第一项目有多少作业计划" + + 函数 替换序号为实体(文本, 选择项, 选择列表): + 从选择项如"第X个"中提取X的值并处理中文数字和阿拉伯数字作为序号 + 根据序号识别出"第1个:XXX,第2个:YYY"格式的选项列表提取出完整的实体名称 + 保留文本中的动作词和目标对象,将文本中的实体引用替换为完整实体名称 + 示例:替换序号为实体("中心变工程进度", "第1个", "第1个:燃气工程,第2个:给水工程") 返回 "燃气工程进度" + + 函数 包含模糊表达(文本): + 检查是否包含"具体是哪些项"、"是哪两个"等模糊表达 + 返回布尔值表示是否包含 + 示例:包含模糊表达("具体是哪些项") 返回 True + + 函数 补全模糊表达(文本,模糊表达): + 结合文本内容和模糊表达,补全语义并返回 + 示例:补全模糊表达("今天送一分公司有多少作业计划", "具体是哪些") 返回 "今天送一分公司具体有哪些作业计划" + + 函数 是查询新属性(文本, 新问题): + 如果新问题中提取不到主体 且仅能提取到查询属性 + 且这个查询属性和文本中提取到的查询属性不同 则返回TRUE + 其他情况均返回FALSE + 示例:是查询新属性("今天送一分公司有多少作业计划", "作业内容") 返回 True + + 函数 替换新属性(文本,新查询属性): + 先删除文本中的"有多少"等类似的表达数量表达, + 再将文本里的查询属性替换为新查询属性,并保持其他内容不变并返回 且保持新查询属性的语气 + 示例:替换新属性("今天送一分公司有多少作业计划", "作业内容") 返回 "今天送一分公司的作业内容" + + 函数 有完整的句意(新问题): + 如果新问题里有主体同时有操作对象或查询对象则返回TRUE + 其他情况均返回FALSE + + 对话历史如下: + {chat_history} + + 请你仅输出还原后的完整问题,不要输出任何变量、中间步骤或解释说明,确保结果自然通顺,语义完整。 + ''' message = [ {"role": "user", "content": prompt} ] - # logger.info(f"*********messages:{prompt}") response = client.chat.completions.create( messages=message, model=model_name, diff --git a/api/main_temp.py b/api/main_temp.py index 3c4ba9d..db1cc60 100644 --- a/api/main_temp.py +++ b/api/main_temp.py @@ -10,13 +10,13 @@ from pydantic import ValidationError from logger_util import setup_logger from intentRecognition import IntentRecognition from slotRecognition import SlotRecognition -from utils import CheckResult, check_standard_name_slot_probability, check_lost +from utils import CheckResult, check_standard_name_slot_probability, check_lost, process_msg_content from config import * from globalData import GlobalData from apscheduler.schedulers.background import BackgroundScheduler MODEL_ERNIE_PATH = R"../ernie/output/checkpoint-14672" -MODEL_UIE_PATH = R"../uie/output/checkpoint-18190" +MODEL_UIE_PATH = R"../uie/output/checkpoint-16380" # 类别名称列表 @@ -272,128 +272,165 @@ def agent(): return jsonify({"error": str(e)}), 500 # 捕捉其他错误并返回 +def format_chat_history(history_messages): + reset_keywords = ["当前", "今天", "昨天", "本周", "下周", "明天", "今日", "打开","工程进度"] + keep_index = 0 + + # Step 1: 查找最靠近当前的 user 消息,若其包含关键词,则记录其索引 + for i in reversed(range(len(history_messages))): + msg = history_messages[i] + if msg.role == "user" and any(kw in msg.content and len(msg.content) > 5 for kw in reset_keywords): + keep_index = i + break + + # Step 2: 截取需要保留的历史消息 + filtered_messages = history_messages[keep_index:] + + # Step 3: 构建格式化历史 + formatted_history = "" + + for i, msg in enumerate(filtered_messages): + formatted_history += f"\n\n" + formatted_history += f"{msg.role}\n" + formatted_history += f"{process_msg_content(msg.content)}\n" + formatted_history += "\n" + + return formatted_history + + + def extract_multi_chat(messages): from openai import OpenAI client = OpenAI(base_url=api_base_url, api_key=api_key) - latest_message = messages[-1] - latest_user_question = latest_message.content if latest_message.role == "user" else "" + history_messages = messages[-7:] if len(messages) >= 7 else messages + chat_history = format_chat_history(history_messages) + logger.info(f"chat_history:{chat_history}") - time_prefixes = ["当前","今天", "昨天", "本周", "下周", "明天", "今日","打开"] - history_messages = [] if any(prefix in latest_user_question and len(latest_user_question) > 3 for prefix in time_prefixes) else messages[:-1] - - logger.info(f"len(history_messages):{len(history_messages)}") - - #最新问题的上一个问题里如果含有时间,则清空最老的历史对话 - last_two_messages = history_messages[-2:] - has_time_prefix = any( - msg.role == "user" and any(prefix in msg.content and len(msg.content) > 3 for prefix in time_prefixes) - for msg in last_two_messages - ) - last_chat_history = "\n".join([f"{msg.role}: {msg.content}" for msg in last_two_messages]) - oldest_chat_history = "" if has_time_prefix else "\n".join([f"{msg.role}: {msg.content}" for msg in history_messages[:2]]) - - logger.info(f"last_chat_history:{last_chat_history}") - logger.info(f"oldest_chat_history:{oldest_chat_history}") prompt = f''' - 你是一个意图识别与补全助手,你的任务是根据用户的最新问题判断是否需要补全,如果不需要补全,则原样返回用户的最新问题,否则需要结合最新对话历史和最老对话历史补全用户的最新问题,并只返回最终的完整问题。 - 请严格按照如下逻辑判断并执行: + 你是一个多轮对话理解和还原专家,擅长从复杂的上下文中提取关键信息,理清语义逻辑,最终还原出用户的真实意图。请你以"自然语言编程"的方式逐步思考并处理以下任务,最终生成完整明确的用户查询。 + + 请严格按照以下步骤执行: + + ## 第一步:初始化变量 + + 初始化以下变量(用于逻辑推理,请勿输出): + - 当前_用户问题 = "" # 表示最终需要还原的完整问题 + - 当前_实体 = "" # 表示用户所提及的公司、项目部、工程名称等具体业务实体 + - 当前_时间 = "" # 表示与查询相关的时间点或时间段 + - 下一步_操作 = "" # 表示当前对话中模型需要用户补充的信息类型 + - 下一步_选择列表 = "" # 表示需要从中选择具体内容的候选项列表 + + ## 第二步:逐轮解析对话历史 + + 从最早的对话轮次开始,依次处理每一轮对话。 + + ### 轮次 X(X从1开始递增) + + ### 如果当前角色为user: + + 请根据之前 assistant 的引导语,结合当前用户输入,判断是否需要进行补全操作,并按以下规则处理: + + - 如果 `下一步_操作 == "补充时间"`:调用函数 `补全时间(当前_用户问题, 用户输入)` 并更新 `当前_用户问题` + - 否则 如果 `下一步_操作 == "补充分公司"`:调用函数 `补全分公司(当前_用户问题, 用户输入)` 并更新 `当前_用户问题` + - 否则 如果 `下一步_操作 == "选择列表"`:调用函数 `替换序号为实体(当前_用户问题, 用户输入, 下一步_选择列表)` 并更新 `当前_用户问题` + - 否则 如果 有完整的句意(用户输入):将当前用户输入作为 `当前_用户问题` + - 否则 如果 包含模糊表达(用户输入):调用函数 `补全模糊表达(当前_用户问题, 用户输入)`并更新 `当前_用户问题` + - 否则 如果 是查询新属性(用户输入):调用函数 `替换新属性(当前_用户问题, 用户输入)`并更新 `当前_用户问题` + - 否则 将当前用户输入作为 `当前_用户问题` + 处理完成后请清空 `下一步_操作` 和`下一步_选择列表` + + ### 如果当前角色为 assistant: + + 请根据 assistant 的输出内容,判断接下来用户是否被引导进行补全,并更新 `下一步_操作`: + + - 如果 assistant 的回复中包含"请问你想查询什么时间的"或类似引导时间的内容:设定 `下一步_操作 = "补充时间"` + - 否则 如果包含"请补充该项目部所属的分公司名称":设定 `下一步_操作 = "补充分公司"` + - 否则 如果包含"请确认您要选择哪一个":设定 `下一步_操作 = "选择列表"`,并将当前 assistant 回复中列出的选项存入 `下一步_选择列表` + - 否则 什么都不做 + + ### 重复以上解析过程 + 请逐条处理每轮对话,直到所有历史对话处理完毕,然后进入第三步。 + + ## 第三步:输出还原后的最终用户问题 + 在完成全部历史消息处理后,请输出变量 `当前_用户问题`,它即为根据上下文补全后的完整查询。 --- - - 【规则判断与补全流程】 - - 第一步:用户最新问题是否存在指代词?→ 结合用户最新问题和历史用户问题进行补全 - - 若用户最新问题中出现模糊表达,如“具体是哪些项”、“是哪两个”、“作业计划分别是什么”、“合肥中心变工程呢”、“具体是哪20项”、“考勤人数呢”等,请根据需要结合最新对话历史和最老对话历史的补全问题信息; - - 需要补全的信息包括时间、主语、查询对象等; - - **特别地,若最新用户问题中未提及时间,但最新对话历史中的用户问题中出现“今天”“昨日”“本周”等时间词,则需要将其补全进最终问题中;** - - **特别地,若最新用户问题中未提及时间,但最老对话历史中的用户问题中出现“今天”“昨日”“本周”等时间词,则需要将其补全进最终问题中;** - - 示例1: - - 用户最新问题:“具体的作业内容是什么” - - 最新对话历史的用户问题:“送一分公司第一项目部今天有多少项作业计划” - - 最新对话历史的AI回答:“送电一分公司第一项目管理部有21项作业计划” - - 则最终提问应为: - “今天送电一分公司第一项目管理部的21项作业计划分别是什么” - - - 示例2: - - 用户最新问题:“2项作业计划是什么” - - 最老对话历史的用户问题:“宋1分公司第一项目部今天有多少作业计划” - - 最老对话历史的AI回答:“未匹配到您说的分公司名:宋1分公司,请提供更准确的分公司名” - - 最新对话历史的用户问题:“送一分公司” - - 最新对话历史的AI回答:“送电一分公司第一项目管理部有2项作业计划” - - 则最终提问应为: - “今天送电一分公司第一项目管理部的2项作业计划是什么” + 辅助函数说明: + 函数 补全时间(文本, 时间词): + 支持识别如"2025-5-15"、"昨天""等模糊时间 + 从时间词里提取时间 + 在文本开头添加提取到的时间并返回,保持其他内容不变 + 如果没有提取到时间则直接返回原文本内容 + 示例:补全时间(""第一项目部有多少作业计划", "今天的") 返回 "今天第一项目部有多少作业计划" + + 函数 补全分公司(文本, 分公司词): + 从分公司词里提取分公司 + 在文本里的时间词之后添加提取到的分公司并返回,保持其他内容不变 + 如果没有提取到分公司信息则直接返回原文本内容 + 示例:补全分公司("今天第一项目有多少作业计划", "送二分公司") 返回 "今天送二分公司第一项目有多少作业计划" + + 函数 替换序号为实体(文本, 选择项, 选择列表): + 从选择项如"第X个"中提取X的值并处理中文数字和阿拉伯数字作为序号 + 根据序号识别出"第1个:XXX,第2个:YYY"格式的选项列表提取出完整的实体名称 + 保留文本中的动作词和目标对象,将文本中的实体引用替换为完整实体名称 + 示例:替换序号为实体("中心变工程进度", "第1个", "第1个:燃气工程,第2个:给水工程") 返回 "燃气工程进度" + + 函数 包含模糊表达(文本): + 检查是否包含"具体是哪些项"、"是哪两个"等模糊表达 + 返回布尔值表示是否包含 + 示例:包含模糊表达("具体是哪些项") 返回 True + + 函数 补全模糊表达(文本,模糊表达): + 结合文本内容和模糊表达,补全语义并返回 + 示例:补全模糊表达("今天送一分公司有多少作业计划", "具体是哪些") 返回 "今天送一分公司具体有哪些作业计划" + + 函数 是查询新属性(文本, 新问题): + 如果新问题中没有查询主体仅有查询对象 则返回TRUE + 如果新问题中仅有查询主体但没有查询对象 则返回TRUE + 其他情况均返回FALSE + 示例:是查询新属性("今天送一分公司有多少作业计划", "作业内容") 返回 True + + 函数 删除数量词(文本): + 删除“有多少”、“多少”、“几条”、“几个”等数量问句词 + + 函数 替换查询属性(文本, 新查询属性): + 说明: + 本函数用于在删除数量词后,将原句中与“新查询属性”同类型的核心查询词替换为“新查询属性”,并确保其他内容保持不变且语义自然。 + + 处理步骤: + 1. 删除文本中的数量类词语,例如“有多少”、“多少”、“几个”、“几条”等。 + 2. 识别原句中的核心查询属性词,判断其与“新查询属性”是否属于相同类别(如均为对象、地点、组织等)。 + 3. 将原有核心查询词替换为“新查询属性”,保留句中其余上下文结构不变。 + 4. 保持句子语气自然,避免引入“是什么”、“有多少”等疑问表达。 + + 返回: + 返回替换后的文本,语义清晰、语气自然。 + + 示例: + 替换查询属性("今天送一分公司有多少作业计划", "作业内容") + → "今天送一分公司的作业内容" + + 替换查询属性("今天送一分公司的班组详情", "送二分公司") + → "今天送二分公司的班组详情" - - 示例3: - - 用户最新问题:“具体的班组详情” - - 最新对话历史的用户问题:“送一分公司第一项目部今天有多少班组” - - 最新对话历史的AI回答:“送电一分公司第一项目部(金上)有20个班组” - - 则最终提问应为: - “送电一分公司第一项目部(金上)今天具体的20个班组详情” - - - 第二步:用户最新问题是否为序号指代(第一个/第2个)?→ 用完整工程/项目/公司名替换补全 - - 精确提取用户所指的序号(如“第3个”指第3个工程名、公司名或项目部名); - - 将该工程、公司或项目部的完整名称(包括括号中的编号)提取出来; - - 用完整工程、公司或项目部的名称替换掉最新对话历史的用户问题中出现的简称或模糊表达,但保留用户问题中的其余所有内容,包括操作动词(如“打开”“查一下”)、时间和目标对象(如“摄像头”、“视频”、"进度情况""作业计划""作业内容")等,不得遗漏; - - 禁止添加用户原始问题中未出现的动作性动词(例如:“打开”“查看”“查询”“显示”“展示”等); - - 最终问题必须贴近原始提问语气和意图,只替换模糊对象的全称,不改变句式和动作行为表达; - - 示例1: - - 用户最新问题:"第二个" 或"第2个" - - 最新对话历史的用户问题:"2025年南苑调相机检修(PROJ-2023-0179)今天有多少作业计划"" - - 最新对话历史的AI回答:你说的工程名可能是,第一个:检修公司调相机一二次设备检修维护和改造服务框架-2025年南苑调相机检修(PROJ-2023-0179),第二个:黄阳-仙河110kV线路工程(PROJ-2024-0047),请确认您要选择哪一个? - - 则最终提问应为: - "黄阳-仙河110kV线路工程(PROJ-2024-0047))今天有多少作业计划" - - - 示例2: - - 用户最新问题:"第2个" 或"第二个" - - 最新对话历史的用户问题:"请帮我查一下今天芦集变电站的进度情况" - - 最新对话历史的AI回答:你说的工程名可能是,第1个:芦集-古沟π入潘集变电站220kV线路工程(PROJ-2024-0189),第二个:淮南芦集220千伏变电站220千伏配电装置改造工程(PROJ-2024-0265),请确认您要选择哪一个? - - 则最终提问应为: - "请帮我查一下今天淮南芦集220千伏变电站220千伏配电装置改造工程(PROJ-2024-0265)的进度情况" - - - 示例3: - - 用户最新问题:"第2个" - - 最新对话历史的用户问题:"宏源电力公司第三项目部今天有多少项作业计划" - - 最新对话历史的AI回答:您说的实施组织名可能是,第1个:安徽宏源电力建设有限公司(线路),第2个:安徽宏源电力建设有限公司(变电),请选择哪一个 - - 则最终提问应为: - "安徽宏源电力建设有限公司(变电)第三项目部今天有多少项作业计划" - - - 示例4: - - 用户最新问题:"第2个" - - 最新对话历史的用户问题:"打开中心变摄像头" - - 最新对话历史的AI回答:您说的工程名可能是,第1个:锦绣-常青π入中心变电站220kV架空线路工程(PROJ-2024-1206),第2个:合肥中心变B包(PROJ-2024-0176),请选择哪一个 - - 则最终提问应为: - "打开合肥中心变B包(PROJ-2024-0176)摄像头" - - - 示例5: - - 最新对话历史的用户问题:“今天循环园工程有多少项作业计划” - - 最新对话历史的AI回答:“您说的工程名可能是:第1个... 第2个... 第3个:黄山巷联-水西220kV线路工程(PROJ-2024-0220),请确认您要选择哪一个?” - - 用户最新问题:“第3个” - - 错误输出(不要这样):“打开黄山巷联-水西220kV线路工程(PROJ-2024-0220)今天有多少项作业计划” - - 正确输出(你应该输出):“黄山巷联-水西220kV线路工程(PROJ-2024-0220)今天有多少项作业计划” - - 第三步:输出最终问题 - - 直接输出最终问题(无解释、无多余前缀或后缀) - - 保持句式自然清晰 - - --- - - 最老对话历史: - {oldest_chat_history} - 最新对话历史: - {last_chat_history} - - 用户最新问题: - {latest_user_question} - - 请输出最终问题:''' + 替换查询属性("今天送一分公司的班组详情", "明天呢") + → "明天送二分公司的班组详情" + + 函数 有完整的句意(新问题): + 如果新问题里有主体同时有操作对象或查询对象则返回TRUE + 其他情况均返回FALSE + + 对话历史如下: + {chat_history} + + 请你仅输出还原后的完整问题,不要输出任何变量、中间步骤或解释说明,确保结果自然通顺,语义完整。 + ''' message = [ {"role": "user", "content": prompt} ] - # logger.info(f"*********messages:{prompt}") response = client.chat.completions.create( messages=message, model=model_name, diff --git a/api/slotRecognition.py b/api/slotRecognition.py index 6f169d3..b88d885 100644 --- a/api/slotRecognition.py +++ b/api/slotRecognition.py @@ -3,7 +3,7 @@ from paddlenlp.transformers import ErnieForTokenClassification, ErnieTokenizer from globalData import GlobalData from utils import standardize_name_only_high_score, clean_useless_company_name -from constants import SUBCONTRACTOR, CONSTRUCTION_UNIT, IMPLEMENTATION_ORG, PAGE, PROGRAM_NAVIGATION +from constants import SUBCONTRACTOR, CONSTRUCTION_UNIT, IMPLEMENTATION_ORG, PAGE, PROGRAM_NAVIGATION, PROJECT_DEPARTMENT import paddle.nn.functional as F @@ -148,8 +148,15 @@ class SlotRecognition: prob_updates = {} for key, value in entities.items(): value = value.replace('#', '') + if "宏源" in value or "宏远" in value: + if key == SUBCONTRACTOR or key == CONSTRUCTION_UNIT: + updates[IMPLEMENTATION_ORG] = value + prob_updates[IMPLEMENTATION_ORG] = slot_probabilities[key] + else: + updates[key] = value + prob_updates[key] = slot_probabilities[key] # 暂时不支持分包商和监管单位的查询 - if key == SUBCONTRACTOR or key == CONSTRUCTION_UNIT or key == IMPLEMENTATION_ORG: + elif key == SUBCONTRACTOR or key == CONSTRUCTION_UNIT or key == IMPLEMENTATION_ORG: # print(f"recognize_probability- key:{key},value:{value}") match_results = standardize_name_only_high_score(value,clean_useless_company_name, GlobalData.simply_to_standard_company_name_map, GlobalData.pinyin_simply_to_standard_company_name_map, 90) if match_results: @@ -177,10 +184,18 @@ class SlotRecognition: else: updates[key] = value prob_updates[key] = slot_probabilities[key] - else: + + # 先不处理 PROJECT_DEPARTMENT,后续单独处理 + elif key != PROJECT_DEPARTMENT: updates[key] = value prob_updates[key] = slot_probabilities[key] + # 最后单独处理 PROJECT_DEPARTMENT + if PROJECT_DEPARTMENT in entities: + value = entities[PROJECT_DEPARTMENT].replace('#', '') + updates[PROJECT_DEPARTMENT] = value + prob_updates[PROJECT_DEPARTMENT] = slot_probabilities[PROJECT_DEPARTMENT] + entities.clear() slot_probabilities.clear() entities.update(updates) diff --git a/api/utils.py b/api/utils.py index 04875dc..a4078a4 100644 --- a/api/utils.py +++ b/api/utils.py @@ -12,7 +12,8 @@ import re from globalData import GlobalData from constants import USELESS_COMPANY_WORDS, USELESS_PROJECT_WORDS, CONSTRUCTION_UNIT, IMPLEMENTATION_ORG, \ - SUBCONTRACTOR, PROJECT_NAME, PROJECT_DEPARTMENT, RISK_LEVEL, TEAM_NAME, USELESS_PROGRAM_DEPARTMENT_WORDS + SUBCONTRACTOR, PROJECT_NAME, PROJECT_DEPARTMENT, RISK_LEVEL, TEAM_NAME, USELESS_PROGRAM_DEPARTMENT_WORDS, \ + SLOT_KEYWORDS from logger_util import setup_logger @@ -397,7 +398,7 @@ def generate_project_prompt_with_key(matched_projects, original_name="", slot_ke if slot_key == CONSTRUCTION_UNIT: type = "建管单位名" elif slot_key == IMPLEMENTATION_ORG: - type = "实施组织名" + type = "分公司名" elif slot_key == SUBCONTRACTOR: type = "分包单位名" elif slot_key == PROJECT_NAME: @@ -657,4 +658,15 @@ def check_standard_name_slot_probability(int_res, slot) -> tuple: return CheckResult.NEEDS_MORE_ROUNDS, "您查询的风险等级在系统中未找到,请确认风险等级后再次提问" return CheckResult.NO_MATCH, "" -# \ No newline at end of file + + +def process_msg_content(content): + if not any(keyword in content for keyword in SLOT_KEYWORDS): + match = re.search(r"^.*?[。!?.!?::]", content) + if match: + first_sentence = match.group(0).rstrip("。!?.!?::") # 去掉末尾的标点符号 + return first_sentence.strip() # 同时去掉前后空白字符 + else: + return content.strip() + else: + return content \ No newline at end of file diff --git a/generated_data/generated.py b/generated_data/generated.py index f09f373..22220f3 100644 --- a/generated_data/generated.py +++ b/generated_data/generated.py @@ -40,6 +40,7 @@ BASE_DATA = { "九号工程", "合州变电站", "合州换流站", + "合州换流站文都", "金牛变电站建筑", "金牛变电站建筑部分", "合州换流站线路", @@ -1050,6 +1051,11 @@ TEMPLATE_CONFIG = { ("{date}{implementation_organization}班组有哪些?", ["date", "implementation_organization"]), ("{date}{implementation_organization}{project_department}现场具体有哪些班组?", ["date", "implementation_organization", "project_department"]), + + ("{date}{project_name}有2项作业计划,班组详情", ["date", "project_name"]), + ("{date}{implementation_organization}{project_department}有1项作业计划,班组详情呢", + ["date", "implementation_organization", "project_department"]), + ("{date}{project_department}现场班组详情是什么?", ["date", "project_department"]), ("{date}{project_manager}现场班组情况?", ["date", "project_manager"]), ("{date}分包单位为{subcontractor}具体班组情况是什么?", ["date", "subcontractor"]), @@ -1146,7 +1152,9 @@ TEMPLATE_CONFIG = { "date": ["今天","最近"], "templates": [ ("{person_name}在哪个{person_query_type}", ["person_name","person_query_type"]), + ("{person_name}在那个{person_query_type}", ["person_name","person_query_type"]), ("请帮我查一下{person_name}在哪个{person_query_type}", ["person_name","person_query_type"]), + ("请帮我查一下{person_name}在那个{person_query_type}", ["person_name","person_query_type"]), ] },