From 94cb44466be734e023a394899726a8c74953f7b5 Mon Sep 17 00:00:00 2001 From: zy123 <646228430@qq.com> Date: Tue, 24 Dec 2024 15:13:11 +0800 Subject: [PATCH] =?UTF-8?q?12.24=20=E7=A6=85=E9=81=93bug=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flask_app/general/json_utils.py | 175 ++++++++- flask_app/general/商务技术评分提取.py | 92 +---- flask_app/货物标/技术参数要求提取.py | 492 ++++++++++++++++---------- 3 files changed, 462 insertions(+), 297 deletions(-) diff --git a/flask_app/general/json_utils.py b/flask_app/general/json_utils.py index 42fa369..9e3ed92 100644 --- a/flask_app/general/json_utils.py +++ b/flask_app/general/json_utils.py @@ -1,6 +1,8 @@ # -*- encoding:utf-8 -*- import json import re +from collections import defaultdict + def insert_missing_commas(json_str): """ @@ -100,13 +102,138 @@ def replace_latex_expressions(json_str): fixed_str = re.sub(r'\$(.*?)\$', replace_match, json_str) return fixed_str +def load_json_with_duplicates(json_str): + """ + 使用 object_pairs_hook 来加载包含重复键的 JSON 字符串, + 并将重复的键存储为列表。 + """ + def parse_object(pairs): + obj = defaultdict(list) + for key, value in pairs: + obj[key].append(value) + return obj -def extract_content_from_json(string): + return json.loads(json_str, object_pairs_hook=parse_object) + +def merge_duplicates(obj): + """ + 递归合并包含重复键的字典,优先保留字典类型的值。 + """ + if isinstance(obj, dict): + merged = {} + for key, values in obj.items(): + if len(values) == 1: + merged[key] = merge_duplicates(values[0]) + else: + # 如果有多个值,优先保留字典类型的值 + dict_values = [v for v in values if isinstance(v, dict)] + if dict_values: + # 如果存在字典类型的值,递归合并它们 + merged_value = {} + for dv in dict_values: + merged_value.update(merge_duplicates(dv)) + merged[key] = merged_value + else: + # 否则,保留最后一个值 + merged[key] = merge_duplicates(values[-1]) + return merged + elif isinstance(obj, list): + return [merge_duplicates(item) for item in obj] + else: + return obj + +def parse_json_with_duplicates(raw_string): + """ + 解析具有重复键的 JSON 字符串,将所有重复的键值对存储为列表。 + + Args: + json_string (str): 需要解析的 JSON 字符串。 + + Returns: + dict: 解析后的字典,重复的键对应的值为列表。 + + eg:输入:"综合实力": { + "评分": "2分", + "要求": "投标人具备电子与智能化工程专业承包二级资质及以上证书得 2分,不能够提供不得分(开标时需提供原件)。" + }, + "综合实力": { + "评分": "2分", + "要求": "投标人具有建筑机电安装工程专业承包三级资质或以上资质得 2分,否则不得分。(证书开标原件备查)。" + } + 输出:"综合实力": [ + { + "评分": "2分", + "要求": "投标人具备电子与智能化工程专业承包二级资质及以上证书得 2分,不能够提供不得分(开标时需提供原件)。" + }, + { + "评分": "2分", + "要求": "投标人具有建筑机电安装工程专业承包三级资质或以上资质得 2分,否则不得分。(证书开标原件备查)。" + }] + """ + + def custom_object_pairs_hook(pairs): + d = defaultdict(list) + for key, value in pairs: + try: + # 如果值是字典或列表,递归处理 + if isinstance(value, dict): + value = process_dict(value) + elif isinstance(value, list): + value = process_list(value) + d[key].append(value) + except Exception as e: + d[key].append(value) # 根据需求决定是否跳过或保留原值 + # 将有多个值的键转换为列表,单个值的键保持原样 + return {key: (values if len(values) > 1 else values[0]) for key, values in d.items()} + + def process_dict(d): + """ + 递归处理字典,确保所有重复键的值为列表。 + + Args: + d (dict): 需要处理的字典。 + + Returns: + dict: 处理后的字典。 + """ + try: + return custom_object_pairs_hook(d.items()) + except Exception as e: + return {} + + def process_list(l): + """ + 递归处理列表,确保列表中的所有字典也被处理。 + + Args: + l (list): 需要处理的列表。 + + Returns: + list: 处理后的列表。 + """ + try: + return [process_dict(item) if isinstance(item, dict) else item for item in l] + except Exception as e: + return [] + + """输入字符串,提取 { 和 } 之间的内容,并将其解析为字典""" + if not raw_string or not raw_string.strip(): + return {} + match = re.search(r'\{[\s\S]*\}', raw_string) + if match: + try: + json_string = match.group(0) + return json.loads(json_string, object_pairs_hook=custom_object_pairs_hook) + except json.JSONDecodeError as e: + print(f"json_utils: extract_content_from_json: JSON decode error: {e}") + return {} + else: + print("未找到有效的 JSON 内容。") + return {} # 返回空字典 +def extract_content_from_json(string,flag=False): """ 输入字符串,尝试解析 JSON 数据: 1. 如果成功解析,返回字典。 - 2. 如果失败,并且字符串长度超过阈值,返回原始字符串。 - 3. 如果失败且字符串长度不超过阈值,返回空字典。 """ if not string or not string.strip(): return {} @@ -119,37 +246,53 @@ def extract_content_from_json(string): original_json = match.group(0) + def parse_json(json_str): + """ + 根据 flag 参数解析 JSON 字符串。 + + Args: + json_str (str): 需要解析的 JSON 字符串。 + + Returns: + dict: 解析后的字典。 + """ + if not flag: + data_with_duplicates = load_json_with_duplicates(json_str) + return merge_duplicates(data_with_duplicates) + else: + return parse_json_with_duplicates(json_str) + # 尝试直接解析原始 JSON 数据 try: - parsed = json.loads(original_json) - return parsed # 返回解析后的字典 + parsed_data = parse_json(original_json) + return parsed_data # 返回解析后的字典 except json.JSONDecodeError: print("直接解析原始 JSON 失败。") # 方法1:逗号修复 try: fixed_json1 = insert_missing_commas(original_json) - parsed = json.loads(fixed_json1) - print("使用方法1:逗号修复成功。") - return parsed # 返回解析后的字典 + parsed_data = parse_json(fixed_json1) + print("使用方法1:逗号修复并处理重复键成功。") + return parsed_data # 返回解析后的字典 except json.JSONDecodeError: print("方法1(逗号修复)解析失败。") # 方法2:LaTeX 表达式替换 try: fixed_json2 = replace_latex_expressions(original_json) - parsed = json.loads(fixed_json2) - print("使用方法2:LaTeX 表达式替换成功。") - return parsed # 返回解析后的字典 + parsed_data = parse_json(fixed_json2) + print("使用方法2:LaTeX 表达式替换并处理重复键成功。") + return parsed_data # 返回解析后的字典 except json.JSONDecodeError: print("方法2(LaTeX 替换)解析失败。") # 方法3:非法转义序列修复 try: fixed_json3 = fix_json_escape_sequences(original_json) - parsed = json.loads(fixed_json3) - print("使用方法3:非法转义序列修复成功。") - return parsed # 返回解析后的字典 + parsed_data = parse_json(fixed_json3) + print("使用方法3:非法转义序列修复并处理重复键成功。") + return parsed_data # 返回解析后的字典 except json.JSONDecodeError: print("方法3(非法转义修复)解析失败。") @@ -157,9 +300,9 @@ def extract_content_from_json(string): print("所有修复方法均失败。") return {} # 返回空字典 -def clean_json_string(json_string): +def clean_json_string(json_string,flag=False): """清理JSON字符串,移除多余的反引号并解析为字典""" - return extract_content_from_json(json_string) + return extract_content_from_json(json_string,flag) def combine_json_results(json_lists): diff --git a/flask_app/general/商务技术评分提取.py b/flask_app/general/商务技术评分提取.py index ce23be3..7924d2f 100644 --- a/flask_app/general/商务技术评分提取.py +++ b/flask_app/general/商务技术评分提取.py @@ -4,6 +4,7 @@ import time from collections import defaultdict from flask_app.general.doubao import get_total_tokens +from flask_app.general.json_utils import clean_json_string, extract_content_from_json from flask_app.general.model_continue_query import process_continue_answers from flask_app.general.通义千问long import upload_file, qianwen_long @@ -63,95 +64,6 @@ def combine_technical_and_business(data, target_values): return extracted_data -def parse_json_with_duplicates(raw_string): - """ - 解析具有重复键的 JSON 字符串,将所有重复的键值对存储为列表。 - - Args: - json_string (str): 需要解析的 JSON 字符串。 - - Returns: - dict: 解析后的字典,重复的键对应的值为列表。 - - eg:输入:"综合实力": { - "评分": "2分", - "要求": "投标人具备电子与智能化工程专业承包二级资质及以上证书得 2分,不能够提供不得分(开标时需提供原件)。" - }, - "综合实力": { - "评分": "2分", - "要求": "投标人具有建筑机电安装工程专业承包三级资质或以上资质得 2分,否则不得分。(证书开标原件备查)。" - } - 输出:"综合实力": [ - { - "评分": "2分", - "要求": "投标人具备电子与智能化工程专业承包二级资质及以上证书得 2分,不能够提供不得分(开标时需提供原件)。" - }, - { - "评分": "2分", - "要求": "投标人具有建筑机电安装工程专业承包三级资质或以上资质得 2分,否则不得分。(证书开标原件备查)。" - }] - """ - - def custom_object_pairs_hook(pairs): - d = defaultdict(list) - for key, value in pairs: - try: - # 如果值是字典或列表,递归处理 - if isinstance(value, dict): - value = process_dict(value) - elif isinstance(value, list): - value = process_list(value) - d[key].append(value) - except Exception as e: - d[key].append(value) # 根据需求决定是否跳过或保留原值 - # 将有多个值的键转换为列表,单个值的键保持原样 - return {key: (values if len(values) > 1 else values[0]) for key, values in d.items()} - - def process_dict(d): - """ - 递归处理字典,确保所有重复键的值为列表。 - - Args: - d (dict): 需要处理的字典。 - - Returns: - dict: 处理后的字典。 - """ - try: - return custom_object_pairs_hook(d.items()) - except Exception as e: - return {} - - def process_list(l): - """ - 递归处理列表,确保列表中的所有字典也被处理。 - - Args: - l (list): 需要处理的列表。 - - Returns: - list: 处理后的列表。 - """ - try: - return [process_dict(item) if isinstance(item, dict) else item for item in l] - except Exception as e: - return [] - - """输入字符串,提取 { 和 } 之间的内容,并将其解析为字典""" - if not raw_string or not raw_string.strip(): - return {} - match = re.search(r'\{[\s\S]*\}', raw_string) - if match: - try: - json_string = match.group(0) - return json.loads(json_string, object_pairs_hook=custom_object_pairs_hook) - except json.JSONDecodeError as e: - print(f"json_utils: extract_content_from_json: JSON decode error: {e}") - return {} - else: - print("未找到有效的 JSON 内容。") - return {} # 返回空字典 - # 防止外键只有一个'一包'的情况 def process_data_based_on_key(data): exclude_word = ["包", "未知", "评分因素"] @@ -384,7 +296,7 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): total_tokens = evaluation_res[1] # print(evaluation_res) # 清理和处理响应 - cleaned_evaluation_res = parse_json_with_duplicates(message) # 处理重复键名的情况 + cleaned_evaluation_res = extract_content_from_json(message,True) # 处理重复键名的情况 # print(json.dumps(cleaned_evaluation_res,ensure_ascii=False,indent=4)) if not cleaned_evaluation_res and total_tokens > 5900: questions_to_continue.append((user_query, evaluation_res)) diff --git a/flask_app/货物标/技术参数要求提取.py b/flask_app/货物标/技术参数要求提取.py index 06a797e..60344ee 100644 --- a/flask_app/货物标/技术参数要求提取.py +++ b/flask_app/货物标/技术参数要求提取.py @@ -292,69 +292,63 @@ def generate_prompt(judge_res, full_text=None): 如果 `judge_res` 不包含 '否' 且有 `full_text`,则添加文件内容部分。 """ base_prompt = ''' -任务:你负责解析采购文件,提取采购需求,并以JSON格式返回,不要遗漏该项目需要采购的货物(或系统)。 +任务:你负责解析采购文件,提取采购需求,并以JSON格式返回,不要遗漏该项目需要采购的货物、设备或系统。 + +输出格式: +1.JSON格式,外层键名为需要采购的货物、设备或系统名称(一级键)。 +2.层次关系用嵌套键值对表示: + -采购活动可能将目标划分为多个系统或货物。若文档通过大标题或表格层次或序号标明这种归属关系,请在JSON中以嵌套形式表示: + -系统作为一级键,包含的货物作为二级键。 + -例子:""" + 1. 激光扫描系统:xxx + 1.1 激光器:xxx + """ + 根据货物前的序号,可以判断'激光扫描系统'是一级键,'激光器'是其下的二级键 + -如果系统提到"系统功能",在该系统的值中添加"系统功能"作为二级键,具体内容不展开; + -系统可以只包含“系统功能”,无需列出具体货物。 +3.防止重复键名规则: + -若同一层级下存在名称相同但采购要求(如型号、参数、功能)不同的货物,请在名称后添加编号以作区分,防止出现重复键名;默认情况下,无需在名称后添加编号,只有在名称相同时才需要添加编号。编号规则:'名称-编号',编号从1递增。例子:若同层级下存在两种名称相同但不同型号或参数的'交换机',那么键名分别是'交换机-1'和'交换机-2'。如果名称不同(如路由器和交换机),则无需编号。 +4.最内层键值应为空列表[]。 要求与指南: 1. 精准定位:请运用文档理解能力,定位文件中的采购需求部分。 -若有采购清单,请直接根据采购清单上的货物(或系统)名称给出结果,若没有采购清单,则从表格或文本中摘取采购信息。 -注意采购目标通常在诸如'名称'列,且每个目标占据一个单元格,你无需提取诸如'说明'、'规格'、'参数'、'描述'等其他列的内容,即你不需要给出详细的采购要求,更不要将这些单元格内的描述拆分作为其的子键,你仅返回采购的货物或系统或模块名称; 2. 采购目标:采购种类通常有硬件(如设备、货物)和软件(如系统软件、应用APP),一次采购活动可以同时包含这两种类型。 -3. 层级结构: - -采购活动可能将目标划分为多个系统或货物。若文档通过标题或表格层次标明这种归属关系,请在JSON中以嵌套形式表示: - -系统作为一级键,包含的货物作为二级键。 - -如果系统仅提到"系统功能",在该系统的值中添加"系统功能"作为二级键,具体内容不展开。 - -系统可以只包含“系统功能”,无需列出具体货物。 -4. 软件需求:对于软件系统或应用采购,若有多个系统且序号分明,请不要遗漏;若明确列出系统模块,提取模块名称并作为系统的子键,无需在模块下再细分功能。 -5. 完整性: - -确保系统内的所有货物均被提取,避免遗漏或添加未提及的内容。 - -若某货物(或系统、模块)在“主要设备功能指标”或类似标题下有详细参数说明,但未在前面清单或表格中诸如'名称'的列中列出,也需添加到结果中。 - -特殊情况: -1.若采购的货物或系统或模块名称前存在三角▲,△、五角★,☆,注意是名称前而非具体的技术参数或采购要求前,在返回名称时请保留前面的▲,△或★,☆符号,如'★高清摄像机'。 -2.若同一层级下存在名称相同但采购要求(如型号、参数、功能)不同的货物,请在名称后添加编号,以避免键名重复;默认情况下否则无需在名称后添加编号。 - -编号规则:'货物名-编号',编号从1递增。 - -注意:编号仅用于逻辑区分,取决于种类数,与采购数量无关。 - -例子:若同层级下存在4个但是两种型号的'交换机',那么命名分别是'交换机-1'和'交换机-2'。 - - -输出格式: -1.JSON格式,最外层键名为'采购需求'。 -2.层次关系用嵌套键值对表示。 -3.嵌套键名为系统或货物或模块名称,与原文保持一致。 -4.最内层键值应为空列表[]。 - -示例输出1,普通系统、货物类采购: +3. 软件类采购:对于软件系统或应用采购,若有多个系统且序号分明,请不要遗漏;若明确列出系统模块,提取模块名称并作为系统的子键,无需在模块下再细分功能。 +4. 完整性: + -若采购的货物或系统或模块名称前存在三角▲,△、五角★,☆,注意是名称前而非具体的技术参数或采购要求前,在返回名称时请保留前面的▲,△或★,☆符号,如'★高清摄像机'。 + -确保系统内的所有货物设备均被按层次提取,对于有明确清单且按序号划分的采购需求,请勿遗漏每一个序号所代表的货物或设备或系统,也不要添加未提及的内容。 + -若某货物(或系统、模块)在“主要设备功能指标”或类似标题下有详细参数说明,但未在前面清单或表格中诸如'名称'的列中列出,也需将该货物名添加到结果中。 + +示例输出1,普通系统、货物类采购,仅供格式参考: {{ - "采购需求": {{ - "交换机-1": [], - "交换机-2": [], - "门禁管理系统": {{ - "系统功能":[] - }}, - "交通监控视频子系统": {{ - "系统功能": [], - "交换机":[], - "高清视频抓拍像机": [], - "补光灯": [] - }}, - "LED全彩显示屏": [] - // 其他系统和货物 - }} + "交换机-1": [], + "交换机-2": [], + "门禁管理系统": {{ + "系统功能":[] + }}, + "交通监控视频子系统": {{ + "系统功能": [], + "交换机":[], + "高清视频抓拍像机": [], + "补光灯": [] + }}, + "LED全彩显示屏": [] + // 其他系统和货物 }} -示例输出2,软件系统类采购: +示例输出2,软件系统类采购,仅供格式参考: {{ - "采购需求": {{ - "信息管理系统": {{ - "通用模块":[], - "用户管理":[] - }}, - "信息检索系统": {{ - "系统功能":[], - "权限管理模块":[] - }}, - "XX管理系统":[], - //其他系统 - }} + "信息管理系统": {{ + "通用模块":[], + "用户管理":[] + }}, + "信息检索系统": {{ + "系统功能":[], + "权限管理模块":[] + }}, + "XX管理系统":[], + //其他系统 }} ''' if '否' not in judge_res and full_text: @@ -363,6 +357,123 @@ def generate_prompt(judge_res, full_text=None): base_prompt += "\n注意事项:\n1.严格按照上述要求执行,确保输出准确性和规范性。\n" return base_prompt +def preprocess_data(data): + """ + 动态识别并将值为非空列表且列表中每个项为单键字典或字符串的键转换为嵌套字典结构。 + 保持最内层的值为空列表 [],而不是空字典 {}。 + + 参数: + data (dict): 原始数据字典。 + + 返回: + dict: 预处理后的数据字典。 + """ + def is_single_key_dict_list(lst): + """ + 判断一个列表是否为非空,且每个项都是单键字典。 + + 参数: + lst (list): 要检查的列表。 + + 返回: + bool: 如果列表为非空且每个项都是单键字典,返回 True,否则返回 False。 + """ + if not isinstance(lst, list): + return False + if not lst: # 空列表不转换 + return False + for item in lst: + if not isinstance(item, dict) or len(item) != 1: + return False + return True + + def is_string_list(lst): + """ + 判断一个列表是否为非空,且每个项都是字符串。 + + 参数: + lst (list): 要检查的列表。 + + 返回: + bool: 如果列表为非空且每个项都是字符串,返回 True,否则返回 False。 + """ + if not isinstance(lst, list): + return False + if not lst: # 空列表不转换 + return False + for item in lst: + if not isinstance(item, str): + return False + return True + + def convert_list_of_dicts_to_dict(lst): + """ + 将列表中的单键字典转换为嵌套字典。 + + 参数: + lst (list): 包含单键字典的列表。 + + 返回: + dict: 合并后的嵌套字典。 + """ + new_dict = {} + for item in lst: + for sub_key, sub_value in item.items(): + if sub_key in new_dict: + print(f"警告: 键 '{sub_key}' 已存在,覆盖其值。") + new_dict[sub_key] = sub_value + return new_dict + + def convert_list_of_strings_to_dict(lst): + """ + 将列表中的字符串转换为嵌套字典,键为字符串,值为[]。 + + 参数: + lst (list): 包含字符串的列表。 + + 返回: + dict: 转换后的嵌套字典。 + """ + new_dict = {} + for item in lst: + if item in new_dict: + print(f"警告: 键 '{item}' 已存在,覆盖其值。") + new_dict[item] = [] + return new_dict + + def recursive_preprocess(current_data, path=""): + """ + 递归预处理函数,用于遍历嵌套字典并转换符合条件的键。 + + 参数: + current_data (dict): 当前遍历的字典。 + path (str): 当前路径,用于调试输出。 + """ + for key, value in current_data.items(): + current_path = f"{path} -> {key}" if path else key + if is_single_key_dict_list(value): + # 转换该键的值 + current_data[key] = convert_list_of_dicts_to_dict(value) + print(f"'{current_path}' 已成功转换为嵌套字典结构。") + elif is_string_list(value): + # 转换该键的值 + current_data[key] = convert_list_of_strings_to_dict(value) + print(f"'{current_path}' 已成功转换为嵌套字典结构。") + elif isinstance(value, dict): + # 递归处理嵌套字典 + recursive_preprocess(value, current_path) + elif isinstance(value, list): + # 如果列表中有嵌套字典,可能需要进一步处理 + for idx, item in enumerate(value): + if isinstance(item, dict): + recursive_preprocess(item, f"{current_path}[{idx}]") + elif isinstance(item, list): + recursive_preprocess({"list_item": item}, f"{current_path}[{idx}]") + + # 开始递归预处理 + recursive_preprocess(data) + return data + def get_technical_requirements(invalid_path,processed_filepath,model_type=1): judge_res = "" file_id = "" @@ -386,138 +497,141 @@ def get_technical_requirements(invalid_path,processed_filepath,model_type=1): user_query = generate_prompt(judge_res, full_text) model_res = doubao_model(user_query) print(model_res) + cleaned_res = clean_json_string(model_res) #转字典 - processed_data=truncate_system_keys(cleaned_res['采购需求']) - key_paths, grouped_paths, good_list, data_copy= generate_key_paths(processed_data) # 提取需要采购的货物清单 key_list:交通监控视频子系统.高清视频抓拍像机 ... grouped_paths是同一系统下同时有'交换机-1'和'交换机-2',提取'交换机' ,输出eg:{'交通标志.标志牌铝板', '交通信号灯.交换机'} - modified_data=rename_keys(data_copy) - user_query_template = """请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。请以 JSON 格式返回结果,键名为\"{}\",键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★和序号,不可擅自增删内容,尤其是不可擅自添加序号。 -**重要限制**: -- **仅提取技术参数或采购要求,不包括任何商务要求**。商务要求通常涉及供应商资格、报价条款、交货时间、质保等内容,是整体的要求;而技术参数或采购要求则具体描述产品的技术规格、功能、性能指标等。 -- **商务要求的关键词示例**(仅供参考,不限于此):报价、交货、合同、资质、认证、服务、保修期等。如果内容包含上述关键词,请仔细甄别是否属于商务要求。 - -要求与指南: -1. 你的键值应该全面,不要遗漏。 - -a.若技术参数或采购要求在表格中,那么单元格内的内容基本都要涵盖 - -对于单元格内以序号分隔的各条参数要求,应逐条提取,并分别作为键值中的字符串列表项。 - -对于无序号标明且在同一单元格内的参数要求或功能说明,也要根据语义分别添加进键值中。 - -b.若技术参数或采购要求在正文部分,应准确定位到与目标货物(设备、系统、功能模块)相关的内容,将其后的技术参数或采购要求或功能说明完整提取,逐一添加到键值的字符串列表中,不得擅自添加或修改序号。 -2. 如果存在嵌套结构,且原文为Markdown 的表格语法,如'摄像机|有效像素|≥900W像素', 请不要返回该Markdown语法,而是使用冒号':'将相关信息拼接在一起,生成一条完整且清晰的技术参数(或采购要求)描述,作为列表中的一个字符串。如"摄像机:有效像素:≥900W像素"。 -3. 字符串中的内容为具体的技术参数要求或采购要求,请不要返回诸如'(1)高清录像功能'这种标题性质且不能体现要求的内容。 -4. 如果该货物没有相关采购要求或技术参数要求,键值应为空列表[]。 - -### 示例输出1如下: -{{ - "摄像机控制键盘": [ - "1、▲支持串行 RS232/RS422 和 IP 混合控制,允许在一个控制器上使用 RS232/RS422/IP 控制单个系统中的摄像机;", - "2、支持 2 组 RS422 串口 VISCA 协议菊花链控制 2x7 台摄像机。", - "★能够自动对焦,提供检测报告" - ] -}} - -### 示例输出2如下(包含嵌套结构): -{{ - "摄像机": [ - "摄像机:有效像素:≥900W像素", - "摄像机:最低照度:彩色≤0.001lx", - "协议:routes 接口开放:具备;▲支持标准 ONVIF 协议与第三方厂家设备进行互联;支持 GB/T28181;应提供 SDK" - ] -}} - -{} -""" - user_query_template_two="""请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。由于该货物存在 {} 种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\";键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。 - -要求与指南: -1. 你的键值应该全面,不要遗漏。 - -a.若技术参数或采购要求在表格中,那么单元格内的内容基本都要涵盖 - -对于单元格内以序号分隔的各条参数要求,应逐条提取,并分别作为键值中的字符串列表项。 - -对于无序号标明且在同一单元格内的参数要求或功能说明,也要根据语义分别添加进键值中。 - -b.若技术参数或采购要求在正文部分,应准确定位到与目标货物(设备、系统、功能模块)相关的内容,将其后的技术参数或采购要求或功能说明完整提取,逐一添加到键值的字符串列表中,不得擅自添加或修改序号。 -2. 如果存在嵌套结构,且原文为Markdown 的表格语法,如'摄像机|有效像素|≥900W像素', 请不要返回该Markdown语法,而是使用冒号':'将相关信息拼接在一起,生成一条完整且清晰的技术参数(或采购要求)描述,作为列表中的一个字符串。如"摄像机:有效像素:≥900W像素"。 -3. 字符串中的内容为具体的技术参数要求或采购要求,请不要返回诸如'(1)高清录像功能'这种标题性质且不能体现要求的内容。 -4. 如果该货物没有相关采购要求或技术参数要求,键值应为空列表[]。 - -### 示例输出1如下: -{{ - "交换机-1": [ - "★1、支持固化千兆电口≥8 个,固化千兆光口≥2 个,桌面型设备;", - "2、支持静态链路聚合" - ], - "交换机-2": [ - "1、交换容量≥52Gbps,包转发率≥38.69Mpps,", - "2、提供国家强制性产品认证证书及测试报告(3C)", - "★能实现信号控制独立传输" - ] -}} - -### 示例输出2如下(包含嵌套结构): -{{ - "摄像机-1": [ - "摄像机:有效像素:≥900W像素", - "摄像机:最低照度:彩色≤0.001lx", - "协议:routes 接口开放:具备;▲支持标准 ONVIF 协议与第三方厂家设备进行互联;支持 GB/T28181;应提供 SDK" - ], - "摄像机-2": [ - "支持夜视", "支持云存储" - ] -}} - -{} - """ - queries = [] - for key in key_paths: - # 将键中的 '.' 替换为 '下的' - modified_key = key.replace('.', '下的') - # 使用修改后的键填充第一个占位符,原始键填充第二个占位符 - if model_type: - new_query = user_query_template.format(modified_key, key, modified_key,f"文件内容:{full_text}") #转豆包后取消注释 - else: - new_query = user_query_template.format(modified_key, key, modified_key,"") - queries.append(new_query) - - # 处理 grouped_paths 中的项,应用 user_query_template_two - for grouped_dict in grouped_paths: - for grouped_key, grouped_key_cnt in grouped_dict.items(): - # 将键中的 '.' 替换为 '下的' - modified_grouped_key = grouped_key.replace('.', '下的') - if model_type: - new_query = user_query_template_two.format(modified_grouped_key, grouped_key_cnt, grouped_key, - modified_grouped_key, f"文件内容:{full_text}") - else: - new_query = user_query_template_two.format(modified_grouped_key, grouped_key_cnt, grouped_key, - modified_grouped_key, "") - queries.append(new_query) - if model_type: - results = multi_threading(queries, "", "", 3,True) # 豆包 - else: - results = multi_threading(queries, "", file_id, 2,True) # 豆包 - temp_final={} - if not results: - print("errror!未获得大模型的回答!") - else: - # 第一步:收集需要调用 `continue_answer` 的问题和解析结果 - questions_to_continue = [] # 存储需要调用 continue_answer 的 (question, parsed) - max_tokens=3900 if model_type==1 else 5900 - for question, response in results: - message=response[0] - parsed = clean_json_string(message) - total_tokens=response[1] - if not parsed and total_tokens>max_tokens: - questions_to_continue.append((question, message)) - else: - temp_final.update(parsed) - # 第二步:多线程处理需要调用 `continue_answer` 的问题 - if questions_to_continue: - continued_results = process_continue_answers(questions_to_continue, model_type, file_id) - temp_final.update(continued_results) - - """根据所有键是否已添加处理技术要求""" - # 更新原始采购需求字典 - final_res=combine_and_update_results(modified_data, temp_final) - ffinal_res=all_postprocess(final_res) - ffinal_res["货物列表"] = good_list - # 输出最终的 JSON 字符串 - return {"采购需求":ffinal_res} + preprocessed_data=preprocess_data(cleaned_res) #确保最内层为[] + processed_data=truncate_system_keys(preprocessed_data) #限制深度 + print(json.dumps(processed_data,ensure_ascii=False,indent=4)) +# key_paths, grouped_paths, good_list, data_copy= generate_key_paths(processed_data) # 提取需要采购的货物清单 key_list:交通监控视频子系统.高清视频抓拍像机 ... grouped_paths是同一系统下同时有'交换机-1'和'交换机-2',提取'交换机' ,输出eg:{'交通标志.标志牌铝板', '交通信号灯.交换机'} +# modified_data=rename_keys(data_copy) +# user_query_template = """请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。请以 JSON 格式返回结果,键名为\"{}\",键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★和序号,不可擅自增删内容,尤其是不可擅自添加序号。 +# **重要限制**: +# - **仅提取技术参数或采购要求,不包括任何商务要求**。商务要求通常涉及供应商资格、报价条款、交货时间、质保等内容,是整体的要求;而技术参数或采购要求则具体描述产品的技术规格、功能、性能指标等。 +# - **商务要求的关键词示例**(仅供参考,不限于此):报价、交货、合同、资质、认证、服务、保修期等。如果内容包含上述关键词,请仔细甄别是否属于商务要求。 +# +# 要求与指南: +# 1. 你的键值应该全面,不要遗漏。 +# -a.若技术参数或采购要求在表格中,那么单元格内的内容基本都要涵盖 +# -对于单元格内以序号分隔的各条参数要求,应逐条提取,并分别作为键值中的字符串列表项。 +# -对于无序号标明且在同一单元格内的参数要求或功能说明,也要根据语义分别添加进键值中。 +# -b.若技术参数或采购要求在正文部分,应准确定位到与目标货物(设备、系统、功能模块)相关的内容,将其后的技术参数或采购要求或功能说明完整提取,逐一添加到键值的字符串列表中,不得擅自添加或修改序号。 +# 2. 如果存在嵌套结构,且原文为Markdown 的表格语法,如'摄像机|有效像素|≥900W像素', 请不要返回该Markdown语法,而是使用冒号':'将相关信息拼接在一起,生成一条完整且清晰的技术参数(或采购要求)描述,作为列表中的一个字符串。如"摄像机:有效像素:≥900W像素"。 +# 3. 字符串中的内容为具体的技术参数要求或采购要求,请不要返回诸如'(1)高清录像功能'这种标题性质且不能体现要求的内容。 +# 4. 如果该货物没有相关采购要求或技术参数要求,键值应为空列表[]。 +# +# ### 示例输出1如下: +# {{ +# "摄像机控制键盘": [ +# "1、▲支持串行 RS232/RS422 和 IP 混合控制,允许在一个控制器上使用 RS232/RS422/IP 控制单个系统中的摄像机;", +# "2、支持 2 组 RS422 串口 VISCA 协议菊花链控制 2x7 台摄像机。", +# "★能够自动对焦,提供检测报告" +# ] +# }} +# +# ### 示例输出2如下(包含嵌套结构): +# {{ +# "摄像机": [ +# "摄像机:有效像素:≥900W像素", +# "摄像机:最低照度:彩色≤0.001lx", +# "协议:routes 接口开放:具备;▲支持标准 ONVIF 协议与第三方厂家设备进行互联;支持 GB/T28181;应提供 SDK" +# ] +# }} +# +# {} +# """ +# user_query_template_two="""请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。由于该货物存在 {} 种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\";键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。 +# +# 要求与指南: +# 1. 你的键值应该全面,不要遗漏。 +# -a.若技术参数或采购要求在表格中,那么单元格内的内容基本都要涵盖 +# -对于单元格内以序号分隔的各条参数要求,应逐条提取,并分别作为键值中的字符串列表项。 +# -对于无序号标明且在同一单元格内的参数要求或功能说明,也要根据语义分别添加进键值中。 +# -b.若技术参数或采购要求在正文部分,应准确定位到与目标货物(设备、系统、功能模块)相关的内容,将其后的技术参数或采购要求或功能说明完整提取,逐一添加到键值的字符串列表中,不得擅自添加或修改序号。 +# 2. 如果存在嵌套结构,且原文为Markdown 的表格语法,如'摄像机|有效像素|≥900W像素', 请不要返回该Markdown语法,而是使用冒号':'将相关信息拼接在一起,生成一条完整且清晰的技术参数(或采购要求)描述,作为列表中的一个字符串。如"摄像机:有效像素:≥900W像素"。 +# 3. 字符串中的内容为具体的技术参数要求或采购要求,请不要返回诸如'(1)高清录像功能'这种标题性质且不能体现要求的内容。 +# 4. 如果该货物没有相关采购要求或技术参数要求,键值应为空列表[]。 +# +# ### 示例输出1如下: +# {{ +# "交换机-1": [ +# "★1、支持固化千兆电口≥8 个,固化千兆光口≥2 个,桌面型设备;", +# "2、支持静态链路聚合" +# ], +# "交换机-2": [ +# "1、交换容量≥52Gbps,包转发率≥38.69Mpps,", +# "2、提供国家强制性产品认证证书及测试报告(3C)", +# "★能实现信号控制独立传输" +# ] +# }} +# +# ### 示例输出2如下(包含嵌套结构): +# {{ +# "摄像机-1": [ +# "摄像机:有效像素:≥900W像素", +# "摄像机:最低照度:彩色≤0.001lx", +# "协议:routes 接口开放:具备;▲支持标准 ONVIF 协议与第三方厂家设备进行互联;支持 GB/T28181;应提供 SDK" +# ], +# "摄像机-2": [ +# "支持夜视", "支持云存储" +# ] +# }} +# +# {} +# """ +# queries = [] +# for key in key_paths: +# # 将键中的 '.' 替换为 '下的' +# modified_key = key.replace('.', '下的') +# # 使用修改后的键填充第一个占位符,原始键填充第二个占位符 +# if model_type: +# new_query = user_query_template.format(modified_key, key, modified_key,f"文件内容:{full_text}") #转豆包后取消注释 +# else: +# new_query = user_query_template.format(modified_key, key, modified_key,"") +# queries.append(new_query) +# +# # 处理 grouped_paths 中的项,应用 user_query_template_two +# for grouped_dict in grouped_paths: +# for grouped_key, grouped_key_cnt in grouped_dict.items(): +# # 将键中的 '.' 替换为 '下的' +# modified_grouped_key = grouped_key.replace('.', '下的') +# if model_type: +# new_query = user_query_template_two.format(modified_grouped_key, grouped_key_cnt, grouped_key, +# modified_grouped_key, f"文件内容:{full_text}") +# else: +# new_query = user_query_template_two.format(modified_grouped_key, grouped_key_cnt, grouped_key, +# modified_grouped_key, "") +# queries.append(new_query) +# if model_type: +# results = multi_threading(queries, "", "", 3,True) # 豆包 +# else: +# results = multi_threading(queries, "", file_id, 2,True) # 豆包 +# temp_final={} +# if not results: +# print("errror!未获得大模型的回答!") +# else: +# # 第一步:收集需要调用 `continue_answer` 的问题和解析结果 +# questions_to_continue = [] # 存储需要调用 continue_answer 的 (question, parsed) +# max_tokens=3900 if model_type==1 else 5900 +# for question, response in results: +# message=response[0] +# parsed = clean_json_string(message) +# total_tokens=response[1] +# if not parsed and total_tokens>max_tokens: +# questions_to_continue.append((question, message)) +# else: +# temp_final.update(parsed) +# # 第二步:多线程处理需要调用 `continue_answer` 的问题 +# if questions_to_continue: +# continued_results = process_continue_answers(questions_to_continue, model_type, file_id) +# temp_final.update(continued_results) +# +# """根据所有键是否已添加处理技术要求""" +# # 更新原始采购需求字典 +# final_res=combine_and_update_results(modified_data, temp_final) +# ffinal_res=all_postprocess(final_res) +# ffinal_res["货物列表"] = good_list +# # 输出最终的 JSON 字符串 +# return {"采购需求":ffinal_res} def test_all_files_in_folder(input_folder, output_folder): # 确保输出文件夹存在 @@ -544,16 +658,12 @@ def test_all_files_in_folder(input_folder, output_folder): # 如果采购需求为空 考虑再调用一次大模型 qianwen-stream if __name__ == "__main__": start_time=time.time() - # truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\469d2aee-9024-4993-896e-2ac7322d41b7\\ztbfile_procurement.docx" - truncate_docfile=r"C:\Users\Administrator\Desktop\fsdownload\6b7ea51f-eb6d-4a4f-a518-dc1f57d27ea1\ztbfile.docx" - truncate_file=r'C:\Users\Administrator\Desktop\fsdownload\6b7ea51f-eb6d-4a4f-a518-dc1f57d27ea1\省考试院院内电子屏采购.pdf' # invalid_path="D:\\flask_project\\flask_app\\static\\output\\output1\\e7dda5cb-10ba-47a8-b989-d2993d34bb89\\ztbfile.pdf" - # truncate_file="D:\\flask_project\\flask_app\\static\\output\\output1\\e7dda5cb-10ba-47a8-b989-d2993d34bb89\\ztbfile_procurement.docx" - # output_folder="C:\\Users\\Administrator\\Desktop\\货物标\\output1\\tmp" # file_id = upload_file(truncate_file) + truncate_file=r'C:\Users\Administrator\Desktop\new招标文件\output5\广水市公安局音视频监控系统设备采购项目_procurement.pdf' invalid_path=r"D:\flask_project\flask_app\static\output\output1\000aac0d-4aa4-4bc3-a9f9-76ff82ec2470\invalid_added.docx" # file_id=upload_file(truncate_file) - # processed_filepath = pdf2txt(truncate_file) + # processed_filepath = convert_file_to_markdown(truncate_file) processed_filepath=r"D:\flask_project\flask_app\static\output\output1\000aac0d-4aa4-4bc3-a9f9-76ff82ec2470\extract1.txt" res=get_technical_requirements(invalid_path,processed_filepath) json_string = json.dumps(res, ensure_ascii=False, indent=4)