diff --git a/Dockerfile b/Dockerfile index 5206337..85f5431 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,4 +29,4 @@ ENV ALIBABA_CLOUD_ACCESS_KEY_SECRET=88oyw7LniqV8i0SnOuSFS5lprfrPtw EXPOSE 5000 # 在容器启动时运行你的应用 -CMD ["python", "flask_app/main/start_up.py"] +CMD ["python", "flask_app/start_up.py"] diff --git a/flask_app/general/post_processing.py b/flask_app/general/post_processing.py index 97c8790..f109edb 100644 --- a/flask_app/general/post_processing.py +++ b/flask_app/general/post_processing.py @@ -69,7 +69,7 @@ def extract_business_requirements(data): counter += 1 business_requirements_string = json.dumps(new_data, ensure_ascii=False, indent=4) - print(business_requirements_string) + # print(business_requirements_string) prompt_template1 = """请帮我从以下文本中摘取商务要求部分,并将信息重新组织,外键名为'商务要求',键值为字符串列表,其中每个字符串为一条商务要求,去除开头的序号(若有)。 #角色 你是一个专业的招投标业务专家,擅长从招标文件中总结商务要求的部分,并逐条列出,作为编写商务要求偏离表的前置准备。 @@ -415,7 +415,7 @@ def outer_post_processing(combined_data, includes, good_list): extracted_info = {} # 初始化采购要求的技术要求 - procurement_reqs = "" + tech_deviation = "" zige_info="" fuhe_info="" @@ -430,10 +430,10 @@ def outer_post_processing(combined_data, includes, good_list): tech_requirements = get_nested(base_info, ["采购要求", "采购需求"], {}) if tech_requirements: - procurement_reqs = extract_matching_keys(tech_requirements, good_list) + tech_deviation = extract_matching_keys(tech_requirements, good_list) else: # 如果 '技术要求' 不存在或为空,可以根据需要设置默认值 - procurement_reqs = "未提供" + tech_deviation = "未提供" if "资格审查" in includes: zige_review = combined_data.get("资格审查", {}) @@ -452,9 +452,9 @@ def outer_post_processing(combined_data, includes, good_list): }, ensure_ascii=False, indent=4) except KeyError as e: print(f"缺少关键字: {e}") - # tech_star_deviation=get_tech_star_deviation(json.dumps(procurement_reqs,ensure_ascii=False,indent=4)) - # business_req_deviation = extract_business_requirements(get_nested(combined_data, ['基础信息'], {})) - # zige_deviation,fuhe_deviation=extract_zige_deviation_table(zige_info,fuhe_info) + tech_star_deviation=get_tech_star_deviation(json.dumps(tech_deviation,ensure_ascii=False,indent=4)) + business_deviation,business_star_deviation = extract_business_requirements(get_nested(combined_data, ['基础信息'], {})) + zige_deviation,fuhe_deviation=extract_zige_deviation_table(zige_info,fuhe_info) # 遍历原始字典的每一个键值对 for key, value in combined_data.items(): @@ -473,7 +473,7 @@ def outer_post_processing(combined_data, includes, good_list): if not processed_data["其他"]: del processed_data["其他"] - return processed_data, extracted_info, procurement_reqs + return processed_data, extracted_info, tech_deviation,tech_star_deviation,business_deviation,business_star_deviation,zige_deviation,fuhe_deviation if __name__ == "__main__": diff --git a/flask_app/general/多线程提问.py b/flask_app/general/多线程提问.py index 747ac5d..45e3bcc 100644 --- a/flask_app/general/多线程提问.py +++ b/flask_app/general/多线程提问.py @@ -130,13 +130,13 @@ def rag_assistant(knowledge_name): #TODO:http格式,有bug还没修改 def create_assistant(knowledge_name): """ - Create an assistant using DashScope API via HTTP request based on the provided knowledge name. + Create an assistant using DashScope routes via HTTP request based on the provided knowledge name. Parameters: knowledge_name (str): The name of the knowledge base to associate with the assistant. Returns: - dict: Response from the API containing assistant details. + dict: Response from the routes containing assistant details. Raises: ValueError: If the DASHSCOPE_API_KEY environment variable is not set. @@ -150,12 +150,12 @@ def create_assistant(knowledge_name): print(f"Error retrieving pipeline ID for knowledge '{knowledge_name}': {e}") return None - # Step 2: Fetch the API Key from Environment Variables + # Step 2: Fetch the routes Key from Environment Variables api_key = os.getenv("DASHSCOPE_API_KEY") if not api_key: raise ValueError("DASHSCOPE_API_KEY environment variable is not set.") - # Step 3: Define the API Endpoint and Headers + # Step 3: Define the routes Endpoint and Headers url = 'https://dashscope.aliyuncs.com/api/v1/assistants' headers = { "Content-Type": "application/json", @@ -258,7 +258,7 @@ def multi_threading(queries, knowledge_name="", file_id="", llm_type=1): result_queue = queue.Queue() max_retries = 2 # 设置最大重试次数 # 使用 ThreadPoolExecutor 管理线程 - with concurrent.futures.ThreadPoolExecutor(max_workers=60) as executor: + with concurrent.futures.ThreadPoolExecutor(max_workers=30) as executor: # 逐个提交任务,每提交一个任务后休眠1秒 future_to_query = {} for index, query in enumerate(queries): diff --git a/flask_app/general/纯技术参数要求提取.py b/flask_app/general/纯技术参数要求提取.py index 60c6484..b975582 100644 --- a/flask_app/general/纯技术参数要求提取.py +++ b/flask_app/general/纯技术参数要求提取.py @@ -39,17 +39,16 @@ def get_technical_requirements_main(file_path,file_type,unique_id,output_folder) # file_id=upload_file(truncate_file) final_res=get_technical_requirements(truncate_file,pdf_path) # 安全地提取 "技术要求" 内部的字典内容 - if isinstance(final_res, dict) and '技术要求' in final_res and isinstance(final_res['技术要求'], dict): - technical_requirements = final_res['技术要求'] + if isinstance(final_res, dict) and '采购需求' in final_res and isinstance(final_res['采购需求'], dict): + technical_requirements = final_res['采购需求'] good_list = technical_requirements.pop('货物列表', []) # 如果 '货物列表' 不存在,返回 [] - print(good_list) logger.info("Collected good_list from the processing function: %s", good_list) return extract_matching_keys(technical_requirements,good_list) else: return final_res if __name__ == "__main__": - file_path="C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\包头市公安支队机动车查验监管系统招标文201907.pdf" + file_path=r"C:\Users\Administrator\Desktop\货物标\zbfiles\2-招标文件(广水市教育局封闭管理).pdf" file_type=2 - output_folder = "C:\\Users\\Administrator\\Desktop\\fsdownload\\45f650ce-e519-457b-9ad6-5840e2ede539\\tmp" + output_folder = r"C:\Users\Administrator\Desktop\fsdownload\39b0c3b4-1807-456c-8330-c5c7d1b7a2ca\tmp" res=get_technical_requirements_main(file_path,file_type,"123",output_folder) print(json.dumps(res,ensure_ascii=False,indent=4)) diff --git a/flask_app/general/通义千问long.py b/flask_app/general/通义千问long.py index a19af39..9f47a65 100644 --- a/flask_app/general/通义千问long.py +++ b/flask_app/general/通义千问long.py @@ -81,7 +81,7 @@ def qianwen_long_text(file_id, user_query): return completion.choices[0].message.content def qianwen_long_stream(file_id, user_query): - print("调用 qianwen-long text...") + print("调用 qianwen-long stream...") """ 使用之前上传的文件,根据用户查询生成响应,并实时显示流式输出。 """ @@ -93,7 +93,7 @@ def qianwen_long_stream(file_id, user_query): # 生成基于文件ID的响应 completion = client.chat.completions.create( model="qwen-long", - temperature=0.5, + temperature=0.4, messages=[ { 'role': 'system', diff --git a/flask_app/old_version/文档理解大模型版知识库处理/删除知识库.py b/flask_app/old_version/文档理解大模型版知识库处理/删除知识库.py index 90aded4..fa48cdd 100644 --- a/flask_app/old_version/文档理解大模型版知识库处理/删除知识库.py +++ b/flask_app/old_version/文档理解大模型版知识库处理/删除知识库.py @@ -28,7 +28,7 @@ def delete_index(client: bailian20231229Client, workspace_id: str, index_id: str headers = {} try: response = client.delete_index_with_options(workspace_id, delete_index_request, headers, runtime) - print("API Response:", response) + print("routes Response:", response) except Exception as error: print(error.message) print(error.data.get("Recommend")) @@ -42,7 +42,7 @@ async def delete_index_async(client: bailian20231229Client, workspace_id: str, i headers = {} try: response = await client.delete_index_with_options_async(workspace_id, delete_index_request, headers, runtime) - print("API Response:", response) + print("routes Response:", response) except Exception as error: print(error.message) print(error.data.get("Recommend")) diff --git a/flask_app/old_version/文档理解大模型版知识库处理/调用文件解析状态查询.py b/flask_app/old_version/文档理解大模型版知识库处理/调用文件解析状态查询.py index 8534aaf..6a5547e 100644 --- a/flask_app/old_version/文档理解大模型版知识库处理/调用文件解析状态查询.py +++ b/flask_app/old_version/文档理解大模型版知识库处理/调用文件解析状态查询.py @@ -22,7 +22,7 @@ def query(): id='docmind-20241008-24363df30d274863894f037dbb7244e8' ) try: - # 复制代码运行请自行打印 API 的返回值 + # 复制代码运行请自行打印 routes 的返回值 response = client.query_doc_parser_status(request) # API返回值格式层级为 body -> data -> 具体属性。可根据业务需要打印相应的结果。获取属性值均以小写开头 # 获取返回结果。建议先把response.body.data转成json,然后再从json里面取具体需要的值。 diff --git a/flask_app/old_version/文档理解大模型版知识库处理/调用文档解析异步提交.py b/flask_app/old_version/文档理解大模型版知识库处理/调用文档解析异步提交.py index 06b1c7f..2088e74 100644 --- a/flask_app/old_version/文档理解大模型版知识库处理/调用文档解析异步提交.py +++ b/flask_app/old_version/文档理解大模型版知识库处理/调用文档解析异步提交.py @@ -29,7 +29,7 @@ def submit_file(): ) runtime = util_models.RuntimeOptions() try: - # 复制代码运行请自行打印 API 的返回值 + # 复制代码运行请自行打印 routes 的返回值 response = client.submit_doc_parser_job_advance(request, runtime) # API返回值格式层级为 body -> data -> 具体属性。可根据业务需要打印相应的结果。如下示例为打印返回的业务id格式 # 获取属性值均以小写开头, diff --git a/flask_app/old_version/文档理解大模型版知识库处理/调用文档解析结果获取.py b/flask_app/old_version/文档理解大模型版知识库处理/调用文档解析结果获取.py index 47dd295..21b85f8 100644 --- a/flask_app/old_version/文档理解大模型版知识库处理/调用文档解析结果获取.py +++ b/flask_app/old_version/文档理解大模型版知识库处理/调用文档解析结果获取.py @@ -24,7 +24,7 @@ def query(): layout_num=0 ) try: - # 复制代码运行请自行打印 API 的返回值 + # 复制代码运行请自行打印 routes 的返回值 response = client.get_doc_parser_result(request) # API返回值格式层级为 body -> data -> 具体属性。可根据业务需要打印相应的结果。获取属性值均以小写开头 # 获取返回结果。建议先把response.body.data转成json,然后再从json里面取具体需要的值。 diff --git a/flask_app/old_version/转化格式/pdf2doc.py b/flask_app/old_version/转化格式/pdf2doc.py index 7073049..ff48841 100644 --- a/flask_app/old_version/转化格式/pdf2doc.py +++ b/flask_app/old_version/转化格式/pdf2doc.py @@ -4,7 +4,7 @@ import os def convert_pdf_to_word(file_path, output_dir, output_format='docx'): """ - Converts a PDF file to a Word document using a specified API. + Converts a PDF file to a Word document using a specified routes. :param file_path: Path to the PDF file to convert. :param output_dir: Directory to save the converted Word document. diff --git a/flask_app/main/start_up.py b/flask_app/start_up.py similarity index 91% rename from flask_app/main/start_up.py rename to flask_app/start_up.py index fbbaad8..375c250 100644 --- a/flask_app/main/start_up.py +++ b/flask_app/start_up.py @@ -403,17 +403,70 @@ def process_and_stream(file_url, zb_type): output_json_path = os.path.join(output_folder, 'final_result.json') extracted_info_path = os.path.join(output_folder, 'extracted_result.json') includes = ["基础信息", "资格审查", "商务评分", "技术评分", "无效标与废标项", "投标文件要求", "开评定标流程"] - final_result, extracted_info, procurement_reqs = outer_post_processing(combined_data, includes, good_list) + final_result, extracted_info,tech_deviation,tech_star_deviation,business_deviation,business_star_deviation,zige_deviation,fuhe_deviation = outer_post_processing(combined_data, includes, good_list) logger.info( - f"Procurement requirements extracted: {json.dumps(procurement_reqs, ensure_ascii=False, indent=4)}") # 添加日志记录 + f"技术偏离表: {json.dumps(tech_deviation, ensure_ascii=False, indent=4)}" + ) # 添加日志记录 + + logger.info( + f"技术偏离表带星: {json.dumps(tech_star_deviation, ensure_ascii=False, indent=4)}" + ) # 添加日志记录 + + logger.info( + f"商务偏离表: {json.dumps(business_deviation, ensure_ascii=False, indent=4)}" + ) # 添加日志记录 + + logger.info( + f"商务偏离表带星: {json.dumps(business_star_deviation, ensure_ascii=False, indent=4)}" + ) # 添加日志记录 + + logger.info( + f"资格检查偏离表: {json.dumps(zige_deviation, ensure_ascii=False, indent=4)}" + ) # 添加日志记录 + + logger.info( + f"符合性检查偏离表: {json.dumps(fuhe_deviation, ensure_ascii=False, indent=4)}" + ) # 添加日志记录 + + # 采购需求 - procurement_reqs_response = { + tech_deviation_response = { 'message': 'procurement_reqs', 'filename': os.path.basename(downloaded_filepath), - 'data': json.dumps(procurement_reqs, ensure_ascii=False) + 'data': json.dumps(tech_deviation, ensure_ascii=False) } - yield f"data: {json.dumps(procurement_reqs_response, ensure_ascii=False)}\n\n" + tech_deviation_star_response = { + 'message': 'jishu_star_deviation', + 'filename': filename, + 'data': json.dumps(tech_star_deviation, ensure_ascii=False) + } + zige_deviation_response = { + 'message': 'zige_deviation', + 'filename': filename, + 'data': json.dumps(zige_deviation, ensure_ascii=False) + } + fuhe_deviation_response = { + 'message': 'fuhe_deviation', + 'filename': filename, + 'data': json.dumps(fuhe_deviation, ensure_ascii=False) + } + shangwu_deviation_response = { + 'message': 'shangwu_deviation', + 'filename': filename, + 'data': json.dumps(business_deviation, ensure_ascii=False) + } + shangwu_star_deviation_response = { + 'message': 'shangwu_star_deviation', + 'filename': filename, + 'data': json.dumps(business_star_deviation, ensure_ascii=False) + } + yield f"data: {json.dumps(tech_deviation_response, ensure_ascii=False)}\n\n" + yield f"data: {json.dumps(tech_deviation_star_response, ensure_ascii=False)}\n\n" + yield f"data: {json.dumps(zige_deviation_response, ensure_ascii=False)}\n\n" + yield f"data: {json.dumps(fuhe_deviation_response, ensure_ascii=False)}\n\n" + yield f"data: {json.dumps(shangwu_deviation_response, ensure_ascii=False)}\n\n" + yield f"data: {json.dumps(shangwu_star_deviation_response, ensure_ascii=False)}\n\n" try: with open(extracted_info_path, 'w', encoding='utf-8') as json_file: @@ -579,12 +632,11 @@ def test_process_and_stream(): "数量": "500立方米", "规格": "C30" } - procurement_reqs_response = { + tech_deviation_response = { 'message': 'procurement_reqs', 'filename': filename, 'data': json.dumps(procurement_reqs, ensure_ascii=False) } - yield f"data: {json.dumps(procurement_reqs_response, ensure_ascii=False)}\n\n" zige_deviation_table = { "资格性检查": ["具有独立承担民事责任的能力;", "具有良好的商业信誉和健全的财务会计制度;", @@ -641,11 +693,14 @@ def test_process_and_stream(): 'filename': filename, 'data': json.dumps(jishu_star_deviation_table, ensure_ascii=False) } + yield f"data:{json.dumps(zige_deviation_response, ensure_ascii=False)}\n\n" yield f"data:{json.dumps(fuhe_deviation_response, ensure_ascii=False)}\n\n" yield f"data:{json.dumps(shangwu_deviation_response, ensure_ascii=False)}\n\n" yield f"data:{json.dumps(shangwu_star_deviation_response, ensure_ascii=False)}\n\n" yield f"data:{json.dumps(jishu_star_deviation_response, ensure_ascii=False)}\n\n" + yield f"data: {json.dumps(tech_deviation_response, ensure_ascii=False)}\n\n" + # 发送完整的大字典 complete_response = { diff --git a/flask_app/货物标/商务服务其他要求提取.py b/flask_app/货物标/商务服务其他要求提取.py index 32841e0..60c59f4 100644 --- a/flask_app/货物标/商务服务其他要求提取.py +++ b/flask_app/货物标/商务服务其他要求提取.py @@ -10,55 +10,83 @@ from flask_app.货物标.截取pdf货物标版 import extract_common_header, cle #正则表达式判断原文中是否有商务、服务、其他要求 def find_exists(truncate_file, required_keys): if not truncate_file: - return ["技术要求","商务要求", "服务要求", "其他要求"] - common_header = extract_common_header(truncate_file) + return ["技术要求", "商务要求", "服务要求", "其他要求"] + + common_header = extract_common_header(truncate_file) # 假设该函数已定义 pdf_document = PdfReader(truncate_file) - text = "" + # 定义正则模式 begin_pattern = re.compile( - r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:服务|项目|商务|技术).*?要求|' - r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:采购|技术标准).*|' - r'^[一二三四五六七八九十百千]+、\s*采购清单', re.MULTILINE) + r'(?:^第[一二三四五六七八九十百千]+(?:章|部分)\s*' # 匹配“第X章”或“第X部分” + r'[\u4e00-\u9fff、()()]*?' # 匹配允许的字符 + r'(?:(?:服务|项目|商务|技术)[\u4e00-\u9fff、()()]*?要求|' # 匹配“服务”、“项目”、“商务”或“技术”后跟“要求” + r'(?:采购|需求)[\u4e00-\u9fff、()()]*?)' # 匹配“采购”或“需求” + r'\s*$|' # 匹配行尾 + r'^第[一二三四五六七八九十百千]+(?:章|部分)(?!.*说明).*?' # 匹配“第X章”后带“采购内容”等,排除“说明” + r'(?:采购内容|采购要求|需求).*|' # 匹配“采购内容”或“采购要求”关键词 + r'^[一二三四五六七八九十百千]+、\s*采购清单)' # 匹配“一、采购清单” + r'\s*$', # 匹配行尾 + re.MULTILINE + ) end_pattern = re.compile( - r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+', re.MULTILINE) + 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' + text += cleaned_text + "\n" + # 匹配起始位置 start_match = re.search(begin_pattern, text) if not start_match: + print("未找到开始模式") return [] start_index = start_match.end() - end_match = re.search(end_pattern, text[start_index:]) + # 匹配结束位置 + 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] else: relevant_text = text[start_index:] - relevant_text=re.sub(r'\s+', '', relevant_text) #删除换行符 空格 - # print(relevant_text) - # Custom logic for "服务要求" + + # 保留换行,避免结构丢失 + relevant_text = re.sub(r'\s+', ' ', relevant_text) + # print(f"提取的内容范围:\n{relevant_text}") + + # 匹配所需的要求 matched_requirements = [] punctuation = r"[,。?!、;:,.?!]*" for req in required_keys: - if re.search(re.escape(req), relevant_text): - if req == "服务要求": + # required_keys 中的元素本身已包含 \s*,直接作为正则模式 + if re.search(req, relevant_text): + if req == "服\s*务\s*要\s*求": # 提取所有包含"服务要求"的行 - lines = [line for line in relevant_text.split('\n') if req in line] + lines = [line for line in relevant_text.split('\n') if re.search(req, line)] # 检查是否存在'技术'紧跟在'服务要求'前面(中间只有标点,标点是可选的) - pattern = r'技术\s*' + punctuation + re.escape(req) - # 如果存在'技术'紧跟'服务要求'并且中间仅有标点(可选),则不添加该要求 - if not any(re.search(pattern, line) for line in lines): + pattern = "技\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*求") + else: + # 如果不存在'技术'紧跟'服务要求',正常添加"服务要求" matched_requirements.append(req) else: matched_requirements.append(req) - return matched_requirements + # 去除 \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 ["技术要求", "服务要求"]] + + return clean_requirements def generate_queries(truncate_file, required_keys): key_list = find_exists(truncate_file, required_keys) @@ -74,24 +102,116 @@ def generate_queries(truncate_file, required_keys): # print(query_base) return queries +def generate_user_query_template(required_keys): + import textwrap + import json + + # 定义所有可能的键 + all_possible_keys = ["技术要求", "服务要求", "商务要求", "其他要求", "技术、服务要求"] + + # 定义每个键对应的示例内容 + example_content1 = { + "技术要求": ["相关技术要求以及服务要求1", "相关技术要求以及服务要求2"], + "服务要求": ["服务要求1", "服务要求2"], + "商务要求": { + "★产品质保期": ["所投 LED 整屏不低于 3 年,健身器材整套不低于 2 年"], + "售后服务方案": ["包含产品配送、安装保障方案"] + }, + "其他要求": { + "子因素名1": ["关于项目采购的其他要求1...", "关于项目采购的其他要求2..."], + "子因素名2": ["关于项目采购的其他要求3...", "关于项目采购的其他要求4..."] + }, + "技术、服务要求": ["相关技术、服务要求内容1", "相关技术、服务要求内容2"] + } + + example_content2 = { + "技术要求": { + "子因素名1": ["相关技术要求1", "相关技术要求2"], + "子因素名2": ["相关技术要求3"] + }, + "服务要求": { + "子因素名1": ["相关服务要求1", "相关服务要求2"], + "子因素名2": ["相关服务要求3", "相关服务要求4"] + }, + "商务要求": ["所投 LED 整屏不低于 3 年,健身器材整套不低于 2 年", "包含产品配送、安装保障方案"], + "其他要求": ["关于项目采购的其他要求1..."], + "技术、服务要求": { + "子因素名1": ["相关技术、服务要求内容1"], + "子因素名2": ["相关技术、服务要求内容2", "相关技术、服务要求内容3"] + } + } + + # 将 required_keys 转换为集合以便于操作 + keys = set(required_keys) + + # 处理互斥关系:如果 "技术要求" 和 "服务要求" 同时存在,则移除 "技术、服务要求" + if "技术要求" in keys and "服务要求" in keys: + keys.discard("技术、服务要求") + # 如果 "技术、服务要求" 存在,则移除 "技术要求" 和 "服务要求" + elif "技术、服务要求" in keys: + keys.discard("技术要求") + keys.discard("服务要求") + + # 确保 keys 中只包含允许的键 + keys = keys.intersection(all_possible_keys) + + # 按照预定义的顺序排序键,以保持一致性 + sorted_keys = [key for key in all_possible_keys if key in keys] + + # 如果没有任何键被选中,返回一个默认的模板或抛出异常 + if not sorted_keys: + raise ValueError("required_keys 中没有有效的键。") + + # 生成提示部分,根据 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},键值为字符串列表,每个字符串表示具体的一条要求,内容需要与原文保持一致,不可擅自总结删减。 + + 要求与指南: + 1. 默认情况无需嵌套,键值为字符串列表;若存在嵌套结构,嵌套键名是原文中该要求下相应子标题,最多一层嵌套。 + 2. JSON 的结构要求: + - 外层键名为 {outer_keys_str} 中的各项。 + - 每个外层键对应的值可以是: + a. 一个对象(字典),其键为子因素名,值为字符串列表。 + b. 一个字符串列表,表示具体的一条条要求。若只有一条要求,也用字符串列表表示。 + - 最多只允许一层嵌套。 + 3. 请优先定位正文部分的大标题'xx要求',在其之后提取'xx要求'相关内容,由于要求的位置比较集中,请尽量避免在全文各处寻找。 + 4. 在提取技术要求或技术、服务要求时(若有),你无需从采购清单或表格中提取货物名以及参数要求,你仅需定位到原文中相应位置(正文部分、而非表格中)并提取正文内容,通常一类要求写在一块大标题下,否则,键值为空列表。 + 5. 若无相关要求,键值为[] + """) + + # 过滤 example_content1 和 example_content2 以仅包含 sorted_keys + def filter_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) + + # 将过滤后的示例转换为格式化的 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) + + # 完整的用户查询模板,包含两份示例输出 + user_query_template = f""" +{prompt_instruction} +以下为示例输出,仅供格式参考: +示例 1: +{json_example1_str} +示例 2: +{json_example2_str} +""" + + return user_query_template def get_business_requirements(procurement_path): file_id=upload_file(procurement_path) - # required_keys = ["技术要求","商务要求", "服务要求", "其他要求"] - # queries = generate_queries(truncate_file, required_keys) - #一起问了,效率慢点,但内容准 - user_query=""" -请你根据该货物类招标文件中的采购要求部分内容,请告诉我文档中技术要求、服务要求、商务要求、其他要求分别是什么,注意事项:在提取技术要求和服务要求的时候,你无需从采购清单或表格中提取货物名以及参数要求,你仅需定位到原文中相应位置(正文部分、而非表格中)并提取原文内容,通常一类要求写在一块大标题下。请以json格式返回结果,可以用嵌套键值对的形式组织回答,默认情况下外层键名是'技术要求','服务要求','商务要求','其他要求',嵌套键名是原文中的相应子标题或者是你对相关子要求的总结,而键值需要与原文保持一致,不可擅自总结删减。 -以下是你需要考虑的特殊情况:如果原文中技术要求与服务要求在一块,那么你应该用外键'技术、服务要求'替换默认外键'技术要求'和'服务要求';若相关要求不存在,对应的键值设为'未知'。以下为示例输出,仅供格式参考: -{ - "技术、服务要求":"相关技术要求以及服务要求", - "商务要求":{ - "★产品质保期":"所投 LED 整屏不低于 3 年,健身器材整套不低于 2 年", - "售后服务方案":"包含产品配送、安装保障方案" - }, - "其他要求":"未知" -}""" - + required_keys = ["技\s*术\s*要\s*求","商\s*务\s*要\s*求", "服\s*务\s*要\s*求", "其\s*他\s*要\s*求"] + contained_keys=find_exists(procurement_path,required_keys) + print(contained_keys) + # queries = generate_queries(truncate_file, contained_keys) + user_query=generate_user_query_template(contained_keys) business_requirements=qianwen_long_stream(file_id,user_query) # Combine and fill missing keys with default values final_res = clean_json_string(business_requirements) @@ -101,7 +221,7 @@ def get_business_requirements(procurement_path): #TODO:改为先判断,再摘取 if __name__ == "__main__": # truncate_file = "C:\\Users\\Administrator\\Desktop\\fsdownload\\e4be098d-b378-4126-9c32-a742b237b3b1\\ztbfile_procurement.docx" - truncate_file="D:\\flask_project\\flask_app\\static\\output\\output1\\55dc56f0-f7fe-4734-95a3-867df3456d49\\招招招标文件(一中多媒体报告厅教学设备)_procurement.docx" - file_id = upload_file(truncate_file) + truncate_file=r"C:\Users\Administrator\Desktop\货物标\output1\2-招标文件(广水市教育局封闭管理)_procurement.pdf" + # file_id = upload_file(truncate_file) res=get_business_requirements(truncate_file) print(json.dumps(res, ensure_ascii=False, indent=4)) diff --git a/flask_app/货物标/技术参数要求提取.py b/flask_app/货物标/技术参数要求提取.py index eac3125..8c988a5 100644 --- a/flask_app/货物标/技术参数要求提取.py +++ b/flask_app/货物标/技术参数要求提取.py @@ -354,11 +354,12 @@ def get_technical_requirements(file_path,invalid_path): ffinal_res = postprocess(cleaned_res) else: # user_query_template = "请你根据该货物标中采购要求部分的内容,请你给出\"{}\"的技术参数(或采购要求),请以json格式返回结果,外层键名为\"{}\", 键值对中的键是你对该要求的总结,而值需要完全与原文保持一致,不可擅自总结删减。" - user_query_template = """请根据以下货物标中采购要求部分的内容,为\"{}\"生成技术参数(或采购要求)。请以 JSON 格式返回结果,外层键名为\"{}\",最内层键值为一个列表,列表中包含若干描述\"{}\"的技术参数(或采购要求)的字符串,内容需与原文一致,即若技术参数前存在序号也要保留,但不可擅自增添或删减。 + user_query_template = """请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。请以 JSON 格式返回结果,键名为\"{}\",键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求的字符串,内容需与原文一致,即若技术参数前存在序号也要保留,但不可擅自增添或删减。 要求与指南: 1. 如果该货物没有相关采购要求或技术参数要求,键值应为空列表。 2. 如果存在嵌套结构,且原文为Markdown 的表格语法,如'摄像机|有效像素|≥900W像素', 请不要返回该Markdown语法,而是使用冒号':'将相关信息拼接在一起,生成一条完整且清晰的技术参数(或采购要求)描述,作为列表中的一个字符串。如"摄像机:有效像素:≥900W像素"。 3. 字符串中的内容为具体的技术参数要求或采购要求,请不要返回诸如'(1)高清录像功能'这种标题性质且不能体现要求的内容。 +4. 你的键值应该全面,对于同一个单元格内的数据,尽量全面,不要遗漏。 ### 示例输出1如下: {{ @@ -368,23 +369,23 @@ def get_technical_requirements(file_path,invalid_path): ] }} -### 示例输出2如下: +### 示例输出2如下(包含嵌套结构): {{ "摄像机": [ "摄像机:有效像素:≥900W像素", "摄像机:最低照度:彩色≤0.001lx", - "协议:API 接口开放:具备;支持标准 ONVIF 协议与第三方厂家设备进行互联;支持 GB/T28181;应提供 SDK" + "协议:routes 接口开放:具备;支持标准 ONVIF 协议与第三方厂家设备进行互联;支持 GB/T28181;应提供 SDK" ] }} """ - user_query_template_two="""请根据以下货物标中采购要求部分的内容,为\"{}\"生成技术参数(或采购要求)。由于该货物存在多种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\", 键值为一个列表,列表中包含若干描述\"{}\"的技术参数(或采购要求)的字符串,需与原文完全一致,即若技术参数前存在序号也要保留,但不可擅自增添或删减。 + user_query_template_two="""请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。由于该货物存在多种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\", 键值为一个列表,列表中包含若干描述\"{}\"的技术参数(或采购要求)的字符串,需与原文完全一致,即若技术参数前存在序号也要保留,但不可擅自增添或删减。 请注意以下特殊情况: 要求与指南: 1. 如果该货物没有相关采购要求或技术参数要求,键值应为空列表。 2. 如果存在嵌套结构,且原文为Markdown 的表格语法,如'摄像机|有效像素|≥900W像素', 请不要返回该Markdown语法,而是使用冒号':'将相关信息拼接在一起,生成一条完整且清晰的技术参数(或采购要求)描述,作为列表中的一个字符串。如"摄像机:有效像素:≥900W像素"。 3. 字符串中的内容为具体的技术参数要求或采购要求,请不要返回诸如'(1)高清录像功能'这种标题性质且不能体现要求的内容。 -### 示例输出,参考1如下: +### 示例输出1如下: {{ "交换机-1": [ "1、支持固化千兆电口≥8 个,固化千兆光口≥2 个,桌面型设备;", @@ -396,12 +397,12 @@ def get_technical_requirements(file_path,invalid_path): ] }} -### 示例输出,参考2如下(包含嵌套结构): +### 示例输出2如下(包含嵌套结构): {{ "摄像机-1": [ "摄像机:有效像素:≥900W像素", "摄像机:最低照度:彩色≤0.001lx", - "协议:API 接口开放:具备;支持标准 ONVIF 协议与第三方厂家设备进行互联;支持 GB/T28181;应提供 SDK" + "协议:routes 接口开放:具备;支持标准 ONVIF 协议与第三方厂家设备进行互联;支持 GB/T28181;应提供 SDK" ], "摄像机-2": [ "支持夜视", "支持云存储" @@ -431,6 +432,7 @@ def get_technical_requirements(file_path,invalid_path): # 打印结果 for question, response in results: technical_requirements.append(response) + # print(response) technical_requirements_combined_res = combine_json_results(technical_requirements) """根据所有键是否已添加处理技术要求""" @@ -440,7 +442,7 @@ def get_technical_requirements(file_path,invalid_path): # final_res = postprocess(cleaned_res) ffinal_res["货物列表"] = good_list # 输出最终的 JSON 字符串 - return {"采购需求": ffinal_res} + return {"采购需求":ffinal_res} def test_all_files_in_folder(input_folder, output_folder): # 确保输出文件夹存在 diff --git a/flask_app/货物标/技术参数要求提取后处理函数.py b/flask_app/货物标/技术参数要求提取后处理函数.py index 84031bf..1bedad1 100644 --- a/flask_app/货物标/技术参数要求提取后处理函数.py +++ b/flask_app/货物标/技术参数要求提取后处理函数.py @@ -111,11 +111,19 @@ def postprocess(data): # 递归处理顶层数据 return {key: convert_dict(val) if isinstance(val, dict) else val for key, val in data.items()} + def all_postprocess(data): - temp=restructure_data(data) - processed_data = {} - for key, value_list in temp.items(): - processed_data[key] = remove_common_prefixes(value_list) + temp = restructure_data(data) + + def recursive_process(item): + if isinstance(item, dict): + return {k: recursive_process(v) for k, v in item.items()} + elif isinstance(item, list): + return remove_common_prefixes(item) + else: + return item + + processed_data = recursive_process(temp) return processed_data def detect_depth(data): """