2024-09-13 15:03:55 +08:00
# -*- encoding:utf-8 -*-
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
2025-02-06 14:39:58 +08:00
from flask_app . general . llm . model_continue_query import process_continue_answers
2024-11-20 19:35:22 +08:00
from flask_app . general . format_change import pdf2docx
2025-02-06 14:39:58 +08:00
from flask_app . general . llm . 多线程提问 import multi_threading
from flask_app . general . llm . 通义千问long_plus import qianwen_long , upload_file , qianwen_plus
from flask_app . general . json_utils import clean_json_string
from flask_app . general . llm . doubao import read_txt_to_string
from flask_app . 货物标 . 技术参数要求提取后处理函数 import main_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
2025-02-06 14:39:58 +08:00
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
2025-02-06 14:39:58 +08:00
elif isinstance ( value , dict ) : # 如果嵌套键只有一个且为'系统功能' 直接删去,替换为[]
2025-01-21 17:05:40 +08:00
if len ( value ) == 1 and ' 系统功能 ' in value :
current_dict [ key ] = [ ]
# 同时将路径添加到 key_paths 和 good_list 中
current_path = ' . ' . join ( path + [ base ] )
key_paths . append ( current_path )
if base not in good_list :
good_list . append ( base )
else :
# 继续递归处理
recurse ( value , path + [ base ] )
2024-11-23 15:38:52 +08:00
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
2025-02-06 14:39:58 +08:00
2024-11-23 15:38:52 +08:00
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
2025-02-06 14:39:58 +08:00
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中的字典 , 防止空格的情况导致匹配不上无法更新
"""
2025-02-06 14:39:58 +08:00
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
2025-02-06 14:39:58 +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 = '''
2025-01-21 17:05:40 +08:00
任务 : 你负责解析采购文件 , 提取采购需求 , 告诉我该项目需要采购的货物 、 设备或系统或服务 , 并以JSON格式返回 , 你无需返回每个采购标的的具体要求或说明 。
2024-12-24 15:13:11 +08:00
2025-01-07 17:35:11 +08:00
* * 输出格式 * * :
2025-01-20 17:14:15 +08:00
1. JSON格式 , 外层键名为需要采购的货物 、 设备或系统或服务名称 ( 一级键 ) 。
2025-01-21 17:05:40 +08:00
2. 最内层键值应为空列表 [ ] 。
3. 无层次关系的内容处理 :
对于文件中未表现出明显层次结构或归属关系的采购需求 , 直接以一级键表示 , 无需嵌套 。
4. 层次关系用嵌套键值对表示 :
- 采购活动可能将目标划分为多个系统或货物 , 若有归属关系 ( 注意归属关系是通过原文大标题或表格结构或序号关系得来的 , 请勿根据系统的诸如 ' 说明 ' 、 ' 规格 ' 、 ' 参数 ' 、 ' 描述 ' 等列来擅自归纳其拥有的子系统或货物 ) , 请在JSON中以嵌套形式表示 :
- 层级规则 : 系统作为一级键 , 其下的子系统或货物名作为二级键 , 注意二级键名也必须和原文保持一致 , 不可擅自总结 。
2025-01-20 17:14:15 +08:00
- 系统功能说明 : 如果系统提到 " 系统功能 " ( 即系统的整体功能说明 ) , 那么在该系统下中添加 " 系统功能 " 作为二级键 , 具体内容不展开 ; 系统可以只有 “ 系统功能 ” 而不包含具体货物 。
2025-01-21 17:05:40 +08:00
- 层数限制 : 最多到二级键 .
2025-01-20 17:14:15 +08:00
- 示例输入 : """
一 、 扫描系统
系统功能 : 支持多种扫描模式 。
1. 激光扫描系统 : xxx
1.1 激光器 : xxx
2. 红外扫描系统 : xxx
二 、 通信系统
2025-01-21 17:05:40 +08:00
1. 通讯天线
2024-12-24 15:13:11 +08:00
"""
2025-01-20 17:14:15 +08:00
- 示例输出 :
{
" 扫描系统 " : {
" 系统功能 " : [ ] ,
2025-01-21 17:05:40 +08:00
" 激光扫描系统 " : [ ] ,
2025-01-20 17:14:15 +08:00
" 红外扫描系统 " : [ ]
} ,
" 通信系统 " : {
2025-01-21 17:05:40 +08:00
" 通讯天线 " : [ ]
2025-01-20 17:14:15 +08:00
}
}
2025-01-21 17:05:40 +08:00
5. 防止重复键名规则 :
2025-01-20 17:14:15 +08:00
- 若同一层级下存在名称相同但采购要求 ( 如型号 、 参数 、 功能 、 说明 ) 不同的采购标的 , 请在名称后添加编号以作区分 , 防止出现重复键名 ; 默认情况下 , 无需在名称后添加编号 , 只有在名称相同时才需要添加编号 。 编号规则 : ' 名称-编号 ' , 编号从1递增 。 例子 : 若同层级下存在两种名称相同但不同型号或参数的 ' 交换机 ' , 那么键名分别是 ' 交换机-1 ' 和 ' 交换机-2 ' 。 如果名称不同 ( 如路由器和交换机 ) , 则无需编号 。
2024-11-28 17:27:44 +08:00
2025-01-07 17:35:11 +08:00
* * 特殊情况 * * :
2025-01-20 17:14:15 +08:00
- 如果文件中未明确说明需要采购的货物 、 设备或系统或服务 , 请直接返回空字典 { { } } 。 不要生成任何无关内容 。
2025-01-07 17:35:11 +08:00
* * 要求与指南 * * :
2024-12-11 17:42:51 +08:00
1. 精准定位 : 请运用文档理解能力 , 定位文件中的采购需求部分 。
2025-01-21 17:05:40 +08:00
- 若有采购清单 , 请直接根据采购清单上的货物 ( 或系统或服务 ) 名称给出结果 , 若没有采购清单 , 则从表格或文本中提取采购需求 。
- 注意采购目标通常在诸如 ' 名称 ' 列 , 且每个目标占据一个单元格 , 你无需提取诸如 ' 说明 ' 、 ' 规格 ' 、 ' 参数 ' 、 ' 描述 ' 等其他列的内容 , 即你不需要给出该目标详细的采购要求 , 更 * * 不要 * * 将这些单元格内包含的内容拆分作为其的二级键 ;
2025-01-20 17:14:15 +08:00
2. 采购标的 : 采购种类通常有硬件 ( 如设备 、 货物 ) 和软件 ( 如系统软件 、 应用APP ) 和服务 ( 如人力 ) , 一次采购活动可以同时包含这三种类型 。
2024-12-24 15:13:11 +08:00
3. 软件类采购 : 对于软件系统或应用采购 , 若有多个系统且序号分明 , 请不要遗漏 ; 若明确列出系统模块 , 提取模块名称并作为系统的子键 , 无需在模块下再细分功能 。
4. 完整性 :
2025-01-20 17:14:15 +08:00
- 若采购的货物或系统或模块或服务名称前存在三角 ▲ , △ 、 五角 ★ , ☆ 或其他特殊符号 , 注意是名称前而非具体的技术参数或采购要求前 , 在返回名称时请保留前面的 ▲ , △ 或 ★ , ☆ 符号 , 如 ' ★高清摄像机 ' 。
- 确保采购文件内的所有采购标的均被按层次提取 , 对于有明确清单且按序号划分的采购需求 , 请勿遗漏每一个序号所代表的采购标的 , 也不要添加未提及的内容
- 若某货物 ( 或系统 、 模块 、 服务 ) 在 “ 主要设备功能指标 ” 或类似标题下有详细参数或要求说明 , 但未在前面清单或表格中诸如 ' 名称 ' 的列中列出 , 也需将该货物名添加到结果中 。
5. 特别说明 : 采购标的可以是某项服务 , 但是请与 ' 服务要求 ' 进行区别 , ' 服务要求 ' 不等同于 ' 服务采购 ' 。
2024-12-24 15:13:11 +08:00
示例输出1 , 普通系统 、 货物类采购 , 仅供格式参考 :
2024-11-28 17:27:44 +08:00
{ {
2024-12-24 15:13:11 +08:00
" 交换机-1 " : [ ] ,
" 交换机-2 " : [ ] ,
" 门禁管理系统 " : { {
" 系统功能 " : [ ]
} } ,
" 交通监控视频子系统 " : { {
" 系统功能 " : [ ] ,
" 交换机 " : [ ] ,
" 高清视频抓拍像机 " : [ ] ,
" 补光灯 " : [ ]
} } ,
" LED全彩显示屏 " : [ ]
/ / 其他系统和货物
2024-11-28 17:27:44 +08:00
} }
2024-12-24 15:13:11 +08:00
示例输出2 , 软件系统类采购 , 仅供格式参考 :
2024-11-28 17:27:44 +08:00
{ {
2024-12-24 15:13:11 +08:00
" 信息管理系统 " : { {
" 通用模块 " : [ ] ,
" 用户管理 " : [ ]
} } ,
" 信息检索系统 " : { {
" 系统功能 " : [ ] ,
" 权限管理模块 " : [ ]
} } ,
" XX管理系统 " : [ ] ,
/ / 其他系统
2024-11-28 17:27:44 +08:00
} }
2025-01-07 17:35:11 +08:00
示例输出3 , 无明确的采购需求 :
{ { } }
2024-11-28 17:27:44 +08:00
'''
2024-12-04 11:48:33 +08:00
if ' 否 ' not in judge_res and full_text :
# 添加文件内容部分
2025-02-06 14:39:58 +08:00
base_prompt = f " 文件内容: { full_text } \n " + base_prompt
2024-12-04 11:48:33 +08:00
base_prompt + = " \n 注意事项: \n 1.严格按照上述要求执行,确保输出准确性和规范性。 \n "
return base_prompt
2025-02-06 14:39:58 +08:00
2024-12-24 15:13:11 +08:00
def preprocess_data ( data ) :
"""
动态识别并将值为非空列表且列表中每个项为单键字典或字符串的键转换为嵌套字典结构 。
保持最内层的值为空列表 [ ] , 而不是空字典 { } 。
参数 :
data ( dict ) : 原始数据字典 。
返回 :
dict : 预处理后的数据字典 。
"""
2025-02-06 14:39:58 +08:00
2024-12-24 15:13:11 +08:00
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
2025-02-06 14:39:58 +08:00
def get_technical_requirements ( invalid_path , processed_filepath , model_type = 1 ) :
2024-12-19 14:34:05 +08:00
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 )
2025-02-06 14:39:58 +08:00
if model_type == 1 :
first_query_template = """
该文件是否说明了采购需求 , 即需要采购哪些内容 ( 包括货物 、 设备 、 系统 、 功能模块等 ) ? 如果有 , 请回答 ' 是 ' , 否则 , 回答 ' 否 '
2024-12-19 14:34:05 +08:00
{ }
2024-12-14 12:12:06 +08:00
"""
2025-02-06 14:39:58 +08:00
judge_query = f " 文件内容: { full_text } \n " + first_query_template
2025-01-17 15:45:53 +08:00
# judge_res = doubao_model(judge_query)
2025-02-06 14:39:58 +08:00
judge_res = qianwen_plus ( judge_query )
2024-12-26 17:20:27 +08:00
if ' 否 ' in judge_res or model_type == 2 :
model_type = 2 # 使用qianwen-long+invalid_path
2025-01-07 17:35:11 +08:00
print ( " processed_filepath中无采购需求!调用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 )
2025-01-17 15:45:53 +08:00
# model_res = doubao_model(user_query)
2025-02-06 14:39:58 +08:00
model_res = qianwen_plus ( user_query )
2024-11-16 14:24:58 +08:00
print ( model_res )
2024-12-24 10:27:01 +08:00
2025-02-06 14:39:58 +08:00
cleaned_res = clean_json_string ( model_res ) # 转字典
2025-01-22 14:52:32 +08:00
if not cleaned_res :
print ( " 本项目没有采购需求!!! " )
return { " 采购需求 " : { } }
2025-02-06 14:39:58 +08:00
preprocessed_data = preprocess_data ( cleaned_res ) # 确保最内层为[]
processed_data = truncate_system_keys ( preprocessed_data ) # 限制深度
key_paths , grouped_paths , good_list , data_copy = generate_key_paths (
processed_data ) # 提取需要采购的货物清单 key_list: 交通监控视频子系统.高清视频抓拍像机 ... grouped_paths是同一系统下同时有'交换机-1'和'交换机-2',提取'交换机' , 输出eg:{'交通标志.标志牌铝板', '交通信号灯.交换机'}
2025-01-24 13:18:37 +08:00
# if len(good_list)>100 and model_type==1: #并发特别高( len(good_list)) , tokens会比较贵, 以后可以考虑qianwen-long, 目前qianwen-plus:0.0008/ktokens long:0.0005/ktokens 差不多价格,暂不考虑
# model_type=2
# file_id=upload_file(processed_filepath)
2025-02-06 14:39:58 +08:00
modified_data = rename_keys ( data_copy )
user_query_template = """ 请根据招标文件中采购要求部分的内容,告诉我 \" {} \" 的技术参数或采购要求是什么。请以 JSON 格式返回结果,键名为 \" {} \" ,键值为一个列表,列表中包含若干描述 \" {} \" 的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★或其他特殊符号(若有)和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。
2025-01-22 14:52:32 +08:00
* * 重要限制 * * :
- * * 仅提取技术参数或采购要求 , 不包括任何商务要求 * * 。 商务要求通常涉及供应商资格 、 报价条款 、 交货时间 、 质保等内容 , 是整体的要求 ; 而技术参数或采购要求则具体描述产品的技术规格 、 功能 、 性能指标等 。
- * * 商务要求的关键词示例 * * ( 仅供参考 , 不限于此 ) : 报价 、 交货 、 合同 、 资质 、 认证 、 服务 、 保修期等 。 如果内容包含上述关键词 , 请仔细甄别是否属于商务要求 。
要求与指南 :
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 "
]
} }
"""
2025-02-06 14:39:58 +08:00
user_query_template_two = """ 请根据招标文件中采购要求部分的内容,告诉我 \" {} \" 的技术参数或采购要求是什么。由于该货物存在 {} 种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以 ' 货物名-编号 ' 区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为 \" {} -1 \" ;键值为一个列表,列表中包含若干描述 \" {} \" 的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★或其他特殊符号(若有)和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。
* * 重要限制 * * :
- * * 仅提取技术参数或采购要求 , 不包括任何商务要求 * * 。 商务要求通常涉及供应商资格 、 报价条款 、 交货时间 、 质保等内容 , 是整体的要求 ; 而技术参数或采购要求则具体描述产品的技术规格 、 功能 、 性能指标等 。
- * * 商务要求的关键词示例 * * ( 仅供参考 , 不限于此 ) : 报价 、 交货 、 合同 、 资质 、 认证 、 服务 、 保修期等 。 如果内容包含上述关键词 , 请仔细甄别是否属于商务要求 。
2025-01-22 14:52:32 +08:00
要求与指南 :
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 ( ' . ' , ' 下的 ' )
# 使用修改后的键填充第一个占位符,原始键填充第二个占位符
2025-02-06 14:39:58 +08:00
new_query = user_query_template . format ( modified_key , key , modified_key ) # 转豆包后取消注释
if model_type == 1 :
new_query = f " 文件内容: { full_text } \n " + new_query
2025-01-22 14:52:32 +08:00
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 ( ' . ' , ' 下的 ' )
2025-02-06 14:39:58 +08:00
new_query = user_query_template_two . format ( modified_grouped_key , grouped_key_cnt , grouped_key , modified_grouped_key )
if model_type == 1 :
new_query = f " 文件内容: { full_text } \n " + new_query
2025-01-22 14:52:32 +08:00
queries . append ( new_query )
2025-02-06 14:39:58 +08:00
if model_type == 1 :
2025-02-06 16:37:52 +08:00
# results = multi_threading(queries, "", "", 3, True) # 豆包
results = multi_threading ( queries , " " , " " , 4 , True ) # plus
2025-01-22 14:52:32 +08:00
else :
2025-02-06 14:39:58 +08:00
results = multi_threading ( queries , " " , file_id , 2 , True ) # qianwen-long
temp_final = { }
2025-01-22 14:52:32 +08:00
if not results :
print ( " errror!未获得大模型的回答! " )
else :
# 第一步:收集需要调用 `continue_answer` 的问题和解析结果
questions_to_continue = [ ] # 存储需要调用 continue_answer 的 (question, parsed)
2025-02-06 14:39:58 +08:00
max_tokens = 3900 if model_type == 1 else 5900
2025-01-22 14:52:32 +08:00
for question , response in results :
2025-02-06 14:39:58 +08:00
message = response [ 0 ]
2025-01-22 14:52:32 +08:00
parsed = clean_json_string ( message )
2025-02-06 14:39:58 +08:00
total_tokens = response [ 1 ]
if not parsed and total_tokens > max_tokens :
2025-01-22 14:52:32 +08:00
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 )
""" 根据所有键是否已添加处理技术要求 """
# 更新原始采购需求字典
2025-02-06 14:39:58 +08:00
final_res = combine_and_update_results ( modified_data , temp_final )
ffinal_res = main_postprocess ( final_res )
2025-01-22 14:52:32 +08:00
ffinal_res [ " 货物列表 " ] = good_list
# 输出最终的 JSON 字符串
2025-02-06 14:39:58 +08:00
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 } " )
2025-02-06 14:39:58 +08:00
2024-12-14 12:12:06 +08:00
# 如果采购需求为空 考虑再调用一次大模型 qianwen-stream
2024-09-13 15:03:55 +08:00
if __name__ == " __main__ " :
2025-02-06 14:39:58 +08:00
start_time = time . time ( )
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"
# file_id = upload_file(truncate_file)
2025-02-06 14:39:58 +08:00
truncate_file = r ' C: \ Users \ Administrator \ Desktop \ 新建文件夹 (3) \ 需求 \ “天府粮仓”青白江区“一园三片”数字化提升和标准化体系建设项目(三次)招标文件( N510113202400010820250102001) _procurement.pdf '
invalid_path = r " C: \ Users \ Administrator \ Desktop \ 货物标 \ extract_files \ 107国道.txt "
2024-11-16 14:24:58 +08:00
# file_id=upload_file(truncate_file)
2024-12-24 15:13:11 +08:00
# processed_filepath = convert_file_to_markdown(truncate_file)
2025-02-06 14:39:58 +08:00
processed_filepath = r " C: \ Users \ Administrator \ Desktop \ 货物标 \ extract_files \ 107国道.txt "
res = get_technical_requirements ( invalid_path , processed_filepath , 1 )
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)
2025-02-06 14:39:58 +08:00
end_time = time . time ( )
print ( " 耗时: " + str ( end_time - start_time ) )