This commit is contained in:
zy123 2024-11-16 14:24:58 +08:00
parent 8a8985d9f4
commit 22a8b5676a
6 changed files with 347 additions and 163 deletions

View File

@ -1,125 +0,0 @@
import PyPDF2
import requests
from flask_app.general.clean_pdf import extract_common_header, clean_page_content
def extract_text_by_page(file_path):
common_header = extract_common_header(file_path)
# print(f"公共抬头:{common_header}")
# print("--------------------正文开始-------------------")
result = ""
with open(file_path, 'rb') as file:
reader = PyPDF2.PdfReader(file)
num_pages = len(reader.pages)
# print(f"Total pages: {num_pages}")
for page_num in range(num_pages):
page = reader.pages[page_num]
text = page.extract_text()
if text:
# print(f"--------第{page_num}页-----------")
cleaned_text = clean_page_content(text,common_header)
# print(cleaned_text)
result += cleaned_text
# print(f"Page {page_num + 1} Content:\n{cleaned_text}")
else:
print(f"Page {page_num + 1} is empty or text could not be extracted.")
return result
def db_call_with_title(file_path):
# 相关参数
url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
api_key = "ad0c363f-1f23-4b13-aba3-698a4f8c3eb8"
model_name = "ep-20241115114052-2clpd" # 豆包Pro 32k模型
# 取txt内容提问
# with open(txt_path, 'r', encoding='utf-8') as f:
# full_text = f.read()
# print(len(full_text))
# 取pdf内容提问
full_text_json = extract_text_json_by_page(file_path)
# 大模型提取目录
# query = f"""
# 任务:根据所提供的投标文件格式文件,将其中商务部分的目录准确提出。
# 要求1.尽可能保证与原文内容一致,不要进行总结归纳。
# 2.如果文件中包含目录,其可能是虚假的目录,一切以正文内容为准。
# 3.所提供文件可能包含技术标或其他内容,要求仅提取商务标内容。
# 4.按所给出的格式输出,不要输出任何其他内容,目录保留正确的层级关系。
# 输出格式:{{
# "一级目录": {{
# "二级目录": {{
# "最内层目录": {{}}
# }}
# }}
# }}
# 文件内容:{full_text}
# """
# 大模型提取页码
query = f"""
任务根据所提供的投标文件json格式文件将其中商务部分的页码准确提出
要求1.所提供的json文件键为页码值为当前页码的文本充分理解后提取商务部分的内容页码
2.如果文件中包含目录其可能是虚假的目录一切以正文内容为准
3.所提供文件可能包含技术标或其他内容要求仅提取商务标相关的页码并且在最后将子目录的页码范围整合到"页码范围"
4.按所给出的格式输出不要输出任何其他内容目录保留正确的层级关系
5.如果商务部分穿插如技术标的相关内容"商务部分"中的page_range中需要将这段跳过
举例假如12到15页为技术标部分其他都是商务标时按列表包含二元组的样式展示出来[(1,11),(16,33)]
如果这种情况没有发生则输出完整的页码范围[(4,20)]
输出格式{{
"商务部分": {{
"一级目录": {{
start_page: 开始页码
end_page: 结束页码
children: {{
"二级目录": {{
start_page: 开始页码
end_page: 结束页码
children: {{
"最内层目录": {{
start_page: 开始页码
end_page: 结束页码
}}
}}
}}
}}
}}
}}
"页码范围": [(4,33)]
}}
文件内容{full_text_json}
"""
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + api_key
}
data = {
"model": model_name,
"messages": [
{
"role": "system",
"content": "你是一个专业的标书制作人,现在需要你对传入的数据进行充分理解后,回答我的问题。"
},
{
"role": "user",
"content": query
}
]
}
response = requests.post(url, headers=headers, json=data)
if response.status_code == 200:
print(response.json()["choices"][0]["message"]["content"])
else:
print(f"请求失败,状态码:{response.status_code},错误信息:{response.text}")
if __name__ == "__main__":
txt_path = "D:/files/bid1/bid_format.txt"
pdf_path_1 = "D:/bid_generator/task_folder/9a447eb0-24b8-4f51-8164-d91a62edea25/tmp/bid_format.pdf"
pdf_path_2 = "D:/files/page_test/技术标穿插测试文件.pdf"
db_call_with_title(pdf_path_2)

