1129Merge branch 'feature/fea-1' into develop

This commit is contained in:
zy123 2024-11-29 15:14:20 +08:00
commit 6f12bde416
35 changed files with 666 additions and 682 deletions

2
.gitignore vendored
View File

@ -42,7 +42,7 @@ flask_app/static/output/
*.rar
# 忽略旧版本代码
flask_app/old_version/
#flask_app/old_version/
# 忽略操作系统特定的文件
.DS_Store

View File

@ -72,6 +72,6 @@ def convert_pdf_to_markdown(file_path):
if __name__ == "__main__":
file_path=r"C:\Users\Administrator\Desktop\货物标\output3\ztbfile_qualification1.pdf"
file_path=r"C:\Users\Administrator\Desktop\fsdownload\e702f1e6-095d-443d-bb7d-ef2e42037cb1\ztbfile_procurement.pdf"
res=convert_pdf_to_markdown(file_path)
print(res)

View File

@ -104,7 +104,7 @@ def extract_content_from_json(string):
# 尝试直接解析原始 JSON 数据
try:
parsed = json.loads(original_json)
print("直接解析原始 JSON 成功。")
# print("直接解析原始 JSON 成功。")
return parsed
except json.JSONDecodeError as original_error:
print(f"直接解析原始 JSON 失败: {original_error}")

View File

@ -238,82 +238,67 @@ def pure_assistant():
return assistant
def llm_call(question, knowledge_name,file_id, result_queue, ans_index, llm_type):
if llm_type==1:
print(f"rag_assistant! question:{question}")
assistant = rag_assistant(knowledge_name)
# assistant=create_assistant(knowledge_name)
elif llm_type==2:
print(f"qianwen_long! question:{question}")
# 获取当前时间
current_time = datetime.now()
# 输出时分秒
print(current_time.strftime("%H:%M:%S.%f")[:-3])
# qianwen_res,usage = qianwen_long(file_id,question) #有bug
qianwen_res = qianwen_long(file_id, question)
result_queue.put((ans_index,(question,qianwen_res)))
return
elif llm_type==3:
# print(f"doubao! question:{question}")
doubao_res=doubao_model(question)
result_queue.put((ans_index, (question, doubao_res)))
return
else :
assistant = pure_assistant()
ans = send_message(assistant, message=question)
result_queue.put((ans_index, (question, ans))) # 在队列中添加索引 (question, ans)
"""
调用不同的 LLM 模型并将结果放入结果队列
"""
try:
if llm_type==1:
print(f"rag_assistant! question:{question}")
assistant = rag_assistant(knowledge_name)
# assistant=create_assistant(knowledge_name)
ans = send_message(assistant, message=question)
result_queue.put((ans_index, (question, ans))) # 在队列中添加索引 (question, ans)
elif llm_type==2:
print(f"qianwen_long! question:{question}")
# qianwen_res,usage = qianwen_long(file_id,question) #有bug
qianwen_res = qianwen_long(file_id, question)
if not qianwen_res:
result_queue.put((ans_index, None)) # 如果为空字符串,直接返回 None
else:
result_queue.put((ans_index, (question, qianwen_res)))
elif llm_type==3:
# print(f"doubao! question:{question}")
doubao_res=doubao_model(question)
if not doubao_res:
result_queue.put((ans_index, None)) # 如果为空字符串,直接返回 None
else:
result_queue.put((ans_index, (question, doubao_res)))
else :
assistant = pure_assistant()
ans = send_message(assistant, message=question)
result_queue.put((ans_index, (question, ans))) # 在队列中添加索引 (question, ans)
except Exception as e:
print(f"LLM 调用失败,查询索引 {ans_index},错误:{e}")
result_queue.put((ans_index, None)) # 使用 None 作为失败的占位符
def multi_threading(queries, knowledge_name="", file_id="", llm_type=1):
if not queries:
return []
print("多线程提问starting multi_threading...")
result_queue = queue.Queue()
max_retries = 2 # 设置最大重试次数
retry_counts = {} # 跟踪每个查询的重试次数
with concurrent.futures.ThreadPoolExecutor(max_workers=60) as executor:
future_to_index = {
executor.submit(llm_call, query, knowledge_name, file_id, result_queue, index, llm_type): index
for index, query in enumerate(queries)
}
with concurrent.futures.ThreadPoolExecutor(max_workers=30) as executor:
future_to_query = {}
for index, query in enumerate(queries):
# time.sleep(0.5) # 每提交一个任务后等待0.5秒目前设置了直接对qianwen-long直接限制无需sleep
future = executor.submit(llm_call, query, knowledge_name, file_id, result_queue, index, llm_type)
future_to_query[future] = index
retry_counts[index] = 0 # 初始化重试次数
for future in concurrent.futures.as_completed(future_to_index):
index = future_to_index[future]
try:
future.result() # 确保任务完成,如果有未处理的异常会在这里抛出
except Exception as exc:
print(f"查询索引 {index} 生成了一个异常:{exc}")
result_queue.put((index, None)) # 使用 None 作为失败的占位符
while future_to_query:
done, _ = concurrent.futures.wait(
future_to_query.keys(),
return_when=concurrent.futures.FIRST_COMPLETED
)
for future in done:
index = future_to_query[future]
del future_to_query[future]
try:
future.result() # 捕获异常或确认任务完成
except Exception as exc:
print(f"Query {index} generated an exception: {exc}")
retry_counts[index] += 1 # 增加重试计数
#Query 0 generated an exception: Error code: 429 - {'error': {'message': 'You exceeded your current quota, please check your plan and billing details. For more information on this error, read the docs: https://help.aliyun.com/zh/dashscope/developer-reference/tongyi-thousand-questions-metering-and-billing.', 'type': 'insufficient_quota', 'param': None, 'code': 'insufficient_quota'}, 'request_id': 'de10e2e9-78c2-978f-8801-862ffb0892e9'}
if retry_counts[index] <= max_retries:
print(f"Retrying query {index} (attempt {retry_counts[index]})...")
print("重试的问题:" + queries[index])
# 重新提交任务
new_future = executor.submit(llm_call, queries[index], knowledge_name, file_id, result_queue, index, llm_type)
future_to_query[new_future] = index
else:
print(f"Query {index} failed after {max_retries} attempts.")
result_queue.put((index, None)) # 添加占位符
# 从队列中获取所有结果并按索引排序
# 初始化结果列表,确保按查询的索引顺序排列
results = [None] * len(queries)
while not result_queue.empty():
index, result = result_queue.get()
results[index] = result
# 检查是否所有结果都是 None
# 可选:过滤掉所有结果为 None 的项
# 如果希望保留 None 以表示失败的查询,可以注释掉以下代码
if all(result is None for result in results):
return []
# 过滤掉None值
results = [r for r in results if r is not None]
return results

View File

@ -12,7 +12,7 @@ from flask_app.货物标.基础信息解析main import aggregate_basic_info_good
from flask_app.货物标.截取pdf货物标版 import truncate_pdf_specific_goods
from flask_app.main.截取pdf import truncate_pdf_specific_engineering,truncate_pdf_main
from flask_app.general.post_processing import inner_post_processing
from flask_app.old_version.基础信息整合 import aggregate_basic_info_engineering
from flask_app.old_version.基础信息整合_old import aggregate_basic_info_engineering
def get_global_logger(unique_id):

View File

@ -174,38 +174,40 @@ def extract_business_deviation(procurement):
business_requirements_string = json.dumps(new_data, ensure_ascii=False, indent=4)
# print(business_requirements_string)
prompt_template1 = """请帮我从以下文本中摘取商务要求部分,并将信息重新组织,外键名为'商务要求',键值为字符串列表,其中每个字符串为一条商务要求,去除开头的序号(若有)。
#角色
你是一个专业的招投标业务专家擅长从招标文件中总结商务要求的部分并逐条列出作为编写商务要求偏离表的前置准备
prompt_template1 = """请帮我从以下文本中摘取商务要求部分,并将信息重新组织,外键名为'商务要求',键值为字符串列表,其中每个字符串为一条商务要求,保留三角▲、五角星★(若有),但是去除开头的序号(若有)。
#角色
你是一个专业的招投标业务专家擅长从招标文件中总结商务要求的部分并逐条列出作为编写商务要求偏离表的前置准备
#要求与指南:
1. 每条内容需要有实际的含义要求不能光有标题性质的表述如'售后服务期限(质保期)及要求'
2. 你的回答内容需从所给文本中整理尽量不改变原文的表达对于嵌套键值对若键值本身符合'商务要求'可直接将其返回若键值本身语义表达不完整可将键值对拼接之后作为一条商务要求拼接符号可以是冒号''
3. 若无商务要求键值为空列表[]
#要求与指南:
1. 每条内容需要有实际的含义要求不能光有标题性质的表述如'售后服务期限(质保期)及要求'
2. 你的回答内容需从所给文本中整理尽量不改变原文的表达请勿擅自添加三角五角星若输入文本中存在嵌套键值对格式且键值本身符合'商务要求'可直接将其添加至'商务要求'的键值中若键值本身语义表达不完整可将键值对拼接之后作为一条商务要求拼接符号可以是冒号''
3. 输入文本中出现三角或五角星开头的地方请格外重视不可漏提若其后面的内容是标题性质的表述不具备实际的要求请你保留三角或五角星根据语义添加至紧跟着的字符串开头中
3. 若无商务要求键值为空列表[]
### 示例输入如下:
{{
"招标要求1": "整个平台运行运维服务,须安排人员驻场对平台进行运行维护,采用 4人轮流值班依照 7×12小时对可视化督察巡控平台进行操作确保平台稳定运行并对线上发现违规操作进行记录通过督察平台推送督办单给线下监督员小程序进行检查。"
"招标要求2": {{
"合同履行期限": "交货期(工期):合同签订之日起 15个日历天内完成并通过项目验收。",
"交货地点": "采购人指定地点",
"报价方式": "本项目报价须为固定总价,包含但不限于:采购、实施、调试、试运行、验收、运维等所有完成本项目相关的一切费用。",
"其他要求": "无。"
}},
"招标要求3": "路口必须在各方向埋设双管。"
}}
### 对应的参考输出如下:
{{
"商务要求":[
"整个平台运行运维服务,须安排人员驻场对平台进行运行维护,采用 4人轮流值班依照 7×12小时对可视化督察巡控平台进行操作确保平台稳定运行并对线上发现违规操作进行记录通过督察平台推送督办单给线下监督员小程序进行检查。",
"交货期(工期):合同签订之日起 15个日历天内完成并通过项目验收。",
"交货地点:采购人指定地点",
"本项目报价须为固定总价,包含但不限于:采购、实施、调试、试运行、验收、运维等所有完成本项目相关的一切费用。"
]
}}
文本内容{full_text}
"""
### 示例输入如下:
{{
"招标要求1": ["▲(1)整个平台运行运维服务,须安排人员驻场对平台进行运行维护,采用 4人轮流值班依照 7×12小时对可视化督察巡控平台进行操作确保平台稳定运行并对线上发现违规操作进行记录通过督察平台推送督办单给线下监督员小程序进行检查。"]
"招标要求2": {{
"合同履行期限": ["★交货期(工期):合同签订之日起 15个日历天内完成并通过项目验收。"],
"交货地点": ["采购人指定地点"],
"报价方式": ["1本项目报价须为固定总价包含但不限于采购、实施、调试、试运行、验收、运维等所有完成本项目相关的一切费用。","2)因投标人自身原因造成漏报、少报皆由其自行承担责任,采购人不再补偿。"],
"其他要求": ["无。"]
}}
}}
### 对应的参考输出如下:
{{
"商务要求":[
"▲整个平台运行运维服务,须安排人员驻场对平台进行运行维护,采用 4人轮流值班依照 7×12小时对可视化督察巡控平台进行操作确保平台稳定运行并对线上发现违规操作进行记录通过督察平台推送督办单给线下监督员小程序进行检查。",
"★交货期(工期):合同签订之日起 15个日历天内完成并通过项目验收。",
"交货地点:采购人指定地点",
"本项目报价须为固定总价,包含但不限于:采购、实施、调试、试运行、验收、运维等所有完成本项目相关的一切费用。",
"因投标人自身原因造成漏报、少报皆由其自行承担责任,采购人不再补偿。"
]
}}
文本内容{full_text}
"""
user_query1 = prompt_template1.format(full_text=business_requirements_string)
model_res1 = doubao_model(user_query1)
# print(model_res)

