微信扫码
添加专属顾问
我要投稿
AI助力标书制作,告别繁琐格式调整,让投标人重获自由! 核心内容: 1. 标书制作的痛点:格式调整与图片插入的繁琐流程 2. AI解决方案:智能解析招标文件,自动提取并精准插入附件 3. 实践案例:知识库管理与企业资质图片的自动化处理
"投标人员的时间守恒定律:格式调整耗时 = 插入图片数 × 标书页数/10" ⏰
graph LR A[招标文件] --> B[智能解析] B --> C[自动提取附件] C --> D[精准插入图片] D --> E[完美格式标书]
温馨提示:观看时请勿羡慕到流泪 😭
数据是LLM的血液,没有数据的Agent就像没有汽油的超跑 —— 只能看不能开
我们采用"分治策略"管理企业资质:
知识库:这里存放着价值百万的"数字房产证"🏠
温馨提示:本流程已通过ISO-9001"防加班"认证 🕒
import requestsimport tempfileimport osimport refrom collections.abc import Generatorfrom typing import Any, Dict, List, Optionalfrom io import BytesIOfrom docx import Documentfrom dify_plugin import Toolfrom dify_plugin.entities.tool import ToolInvokeMessageclass DocxTitleTool(Tool):def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:"""提取DOCX文件中的标题Args:tool_parameters: 包含docx文件和title_level参数"""# 获取参数uploaded_file = tool_parameters.get('docx')title_level = tool_parameters.get('title_level', 1)if not uploaded_file:yield self.create_text_message("请上传DOCX文件")returntry:title_level = int(title_level)if title_level < 1 or title_level > 9:yield self.create_text_message("标题级别必须在1-9之间")returnexcept (ValueError, TypeError):yield self.create_text_message("标题级别必须是有效的数字")returntry:# 下载文件file_url = "http://api:5001" + uploaded_file.urlresponse = requests.get(file_url)if response.status_code != 200:yield self.create_text_message(f"文件下载失败,状态码: {response.status_code}")return# 创建临时文件保存DOCX内容with tempfile.NamedTemporaryFile(suffix='.docx', delete=False) as temp_file:temp_file.write(response.content)temp_file_path = temp_file.nametry:# 提取标题headings_result = self._extract_headings_by_level(temp_file_path, title_level)if not headings_result:yield self.create_text_message("未在文档中找到指定级别的标题")return# 返回结果yield self.create_json_message({"result": headings_result,"summary": f"成功提取了{title_level}级及以上的标题"})finally:# 清理临时文件if os.path.exists(temp_file_path):os.unlink(temp_file_path)except Exception as e:yield self.create_text_message(f"处理文件时发生错误: {str(e)}")def _get_heading_level(self, paragraph) -> Optional[int]:"""获取段落的标题级别Args:paragraph: 文档段落对象Returns:int: 标题级别 (1-9),如果不是标题则返回None"""# 方法1: 通过样式名称判断style_name = paragraph.style.name.lower()# 检查标准的标题样式if 'heading' in style_name:# 提取数字,如 'Heading 1' -> 1match = re.search(r'heading\s*(\d+)', style_name)if match:return int(match.group(1))# 检查中文标题样式if '标题' in style_name:match = re.search(r'标题\s*(\d+)', style_name)if match:return int(match.group(1))# 方法2: 通过outline_level属性判断if hasattr(paragraph, '_p') and hasattr(paragraph._p, 'pPr'):pPr = paragraph._p.pPrif pPr is not None and hasattr(pPr, 'outlineLvl_val'):outline_level = pPr.outlineLvl_valif outline_level is not None:return outline_level + 1 # outline_level是0-based,转换为1-based# 方法3: 通过样式的outline_level属性try:if hasattr(paragraph.style, '_element') and hasattr(paragraph.style._element, 'pPr'):pPr = paragraph.style._element.pPrif pPr is not None:outline_lvl = pPr.find('.//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}outlineLvl')if outline_lvl is not None:val = outline_lvl.get('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}val')if val is not None:return int(val) + 1except:passreturn Nonedef _extract_headings_by_level(self, file_path: str, max_level: int) -> List[Dict[str, List[str]]]:"""提取指定级别的标题Args:file_path: DOCX文件路径max_level: 最大标题级别Returns:List[Dict[str, List[str]]]: 按用户要求格式返回的标题列表"""try:# 加载文档doc = Document(file_path)# 按级别收集标题headings_by_level = {}for level in range(1, max_level + 1):headings_by_level[level] = []# 遍历所有段落for paragraph in doc.paragraphs:# 跳过空段落if not paragraph.text.strip():continue# 获取标题级别heading_level = self._get_heading_level(paragraph)if heading_level is not None and heading_level <= max_level:headings_by_level[heading_level].append(paragraph.text.strip())# 转换为用户要求的格式result = []level_names = {1: "一级标题",2: "二级标题",3: "三级标题",4: "四级标题",5: "五级标题",6: "六级标题",7: "七级标题",8: "八级标题",9: "九级标题"}for level in range(1, max_level + 1):if headings_by_level[level]: # 只添加有内容的级别level_name = level_names.get(level, f"{level}级标题")result.append({level_name: headings_by_level[level]})return resultexcept Exception as e:raise Exception(f"提取标题时发生错误: {str(e)}")
你是一个招标文件分析助手,在招投标领域,乙方投标文件,必须按照甲方招标文件中规定的格式进行撰写#输入是招标文件的章节标题,请判断对于投标文件的格式要求,所在的章节,然后返回开始章节标题,和结束章节标题#符合条件的章节,一般包含有"附件"字样,即该章,附带了许多格式化的表格,投标人照此写标书#如果没有结束章节标题,则置为空参考输出格式:{"开始标题":”XXX“,”结束标题“:"XXX"}{{#投标文件的章节名称.result#}}/no_think经过LLM后,去掉输出结果的think标签import re,jsonfrom typing import Listimport jsondef main(arg1): tmp = re.sub(r"<think>[\s\S]*?</think>", "", arg1, flags=re.DOTALL) tmp = re.sub(r'^\s*```(?:json)?\s*\n?', '', tmp,flags=re.IGNORECASE) tmp = re.sub(r'\n?```\s*$', '', tmp) tmp = tmp.replace(r'\n', '\n').replace(r'\"', '"').strip() result=tmp return { 'result':result }# ===== 数据结构定义 =====定义 ElementInfo: 元素类型: 'paragraph' 或 'table' XML内容: 字符串 文本内容: 字符串 (用于预览) 索引: 整数 分区格式: 字典 (可选)定义 SectionInfo: 标题: 字符串 元素列表: [ElementInfo]定义 PageFormatInfo: 页面宽度, 高度, 方向, 四周边距: 整数/字符串定义 DocumentExtractResult: 章节列表: [SectionInfo] 页面格式: PageFormatInfo 源文件路径: 字符串 元素总数: 整数 抽取信息: 字典 分区格式列表: [字典] (可选) 样式XML: 字符串 (可选) 编号XML: 字符串 (可选)# ===== 文档解析器类 =====类 DocxExtractor: 初始化(文档路径): 加载DOCX文档 获取页面格式(章节索引=0): 尝试从文档获取页面设置 失败则返回默认A4格式 查找单章节(开始标题, 标题级别=1, 结束标题=None): 预处理标题(移除空格) 收集所有文档元素(段落+表格) 遍历元素: - 发现匹配的开始标题 → 创建新章节 - 继续收集元素直到: a) 遇到结束标题 b) 遇到同级标题(无结束标题时) 返回找到的章节列表 抽取单章节(开始标题, 标题级别=1, 结束标题=None): 调用查找单章节 收集分区格式信息 确定最终页面格式: 优先使用提取的分区格式 否则调用获取页面格式 构建DocumentExtractResult对象 返回结果# ===== 插件工具类 =====类 DocxExtractorTool (继承Tool): 执行入口(参数): 验证必要参数(文档文件, 开始标题) 下载DOCX文件 → 保存为临时文件 创建DocxExtractor实例 调用抽取单章节方法 处理结果: 添加成功元数据 转换为JSON格式 生成输出文件 异常处理: 值错误/通用错误 → 生成错误JSON文件 返回处理结果(文件blob或错误信息)
// XML数据结构:标书的DNA{ "element_type": "paragraph", "xml_content": "<w:p>7-2纳税证明</w:p>", "text_content": "7-2纳税证明", "index": 642}智能匹配流程:a.文字:"7-2纳税证明"发出相亲请求b.知识库:匹配到纳税证明图片集c.LLM媒人:生成"结婚证"(XML数据)d.输出:文字+图片的完美组合
大模型将图片转换为xml的prompt:
你是一个json数据处理专家,请将数据A,按照参考格式,进行构造,然后返回一个新的json数据#如果待处理数据只有文字,则生成element_type为paragraph。如果待处理数据还有图像,则生成element_type为picture。#index字段,与B保持一致,举例B为100,则输出的所有index都是100数据A:{{检索到的图片url.result#}}数据B:{{#当前数据索引#}}如果只有文字,输出格式参考 { "element_type": "paragraph", "xml_content": "xxxx", "text_content": "xxx", "index": xxx, "section_format": null }如果是图像,输出格式参考{ "element_type": "picture", "image_path": "XXX, "index": XXX}函数 assemble_document(输入: 抽取结果, 输出路径, 是否清理分区属性):尝试:创建临时目录和DOCX基础结构初始化XML内容列表添加DOCX文档头部到XML列表收集所有元素并按原始索引排序遍历每个元素:如果元素类型是图片:从Docker获取图片数据生成图片XML并保存到媒体目录将图片XML添加到内容列表如果元素类型是段落或表格:获取元素XML内容根据标志决定是否清理分区属性将处理后的XML添加到内容列表添加DOCX文档尾部到XML列表将完整XML写入document.xml文件创建辅助文件:[Content_Types].xml主关系文件(.rels)文档关系文件(document.xml.rels)样式文件(styles.xml)编号文件(numbering.xml)将临时目录打包为DOCX文件输出处理统计信息异常处理:抛出错误信息最终:清理临时目录
使用xml标签,确保了生成标书的格式的正确性,而且自动插入图片,节省了投标人员一部分时间。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2025-10-19
陪你聊天的豆包,悄悄开始带货了
2025-10-09
Sales Navigator 三大 AI 功能,轻松搞定 B2B 复杂决策网!
2025-10-07
OpenAI 发布的职业提示词第二弹:营销相关提示词
2025-09-30
AI营销时代来了:对农业人意味着什么?
2025-09-29
百度电商MultiAgent视频生成系统
2025-09-15
淘宝悄悄上线了AI导购,懒人购物原来可以这么爽。
2025-09-12
成本砍掉 90%!用n8n、多维表格和Nano Banana,搭全自动AI试衣工作流,上新速度提升 1000%!
2025-09-07
零售AI:90%在用,20%营收差定生死
2025-08-28
2025-08-05
2025-08-10
2025-09-12
2025-08-13
2025-09-15
2025-08-21
2025-08-29
2025-10-19
2025-08-29
2025-09-30
2025-06-26
2025-06-15
2025-06-03
2025-05-29
2025-05-26
2025-05-22
2025-05-21