152
flask_app/general/doubao.py Normal file
View File

@ -0,0 +1,152 @@
import PyPDF2
import requests
from flask_app.general.clean_pdf import extract_common_header, clean_page_content
def extract_text_by_page(file_path):
common_header = extract_common_header(file_path)
# print(f"公共抬头:{common_header}")
# print("--------------------正文开始-------------------")
result = ""
with open(file_path, 'rb') as file:
reader = PyPDF2.PdfReader(file)
num_pages = len(reader.pages)
# print(f"Total pages: {num_pages}")
for page_num in range(num_pages):
page = reader.pages[page_num]
text = page.extract_text()
if text:
# print(f"--------第{page_num}页-----------")
cleaned_text = clean_page_content(text,common_header)
# print(cleaned_text)
result += cleaned_text
# print(f"Page {page_num + 1} Content:\n{cleaned_text}")
else:
print(f"Page {page_num + 1} is empty or text could not be extracted.")
return result
def read_txt_to_string(file_path):
"""
读取txt文件内容并返回一个包含所有内容的字符串保持原有格式
参数:
- file_path (str): txt文件的路径
返回:
- str: 包含文件内容的字符串
"""
try:
with open(file_path, 'r', encoding='utf-8') as file: # 确保使用适当的编码
content = file.read() # 使用 read() 保持文件格式
return content
except FileNotFoundError:
return "错误:文件未找到。"
except Exception as e:
return f"错误:读取文件时发生错误。详细信息:{e}"
def doubao_model(full_user_query):
# 相关参数
url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
api_key = "ad0c363f-1f23-4b13-aba3-698a4f8c3eb8"
model_name = "ep-20241115114052-2clpd" # 豆包Pro 32k模型
# 大模型提取页码
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + api_key
}
data = {
"model": model_name,
"messages": [
{
"role": "user",
"content": full_user_query
}
],
"temperature":0.5
}
response = requests.post(url, headers=headers, json=data)
if response.status_code == 200:
# print(response.json()["choices"][0]["message"]["content"])
return response.json()["choices"][0]["message"]["content"]
else:
print(f"请求失败,状态码:{response.status_code},错误信息:{response.text}")
def generate_full_user_query(file_path, prompt_template):
"""
根据文件路径和提示词模板生成完整的user_query
参数
- file_path (str): 需要解析的文件路径
- prompt_template (str): 包含{full_text}占位符的提示词模板
返回
- str: 完整的user_query
"""
# 假设extract_text_by_page已经定义用于提取文件内容
full_text = extract_text_by_page(file_path)
# full_text=read_txt_to_string(file_path)
# 格式化提示词,将提取的文件内容插入到模板中
user_query = prompt_template.format(full_text=full_text)
return user_query
#7.文件内容为markdown格式 表格特殊情况处理对于表格数据可能存在原始pdf转换markdown时跨页导致同一个货物名称或系统名称分隔在上下两个单元格内你需要通过上下文语义判断是否合并之后才是完整且正确的货物名称或系统名称
if __name__ == "__main__":
txt_path = r"output.txt"
pdf_path_1 = "D:/bid_generator/task_folder/9a447eb0-24b8-4f51-8164-d91a62edea25/tmp/bid_format.pdf"
pdf_path_2 = r"output1.txt"
prompt_template = '''
任务解析采购文件提取采购需求并以JSON格式返回
要求与指南
1. 精准定位运用文档理解能力找到文件中的采购需求部分
2. 系统归属若货物明确属于某个系统则将其作为该系统的二级键
3. 非清单形式处理若未出现采购清单则从表格或文字中摘取系统和货物信息
4. 软件需求对于软件应用需求列出系统模块构成并作为系统键值的一部分
5. 系统功能若文中提及系统功能则在系统值中添加'系统功能'二级键不展开具体内容
6. 完整性确保不遗漏系统内的货物也不添加未提及的内容
输出格式
1.JSON格式最外层键名为'采购需求'
2.嵌套键名为系统或货物名称与原文保持一致
3.键值应为空对象{{}}仅返回名称
4.不包含'说明''规格''技术参数'等列内容
5.层次关系用嵌套键值对表示
6.最后一级键内值留空或填'未知'如数量较多或未知内容
特殊情况处理
同一层级下同名但采购要求不同的货物'货物名-编号'区分编号从1递增
示例输出结构
{{
"采购需求": {{
"交换机-1": {{}},
"交换机-2": {{}},
"门禁管理系统": {{
// 可包含其他货物或模块
}},
"交通监控视频子系统": {{
"系统功能": {{}},
"高清视频抓拍像机": {{}},
"补光灯": {{}}
}},
"LED全彩显示屏": {{}}
// 其他系统和货物
}}
}}
文件内容已包含{full_text}
注意事项
1.严格按照上述要求执行确保输出准确性和规范性
2.如有任何疑问或不确定内容请保留原文描述必要时使用'未知'标注
'''
user_query=generate_full_user_query(pdf_path_2,prompt_template)
res=doubao_model(user_query)
print(res)
# print("--------------------")
# print(user_query)

