2024-09-13 15:03:55 +08:00
|
|
|
|
# -*- encoding:utf-8 -*-
|
2024-12-19 17:32:39 +08:00
|
|
|
|
import concurrent.futures
|
2024-09-13 15:03:55 +08:00
|
|
|
|
import json
|
|
|
|
|
import os
|
2024-11-08 15:44:29 +08:00
|
|
|
|
import re
|
2024-11-17 12:11:11 +08:00
|
|
|
|
import time
|
2024-11-23 15:38:52 +08:00
|
|
|
|
from collections import defaultdict
|
|
|
|
|
from copy import deepcopy
|
2024-12-19 17:32:39 +08:00
|
|
|
|
from flask_app.general.model_continue_query import continue_answer, process_continue_answers
|
2024-12-17 14:47:19 +08:00
|
|
|
|
from flask_app.general.file2markdown import convert_file_to_markdown
|
2024-11-20 19:35:22 +08:00
|
|
|
|
from flask_app.general.format_change import pdf2docx
|
2024-10-22 10:06:22 +08:00
|
|
|
|
from flask_app.general.多线程提问 import multi_threading
|
|
|
|
|
from flask_app.general.通义千问long import qianwen_long, upload_file
|
|
|
|
|
from flask_app.general.json_utils import clean_json_string, combine_json_results
|
2024-11-26 15:06:57 +08:00
|
|
|
|
from flask_app.general.doubao import doubao_model, generate_full_user_query, pdf2txt, read_txt_to_string
|
2024-12-19 12:18:50 +08:00
|
|
|
|
from flask_app.货物标.技术参数要求提取后处理函数 import all_postprocess
|
2024-11-17 12:11:11 +08:00
|
|
|
|
|
2024-11-23 16:32:07 +08:00
|
|
|
|
def truncate_system_keys(data):
|
|
|
|
|
"""
|
|
|
|
|
遍历输入的字典,若键名中包含'系统'或'软件',且其子键数量 >= 3,则将其子键的值设置为[],限制深度为一层。
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
data (dict or list): 输入的数据,可以是嵌套的字典或列表。
|
2024-11-17 12:11:11 +08:00
|
|
|
|
|
2024-11-23 16:32:07 +08:00
|
|
|
|
Returns:
|
|
|
|
|
dict or list: 处理后的数据。
|
|
|
|
|
"""
|
|
|
|
|
if isinstance(data, dict):
|
|
|
|
|
new_data = {}
|
|
|
|
|
for key, value in data.items():
|
|
|
|
|
# 检查键名是否包含'系统'或'软件'
|
|
|
|
|
if '系统' in key or '软件' in key:
|
|
|
|
|
if isinstance(value, dict):
|
|
|
|
|
child_count = len(value)
|
|
|
|
|
# 判断子键数量是否 >= 3
|
|
|
|
|
if child_count >= 3:
|
|
|
|
|
# 截断子键,将其值设置为 []
|
|
|
|
|
new_data[key] = {subkey: [] for subkey in value.keys()}
|
|
|
|
|
else:
|
|
|
|
|
# 子键数量少于3,递归处理子键
|
|
|
|
|
new_data[key] = truncate_system_keys(value)
|
|
|
|
|
else:
|
|
|
|
|
# 如果值不是字典(例如列表),直接设置为 []
|
|
|
|
|
new_data[key] = []
|
|
|
|
|
else:
|
|
|
|
|
# 不包含'系统'或'软件',递归处理子字典或列表
|
|
|
|
|
new_data[key] = truncate_system_keys(value)
|
|
|
|
|
return new_data
|
|
|
|
|
elif isinstance(data, list):
|
|
|
|
|
# 如果是列表,递归处理列表中的每个元素
|
|
|
|
|
return [truncate_system_keys(item) for item in data]
|
|
|
|
|
else:
|
|
|
|
|
# 对于其他类型的数据,保持不变
|
|
|
|
|
return data
|
2024-11-23 15:38:52 +08:00
|
|
|
|
def generate_key_paths(data):
|
2024-10-25 17:50:20 +08:00
|
|
|
|
"""
|
2024-11-23 15:38:52 +08:00
|
|
|
|
处理输入的字典,生成 key_paths, grouped_paths 和 good_list,并根据条件修改原始字典。
|
2024-10-25 17:50:20 +08:00
|
|
|
|
|
|
|
|
|
参数:
|
2024-11-23 15:38:52 +08:00
|
|
|
|
data (dict): 输入的嵌套字典。
|
2024-10-25 17:50:20 +08:00
|
|
|
|
|
|
|
|
|
返回:
|
2024-11-23 15:38:52 +08:00
|
|
|
|
tuple: 包含 key_paths, grouped_paths 和 good_list 的元组。
|
2024-10-25 17:50:20 +08:00
|
|
|
|
"""
|
2024-11-23 15:38:52 +08:00
|
|
|
|
# 编译用于匹配后缀的正则表达式模式
|
|
|
|
|
pattern = re.compile(r'(.+)-\d+$')
|
2024-11-17 16:29:02 +08:00
|
|
|
|
|
2024-12-03 17:37:02 +08:00
|
|
|
|
# 初始化结果列表和字典
|
2024-09-13 15:03:55 +08:00
|
|
|
|
key_paths = []
|
2024-12-03 17:37:02 +08:00
|
|
|
|
grouped_counts = defaultdict(int) # 用于记录每个 grouped_path 的数量
|
2024-11-23 15:38:52 +08:00
|
|
|
|
good_list = []
|
2024-10-25 17:50:20 +08:00
|
|
|
|
|
2024-11-23 15:38:52 +08:00
|
|
|
|
def recurse(current_dict, path):
|
|
|
|
|
"""
|
2024-12-03 17:37:02 +08:00
|
|
|
|
递归遍历字典,处理 key_paths 和 grouped_paths,并收集 good_list 和 grouped_counts。
|
2024-11-23 15:38:52 +08:00
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
current_dict (dict): 当前遍历的字典。
|
|
|
|
|
path (list): 当前路径的键列表。
|
|
|
|
|
"""
|
|
|
|
|
# 第一遍遍历,统计每个基名的出现次数
|
|
|
|
|
base_name_count = {}
|
|
|
|
|
base_names = {}
|
|
|
|
|
for key in current_dict.keys():
|
|
|
|
|
match = pattern.match(key)
|
|
|
|
|
if match:
|
|
|
|
|
base = match.group(1)
|
2024-09-13 15:03:55 +08:00
|
|
|
|
else:
|
2024-11-23 15:38:52 +08:00
|
|
|
|
base = key
|
|
|
|
|
base_names[key] = base
|
|
|
|
|
base_name_count[base] = base_name_count.get(base, 0) + 1
|
|
|
|
|
|
|
|
|
|
# 第二遍遍历,根据基名的出现次数分类
|
|
|
|
|
keys_to_rename = {}
|
|
|
|
|
for key, base in base_names.items():
|
|
|
|
|
if base_name_count[base] == 1:
|
|
|
|
|
# 检查是否是最内层(值为列表)
|
|
|
|
|
value = current_dict[key]
|
|
|
|
|
if isinstance(value, list):
|
|
|
|
|
current_path = '.'.join(path + [base])
|
|
|
|
|
key_paths.append(current_path)
|
|
|
|
|
# 收集 good_list,保持顺序且不重复
|
|
|
|
|
if base not in good_list:
|
|
|
|
|
good_list.append(base)
|
|
|
|
|
# 如果原键名有后缀,需要记录以便后续重命名
|
|
|
|
|
if key != base:
|
|
|
|
|
keys_to_rename[key] = base
|
|
|
|
|
elif isinstance(value, dict):
|
|
|
|
|
# 继续递归处理
|
|
|
|
|
recurse(value, path + [base])
|
|
|
|
|
else:
|
2024-12-03 17:37:02 +08:00
|
|
|
|
# 记录分组路径,并统计数量
|
|
|
|
|
grouped_path = '.'.join(path + [base])
|
|
|
|
|
grouped_counts[grouped_path] += 1
|
2024-11-23 15:38:52 +08:00
|
|
|
|
|
|
|
|
|
# 执行键名的重命名,同时保持原有顺序
|
|
|
|
|
if keys_to_rename:
|
|
|
|
|
new_ordered_dict = {}
|
|
|
|
|
for key in current_dict.keys():
|
|
|
|
|
if key in keys_to_rename:
|
|
|
|
|
new_key = keys_to_rename[key]
|
|
|
|
|
new_ordered_dict[new_key] = current_dict[key]
|
|
|
|
|
else:
|
|
|
|
|
new_ordered_dict[key] = current_dict[key]
|
|
|
|
|
current_dict.clear()
|
|
|
|
|
current_dict.update(new_ordered_dict)
|
|
|
|
|
|
|
|
|
|
# 对于基名重复的键,继续递归(如果值是字典)
|
|
|
|
|
for key, base in base_names.items():
|
|
|
|
|
if base_name_count[base] > 1:
|
|
|
|
|
value = current_dict[key]
|
|
|
|
|
if isinstance(value, dict):
|
|
|
|
|
recurse(value, path + [base])
|
|
|
|
|
elif isinstance(value, list):
|
|
|
|
|
# 如果值是列表,仍需收集基名到 good_list
|
|
|
|
|
if base not in good_list:
|
|
|
|
|
good_list.append(base)
|
|
|
|
|
|
|
|
|
|
# 深拷贝数据以避免修改原始输入
|
|
|
|
|
data_copy = deepcopy(data)
|
|
|
|
|
|
|
|
|
|
# 开始递归遍历
|
|
|
|
|
recurse(data_copy, [])
|
|
|
|
|
|
|
|
|
|
def collect_grouped_paths(current_dict, path, collected):
|
|
|
|
|
for key in current_dict.keys():
|
|
|
|
|
match = pattern.match(key)
|
|
|
|
|
if match:
|
|
|
|
|
base = match.group(1)
|
|
|
|
|
else:
|
|
|
|
|
base = key
|
|
|
|
|
current_path = '.'.join(path + [base])
|
2024-12-03 17:37:02 +08:00
|
|
|
|
if current_path in grouped_counts and current_path not in collected:
|
2024-11-23 15:38:52 +08:00
|
|
|
|
collected.append(current_path)
|
|
|
|
|
value = current_dict[key]
|
|
|
|
|
if isinstance(value, dict):
|
|
|
|
|
collect_grouped_paths(value, path + [base], collected)
|
|
|
|
|
|
|
|
|
|
collected_grouped_paths = []
|
|
|
|
|
collect_grouped_paths(data_copy, [], collected_grouped_paths)
|
2024-12-03 17:37:02 +08:00
|
|
|
|
|
|
|
|
|
# 将 grouped_paths 转换为包含数量的字典列表
|
|
|
|
|
grouped_paths = [{path: grouped_counts[path]} for path in collected_grouped_paths]
|
2024-11-23 15:38:52 +08:00
|
|
|
|
|
|
|
|
|
return key_paths, grouped_paths, good_list, data_copy
|
|
|
|
|
def rename_keys(data):
|
|
|
|
|
"""
|
|
|
|
|
对整个数据结构进行重命名处理。
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def rename_keys_recursive(current_dict):
|
|
|
|
|
"""
|
2024-12-03 17:37:02 +08:00
|
|
|
|
递归地重名字典中的键,确保同一层级下具有相同基名的键被正确编号。
|
2024-11-23 15:38:52 +08:00
|
|
|
|
"""
|
|
|
|
|
if not isinstance(current_dict, dict):
|
|
|
|
|
return current_dict
|
|
|
|
|
|
|
|
|
|
key_order = list(current_dict.keys())
|
|
|
|
|
base_name_dict = defaultdict(list)
|
|
|
|
|
|
|
|
|
|
# 辅助函数:提取基名(去除可能的 -数字 后缀)
|
|
|
|
|
def get_base_name(key):
|
|
|
|
|
if '-' in key:
|
|
|
|
|
parts = key.rsplit('-', 1)
|
|
|
|
|
if parts[1].isdigit():
|
|
|
|
|
return parts[0]
|
|
|
|
|
return key
|
|
|
|
|
|
|
|
|
|
# 将键按基名分组
|
|
|
|
|
for key in key_order:
|
|
|
|
|
base = get_base_name(key)
|
|
|
|
|
base_name_dict[base].append(key)
|
|
|
|
|
|
|
|
|
|
new_dict = {}
|
|
|
|
|
for key in key_order:
|
|
|
|
|
base = get_base_name(key)
|
|
|
|
|
keys = base_name_dict[base]
|
|
|
|
|
if len(keys) > 1:
|
|
|
|
|
# 如果存在同基名的多个键,则进行重命名
|
|
|
|
|
if base not in new_dict:
|
|
|
|
|
# 按原始顺序对需要重命名的键进行排序
|
|
|
|
|
sorted_keys = sorted(keys, key=lambda x: key_order.index(x))
|
|
|
|
|
for idx, original_key in enumerate(sorted_keys, start=1):
|
|
|
|
|
new_key = f"{base}-{idx}"
|
|
|
|
|
# 如果值是字典,递归处理
|
|
|
|
|
if isinstance(current_dict[original_key], dict):
|
|
|
|
|
new_dict[new_key] = rename_keys_recursive(current_dict[original_key])
|
|
|
|
|
else:
|
|
|
|
|
new_dict[new_key] = current_dict[original_key]
|
|
|
|
|
else:
|
|
|
|
|
# 如果没有重复的基名,保持原名
|
|
|
|
|
if isinstance(current_dict[key], dict):
|
|
|
|
|
new_dict[key] = rename_keys_recursive(current_dict[key])
|
|
|
|
|
else:
|
|
|
|
|
new_dict[key] = current_dict[key]
|
|
|
|
|
|
|
|
|
|
return new_dict
|
2024-12-03 17:37:02 +08:00
|
|
|
|
|
|
|
|
|
# 对整个数据结构进行递归重命名
|
|
|
|
|
return rename_keys_recursive(data)
|
2024-09-13 15:03:55 +08:00
|
|
|
|
|
|
|
|
|
def combine_and_update_results(original_data, updates):
|
2024-12-03 17:37:02 +08:00
|
|
|
|
"""
|
|
|
|
|
先规范化original和updates中的字典,防止空格的情况导致匹配不上无法更新
|
|
|
|
|
"""
|
2024-11-08 15:44:29 +08:00
|
|
|
|
def normalize_key(key):
|
|
|
|
|
"""
|
|
|
|
|
规范化键名:
|
|
|
|
|
- 替换全角点号为半角点号。
|
|
|
|
|
- 删除所有空格(包括半角和全角)。
|
|
|
|
|
"""
|
|
|
|
|
# 替换全角点号(.、。)为半角点号(.)
|
|
|
|
|
key = key.replace('.', '.').replace('。', '.')
|
|
|
|
|
# 删除所有空格(半角空格和全角空格)
|
|
|
|
|
key = key.replace(' ', '').replace('\u3000', '')
|
|
|
|
|
return key
|
|
|
|
|
|
|
|
|
|
def normalize_original_data(d):
|
|
|
|
|
"""
|
|
|
|
|
递归规范化原始数据字典的键。
|
|
|
|
|
"""
|
|
|
|
|
if not isinstance(d, dict):
|
|
|
|
|
return d
|
|
|
|
|
normalized = {}
|
|
|
|
|
for k, v in d.items():
|
|
|
|
|
nk = normalize_key(k)
|
|
|
|
|
normalized[nk] = normalize_original_data(v)
|
|
|
|
|
return normalized
|
|
|
|
|
|
|
|
|
|
def normalize_update_value(value):
|
|
|
|
|
"""
|
|
|
|
|
递归规范化更新字典中嵌套的字典的键。
|
|
|
|
|
"""
|
|
|
|
|
if isinstance(value, dict):
|
|
|
|
|
return {normalize_key(k): normalize_update_value(v) for k, v in value.items()}
|
|
|
|
|
else:
|
|
|
|
|
return value
|
|
|
|
|
|
2024-09-13 15:03:55 +08:00
|
|
|
|
def recursive_update(data, key, value):
|
2024-11-08 15:44:29 +08:00
|
|
|
|
"""
|
|
|
|
|
递归更新嵌套字典。
|
|
|
|
|
"""
|
2024-09-13 15:03:55 +08:00
|
|
|
|
keys = key.split('.')
|
|
|
|
|
for k in keys[:-1]:
|
|
|
|
|
data = data.setdefault(k, {})
|
|
|
|
|
if isinstance(value, dict) and isinstance(data.get(keys[-1], None), dict):
|
|
|
|
|
data[keys[-1]] = {**data.get(keys[-1], {}), **value}
|
|
|
|
|
else:
|
|
|
|
|
data[keys[-1]] = value
|
2024-11-08 15:44:29 +08:00
|
|
|
|
|
|
|
|
|
# 1. 规范化原始数据字典的键
|
|
|
|
|
original_data = normalize_original_data(original_data)
|
|
|
|
|
|
|
|
|
|
# 2. 规范化更新字典的键
|
|
|
|
|
normalized_updates = {}
|
2024-09-13 15:03:55 +08:00
|
|
|
|
for key, value in updates.items():
|
2024-11-08 15:44:29 +08:00
|
|
|
|
nk = normalize_key(key)
|
|
|
|
|
nv = normalize_update_value(value)
|
|
|
|
|
normalized_updates[nk] = nv
|
|
|
|
|
|
|
|
|
|
# 3. 执行递归更新
|
|
|
|
|
for key, value in normalized_updates.items():
|
2024-09-13 15:03:55 +08:00
|
|
|
|
recursive_update(original_data, key, value)
|
2024-11-08 15:44:29 +08:00
|
|
|
|
|
2024-09-13 15:03:55 +08:00
|
|
|
|
return original_data
|
2024-11-18 13:18:43 +08:00
|
|
|
|
|
2024-12-04 11:48:33 +08:00
|
|
|
|
def generate_prompt(judge_res, full_text=None):
|
|
|
|
|
"""
|
|
|
|
|
获取需要采购的货物名称
|
|
|
|
|
根据 `judge_res` 和 `full_text` 动态生成 prompt。
|
|
|
|
|
如果 `judge_res` 包含 '否',则不添加文件内容部分。
|
|
|
|
|
如果 `judge_res` 不包含 '否' 且有 `full_text`,则添加文件内容部分。
|
|
|
|
|
"""
|
|
|
|
|
base_prompt = '''
|
2024-11-28 17:27:44 +08:00
|
|
|
|
任务:你负责解析采购文件,提取采购需求,并以JSON格式返回,不要遗漏该项目需要采购的货物(或系统)。
|
|
|
|
|
|
|
|
|
|
要求与指南:
|
2024-12-11 17:42:51 +08:00
|
|
|
|
1. 精准定位:请运用文档理解能力,定位文件中的采购需求部分。
|
|
|
|
|
-若有采购清单,请直接根据采购清单上的货物(或系统)名称给出结果,若没有采购清单,则从表格或文本中摘取采购信息。
|
|
|
|
|
-注意采购目标通常在诸如'名称'列,且每个目标占据一个单元格,你无需提取诸如'说明'、'规格'、'参数'、'描述'等其他列的内容,即你不需要给出详细的采购要求,更不要将这些单元格内的描述拆分作为其的子键,你仅返回采购的货物或系统或模块名称;
|
2024-11-28 17:27:44 +08:00
|
|
|
|
2. 采购目标:采购种类通常有硬件(如设备、货物)和软件(如系统软件、应用APP),一次采购活动可以同时包含这两种类型。
|
2024-12-11 17:42:51 +08:00
|
|
|
|
3. 层级结构:
|
|
|
|
|
-采购活动可能将目标划分为多个系统或货物。若文档通过标题或表格层次标明这种归属关系,请在JSON中以嵌套形式表示:
|
|
|
|
|
-系统作为一级键,包含的货物作为二级键。
|
|
|
|
|
-如果系统仅提到"系统功能",在该系统的值中添加"系统功能"作为二级键,具体内容不展开。
|
|
|
|
|
-系统可以只包含“系统功能”,无需列出具体货物。
|
|
|
|
|
4. 软件需求:对于软件系统或应用采购,若有多个系统且序号分明,请不要遗漏;若明确列出系统模块,提取模块名称并作为系统的子键,无需在模块下再细分功能。
|
|
|
|
|
5. 完整性:
|
|
|
|
|
-确保系统内的所有货物均被提取,避免遗漏或添加未提及的内容。
|
|
|
|
|
-若某货物(或系统、模块)在“主要设备功能指标”或类似标题下有详细参数说明,但未在前面清单或表格中诸如'名称'的列中列出,也需添加到结果中。
|
2024-11-28 17:27:44 +08:00
|
|
|
|
|
2024-12-03 17:37:02 +08:00
|
|
|
|
特殊情况:
|
2024-12-04 11:48:33 +08:00
|
|
|
|
1.若采购的货物或系统或模块名称前存在三角▲,△、五角★,☆,注意是名称前而非具体的技术参数或采购要求前,在返回名称时请保留前面的▲,△或★,☆符号,如'★高清摄像机'。
|
2024-12-19 12:18:50 +08:00
|
|
|
|
2.若同一层级下存在名称相同但采购要求(如型号、参数、功能)不同的货物,请在名称后添加编号,以避免键名重复;默认情况下否则无需在名称后添加编号。
|
|
|
|
|
-编号规则:'货物名-编号',编号从1递增。
|
|
|
|
|
-注意:编号仅用于逻辑区分,取决于种类数,与采购数量无关。
|
|
|
|
|
-例子:若同层级下存在4个但是两种型号的'交换机',那么命名分别是'交换机-1'和'交换机-2'。
|
|
|
|
|
|
2024-12-03 17:37:02 +08:00
|
|
|
|
|
2024-11-28 17:27:44 +08:00
|
|
|
|
输出格式:
|
|
|
|
|
1.JSON格式,最外层键名为'采购需求'。
|
|
|
|
|
2.层次关系用嵌套键值对表示。
|
|
|
|
|
3.嵌套键名为系统或货物或模块名称,与原文保持一致。
|
|
|
|
|
4.最内层键值应为空列表[]。
|
|
|
|
|
|
|
|
|
|
示例输出1,普通系统、货物类采购:
|
|
|
|
|
{{
|
|
|
|
|
"采购需求": {{
|
|
|
|
|
"交换机-1": [],
|
|
|
|
|
"交换机-2": [],
|
|
|
|
|
"门禁管理系统": {{
|
|
|
|
|
"系统功能":[]
|
|
|
|
|
}},
|
|
|
|
|
"交通监控视频子系统": {{
|
|
|
|
|
"系统功能": [],
|
|
|
|
|
"交换机":[],
|
|
|
|
|
"高清视频抓拍像机": [],
|
|
|
|
|
"补光灯": []
|
|
|
|
|
}},
|
|
|
|
|
"LED全彩显示屏": []
|
|
|
|
|
// 其他系统和货物
|
|
|
|
|
}}
|
|
|
|
|
}}
|
|
|
|
|
示例输出2,软件系统类采购:
|
|
|
|
|
{{
|
|
|
|
|
"采购需求": {{
|
|
|
|
|
"信息管理系统": {{
|
|
|
|
|
"通用模块":[],
|
|
|
|
|
"用户管理":[]
|
|
|
|
|
}},
|
|
|
|
|
"信息检索系统": {{
|
|
|
|
|
"系统功能":[],
|
|
|
|
|
"权限管理模块":[]
|
|
|
|
|
}},
|
|
|
|
|
"XX管理系统":[],
|
|
|
|
|
//其他系统
|
|
|
|
|
}}
|
|
|
|
|
}}
|
|
|
|
|
'''
|
2024-12-04 11:48:33 +08:00
|
|
|
|
if '否' not in judge_res and full_text:
|
|
|
|
|
# 添加文件内容部分
|
|
|
|
|
base_prompt += f"\n文件内容:\n{full_text}\n"
|
|
|
|
|
base_prompt += "\n注意事项:\n1.严格按照上述要求执行,确保输出准确性和规范性。\n"
|
|
|
|
|
return base_prompt
|
|
|
|
|
|
2024-12-19 14:34:05 +08:00
|
|
|
|
def get_technical_requirements(invalid_path,processed_filepath,model_type=1):
|
|
|
|
|
judge_res = ""
|
2024-12-12 10:22:22 +08:00
|
|
|
|
file_id = ""
|
2024-12-19 14:34:05 +08:00
|
|
|
|
full_text = read_txt_to_string(processed_filepath)
|
|
|
|
|
if model_type:
|
|
|
|
|
first_query_template = """该文件是否说明了采购需求,即需要采购哪些内容(包括货物、设备、系统、功能模块等)?如果有,请回答'是',否则,回答'否'
|
|
|
|
|
{}
|
2024-12-14 12:12:06 +08:00
|
|
|
|
"""
|
2024-12-19 14:34:05 +08:00
|
|
|
|
judge_query = first_query_template.format(f"文件内容:{full_text}")
|
|
|
|
|
judge_res = doubao_model(judge_query)
|
|
|
|
|
if '否' in judge_res or model_type == 0:
|
2024-12-12 10:22:22 +08:00
|
|
|
|
model_type = 0 # 使用qianwen-long+invalid_path
|
2024-11-28 17:27:44 +08:00
|
|
|
|
print("no!调用invalid_path")
|
2024-12-19 14:34:05 +08:00
|
|
|
|
if invalid_path.lower().endswith('.pdf'): # 确保上传的是docx upload中一定是docx,但是get_deviation中可能上传的是pdf
|
2024-12-17 15:42:20 +08:00
|
|
|
|
invalid_path = pdf2docx(invalid_path)
|
2024-12-19 14:34:05 +08:00
|
|
|
|
file_id = upload_file(invalid_path)
|
2024-12-04 11:48:33 +08:00
|
|
|
|
user_query = generate_prompt(judge_res)
|
2024-12-19 14:34:05 +08:00
|
|
|
|
model_res = qianwen_long(file_id, user_query)
|
2024-11-16 16:14:53 +08:00
|
|
|
|
print(model_res)
|
2024-11-16 14:24:58 +08:00
|
|
|
|
else:
|
2024-12-19 14:34:05 +08:00
|
|
|
|
user_query = generate_prompt(judge_res, full_text)
|
|
|
|
|
model_res = doubao_model(user_query)
|
2024-11-16 14:24:58 +08:00
|
|
|
|
print(model_res)
|
2024-12-24 10:27:01 +08:00
|
|
|
|
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}
|
2024-09-13 16:05:16 +08:00
|
|
|
|
|
2024-09-13 15:03:55 +08:00
|
|
|
|
def test_all_files_in_folder(input_folder, output_folder):
|
|
|
|
|
# 确保输出文件夹存在
|
|
|
|
|
if not os.path.exists(output_folder):
|
|
|
|
|
os.makedirs(output_folder)
|
|
|
|
|
|
|
|
|
|
# 遍历指定文件夹中的所有文件
|
|
|
|
|
for filename in os.listdir(input_folder):
|
|
|
|
|
file_path = os.path.join(input_folder, filename)
|
|
|
|
|
# 检查是否是文件
|
|
|
|
|
if os.path.isfile(file_path):
|
|
|
|
|
print(f"处理文件: {file_path}")
|
|
|
|
|
# 调用函数处理文件
|
|
|
|
|
try:
|
|
|
|
|
json_result = get_technical_requirements(file_path)
|
|
|
|
|
# 定义输出文件的路径
|
|
|
|
|
output_file_path = os.path.join(output_folder, os.path.splitext(filename)[0] + '.json')
|
|
|
|
|
# 保存JSON结果到文件
|
|
|
|
|
with open(output_file_path, 'w', encoding='utf-8') as json_file:
|
2024-10-16 20:18:55 +08:00
|
|
|
|
json.dump(json_result, json_file, ensure_ascii=False, indent=4)
|
2024-09-13 15:03:55 +08:00
|
|
|
|
print(f"结果已保存到: {output_file_path}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"处理文件 {file_path} 时出错: {e}")
|
2024-12-14 12:12:06 +08:00
|
|
|
|
# 如果采购需求为空 考虑再调用一次大模型 qianwen-stream
|
2024-09-13 15:03:55 +08:00
|
|
|
|
if __name__ == "__main__":
|
2024-11-17 12:11:11 +08:00
|
|
|
|
start_time=time.time()
|
2024-11-15 14:43:10 +08:00
|
|
|
|
# truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\469d2aee-9024-4993-896e-2ac7322d41b7\\ztbfile_procurement.docx"
|
2024-12-12 18:03:04 +08:00
|
|
|
|
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'
|
2024-11-06 12:20:24 +08:00
|
|
|
|
# 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)
|
2024-12-24 10:21:27 +08:00
|
|
|
|
invalid_path=r"D:\flask_project\flask_app\static\output\output1\000aac0d-4aa4-4bc3-a9f9-76ff82ec2470\invalid_added.docx"
|
2024-11-16 14:24:58 +08:00
|
|
|
|
# file_id=upload_file(truncate_file)
|
2024-12-14 12:12:06 +08:00
|
|
|
|
# processed_filepath = pdf2txt(truncate_file)
|
2024-12-24 10:21:27 +08:00
|
|
|
|
processed_filepath=r"D:\flask_project\flask_app\static\output\output1\000aac0d-4aa4-4bc3-a9f9-76ff82ec2470\extract1.txt"
|
2024-12-14 12:12:06 +08:00
|
|
|
|
res=get_technical_requirements(invalid_path,processed_filepath)
|
2024-09-23 15:49:30 +08:00
|
|
|
|
json_string = json.dumps(res, ensure_ascii=False, indent=4)
|
|
|
|
|
print(json_string)
|
2024-11-06 12:20:24 +08:00
|
|
|
|
# # input_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\output1"
|
|
|
|
|
# # output_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\output3"
|
2024-11-17 12:11:11 +08:00
|
|
|
|
# # test_all_files_in_folder(input_folder, output_folder)
|
|
|
|
|
end_time=time.time()
|
|
|
|
|
print("耗时:"+str(end_time-start_time))
|