12.24 禅道bug修改

This commit is contained in:
zy123 2024-12-24 15:13:11 +08:00
parent 5a3c8aae0f
commit 94cb44466b
3 changed files with 462 additions and 297 deletions

View File

@ -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逗号修复解析失败。")
# 方法2LaTeX 表达式替换
try:
fixed_json2 = replace_latex_expressions(original_json)
parsed = json.loads(fixed_json2)
print("使用方法2LaTeX 表达式替换成功。")
return parsed # 返回解析后的字典
parsed_data = parse_json(fixed_json2)
print("使用方法2LaTeX 表达式替换并处理重复键成功。")
return parsed_data # 返回解析后的字典
except json.JSONDecodeError:
print("方法2LaTeX 替换)解析失败。")
# 方法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):

View File

@ -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))

View File

@ -292,40 +292,37 @@ 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. 完整性
-确保系统内的所有货物均被提取避免遗漏或添加未提及的内容
-若某货物或系统模块主要设备功能指标或类似标题下有详细参数说明但未在前面清单或表格中诸如'名称'的列中列出也需添加到结果中
3. 软件类采购对于软件系统或应用采购若有多个系统且序号分明请不要遗漏若明确列出系统模块提取模块名称并作为系统的子键无需在模块下再细分功能
4. 完整性
-若采购的货物或系统或模块名称前存在三角,五角,注意是名称前而非具体的技术参数或采购要求前在返回名称时请保留前面的,,符号'★高清摄像机'
-确保系统内的所有货物设备均被按层次提取对于有明确清单且按序号划分的采购需求,请勿遗漏每一个序号所代表的货物或设备或系统也不要添加未提及的内容
-若某货物或系统模块主要设备功能指标或类似标题下有详细参数说明但未在前面清单或表格中诸如'名称'的列中列出也需将该货物名添加到结果中
特殊情况
1.若采购的货物或系统或模块名称前存在三角,五角,注意是名称前而非具体的技术参数或采购要求前在返回名称时请保留前面的,,符号'★高清摄像机'
2.若同一层级下存在名称相同但采购要求如型号参数功能不同的货物请在名称后添加编号以避免键名重复默认情况下否则无需在名称后添加编号
-编号规则'货物名-编号'编号从1递增
-注意编号仅用于逻辑区分取决于种类数与采购数量无关
-例子若同层级下存在4个但是两种型号的'交换机'那么命名分别是'交换机-1''交换机-2'
输出格式
1.JSON格式最外层键名为'采购需求'
2.层次关系用嵌套键值对表示
3.嵌套键名为系统或货物或模块名称与原文保持一致
4.最内层键值应为空列表[]
示例输出1普通系统货物类采购
示例输出1普通系统货物类采购仅供格式参考
{{
"采购需求": {{
"交换机-1": [],
"交换机-2": [],
"门禁管理系统": {{
@ -339,11 +336,9 @@ def generate_prompt(judge_res, full_text=None):
}},
"LED全彩显示屏": []
// 其他系统和货物
}}
}}
示例输出2软件系统类采购
示例输出2软件系统类采购仅供格式参考
{{
"采购需求": {{
"信息管理系统": {{
"通用模块":[],
"用户管理":[]
@ -354,7 +349,6 @@ def generate_prompt(judge_res, full_text=None):
}},
"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)