View File

@ -0,0 +1,73 @@
import requests
import json
def get_file_content(filePath):
with open(filePath, 'rb') as fp:
return fp.read()
class TextinOcr(object):
def __init__(self, app_id, app_secret):
self._app_id = app_id
self._app_secret = app_secret
self.host = 'https://api.textin.com'
def recognize_pdf2md(self, image, options):
"""
pdf to markdown
:param options: request params
:param image: file bytes
:return: response
options = {
'pdf_pwd': None, #当pdf为加密文档时需要提供密码。 备注:对前端封装该接口时,需要自行对密码进行安全防护
'dpi': 144, # 设置dpi为144
'page_start': 0, #当上传的是pdf时page_start 表示从第几页开始转
'page_count': 1000, # 设置解析的页数为1000页
'apply_document_tree': 1, #是否生成标题默认为1生成标题
'markdown_details': 1,
'page_details': 0, # 不包含页面细节信息
'table_flavor': 'md', #按md语法输出表格
'get_image': 'none', #获取markdown里的图片默认为none不返回任何图像
'parse_mode': 'scan', # PDF解析模式默认为scan模式仅按文字识别方式处理。
}
"""
url = self.host + '/ai/service/v1/pdf_to_markdown'
headers = {
'x-ti-app-id': self._app_id,
'x-ti-secret-code': self._app_secret
}
return requests.post(url, data=image, headers=headers, params=options)
if __name__ == "__main__":
# 请登录后前往 “工作台-账号设置-开发者信息” 查看 app-id/app-secret
textin = TextinOcr('77c60b5b961381ba80f427966cdfe8ee', '31261fde2bb4ffed73f13ace24c495b5')
file_path=r'C:\Users\Administrator\Desktop\货物标\output1\磋商文件_procurement.pdf'
image = get_file_content(file_path)
resp = textin.recognize_pdf2md(image, {
'page_start': 0,
'page_count': 50, # 设置解析页数为50页
'table_flavor': 'md', #html 按html语法输出表格
'parse_mode': 'scan', # 设置解析模式为scan模式
'page_details': 0, # 不包含页面细节
'markdown_details': 1,
'apply_document_tree': 1,
'dpi': 144 # 分辨率设置为144 dpi
})
print("request time: ", resp.elapsed.total_seconds())
data = json.loads(resp.text)
if 'result' in data and 'markdown' in data['result']:
markdown_content = data['result']['markdown']
# 定义输出文件名
output_file = 'output1.txt'
try:
# 将 markdown 内容写入文件,使用 UTF-8 编码
with open(output_file, 'w', encoding='utf-8') as file:
file.write(markdown_content)
print(f"Markdown 内容已成功保存到 '{output_file}'")
except IOError as e:
print(f"写入文件时出错: {e}")
# with open('result.json', 'w', encoding='utf-8') as fw:
# json.dump(result, fw, indent=4, ensure_ascii=False)

