From eaa384cf7e4d86890d8ff2377732f0e161f53b75 Mon Sep 17 00:00:00 2001 From: python Date: Tue, 9 Dec 2025 12:56:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=8F=90=E7=A4=BA=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=92=8CAI=E6=9C=8D=E5=8A=A1=E5=86=85=E5=AE=B9?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=BC=BA=E4=BF=A1=E6=81=AF=E6=8F=90=E5=8F=96?= =?UTF-8?q?=E5=8A=A9=E6=89=8B=E7=9A=84=E6=8F=8F=E8=BF=B0=EF=BC=8C=E6=98=8E?= =?UTF-8?q?=E7=A1=AE=E6=8F=90=E5=8F=96=E8=A6=81=E6=B1=82=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=90=8E=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=E4=BB=A5?= =?UTF-8?q?=E6=8E=A8=E6=96=AD=E7=BC=BA=E5=A4=B1=E5=AD=97=E6=AE=B5=EF=BC=8C?= =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=AD=97=E6=AE=B5=E6=8F=90=E5=8F=96=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E4=BB=A5=E6=8F=90=E9=AB=98=E6=95=B0=E6=8D=AE=E6=8F=90?= =?UTF-8?q?=E5=8F=96=E7=9A=84=E5=87=86=E7=A1=AE=E6=80=A7=E5=92=8C=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/prompt_config.json | 28 ++++--- services/ai_service.py | 170 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 180 insertions(+), 18 deletions(-) diff --git a/config/prompt_config.json b/config/prompt_config.json index 368ce0f..637747f 100644 --- a/config/prompt_config.json +++ b/config/prompt_config.json @@ -1,15 +1,15 @@ { "prompt_template": { - "intro": "请从以下输入文本中提取结构化信息。仔细分析文本内容,准确提取每个字段的值。", + "intro": "请从以下输入文本中提取结构化信息。仔细分析文本内容,准确提取每个字段的值。\n\n⚠️ 重要提醒:请逐字逐句仔细阅读输入文本,不要遗漏任何信息。对于性别、年龄、职务、单位、文化程度等字段,请特别仔细查找,这些信息可能以各种形式出现在文本中。", "input_text_label": "输入文本:", "output_fields_label": "需要提取的字段(请仔细分析每个字段,确保提取完整):", "json_format_label": "请严格按照以下JSON格式返回结果,只返回JSON对象,不要包含任何其他文字说明或markdown代码块标记:", "requirements_label": "重要要求(请严格遵守):", "requirements": [ - "逐字逐句仔细分析输入文本,不要遗漏任何信息", - "对于每个字段,请从多个角度思考:直接提及、同义词、隐含信息、可推断信息", - "如果文本中明确提到某个信息,必须提取出来,不能设为空", - "如果可以通过已有信息合理推断(如根据出生年月推算年龄),请进行推断并填写", + "⚠️ 逐字逐句仔细分析输入文本,不要遗漏任何信息。请特别关注性别、年龄、职务、单位、文化程度等字段", + "对于每个字段,请从多个角度思考:直接提及、同义词、隐含信息、可推断信息。例如:性别可能以\"男\"、\"女\"、\"男性\"、\"女性\"、\"先生\"、\"女士\"等形式出现", + "如果文本中明确提到某个信息(如\"30岁\"、\"男\"、\"总经理\"、\"某公司\"等),必须提取出来,不能设为空", + "如果可以通过已有信息合理推断,请进行推断并填写:\n - 根据出生年月(如1980年05月)和当前年份(2024年)计算年龄(44岁)\n - 从单位及职务(如\"某公司总经理\")中拆分单位(\"某公司\")和职务(\"总经理\")\n - 从工作基本情况中提取性别、文化程度等信息", "如果某个字段在输入文本中确实找不到任何相关信息,该字段值才设为空字符串\"\"", "日期格式统一为中文格式:YYYY年MM月(如:1980年05月表示1980年5月),如果包含日期信息则格式为YYYY年MM月DD日(如:1985年05月17日)。注意:年份必须是4位数字,月份和日期必须是2位数字(如1980年5月应格式化为1980年05月,不是1980年5月)", "性别统一为\"男\"或\"女\",不要使用\"男性\"或\"女性\"。如果文本中提到\"男性\"、\"男\"、\"先生\"等,统一转换为\"男\";如果提到\"女性\"、\"女\"、\"女士\"等,统一转换为\"女\"", @@ -47,8 +47,11 @@ "description": "被核查人员性别", "rules": [ "只能返回\"男\"或\"女\"", - "如果文本中提到\"男性\"、\"男性公民\"等,统一转换为\"男\"", - "如果文本中提到\"女性\"、\"女性公民\"等,统一转换为\"女\"" + "如果文本中提到\"男性\"、\"男性公民\"、\"男\"、\"先生\"等,统一转换为\"男\"", + "如果文本中提到\"女性\"、\"女性公民\"、\"女\"、\"女士\"等,统一转换为\"女\"", + "请仔细查找文本中所有可能表示性别的词汇,不要遗漏", + "如果文本中提到\"XXX,男,...\"或\"XXX,女,...\",必须提取性别", + "如果工作基本情况中提到性别信息,必须提取" ] }, "target_date_of_birth": { @@ -122,9 +125,10 @@ "target_age": { "description": "被核查人员年龄", "rules": [ - "如果文本中直接提到年龄(如\"30岁\"、\"30周岁\"、\"年龄30\"),直接提取数字部分", - "如果只有出生年月(如\"1980年5月出生\"、\"198005\"),可以根据当前年份(2024年)计算年龄", + "如果文本中直接提到年龄(如\"30岁\"、\"30周岁\"、\"年龄30\"、\"现年30\"),直接提取数字部分", + "如果只有出生年月(如\"1980年5月出生\"、\"1980年05月\"),必须根据当前年份(2024年)计算年龄(如1980年出生,2024-1980=44岁)", "年龄格式:纯数字,单位为岁,如\"44\"表示44岁", + "⚠️ 如果已有出生年月信息,必须计算年龄,不能设为空字符串", "如果文本中既没有直接提到年龄,也没有出生年月信息,则设为空字符串" ] }, @@ -144,8 +148,9 @@ "从单位及职务信息中提取单位名称部分", "单位名称包括:公司、企业、机关、事业单位、部门等(如\"XX公司\"、\"XX局\"、\"XX部门\"、\"XX委员会\"等)", "如果文本中只提到单位名称,直接提取", - "如果文本中提到\"XX公司总经理\",提取\"XX公司\"部分", + "⚠️ 如果文本中提到\"XX公司总经理\",必须提取\"XX公司\"部分,不能设为空", "如果文本中提到\"XX单位XX职务\",提取\"XX单位\"部分", + "如果已有单位及职务字段(target_organization_and_position),必须从中拆分出单位名称", "保持单位名称的完整性,不要遗漏" ] }, @@ -155,8 +160,9 @@ "从单位及职务信息中提取职务名称部分", "职务名称包括:总经理、经理、局长、处长、科长、主任、书记、部长等", "如果文本中只提到职务名称,直接提取", - "如果文本中提到\"XX公司总经理\",提取\"总经理\"部分", + "⚠️ 如果文本中提到\"XX公司总经理\",必须提取\"总经理\"部分,不能设为空", "如果文本中提到\"XX单位XX职务\",提取\"XX职务\"部分", + "如果已有单位及职务字段(target_organization_and_position),必须从中拆分出职务名称", "如果文本中提到多个职务,需要全部提取,用\"、\"连接", "保持职务名称的准确性" ] diff --git a/services/ai_service.py b/services/ai_service.py index aa2af08..d27da71 100644 --- a/services/ai_service.py +++ b/services/ai_service.py @@ -204,7 +204,7 @@ class AIService: "messages": [ { "role": "system", - "content": "你是一个专业的数据提取助手。请仔细分析用户提供的输入文本,提取所有相关信息,并严格按照指定的JSON格式返回结果。只返回JSON对象,不要包含任何其他文字说明、思考过程或markdown代码块标记。" + "content": "你是一个专业的数据提取助手。请仔细分析用户提供的输入文本,提取所有相关信息,并严格按照指定的JSON格式返回结果。\n\n重要要求:\n1. 必须仔细阅读输入文本的每一个字,不要遗漏任何信息\n2. 对于每个字段,请从多个角度思考:直接提及、同义词、隐含信息、可推断信息\n3. 如果文本中明确提到某个信息(如性别、年龄、职务等),必须提取出来,不能设为空\n4. 如果可以通过已有信息合理推断(如根据出生年月推算年龄,从单位及职务中拆分单位和职务),请进行推断并填写\n5. 只返回JSON对象,不要包含任何其他文字说明、思考过程或markdown代码块标记" }, { "role": "user", @@ -212,12 +212,12 @@ class AIService: } ], "stream": False, - "presence_penalty": 1.03, - "frequency_penalty": 1.0, - "repetition_penalty": 1.0, - "temperature": 0.3, - "top_p": 0.95, - "top_k": 1, + "presence_penalty": 1.05, # 提高presence_penalty,鼓励模型提取更多不同字段 + "frequency_penalty": 1.02, # 提高frequency_penalty,减少重复 + "repetition_penalty": 1.05, # 提高repetition_penalty,避免重复 + "temperature": 0.2, # 降低temperature,提高确定性 + "top_p": 0.9, # 降低top_p,更聚焦 + "top_k": 40, # 增加top_k,允许更多选择 "seed": 1, "max_tokens": self.api_max_tokens, "n": 1, @@ -305,6 +305,12 @@ class AIService: for key in ['target_name', 'target_gender', 'target_age', 'target_date_of_birth']: if key in normalized_data: print(f"[AI服务] 日期格式化后 {key} = '{normalized_data[key]}'") + # 后处理:从已有信息推断缺失字段 + normalized_data = self._post_process_inferred_fields(normalized_data, output_fields) + # 打印后处理后的关键字段 + for key in ['target_name', 'target_gender', 'target_age', 'target_date_of_birth', 'target_organization', 'target_position']: + if key in normalized_data: + print(f"[AI服务] 后处理后 {key} = '{normalized_data[key]}'") return normalized_data # 如果无法提取JSON,记录错误 @@ -840,6 +846,156 @@ class AIService: # 如果无法解析,返回原值 return date_str + def _post_process_inferred_fields(self, data: Dict, output_fields: List[Dict]) -> Dict: + """ + 后处理:从已有信息推断缺失字段 + + Args: + data: 提取的数据字典 + output_fields: 输出字段列表 + + Returns: + 后处理后的数据字典 + """ + # 创建字段编码到字段信息的映射 + field_code_map = {field['field_code']: field for field in output_fields} + + # 1. 从出生年月计算年龄 + if 'target_age' in field_code_map and (not data.get('target_age') or data.get('target_age') == ''): + if 'target_date_of_birth' in data and data.get('target_date_of_birth'): + age = self._calculate_age_from_birth_date(data['target_date_of_birth']) + if age: + data['target_age'] = str(age) + print(f"[AI服务] 后处理:从出生年月 '{data['target_date_of_birth']}' 计算年龄: {age}岁") + + # 2. 从单位及职务中拆分单位和职务 + if 'target_organization_and_position' in data and data.get('target_organization_and_position'): + org_pos = data['target_organization_and_position'] + + # 拆分单位 + if 'target_organization' in field_code_map and (not data.get('target_organization') or data.get('target_organization') == ''): + org = self._extract_organization_from_org_pos(org_pos) + if org: + data['target_organization'] = org + print(f"[AI服务] 后处理:从单位及职务 '{org_pos}' 提取单位: {org}") + + # 拆分职务 + if 'target_position' in field_code_map and (not data.get('target_position') or data.get('target_position') == ''): + pos = self._extract_position_from_org_pos(org_pos) + if pos: + data['target_position'] = pos + print(f"[AI服务] 后处理:从单位及职务 '{org_pos}' 提取职务: {pos}") + + return data + + def _calculate_age_from_birth_date(self, birth_date: str) -> Optional[int]: + """ + 从出生年月计算年龄 + + Args: + birth_date: 出生年月,格式如 "1980年05月" 或 "198005" + + Returns: + 年龄(整数),如果无法计算则返回None + """ + if not birth_date: + return None + + birth_date = str(birth_date).strip() + + # 提取年份 + year_match = re.search(r'(\d{4})', birth_date) + if not year_match: + return None + + birth_year = int(year_match.group(1)) + current_year = 2024 # 当前年份 + + # 计算年龄 + age = current_year - birth_year + + # 验证年龄合理性(0-150岁) + if 0 <= age <= 150: + return age + + return None + + def _extract_organization_from_org_pos(self, org_pos: str) -> Optional[str]: + """ + 从单位及职务中提取单位名称 + + Args: + org_pos: 单位及职务,如 "某公司总经理" + + Returns: + 单位名称,如 "某公司" + """ + if not org_pos: + return None + + org_pos = str(org_pos).strip() + + # 常见职务关键词 + position_keywords = [ + '总经理', '经理', '局长', '处长', '科长', '主任', '书记', '部长', + '副部长', '副经理', '副局长', '副处长', '副科长', '副主任', '副书记', + '董事长', '副董事长', '总裁', '副总裁', '总监', '副总监', + '部长', '副部长', '司长', '副司长', '厅长', '副厅长', + '市长', '副市长', '县长', '副县长', '乡长', '副乡长', + '镇长', '副镇长', '村长', '副村长' + ] + + # 尝试匹配:单位 + 职务 + for pos_keyword in position_keywords: + if pos_keyword in org_pos: + # 找到职务位置,提取前面的单位部分 + pos_index = org_pos.find(pos_keyword) + if pos_index > 0: + org = org_pos[:pos_index].strip() + if org: + return org + + # 如果没有找到明确的职务关键词,尝试其他模式 + # 例如:"XX公司XX部门XX职务" + # 这里简单返回,可能需要更复杂的逻辑 + + return None + + def _extract_position_from_org_pos(self, org_pos: str) -> Optional[str]: + """ + 从单位及职务中提取职务名称 + + Args: + org_pos: 单位及职务,如 "某公司总经理" + + Returns: + 职务名称,如 "总经理" + """ + if not org_pos: + return None + + org_pos = str(org_pos).strip() + + # 常见职务关键词 + position_keywords = [ + '总经理', '经理', '局长', '处长', '科长', '主任', '书记', '部长', + '副部长', '副经理', '副局长', '副处长', '副科长', '副主任', '副书记', + '董事长', '副董事长', '总裁', '副总裁', '总监', '副总监', + '部长', '副部长', '司长', '副司长', '厅长', '副厅长', + '市长', '副市长', '县长', '副县长', '乡长', '副乡长', + '镇长', '副镇长', '村长', '副村长' + ] + + # 按长度从长到短排序,优先匹配长关键词(如"副经理"优先于"经理") + position_keywords.sort(key=len, reverse=True) + + # 尝试匹配职务关键词 + for pos_keyword in position_keywords: + if pos_keyword in org_pos: + return pos_keyword + + return None + def _parse_text_response(self, text: str, output_fields: List[Dict]) -> Dict: """ 从文本响应中解析字段值(备用方案)