View File

@ -8,18 +8,75 @@ client = OpenAI(
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
# 获取文件列表
file_stk = client.files.list()
# 将文件信息解析为 JSON 格式
file_data = json.loads(file_stk.model_dump_json())
def delete_all_files():
"""
查询所有文件并删除
"""
try:
# 获取文件列表
file_stk = client.files.list()
# 提取所有文件的 id
file_ids = [file["id"] for file in file_data["data"]]
# 将文件信息解析为 JSON 格式
file_data = json.loads(file_stk.model_dump_json())
# num=len(file_ids)
# print(num)
# 循环删除每个文件
for file_id in file_ids:
file_object = client.files.delete(file_id)
print(file_object.model_dump_json())
# 提取所有文件的 id
file_ids = [file["id"] for file in file_data["data"]]
# 循环删除每个文件
for file_id in file_ids:
file_object = client.files.delete(file_id)
print(f"Deleted file with id: {file_id} - {file_object.model_dump_json()}")
except Exception as e:
print(f"An error occurred while deleting files: {e}")
def read_file_ids(output_folder):
file_ids = []
file_ids_path = os.path.join(output_folder, 'file_ids.txt')
# 检查文件是否存在
if os.path.exists(file_ids_path):
try:
with open(file_ids_path, 'r', encoding='utf-8') as file:
# 按行读取文件内容
file_ids = [line.strip() for line in file.readlines()]
print(f"读取到的文件 ID 列表:{file_ids}")
except Exception as e:
print(f"读取 file_ids.txt 文件时发生错误: {e}")
else:
print(f"文件 {file_ids_path} 不存在。")
return file_ids
def delete_file_by_ids(file_ids):
"""
根据传入的 file_id 列表删除指定的文件
:param file_ids: 一个包含文件 ID 的字符串列表
"""
if not isinstance(file_ids, list):
print("Error: file_ids should be a list.")
return
if not all(isinstance(file_id, str) for file_id in file_ids):
print("Error: Each file_id should be a string.")
return
try:
# 删除指定文件
for file_id in file_ids:
try:
# 假设 client.files.delete 会返回一个文件对象
file_object = client.files.delete(file_id)
# print(f"Deleted file with id: {file_id} - {file_object.model_dump_json()}")
except Exception as e:
# 处理删除单个文件时的异常
print(f"Failed to delete file with id {file_id}: {e}")
except Exception as e:
print(f"An error occurred while processing the file_ids: {e}")
if __name__ == '__main__':
delete_all_files()

View File

@ -1,6 +1,10 @@
import ast
import json
import re
import threading
from functools import wraps
from flask import g
from ratelimit import limits, sleep_and_retry
import random
import time
@ -8,16 +12,35 @@ from pathlib import Path
from openai import OpenAI
import os
def upload_file(file_path):
file_write_lock = threading.Lock()
def upload_file(file_path,output_folder=""):
"""
Uploads a file to DashScope and returns the file ID.
Additionally, saves the file ID to 'file_ids.txt' in the given output folder.
"""
if not output_folder:
output_folder=os.path.dirname(file_path)
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
# 上传文件并获取 file_id
file = client.files.create(file=Path(file_path), purpose="file-extract")
return file.id
file_id = file.id
# 创建output_folder路径如果它不存在
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# 确保文件写入是互斥的
with file_write_lock: # 在这个代码块中,其他线程无法进入
file_ids_path = os.path.join(output_folder, 'file_ids.txt')
# 如果文件不存在,就创建它并写入 file_id
with open(file_ids_path, 'a') as f:
f.write(f'{file_id}\n')
return file_id
@sleep_and_retry
@limits(calls=4, period=1) # 每秒最多调用4次
@ -32,95 +55,178 @@ def shared_rate_limit(func):
return func(*args, **kwargs)
return wrapper
@shared_rate_limit
def qianwen_long(file_id, user_query):
print("call qianwen-long...")
def qianwen_long(file_id, user_query, max_retries=2, backoff_factor=1.0):
"""
Uses a previously uploaded file to generate a response based on a user query.
基于上传的文件 ID 和用户查询生成响应并在失败时自动重试
参数
- file_id: 上传文件的 ID
- user_query: 用户查询
- max_retries: 最大重试次数默认 2
- backoff_factor: 指数退避的基础等待时间默认 1.0
"""
print("call qianwen_long...")
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
# Generate a response based on the file ID
completion = client.chat.completions.create(
model="qwen-long",
# top_p=0.5,
temperature=0.5,
# response_format={"type":"json_object"},
messages=[
{
'role': 'system',
'content': f'fileid://{file_id}'
},
{
'role': 'user',
'content': user_query
}
],
stream=False
)
for attempt in range(1, max_retries + 2): # +1 是为了包括初始调用
try:
# 调用 API
completion = client.chat.completions.create(
model="qwen-long",
temperature=0.5,
messages=[
{
'role': 'system',
'content': f'fileid://{file_id}'
},
{
'role': 'user',
'content': user_query
}
],
stream=False
)
# 如果调用成功,返回响应内容
return completion.choices[0].message.content
except Exception as exc:
# 提取错误代码
error_code, error_code_string = extract_error_details(str(exc))
print(f"{attempt} 次尝试失败,查询:'{user_query}',错误:{exc}")
if error_code == 429:
if attempt <= max_retries:
sleep_time = backoff_factor * (2 ** (attempt - 1)) # 指数退避
print(f"错误代码为 429将在 {sleep_time} 秒后重试...")
time.sleep(sleep_time)
else:
print(f"查询 '{user_query}' 的所有 {max_retries + 1} 次尝试均失败429 错误)。")
elif error_code == 400 and error_code_string in ['data_inspection_failed', 'ResponseTimeout','DataInspectionFailed','response_timeout']:
if attempt == 1: # 只重试一次
print(f"错误代码为 400 - {error_code_string},将立即重试...")
continue # 直接跳到下一次循环(即重试一次)
else:
print(f"查询 '{user_query}' 的所有 {max_retries + 1} 次尝试均失败400 - {error_code_string})。")
else:
# 对于非 429 和非特定 400 错误,不进行重试,直接抛出异常
print(f"遇到非 429 或非 'data_inspection_failed' 的 400 错误(错误代码:{error_code}),不进行重试。")
return ""
def extract_error_details(error_message):
"""
从错误消息中提取错误代码和内部错误代码
假设错误消息的格式包含 'Error code: XXX - {...}'
"""
# 提取数值型错误代码
error_code_match = re.search(r'Error code:\s*(\d+)', error_message)
error_code = int(error_code_match.group(1)) if error_code_match else None
# 提取内部错误代码字符串(如 'data_inspection_failed'
error_code_string = None
error_dict_match = re.search(r'Error code:\s*\d+\s*-\s*(\{.*\})', error_message)
if error_dict_match:
error_dict_str = error_dict_match.group(1)
try:
# 使用 ast.literal_eval 解析字典字符串
error_dict = ast.literal_eval(error_dict_str)
error_code_string = error_dict.get('error', {}).get('code')
print(error_code_string)
except Exception as e:
print(f"解析错误消息失败: {e}")
return error_code, error_code_string
# Return the response content
# return completion.choices[0].message.content,completion.usage
return completion.choices[0].message.content
@shared_rate_limit
def qianwen_long_stream(file_id, user_query):
print("调用 qianwen-long stream...")
def qianwen_long_stream(file_id, user_query, max_retries = 2, backoff_factor = 1.0):
"""
使用之前上传的文件根据用户查询生成响应并实时显示流式输出
参数
- file_id: 上传文件的 ID
- user_query: 用户查询
- max_retries: 最大重试次数默认 2
- backoff_factor: 指数退避的基础等待时间默认 1.0
返回:
- Optional[str]: 成功时返回响应内容失败时返回空字符串
"""
print("调用 qianwen-long stream...")
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
# 生成基于文件ID的响应
completion = client.chat.completions.create(
model="qwen-long",
temperature=0.4,
messages=[
{
'role': 'system',
'content': f'fileid://{file_id}'
},
{
'role': 'user',
'content': user_query
}
],
stream=True # 启用流式响应
)
for attempt in range(1, max_retries + 2): # +1 是为了包括初始调用
try:
# 生成基于文件ID的响应
completion = client.chat.completions.create(
model="qwen-long",
temperature=0.4,
max_tokens=5000,
messages=[
{
'role': 'system',
'content': f'fileid://{file_id}'
},
{
'role': 'user',
'content': user_query
}
],
stream=True # 启用流式响应
)
full_response = "" # 用于存储完整的响应内容
full_response = "" # 用于存储完整的响应内容
try:
for chunk in completion:
# 假设chunk是一个对象先将其转换为字典
if hasattr(chunk, 'to_dict'):
chunk_data = chunk.to_dict()
for chunk in completion:
if hasattr(chunk, 'to_dict'):
chunk_data = chunk.to_dict()
else:
chunk_data = json.loads(chunk.model_dump_json())
choices = chunk_data.get('choices', [])
if not choices:
continue
choice = choices[0]
delta = choice.get('delta', {})
content = delta.get('content', '')
if content:
full_response += content
# print(content, end='', flush=True) # 实时打印内容
if choice.get('finish_reason'):
break
return full_response # 返回完整的响应内容
except Exception as exc:
# 提取错误代码
error_code, error_code_string = extract_error_details(str(exc))
print(f"{attempt} 次尝试失败,查询:'{user_query}',错误:{exc}")
if error_code == 429:
if attempt <= max_retries:
sleep_time = backoff_factor * (2 ** (attempt - 1)) # 指数退避
print(f"错误代码为 429将在 {sleep_time} 秒后重试...")
time.sleep(sleep_time)
else:
print(f"查询 '{user_query}' 的所有 {max_retries + 1} 次尝试均失败429 错误)。")
elif error_code == 400 and error_code_string in ['data_inspection_failed', 'ResponseTimeout',
'DataInspectionFailed', 'response_timeout']:
if attempt == 1: # 只重试一次
print(f"错误代码为 400 - {error_code_string},将立即重试...")
continue # 直接跳到下一次循环(即重试一次)
else:
print(f"查询 '{user_query}' 的所有 {max_retries + 1} 次尝试均失败400 - {error_code_string})。")
else:
# 如果没有to_dict方法尝试直接转换为JSON
chunk_data = json.loads(chunk.model_dump_json())
# 检查是否有'choices'字段
choices = chunk_data.get('choices', [])
if not choices:
continue # 如果没有choices跳过当前chunk
choice = choices[0] # 获取第一个choice
delta = choice.get('delta', {})
# 提取'content'字段
content = delta.get('content', '')
if content:
# print(content, end='', flush=True) # 实时打印内容
full_response += content
# 检查是否有结束条件
if choice.get('finish_reason'):
break
except KeyboardInterrupt:
print("\n中断流式响应。")
except Exception as e:
print(f"\n处理流式响应时出错: {e}")
return full_response # 返回完整的响应内容
# 对于非 429 和非特定 400 错误,不进行重试,直接抛出异常
print(f"遇到非 429 或非 'data_inspection_failed' 的 400 错误(错误代码:{error_code}),不进行重试。")
# 如果所有尝试都失败了,返回空字符串
return ""
@shared_rate_limit
def qianwen_long_text(file_id, user_query):

View File

@ -113,9 +113,16 @@ def combine_evaluation_standards(evaluation_method):
# user_query_2 = (
# "根据该文档中的评标办法前附表,请你列出该文件的技术评分,商务评分,投标报价评审标准以及它们对应的具体评分要求,若对应内容中存在其他信息,在键名如'技术评分'中新增子键名'备注'存放该信息。如果评分内容因素不是这3个则返回文档中给定的评分内容因素以及它的评分要求。请以json格式返回结果不要回答有关形式、资格、响应性评审标准的内容")
user_query_2 = (
"""
根据该文档中的评标办法前附表请你列出该文件的技术评分商务评分投标报价评审以及它们对应的具体评分要求请以json格式返回结果最外层键名分别是'技术评分','商务评分','投标报价评审',请在这三大项评分中分别用若干键值对表示具体评分项外层键名为各评审因素可能存在嵌套关系但最内层键值为一个列表列表中包含若干可为一描述该评审因素的评分及要求的字典内层键名分别是'评分''要求'若无评分可删去'评分'键值对'要求'中说明了该评审因素的评分标准若这三大项评分中存在其他信息则在相应评分大块内部新增键名'备注'存放该信息键值为具体的要求否则不需要如果评分内容因素不是这三大项则返回文档中给定的评分内容因素以及它们的具体评分要求
以下为需要考虑的注意事项1.不要回答有关资格审查的内容也不要从评标办法正文中提取回答 2.若大项的'xx评分'要求未在文中说明则键名'xx评分'的键值设为'本项目无xx评分项'例如"技术评分":"本项目无技术评分项" 3. 如果该招标活动有多个包则最外层键名为对应的包名,否则不需要 4.你无需将表格的单元格内的内容进行拆分需要将它视为一个整体以下为示例输出仅供格式参考
"""根据该文档中的评标办法表格请你列出该文件的技术评分商务评分投标报价评审以及它们对应的具体评分要求请以json格式返回结果最外层键名分别是'技术评分','商务评分','投标报价评审',请在这三大项评分中分别用若干键值对表示具体评分项,外层键名为各评审因素,可能存在嵌套关系,但最内层键值为一个列表,列表中包含若干(可为一)描述该评审因素的评分及要求的字典,内层键名分别是'评分''要求',若无评分,可删去'评分'键值对,'要求'中说明了该评审因素的评分标准;若这三大项评分中存在其他信息,则在相应评分大块内部新增键名'备注'存放该信息,键值为具体的要求,否则不需要。如果评分内容(因素)不是这三大项,则返回文档中给定的评分内容(因素)以及它们的具体评分要求。
要求与指南
1. 请首先定位评分细则的表格不要回答有关资格审查的内容也不要从评标办法正文中提取回答
2. 若大项的'xx评分'要求未在文中说明则键名'xx评分'的键值设为'本项目无xx评分项'例如"技术评分":"本项目无技术评分项"
3. 如果该招标活动有多个包则最外层键名为对应的包名,否则最外层键名为各大评分项
4. 你无需将表格的单元格内的内容进行拆分需要将它视为一个整体
5. '评分'的键值不能是一个范围数字'0-5分'应该是一个具体数字'5分'或者是一个定性的指标如'合格制'
以下为示例输出仅供格式参考
{
"一包": {
"技术评分": {
@ -181,7 +188,7 @@ def combine_evaluation_standards(evaluation_method):
return update_json #商务标技术标整合
if __name__ == "__main__":
# evaluation_method="C:\\Users\\Administrator\\Desktop\\招标文件\\招标01_evaluation_method.pdf"
evaluation_method= r"D:\flask_project\flask_app\static\output\output1\9a4e3bc3-8367-4529-87db-af0ea53dc348\ztbfile_evaluation_method.pdf"
evaluation_method= r"C:\Users\Administrator\Desktop\招标文件\招标04_evaluation_method.pdf"
evaluation_standards_res=combine_evaluation_standards(evaluation_method)
# 从结果中提取"商务标"和"技术标"
technical_standards = {"技术评分": evaluation_standards_res.get("技术评分", {})}

View File

@ -216,7 +216,7 @@ def process_string_list(string_list):
def main():
# 定义JSON文件路径
# json_path = "flask_app/general/static/插入位置.json"
json_path = "D:\\flask_project\\flask_app\\general\\static\\插入位置.json"
json_path = "/flask_app/general/static/插入位置.json"
# 加载数据
data_dict = load_data(json_path)

View File

@ -5,14 +5,14 @@ import time
from concurrent.futures import ThreadPoolExecutor
from flask_app.main.截取pdf import truncate_pdf_multiple
from flask_app.general.table_content_extraction import extract_tables_main
from flask_app.old_version.文档理解大模型版知识库处理.知识库操作 import addfileToKnowledge, deleteKnowledge
from flask_app.old_version.文档理解大模型版知识库处理.知识库操作_old import addfileToKnowledge, deleteKnowledge
from flask_app.main.提取json工程标版 import convert_clause_to_json
from flask_app.general.json_utils import transform_json_values
from flask_app.main.无效标和废标和禁止投标整合 import combine_find_invalid
from flask_app.main.投标人须知正文提取指定内容工程标 import extract_from_notice
import concurrent.futures
from flask_app.old_version.基础信息整合 import combine_basic_info
from flask_app.old_version.资格审查模块old import combine_review_standards
from flask_app.old_version.基础信息整合_old import combine_basic_info
from flask_app.old_version.资格审查模块old_old import combine_review_standards
from flask_app.main.商务评分技术评分整合 import combine_evaluation_standards
from flask_app.general.format_change import pdf2docx, docx2pdf
from flask_app.general.docx截取docx import copy_docx

View File

@ -3,7 +3,7 @@ import uuid
from llama_index.readers.dashscope.base import DashScopeParse
from llama_index.readers.dashscope.utils import ResultType
from llama_index.indices.managed.dashscope import DashScopeCloudIndex
from flask_app.old_version.文档理解大模型版知识库处理.删除知识库 import delete_index, create_client
from flask_app.old_version.文档理解大模型版知识库处理.删除知识库_old import delete_index, create_client
def addfileToKnowledge(filepath,knowledge_name):

View File

@ -9,7 +9,7 @@ from flask_app.general.通义千问long import upload_file, qianwen_long
from concurrent.futures import ThreadPoolExecutor
from flask_app.general.table_content_extraction import extract_tables_main
from flask_app.old_version.不得存在及禁止投标情形 import find_forbidden, process_string_list
from flask_app.old_version.不得存在及禁止投标情形_old import find_forbidden, process_string_list
#处理跨页的段落

View File

@ -3,7 +3,7 @@ import os
from flask_app.main.提取json工程标版 import convert_clause_to_json
from flask_app.general.json_utils import extract_content_from_json
from flask_app.old_version.形式响应评审old import process_reviews
from flask_app.old_version.资格评审old import process_qualification
from flask_app.old_version.资格评审old_old import process_qualification
from flask_app.general.通义千问long import upload_file, qianwen_long
from concurrent.futures import ThreadPoolExecutor

View File

@ -9,7 +9,7 @@ from flask_app.routes.get_deviation import get_deviation_bp
from flask_app.routes.little_zbparse import little_zbparse_bp
from flask_app.routes.upload import upload_bp
from flask_app.routes.test_zbparse import test_zbparse_bp
from flask_app.general.清除file_id import delete_file_by_ids,read_file_ids
class FlaskAppWithLimiter(Flask):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@ -23,25 +23,6 @@ def create_app():
handler.setFormatter(CSTFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
app.logger.addHandler(handler)
app.logger.setLevel(logging.INFO)
# @app.before_request
# def before_request():
# """
# 每个请求开始前初始化 logger 和 output_folder
# 根据请求的端点选择不同的子文件夹。
# """
# # 确定当前请求的端点
# blueprint = request.blueprint
# # 映射端点到子文件夹
# subfolder_map = {
# 'get_deviation': 'output3',
# 'little_zbparse': 'output2',
# 'upload': 'output1',
# 'test_zbparse': 'test_output'
# }
# # 获取对应的子文件夹,默认为 'output1'
# subfolder = subfolder_map.get(blueprint, 'output1')
# # 创建 logger 和 output_folder
# create_logger(app, subfolder)
# 注册蓝图
app.register_blueprint(get_deviation_bp)
@ -58,6 +39,13 @@ def create_app():
monitor = getattr(g, 'monitor', None)
if limiter and not monitor.is_timeout:
limiter.semaphore.release()
output_folder = getattr(g, 'output_folder', None)
if output_folder:
# 执行与output_folder相关的清理操作例如删除临时文件
logger = g.logger
logger.info(f"正在清理输出文件夹: {output_folder}")
file_ids=read_file_ids(output_folder)
delete_file_by_ids(file_ids)
return app
#TODO:培训要求、总体要求、进度要求、'建设要求'到技术要求中,归类到其他要求中

View File

@ -1,168 +1,3 @@
import json
import re
def insert_missing_commas(json_str):
"""
使用正则表达式在缺失逗号的位置插入逗号
具体来说寻找一个值的结束引号后紧跟着下一个键的开始引号并在中间插入逗号
"""
# 这个正则匹配一个字符串结尾的引号,可能有空白字符,然后是另一个键的引号
pattern = r'(":\s*"[^"]*)"\s*(")'
replacement = r'\1", \2'
previous_str = None
while previous_str != json_str:
previous_str = json_str
json_str = re.sub(pattern, replacement, json_str)
return json_str
def fix_json_escape_sequences(json_str):
"""
修复 JSON 字符串中的非法转义序列
将所有不符合 JSON 规范的反斜杠进行转义
"""
# JSON 中合法的转义字符
valid_escapes = ['"', '\\', '/', 'b', 'f', 'n', 'r', 't', 'u']
# 使用正则表达式找到所有反斜杠
# 如果反斜杠后面不是合法的转义字符,则进行转义
pattern = re.compile(r'\\(?!["\\/bfnrtu])')
fixed_str = pattern.sub(r'\\\\', json_str)
return fixed_str
def replace_latex_expressions(json_str):
"""
替换 JSON 字符串中的 LaTeX 风格表达式
例如 $ \geq 2 m$ 替换为 2
"""
# 定义 LaTeX 符号到 Unicode 字符的映射
latex_mapping = {
r'\geq': '',
r'\leq': '',
r'\times': '×',
r'\frac': '/', # 简单处理分数
r'\neq': '',
r'\approx': '',
r'\pm': '±',
r'\alpha': 'α',
r'\beta': 'β',
r'\gamma': 'γ',
r'\delta': 'δ',
r'\pi': 'π',
r'\sqrt': '',
r'\infty': '',
r'\cup': '',
r'\cap': '',
r'\subseteq': '',
r'\supseteq': '',
r'\forall': '',
r'\exists': '',
r'\rightarrow': '',
r'\leftarrow': '',
# 添加更多需要的映射
}
# 处理每一个 LaTeX 表达式
def replace_match(match):
expr = match.group(1)
for latex, char in latex_mapping.items():
expr = expr.replace(latex, char)
# 替换单位符号,例如 ' m' 到 '米', ' s' 到 '秒', 等等
expr = re.sub(r'(\d+)\s*m', r'\1米', expr)
expr = re.sub(r'(\d+)\s*s', r'\1秒', expr)
expr = re.sub(r'(\d+)\s*kg', r'\1公斤', expr)
expr = re.sub(r'(\d+)\s*A', r'\1安', expr) # 例如电流单位安培
# 继续添加更多单位
return expr
# 替换所有 $...$ 包围的内容
fixed_str = re.sub(r'\$(.*?)\$', replace_match, json_str)
return fixed_str
def extract_content_from_json(string):
"""
输入字符串尝试解析 JSON 数据
1. 尝试直接解析原始 JSON
2. 如果直接解析失败按顺序使用不同修复方法尝试解析
"""
if not string.strip():
return {}
# 提取第一个匹配的 JSON 对象
match = re.search(r'\{[\s\S]*\}', string)
if not match:
print("未找到有效的 JSON 内容。")
return {}
original_json = match.group(0)
# 尝试直接解析原始 JSON 数据
try:
parsed = json.loads(original_json)
print("直接解析原始 JSON 成功。")
return parsed
except json.JSONDecodeError as original_error:
print(f"直接解析原始 JSON 失败: {original_error}")
# 方法1逗号修复
try:
fixed_json1 = insert_missing_commas(original_json)
parsed = json.loads(fixed_json1)
print("使用方法1逗号修复成功。")
return parsed
except json.JSONDecodeError as error1:
print(f"方法1逗号修复解析失败: {error1}")
# 方法2LaTeX 表达式替换
try:
fixed_json2 = replace_latex_expressions(original_json)
parsed = json.loads(fixed_json2)
print("使用方法2LaTeX 表达式替换成功。")
return parsed
except json.JSONDecodeError as error2:
print(f"方法2LaTeX 替换)解析失败: {error2}")
# 方法3非法转义序列修复
try:
fixed_json3 = fix_json_escape_sequences(original_json)
parsed = json.loads(fixed_json3)
print("使用方法3非法转义序列修复成功。")
return parsed
except json.JSONDecodeError as error3:
print(f"方法3非法转义修复解析失败: {error3}")
# 如果所有方法都失败,返回空字典
print("所有修复方法均失败,返回空字典。")
return {}
def replace_latex_expressions_in_dict(obj):
"""
递归遍历字典或列表替换其中的 LaTeX 表达式
"""
if isinstance(obj, dict):
return {k: replace_latex_expressions_in_dict(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [replace_latex_expressions_in_dict(item) for item in obj]
elif isinstance(obj, str):
# 仅处理被 $...$ 包围的内容
def replace_match(match):
expr = match.group(1)
return replace_latex_expressions(expr)
# 替换所有 $...$ 包围的内容
return re.sub(r'\$(.*?)\$', replace_match, obj)
else:
return obj
test_json_1 = """
{
"示例": "速度为 $v = 3 \\times 10^2 m/s$,加速度为 $a \\geq 9.8 m/s^2$。"
}
"""
res=extract_content_from_json(test_json_1)
print(res)
data=""
if "哈哈" in data:
print("yes")

View File

@ -2,18 +2,19 @@
import json
import re
from PyPDF2 import PdfReader
import textwrap
from flask_app.general.doubao import read_txt_to_string, pdf2txt
from flask_app.general.json_utils import combine_json_results,clean_json_string
from flask_app.general.通义千问long import upload_file,qianwen_long_stream
from flask_app.general.json_utils import combine_json_results, clean_json_string
from flask_app.general.通义千问long import upload_file, qianwen_long_stream
from flask_app.货物标.截取pdf货物标版 import extract_common_header, clean_page_content
import concurrent.futures
from flask_app.general.doubao import doubao_model
#正则表达式判断原文中是否有商务、服务、其他要求
def find_exists(truncate_file, required_keys):
if not truncate_file:
return ["技术要求", "商务要求", "服务要求", "其他要求"]
# 正则表达式判断原文中是否有商务、服务、其他要求
def find_exists(truncate_file, required_keys):
# if not truncate_file:
# return ["技术要求", "商务要求", "服务要求", "其他要求"]
common_header = extract_common_header(truncate_file) # 假设该函数已定义
pdf_document = PdfReader(truncate_file)
@ -33,63 +34,92 @@ def find_exists(truncate_file, required_keys):
end_pattern = re.compile(
r'第[一二三四五六七八九1-9]+(?:章|部分)\s*[\u4e00-\u9fff、()]+\s*$', re.MULTILINE)
# 遍历所有页面,拼接全文
text = ""
for page in pdf_document.pages:
page_text = page.extract_text() or ""
cleaned_text = clean_page_content(page_text, common_header)
text += cleaned_text + "\n"
# 只处理第一页和最后一页
first_page = pdf_document.pages[0].extract_text() or ""
last_page = pdf_document.pages[-1].extract_text() or ""
# 匹配起始位置
start_match = re.search(begin_pattern, text)
# 清理页面内容
first_page_clean = clean_page_content(first_page, common_header)
last_page_clean = clean_page_content(last_page, common_header)
# 在第一页寻找起始位置
start_match = re.search(begin_pattern, first_page_clean)
if not start_match:
print("未找到开始模式")
return []
start_index = start_match.end()
# 匹配结束位置
end_match = re.search(end_pattern, text[start_index:])
if end_match:
end_index = start_index + end_match.start()
relevant_text = text[start_index:end_index]
print("未找到开始模式,返回完整第一页")
first_content = first_page_clean
else:
relevant_text = text[start_index:]
start_index = start_match.end()
first_content = first_page_clean[start_index:]
# 保留换行,避免结构丢失
# 在最后一页寻找结束位置
end_match = re.search(end_pattern, last_page_clean)
if not end_match:
print("未找到结束模式,返回完整最后一页")
last_content = last_page_clean
else:
last_content = last_page_clean[:end_match.start()]
# 获取中间页面的内容
middle_content = ""
if len(pdf_document.pages) > 2:
for page_num in range(1, len(pdf_document.pages) - 1):
page_text = pdf_document.pages[page_num].extract_text() or ""
cleaned_text = clean_page_content(page_text, common_header)
middle_content += cleaned_text + "\n"
# 组合所有内容
relevant_text = first_content + "\n" + middle_content + "\n" + last_content
relevant_text = re.sub(r'\s+', ' ', relevant_text)
# print(f"提取的内容范围:\n{relevant_text}")
# 匹配所需的要求
matched_requirements = []
punctuation = r"[,。?!、;:,.?!]*"
for req in required_keys:
# required_keys 中的元素本身已包含 \s*,直接作为正则模式
if re.search(req, relevant_text):
if req == "\s*务\s*要\s*求":
# 提取所有包含"服务要求"的行
# 替换特定的关键词
if req in [r"\s*体\s*要\s*求", r"\s*设\s*要\s*求"]:
# 匹配到“总体要求”或“建设要求”时,添加“技术要求”而非原关键词
tech_req = r"\s*术\s*要\s*求"
if tech_req not in matched_requirements:
matched_requirements.append(tech_req)
elif req in [r"\s*训\s*要\s*求",r"\s*保\s*要\s*求",r"\s*后\s*要\s*求"]:
# 匹配到“培训要求”时,添加“服务要求”而非原关键词
service_req = r"\s*务\s*要\s*求"
if service_req not in matched_requirements:
matched_requirements.append(service_req)
elif req in [r"\s*度\s*要\s*求",r"\s*期\s*要\s*求"]:
busi_req = r"\s*务\s*要\s*求"
if busi_req not in matched_requirements:
matched_requirements.append(busi_req)
elif req == r"\s*务\s*要\s*求":
# 处理“服务要求”时的特殊逻辑
lines = [line for line in relevant_text.split('\n') if re.search(req, line)]
# 检查是否存在'技术'紧跟在'服务要求'前面(中间只有标点,标点是可选的)
pattern = "\s*术" + punctuation + req
pattern = r"\s*术" + punctuation + req
if any(re.search(pattern, line) for line in lines):
# 如果存在'技术'紧跟'服务要求',添加"技术、服务要求"
if "\s*术\s*、\s*服\s*务\s*要\s*求" not in matched_requirements:
matched_requirements.append("\s*术\s*、\s*服\s*务\s*要\s*求")
combined_req = r"\s*术\s*、\s*服\s*务\s*要\s*求"
if combined_req not in matched_requirements:
matched_requirements.append(combined_req)
else:
# 如果不存在'技术'紧跟'服务要求',正常添加"服务要求"
matched_requirements.append(req)
if req not in matched_requirements:
matched_requirements.append(req)
else:
matched_requirements.append(req)
# 对于其他匹配的关键词,直接添加
if req not in matched_requirements:
matched_requirements.append(req)
# 去除 \s*,仅返回原始关键词
clean_requirements = [re.sub(r'\\s\*', '', req) for req in matched_requirements]
# 判断互斥关系:如果有"技术、服务要求",删除"技术要求"和"服务要求"
if "技术、服务要求" in clean_requirements:
clean_requirements = [req for req in clean_requirements if req not in ["技术要求", "服务要求"]]
# 确保最终返回的列表仅包含指定的五项
# allowed_requirements = {"技术要求", "服务要求", "商务要求", "其他要求", "技术、服务要求"}
# final_requirements = [req for req in clean_requirements if req in allowed_requirements]
return clean_requirements
def generate_queries(truncate_file, required_keys):
key_list = find_exists(truncate_file, required_keys)
queries = []
@ -104,48 +134,47 @@ def generate_queries(truncate_file, required_keys):
# print(query_base)
return queries
def generate_user_query_template(required_keys,processed_filepath):
import textwrap
import json
# 定义所有可能的键
all_possible_keys = ["技术要求", "服务要求", "商务要求", "其他要求", "技术、服务要求","总体要求","进度要求","培训要求"]
def generate_template(required_keys, type=1):
# 定义每个键对应的示例内容
example_content1 = {
"技术要求": ["相关技术要求1", "相关技术要求2"],
"服务要求": ["服务要求1", "服务要求2"],
"服务要求": ["服务要求1", "服务要求2", "服务要求3"],
"商务要求": ["商务要求1", "商务要求2"],
"其他要求": ["关于项目采购的其他要求1...", "关于项目采购的其他要求2...", "关于项目采购的其他要求3..."],
"技术、服务要求": ["相关技术、服务要求内容1", "相关技术、服务要求内容2", "相关技术、服务要求内容3"]
}
example_content2 = {
"技术要求": {
"子因素名1": ["相关技术要求1", "相关技术要求2"]
},
"服务要求": {
"子因素名1": ["服务要求1"],
"子因素名2": ["服务要求2", "服务要求3"]
},
"商务要求": {
"子因素名1": ["商务要求1"],
"子因素名2": ["商务要求2"]
},
"其他要求": {
"子因素名1": ["关于项目采购的其他要求1...", "关于项目采购的其他要求2..."],
"子因素名2": ["关于项目采购的其他要求3...", "关于项目采购的其他要求4..."]
"子因素名2": ["关于项目采购的其他要求3..."]
},
"技术、服务要求": ["相关技术、服务要求内容1", "相关技术、服务要求内容2"]
}
example_content2 = {
"技术要求": {
"子因素名1": ["相关技术要求1", "相关技术要求2"],
"子因素名2": ["相关技术要求3"]
},
"服务要求": {
"子因素名1": ["相关服务要求1", "相关服务要求2"],
"子因素名2": ["相关服务要求3", "相关服务要求4"]
},
"商务要求": ["商务要求1", "商务要求2"],
"其他要求": ["关于项目采购的其他要求1..."],
"技术、服务要求": {
"子因素名1": ["相关技术、服务要求内容1"],
"子因素名2": ["相关技术、服务要求内容2", "相关技术、服务要求内容3"]
}
}
# 将 required_keys 转换为集合以便于操作
keys = set(required_keys)
type_to_keys_map = {
1: ["服务要求", "商务要求", "其他要求"],
2: ["技术要求", "技术、服务要求"]
}
# 根据 type 获取对应的 all_possible_keys
chosen_keys = type_to_keys_map.get(type, [])
another_keys_list = type_to_keys_map.get(3 - type, []) # 3 - type 将 type 1 映射到 2反之亦然
another_keys_str = ', '.join([f"'{key}'" for key in another_keys_list])
# 处理互斥关系:如果 "技术要求" 和 "服务要求" 同时存在,则移除 "技术、服务要求"
if "技术要求" in keys and "服务要求" in keys:
keys.discard("技术、服务要求")
@ -155,121 +184,106 @@ def generate_user_query_template(required_keys,processed_filepath):
keys.discard("服务要求")
# 确保 keys 中只包含允许的键
keys = keys.intersection(all_possible_keys)
keys = keys.intersection(chosen_keys)
# 按照预定义的顺序排序键,以保持一致性
sorted_keys = [key for key in all_possible_keys if key in keys]
sorted_keys = [key for key in chosen_keys if key in keys]
# 如果没有任何键被选中,返回一个默认的模板或抛出异常
# 如果没有任何键被选中,返回""
if not sorted_keys:
raise ValueError("required_keys 中没有有效的键。")
return ""
# 生成提示部分,根据 sorted_keys 动态构建
keys_str = ''.join(sorted_keys)
outer_keys_str = ', '.join([f"'{key}'" for key in sorted_keys])
# 使用三引号定义多行字符串,便于编辑和维护
prompt_instruction = textwrap.dedent(f"""请你根据该货物类招标文件中的采购要求部分内容(技术、服务及商务要求部分内容),请告诉我该项目采购的{keys_str}分别是什么请以json格式返回结果默认情况下外层键名是{outer_keys_str},键值为字符串列表,每个字符串表示具体的一条要求,请按原文内容回答,保留三角▲、五角星★和序号(若有),不要擅自增添内容。
# 生成模板的通用部分
def generate_prompt_instruction(keys_str, outer_keys_str, another_keys_str, type):
if type == 1:
specific_instructions = textwrap.dedent(
"""4. 若章节开头位置或者采购清单中除了需要采购的货物、数量、单位之外,还有带三角▲或五角星★的描述内容(如工期要求、进度要求、品牌要求等商务要求),也请将该部分内容提取出来,添加在外层键名为'商务要求'的键值部分;若存在标题包含'工期要求''进度要求'等和商务要求有关的关键字,也请将该标题下的内容提取,添加在外层键名为'商务要求'的键值部分。请不要遗漏这部分的'商务要求'
5. 在提取'服务要求'的时候若原文包含正文和表格中存在'安装要求''售后要求''维护要求''培训要求'等服务相关的要求说明请添加至'服务要求'的键值部分不要遗漏这部分的'服务要求'
"""
)
else:
specific_instructions = textwrap.dedent(
"""4. 在提取技术要求或技术、服务要求时,你无需从采购清单或表格中提取技术要求以及参数要求,你仅需定位到原文中包含'技术要求''技术、服务要求'关键字的标题并提取其后相关内容,从正文中提取;若技术要求或技术服务要求的内容全在表格中,键值为空列表[]。
5. 在提取'技术要求'注意不要提取有关'安装、售后、维护、运维、培训、质保'等要求它们不属于'技术要求'但是若大标题中包含'总体要求''建设要求'等和技术要求相关的关键字请添加到'技术要求'的键值部分
"""
)
return textwrap.dedent(
f"""请你根据该货物类招标文件中的采购要求部分内容,请告诉我该项目采购的{keys_str}分别是什么请以json格式返回结果默认情况下外层键名是{outer_keys_str}键值为字符串列表每个字符串表示具体的一条要求可以按原文中的序号作划分若有序号的话请按原文内容回答保留三角▲、五角星★和序号若有不要擅自增添内容及序号。注意1. 若相应要求下存在子标题表示子要求因素可以将它忽略而不是将它与下文具体要求进行多行合并或者作为该要求下的嵌套键名总之字符串列表中只提取具体的要求。2. 请不要提取{another_keys_str}中的内容。
要求与指南
1. 默认情况无需嵌套键值为字符串列表若存在嵌套结构嵌套键名是原文中该要求下相应子标题最多一层嵌套
2. JSON 的结构要求
- 外层键名为 {outer_keys_str} 中的各项
1. JSON 的结构要求
- 默认情况无需嵌套键值对键值为字符串列表若存在嵌套结构(即有明确标题表示各子要求)则嵌套键名是各子要求嵌套键值为字符串列表为该子要求下的若干要求最多一层嵌套
- 每个外层键对应的值可以是
a. 一个对象字典其键为子因素名值为字符串列表
b. 一个字符串列表表示具体的一条条要求若只有一条要求也用字符串列表表示
a. 一个字符串列表表示具体的一条条要求若只有一条要求也用字符串列表表示
b. 一个对象字典其键为子因素名值为字符串列表
c. 若文档中无符合的相关要求键值为空列表[]
- 最多只允许一层嵌套
3. 请优先定位正文部分的大标题'xx要求'在其之后提取'xx要求'相关内容
4. 若章节开头位置或者采购清单中除了需要采购的货物数量单位之外还有带三角或五角星的描述内容如工期要求质保要求等商务要求请将该部分内容提取出来添加在键名为'商务要求'的字典的键值部分,注意请不要返回Markdown语法必要时使用冒号':'将相关信息拼接在一起
5. 在提取技术要求或技术服务要求时若有你无需从采购清单或表格中提取货物名以及参数要求你仅需定位到原文中大标题'技术要求''技术、服务要求'部分提取正文内容若内容全在表格中键值为空列表[]
6. 若无相关要求键值为[]
""" )
2. 请优先且准确定位正文部分包含以下关键字的标题{outer_keys_str}在其之后提取'XX要求'相关内容尽量避免在无关地方提取内容
3. 注意请不要返回Markdown表格语法必要时使用冒号':'将相关信息拼接在一起
{specific_instructions}
6. 字符串列表中的每个字符串内容需与原文内容保持一致保留前面的三角五角星和序号若有而且你不可以擅自添加序号
""")
# 过滤 example_content1 和 example_content2 以仅包含 sorted_keys
def filter_content(example_content, keys):
# 过滤示例内容
def filter_example_content(example_content, keys):
return {k: v for k, v in example_content.items() if k in keys}
filtered_example_content1 = filter_content(example_content1, sorted_keys)
filtered_example_content2 = filter_content(example_content2, sorted_keys)
def format_example(example_content):
return json.dumps(example_content, indent=4, ensure_ascii=False)
# 将过滤后的示例转换为格式化的 JSON 字符串
json_example1_str = json.dumps(filtered_example_content1, indent=4, ensure_ascii=False)
json_example2_str = json.dumps(filtered_example_content2, indent=4, ensure_ascii=False)
# 从文件中读取内容
# full_text = read_txt_to_string(processed_filepath)
filtered_example_content1 = filter_example_content(example_content1, sorted_keys)
filtered_example_content2 = filter_example_content(example_content2, sorted_keys)
tech_json_example1_str = format_example(filtered_example_content1)
tech_json_example2_str = format_example(filtered_example_content2)
keys_str = ''.join(sorted_keys)
outer_keys_str = ', '.join([f"'{key}'" for key in sorted_keys])
prompt_instruction = generate_prompt_instruction(keys_str, outer_keys_str, another_keys_str, type)
# 完整的用户查询模板,包含两份示例输出
user_query_template = f"""
{prompt_instruction}
以下为示例输出仅供格式参考
示例 1
{json_example1_str}
示例 2
{json_example2_str}
"""
# 文本内容:{full_text}
{prompt_instruction}
以下为示例输出仅供格式参考
示例 1,无嵌套键值对
{tech_json_example1_str}
示例 2嵌套键值对形式
{tech_json_example2_str}
"""
return user_query_template
def merge_requirements(input_dict):
# 初始化一个临时字典,用于存储标准化后的键
temp_dict = {}
# 初始化最终字典,只包含指定的四个键
final_keys = ['技术要求', '商务要求', '服务要求', '其他要求']
final_dict = {key: "" for key in final_keys}
# 如果输入字典中有'其他要求',保留其内容
if '其他要求' in temp_dict and temp_dict['其他要求'].strip():
final_dict['其他要求'] = temp_dict['其他要求'].strip()
# 处理'技术要求', '商务要求', '服务要求'
for key in ['技术要求', '商务要求', '服务要求']:
if key in temp_dict:
final_dict[key] = temp_dict[key].strip()
# 收集需要合并到'其他要求'的内容
merge_keys = ['总体要求', '进度要求', '培训要求']
merged_contents = []
for key in merge_keys:
if key in temp_dict and temp_dict[key].strip():
merged_contents.append(temp_dict[key].strip())
# 如果有需要合并的内容
if merged_contents:
merged_text = " ".join(merged_contents)
if final_dict['其他要求']:
final_dict['其他要求'] += " " + merged_text
else:
final_dict['其他要求'] = merged_text
# 移除多余的空格
for key in final_dict:
final_dict[key] = final_dict[key].strip()
return final_dict
#,"总\s*体\s*要\s*求","进\s*度\s*要\s*求","培\s*训\s*要\s*求"
def get_business_requirements(procurement_path,processed_filepath):
file_id=upload_file(procurement_path)
required_keys = ["\s*术\s*要\s*求","\s*务\s*要\s*求", "\s*务\s*要\s*求", "\s*他\s*要\s*求"]
contained_keys=find_exists(procurement_path,required_keys)
def get_business_requirements(procurement_path,procurement_docpath):
file_id = upload_file(procurement_docpath)
print(file_id)
required_keys = ["\s*术\s*要\s*求", "\s*务\s*要\s*求", "\s*务\s*要\s*求", "\s*他\s*要\s*求","\s*体\s*要\s*求","\s*设\s*要\s*求","\s*度\s*要\s*求","\s*期\s*要\s*求","\s*保\s*要\s*求","\s*训\s*要\s*求","\s*后\s*要\s*求"]
contained_keys = find_exists(procurement_path, required_keys)
print(contained_keys)
if not contained_keys:
return {}
# queries = generate_queries(truncate_file, contained_keys)
user_query=generate_user_query_template(contained_keys,processed_filepath)
# print(user_query)
model_res=qianwen_long_stream(file_id,user_query)
# model_res=doubao_model(user_query)
# Combine and fill missing keys with default values
final_res = clean_json_string(model_res)
# final_res.update({key: final_res.get(key, "") for key in required_keys})
busi_user_query = generate_template(contained_keys, 1)
tech_user_query = generate_template(contained_keys, 2)
final_res={}
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
futures = []
if busi_user_query:
futures.append(executor.submit(qianwen_long_stream, file_id, busi_user_query, 2, 1))
if tech_user_query:
futures.append(executor.submit(qianwen_long_stream, file_id, tech_user_query, 2, 1))
# 获取结果
for future in concurrent.futures.as_completed(futures):
try:
result = future.result()
if result: # 确保结果不为空
final_res.update(clean_json_string(result))
except Exception as e:
print(f"An error occurred: {e}")
return final_res
#TODO:改为先判断,再摘取
# TODO:改为先判断,再摘取
if __name__ == "__main__":
# truncate_file = "C:\\Users\\Administrator\\Desktop\\fsdownload\\e4be098d-b378-4126-9c32-a742b237b3b1\\ztbfile_procurement.docx"
truncate_file=r"C:\Users\Administrator\Desktop\fsdownload\fa0d51a1-0d63-4c0d-9002-cf8ac3f2211a\ztbfile_procurement.pdf"
truncate_file = r"C:\Users\Administrator\Desktop\货物标\output1\陕西省公安厅交通警察总队高速公路交通安全智能感知巡查系统项目 (1)_procurement.pdf"
docx_path=r'C:\Users\Administrator\Desktop\货物标\output1\陕西省公安厅交通警察总队高速公路交通安全智能感知巡查系统项目 (1)_procurement.docx'
# truncate_file=r"C:\Users\Administrator\Desktop\new招标文件\output5\HBDL-2024-0519-001-招标文件_procurement.pdf"
# file_id = upload_file(truncate_file)
processed_filepath = pdf2txt(truncate_file)
res=get_business_requirements(truncate_file,processed_filepath)
print(json.dumps(res, ensure_ascii=False, indent=4))
final_res= get_business_requirements(truncate_file, docx_path)
print(json.dumps(final_res, ensure_ascii=False, indent=4))

View File

@ -290,151 +290,131 @@ def combine_and_update_results(original_data, updates):
return original_data
# user_query1 = """
# 请你首先定位该采购文件中的采购清单或采购需求部分,请告诉我需要采购的货物,如果有采购清单,请直接根据清单上的货物(或系统)名称给出结果,注意不要返回'说明'或'规格'或'技术参数'列中的内容若没有采购清单你要从表格中或文中摘取需要采购的系统和货物采购需求中可能包含层次关系例如采购的某系统中可能包含几种货物那么你需要用嵌套键值对表示这种关系且不要遗漏该系统中包含的货物你的输出请以json格式返回最外层键名为'采购需求',嵌套键名为对应的系统名称或货物名称,需与原文保持一致,无需给出采购数量和单位。以下为需要考虑的特殊情况:如果采购清单中同一层级(或同一系统)下存在同名货物且它们的采购要求有所不同,请你以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,例如若采购清单中有两种型号的'交换机',那么你应返回两个键名,'交换机-1'和'交换机-2';如有未知内容,在对应键值处填'未知'。以下为考虑了特殊情况的示例输出:
# {
# "采购需求": {
# "交换机-1"{},
# "交换机-2":{},
# "门禁管理系统": {},
# "交通监控视频子系统": {
# "高清视频抓拍像机":{},
# "补光灯":{}
# },
# "LED全彩显示屏": {}
# }
# }
# """
#文件内容以markdown格式组织其中表格部分若有以html语法组织
def get_technical_requirements(file_path,invalid_path,processed_filepath):
docx_file_path=pdf2docx(file_path)
file_id=upload_file(docx_file_path)
# docx_file_path=pdf2docx(file_path)
file_id=upload_file(file_path) #目前传入的为docx文档
first_query_template="该文件是否说明了采购需求,即需要采购哪些货物?如果有,请回答'',否则,回答''" #防止截取失败
judge_res=qianwen_long(file_id,first_query_template)
prompt_template1 = '''
任务解析采购文件提取采购需求并以JSON格式返回
任务解析采购文件提取采购需求并以JSON格式返回
要求与指南
1. 精准定位请运用文档理解能力找到文件中的采购需求部分若有采购清单请直接根据采购清单上的货物或系统名称给出结果
2. 采购目标采购目标通常有硬件如设备货物和软件如系统软件应用APP一次采购活动可能同时包含这两种类型对于工程类的施工建设采购需求无需提取
3. 非清单形式处理若未出现采购清单则从表格或文字中摘取采购信息
4. 系统归属一些采购活动可能将采购目标划分为若干系统和货物每个系统可能包含若干货物则将这些货物名称作为该系统的二级键系统可以只包含总体'系统功能'而无货物
5. 软件需求对于软件应用或系统软件需求仅需列出系统模块构成若有并作为系统键值的一部分无需在模块下再细分功能
6. 系统功能若采购的某系统提及总体系统功能则在系统值中添加'系统功能'二级键不展开具体内容
7. 完整性确保不遗漏系统内的货物也不添加未提及的内容
要求与指南
1. 精准定位请运用文档理解能力找到文件中的采购需求部分若有采购清单请直接根据采购清单上的货物或系统名称给出结果若未出现采购清单则从表格或文字中摘取采购信息
2. 采购目标采购种类通常有硬件如设备货物和软件如系统软件应用APP一次采购活动可以同时包含这两种类型
3. 系统归属一些采购活动可能将采购目标划分为若干系统和货物每个系统可能包含若干货物则将这些货物名称作为该系统的二级键系统可以只包含总体'系统功能'而无货物
4. 软件需求对于软件应用或系统软件需求仅需列出系统模块构成若有并作为系统键值的一部分无需在模块下再细分功能
5. 系统功能若采购的某系统提及总体系统功能则在系统值中添加'系统功能'二级键不展开具体内容
6. 完整性确保不遗漏系统内的货物也不添加未提及的内容若采购清单之外有额外的货物采购要求且该货物暂未提取至JSON回答中请将这些货物名称也包含进来
输出格式
1.JSON格式最外层键名为'采购需求'
2.层次关系用嵌套键值对表示
3.嵌套键名为系统或货物或模块名称与原文保持一致
4.最内层键值应为空列表[]
5.不包含'说明''规格''技术参数'等列内容仅返回采购的货物或系统或模块名称
输出格式
1.JSON格式最外层键名为'采购需求'
2.层次关系用嵌套键值对表示
3.嵌套键名为系统或货物或模块名称与原文保持一致
4.最内层键值应为空列表[]
5.不包含'说明''规格''技术参数'等列内容仅返回采购的货物或系统或模块名称
特殊情况处理
同一层级如同一系统中下同名但采购要求不同的货物'货物名-编号'区分编号从1递增例如若同层级下存在两种型号的交换机那么命名分别是'交换机-1''交换机-2'以规避重复键名否则无需在名称后添加编号
特殊情况处理
同一层级如同一系统中下同名但采购要求不同的货物'货物名-编号'区分编号从1递增例如若同层级下存在两种型号的交换机那么命名分别是'交换机-1''交换机-2'以规避重复键名否则无需在名称后添加编号
{{
"采购需求": {{
"交换机-1": [],
"交换机-2": [],
"门禁管理系统": {{
"系统功能":[]
}},
"交通监控视频子系统": {{
"系统功能": [],
"高清视频抓拍像机": [],
"补光灯": []
}},
"LED全彩显示屏": []
// 其他系统和货物
}}
}}
示例输出2系统软件采购
{{
"采购需求": {{
"信息管理系统": {{
"通用模块":[],
"用户管理":[]
}},
"信息检索系统": {{
"系统功能":[],
"权限管理模块":[]
}},
"XX小程序":[],
"数据分析中心":[]
}}
}}
{{
"采购需求": {{
"交换机-1": [],
"交换机-2": [],
"门禁管理系统": {{
"系统功能":[]
}},
"交通监控视频子系统": {{
"系统功能": [],
"高清视频抓拍像机": [],
"补光灯": []
}},
"LED全彩显示屏": []
// 其他系统和货物
}}
}}
示例输出2系统软件采购
{{
"采购需求": {{
"信息管理系统": {{
"通用模块":[],
"用户管理":[]
}},
"信息检索系统": {{
"系统功能":[],
"权限管理模块":[]
}},
"XX小程序":[],
"数据分析中心":[]
}}
}}
注意事项
1.严格按照上述要求执行确保输出准确性和规范性
2.如有任何疑问或不确定内容请保留原文描述必要时使用'未知'标注
'''
注意事项
1.严格按照上述要求执行确保输出准确性和规范性
2.如有任何疑问或不确定内容请保留原文描述必要时使用'未知'标注
'''
prompt_template2 = '''
任务解析采购文件提取采购需求并以JSON格式返回
任务你负责解析采购文件提取采购需求并以JSON格式返回不要遗漏该项目需要采购的货物或系统
要求与指南
1. 精准定位请运用文档理解能力找到文件中的采购需求部分若有采购清单请直接根据采购清单上的货物或系统名称给出结果
2. 采购目标采购目标通常有硬件如设备货物和软件如系统软件应用APP一次采购活动可能同时包含这两种类型对于工程类的施工建设采购需求无需提取
3. 非清单形式处理若未出现采购清单则从表格或文字中摘取采购信息
4. 系统归属一些采购活动可能将采购目标划分为若干系统和货物每个系统可能包含若干货物则将这些货物名称作为该系统的二级键系统可以只包含总体'系统功能'而无货物
5. 软件需求对于软件应用或系统软件采购若有多个系统且序号分明请不要遗漏最多仅需列出系统模块构成若有并作为该系统键值的一部分无需在模块下再细分功能
6. 系统功能若采购的某系统提及总体系统功能则在系统值中添加'系统功能'二级键不展开具体内容
7. 完整性确保不遗漏系统内的货物也不添加未提及的内容
要求与指南
1. 精准定位请运用文档理解能力定位文件中的采购需求部分若有采购清单请直接根据采购清单上的货物或系统名称给出结果注意你无需提取诸如'说明''规格''技术参数''描述'等列的内容即你不需要给出详细的采购要求仅返回采购的货物或系统或模块名称若没有采购清单则从表格或文本中摘取采购信息
2. 采购目标采购种类通常有硬件如设备货物和软件如系统软件应用APP一次采购活动可以同时包含这两种类型
3. 系统归属一些采购活动可能将采购目标划分为若干系统和货物每个系统可能包含若干货物则将这些货物名称作为该系统的二级键注意这种包含关系是通过表格结构或文档标题层次来得出的系统可以只包含总体'系统功能'而无货物
5. 软件需求对于软件应用或系统软件采购若有多个系统且序号分明请不要遗漏最多仅需列出系统模块构成若有并作为该系统键值的一部分无需在模块下再细分功能
5. 系统功能若采购的某系统提及总体系统功能则在系统值中添加'系统功能'二级键不展开具体内容
6. 完整性确保不遗漏系统内的货物也不添加未提及的内容'采购清单'中未提取的货物或系统名称在形如'主要设备功能指标'的标题下有详细参数指标要求请将该货物名也添加至返回中
输出格式
1.JSON格式最外层键名为'采购需求'
2.层次关系用嵌套键值对表示
3.嵌套键名为系统或货物或模块名称与原文保持一致
4.最内层键值应为空列表[]
5.不包含'说明''规格''技术参数'等列内容仅返回采购的货物或系统或模块名称
输出格式
1.JSON格式最外层键名为'采购需求'
2.层次关系用嵌套键值对表示
3.嵌套键名为系统或货物或模块名称与原文保持一致
4.最内层键值应为空列表[]
特殊情况处理
若同一层级如同一系统中下存在同名但采购要求不同的货物'货物名-编号'区分编号从1递增例如若同层级下存在两种型号的交换机那么命名分别是'交换机-1''交换机-2'以规避重复键名否则无需在名称后添加编号
特殊情况处理
若同一层级如同一系统中下存在同名但采购要求不同的货物'货物名-编号'区分编号从1递增例如若同层级下存在两种型号的交换机那么命名分别是'交换机-1''交换机-2'以规避重复键名否则无需在名称后添加编号
示例输出1普通系统货物类采购
{{
"采购需求": {{
"交换机-1": [],
"交换机-2": [],
"门禁管理系统": {{
"系统功能":[]
}},
"交通监控视频子系统": {{
"系统功能": [],
"交换机":[],
"高清视频抓拍像机": [],
"补光灯": []
}},
"LED全彩显示屏": []
// 其他系统和货物
}}
}}
示例输出2系统软件采购
{{
"采购需求": {{
"信息管理系统": {{
"通用模块":[],
"用户管理":[]
}},
"信息检索系统": {{
"系统功能":[],
"权限管理模块":[]
}},
"XX管理系统":[],
"XX管理系统":[]
}}
}}
示例输出1普通系统货物类采购
{{
"采购需求": {{
"交换机-1": [],
"交换机-2": [],
"门禁管理系统": {{
"系统功能":[]
}},
"交通监控视频子系统": {{
"系统功能": [],
"交换机":[],
"高清视频抓拍像机": [],
"补光灯": []
}},
"LED全彩显示屏": []
// 其他系统和货物
}}
}}
示例输出2软件系统类采购
{{
"采购需求": {{
"信息管理系统": {{
"通用模块":[],
"用户管理":[]
}},
"信息检索系统": {{
"系统功能":[],
"权限管理模块":[]
}},
"XX管理系统":[],
//其他系统
}}
}}
文件内容{full_text}
文件内容{full_text}
注意事项
1.严格按照上述要求执行确保输出准确性和规范性
2.如有任何疑问或不确定内容请保留原文描述必要时使用'未知'标注
'''
注意事项
1.严格按照上述要求执行确保输出准确性和规范性
'''
if '' in judge_res:
print("no!调用invalid_path")
file_id=upload_file(invalid_path)
print("调用invalid_path")
model_res=qianwen_long(file_id,prompt_template1)
print(model_res)
else:
@ -444,7 +424,6 @@ def get_technical_requirements(file_path,invalid_path,processed_filepath):
model_res=doubao_model(user_query)
# model_res = qianwen_long(file_id,prompt_template1)
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:{'交通标志.标志牌铝板', '交通信号灯.交换机'}
@ -571,16 +550,17 @@ def test_all_files_in_folder(input_folder, output_folder):
if __name__ == "__main__":
start_time=time.time()
# truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\469d2aee-9024-4993-896e-2ac7322d41b7\\ztbfile_procurement.docx"
truncate_file=r"C:\Users\Administrator\Desktop\fsdownload\5950ad84-30c8-4643-b6de-b13ef5be7a5c\ztbfile.pdf"
truncate_docfile=r"C:\Users\Administrator\Desktop\货物标\output1\招标文件(107国道)_procurement.docx"
truncate_file=r'C:\Users\Administrator\Desktop\fsdownload\e702f1e6-095d-443d-bb7d-ef2e42037cb1\ztbfile_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"
invalid_path=r"C:\Users\Administrator\Desktop\fsdownload\a110ed59-00e8-47ec-873a-bd4579a6e628\ztbfile.pdf"
# file_id=upload_file(truncate_file)
# processed_filepath = pdf2txt(truncate_file)
processed_filepath=r"C:\Users\Administrator\Desktop\fsdownload\5950ad84-30c8-4643-b6de-b13ef5be7a5c\tmp\extract1.txt"
res=get_technical_requirements(truncate_file,invalid_path,processed_filepath)
processed_filepath = pdf2txt(truncate_file)
# processed_filepath=r"C:\Users\Administrator\Desktop\fsdownload\e702f1e6-095d-443d-bb7d-ef2e42037cb1\金水河沿线排涝泵站提档升级项目.txt"
res=get_technical_requirements(truncate_docfile,invalid_path,processed_filepath)
json_string = json.dumps(res, ensure_ascii=False, indent=4)
print(json_string)
# # input_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\output1"

View File

@ -4,14 +4,15 @@ import time
from flask_app.general.doubao import pdf2txt
from flask_app.general.file2markdown import convert_pdf_to_markdown
from flask_app.general.format_change import pdf2docx
from flask_app.货物标.技术参数要求提取 import get_technical_requirements
from flask_app.general.通义千问long import upload_file
from flask_app.货物标.商务服务其他要求提取 import get_business_requirements
#获取采购清单
# 获取采购清单
def fetch_procurement_reqs(procurement_path, invalid_path):
# procurement_docpath = pdf2docx(procurement_path) # 采购需求docx
procurement_docpath = pdf2docx(procurement_path) # 采购需求docx
# 定义默认的 procurement_reqs 字典
DEFAULT_PROCUREMENT_REQS = {
"采购需求": "",
@ -26,14 +27,16 @@ def fetch_procurement_reqs(procurement_path, invalid_path):
return DEFAULT_PROCUREMENT_REQS.copy()
try:
# processed_filepath = convert_pdf_to_markdown(procurement_path) # 转markdown格式
# processe:d_filepath = convert_pdf_to_markdown(procurement_path) # 转markdown格式
processed_filepath = pdf2txt(procurement_path) # 纯文本提取
# 使用 ThreadPoolExecutor 并行处理 get_technical_requirements 和 get_business_requirements
with concurrent.futures.ThreadPoolExecutor() as executor:
# 提交任务给线程池
future_technical = executor.submit(get_technical_requirements, procurement_path, invalid_path,processed_filepath)
future_technical = executor.submit(get_technical_requirements, procurement_docpath, invalid_path,
processed_filepath)
time.sleep(0.5) # 保持原有的延时
future_business = executor.submit(get_business_requirements, procurement_path,processed_filepath)
future_business = executor.submit(get_business_requirements, procurement_path, procurement_docpath)
# 获取并行任务的结果
technical_requirements = future_technical.result()
@ -44,8 +47,6 @@ def fetch_procurement_reqs(procurement_path, invalid_path):
"采购需求": technical_requirements.get("采购需求", {})
}
# 合并 business_requirements 到 procurement_reqs 中
# 这样无论 business_requirements 包含什么键(如 "技术要求"、"服务要求" 或 "技术、服务要求"),都将被保留
procurement_reqs.update(business_requirements)
# 如果需要确保所有默认键存在,可以取消下面的注释
@ -60,16 +61,17 @@ def fetch_procurement_reqs(procurement_path, invalid_path):
return DEFAULT_PROCUREMENT_REQS.copy()
#TODO:技术要求可以在技术参数之后执行,把完整的技术参数输入,问大模型,除了上述内容还有哪些,这样的话把技术标和其他的区分开。
#TODO: 094有问题
# TODO:技术要求可以在技术参数之后执行,把完整的技术参数输入,问大模型,除了上述内容还有哪些,这样的话把技术标和其他的区分开。
# TODO:0362
if __name__ == "__main__":
start_time=time.time()
start_time = time.time()
output_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\货物标output"
# file_path="C:\\Users\\Administrator\\Desktop\\货物标\\output1\\2-招标文件2020年广水市中小学教师办公电脑系统及多媒体“班班通”设备采购安装项目_procurement.pdf"
procurement_path = r"C:\Users\Administrator\Desktop\货物标\output1\招标文件(107国道)_procurement.pdf"
procurement_docpath=r"C:\Users\Administrator\Desktop\fsdownload\fa0d51a1-0d63-4c0d-9002-cf8ac3f2211a"
invalid_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\db79e9e0-830e-442c-8cb6-1d036215f8ff\\ztbfile.pdf"
res=fetch_procurement_reqs(procurement_path,invalid_path)
procurement_path = r"C:\Users\Administrator\Desktop\货物标\output1\陕西省公安厅交通警察总队高速公路交通安全智能感知巡查系统项目 (1)_procurement.pdf"
procurement_docpath = r"C:\Users\Administrator\Desktop\fsdownload\fa0d51a1-0d63-4c0d-9002-cf8ac3f2211a"
invalid_path = "C:\\Users\\Administrator\\Desktop\\fsdownload\\db79e9e0-830e-442c-8cb6-1d036215f8ff\\ztbfile.pdf"
res = fetch_procurement_reqs(procurement_path, invalid_path)
print(json.dumps(res, ensure_ascii=False, indent=4))
end_time=time.time()
print("耗时:"+str(end_time-start_time))
end_time = time.time()
print("耗时:" + str(end_time - start_time))

View File

@ -225,7 +225,14 @@ def combine_evaluation_standards(truncate_file):
user_query = (
"""
根据该文档中的评分标准表格中的内容请你列出该文件的技术评分商务评分投标报价评审以及它们对应的具体评分要求请以json格式返回结果最外层键名分别是'技术评分','商务评分','投标报价评分',请在这三大项评分中分别用若干键值对表示具体评分项外层键名为各评审因素键值为一个列表列表中包含若干可为一描述该评审因素的评分及要求的字典内层键名分别是'评分''要求'若无评分可删去'评分'键值对'要求'中说明了该评审因素的评分标准若这三大项评分中存在其他信息则在相应评分大块内部新增键名'备注'存放该信息键值为具体的要求否则不需要如果评分内容因素不是这三大项则返回文档中给定的评分内容因素以及它们的具体评分要求
以下为需要考虑的注意事项1.不要回答有关资格审查符合性审查的内容也不要从评标办法正文中表格外提取回答 2.若大项的'xx评分'要求未在文中说明则键名'xx评分'的键值设为'本项目无xx评分项'例如"技术评分":"本项目无技术评分项" 3. 如果该招标活动有多个包则最外层键名为对应的包名,否则不需要 4.你无需将表格的单元格内的内容进行拆分需要将它视为一个整体以下为示例输出仅供格式参考
要求与指南
1.请首先定位评分细则的表格不要回答有关资格审查符合性审查的内容也不要从评标办法正文中表格外提取回答
2.若大项的'xx评分'要求未在文中说明则键名'xx评分'的键值设为'本项目无xx评分项'例如"技术评分":"本项目无技术评分项"
3. 如果该招标活动有多个包则最外层键名为对应的包名,否则不需要
4.你无需将表格的单元格内的内容进行拆分需要将它视为一个整体
5. '评分'的键值不能是一个范围数字'0-5分'应该是一个具体数字'5分'或者是一个定性的指标如'合格制'
以下为示例输出仅供格式参考
{
"一包": {
"技术评分": {

View File

@ -213,6 +213,7 @@ def post_process_baseinfo(base_info,logger):
logger.error(f"Error in post_process_baseinfo: {e}")
return base_info, [] # 返回空列表
#TODO:错误处理通过返回值completion来错误处理而不是正则表达 学习装饰器、 整体后处理
def goods_bid_main(output_folder, file_path, file_type, unique_id):
logger = get_global_logger(unique_id)
# 预处理文件,获取处理后的数据