View File

@ -76,7 +76,8 @@ def generate_queries(truncate_file, required_keys):
return queries
def get_business_requirements(truncate_file,file_id):
def get_business_requirements(procurement_path):
file_id=upload_file(procurement_path)
# required_keys = ["技术要求","商务要求", "服务要求", "其他要求"]
# queries = generate_queries(truncate_file, required_keys)
#一起问了,效率慢点,但内容准

View File

@ -7,7 +7,7 @@ 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
from flask_app.货物标.截取pdf货物标版 import truncate_pdf_main
from flask_app.general.doubao import doubao_model,generate_full_user_query
def generate_key_paths(data, parent_key=''):
"""
生成嵌套字典中的键路径并提取最内层的键名
@ -160,47 +160,130 @@ def postprocess(data):
# 递归处理顶层数据
return {key: convert_dict(val) if isinstance(val, dict) else val for key, val in data.items()}
def get_technical_requirements(file_id,invalid_path):
first_query="该文档中是否说明了采购需求,即需要采购哪些货物?如果有,请回答'',否则,回答''"
judge_res=qianwen_long(file_id,first_query)
if '' in judge_res:
file_id=upload_file(invalid_path)
print("调用invalid_path")
user_query1 = """
请你首先定位该采购文件中的采购清单或采购需求部分请告诉我需要采购的货物如果有采购清单请直接根据清单上的货物或系统名称给出结果注意不要返回'说明''规格''技术参数'列中的内容若没有采购清单你要从表格中或文中摘取需要采购的系统和货物采购需求中可能包含层次关系例如采购的某系统中可能包含几种货物那么你需要用嵌套键值对表示这种关系且不要遗漏该系统中包含的货物你的输出请以json格式返回最外层键名为'采购需求'嵌套键名为对应的系统名称或货物名称需与原文保持一致无需给出采购数量和单位以下为需要考虑的特殊情况如果采购清单中同一层级(或同一系统)下存在同名货物且它们的采购要求有所不同请你以'货物名-编号'区分多种型号编号为从 1 开始的自然数依次递增,例如若采购清单中有两种型号的'交换机'那么你应返回两个键名'交换机-1''交换机-2'如有未知内容在对应键值处填'未知'以下为考虑了特殊情况的示例输出
{
"采购需求": {
"交换机-1"{},
"交换机-2":{},
"门禁管理系统": {},
"交通监控视频子系统": {
"高清视频抓拍像机":{},
"补光灯":{}
},
"LED全彩显示屏": {}
}
}
"""
# user_query1 = """
# 请你首先定位该采购文件中的采购清单或采购需求部分,请告诉我该项目需要采购的系统或货物,要求回答全面不要遗漏,最细提取到具体的货物名称,而不是货物的功能需求。如果有采购清单,请直接根据清单上的货物(或系统)名称给出结果,注意不要返回'说明'或'规格'或'技术参数'列中的内容若没有采购清单你要从表格中或文中摘取需要采购的系统和货物。请以json格式返回结果外层键名为采购的系统或货物名称注意采购需求中可能包含层次关系例如采购的某系统中可能包含几种货物那么你需要用嵌套键值对表示这种关系此时内层键名为该系统所需的货物名需与原文保持一致无需给出采购数量和单位。以下为需要考虑的特殊情况如果采购清单中同一层级(或同一系统)下存在同名货物且它们的采购要求有所不同,请你以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,例如若采购清单中有两种型号的'交换机',那么你应返回两个键名,'交换机-1'和'交换机-2';如果采购的某系统说明了该系统整体功能,那么在其内层键名中除了有该系统包含的货物,还应包含'系统功能',具体键名同原文中的描述;如有未知内容,在对应键值处填'未知'。以下为考虑了特殊情况的示例输出:
# {
# 请你首先定位该采购文件中的采购清单或采购需求部分,请告诉我需要采购的货物,如果有采购清单,请直接根据清单上的货物(或系统)名称给出结果,注意不要返回'说明'或'规格'或'技术参数'列中的内容若没有采购清单你要从表格中或文中摘取需要采购的系统和货物采购需求中可能包含层次关系例如采购的某系统中可能包含几种货物那么你需要用嵌套键值对表示这种关系且不要遗漏该系统中包含的货物你的输出请以json格式返回最外层键名为'采购需求',嵌套键名为对应的系统名称或货物名称,需与原文保持一致,无需给出采购数量和单位。以下为需要考虑的特殊情况:如果采购清单中同一层级(或同一系统)下存在同名货物且它们的采购要求有所不同,请你以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,例如若采购清单中有两种型号的'交换机',那么你应返回两个键名,'交换机-1'和'交换机-2';如有未知内容,在对应键值处填'未知'。以下为考虑了特殊情况的示例输出:
# {
# "采购需求": {
# "交换机-1"{},
# "交换机-2":{},
# "门禁管理系统": {},
# "交通监控视频子系统": {
# "系统功能":{}
# "高清视频抓拍像机":{},
# "补光灯":{}
# },
# "LED全彩显示屏": {}
# }
# """
res = qianwen_long(file_id, user_query1)
print(res)
cleaned_res = clean_json_string(res) #转字典
# }
# """
def get_technical_requirements(file_path,invalid_path):
file_id=upload_file(file_path)
first_query_template="该文件是否说明了采购需求,即需要采购哪些货物?如果有,请回答'',否则,回答''"
# first_query=generate_full_user_query(file_path,first_query_template)
# judge_res=doubao_model(first_query)
judge_res=qianwen_long(file_id,first_query_template)
prompt_template1 = '''
任务解析采购文件提取采购需求并以JSON格式返回
要求与指南
1. 精准定位运用文档理解能力找到文件中的采购需求部分
2. 系统归属若货物明确属于某个系统则将其作为该系统的二级键
3. 非清单形式处理若未出现采购清单则从表格或文字中摘取系统和货物信息
4. 软件需求对于软件应用需求列出系统模块构成并作为系统键值的一部分
5. 系统功能若文中提及系统功能则在系统值中添加'系统功能'二级键不展开具体内容
6. 完整性确保不遗漏系统内的货物也不添加未提及的内容
输出格式
1.JSON格式最外层键名为'采购需求'
2.嵌套键名为系统或货物名称与原文保持一致
3.键值应为空对象{{}}仅返回名称
4.不包含'说明''规格''技术参数'等列内容
5.层次关系用嵌套键值对表示
6.最后一级键内值留空或填'未知'如数量较多或未知内容
特殊情况处理
同一层级下同名但采购要求不同的货物'货物名-编号'区分编号从1递增
示例输出结构
{{
"采购需求": {{
"交换机-1": {{}},
"交换机-2": {{}},
"门禁管理系统": {{
// 可包含其他货物或模块
}},
"交通监控视频子系统": {{
"系统功能": {{}},
"高清视频抓拍像机": {{}},
"补光灯": {{}}
}},
"LED全彩显示屏": {{}}
// 其他系统和货物
}}
}}
注意事项
1.严格按照上述要求执行确保输出准确性和规范性
2.如有任何疑问或不确定内容请保留原文描述必要时使用'未知'标注
'''
prompt_template2 = '''
任务解析采购文件提取采购需求并以JSON格式返回
要求与指南
1. 精准定位运用文档理解能力找到文件中的采购需求部分
2. 系统归属若货物明确属于某个系统则将其作为该系统的二级键
3. 非清单形式处理若未出现采购清单则从表格或文字中摘取系统和货物信息
4. 软件需求对于软件应用需求列出系统模块构成并作为系统键值的一部分
5. 系统功能若文中提及系统功能则在系统值中添加'系统功能'二级键不展开具体内容
6. 完整性确保不遗漏系统内的货物也不添加未提及的内容
输出格式
1.JSON格式最外层键名为'采购需求'
2.嵌套键名为系统或货物名称与原文保持一致
3.键值应为空对象{{}}仅返回名称
4.不包含'说明''规格''技术参数'等列内容
5.层次关系用嵌套键值对表示
6.最后一级键内值留空或填'未知'如数量较多或未知内容
特殊情况处理
同一层级下同名但采购要求不同的货物'货物名-编号'区分编号从1递增
示例输出结构
{{
"采购需求": {{
"交换机-1": {{}},
"交换机-2": {{}},
"门禁管理系统": {{
// 可包含其他货物或模块
}},
"交通监控视频子系统": {{
"系统功能": {{}},
"高清视频抓拍像机": {{}},
"补光灯": {{}}
}},
"LED全彩显示屏": {{}}
// 其他系统和货物
}}
}}
文件内容已包含{full_text}
注意事项
1.严格按照上述要求执行确保输出准确性和规范性
2.如有任何疑问或不确定内容请保留原文描述必要时使用'未知'标注
'''
if '' in judge_res:
file_id=upload_file(invalid_path)
print("调用invalid_path")
model_res=qianwen_long(file_id,prompt_template1)
else:
user_query=generate_full_user_query(file_path,prompt_template2)
model_res=doubao_model(user_query)
print(model_res)
# res = qianwen_long(file_id, user_query1)
cleaned_res = clean_json_string(model_res) #转字典
keys_list,good_list,grouped_paths,no_keys_added= generate_key_paths(cleaned_res['采购需求']) # 提取需要采购的货物清单 key_list交通监控视频子系统.高清视频抓拍像机 ...
# if '采购需求' in cleaned_res:
# cleaned_res['技术要求'] = cleaned_res.pop('采购需求')
if no_keys_added:
final_res = postprocess(cleaned_res)
else:
@ -286,14 +369,14 @@ def test_all_files_in_folder(input_folder, output_folder):
if __name__ == "__main__":
# truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\469d2aee-9024-4993-896e-2ac7322d41b7\\ztbfile_procurement.docx"
truncate_file=r"C:\Users\Administrator\Desktop\货物标\output1\招标文件(107国道)_procurement.docx"
truncate_file=r"C:\Users\Administrator\Desktop\货物标\output1\招标文件(107国道)_procurement.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)
invalid_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\a110ed59-00e8-47ec-873a-bd4579a6e628\\ztbfile.pdf"
file_id=upload_file(truncate_file)
res=get_technical_requirements(file_id,invalid_path)
# file_id=upload_file(truncate_file)
res=get_technical_requirements(truncate_file,invalid_path)
json_string = json.dumps(res, ensure_ascii=False, indent=4)
print(json_string)
# # input_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\output1"

View File

@ -23,14 +23,14 @@ def fetch_procurement_reqs(procurement_path, procurement_docpath, invalid_path):
try:
# 上传文件并获取 file_id
file_id = upload_file(procurement_docpath)
# file_id = upload_file(procurement_docpath)
# 使用 ThreadPoolExecutor 并行处理 get_technical_requirements 和 get_business_requirements
with concurrent.futures.ThreadPoolExecutor() as executor:
# 提交任务给线程池
future_technical = executor.submit(get_technical_requirements, file_id, invalid_path)
future_technical = executor.submit(get_technical_requirements, procurement_path, invalid_path)
time.sleep(0.5) # 保持原有的延时
future_business = executor.submit(get_business_requirements, procurement_path, file_id)
future_business = executor.submit(get_business_requirements, procurement_path)
# 获取并行任务的结果
technical_requirements = future_technical.result()