From 19f7d7b38be617c6e1396adb5f80e541892b4401 Mon Sep 17 00:00:00 2001 From: zy123 <646228430@qq.com> Date: Thu, 6 Feb 2025 14:39:58 +0800 Subject: [PATCH] =?UTF-8?q?2.6=20=E7=BB=93=E6=9E=84=E6=95=B4=E7=90=86?= =?UTF-8?q?=E3=80=81=E5=A2=9E=E5=8A=A0=E4=BA=86cache=E5=91=BD=E4=B8=AD?= =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/encodings.xml | 4 + flask_app/general/clean_pdf.py | 2 - flask_app/general/docx截取docx.py | 128 +++- flask_app/general/file2markdown.py | 1 - flask_app/general/format_amout.py | 2 - flask_app/general/format_change.py | 104 +-- flask_app/general/llm/__init__.py | 0 flask_app/general/llm/deepseek.py | 75 ++ flask_app/general/{ => llm}/doubao.py | 153 +--- .../general/{ => llm}/model_continue_query.py | 4 +- flask_app/general/{ => llm}/多线程提问.py | 7 +- flask_app/general/{ => llm}/清除file_id.py | 0 .../通义千问long_plus.py} | 0 flask_app/general/post_processing.py | 15 +- flask_app/general/table_content_extraction.py | 4 - flask_app/general/商务技术评分提取.py | 8 +- flask_app/general/截取pdf通用函数.py | 3 - .../general/投标人须知正文提取指定内容.py | 4 +- flask_app/general/无效标和废标公共代码.py | 8 +- flask_app/general/通用功能函数.py | 27 +- .../old_version/extarct_img_2_txt_old.py | 123 ++++ .../table_ocr_old.py} | 3 - .../old_version/不得存在及禁止投标情形_old.py | 2 +- .../判断是否分包等_old.py} | 8 +- .../商务评分技术评分整合old_version.py | 2 +- flask_app/old_version/基础信息整合_old.py | 8 +- flask_app/old_version/形式响应评审old.py | 2 +- .../提取打勾符号_old.py} | 5 - .../old_version/无效标和废标公共代码_old.py | 4 +- .../无效标和废标和禁止投标整合_old.py | 2 +- flask_app/old_version/评分标准提取main_old.py | 2 +- flask_app/old_version/资格审查模块old_old.py | 2 +- flask_app/old_version/资格评审old_old.py | 4 +- flask_app/routes/get_deviation.py | 2 +- flask_app/routes/utils.py | 4 +- .../{偏离表main.py => 偏离表数据解析main.py} | 223 +++--- flask_app/routes/判断是否是招标文件.py | 5 +- flask_app/routes/小解析main.py | 4 +- flask_app/routes/货物标解析main.py | 3 +- flask_app/start_up.py | 4 +- flask_app/static/提示词/基本信息货物标.txt | 2 - .../{货物标 => test_case}/find_zbfile.py | 0 flask_app/test_case/test_doubao.py | 2 +- flask_app/test_case/test_qianwen_long.py | 2 +- .../资格评审5种情况测试脚本.py | 0 flask_app/testdir/待合并代码.py | 662 ------------------ flask_app/工程标/find_tbformat.py | 42 -- flask_app/工程标/基础信息整合工程标.py | 6 +- flask_app/工程标/形式响应评审.py | 26 +- flask_app/工程标/资格审查模块.py | 2 +- flask_app/工程标/资格评审.py | 4 +- flask_app/货物标/商务服务其他要求提取.py | 25 +- flask_app/货物标/基础信息解析货物标版.py | 8 +- flask_app/货物标/技术参数要求提取.py | 119 ++-- .../货物标/技术参数要求提取后处理函数.py | 61 +- flask_app/货物标/提取采购需求main.py | 3 - flask_app/货物标/资格审查main.py | 2 +- 57 files changed, 579 insertions(+), 1348 deletions(-) create mode 100644 flask_app/general/llm/__init__.py create mode 100644 flask_app/general/llm/deepseek.py rename flask_app/general/{ => llm}/doubao.py (61%) rename flask_app/general/{ => llm}/model_continue_query.py (99%) rename flask_app/general/{ => llm}/多线程提问.py (98%) rename flask_app/general/{ => llm}/清除file_id.py (100%) rename flask_app/general/{通义千问long.py => llm/通义千问long_plus.py} (100%) create mode 100644 flask_app/old_version/extarct_img_2_txt_old.py rename flask_app/{general/table_ocr.py => old_version/table_ocr_old.py} (98%) rename flask_app/{工程标/判断是否分包等.py => old_version/判断是否分包等_old.py} (97%) rename flask_app/{工程标/提取打勾符号.py => old_version/提取打勾符号_old.py} (99%) rename flask_app/routes/{偏离表main.py => 偏离表数据解析main.py} (76%) rename flask_app/{货物标 => test_case}/find_zbfile.py (100%) rename flask_app/{工程标 => test_case}/资格评审5种情况测试脚本.py (100%) delete mode 100644 flask_app/testdir/待合并代码.py delete mode 100644 flask_app/工程标/find_tbformat.py diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 13db666..8446012 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -15,5 +15,9 @@ + + + + \ No newline at end of file diff --git a/flask_app/general/clean_pdf.py b/flask_app/general/clean_pdf.py index a3860f7..c968058 100644 --- a/flask_app/general/clean_pdf.py +++ b/flask_app/general/clean_pdf.py @@ -1,9 +1,7 @@ import re - import fitz from PyPDF2 import PdfReader - def extract_common_header(pdf_path): """ 提取 PDF 文件的公共页眉。 diff --git a/flask_app/general/docx截取docx.py b/flask_app/general/docx截取docx.py index cf8ea8a..662ee2f 100644 --- a/flask_app/general/docx截取docx.py +++ b/flask_app/general/docx截取docx.py @@ -1,54 +1,108 @@ -from docx import Document -import re import os +import copy +import regex +from docx import Document +from docx.text.paragraph import Paragraph +from docx.table import Table +def iter_block_items(parent): + """ + 遍历给定 parent(Document 或 _Cell)的块级对象, + 依次返回其中的 Paragraph(段落)和 Table(表格)。 + """ + # 对于 Document 对象,获取 body 节点 + parent_elm = parent.element.body + for child in parent_elm.iterchildren(): + tag = child.tag.lower() + if tag.endswith('}p'): + yield Paragraph(child, parent) + elif tag.endswith('}tbl'): + yield Table(child, parent) +#暂用不上,因为需要pdf文件->每页后面打标记->转docx。后面这一步钱无法省。 def copy_docx(source_path): - doc = Document(source_path) # 打开源文档 + """ + 从文档中截取内容: + - 从第一个匹配 begin_pattern 的段落开始(按文档顺序) + - 到最后一个匹配 end_pattern 的段落结束(按文档倒序匹配) + 如果没有匹配到 begin_pattern,则默认从第一段开始; + 如果没有匹配到 end_pattern,则默认到最后一段结束; + 同时确保匹配到的起始段落一定在结束段落之前, + 否则也采用默认的整个文档范围。 + 同时保留中间的所有块级对象(包括文本、表格等), + 并尽量保持原来的格式、样式不变。 + """ + # 打开源文档,并构建新文档 + doc = Document(source_path) output_folder = os.path.dirname(source_path) - - # 获取原文件名并添加后缀 original_file_name = os.path.basename(source_path) file_name_without_ext, file_ext = os.path.splitext(original_file_name) modified_file_name = file_name_without_ext + "_invalid" + file_ext destination_path = os.path.join(output_folder, modified_file_name) + new_doc = Document() - new_doc = Document() # 创建新文档 + # 定义正则表达式(使用 regex 模块): + begin_pattern = regex.compile( + r'.*(? end_index: + begin_index = 0 + end_index = len(block_items) - 1 + + # 将 begin_index 到 end_index 之间的所有块(包括段落和表格)复制到新文档中 + for block in block_items[begin_index : end_index + 1]: + # 直接将原块的 XML 进行 deep copy 后添加到新文档的 body 中 + new_doc._body._element.append(copy.deepcopy(block._element)) + + new_doc.save(destination_path) + print("docx截取成功,已保存到:", destination_path) return destination_path - -# 调用函数 +# 测试调用 if __name__ == '__main__': - source_path = "C:\\Users\\Administrator\\Desktop\\fsdownload\\796f7bb3-2f7a-4332-b044-9d817a07861e\\ztbfile.docx" - res=copy_docx(source_path) - print(res) \ No newline at end of file + source_path = r"C:\Users\Administrator\Desktop\货物标\zbfilesdocx\6_2定版视频会议磋商文件.docx" + res = copy_docx(source_path) + print(res) diff --git a/flask_app/general/file2markdown.py b/flask_app/general/file2markdown.py index 7481732..1810efc 100644 --- a/flask_app/general/file2markdown.py +++ b/flask_app/general/file2markdown.py @@ -1,5 +1,4 @@ import os.path - import requests import json diff --git a/flask_app/general/format_amout.py b/flask_app/general/format_amout.py index d7a5b55..c9c9090 100644 --- a/flask_app/general/format_amout.py +++ b/flask_app/general/format_amout.py @@ -1,5 +1,3 @@ -import re - """ 处理全角点号->半角点号 处理逗号分隔的数字: 你的当前代码无法处理包含逗号的数字,例如 "1,000元"。可以在提取数字之前移除逗号。 diff --git a/flask_app/general/format_change.py b/flask_app/general/format_change.py index 54a7376..86798a2 100644 --- a/flask_app/general/format_change.py +++ b/flask_app/general/format_change.py @@ -3,7 +3,6 @@ import os import mimetypes import requests from PyPDF2 import PdfReader - from flask_app.general.clean_pdf import is_scanned_pdf def download_file(url, local_filename): """ @@ -74,7 +73,7 @@ def download_file(url, local_filename): return None,4 -def upload_file(file_path, url): +def local_file_2_url(file_path, url): receive_file_url = "" # 定义文件名和路径 filename = file_path.split('/')[-1] @@ -128,7 +127,7 @@ def pdf2docx(local_path_in): return docx_file_path remote_url = 'http://47.98.59.178:8000/v3/3rd/files/transfer/p2d' - receive_download_url = upload_file(local_path_in, remote_url) # 转换完成,得到下载链接 + receive_download_url = local_file_2_url(local_path_in, remote_url) # 转换完成,得到下载链接 local_filename = os.path.join(folder,filename) # 输出文件名 C:\Users\Administrator\Desktop\货物标\zbfiles\6.2定版视频会议磋商文件 不带后缀 downloaded_filepath, file_type = download_file(receive_download_url, local_filename) @@ -148,7 +147,7 @@ def doc2docx(local_path_in): return docx_file_path remote_url = 'http://47.98.59.178:8000/v3/3rd/files/transfer/d2d' - receive_download_url = upload_file(local_path_in, remote_url) + receive_download_url = local_file_2_url(local_path_in, remote_url) print(receive_download_url) filename, folder = get_filename_and_folder(local_path_in) # 输入输出在同一个文件夹 local_filename = os.path.join(folder, filename) # 输出文件名 @@ -180,7 +179,7 @@ def docx2pdf(local_path_in,force=False): print(f"跳过转换,文件已存在: {pdf_file_path}") return pdf_file_path # 跳过转换 remote_url = 'http://47.98.59.178:8000/v3/3rd/files/transfer/d2p' - receive_download_url = upload_file(local_path_in, remote_url) + receive_download_url = local_file_2_url(local_path_in, remote_url) filename, folder = get_filename_and_folder(local_path_in) # 输入输出在同一个文件夹 local_filename = os.path.join(folder, filename) # 输出文件名 downloaded_filepath,file_type = download_file(receive_download_url, local_filename) @@ -201,101 +200,6 @@ def get_pdf_page_count(file_path): print(f"读取 PDF 页码时出错:{e}") return 0 -# def docx2pdf(file_path): -# """ -# 将本地的 .docx 或 .doc 文件转换为 .pdf 文件。 -# -# 参数: -# - file_path: str, 本地文件的路径,支持 .docx 和 .doc 格式。 -# """ -# # 检查文件是否存在 -# if not file_path: -# return "" -# # 获取文件名和扩展名 -# base_name = os.path.basename(file_path) -# name, ext = os.path.splitext(base_name) -# ext = ext.lower().lstrip('.') -# -# if ext not in ['docx', 'doc']: -# raise ValueError(f"doc2pdf 仅支持 .docx 和 .doc 文件,当前文件扩展名为: .{ext}") -# -# # 定义转换接口 -# endpoint = 'http://120.26.236.97:5008/convert_to_pdf' -# # endpoint = 'http://192.168.0.2:5008/convert_to_pdf' -# -# # 获取文件所在目录 -# output_dir = os.path.dirname(file_path) -# -# # 准备上传的文件 -# with open(file_path, 'rb') as f: -# files = {'file': (base_name, f)} -# try: -# print(f"正在将 {base_name} 转换为 .pdf 格式...") -# response = requests.post(endpoint, files=files) -# response.raise_for_status() # 检查请求是否成功 -# except requests.RequestException as e: -# print(f"转换过程中发生错误: {e}") -# return -# -# # 准备保存转换后文件的路径 -# output_file_name = f"{name}.pdf" -# output_path = os.path.join(output_dir, output_file_name) -# -# # 保存转换后的文件 -# with open(output_path, 'wb') as out_file: -# out_file.write(response.content) -# -# print(f"文件已成功转换并保存至: {output_path}") -# return output_path -# -# -# def doc2docx(file_path): -# """ -# 将本地的 .doc 文件转换为 .docx 文件。 -# -# 参数: -# - file_path: str, 本地文件的路径,支持 .doc 格式。 -# """ -# # 检查文件是否存在 -# if not file_path: -# return "" -# # 获取文件名和扩展名 -# base_name = os.path.basename(file_path) -# name, ext = os.path.splitext(base_name) -# ext = ext.lower().lstrip('.') -# -# if ext != 'doc': -# raise ValueError(f"doc2docx 仅支持 .doc 文件,当前文件扩展名为: .{ext}") -# -# # 定义转换接口 -# endpoint = 'http://120.26.236.97:5008/convert_to_docx' -# -# # 获取文件所在目录 -# output_dir = os.path.dirname(file_path) -# -# # 准备上传的文件 -# with open(file_path, 'rb') as f: -# files = {'file': (base_name, f)} -# try: -# print(f"正在将 {base_name} 转换为 .docx 格式...") -# response = requests.post(endpoint, files=files) -# response.raise_for_status() # 检查请求是否成功 -# except requests.RequestException as e: -# print(f"转换过程中发生错误: {e}") -# return -# -# # 准备保存转换后文件的路径 -# output_file_name = f"{name}.docx" -# output_path = os.path.join(output_dir, output_file_name) -# -# # 保存转换后的文件 -# with open(output_path, 'wb') as out_file: -# out_file.write(response.content) -# -# print(f"文件已成功转换并保存至: {output_path}") -# return output_path - - if __name__ == '__main__': # 替换为你的文件路径和API URL # local_path_in="C:\\Users\\Administrator\\Desktop\\fsdownload\\1fbbb6ff-7ddc-40bb-8857-b7de37aece3f\\兴欣工程.pdf" diff --git a/flask_app/general/llm/__init__.py b/flask_app/general/llm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flask_app/general/llm/deepseek.py b/flask_app/general/llm/deepseek.py new file mode 100644 index 0000000..6489b94 --- /dev/null +++ b/flask_app/general/llm/deepseek.py @@ -0,0 +1,75 @@ +import os +import json +from openai import OpenAI + + +def deepseek(user_query, need_extra=False): + """ + 调用 deepseek 接口生成回答。 + + 参数: + user_query (str): 用户输入的查询内容。 + need_extra (bool): 是否额外返回 usage 信息(completion_tokens),默认为 False。 + + 返回: + 如果 need_extra 为 True,则返回一个元组 (full_response, completion_tokens); + 否则仅返回 full_response。 + """ + client = OpenAI( + # 如果没有配置环境变量,请直接将 api_key 替换为实际的 API Key,例如:api_key="sk-xxx" + api_key=os.getenv("DASHSCOPE_API_KEY"), + base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", + ) + + # 调用接口,开启流式输出并包含 usage 信息 + completion = client.chat.completions.create( + model="deepseek-r1-distill-qwen-32b", + temperature=0.5, + messages=[ + {'role': 'user', 'content': user_query} + ], + stream=True, + stream_options={"include_usage": True} + ) + + full_response = "" + completion_tokens = 0 + + for chunk in completion: + # 解析 chunk 数据:优先使用 to_dict 方法,否则采用 model_dump_json 进行转换 + if hasattr(chunk, 'to_dict'): + chunk_data = chunk.to_dict() + else: + chunk_data = json.loads(chunk.model_dump_json()) + + # 处理 usage 信息 + usage = chunk_data.get('usage') + if usage is not None: + completion_tokens = usage.get('completion_tokens', 0) + + # 处理 choices 信息 + choices = chunk_data.get('choices', []) + if choices: + 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'): + pass + + # 根据 need_extra 决定返回值 + if need_extra: + return full_response, completion_tokens + else: + return full_response + + +# 示例调用 +if __name__ == "__main__": + query = "1+1等于几?请用json格式回答,键名为'答案',键值为你的回答" + result = deepseek(query, need_extra=True) + print("\n完整内容为:", result) diff --git a/flask_app/general/doubao.py b/flask_app/general/llm/doubao.py similarity index 61% rename from flask_app/general/doubao.py rename to flask_app/general/llm/doubao.py index 29b687b..990d195 100644 --- a/flask_app/general/doubao.py +++ b/flask_app/general/llm/doubao.py @@ -2,134 +2,10 @@ import json import os import re import time -import fitz import PyPDF2 -import tempfile import requests from ratelimit import sleep_and_retry, limits from flask_app.general.clean_pdf import extract_common_header, clean_page_content -from flask_app.general.table_ocr import CommonOcr - -# 调用豆包对json形式的表格数据进行重构 -def extract_img_table_text(ocr_result_pages): - print(ocr_result_pages) - base_prompt = ''' - 任务:你负责解析以json形式输入的表格信息,根据提供的文件内容来恢复表格信息并输出,不要遗漏任何一个文字。 - - 要求与指南: - 1. 请运用文档表格理解能力,根据文件内容定位表格的每列和每行,列与列之间用丨分隔,若某列或者某行没有信息则用/填充。 - 2. 请不要遗漏任何一个文字,同时不要打乱行与行之间的顺序,也不要打乱列与列之间的顺序,严格按照文字的位置信息来恢复表格信息。 - 示例输出: - 表格标题: - |序号|名称|数量|单位|单价(元)|总价(元)|技术参数|备注| - |形象展示区| - |1|公园主E题雕塑|1|套|/|/|根据江夏体育与文化元素定制|/| - - .......... - ''' - base_prompt += f"\n\n文件内容:\n{json.dumps(ocr_result_pages, ensure_ascii=False, indent=4)}" - model_res = doubao_model(base_prompt) - return model_res - -# 判断pdf中是否有图片, 并输出含有图片的页面列表 -def has_images(pdf_path): - # 打开PDF文件 - pdf_document = fitz.open(pdf_path) - # 存储包含图片的页面页数 - pages_with_imgs = {} - # 遍历PDF的每一页 - for page_num in range(pdf_document.page_count): - page = pdf_document.load_page(page_num) - # 获取页面的图片列表 - images = page.get_images(full=True) - # 如果页面中有图片,返回True - if images: - pages_with_imgs[page_num + 1] = images - # 如果遍历了所有页面,都没有图片,则返回False - return pages_with_imgs - -# 调用通用表格识别对图片中的表格进行提取,放回json形式的表格结构 -def table_ocr_extract(image_path): - table_ocr = CommonOcr(img_path=image_path) # 创建时传递 img_path - return table_ocr.recognize() - -# def ocr_extract(image_path): -# # 调用您的OCR引擎来识别图像中的文本 -# # return OcrEngine.recognize_text_from_image(image_path) -# # 调用本地ocr -# return local_ocr.run(image_path) - -# 提取pdf中某一页的所有图片 -def extract_images_from_page(pdf_path, image_list, page_num): - images = [] - try: - doc = fitz.open(pdf_path) - - for img in image_list: - xref = img[0] - base_image = doc.extract_image(xref) - image_bytes = base_image['image'] - image_ext = base_image.get('ext', 'png') - images.append({'data': image_bytes, 'ext': image_ext, 'page_num': page_num + 1}) - except Exception as e: - print(f"提取图片时出错: {e}") - return images -def pdf_image2txt(file_path, img_pdf_list): - common_header = extract_common_header(file_path) - # print(f"公共抬头:{common_header}") - # print("--------------------正文开始-------------------") - result = "" - pdf_document = fitz.open(file_path) - 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() - text = page.extract_text() or "" - cleaned_text = clean_page_content(text, common_header) - # # print(f"--------第{page_num}页-----------") - if (page_num + 1) in img_pdf_list: - print(f"第 {page_num + 1} 页含有图片,开始提取图片并OCR") - images = extract_images_from_page(file_path, img_pdf_list[page_num + 1], page_num) - for img in images: - try: - with tempfile.NamedTemporaryFile(delete=False, suffix='.' + img['ext']) as temp_image: - temp_image.write(img['data']) - temp_image.flush() - # 调用OCR函数 - ocr_result = table_ocr_extract(temp_image.name) - ocr_result = json.loads(ocr_result) - # 判断是否提取成功并且 pages 中有数据 - if ocr_result['code'] == 200 and len(ocr_result['result']['pages']) > 0: - print("提取成功,图片数据已提取。") - ocr_result_pages = ocr_result['result']['pages'] - table_text = extract_img_table_text(ocr_result_pages) - if table_text.strip(): - cleaned_text += "\n" + table_text - else: - print("提取失败或没有页面数据。") - except Exception as e: - print(f"OCR处理失败: {e}") - finally: - try: - os.remove(temp_image.name) - except Exception as e: - print(f"删除临时文件失败: {e}") - result += cleaned_text - - directory = os.path.dirname(os.path.abspath(file_path)) - output_path = os.path.join(directory, 'extract.txt') - # 将结果保存到 extract.txt 文件中 - try: - with open(output_path, 'w', encoding='utf-8') as output_file: - output_file.write(result) - print(f"提取内容已保存到: {output_path}") - except IOError as e: - print(f"写入文件时发生错误: {e}") - # 返回保存的文件路径 - return output_path def pdf2txt(file_path): common_header = extract_common_header(file_path) @@ -182,32 +58,9 @@ def read_txt_to_string(file_path): except Exception as e: return f"错误:读取文件时发生错误。详细信息:{e}" -def count_tokens(text): - """ - 统计文本中的 tokens 数量: - 1. 英文字母+数字作为一个 token(如 DN90)。 - 2. 数字+小数点/百分号作为一个 token(如 0.25%)。 - 3. 单个中文字符作为一个 token。 - 4. 单个符号或标点符号作为一个 token。 - 5. 忽略空白字符(空格、空行等)。 - """ - # 正则表达式: - # - 英文字母和数字组合:DN90 - # - 数字+小数点/百分号组合:0.25% - # - 单个中文字符:[\u4e00-\u9fff] - # - 单个非空白符号:[^\s] - token_pattern = r'[a-zA-Z0-9]+(?:\.\d+)?%?|[\u4e00-\u9fff]|[^\s]' - tokens = re.findall(token_pattern, text) - return len(tokens)# 返回 tokens 数量和匹配的 token 列表 - def get_total_tokens(text): """ - 调用 API 计算给定文本的总 Token 数量。 - - 参数: - - text (str): 需要计算 Token 的文本。 - - model (str): 使用的模型名称,默认值为 "ep-20241119121710-425g6"。 - + 调用 API 计算给定文本的总 Token 数量。 注:doubao的计算方法!与qianwen不一样 返回: - int: 文本的 total_tokens 数量。 """ @@ -241,7 +94,6 @@ def get_total_tokens(text): print(f"获取 Token 数量失败:{e}") return 0 - @sleep_and_retry @limits(calls=10, period=1) # 每秒最多调用10次 def doubao_model(full_user_query, need_extra=False): @@ -356,7 +208,6 @@ def doubao_model(full_user_query, need_extra=False): else: return None - def generate_full_user_query(file_path, prompt_template): """ 根据文件路径和提示词模板生成完整的user_query。 @@ -372,10 +223,8 @@ def generate_full_user_query(file_path, prompt_template): 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" diff --git a/flask_app/general/model_continue_query.py b/flask_app/general/llm/model_continue_query.py similarity index 99% rename from flask_app/general/model_continue_query.py rename to flask_app/general/llm/model_continue_query.py index e0df96e..4a17dd7 100644 --- a/flask_app/general/model_continue_query.py +++ b/flask_app/general/llm/model_continue_query.py @@ -1,10 +1,8 @@ # -*- encoding:utf-8 -*- import concurrent.futures import json -from flask_app.general.doubao import doubao_model from flask_app.general.json_utils import clean_json_string -from flask_app.general.通义千问long import qianwen_long, qianwen_long_stream, qianwen_plus - +from flask_app.general.llm.通义千问long_plus import qianwen_long, qianwen_long_stream, qianwen_plus def generate_continue_query(original_query, original_answer): """ diff --git a/flask_app/general/多线程提问.py b/flask_app/general/llm/多线程提问.py similarity index 98% rename from flask_app/general/多线程提问.py rename to flask_app/general/llm/多线程提问.py index 6eb302e..5e68bd4 100644 --- a/flask_app/general/多线程提问.py +++ b/flask_app/general/llm/多线程提问.py @@ -1,19 +1,14 @@ # 基于知识库提问的通用模板, # assistant_id -import json -import os import re import queue import concurrent.futures import time -from datetime import datetime -import requests from dashscope import Assistants, Messages, Runs, Threads from llama_index.indices.managed.dashscope import DashScopeCloudRetriever -from flask_app.general.doubao import doubao_model -from flask_app.general.通义千问long import qianwen_long, upload_file, qianwen_plus +from flask_app.general.llm.通义千问long_plus import qianwen_long, upload_file, qianwen_plus prompt = """ # 角色 diff --git a/flask_app/general/清除file_id.py b/flask_app/general/llm/清除file_id.py similarity index 100% rename from flask_app/general/清除file_id.py rename to flask_app/general/llm/清除file_id.py diff --git a/flask_app/general/通义千问long.py b/flask_app/general/llm/通义千问long_plus.py similarity index 100% rename from flask_app/general/通义千问long.py rename to flask_app/general/llm/通义千问long_plus.py diff --git a/flask_app/general/post_processing.py b/flask_app/general/post_processing.py index eede9a0..0e9e180 100644 --- a/flask_app/general/post_processing.py +++ b/flask_app/general/post_processing.py @@ -3,7 +3,7 @@ import json import re from flask_app.general.format_date import format_chinese_date from flask_app.general.format_amout import format_amount -from flask_app.routes.偏离表main import postprocess_technical_table, prepare_for_zige_info, process_functions_in_parallel +from flask_app.routes.偏离表数据解析main import postprocess_technical_table, prepare_for_zige_info, process_functions_in_parallel # 定义一个辅助函数用于获取嵌套字典中的值 @@ -15,7 +15,6 @@ def get_nested(dic, keys, default=None): return default return dic - def inner_post_processing(base_info): # print(json.dumps(base_info,ensure_ascii=False,indent=4)) """ @@ -239,16 +238,10 @@ def inner_post_processing(base_info): return extracted_info - def outer_post_processing(combined_data, includes, good_list): """ - 外层处理函数,调用内层 post_processing 处理 '基础信息',并构建 processed_data。 - 额外提取 '采购要求' 下的 '技术要求' 内容。 - - 参数: - combined_data (dict): 原始合并数据。 - includes (list): 需要包含的键列表。 - + 外层处理函数,调用内层 inner_post_processing 处理 '基础信息',并构建 processed_data。 + 额外提取 '采购要求' 下的 '技术要求' 内容以及各块的信息 =》生成商务、技术偏离表所需信息 返回: tuple: (processed_data, extracted_info, procurement_reqs) """ @@ -272,7 +265,7 @@ def outer_post_processing(combined_data, includes, good_list): # 检查 '基础信息' 是否在 includes 中 if "基础信息" in includes: base_info = combined_data.get("基础信息", {}) - # 调用内层 post_processing 处理 '基础信息' + # 调用内层 inner_post_processing 处理 '基础信息' extracted_info = inner_post_processing(base_info) # 将 '基础信息' 保留在处理后的数据中 processed_data["基础信息"] = base_info diff --git a/flask_app/general/table_content_extraction.py b/flask_app/general/table_content_extraction.py index 10985ca..6e2040e 100644 --- a/flask_app/general/table_content_extraction.py +++ b/flask_app/general/table_content_extraction.py @@ -1,10 +1,6 @@ import os - from docx import Document import json - - - def read_tables_from_docx(file_path): """读取DOCX文件中的表格数据,并以嵌套字典的形式返回.""" doc = Document(file_path) diff --git a/flask_app/general/商务技术评分提取.py b/flask_app/general/商务技术评分提取.py index 5fe02a7..a011ab8 100644 --- a/flask_app/general/商务技术评分提取.py +++ b/flask_app/general/商务技术评分提取.py @@ -3,12 +3,12 @@ import os import re import time from typing import Any, Dict -from flask_app.general.doubao import read_txt_to_string +from flask_app.general.llm.doubao import read_txt_to_string from flask_app.general.file2markdown import convert_file_to_markdown from flask_app.general.format_change import get_pdf_page_count, pdf2docx from flask_app.general.json_utils import extract_content_from_json -from flask_app.general.model_continue_query import process_continue_answers -from flask_app.general.通义千问long import upload_file, qianwen_long, qianwen_plus, qianwen_long_stream +from flask_app.general.llm.model_continue_query import process_continue_answers +from flask_app.general.llm.通义千问long_plus import upload_file, qianwen_long, qianwen_plus, qianwen_long_stream def remove_unknown_scores(data): @@ -455,7 +455,7 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): user_query=generate_prompt(zb_type) if model_type==4: full_text = read_txt_to_string(processed_filepath) - user_query += f"\n文件内容:\n{full_text}\n" + user_query = f"文本内容:\n{full_text}\n" + user_query questions_to_continue = [] temp_final={} if model_type==4: diff --git a/flask_app/general/截取pdf通用函数.py b/flask_app/general/截取pdf通用函数.py index fc77ea0..d45fdc0 100644 --- a/flask_app/general/截取pdf通用函数.py +++ b/flask_app/general/截取pdf通用函数.py @@ -114,7 +114,6 @@ def check_pdf_pages(pdf_path, mode, logger): else: return True, ['', '', '', '', '', pdf_path, ''] - def save_extracted_pages(pdf_path, output_folder, start_page, end_page, output_suffix, common_header): try: if start_page is None or end_page is None: @@ -237,14 +236,12 @@ def is_pdf_or_doc(filename): # 判断文件是否为PDF或Word文档 return filename.lower().endswith(('.pdf', '.doc', '.docx')) - def convert_to_pdf(file_path): # 假设 docx2pdf 函数已经被定义,这里仅根据文件扩展名来决定是否需要转换 if file_path.lower().endswith(('.doc', '.docx')): return docx2pdf(file_path) return file_path - def get_invalid_file(file_path, output_folder, common_header, begin_page): """ 从指定的PDF文件中提取无效部分并保存到输出文件夹中。 diff --git a/flask_app/general/投标人须知正文提取指定内容.py b/flask_app/general/投标人须知正文提取指定内容.py index 580b8bd..cf63dbc 100644 --- a/flask_app/general/投标人须知正文提取指定内容.py +++ b/flask_app/general/投标人须知正文提取指定内容.py @@ -2,8 +2,8 @@ import json import re from flask_app.general.json_utils import clean_json_string -from flask_app.general.model_continue_query import process_continue_answers -from flask_app.general.通义千问long import upload_file, qianwen_long_stream +from flask_app.general.llm.model_continue_query import process_continue_answers +from flask_app.general.llm.通义千问long_plus import upload_file, qianwen_long_stream #提取两个大标题之间的内容 def extract_between_sections(data, target_values,flag=False): diff --git a/flask_app/general/无效标和废标公共代码.py b/flask_app/general/无效标和废标公共代码.py index 0a4028a..bb8cf1f 100644 --- a/flask_app/general/无效标和废标公共代码.py +++ b/flask_app/general/无效标和废标公共代码.py @@ -4,10 +4,8 @@ import re import regex import time from concurrent.futures import ThreadPoolExecutor -from flask_app.general.doubao import generate_full_user_query -from flask_app.general.format_change import pdf2docx -from flask_app.general.insert_del_pagemark import insert_mark -from flask_app.general.通义千问long import qianwen_plus +from flask_app.general.llm.doubao import generate_full_user_query +from flask_app.general.llm.通义千问long_plus import qianwen_plus from flask_app.general.通用功能函数 import process_string_list from collections import OrderedDict from docx import Document @@ -526,7 +524,6 @@ def handle_query(file_path, user_query, output_file, result_key, keywords): r'其\s*他.*?情\s*形\s*[::]', r'包\s*括' ] - doc_contents = extract_file_elements(file_path) processed_paragraphs = preprocess_paragraphs(doc_contents) extracted_contents = extract_text_with_keywords(processed_paragraphs, [keywords], follow_up_keywords) @@ -587,7 +584,6 @@ def handle_query(file_path, user_query, output_file, result_key, keywords): print(f"handle_query 在处理 {result_key} 时发生异常: {e}") return {result_key: [f"未解析到'{result_key}'!"]} - def combine_find_invalid(invalid_docpath, output_dir): os.makedirs(output_dir, exist_ok=True) queries = [ diff --git a/flask_app/general/通用功能函数.py b/flask_app/general/通用功能函数.py index f7888bd..264f47c 100644 --- a/flask_app/general/通用功能函数.py +++ b/flask_app/general/通用功能函数.py @@ -1,11 +1,10 @@ # -*- encoding:utf-8 -*- -import ast import logging import re from flask_app.general.json_utils import clean_json_string -from flask_app.general.多线程提问 import multi_threading -from flask_app.general.通义千问long import upload_file, qianwen_long -from flask_app.工程标.判断是否分包等 import read_questions_from_judge +from flask_app.general.llm.多线程提问 import multi_threading +from flask_app.general.llm.通义千问long_plus import upload_file, qianwen_long +from flask_app.old_version.判断是否分包等_old import read_questions_from_judge def get_deviation_requirements(invalid_path): file_id=upload_file(invalid_path) @@ -240,6 +239,26 @@ def process_string_list(string_list): print(f"Error occurred: {e}") return [] # 出现任何异常时返回空列表 +#统计tokens,非常粗糙;现在直接调用qianwen doubao的相关接口直接统计,该函数废弃。 +def count_tokens(text): + """ + 统计文本中的 tokens 数量: + 1. 英文字母+数字作为一个 token(如 DN90)。 + 2. 数字+小数点/百分号作为一个 token(如 0.25%)。 + 3. 单个中文字符作为一个 token。 + 4. 单个符号或标点符号作为一个 token。 + 5. 忽略空白字符(空格、空行等)。 + """ + # 正则表达式: + # - 英文字母和数字组合:DN90 + # - 数字+小数点/百分号组合:0.25% + # - 单个中文字符:[\u4e00-\u9fff] + # - 单个非空白符号:[^\s] + token_pattern = r'[a-zA-Z0-9]+(?:\.\d+)?%?|[\u4e00-\u9fff]|[^\s]' + tokens = re.findall(token_pattern, text) + return len(tokens)# 返回 tokens 数量和匹配的 token 列表 + +#根据id获取日志 def get_global_logger(unique_id): if unique_id is None: return logging.getLogger() # 获取默认的日志器 diff --git a/flask_app/old_version/extarct_img_2_txt_old.py b/flask_app/old_version/extarct_img_2_txt_old.py new file mode 100644 index 0000000..8f1aea3 --- /dev/null +++ b/flask_app/old_version/extarct_img_2_txt_old.py @@ -0,0 +1,123 @@ +import json +import os +import fitz +import PyPDF2 +import tempfile +from flask_app.general.clean_pdf import extract_common_header, clean_page_content +from flask_app.general.llm.doubao import doubao_model +from flask_app.old_version.table_ocr_old import CommonOcr + +def extract_img_table_text(ocr_result_pages): + # 调用豆包对json形式的表格数据进行重构 + # print(ocr_result_pages) + base_prompt = ''' + 任务:你负责解析以json形式输入的表格信息,根据提供的文件内容来恢复表格信息并输出,不要遗漏任何一个文字。 + + 要求与指南: + 1. 请运用文档表格理解能力,根据文件内容定位表格的每列和每行,列与列之间用丨分隔,若某列或者某行没有信息则用/填充。 + 2. 请不要遗漏任何一个文字,同时不要打乱行与行之间的顺序,也不要打乱列与列之间的顺序,严格按照文字的位置信息来恢复表格信息。 + 示例输出: + 表格标题: + |序号|名称|数量|单位|单价(元)|总价(元)|技术参数|备注| + |形象展示区| + |1|公园主E题雕塑|1|套|/|/|根据江夏体育与文化元素定制|/| + + .......... + ''' + base_prompt += f"\n\n文件内容:\n{json.dumps(ocr_result_pages, ensure_ascii=False, indent=4)}" + model_res = doubao_model(base_prompt) + return model_res + +# 判断pdf中是否有图片, 并输出含有图片的页面列表 +def has_images(pdf_path): + # 打开PDF文件 + pdf_document = fitz.open(pdf_path) + # 存储包含图片的页面页数 + pages_with_imgs = {} + # 遍历PDF的每一页 + for page_num in range(pdf_document.page_count): + page = pdf_document.load_page(page_num) + # 获取页面的图片列表 + images = page.get_images(full=True) + # 如果页面中有图片,返回True + if images: + pages_with_imgs[page_num + 1] = images + # 如果遍历了所有页面,都没有图片,则返回False + return pages_with_imgs + +# 调用通用表格识别对图片中的表格进行提取,放回json形式的表格结构 +def table_ocr_extract(image_path): + table_ocr = CommonOcr(img_path=image_path) # 创建时传递 img_path + return table_ocr.recognize() + +# 提取pdf中某一页的所有图片 +def extract_images_from_page(pdf_path, image_list, page_num): + images = [] + try: + doc = fitz.open(pdf_path) + + for img in image_list: + xref = img[0] + base_image = doc.extract_image(xref) + image_bytes = base_image['image'] + image_ext = base_image.get('ext', 'png') + images.append({'data': image_bytes, 'ext': image_ext, 'page_num': page_num + 1}) + except Exception as e: + print(f"提取图片时出错: {e}") + return images +def pdf_image2txt(file_path, img_pdf_list): + common_header = extract_common_header(file_path) + # print(f"公共抬头:{common_header}") + # print("--------------------正文开始-------------------") + result = "" + pdf_document = fitz.open(file_path) + 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() + text = page.extract_text() or "" + cleaned_text = clean_page_content(text, common_header) + # # print(f"--------第{page_num}页-----------") + if (page_num + 1) in img_pdf_list: + print(f"第 {page_num + 1} 页含有图片,开始提取图片并OCR") + images = extract_images_from_page(file_path, img_pdf_list[page_num + 1], page_num) + for img in images: + try: + with tempfile.NamedTemporaryFile(delete=False, suffix='.' + img['ext']) as temp_image: + temp_image.write(img['data']) + temp_image.flush() + # 调用OCR函数 + ocr_result = table_ocr_extract(temp_image.name) + ocr_result = json.loads(ocr_result) + # 判断是否提取成功并且 pages 中有数据 + if ocr_result['code'] == 200 and len(ocr_result['result']['pages']) > 0: + print("提取成功,图片数据已提取。") + ocr_result_pages = ocr_result['result']['pages'] + table_text = extract_img_table_text(ocr_result_pages) + if table_text.strip(): + cleaned_text += "\n" + table_text + else: + print("提取失败或没有页面数据。") + except Exception as e: + print(f"OCR处理失败: {e}") + finally: + try: + os.remove(temp_image.name) + except Exception as e: + print(f"删除临时文件失败: {e}") + result += cleaned_text + + directory = os.path.dirname(os.path.abspath(file_path)) + output_path = os.path.join(directory, 'extract.txt') + # 将结果保存到 extract.txt 文件中 + try: + with open(output_path, 'w', encoding='utf-8') as output_file: + output_file.write(result) + print(f"提取内容已保存到: {output_path}") + except IOError as e: + print(f"写入文件时发生错误: {e}") + # 返回保存的文件路径 + return output_path \ No newline at end of file diff --git a/flask_app/general/table_ocr.py b/flask_app/old_version/table_ocr_old.py similarity index 98% rename from flask_app/general/table_ocr.py rename to flask_app/old_version/table_ocr_old.py index 1621a46..410dabe 100644 --- a/flask_app/general/table_ocr.py +++ b/flask_app/old_version/table_ocr_old.py @@ -1,7 +1,5 @@ import os - import requests -import json def get_file_content(filePath): with open(filePath, 'rb') as fp: @@ -27,7 +25,6 @@ class CommonOcr(object): except Exception as e: return str(e) - if __name__ == "__main__": png_path = "test.png" ocr = CommonOcr(img_path=png_path) # 将路径传入构造函数 diff --git a/flask_app/old_version/不得存在及禁止投标情形_old.py b/flask_app/old_version/不得存在及禁止投标情形_old.py index 9a4fe1b..933863b 100644 --- a/flask_app/old_version/不得存在及禁止投标情形_old.py +++ b/flask_app/old_version/不得存在及禁止投标情形_old.py @@ -4,7 +4,7 @@ import re from PyPDF2 import PdfWriter, PdfReader -from flask_app.general.通义千问long import upload_file, qianwen_long +from flask_app.general.llm.通义千问long_plus import upload_file, qianwen_long from flask_app.general.通用功能函数 import process_string_list diff --git a/flask_app/工程标/判断是否分包等.py b/flask_app/old_version/判断是否分包等_old.py similarity index 97% rename from flask_app/工程标/判断是否分包等.py rename to flask_app/old_version/判断是否分包等_old.py index 71ea1e9..acc04d5 100644 --- a/flask_app/工程标/判断是否分包等.py +++ b/flask_app/old_version/判断是否分包等_old.py @@ -2,10 +2,10 @@ import json import os.path import re -from flask_app.general.json_utils import extract_content_from_json, clean_json_string # 可以选择性地导入特定的函数 -from flask_app.工程标.提取打勾符号 import read_pdf_and_judge_main -from flask_app.general.多线程提问 import multi_threading -from flask_app.general.通义千问long import qianwen_long,upload_file +from flask_app.general.json_utils import extract_content_from_json # 可以选择性地导入特定的函数 +from flask_app.old_version.提取打勾符号_old import read_pdf_and_judge_main +from flask_app.general.llm.多线程提问 import multi_threading +from flask_app.general.llm.通义千问long_plus import qianwen_long,upload_file #调用qianwen-ask之后,组织提示词问百炼。 def construct_judge_questions(json_data): diff --git a/flask_app/old_version/商务评分技术评分整合old_version.py b/flask_app/old_version/商务评分技术评分整合old_version.py index 3758959..cd80987 100644 --- a/flask_app/old_version/商务评分技术评分整合old_version.py +++ b/flask_app/old_version/商务评分技术评分整合old_version.py @@ -3,7 +3,7 @@ import json from flask_app.general.json_utils import clean_json_string from flask_app.general.商务技术评分提取 import combine_technical_and_business -from flask_app.general.通义千问long import upload_file, qianwen_long +from flask_app.general.llm.通义千问long_plus import upload_file, qianwen_long def combine_evaluation_standards(evaluation_method): # 商务标、技术标评分项:千问 diff --git a/flask_app/old_version/基础信息整合_old.py b/flask_app/old_version/基础信息整合_old.py index 2458fe9..309ee4e 100644 --- a/flask_app/old_version/基础信息整合_old.py +++ b/flask_app/old_version/基础信息整合_old.py @@ -1,10 +1,10 @@ import json -from flask_app.general.json_utils import clean_json_string, add_outer_key +from flask_app.general.json_utils import clean_json_string from flask_app.工程标.投标人须知正文提取指定内容工程标 import extract_from_notice -from flask_app.工程标.判断是否分包等 import judge_whether_main, read_questions_from_judge -from flask_app.general.多线程提问 import read_questions_from_file, multi_threading -from flask_app.general.通义千问long import upload_file +from flask_app.old_version.判断是否分包等_old import judge_whether_main, read_questions_from_judge +from flask_app.general.llm.多线程提问 import read_questions_from_file, multi_threading +from flask_app.general.llm.通义千问long_plus import upload_file from flask_app.general.通用功能函数 import judge_consortium_bidding def aggregate_basic_info_engineering(baseinfo_list): diff --git a/flask_app/old_version/形式响应评审old.py b/flask_app/old_version/形式响应评审old.py index ad2bbeb..34dba12 100644 --- a/flask_app/old_version/形式响应评审old.py +++ b/flask_app/old_version/形式响应评审old.py @@ -3,7 +3,7 @@ import re import json import time -from flask_app.general.多线程提问 import multi_threading +from flask_app.general.llm.多线程提问 import multi_threading from flask_app.工程标.根据条款号整合json import process_and_merge_entries,process_and_merge2 from flask_app.general.json_utils import extract_content_from_json from flask_app.工程标.截取pdf工程标版 import truncate_pdf_main diff --git a/flask_app/工程标/提取打勾符号.py b/flask_app/old_version/提取打勾符号_old.py similarity index 99% rename from flask_app/工程标/提取打勾符号.py rename to flask_app/old_version/提取打勾符号_old.py index 554672c..d124e8f 100644 --- a/flask_app/工程标/提取打勾符号.py +++ b/flask_app/old_version/提取打勾符号_old.py @@ -1,6 +1,5 @@ import re import PyPDF2 -import json def extract_key_value_pairs(text): # 更新正则表达式来包括对"团"的处理和行尾斜线 @@ -53,7 +52,6 @@ def extract_key_value_pairs(text): results[key] = "无" # 为键赋值"无" return results - def read_pdf_and_judge_main(file_path, output_txt_path): with open(file_path, 'rb') as file: reader = PyPDF2.PdfReader(file) @@ -80,9 +78,6 @@ def read_pdf_and_judge_main(file_path, output_txt_path): print(f"da_gou signal: Data extraction complete and saved to '{output_txt_path}'.") - - - if __name__ == "__main__": # 示例调用 file_path ="C:\\Users\Administrator\\Desktop\\fsdownload\\ee2d8828-bae0-465a-9171-7b2dd7453251\\ztbfile_tobidders_notice_table.pdf" diff --git a/flask_app/old_version/无效标和废标公共代码_old.py b/flask_app/old_version/无效标和废标公共代码_old.py index d046093..ec95737 100644 --- a/flask_app/old_version/无效标和废标公共代码_old.py +++ b/flask_app/old_version/无效标和废标公共代码_old.py @@ -4,8 +4,8 @@ import re import regex import time from concurrent.futures import ThreadPoolExecutor -from flask_app.general.doubao import generate_full_user_query -from flask_app.general.通义千问long import qianwen_plus +from flask_app.general.llm.doubao import generate_full_user_query +from flask_app.general.llm.通义千问long_plus import qianwen_plus from flask_app.general.通用功能函数 import process_string_list from collections import OrderedDict from docx import Document diff --git a/flask_app/old_version/无效标和废标和禁止投标整合_old.py b/flask_app/old_version/无效标和废标和禁止投标整合_old.py index 67bf587..dc9f000 100644 --- a/flask_app/old_version/无效标和废标和禁止投标整合_old.py +++ b/flask_app/old_version/无效标和废标和禁止投标整合_old.py @@ -5,7 +5,7 @@ import time import re from flask_app.general.format_change import pdf2docx -from flask_app.general.通义千问long import upload_file, qianwen_long +from flask_app.general.llm.通义千问long_plus import upload_file, qianwen_long from concurrent.futures import ThreadPoolExecutor from flask_app.general.table_content_extraction import extract_tables_main diff --git a/flask_app/old_version/评分标准提取main_old.py b/flask_app/old_version/评分标准提取main_old.py index 1c0eabe..4193539 100644 --- a/flask_app/old_version/评分标准提取main_old.py +++ b/flask_app/old_version/评分标准提取main_old.py @@ -4,7 +4,7 @@ import time from flask_app.general.json_utils import clean_json_string from flask_app.general.商务技术评分提取 import combine_technical_and_business, \ process_data_based_on_key, reorganize_data -from flask_app.general.通义千问long import upload_file, qianwen_long +from flask_app.general.llm.通义千问long_plus import upload_file, qianwen_long def combine_evaluation_standards(truncate_file): # 定义默认的评审结果字典 diff --git a/flask_app/old_version/资格审查模块old_old.py b/flask_app/old_version/资格审查模块old_old.py index f751aa4..e5b43de 100644 --- a/flask_app/old_version/资格审查模块old_old.py +++ b/flask_app/old_version/资格审查模块old_old.py @@ -4,7 +4,7 @@ from flask_app.old_version.提取json工程标版_old import convert_clause_to_j 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_old import process_qualification -from flask_app.general.通义千问long import upload_file, qianwen_long +from flask_app.general.llm.通义千问long_plus import upload_file, qianwen_long from concurrent.futures import ThreadPoolExecutor diff --git a/flask_app/old_version/资格评审old_old.py b/flask_app/old_version/资格评审old_old.py index 58a3c84..685272f 100644 --- a/flask_app/old_version/资格评审old_old.py +++ b/flask_app/old_version/资格评审old_old.py @@ -3,8 +3,8 @@ import json import re from flask_app.general.json_utils import clean_json_string, combine_json_results, add_keys_to_json -from flask_app.general.多线程提问 import multi_threading, read_questions_from_file -from flask_app.general.通义千问long import upload_file +from flask_app.general.llm.多线程提问 import multi_threading, read_questions_from_file +from flask_app.general.llm.通义千问long_plus import upload_file def merge_dictionaries_under_common_key(dicts, common_key): diff --git a/flask_app/routes/get_deviation.py b/flask_app/routes/get_deviation.py index 019f39a..5f13d77 100644 --- a/flask_app/routes/get_deviation.py +++ b/flask_app/routes/get_deviation.py @@ -4,7 +4,7 @@ import json from flask import Blueprint, Response, g import os from flask_app.general.format_change import download_file -from flask_app.routes.偏离表main import get_tech_and_business_deviation +from flask_app.routes.偏离表数据解析main import get_tech_and_business_deviation from flask_app.routes.utils import generate_deviation_response, validate_and_setup_logger, create_response, sse_format, \ log_error_unique_id from flask_app.ConnectionLimiter import require_connection_limit diff --git a/flask_app/routes/utils.py b/flask_app/routes/utils.py index 33cf5f8..3887139 100644 --- a/flask_app/routes/utils.py +++ b/flask_app/routes/utils.py @@ -5,8 +5,8 @@ from functools import wraps from flask import request, jsonify, current_app, g -from flask_app.general.清除file_id import read_file_ids, delete_file_by_ids -from flask_app.general.通义千问long import upload_file, qianwen_long +from flask_app.general.llm.清除file_id import read_file_ids, delete_file_by_ids +from flask_app.general.llm.通义千问long_plus import upload_file, qianwen_long from flask_app.logger_setup import create_logger diff --git a/flask_app/routes/偏离表main.py b/flask_app/routes/偏离表数据解析main.py similarity index 76% rename from flask_app/routes/偏离表main.py rename to flask_app/routes/偏离表数据解析main.py index c7bd244..94c2bba 100644 --- a/flask_app/routes/偏离表main.py +++ b/flask_app/routes/偏离表数据解析main.py @@ -7,7 +7,7 @@ from copy import deepcopy from flask_app.general.format_change import docx2pdf,doc2docx from flask_app.general.json_utils import clean_json_string, rename_outer_key from flask_app.general.merge_pdfs import merge_pdfs -from flask_app.general.通义千问long import qianwen_plus +from flask_app.general.llm.通义千问long_plus import qianwen_plus from flask_app.general.通用功能函数 import get_global_logger from flask_app.general.截取pdf_main import truncate_pdf_multiple from flask_app.货物标.提取采购需求main import fetch_procurement_reqs @@ -60,57 +60,52 @@ def prepare_for_zige_info(zige_review): return "", "", "" def extract_zige_deviation_table(zige_info, fuhe_info, zigefuhe_info): prompt_template1 = """ - 任务:给出一份文本,根据文本提取资格性检查的具体评审标准。 - 输出要求: - 1.以json格式返回结果,不要输出其他内容。 - 2.键名为"资格性检查",键值为字符串列表,每个字符串为一条评审标准,评审标准不分先后,不要有序号标注。 - 要求与指南: - 1. 评审标准是具体的内容,不要返回诸如'本项目的特定资格要求:'这种标题性质且不能体现具体评审标准的内容。 - 2. 若文本中存在相同或相似的表述,仅需取其中一个作为键值中的一条即可。 - - 文本内容:{full_text} +文本内容:{full_text} +任务:给出一份文本,根据文本提取资格性检查的具体评审标准。 +输出要求: + 1.以json格式返回结果,不要输出其他内容。 + 2.键名为"资格性检查",键值为字符串列表,每个字符串为一条评审标准,评审标准不分先后,不要有序号标注。 +要求与指南: + 1. 评审标准是具体的内容,不要返回诸如'本项目的特定资格要求:'这种标题性质且不能体现具体评审标准的内容。 + 2. 若文本中存在相同或相似的表述,仅需取其中一个作为键值中的一条即可。 """ - prompt_template2 = """ - 任务:给出一份文本,根据文本提取符合性检查的具体评审标准。 - 输出要求: - 1.以json格式返回结果,不要输出其他内容。 - 2.键名为"符合性检查",键值为字符串列表,每个字符串为一条评审标准,评审标准不分先后,不要有序号标注。 - 3.仔细检查你所选取的标准,若发现这些标准实际上是在描述不允许出现的符合性审查情况,则将外键替换为'符合性检查(以下情况不得出现)',并将这些标准写入其中。 - 要求与指南: - 1. 评审标准应该是具体的内容,不要返回诸如'本项目的特定符合性要求:'这种标题性质且不能体现具体评审标准的内容。 - 2. 若文本中存在相同或相似的表述,仅需取其中一个作为键值中的一条即可。 - 输出示例1: - {{ - "符合性检查": [ - "因素1", - "因素2", - ... - ] - }} - 输出示例2: - {{ - "符合性检查(以下情况不得出现)": [ - "因素1", - "因素2", - ... - ] - }} - - 文本内容:{full_text} - """ +文本内容:{full_text} +任务:给出一份文本,根据文本提取符合性检查的具体评审标准。 +输出要求: + 1.以json格式返回结果,不要输出其他内容。 + 2.键名为"符合性检查",键值为字符串列表,每个字符串为一条评审标准,评审标准不分先后,不要有序号标注。 + 3.仔细检查你所选取的标准,若发现这些标准实际上是在描述不允许出现的符合性审查情况,则将外键替换为'符合性检查(以下情况不得出现)',并将这些标准写入其中。 +要求与指南: + 1. 评审标准应该是具体的内容,不要返回诸如'本项目的特定符合性要求:'这种标题性质且不能体现具体评审标准的内容。 + 2. 若文本中存在相同或相似的表述,仅需取其中一个作为键值中的一条即可。 +输出示例1: + {{ + "符合性检查": [ + "因素1", + "因素2", + ... + ] + }} +输出示例2: + {{ + "符合性检查(以下情况不得出现)": [ + "因素1", + "因素2", + ... + ] + }} +""" prompt_template3 = """ - 任务:给出一份文本,根据文本提取资格性检查和符合性检查的具体评审标准。 - 输出要求: - 1.以json格式返回结果,不要输出其他内容。 - 2.键名为"资格性和符合性检查",键值为字符串列表,每个字符串为一条评审标准,评审标准不分先后,不要有序号标注。 - 要求与指南: - 1. 评审标准应该是具体的内容,不要返回诸如'本项目的特定符合性要求:'这种标题性质且不能体现具体评审标准的内容。 - 2. 若文本中存在相同或相似的表述,仅需取其中一个作为键值中的一条即可。 - - 文本内容:{full_text} - """ - +文本内容:{full_text} +任务:给出一份文本,根据文本提取资格性检查和符合性检查的具体评审标准。 +输出要求: + 1.以json格式返回结果,不要输出其他内容。 + 2.键名为"资格性和符合性检查",键值为字符串列表,每个字符串为一条评审标准,评审标准不分先后,不要有序号标注。 +要求与指南: + 1. 评审标准应该是具体的内容,不要返回诸如'本项目的特定符合性要求:'这种标题性质且不能体现具体评审标准的内容。 + 2. 若文本中存在相同或相似的表述,仅需取其中一个作为键值中的一条即可。 +""" def get_model_response(query): return qianwen_plus(query) @@ -276,7 +271,9 @@ def extract_business_deviation(busi_requirements_dict): renamed_requirements = rename_outer_keys(updated_requirements) business_requirements_string = json.dumps(renamed_requirements, ensure_ascii=False, indent=4) # print(business_requirements_string) - prompt_template1 = """以下文本是项目采购需求的商务要求部分,请帮我将信息重新组织,键名为'商务要求',键值为字符串列表,其中每个字符串为一条商务要求,保留三角▲、五角星★(若有),但是去除开头的序号(若有)。 + prompt_template1 = """ +文本内容:{full_text} +以上文本是项目采购需求的商务要求部分,请帮我将信息重新组织,键名为'商务要求',键值为字符串列表,其中每个字符串为一条商务要求,保留三角▲、五角星★(若有),但是去除开头的序号(若有)。 **角色** 你是一个专业的招投标业务专家,擅长从招标文件中总结商务要求的部分,并逐条列出,作为编写商务要求偏离表的前置准备。 @@ -333,67 +330,65 @@ def extract_business_deviation(busi_requirements_dict): "因投标人自身原因造成漏报、少报皆由其自行承担责任,采购人不再补偿。" ] }} - -文本内容:{full_text} - """ + """ user_query1 = prompt_template1.format(full_text=business_requirements_string) - prompt_template2 = """以下文本是项目采购需求的商务要求部分。请从中提取以★、▲或其他特殊符号开头的要求项,它们一般是重要的商务要求,需要额外响应。返回结果应仅包含一个键名“重要商务要求”,其键值为字符串列表,每个字符串对应一个以★、▲或特殊符号开头的要求项,但是去除开头的序号(若有)。 + prompt_template2 = """ +文本内容:{full_text} +以上文本是项目采购需求的商务要求部分。请从中提取以★、▲或其他特殊符号开头的要求项,它们一般是重要的商务要求,需要额外响应。返回结果应仅包含一个键名“重要商务要求”,其键值为字符串列表,每个字符串对应一个以★、▲或特殊符号开头的要求项,但是去除开头的序号(若有)。 - **要求与指南**: - 1. 每个以★、▲或其他特殊符号开头的要求项应作为单独的字符串。 - 2. 每条内容需要有实际的含义、要求,不能光有标题性质的表述如'★售后服务期限(质保期)及要求';对于类似'技术要求中带★条款项不满足的视为无效投标'这种描述带星★或带三角▲或带特殊符号的响应情况的,它本身不属于具体的★、▲商务要求,因此不需要添加进字符串列表中。 - 3. 你的回答内容需从所给文本中提取,尽量不改变原文的表达(除非以下要求与指南3.的特殊情况),请勿擅自添加三角▲、五角星★,请勿返回普通的商务要求。 - 4. 若无重要商务要求(即★、▲或其他特殊符号开头的商务要求项),键值为空列表,即[],无需额外返回说明性文字。 - 5. 若输入文本中存在嵌套键值对格式,且键值本身语义完整且符合'重要商务要求',可直接将其添加至'商务要求'的键值中;若键值字符串本身语义表达不完整,可将键值对用冒号':'拼接之后作为一条商务要求,若键值字符串以★、▲或其他特殊符号开头,将符号移至拼接后的开头,例如'"★交货地点:采购人指定地点"'。 - 6. 对于以三角▲或五角星★或其他特殊符号开头的字符串: - a. 如果该字符串仅为标题性质的表述且不具备实际商务要求的含义,请根据语义关联性将其开头的三角▲或五角星★添加到紧随其后的若干(可为一)内容之后,形成完整的商务要求,并确保整个内容连贯。 - 注:默认在该字符串后面的一个字符串开头添加三角▲或五角星★,若有明确的序号或者语义表示了其后若干字符串之间的相关性,那么可在这些字符串开头都添加三角▲或五角星★,作为若干商务要求。 - - 示例输入: - ``` - "★售后服务期限(质保期)及要求" - "提供高质量的售后服务,服务期限不少于两年。" - ``` - - 示例输出: - ``` - "★提供高质量的售后服务,服务期限不少于两年。" - ``` - b. 如果该字符串已经包含实际的商务要求,那么该内容作为一条完整的商务要求,保留开头的三角▲或五角星★。 - - 示例输入: - ``` - "★提供高质量的售后服务,服务期限不少于两年。" - ``` - - 示例输出: - ``` - "★提供高质量的售后服务,服务期限不少于两年。" - ``` - c. 无论哪种情况,都需确保不遗漏任何以三角▲或五角星★或其他特殊符号开头的重要信息。 +**要求与指南**: + 1. 每个以★、▲或其他特殊符号开头的要求项应作为单独的字符串。 + 2. 每条内容需要有实际的含义、要求,不能光有标题性质的表述如'★售后服务期限(质保期)及要求';对于类似'技术要求中带★条款项不满足的视为无效投标'这种描述带星★或带三角▲或带特殊符号的响应情况的,它本身不属于具体的★、▲商务要求,因此不需要添加进字符串列表中。 + 3. 你的回答内容需从所给文本中提取,尽量不改变原文的表达(除非以下要求与指南3.的特殊情况),请勿擅自添加三角▲、五角星★,请勿返回普通的商务要求。 + 4. 若无重要商务要求(即★、▲或其他特殊符号开头的商务要求项),键值为空列表,即[],无需额外返回说明性文字。 + 5. 若输入文本中存在嵌套键值对格式,且键值本身语义完整且符合'重要商务要求',可直接将其添加至'商务要求'的键值中;若键值字符串本身语义表达不完整,可将键值对用冒号':'拼接之后作为一条商务要求,若键值字符串以★、▲或其他特殊符号开头,将符号移至拼接后的开头,例如'"★交货地点:采购人指定地点"'。 + 6. 对于以三角▲或五角星★或其他特殊符号开头的字符串: + a. 如果该字符串仅为标题性质的表述且不具备实际商务要求的含义,请根据语义关联性将其开头的三角▲或五角星★添加到紧随其后的若干(可为一)内容之后,形成完整的商务要求,并确保整个内容连贯。 + 注:默认在该字符串后面的一个字符串开头添加三角▲或五角星★,若有明确的序号或者语义表示了其后若干字符串之间的相关性,那么可在这些字符串开头都添加三角▲或五角星★,作为若干商务要求。 + - 示例输入: + ``` + "★售后服务期限(质保期)及要求" + "提供高质量的售后服务,服务期限不少于两年。" + ``` + - 示例输出: + ``` + "★提供高质量的售后服务,服务期限不少于两年。" + ``` + b. 如果该字符串已经包含实际的商务要求,那么该内容作为一条完整的商务要求,保留开头的三角▲或五角星★。 + - 示例输入: + ``` + "★提供高质量的售后服务,服务期限不少于两年。" + ``` + - 示例输出: + ``` + "★提供高质量的售后服务,服务期限不少于两年。" + ``` + c. 无论哪种情况,都需确保不遗漏任何以三角▲或五角星★或其他特殊符号开头的重要信息。 - **禁止内容**: - 确保所有输出内容均基于提供的实际文本内容,不使用任何预设的示例作为回答,禁止编造回答。 +**禁止内容**: +确保所有输出内容均基于提供的实际文本内容,不使用任何预设的示例作为回答,禁止编造回答。 - **示例输入如下**: - {{ - "商务要求-1": ["▲(1)整个平台运行运维服务,须安排人员驻场对平台进行运行维护,采用 4人轮流值班,依照 7×12小时对可视化督察巡控平台进行操作,确保平台稳定运行。","▲ (一) 投标人","1.投标人需要获得 ISO9001 质量管理体系认证 、ISO 14001 环境管理体系认证及 OHSAS18001 职业健康安全管理体系认证。","2.投标人具备网络运营商资格。"] - "商务要求-2": {{ - "合同履行期限": ["★交货期(工期):合同签订之日起 15个日历天内完成,并通过项目验收。"], - "交货地点": ["★采购人指定地点"], - "报价方式": ["(1)本项目报价须为固定总价,包含但不限于:采购、实施、调试、试运行、验收、运维等所有完成本项目相关的一切费用。","(2)因投标人自身原因造成漏报、少报皆由其自行承担责任,采购人不再补偿。"], - "其他要求": ["无。"] - }} - }} - **对应的参考输出如下**: - {{ - "重要商务要求":[ - "▲整个平台运行运维服务,须安排人员驻场对平台进行运行维护,采用 4人轮流值班,依照 7×12小时对可视化督察巡控平台进行操作,确保平台稳定运行。", - "▲投标人 获得 ISO9001 质量管理体系认证 、ISO 14001 环境管理体系认证及 OHSAS18001 职业健康安全管理体系认证。", - "▲投标人具备网络运营商资格" - "★交货期(工期):合同签订之日起 15个日历天内完成,并通过项目验收。", - "★交货地点:采购人指定地点" - ] - }} - - 文本内容:{full_text} - """ +**示例输入如下**: +{{ + "商务要求-1": ["▲(1)整个平台运行运维服务,须安排人员驻场对平台进行运行维护,采用 4人轮流值班,依照 7×12小时对可视化督察巡控平台进行操作,确保平台稳定运行。","▲ (一) 投标人","1.投标人需要获得 ISO9001 质量管理体系认证 、ISO 14001 环境管理体系认证及 OHSAS18001 职业健康安全管理体系认证。","2.投标人具备网络运营商资格。"] + "商务要求-2": {{ + "合同履行期限": ["★交货期(工期):合同签订之日起 15个日历天内完成,并通过项目验收。"], + "交货地点": ["★采购人指定地点"], + "报价方式": ["(1)本项目报价须为固定总价,包含但不限于:采购、实施、调试、试运行、验收、运维等所有完成本项目相关的一切费用。","(2)因投标人自身原因造成漏报、少报皆由其自行承担责任,采购人不再补偿。"], + "其他要求": ["无。"] + }} +}} +**对应的参考输出如下**: +{{ + "重要商务要求":[ + "▲整个平台运行运维服务,须安排人员驻场对平台进行运行维护,采用 4人轮流值班,依照 7×12小时对可视化督察巡控平台进行操作,确保平台稳定运行。", + "▲投标人 获得 ISO9001 质量管理体系认证 、ISO 14001 环境管理体系认证及 OHSAS18001 职业健康安全管理体系认证。", + "▲投标人具备网络运营商资格" + "★交货期(工期):合同签订之日起 15个日历天内完成,并通过项目验收。", + "★交货地点:采购人指定地点" + ] +}} + """ user_query2 = prompt_template2.format(full_text=business_requirements_string) queries = [user_query1, user_query2] # 初始化结果列表,默认值为 None @@ -416,10 +411,8 @@ def extract_business_deviation(busi_requirements_dict): business_req_deviation = results[0] if results[0] is not None else default_return[0] business_star_req_deviation = results[1] if results[1] is not None else default_return[1] business_star_req_deviation=rename_outer_key(business_star_req_deviation,"商务要求带星") - return business_req_deviation, business_star_req_deviation - def get_tech_star_deviation(tech_requirements_dict): def get_tech_star_deviation_directly(tech_dict): """ @@ -474,7 +467,9 @@ def get_tech_star_deviation(tech_requirements_dict): if not tech_dict: return {} tech_string = json.dumps(tech_dict, ensure_ascii=False, indent=4) - prompt_template = """以下输入文本包含采购标的的技术参数要求或采购要求。请从每个键对应的字符串列表中提取带有星★或三角▲或其他特殊符号开头的的要求项。返回结果仅为符合要求的 JSON 格式对象,每个键名保持不变,键值为包含该键名下的带星或带三角或以其他特殊符合开头的要求项的字符串列表。 + prompt_template = """ +文本内容:{full_text} +以上输入文本包含采购标的的技术参数要求或采购要求。请从每个键对应的字符串列表中提取带有星★或三角▲或其他特殊符号开头的的要求项。返回结果仅为符合要求的 JSON 格式对象,每个键名保持不变,键值为包含该键名下的带星或带三角或以其他特殊符合开头的要求项的字符串列表。 要求与指南: 1. 仅保留符合条件的键值对:如果某键名下没有任何以星号(★)、三角符号(▲)或其他特殊符号开头的要求项,则该键名及其对应内容不包含在输出结果中。 2. 逐条提取:每个以星号(★)、三角符号(▲)或其他特殊符号开头的要求项,均作为独立的字符串保存在对应键的值列表中。 @@ -520,8 +515,6 @@ def get_tech_star_deviation(tech_requirements_dict): }} ### 对应的输出如下: {{}} - -输入文本内容:{full_text} """ user_query = prompt_template.format(full_text=tech_string) # 调用模型接口,假设qianwen_plus是已定义的模型调用函数 @@ -538,7 +531,9 @@ def get_tech_star_deviation(tech_requirements_dict): return get_tech_star_deviation_model(tech_requirements_dict) def get_proof_materials(all_data_info): - prompt_template = """以下文本是从招标文件中摘取的资格审查、采购需求、商务条款、技术评分相关内容。请根据这些内容,提取并列出投标人需要提交的证明材料。 + prompt_template = """ +文本内容:{full_text} +以上文本是从招标文件中摘取的资格审查、采购需求、商务条款、技术评分相关内容。请根据这些内容,提取并列出投标人需要提交的证明材料。 格式要求: 请以 JSON 格式返回结果: - 键名为 '证明材料'。 @@ -560,8 +555,6 @@ def get_proof_materials(all_data_info): "发射器:外壳需有正规厂家世标认证" ] }} - - 输入文本:{full_text} """ user_query=prompt_template.format(full_text=all_data_info) # print(user_query) diff --git a/flask_app/routes/判断是否是招标文件.py b/flask_app/routes/判断是否是招标文件.py index e2e649c..5cae9d0 100644 --- a/flask_app/routes/判断是否是招标文件.py +++ b/flask_app/routes/判断是否是招标文件.py @@ -1,10 +1,7 @@ import time -import multiprocessing -from concurrent.futures import ThreadPoolExecutor, TimeoutError -from queue import Queue from PyPDF2 import PdfReader # 确保已安装 PyPDF2: pip install PyPDF2 -from flask_app.general.通义千问long import upload_file, qianwen_long +from flask_app.general.llm.通义千问long_plus import upload_file, qianwen_long def judge_zbfile_exec(file_path): diff --git a/flask_app/routes/小解析main.py b/flask_app/routes/小解析main.py index d6f8cb2..9e958e4 100644 --- a/flask_app/routes/小解析main.py +++ b/flask_app/routes/小解析main.py @@ -5,8 +5,8 @@ import time from flask_app.general.format_change import docx2pdf from flask_app.general.json_utils import clean_json_string -from flask_app.general.多线程提问 import read_questions_from_file, multi_threading -from flask_app.general.通义千问long import upload_file +from flask_app.general.llm.多线程提问 import read_questions_from_file, multi_threading +from flask_app.general.llm.通义千问long_plus import upload_file from flask_app.general.通用功能函数 import get_global_logger,aggregate_basic_info from flask_app.general.截取pdf_main import truncate_pdf_multiple from flask_app.general.post_processing import inner_post_processing diff --git a/flask_app/routes/货物标解析main.py b/flask_app/routes/货物标解析main.py index d413cbf..a5b4b3c 100644 --- a/flask_app/routes/货物标解析main.py +++ b/flask_app/routes/货物标解析main.py @@ -36,7 +36,7 @@ def preprocess_files(output_folder, file_path, file_type,logger): truncate_files = truncate_pdf_multiple(pdf_path, output_folder,logger,'goods') # index: 0->商务技术服务要求 1->评标办法 2->资格审查 3->投标人须知前附表 4->投标人须知正文 # 处理各个部分 - invalid_path=truncate_files[6] if truncate_files[6] != "" else pdf_path #无效标 + invalid_path = truncate_files[6] if truncate_files[6] != "" else pdf_path #无效标 invalid_added_pdf = insert_mark(invalid_path) invalid_added_docx = pdf2docx(invalid_added_pdf) # 有标记的invalid_path @@ -271,7 +271,6 @@ def goods_bid_main(output_folder, file_path, file_type, unique_id): # 无法判断用户上传的是否为乱码文件,可以考虑并行调用大模型,如果为乱码文件直接return None # 国道107 在提取成json文件时,有'湖北众恒永业工程项目管理有限公司广水分公司编'干扰,尝试清除 -#截取json文件有些问题:C:\Users\Administrator\Desktop\新建文件夹 (3)\test keywords和special... if __name__ == "__main__": # 配置日志器 unique_id = "uuidzyzy11" diff --git a/flask_app/start_up.py b/flask_app/start_up.py index 5fad420..0ef95f5 100644 --- a/flask_app/start_up.py +++ b/flask_app/start_up.py @@ -1,13 +1,13 @@ # flask_app/start_up.py -from flask import Flask, request, g +from flask import Flask, g from flask_app.ConnectionLimiter import ConnectionLimiter from flask_app.logger_setup import create_logger_main 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 +from flask_app.general.llm.清除file_id import delete_file_by_ids,read_file_ids from flask_app.routes.judge_zbfile import judge_zbfile_bp class FlaskAppWithLimiter(Flask): def __init__(self, *args, **kwargs): diff --git a/flask_app/static/提示词/基本信息货物标.txt b/flask_app/static/提示词/基本信息货物标.txt index 15f5994..61cb981 100644 --- a/flask_app/static/提示词/基本信息货物标.txt +++ b/flask_app/static/提示词/基本信息货物标.txt @@ -64,5 +64,3 @@ - - diff --git a/flask_app/货物标/find_zbfile.py b/flask_app/test_case/find_zbfile.py similarity index 100% rename from flask_app/货物标/find_zbfile.py rename to flask_app/test_case/find_zbfile.py diff --git a/flask_app/test_case/test_doubao.py b/flask_app/test_case/test_doubao.py index ded4842..7b12c58 100644 --- a/flask_app/test_case/test_doubao.py +++ b/flask_app/test_case/test_doubao.py @@ -1,7 +1,7 @@ # -*- encoding:utf-8 -*- import concurrent.futures -from flask_app.general.doubao import doubao_model +from flask_app.general.llm.doubao import doubao_model # 多线程压力测试 diff --git a/flask_app/test_case/test_qianwen_long.py b/flask_app/test_case/test_qianwen_long.py index fd7439f..a7ce4cc 100644 --- a/flask_app/test_case/test_qianwen_long.py +++ b/flask_app/test_case/test_qianwen_long.py @@ -1,5 +1,5 @@ import concurrent.futures -from flask_app.general.通义千问long import qianwen_long, upload_file +from flask_app.general.llm.通义千问long_plus import qianwen_long, upload_file def multi_threaded_calls(file_id, user_query, num_threads=1): diff --git a/flask_app/工程标/资格评审5种情况测试脚本.py b/flask_app/test_case/资格评审5种情况测试脚本.py similarity index 100% rename from flask_app/工程标/资格评审5种情况测试脚本.py rename to flask_app/test_case/资格评审5种情况测试脚本.py diff --git a/flask_app/testdir/待合并代码.py b/flask_app/testdir/待合并代码.py deleted file mode 100644 index 44564f0..0000000 --- a/flask_app/testdir/待合并代码.py +++ /dev/null @@ -1,662 +0,0 @@ -import json -import os -import re -import regex -import time -from concurrent.futures import ThreadPoolExecutor -from flask_app.general.doubao import generate_full_user_query -from flask_app.general.insert_del_pagemark import insert_mark -from flask_app.general.通义千问long import qianwen_plus -from flask_app.general.通用功能函数 import process_string_list -from collections import OrderedDict -from docx import Document - - -def clean_dict_datas(extracted_contents, keywords, excludes): # 让正则表达式提取到的东西格式化 - all_text1 = {} # 用于存储 len(text_list) == 1 的结果,按序号保存 - all_text2 = {} # 用于存储 len(text_list) > 1 的结果,按序号保存 - # 定义用于分割句子的正则表达式,包括中文和西文的结束标点 - split_pattern = r'(?<=[。!?\!\?])' - clean_pattern = (r'^\s*(?:[((]\s*\d+\s*[)))]|' - r'[A-Za-z]?\d+(?:\.\s*\d+)*[\s\.、.)\)]+|' - r'[一二三四五六七八九十]+、|' - r'[A-Za-z][))\.、.]?\s*)') - idx = 0 # 共享的序号标记 - for key, text_list in extracted_contents.items(): - if len(text_list) == 1: - for data in text_list: - # print(data) - # 检查是否包含任何需要排除的字符串 - if any(exclude in data for exclude in excludes): - continue # 如果包含任何排除字符串,跳过这个数据 - # 去掉开头的序号,eg:1 | (1) |(2) | 1. | 2.(全角点)| 3、 | 1.1 | 2.3.4 | A1 | C1.1 | 一、 - data = re.sub(clean_pattern, '', data).strip() - keyword_match = re.search(keywords, data) - if keyword_match: - # 从关键词位置开始查找结束标点符号 - start_pos = keyword_match.start() - # 截取从关键词开始到后面的内容 - substring = data[start_pos:] - # 按定义的结束标点分割 - sentences = re.split(split_pattern, substring, 1) - if len(sentences) > 0 and sentences[0]: - # 只取第一句,保留标点 - cleaned_text = data[:start_pos] + sentences[0] # eg:经采购人允许,潜在投标人可进入项目现场进行考察,但潜在投标人不得因此使采购人承担有关责任和蒙受损失。潜在投标人应自行承担现场考察的全部费用、责任和风险。 - # 经采购人允许,潜在投标人可进入项目现场进行考察,但潜在投标人不得因此使采购人承担有关责任和蒙受损失。 - else: - cleaned_text = data # 如果没有标点,使用整个字符串 - else: - # 如果没有找到关键词,保留原文本 - cleaned_text = data - # 删除空格 - cleaned_text_no_spaces = cleaned_text.replace(' ', '').replace(' ', '') - # 如果长度大于8,则添加到结果列表 - if len(cleaned_text_no_spaces) > 8: - all_text1[idx] = cleaned_text_no_spaces - idx += 1 # 更新共享序号 - else: - # print(text_list) - # print("*********") - # 用于处理结构化文本,清理掉不必要的序号,并将分割后的段落合并,最终形成更简洁和格式化的输出。 - data = re.sub(clean_pattern, '', text_list[0]).strip() # 只去除第一个的序号 - # 将修改后的第一个元素和剩余的元素连接起来 - text_list[0] = data # 更新列表中的第一个元素 - joined_text = "\n".join(text_list) # 如果列表中有多个元素,则连接它们 - # 删除空格 - joined_text_no_spaces = joined_text.replace(' ', '').replace(' ', '') - all_text2[idx] = joined_text_no_spaces - idx += 1 # 更新共享序号 - - return all_text1, all_text2 # all_texts1要额外用gpt all_text2直接返回结果 - -#处理跨页的段落 -def preprocess_paragraphs(elements): - processed = [] # 初始化处理后的段落列表 - index = 0 - flag = False # 初始化标志位 - is_combine_table = False - - # 定义两个新的正则表达式模式 - pattern_numbered = re.compile(r'^\s*([一二三四五六七八九十]{1,2})\s*、\s*') - pattern_parentheses = re.compile(r'^\s*[((]\s*([一二三四五六七八九十]{1,2})\s*[))]\s*') - - # 定义列表项的模式 - list_item_pattern = re.compile( - r'^\s*(' - r'[(\(]\d+[)\)]|' # 匹配:(1) 或 (1) - r'[A-Za-z]\.\s*|' # 匹配:A. 或 b. - r'[一二三四五六七八九十]+、|' # 匹配:一、二、三、 - r'第[一二三四五六七八九十百零]+[章节部分节]|' # 匹配:第x章,第x部分,第x节 - r'[A-Za-z]\d+(?:\.\d+)*[\s\.、.)\)]?|' # 匹配:A1.2 等 - r'\d+(?:\.\d+)+[\s\.、.)\)]?(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万])|' # 匹配:数字序号如1.1 1.1.1 - r'(?=\d+\s(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万]))|' # 数字后空格,空格后非指定关键字 - r'(?=\d+[、..])(?!\s*[号条款节章项例页段部步点年月日时分秒个元千万])' # 数字后直接跟顿号或点号 - r')' - ) - - # 新增的正则表达式,用于匹配以数字序号开头的段落 - pattern_numeric_header = re.compile( - r'^(? max_space_count for space in re.findall(r'\s+', text)) - - # 正则表达式用于检测页面标记 - pattern_marker = re.compile(r'\$\$index_mark_\d+\$\$') - - # 辅助函数:查找上一个非空且非标记的段落 - def find_prev_text(current_index): - for i in range(current_index - 1, -1, -1): - if isinstance(elements[i], str): - return '', -1 - try: - text = elements[i].text.strip() - except AttributeError: - continue # 如果段落对象没有 text 属性,跳过 - if text and not pattern_marker.search(text): - return text, i - return '', -1 - - # 辅助函数:查找下一个非空且非标记的段落 - def find_next_text(current_index): - for i in range(current_index + 1, len(elements)): - if isinstance(elements[i], str): - return '', -1 - try: - text = elements[i].text.strip() - except AttributeError: - continue # 如果段落对象没有 text 属性,跳过 - # 跳过空白段落和页面标记 - if not text or pattern_marker.search(text): - continue - # 跳过匹配排除模式的段落 - if (pattern_numbered.match(text) or pattern_parentheses.match(text)) and len(text) < 8: - continue - return text, i - return '', -1 - - while index < len(elements): - if isinstance(elements[index], str): - processed.append(elements[index]) - index += 1 - continue - try: - current_text = elements[index].text.strip() # 去除当前段落的前后空白 - except AttributeError: - # 如果段落对象没有 text 属性,跳过该段落 - index += 1 - continue - - # 检查当前段落是否为页面标记 - if pattern_marker.search(current_text): - # 动态查找前一个非空段落 - prev_text, prev_index = find_prev_text(index) - # 动态查找后一个非空段落 - next_text, next_index = find_next_text(index) - - # 应用现有的合并逻辑 - if prev_text and next_text and not has_long_spaces(prev_text) and not has_long_spaces(next_text): - if not prev_text.endswith(('。', '!', '?')): # ',', ',', 先注释了,如果逗号,可能还没结束。 - # 检查后一个段落是否为列表项 - if not list_item_pattern.match(next_text) and len(prev_text) > 30: - # 合并前后段落 - merged_text = prev_text + ' ' + next_text # 为了可读性添加空格 - if prev_index < len(elements): - # 移除 processed 中的前一个段落 - if processed and processed[-1] == prev_text: - processed.pop() - # 添加合并后的文本 - processed.append(merged_text) - - # 跳过标记以及前后所有空白段落,直到 next_index - index = next_index + 1 - continue # 继续下一个循环 - - # 如果不满足合并条件,跳过标记及其周围的空白段落 - # 计算下一个需要处理的索引 - # 从当前 index 向下,跳过所有连续的空白段落和标记 - skip_index = index + 1 - while skip_index < len(elements): - if isinstance(elements[skip_index], str): - break - try: - skip_text = elements[skip_index].text.strip() - except AttributeError: - skip_index += 1 - continue # 如果段落对象没有 text 属性,跳过 - if skip_text == '' or pattern_marker.search(skip_text): - skip_index += 1 - else: - break - index = skip_index - continue # 继续下一个循环 - - # 检查当前段落是否匹配任一排除模式 - if (pattern_numbered.match(current_text) or pattern_parentheses.match(current_text)) and len(current_text) < 8: - # 如果匹配,则跳过当前段落,不添加到processed列表中 - index += 1 - continue - - # 检查是否为以数字序号开头的段落 - match = pattern_numeric_header.match(current_text) - if not match: - match = pattern_numeric_header_fallback.match(current_text) - - if match: - # 当前段落以数字序号开头,直接添加到 processed - processed.append(current_text) - flag = True # 设置标志位,准备处理下一个段落 - index += 1 - continue - else: - if flag: - if not list_item_pattern.match(current_text): - if processed: - # **新增逻辑开始** - next_non_empty_text, next_non_empty_index = find_next_text(index) - is_next_numbered = False - if next_non_empty_text: - is_next_numbered = bool( - pattern_numeric_header.match(next_non_empty_text) or - pattern_numeric_header_fallback.match(next_non_empty_text) - ) - - if is_next_numbered and len(processed[-1]) > 30: - # 只有在下一个段落以数字序号开头且上一个段落长度大于30时,才将当前段落追加到上一个段落 - processed[-1] = processed[-1] + ' ' + current_text - else: - # 否则,不追加,而是作为新的段落添加 - processed.append(current_text) - # **新增逻辑结束** - else: - # **新增处理:匹配 list_item_pattern 的段落也应被保存** - processed.append(current_text) - # 无论是否追加,都将 flag 重置 - flag = False - index += 1 - continue - else: - # flag 为 False,直接添加到 processed - processed.append(current_text) - index += 1 - continue - - return processed - -def extract_text_with_keywords(processed_paragraphs, keywords, follow_up_keywords): - if isinstance(keywords, str): - keywords = [keywords] - extracted_paragraphs = OrderedDict() - continue_collecting = False - current_section_pattern = None - active_key = None - - def match_keywords(text, patterns): - # 首先检查关键词是否匹配 - for pattern in patterns: - if re.search(pattern, text, re.IGNORECASE): - return True - return False - - def extract_from_text(text, current_index): - nonlocal continue_collecting, current_section_pattern, active_key - if text == "": - return current_index - - if continue_collecting: - # 如果是收集状态,并且下面有表格,则把表格内容全部追加到active_key中去 - if text == '[$$table_start$$]': - current_index += 1 - while (processed_paragraphs[current_index] != '[$$table_over$$]'): - extracted_paragraphs[active_key].append(processed_paragraphs[current_index]) - current_index += 1 - return current_index - if current_section_pattern and re.match(current_section_pattern, text): - continue_collecting = False - active_key = None - else: - if active_key is not None: - extracted_paragraphs[active_key].append(text) - return current_index - - if match_keywords(text, keywords): - active_key = text - extracted_paragraphs[active_key] = [text] - if match_keywords(text, follow_up_keywords): - continue_collecting = True - section_number = re.match(r'^(\d+([..]\d+)*)\s*[..]?', text) # 修改后的正则,支持 '数字 、' 和 '数字.' - if section_number: #当前匹配的行前有序号,那么就匹配到下个相似序号为止停止收集 - current_section_number = section_number.group(1) - level_count = current_section_number.count('.') - # 获取章节的各级部分 - parts = current_section_number.split('.') - # Pattern to match current level, e.g., 3.4.5 添加负向前瞻以防止匹配四级或更高层级 - pattern = r'^' + (r'\d+\s*[..]\s*') * level_count + r'\d+' + r'(?!\s*[..]\s*\d+)' - matched_patterns = [pattern] # start with the full pattern - - # for i in range(1, 6): #同级,与matched_patterns = [pattern]重复了,故注释 - # # 复制 parts 列表以避免修改原列表 - # new_parts = parts. copy() - # new_parts[-1] = str(int(new_parts[-1]) + i) - # # 使用不同的分隔符 - # next_pattern = r'^' + r'\s*[..]\s*'.join(new_parts) - # matched_patterns.append(next_pattern) - - # Parent section (if applicable) - if len(parts) > 1: - for i in range(1, 6): #考虑原文档的书写不规范,跳序号的情况,目前设置了范围<5 - parent_section_parts = parts[:-1].copy() - parent_section_parts[-1] = str(int(parent_section_parts[-1]) + i) - parent_pattern = r'^' + r'\s*[..]\s*'.join(parent_section_parts)+ r'(?!\s*[..]\s*\d+)' - matched_patterns.append(parent_pattern) - - # 添加对 '数字 、' 格式的支持 - digit_comma_pattern = r'^\d+\s*、' - matched_patterns.append(digit_comma_pattern) - - # 获取当前顶级章节编号 - current_top_level_num = int(current_section_number.split('.')[0]) - for i in range(1, 6): - next_top_level_num = current_top_level_num + i - next_top_level_pattern = r'^' + str(next_top_level_num) + r'\s*[..]' - # 检查是否已经包含了该模式,避免重复 - if next_top_level_pattern not in matched_patterns: - matched_patterns.append(next_top_level_pattern) - - # Combine the patterns - combined_pattern = r'(' + r')|('.join(matched_patterns) + r')' - current_section_pattern = re.compile(combined_pattern) - - else: - found_next_number = False - current_section_pattern = None - - while current_index < len(processed_paragraphs) - 1: - current_index += 1 - next_text = processed_paragraphs[current_index].strip() - # 添加对空白行的处理 - if not next_text: - continue # 跳过空白行,进入下一个循环 - if not found_next_number: - # 修改后的正则,支持 '数字 、' 格式 - next_section_number = re.match(r'^([A-Za-z0-9]+(?:[..][A-Za-z0-9]+)*)|([((]\s*\d+\s*[))])|(\d+\s*、)', - next_text) - if next_section_number: - found_next_number = True - if next_section_number.group(1): - section_parts = next_section_number.group(1).split('.') - dynamic_pattern = r'^' + r'[..]'.join( - [r'[A-Za-z0-9]+' for _ in section_parts]) + r'\b' - elif next_section_number.group(2): - dynamic_pattern = r'^[\(\(]\s*\d+\s*[\)\)]' - elif next_section_number.group(3): - dynamic_pattern = r'^\d+\s*、' - current_section_pattern = re.compile(dynamic_pattern) - if current_section_pattern and re.match(current_section_pattern, next_text): - extracted_paragraphs[active_key].append(next_text) - else: - continue_collecting = False - active_key = None - break - - return current_index - - index = 0 - while index < len(processed_paragraphs): - # print(processed_paragraphs[index].strip()) - index = extract_from_text(processed_paragraphs[index].strip(), index) - # print("--------------") - index += 1 - return extracted_paragraphs - -# 分割表格中单元格文本 -def split_cell_text(text): - # 定义用于提取括号内容的正则表达式,支持中英文括号 - bracket_pattern = re.compile(r'[((][^(()))]+[))]') - - # 1. 先提取并替换括号内容 - bracket_contents = [] - - def replace_bracket_content(match): - bracket_contents.append(match.group(0)) # 保存括号内容 - return f"" # 使用占位符替换括号内容 - - item_with_placeholders = bracket_pattern.sub(replace_bracket_content, text) - # print("-----------") - # print(text) - # 2. 分割句子,保证句子完整性(按标点符号和序号分割) - split_sentences = regex.split( - r'(?<=[。!?!?\?])|' # 在中文句号、感叹号、问号后分割 - r'(?", lambda m: bracket_contents[int(m.group(1))], s) for s in - split_sentences] - # 4. 过滤空字符串 - split_sentences = [s for s in split_sentences if s.strip()] - # print(split_sentences) - return split_sentences - -# 文件预处理----按文件顺序提取文本和表格,并合并跨页表格 -def extract_file_elements(file_path): - doc = Document(file_path) - doc_elements = doc.element.body - doc_paragraphs = doc.paragraphs - doc_tables = doc.tables - pre_table_head = None - table_combine = False - paragraph_index = 0 - tables_index = 0 - doc_contents = [] - - pattern_marker = re.compile(r'\$\$index_mark_\d+\$\$') - - # 遍历文件元素 - for element in doc_elements: - # 如果是段落 - if element.tag.endswith('}p'): - if pre_table_head: - text = doc_paragraphs[paragraph_index].text - # 如果上一个是表格,并且之后没有文本或为跨页标记,则不提取 - if (text == '' or pattern_marker.search(text)): - paragraph_index += 1 - continue - # 如果遇到有效文本,则说明表格提取完毕 - else: - doc_contents.append('[$$table_over$$]') - table_combine = False - pre_table_head = None - doc_contents.append(doc_paragraphs[paragraph_index]) - paragraph_index += 1 - # 如果是表格 - elif element.tag.endswith('}tbl'): - table = doc_tables[tables_index] - table_content = [] - for row_idx, row in enumerate(table.rows): - if row_idx == 0: - # 跳过表头 - if pre_table_head: - table_combine = True - if pre_table_head == row.cells[0].text: - continue - # 记录初始表头 - else: - pre_table_head = row.cells[0].text - doc_contents.append('[$$table_start$$]') - continue - # 遍历每一行中的单元格 - for cell in row.cells: - cell_text = cell.text.strip() # 去除单元格内容前后空白 - if len(cell_text) > 8: # 检查文字数量是否大于8 - cell_text = split_cell_text(cell_text) - table_content += cell_text - # 合并跨页表格 - if table_combine: - if not doc_contents[-1].endswith(('。', '!', '?', ';')): - doc_contents[-1] += ' ' + table_content[0] - table_content.pop(0) - doc_contents.extend(table_content) - # doc_contents.append('[$$table_over$$]') - tables_index += 1 - return doc_contents - -def handle_query(file_path, user_query, output_file, result_key, keywords): - try: - excludes = ["说明表", "重新招标", "否决所有", "否决投标的条件", "本人保证:", "我方"] - follow_up_keywords = [ - r'情\s*形\s*之\s*一', - r'情\s*况\s*之\s*一', - r'下\s*列(?!\s*公式)', # 增加负向前瞻,排除“下列公式” - r'以\s*下(?!\s*公式)', # 增加负向前瞻,排除“以下公式” - r'其\s*他.*?情\s*形\s*[::]', - r'包\s*括' - ] - - doc_contents = extract_file_elements(file_path) - processed_paragraphs = preprocess_paragraphs(doc_contents) - extracted_contents = extract_text_with_keywords(processed_paragraphs, [keywords], follow_up_keywords) - all_texts1,all_texts2 = clean_dict_datas(extracted_contents, keywords, excludes) # 列表 - - # 1. 得到有序的 all_text1_items - all_text1_items = sorted(all_texts1.items(), key=lambda x: x[0]) - # 2. 得到纯内容列表 - all_texts1_list = [content for (_, content) in all_text1_items] - # print(all_texts) - # Proceed only if there is content to write - selected_contents = {} - final_list=[] - seen_contents = set() # 使用集合跟踪已添加的内容以去重 - if all_texts1_list: - with open(output_file, 'w', encoding='utf-8') as file: - counter = 1 - for content in all_texts1_list: - # 使用内容的前25个字符作为去重的依据 - key = content[:25] # 提取前25个字符 - if key not in seen_contents: # 如果前30个字符未出现过 - file.write(f"{counter}. {content}\n") - file.write("..............." + '\n') - seen_contents.add(key) # 标记前30个字符为已写入 - counter += 1 - - # 生成用户查询 - user_query = generate_full_user_query(output_file, user_query) - model_ans = qianwen_plus(user_query) # 豆包模型返回结果 - # file_id = upload_file(output_file) - # model_ans = qianwen_long(file_id, user_query) - num_list = process_string_list(model_ans) # 处理模型返回的序号 - print(result_key + "选中的序号:" + str(num_list)) - - for index in num_list: - if 1 <= index <= len(all_texts1_list): - original_global_idx = all_text1_items[index - 1][0] - content = all_text1_items[index - 1][1] - selected_contents[original_global_idx] = content - # 把选中的 all_text1 内容 + all_text2 合并 - merged_dict = {} - # 先合并用户选中的 all_text1 - for idx, txt in selected_contents.items(): - merged_dict[idx] = txt - # 再合并 all_text2 - for idx, txt in all_texts2.items(): - merged_dict[idx] = txt - final_list = [txt for idx, txt in sorted(merged_dict.items(), key=lambda x: x[0])] - - return {result_key: final_list} - except Exception as e: - print(f"handle_query 在处理 {result_key} 时发生异常: {e}") - return {result_key: []} - -def combine_find_invalid(invalid_docpath, output_dir): - os.makedirs(output_dir, exist_ok=True) - queries = [ - ( - r'否\s*决|' - r'无\s*效\s*投\s*标|' - r'无\s*效\s*文\s*件|' - r'(?:文\s*件|投\s*标|响\s*应)\s*[\u4e00-\u9fa5]?\s*(?:无|失)\s*效|' - r'无\s*效\s*响\s*应|' - r'无\s*效\s*报\s*价|' - r'无\s*效\s*标|' - r'视\s*为\s*无\s*效|' - r'被\s*拒\s*绝|' - r'将\s*拒\s*绝|' - r'予\s*以\s*拒\s*绝', - """以下是从招标文件中摘取的内容,文本中序号分明,各信息之间以...............分割。 -任务目标: -从文本中筛选所有描述否决投标,拒绝投标,投标、响应无效或类似表述的情况,并返回对应的序号。 -要求与指南: - 文本中可能存在无关的信息,请准确筛选符合条件的信息,并将符合条件的信息的序号返回。 -输出格式: - 以 [x, x, x] 的形式返回,x 为符合条件的信息的序号,为自然数。 - 如果文本中没有符合条件的信息,请返回 []。 -特殊情况: - 如果某序号的内容明显分为几部分且一部分内容符合筛选条件,但其他部分明显是无关内容,请返回符合部分的字符串内容代替序号。 -示例输出,仅供格式参考: - [1,3,4,6] -文本内容:{full_text} - """, - os.path.join(output_dir, "temp1.txt"), - "否决和无效投标情形" - ), -# ( -# r'废\s*标', -# """以下是从招标文件中摘取的内容,文本中序号分明,文本内之间的信息以'...............'分割。 -# 任务目标: -# 请根据以下内容,筛选出 废标项的情况 (明确描述导致 废标 的情况)并返回对应的序号。 -# 要求与指南: -# 文本中可能存在无关的信息,请准确筛选符合条件的信息,并将符合条件的信息的序号返回。 -# 输出格式: -# 返回结果以 [x, x, x] 的形式,其中 x 为符合条件的信息的序号,为自然数。 -# 如果文本中没有任何符合条件的废标情况,请返回 []。 -# 示例输出,仅供格式参考: -# [1,3,4,6] -# 文本内容:{full_text} -# """, -# os.path.join(output_dir, "temp2.txt"), -# "废标项" -# ), -# ( -# r'不\s*得(?!\s*(分|力))|禁\s*止\s*投\s*标', -# """以下是从招标文件中摘取的内容,文本中序号分明,文本内的条款以'...............'分割。条款规定了各方不得存在的情形。请根据以下要求进行筛选: -# **投标相关主体与非投标相关主体的定义**: -# 投标相关主体:包括但不限于“投标人”、“中标人”、“供应商”、“联合体投标各方”、“响应人”、“应答人”或其他描述投标方的词语。 -# 非投标相关主体:包括但不限于“招标人”、“采购人”、“评标委员会”或其他描述非投标方的词语。 -# **筛选要求**: -# 1. **仅筛选**明确描述投标相关主体禁止情形或不得存在的情形的条款,不包含笼统或未具体说明情形的条款。例如: -# 若条款内容包含'投标人不得存在的其他关联情形'这样的笼统描述,而未说明具体的情形,则无需添加该条款。 -# 2. **排除**仅描述非投标相关主体行为限制或禁止情形的条款,例如“招标人不得泄露信息”或“评标委员会不得收受贿赂”,则无需返回。 -# 3. 若条款同时描述了对投标相关主体与非投标相关主体的行为限制、禁止情形,也需返回。 -# 4. **特殊情况**:如果条款中包含“磋商小组”、”各方“等既能指代投标相关主体又能指代非投标相关主体的词汇: -# 若在语境中其指代或包含投标相关主体,则应将其考虑在内;否则,排除该条款。 -# -# **输出格式**: -# 返回结果以 [x, x, x] 的形式,其中 x 为符合条件的条款的序号,为自然数。 -# 如果没有符合条件的条款,返回 `[]`。 -# **示例**: -# - **符合条件**: -# - `1. 投标人不得...` → 包含,返回序号 1。 -# - `3. 联合体投标各方不得...` → 包含,返回序号 3。 -# - **不符合条件**: -# - `2. 采购人不得...` → 主语为“采购人”,排除。 -# -示例输出: [1,3] -# 请根据上述筛选要求,阅读以下文本内容,并返回符合条件的条款序号, -# -# 文本内容:{full_text} -# """, -# os.path.join(output_dir, "temp3.txt"), -# "不得存在的情形" -# ) - ] - results = [] - - # 使用线程池来并行处理查询 - with ThreadPoolExecutor() as executor: - futures = [] - for keywords, user_query, output_file, result_key in queries: - future = executor.submit(handle_query, invalid_docpath, user_query, output_file, result_key, keywords) - futures.append((future, result_key)) # 保持顺序 - time.sleep(0.5) # 暂停0.5秒后再提交下一个任务 - - for future, result_key in futures: - try: - result = future.result() - except Exception as e: - print(f"线程处理 {result_key} 时出错: {e}") - result = {result_key: ""} - results.append(result) - combined_dict = {} - for d in results: - combined_dict.update(d) - - print("无效标与废标done...") - return {"无效标与废标项": combined_dict} - -if __name__ == '__main__': - start_time = time.time() - # truncate_json_path = "C:\\Users\\Administrator\\Desktop\\货物标\\output4\\tmp2\\竞争性谈判文件(3)_tobidders_notice_part1\\truncate_output.json" - # truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output4\\招标文件(实高电子显示屏)_tobidders_notice_part1.docx" - # clause_path = "D:\\flask_project\\flask_app\\static\\output\\output1\\77a48c63-f39f-419b-af2a-7b3dbf41b70b\\clause1.json" - # doc_path="C:\\Users\\Administrator\\Desktop\\货物标\\zbfilesdocx\\磋商文件(1).docx" - # doc_path = r'C:\Users\Administrator\Desktop\new招标文件\tmp\2024-贵州-贵州省罗甸县 2024 年度广州市协作资金龙坪镇、边阳镇产业路硬化建设项目.docx' - pdf_path = r'C:\Users\Administrator\Desktop\货物\test5\磋商采购文件-恩施市森林火灾风险普查样品检测服务.pdf' - - output_dir = r"C:\Users\Administrator\Desktop\货物\test5" - # invalid_added = insert_mark(pdf_path) - # invalid_added_docx = pdf2docx(invalid_added) - invalid_added_docx=r'C:\Users\Administrator\Desktop\货物\test3\invalid_added.docx' - results = combine_find_invalid(invalid_added_docx, output_dir) - end_time = time.time() - print("Results:", json.dumps(results, ensure_ascii=False, indent=4)) - # print("Elapsed time:", str(end_time - start_time)) \ No newline at end of file diff --git a/flask_app/工程标/find_tbformat.py b/flask_app/工程标/find_tbformat.py deleted file mode 100644 index 5575cdd..0000000 --- a/flask_app/工程标/find_tbformat.py +++ /dev/null @@ -1,42 +0,0 @@ -import PyPDF2 -import re - -def extract_contents_with_pages(pdf_path, keyword): - with open(pdf_path, "rb") as file: - reader = PyPDF2.PdfReader(file) - for page_number in range(len(reader.pages)): - page = reader.pages[page_number] - text = page.extract_text() - if text: - lines = text.split('\n') - for line in lines: - if keyword.lower() in line.lower(): - match = re.search(r"\d+(?=\s*$)", line) - if match: - return int(match.group(0)) # 直接返回整数类型的页码 - return None # 如果遍历完所有页面后仍未找到页码,返回None - -def split_pdf(pdf_path, start_page, output_path): - """切分PDF文件从start_page到end_page""" - with open(pdf_path, "rb") as file: - reader = PyPDF2.PdfReader(file) - writer = PyPDF2.PdfWriter() - end_page = len(reader.pages) - # 确保start_page是整数 - start_page = int(start_page) - # 注意页码从0开始,因此需要调整页码索引 - for i in range(start_page - 1, end_page): - writer.add_page(reader.pages[i]) - with open(output_path, "wb") as output_pdf: - writer.write(output_pdf) - -# 使用示例 -pdf_path = "D:\\项目\\工程招标\\zb1.pdf" -output_path = "D:\\项目\\工程招标\\tb_format.pdf" -keyword = "投标文件格式" # 修改为你想查找的关键字 -page_number = extract_contents_with_pages(pdf_path, keyword) -print(page_number) -if page_number is not None: - split_pdf(pdf_path, page_number, output_path) -else: - print("未找到含有关键字的页码") diff --git a/flask_app/工程标/基础信息整合工程标.py b/flask_app/工程标/基础信息整合工程标.py index ef37e05..6ee2f25 100644 --- a/flask_app/工程标/基础信息整合工程标.py +++ b/flask_app/工程标/基础信息整合工程标.py @@ -5,9 +5,9 @@ import concurrent.futures from flask_app.general.json_utils import clean_json_string, add_outer_key from flask_app.general.通用功能函数 import process_judge_questions, aggregate_basic_info from flask_app.general.投标人须知正文提取指定内容 import extract_from_notice -from flask_app.工程标.判断是否分包等 import merge_json_to_list -from flask_app.general.多线程提问 import read_questions_from_file, multi_threading -from flask_app.general.通义千问long import upload_file +from flask_app.old_version.判断是否分包等_old import merge_json_to_list +from flask_app.general.llm.多线程提问 import read_questions_from_file, multi_threading +from flask_app.general.llm.通义千问long_plus import upload_file def update_baseinfo_lists(baseinfo_list1, baseinfo_list2): # 创建一个字典,用于存储 baseinfo_list1 中的所有键值对 diff --git a/flask_app/工程标/形式响应评审.py b/flask_app/工程标/形式响应评审.py index 9c4eabe..3ee1275 100644 --- a/flask_app/工程标/形式响应评审.py +++ b/flask_app/工程标/形式响应评审.py @@ -4,34 +4,12 @@ import re import json import time -from flask_app.general.多线程提问 import multi_threading +from flask_app.general.llm.多线程提问 import multi_threading from flask_app.工程标.根据条款号整合json import process_and_merge_entries,process_and_merge2 from flask_app.general.json_utils import clean_json_string from flask_app.general.投标人须知正文条款提取成json文件 import convert_clause_to_json -from flask_app.general.通义千问long import upload_file +from flask_app.general.llm.通义千问long_plus import upload_file from flask_app.general.merge_pdfs import merge_pdfs -prompt = """ -# 角色 -你是一个文档处理专家,专门负责理解和操作基于特定内容的文档任务,这包括解析、总结、搜索或生成与给定文档相关的各类信息。 - -## 技能 -### 技能 1:文档解析与摘要 -- 深入理解并分析${document1}的内容,提取关键信息。 -- 根据需求生成简洁明了的摘要,保持原文核心意义不变。 - -### 技能 2:信息检索与关联 -- 在${document1}中高效检索特定信息或关键词。 -- 能够识别并链接到文档内部或外部的相关内容,增强信息的连贯性和深度。 - -## 限制 -- 所有操作均需基于${document1}的内容,不可超出此范围创造信息。 -- 在处理敏感或机密信息时,需遵守严格的隐私和安全规定。 -- 确保所有生成或改编的内容逻辑连贯,无误导性信息。 - -请注意,上述技能执行时将直接利用并参考${document1}的具体内容,以确保所有产出紧密相关且高质量。 -""" - - def update_json_data(original_data, updates1, updates2,second_response_list): """ 根据提供的更新字典覆盖原始JSON数据中对应的键值,支持点分隔的键来表示嵌套结构。 diff --git a/flask_app/工程标/资格审查模块.py b/flask_app/工程标/资格审查模块.py index 67c75d3..ffc9e6b 100644 --- a/flask_app/工程标/资格审查模块.py +++ b/flask_app/工程标/资格审查模块.py @@ -7,7 +7,7 @@ from flask_app.general.json_utils import extract_content_from_json, clean_json_s from flask_app.general.table_content_extraction import extract_tables_main from flask_app.工程标.形式响应评审 import process_reviews from flask_app.工程标.资格评审 import process_qualification -from flask_app.general.通义千问long import upload_file, qianwen_long +from flask_app.general.llm.通义千问long_plus import upload_file, qianwen_long from concurrent.futures import ThreadPoolExecutor from flask_app.货物标.资格审查main import combine_qualification_review from flask_app.general.merge_pdfs import merge_pdfs diff --git a/flask_app/工程标/资格评审.py b/flask_app/工程标/资格评审.py index 2888f51..298699e 100644 --- a/flask_app/工程标/资格评审.py +++ b/flask_app/工程标/资格评审.py @@ -3,8 +3,8 @@ import json import re from flask_app.general.json_utils import clean_json_string, combine_json_results, add_keys_to_json -from flask_app.general.多线程提问 import multi_threading, read_questions_from_file -from flask_app.general.通义千问long import upload_file, qianwen_long +from flask_app.general.llm.多线程提问 import multi_threading, read_questions_from_file +from flask_app.general.llm.通义千问long_plus import upload_file, qianwen_long # 这个函数的主要用途是将多个相关的字典(都包含 'common_key' 键)合并成一个更大的、综合的字典,所有相关信息都集中在 'common_key' 键下 diff --git a/flask_app/货物标/商务服务其他要求提取.py b/flask_app/货物标/商务服务其他要求提取.py index bdd9f05..cf23334 100644 --- a/flask_app/货物标/商务服务其他要求提取.py +++ b/flask_app/货物标/商务服务其他要求提取.py @@ -4,15 +4,14 @@ import re import fitz from PyPDF2 import PdfReader import textwrap -from flask_app.general.doubao import read_txt_to_string +from flask_app.general.llm.doubao import read_txt_to_string from flask_app.general.json_utils import clean_json_string -from flask_app.general.model_continue_query import continue_answer, process_continue_answers +from flask_app.general.llm.model_continue_query import process_continue_answers from flask_app.general.截取pdf通用函数 import create_get_text_function -from flask_app.general.通义千问long import upload_file, qianwen_long_stream, qianwen_plus +from flask_app.general.llm.通义千问long_plus import upload_file, qianwen_long_stream, qianwen_plus from flask_app.general.clean_pdf import extract_common_header, clean_page_content from flask_app.general.format_change import docx2pdf, pdf2docx import concurrent.futures -from flask_app.general.doubao import doubao_model # 正则表达式判断原文中是否有商务、服务、其他要求 @@ -157,22 +156,6 @@ def find_exists(truncate_file, required_keys): # 最终返回清理后的要求列表 return clean_requirements - -def generate_queries(truncate_file, required_keys): - key_list = find_exists(truncate_file, required_keys) - queries = [] - user_query_template = "这是一份货物标中采购要求部分的内容,请告诉我\"{}\"是什么,请以json格式返回结果,外层键名是\"{}\",内层键值对中的键名是原文中的标题或者是你对相关子要求的总结,而键值需要完全与原文保持一致,不可擅自总结删减,注意你无需回答采购清单中具体设备的技术参数要求,仅需从正文部分开始提取," - for key in key_list: - query_base = user_query_template.format(key, key) - other_keys = [k for k in key_list if k != key] - if other_keys: - query_base += "也不需要回答\"{}\"中的内容,".format("\"和\"".join(other_keys)) - query_base += "若相关要求不存在,在键值中填'未知'。" - queries.append(query_base) - # print(query_base) - return queries - - def generate_template(required_keys,full_text, type=1): # 定义每个键对应的示例内容 example_content1 = { @@ -318,7 +301,7 @@ def generate_template(required_keys,full_text, type=1): {tech_json_example2_str} """ if full_text: - user_query_template += f"\n\n文件内容:{full_text}" + user_query_template = f"文件内容:{full_text}\n" + user_query_template return user_query_template def get_business_requirements(procurement_path, processed_filepath, model_type): diff --git a/flask_app/货物标/基础信息解析货物标版.py b/flask_app/货物标/基础信息解析货物标版.py index 88aae9c..abdf88d 100644 --- a/flask_app/货物标/基础信息解析货物标版.py +++ b/flask_app/货物标/基础信息解析货物标版.py @@ -4,10 +4,10 @@ import threading import time import concurrent.futures from flask_app.general.json_utils import clean_json_string, add_outer_key -from flask_app.general.通用功能函数 import process_judge_questions, aggregate_basic_info, get_deviation_requirements -from flask_app.general.多线程提问 import read_questions_from_file, multi_threading -from flask_app.general.通义千问long import upload_file -from flask_app.工程标.判断是否分包等 import merge_json_to_list +from flask_app.general.通用功能函数 import process_judge_questions, aggregate_basic_info +from flask_app.general.llm.多线程提问 import read_questions_from_file, multi_threading +from flask_app.general.llm.通义千问long_plus import upload_file +from flask_app.old_version.判断是否分包等_old import merge_json_to_list from flask_app.general.投标人须知正文提取指定内容 import extract_from_notice from flask_app.货物标.提取采购需求main import fetch_procurement_reqs diff --git a/flask_app/货物标/技术参数要求提取.py b/flask_app/货物标/技术参数要求提取.py index 563d49e..6eac7bf 100644 --- a/flask_app/货物标/技术参数要求提取.py +++ b/flask_app/货物标/技术参数要求提取.py @@ -1,19 +1,18 @@ # -*- encoding:utf-8 -*- -import concurrent.futures import json import os import re import time from collections import defaultdict from copy import deepcopy -from flask_app.general.model_continue_query import continue_answer, process_continue_answers -from flask_app.general.file2markdown import convert_file_to_markdown +from flask_app.general.llm.model_continue_query import process_continue_answers from flask_app.general.format_change import pdf2docx -from flask_app.general.多线程提问 import multi_threading -from flask_app.general.通义千问long import qianwen_long, upload_file, qianwen_plus -from flask_app.general.json_utils import clean_json_string, combine_json_results -from flask_app.general.doubao import doubao_model, generate_full_user_query, pdf2txt, read_txt_to_string -from flask_app.货物标.技术参数要求提取后处理函数 import all_postprocess +from flask_app.general.llm.多线程提问 import multi_threading +from flask_app.general.llm.通义千问long_plus import qianwen_long, upload_file, qianwen_plus +from flask_app.general.json_utils import clean_json_string +from flask_app.general.llm.doubao import read_txt_to_string +from flask_app.货物标.技术参数要求提取后处理函数 import main_postprocess + def truncate_system_keys(data): """ @@ -52,6 +51,8 @@ def truncate_system_keys(data): else: # 对于其他类型的数据,保持不变 return data + + def generate_key_paths(data): """ 处理输入的字典,生成 key_paths, grouped_paths 和 good_list,并根据条件修改原始字典。 @@ -105,7 +106,7 @@ def generate_key_paths(data): # 如果原键名有后缀,需要记录以便后续重命名 if key != base: keys_to_rename[key] = base - elif isinstance(value, dict): #如果嵌套键只有一个且为'系统功能' 直接删去,替换为[] + elif isinstance(value, dict): # 如果嵌套键只有一个且为'系统功能' 直接删去,替换为[] if len(value) == 1 and '系统功能' in value: current_dict[key] = [] # 同时将路径添加到 key_paths 和 good_list 中 @@ -171,6 +172,8 @@ def generate_key_paths(data): grouped_paths = [{path: grouped_counts[path]} for path in collected_grouped_paths] return key_paths, grouped_paths, good_list, data_copy + + def rename_keys(data): """ 对整个数据结构进行重命名处理。 @@ -227,10 +230,12 @@ def rename_keys(data): # 对整个数据结构进行递归重命名 return rename_keys_recursive(data) + def combine_and_update_results(original_data, updates): """ 先规范化original和updates中的字典,防止空格的情况导致匹配不上无法更新 """ + def normalize_key(key): """ 规范化键名: @@ -292,6 +297,7 @@ def combine_and_update_results(original_data, updates): return original_data + def generate_prompt(judge_res, full_text=None): """ 获取需要采购的货物名称 @@ -384,10 +390,11 @@ def generate_prompt(judge_res, full_text=None): ''' if '否' not in judge_res and full_text: # 添加文件内容部分 - base_prompt += f"\n文件内容:\n{full_text}\n" + base_prompt = f"文件内容:{full_text}\n" + base_prompt base_prompt += "\n注意事项:\n1.严格按照上述要求执行,确保输出准确性和规范性。\n" return base_prompt + def preprocess_data(data): """ 动态识别并将值为非空列表且列表中每个项为单键字典或字符串的键转换为嵌套字典结构。 @@ -399,6 +406,7 @@ def preprocess_data(data): 返回: dict: 预处理后的数据字典。 """ + def is_single_key_dict_list(lst): """ 判断一个列表是否为非空,且每个项都是单键字典。 @@ -505,17 +513,19 @@ def preprocess_data(data): recursive_preprocess(data) return data -def get_technical_requirements(invalid_path,processed_filepath,model_type=1): + +def get_technical_requirements(invalid_path, processed_filepath, model_type=1): judge_res = "" file_id = "" full_text = read_txt_to_string(processed_filepath) - if model_type==1: - first_query_template = """该文件是否说明了采购需求,即需要采购哪些内容(包括货物、设备、系统、功能模块等)?如果有,请回答'是',否则,回答'否' + if model_type == 1: + first_query_template = """ +该文件是否说明了采购需求,即需要采购哪些内容(包括货物、设备、系统、功能模块等)?如果有,请回答'是',否则,回答'否' {} """ - judge_query = first_query_template.format(f"文件内容:{full_text}") + judge_query = f"文件内容:{full_text}\n" + first_query_template # judge_res = doubao_model(judge_query) - judge_res=qianwen_plus(judge_query) + judge_res = qianwen_plus(judge_query) if '否' in judge_res or model_type == 2: model_type = 2 # 使用qianwen-long+invalid_path print("processed_filepath中无采购需求!调用invalid_path") @@ -528,21 +538,22 @@ def get_technical_requirements(invalid_path,processed_filepath,model_type=1): else: user_query = generate_prompt(judge_res, full_text) # model_res = doubao_model(user_query) - model_res=qianwen_plus(user_query) + model_res = qianwen_plus(user_query) print(model_res) - cleaned_res = clean_json_string(model_res) #转字典 + cleaned_res = clean_json_string(model_res) # 转字典 if not cleaned_res: print("本项目没有采购需求!!!") return {"采购需求": {}} - preprocessed_data=preprocess_data(cleaned_res) #确保最内层为[] - processed_data=truncate_system_keys(preprocessed_data) #限制深度 - key_paths, grouped_paths, good_list, data_copy= generate_key_paths(processed_data) # 提取需要采购的货物清单 key_list:交通监控视频子系统.高清视频抓拍像机 ... grouped_paths是同一系统下同时有'交换机-1'和'交换机-2',提取'交换机' ,输出eg:{'交通标志.标志牌铝板', '交通信号灯.交换机'} + preprocessed_data = preprocess_data(cleaned_res) # 确保最内层为[] + processed_data = truncate_system_keys(preprocessed_data) # 限制深度 + key_paths, grouped_paths, good_list, data_copy = generate_key_paths( + processed_data) # 提取需要采购的货物清单 key_list:交通监控视频子系统.高清视频抓拍像机 ... grouped_paths是同一系统下同时有'交换机-1'和'交换机-2',提取'交换机' ,输出eg:{'交通标志.标志牌铝板', '交通信号灯.交换机'} # if len(good_list)>100 and model_type==1: #并发特别高(len(good_list)),tokens会比较贵,以后可以考虑qianwen-long, 目前qianwen-plus:0.0008/ktokens long:0.0005/ktokens 差不多价格,暂不考虑 # model_type=2 # file_id=upload_file(processed_filepath) - modified_data=rename_keys(data_copy) - user_query_template = """请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。请以 JSON 格式返回结果,键名为\"{}\",键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★或其他特殊符号(若有)和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。 + modified_data = rename_keys(data_copy) + user_query_template = """请根据招标文件中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。请以 JSON 格式返回结果,键名为\"{}\",键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★或其他特殊符号(若有)和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。 **重要限制**: - **仅提取技术参数或采购要求,不包括任何商务要求**。商务要求通常涉及供应商资格、报价条款、交货时间、质保等内容,是整体的要求;而技术参数或采购要求则具体描述产品的技术规格、功能、性能指标等。 - **商务要求的关键词示例**(仅供参考,不限于此):报价、交货、合同、资质、认证、服务、保修期等。如果内容包含上述关键词,请仔细甄别是否属于商务要求。 @@ -574,10 +585,11 @@ def get_technical_requirements(invalid_path,processed_filepath,model_type=1): "协议:routes 接口开放:具备;▲支持标准 ONVIF 协议与第三方厂家设备进行互联;支持 GB/T28181;应提供 SDK" ] }} - -{} """ - user_query_template_two="""请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。由于该货物存在 {} 种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\";键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★或其他特殊符号(若有)和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。 + user_query_template_two = """请根据招标文件中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。由于该货物存在 {} 种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\";键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★或其他特殊符号(若有)和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。 +**重要限制**: +- **仅提取技术参数或采购要求,不包括任何商务要求**。商务要求通常涉及供应商资格、报价条款、交货时间、质保等内容,是整体的要求;而技术参数或采购要求则具体描述产品的技术规格、功能、性能指标等。 +- **商务要求的关键词示例**(仅供参考,不限于此):报价、交货、合同、资质、认证、服务、保修期等。如果内容包含上述关键词,请仔细甄别是否属于商务要求。 要求与指南: 1. 你的键值应该全面,不要遗漏。 @@ -613,18 +625,15 @@ def get_technical_requirements(invalid_path,processed_filepath,model_type=1): "支持夜视", "支持云存储" ] }} - -{} """ queries = [] for key in key_paths: # 将键中的 '.' 替换为 '下的' modified_key = key.replace('.', '下的') # 使用修改后的键填充第一个占位符,原始键填充第二个占位符 - if model_type==1: - new_query = user_query_template.format(modified_key, key, modified_key,f"文件内容:{full_text}") #转豆包后取消注释 - else: - new_query = user_query_template.format(modified_key, key, modified_key,"") + new_query = user_query_template.format(modified_key, key, modified_key) # 转豆包后取消注释 + if model_type == 1: + new_query = f"文件内容:{full_text}\n" + new_query queries.append(new_query) # 处理 grouped_paths 中的项,应用 user_query_template_two @@ -632,29 +641,26 @@ def get_technical_requirements(invalid_path,processed_filepath,model_type=1): for grouped_key, grouped_key_cnt in grouped_dict.items(): # 将键中的 '.' 替换为 '下的' modified_grouped_key = grouped_key.replace('.', '下的') - if model_type==1: - new_query = user_query_template_two.format(modified_grouped_key, grouped_key_cnt, grouped_key, - modified_grouped_key, f"文件内容:{full_text}") - else: - new_query = user_query_template_two.format(modified_grouped_key, grouped_key_cnt, grouped_key, - modified_grouped_key, "") + new_query = user_query_template_two.format(modified_grouped_key, grouped_key_cnt, grouped_key, modified_grouped_key) + if model_type == 1: + new_query=f"文件内容:{full_text}\n" + new_query queries.append(new_query) - if model_type==1: - results = multi_threading(queries, "", "", 3,True) # 豆包 + if model_type == 1: + results = multi_threading(queries, "", "", 3, True) # 豆包 else: - results = multi_threading(queries, "", file_id, 2,True) # qianwen-long - temp_final={} + results = multi_threading(queries, "", file_id, 2, True) # qianwen-long + temp_final = {} if not results: print("errror!未获得大模型的回答!") else: # 第一步:收集需要调用 `continue_answer` 的问题和解析结果 questions_to_continue = [] # 存储需要调用 continue_answer 的 (question, parsed) - max_tokens=3900 if model_type==1 else 5900 + max_tokens = 3900 if model_type == 1 else 5900 for question, response in results: - message=response[0] + message = response[0] parsed = clean_json_string(message) - total_tokens=response[1] - if not parsed and total_tokens>max_tokens: + total_tokens = response[1] + if not parsed and total_tokens > max_tokens: questions_to_continue.append((question, message)) else: temp_final.update(parsed) @@ -665,11 +671,12 @@ def get_technical_requirements(invalid_path,processed_filepath,model_type=1): """根据所有键是否已添加处理技术要求""" # 更新原始采购需求字典 - final_res=combine_and_update_results(modified_data, temp_final) - ffinal_res=all_postprocess(final_res) + final_res = combine_and_update_results(modified_data, temp_final) + ffinal_res = main_postprocess(final_res) ffinal_res["货物列表"] = good_list # 输出最终的 JSON 字符串 - return {"采购需求":ffinal_res} + return {"采购需求": ffinal_res} + def test_all_files_in_folder(input_folder, output_folder): # 确保输出文件夹存在 @@ -693,21 +700,23 @@ def test_all_files_in_folder(input_folder, output_folder): print(f"结果已保存到: {output_file_path}") except Exception as e: print(f"处理文件 {file_path} 时出错: {e}") + + # 如果采购需求为空 考虑再调用一次大模型 qianwen-stream if __name__ == "__main__": - start_time=time.time() + start_time = time.time() # invalid_path="D:\\flask_project\\flask_app\\static\\output\\output1\\e7dda5cb-10ba-47a8-b989-d2993d34bb89\\ztbfile.pdf" # file_id = upload_file(truncate_file) - truncate_file=r'C:\Users\Administrator\Desktop\新建文件夹 (3)\需求\“天府粮仓”青白江区“一园三片”数字化提升和标准化体系建设项目(三次)招标文件(N510113202400010820250102001)_procurement.pdf' - invalid_path=r"C:\Users\Administrator\Desktop\货物标\extract_files\107国道.txt" + truncate_file = r'C:\Users\Administrator\Desktop\新建文件夹 (3)\需求\“天府粮仓”青白江区“一园三片”数字化提升和标准化体系建设项目(三次)招标文件(N510113202400010820250102001)_procurement.pdf' + invalid_path = r"C:\Users\Administrator\Desktop\货物标\extract_files\107国道.txt" # file_id=upload_file(truncate_file) # processed_filepath = convert_file_to_markdown(truncate_file) - processed_filepath=r"C:\Users\Administrator\Desktop\货物标\extract_files\107国道.txt" - res=get_technical_requirements(invalid_path,processed_filepath,1) + processed_filepath = r"C:\Users\Administrator\Desktop\货物标\extract_files\107国道.txt" + res = get_technical_requirements(invalid_path, processed_filepath, 1) json_string = json.dumps(res, ensure_ascii=False, indent=4) print(json_string) # # input_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\output1" # # output_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\output3" # # test_all_files_in_folder(input_folder, output_folder) - end_time=time.time() - print("耗时:"+str(end_time-start_time)) \ No newline at end of file + end_time = time.time() + print("耗时:" + str(end_time - start_time)) diff --git a/flask_app/货物标/技术参数要求提取后处理函数.py b/flask_app/货物标/技术参数要求提取后处理函数.py index b7221ce..6ee2595 100644 --- a/flask_app/货物标/技术参数要求提取后处理函数.py +++ b/flask_app/货物标/技术参数要求提取后处理函数.py @@ -1,9 +1,11 @@ import json import re from collections import defaultdict -#传输技术参数需求的时候后处理 12.27版本,对重复的键名,若键值一样,不添加后缀-a -b.. -#按货物名提取技术参数,将货物名前的星等特殊符号(如'★交换机')带到具体的参数要求中 + def postprocess_technical_table(data, good_list, special_keys=None, parent_key=''): + """ + 传输技术参数需求的时候后处理,输入的data为经过main_postprocess处理的采购需求数据 + """ def get_suffix(n): """ 根据数字n返回对应的字母后缀。 @@ -18,7 +20,12 @@ def postprocess_technical_table(data, good_list, special_keys=None, parent_key=' def count_matching_keys(data, patterns, special_keys, key_value_map=None): """ 递归统计匹配键的出现次数及其对应的唯一值,仅统计值为列表的键。 - 不包括 special_keys 中的键。 + 对于每个键,会先去除键名中的空格,如果这个清理后的键不在 special_keys 列表中且满足至少一个patterns,就把对应的值(列表转换为元组)添加到 key_value_map 字典中。 + 注意:同一个键对应的相同值(转换成元组后)只会保存一次,保证唯一性。 + 返回结构:{ + 'key1': [tuple_value1, tuple_value2, ...], + 'key2': [tuple_value1, ...], + } """ if key_value_map is None: key_value_map = defaultdict(list) @@ -29,7 +36,7 @@ def postprocess_technical_table(data, good_list, special_keys=None, parent_key=' if isinstance(value, list): if clean_key not in special_keys and any(pattern.match(clean_key) for pattern in patterns): value_tuple = tuple(value) - if value_tuple not in key_value_map[clean_key]: + if value_tuple not in key_value_map[clean_key]: #每个键下的值若一样,则只保留一个 key_value_map[clean_key].append(value_tuple) elif isinstance(value, dict): count_matching_keys(value, patterns, special_keys, key_value_map) @@ -42,7 +49,8 @@ def postprocess_technical_table(data, good_list, special_keys=None, parent_key=' def assign_suffixes(key_value_map): """ - 为每个键的每个唯一值分配后缀。 + 如果该键只有一个唯一值,则不添加后缀-a -b.. + 如果有多个唯一值,则给第一个值不添加后缀,后续的值按照顺序分别添加 -a、-b、-c 返回一个字典,键为原键名,值为另一个字典,键为值元组,值为对应的后缀(如果需要)。 """ suffix_assignment = defaultdict(dict) @@ -74,7 +82,7 @@ def postprocess_technical_table(data, good_list, special_keys=None, parent_key=' elif any(pattern.match(clean_key) for pattern in patterns): # 处理普通匹配键 # 检查是否以特殊符号开头 - if clean_key.startswith(('▲', '★','●','■','◆','☆','△','◇','○','□','#')): + if clean_key.startswith(('▲', '★','●','■','◆','☆','△','◇','○','□','#')): #货物名以特殊符号开头->移至键值(技术参数)开头 #提取符号并去除符号: symbol = clean_key[0] stripped_key = clean_key[1:] @@ -131,23 +139,8 @@ def postprocess_technical_table(data, good_list, special_keys=None, parent_key=' return filtered_data - - -def postprocess(data): - """递归地转换字典中的值为列表,如果所有键对应的值都是'/', '{}' 或 '未知'""" - def convert_dict(value): - # 如果所有值是'/', '{}' 或 '未知' - if all(v in ['/', '未知', {}] for v in value.values()): - return list(value.keys()) - else: - # 如果不满足条件,则递归处理嵌套的字典 - return {k: convert_dict(v) if isinstance(v, dict) else v for k, v in value.items()} - - # 递归处理顶层数据 - return {key: convert_dict(val) if isinstance(val, dict) else val for key, val in data.items()} - - -def all_postprocess(data): +def main_postprocess(data): + #解析采购要求时候的后处理,用于前端网页展示。 def recursive_process(item): pattern = re.compile(r'(.+)-\d+$') @@ -164,10 +157,9 @@ def all_postprocess(data): return item temp = restructure_data(data) - processed_data = recursive_process(temp) + processed_data = recursive_process(temp) #重构数据以标准化嵌套层级至三层,便于前端展示。 return processed_data - def restructure_data(data): """ 重构数据以标准化嵌套层级至三层。 @@ -223,15 +215,6 @@ def restructure_data(data): else: return structured_data - -# 定义获取所有以':'结尾的前缀的函数 -def get_prefixes(s): - prefixes = [] - for i in range(len(s)): - if s[i] in [':', ':']: - prefixes.append(s[:i+1]) - return prefixes - # 定义删除公共前缀的函数 def remove_common_prefixes(string_list, min_occurrence=3): """ @@ -244,6 +227,14 @@ def remove_common_prefixes(string_list, min_occurrence=3): Returns: list: 删除公共前缀后的字符串列表。 """ + # 定义获取所有以':'结尾的前缀的函数 + def get_prefixes(s): + prefixes = [] + for i in range(len(s)): + if s[i] in [':', ':']: + prefixes.append(s[:i + 1]) + return prefixes + if not string_list: return string_list @@ -305,7 +296,7 @@ if __name__ == "__main__": ], } # 处理数据 - result = all_postprocess(sample_data) + result = main_postprocess(sample_data) # 输出处理结果 print(json.dumps(result,ensure_ascii=False,indent=4)) diff --git a/flask_app/货物标/提取采购需求main.py b/flask_app/货物标/提取采购需求main.py index ec8e3fc..0dd47b4 100644 --- a/flask_app/货物标/提取采购需求main.py +++ b/flask_app/货物标/提取采购需求main.py @@ -3,11 +3,8 @@ import json import os import time from flask_app.general.format_change import get_pdf_page_count -from flask_app.general.doubao import pdf2txt from flask_app.general.file2markdown import convert_file_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 diff --git a/flask_app/货物标/资格审查main.py b/flask_app/货物标/资格审查main.py index 632b3e8..d120d4c 100644 --- a/flask_app/货物标/资格审查main.py +++ b/flask_app/货物标/资格审查main.py @@ -3,7 +3,7 @@ import json import re import time from concurrent.futures import ThreadPoolExecutor, as_completed -from flask_app.general.通义千问long import upload_file, qianwen_long +from flask_app.general.llm.通义千问long_plus import upload_file, qianwen_long from flask_app.general.json_utils import clean_json_string