乐于分享
好东西不私藏

[Dify]Dify Word 文档批注功能探索:让大模型生成审阅意见并写回

[Dify]Dify Word 文档批注功能探索:让大模型生成审阅意见并写回

在使用 Dify 搭建工作流时,发现一个好用的插件 Word 文档操作工具插件 ,上一次研究了插件中pdf转word,今天研究一下插件中的文档批注 功能。

测试这样一个场景:让大模型先阅读 Word 文档,再把生成的审阅意见直接写回原始文档批注中,输出一份可继续修改和流转的审阅版 Word。


一、Word 文档操作工具插件

在插件市场中搜索 Word,比较容易找到这个插件。 根据插件说明,文档批注功能接收两个核心输入:

一个是原始 Word 文档,也就是需要被添加批注的 .docx 文件; 另一个是批注映射 JSON,其中键是文档中要查找的摘要文本,值是对应要添加的批注内容。

这个 JSON 支持两种格式:

一种是对象格式,也就是“原文摘要 : 批注内容”的直接映射;

一种是数组格式,本质上也是多组同样的映射,只是包在数组里。

从结构上看,这个插件本质上是在做一件事:

根据“原文片段—批注意见”的映射关系,把批注挂回 Word 对应位置。


二、文档批注场景

这个能力非常适合“文档审阅类”应用。

例如:

  • 通知、方案、制度文件审阅
  • 合同初稿风险提示
  • 汇报材料优化建议
  • 招标采购文件表述检查
  • 项目申报材料规范性审阅

这些场景有一个共同点:

文档已经有了,但还需要审阅意见,而且最好直接体现在原文里。

相比单独输出一份“问题清单”,直接把批注写回 Word 的优势更明显:

  • 审阅意见和原文位置对应更直观
  • 使用者不用来回对照
  • 更接近真实办公习惯
  • 更适合作为修改底稿流转

三、工作流测试目标

这次我做的是一个最小功能版流程,目标比较明确:

  1. 上传一份 Word 文档
  2. 提取文档正文
  3. 让大模型生成批注映射 JSON
  4. 对 JSON 做校验和清洗
  5. 调用 Word 文档批注插件
  6. 输出带批注的 Word 文件

整个流程并不复杂,但比较完整,已经能把“大模型审阅 + Word 落批注”这条链路跑通。


四、工作流整体思路

本次测试工作流整体结构如下:

从结构上看,主要分成三部分。

第一部分:文档内容抽取

先把上传的 Word 文档提取成文本,供后续大模型阅读。

第二部分:大模型生成批注 JSON

让模型基于文档内容和审阅要求,输出符合插件格式要求的 JSON。

第三部分:插件回写 Word 批注

将清洗后的 JSON 和原始 Word 一起传给插件,输出带批注的 Word 文件。


五、工作流搭建

在开始节点中,除了上传文件,增加了几个可选输入项,并设置了默认值。

1. 审阅要求

用于告诉模型,这次审阅重点关注什么。

例如默认值可以设置为: 从错别字、语法错误、逻辑不通、表述不规范、专业错误、风险提示等方面进行审阅,并仅输出最值得批注的 5—10 处内容。

这样,可以直接使用默认要求,也可以根据具体文档自行调整审阅重点。

2. 审阅者

用于传给插件,作为批注作者名称。 例如默认设置为“AI审阅”。

3. 输出文件名称

用于设置最终生成文件的名称。 例如默认设置为“审阅批注版”。

这样配置的好处是:

不填写也能直接运行,想个性化时也可以自行调整。


六、LLM 节点

整个批注效果好不好,很大程度取决于大模型输出的 JSON 是否稳定、是否便于后续匹配原文。

因此,在提示词设计上,我重点约束了几个方面:

  • 只输出合法 JSON
  • key 必须尽量取自原文
  • value 要简明、明确、可操作
  • 仅输出最值得批注的少量内容
  • 不输出解释、前言、后缀或 Markdown 代码块

同时,为了提升稳定性,我还在提示词中加入了:

  • 插件支持的 JSON 格式说明
  • 正确示例
  • 错误示例

比如,在提示词里会明确告诉模型:

对象格式就是“原文摘要:批注内容”的直接映射; 数组格式则是多组这样的映射集合。 同时还会专门提醒模型,不能输出“以下是审阅结果”之类的说明文字,也不能把 key 写成“这段话主要讲的是……”这种概括句,而应该尽量直接取自原文。

这样做的目的,就是尽可能提高模型输出的稳定性,减少后续匹配失败的概率。

你是专业文档审阅助手。请对上传的 Word 文档内容进行审阅,并生成用于 Word 批注插件的 JSON 数据。审阅重点:{{#1774257312397.review_requirements#}}插件要求:- 输出内容必须是合法 JSON- 支持两种格式:  1. 对象格式:     {"摘要1":"批注1","摘要2":"批注2"}  2. 数组格式:     [{"摘要1":"批注1","摘要2":"批注2"},{"摘要3":"批注3"}]- 键为文档中要查找的摘要文本,尽量是一句话或者一个较短段落,必须尽量与原文保持一致,便于后续定位和匹配- 值为要添加的对应批注输出要求:1. 只输出合法 JSON,不要输出任何解释、前言、后缀、说明文字或 Markdown 代码块。2. 优先输出“对象格式”JSON,除非确有必要再使用“数组格式”。3. key 必须尽量直接取自原文,不能随意改写,不能概括替代,不能写成“第一段内容”“这句话”等模糊表述。4. key 尽量为一句完整原文,或一个较短自然段,不要选择标题,不要选择过长段落。5. value 为对应批注意见,语言简明、明确、可操作,适合直接作为 Word 批注内容。6. 仅输出最值得批注的 5-10 处内容,不要覆盖全文每一句。7. 批注重点可包括:表述不规范、逻辑不清、信息不完整、风险提示、专业表述不准确等。8. 如果文档整体质量较高,也应尽量找出少量确有价值的批注点,但不要为了凑数量强行批注。正确示例1(对象格式):{  "请各部门及时报送相关材料。""建议补充明确的报送截止时间和报送方式,便于执行。",  "请认真贯彻落实。""表述较为原则,建议结合本文主题补充更具体的落实要求。"}正确示例2(数组格式):[  {    "为进一步做好相关工作,现将有关事项通知如下。""如后文事项较多,建议采用分条编号方式,增强层次性。",    "请各部门及时报送相关材料。""建议补充明确的报送截止时间和报送方式。"  },  {    "请认真贯彻落实。""表述较为原则,建议补充更具体的执行要求。"  }]错误示例1(不要这样输出):以下是审阅结果:{  "某句话""某批注"}错误原因:包含了多余说明文字,不是纯 JSON。错误示例2(不要这样输出):{  "这段话主要讲的是报送要求""建议更明确"}错误原因:key 不是原文片段,后续可能无法匹配到文档位置。错误示例3(不要这样输出):```json{  "请各部门及时报送相关材料。""建议补充时间。"}

七、JSON 清洗代码节点

虽然在 LLM 提示词中已经明确要求“只输出 JSON”,但在实际运行时,大模型仍然可能出现以下情况:

  • 前面多输出一句说明文字
  • 自动加上代码块标记
  • JSON 结构不完整
  • 键值类型不符合要求

如果把这类原始结果直接传给插件,就容易导致执行失败,而且问题不容易定位。

因此,我在 LLM 节点后面增加了一个 JSON 清洗代码节点,主要用于:

  1. 去掉多余的代码块包裹
  2. 校验输出是否为合法 JSON
  3. 校验 JSON 是否符合插件支持的格式
  4. 输出标准化后的 JSON 字符串

如果校验失败,则直接提示:

LLM 输出不是合法 JSON,请检查提示词或模型输出。

这样可以把格式问题拦在插件前面,减少后续报错,也更方便排查问题。

import jsonimport redef main(llm_text: str) -> dict:    if llm_text is None:        raise ValueError("格式错误:LLM 未返回内容")    text = llm_text.strip()    # 去掉 Markdown 代码块包裹    text = re.sub(r"^```json\s*""", text, flags=re.IGNORECASE)    text = re.sub(r"^```\s*""", text)    text = re.sub(r"\s*```$""", text)    if not text:        raise ValueError("格式错误:LLM 返回内容为空")    # 尝试解析 JSON    try:        data = json.loads(text)    except Exception:        raise ValueError("格式错误:LLM 输出不是合法 JSON,请检查提示词或模型输出")    # 只允许两种格式:    # 1. 对象格式:{"摘要1":"批注1"}    # 2. 数组格式:[{"摘要1":"批注1"}, {"摘要2":"批注2"}]    if isinstance(data, dict):        for k, v in data.items():            if not isinstance(k, stror not isinstance(v, str):                raise ValueError("格式错误:对象格式中,键和值都必须是字符串")    elif isinstance(data, list):        for item in data:            if not isinstance(item, dict):                raise ValueError("格式错误:数组格式中,每个元素都必须是对象")            for k, v in item.items():                if not isinstance(k, stror not isinstance(v, str):                    raise ValueError("格式错误:数组格式中,键和值都必须是字符串")    else:        raise ValueError("格式错误:仅支持对象格式或对象数组格式")    # 返回清洗后的标准 JSON 字符串,供插件直接使用    return {        "clean_comments_json": json.dumps(data, ensure_ascii=False)    }

八、Word 文档批注插件

在插件节点中,主要传入了以下参数:

1. 原始 Word 文档

直接引用开始节点上传的文件。

2. 批注 JSON

引用 JSON 清洗节点输出的标准 JSON 字符串。

3. 批注者

引用开始节点中的 author

4. 输出文件名称

引用开始节点中的 output_filename

5. 相似度阈值

这里固定设置为 0.8

这个参数决定了插件对文本匹配的严格程度: 值越高,要求匹配越精确;值越低,则允许更模糊的匹配。 在当前测试中,0.8 是一个相对平衡的值。


九、工作流运行效果

流程跑通后,最终可以输出一份带批注的 Word 文件

这份文件会保留原始文档结构,同时在对应句子或段落位置添加批注。使用者打开 Word 后,可以直接看到模型给出的审阅意见。

从结果来看,这种方式的几个优势比较明显。

1. 审阅意见直接回到原文

不需要再单独对照一份问题清单,阅读和修改都更方便。

2. 更贴近实际办公场景

比起只输出一段结果文本,带批注的 Word 更像真实工作中的审阅稿。

3. 整个流程形成闭环

从“读文档”到“出批注版文件”,整个过程可以在一个工作流中完成。


从这次测试结果看,Word 文档批注功能已经能够较好支持“文档解析—批注生成—结果写回”的完整链路,作为文档智能审阅场景的基础能力,已经具备较强的实用价值。

如果这篇文章对你有帮助,欢迎点个赞、点个在看,也欢迎转发给有需要的朋友。 想第一时间收到更新,也可以给我加个星标。 感谢你的阅读,我们下次再见。