尧图精选

用Dify构建PDF翻译流水线:保留原格式的中英互译实战

🕒 发布时间:2026/9/17 17:03:22 📁 来源:尧图网络
把一份排版精美的英文 PDF 翻译成中文还要保持原来的版式表格不错位、图片不乱跑——这个需求在我混的几个技术群里几乎每个月都会被翻出来问一次。以前大家要么一页页截图丢进翻译软件要么先转成 Word 再手动调格式碰到几十页的文档效率低到怀疑人生。直到我在 Dify 里把这条链路由工作流串起来之后上传 PDF、自动解析、调用大模型翻译、最终输出一份保留原格式的译文十几页的文档大概来回十分钟就能出第一版。这篇就把整套方案复盘出来包括我最开始踩过的坑和最后沉淀下来的流程细节。如果你是正在用 Dify 处理文档类需求、或者被 PDF 翻译折磨过的朋友这篇应该能帮你少走不少弯路。1. 动手之前先把方案想清楚1.1 “原格式翻译”的本质是三条子任务的组合很多朋友第一次听到“PDF 原格式翻译”第一反应是“这有什么难的复制粘贴到翻译引擎不就完了”。真做一次就明白了这件事根本不是“翻译”本身难而是它把三件性质完全不同的任务绑在了一起。第一件是内容提取。你得从 PDF 里拿到干净、顺序正确的文本。第二件才是语言转换让大模型或翻译引擎把内容翻成目标语言。第三件是格式重建把翻译后的内容重新渲染成一份版式可用的文档。大多数在线翻译工具只解决了第二件事第一件事靠上传整个 PDF 让工具后台处理第三件事则是直接丢弃——给你一段纯文本或一个排版崩坏的网页让你自己再手工排版一次。明白了这一点你的思路就会清晰很多如果我们要在 Dify 里实现端到端流程真正要下功夫的不是翻译 Prompt而是两头的“提取”和“重建”。翻译是把文本从 A 语言变成 B 语言提取和重建是把无结构的 PDF 文件变成有结构、可重新输出的中间格式。1.2 为什么选 Dify而不是抱着现成翻译工具不放市面上其实有不少能直接处理 PDF 翻译的工具DeepL、Google 翻译都有文档翻译入口沉浸式翻译这类浏览器插件也很流行。但用它们做一批、几十份文档的时候问题就来了。在线工具的交互是封闭的上传、等待、下载每一份文档都要重复一遍没有批处理。术语不可控公司内部的产品名、行业专有名词翻译工具不会替你遵守术语表而且你没法在没有企业版的前提下干预结果。数据安全边界不清晰合同、技术手册这类文档直接传到第三方服务很多公司是不允许的。定制能力为零你想在翻译前过滤页眉页脚、想在翻译后统一术语、想在结果里保留表格结构它都做不到。Dify 的定位是 LLM 应用开发平台你可以在里面编排工作流、挂载知识库、配置多个模型甚至可以把自己公司内部的术语表做成知识库喂给模型。它能解决上面那些问题的本质原因是你不受制于某个产品的固定交互而是按自己的业务逻辑搭流水线。而且近期 Dify 社区版的迭代节奏很快文档解析、知识库流水线这些能力一直在补强这种“平台 工作流”的玩法比死磕单一翻译工具要灵活得多。当然它也有门槛最常见的门槛是自己部署、维护一套 Dify 实例需要花点时间。但一旦跑起来后续做多语言文档批处理、知识库问答、内容抽取都是同一套底座上继续长出来的能力性价比是值得的。1.3 技术路径选型转中间格式再重建比直接动 PDF 坐标靠谱决定用 Dify 之后还有一个绕不开的选型用什么方式实现“原格式”三个字。我最早尝试过直接在 PDF 上做文本替换就是用 PyMuPDF 找到每个文本块的位置把译文替换进去。这个思路看起来直接实际操作下来问题非常大译文和原文的长度通常不一样英译中平均会短 30% 左右中译英又会变长一个词替换成另一个词原有文本框根本装不下要么溢出要么重叠表格和列表更是重灾区。这个方法只适合短文本、小幅修改不适合整篇文档翻译。后来我换了一个思路不追求在原始页面坐标上做文章而是把 PDF 先转成有逻辑结构的中间格式翻译完再重建文档。具体有两条路PDF 转 DOCX翻译后利用 Word 或 LibreOffice 导出 PDF。适合版式复杂的商务文档、合同、标书Word 的流式排版会自动处理换行、分页和表格列宽。PDF 转 Markdown 或 HTML翻译后通过 Pandoc 或 WeasyPrint 生成新 PDF。适合技术文档、论文、说明手册对标题层级和代码块的还原很好。我现在默认采用第二条路线因为 Markdown 是 Dify 工作流里最好处理的文本格式LLM 对 Markdown 结构标记的理解非常成熟生成结果稳定。如果遇到对版式要求极其苛刻的文档再临时切到 DOCX 管线。2. PDF 翻译的难点不在语言而在文档结构2.1 PDF 的底层逻辑是“排版坐标”不是“内容流”在处理 PDF 之前有必要理解一个关键概念PDF 文件本质上记录的是每个字符在页面上的精确坐标而不是像 Word 那样的逻辑内容流。你看到的一个段落在 PDF 内部可能是几十个独立的文本绘制指令每个指令指定字体、字号、位置、颜色。PDF 没有“段落”这个概念更没有“这一段和下一段是连续的”这种语义信息。这个特性带来的直接后果就是用程序提取 PDF 文本时提取工具只能按照文件内部的顺序把字符“倒”出来而这个顺序很可能不是人眼阅读的顺序。双栏论文里第一栏读完之后应该接第二栏顶部但工具可能把第一栏和第二栏的文字交错输出表格里的数字、图注、页眉页脚也可能混在正文里顺序完全看构建文件时的内部组织方式。我经常用一个类比来跟朋友解释PDF 就像一张已经拍好的照片Word 则是一篇带大纲的作文。照片里的每个元素位置固定你想修改其中一个字必须重新拍一张作文里的大纲则能让你调整段落顺序字变多字变少都会自动重排。PDF 翻译要面对的就是“怎么把照片里的信息无损还原成作文再重新拍成另一张照片”。2.2 三类典型 PDF 的难度分级不是所有 PDF 难度都一样。我自己按提取难度把 PDF 分成三类不同类别处理策略完全不同。类型特征提取难度最佳处理路径数字化单栏文档网页直接打印或 Word 导出的单栏 PDF有文本层低常规提取后分块翻译多栏/复杂排版文档学术论文、杂志、产品手册双栏或多栏含表格、图注、页眉页脚高坐标排序 分栏处理或先转 DOCX扫描件图片型 PDF没有文本层很高先 OCR再进入翻译流水线单栏文档最简单PDF 里文字的存储顺序基本等于阅读顺序直接提取就能拿到相对干净的文本。多栏文档我通常会先看看提取结果乱到什么程度再用 pdfplumber 按坐标排序或者借助 OCR 引擎的版面分析能力重排。扫描件是另一回事没有文本层就谈不上提取必须先做 OCR而且扫描件的版面分析还涉及表格识别、图片区域检测现在一般都直接交给 PaddleOCR 或 RapidOCR 这类完整 OCR 引擎。记住一条经验拿到 PDF 后不要急着搭流水线先用工具抽一页看看文本提取质量再决定路径。很多人在这一步偷懒后面代码节点写了一堆补丁也救不回来。2.3 翻译后的版式失衡文本长度变化比想象中更麻烦就算文本提取干净了翻译完成之后还会遇到一个特别容易被忽略的问题译文长度和原文不一样而且不是简单的等比例缩放。英文翻译成中文同样的内容字数通常会缩短 30% 到 40%因为中文的信息密度更高中译英则反过来长度可能膨胀 20% 到 50%。放在原始坐标上替换文本文本框大小不变短了留白太多长了溢出换行。即便是用流式排版的重建方案也可能出现这样的情况原文五行的段落译文只有三行原来的分页位置失效原文两栏高度一致译文一栏明显比另一栏短版式看起来很不协调。处理这个问题的核心原则是不要试图在新文档里保持“每一段长度一致”而是保持“段落逻辑一致 整体版式风格一致”。简单说标题还是标题层级不变表格还是表格列数和数据结构不变图片位置不追求像素级对齐但顺序和上下文不变。接受这种“逻辑上的原格式”你的实现难度会下降一大截出稿速度也快得多。3. 基于 Dify 工作流的整体设计3.1 把 Dify 当作流水线调度中心明确了技术路径之后我回到 Dify 里开始设计工作流。一个核心观念先摆出来Dify 不是翻译引擎它是流水线调度中心。Dify 工作流最大的价值是把你需要人工连接的操作变成可视化节点文件上传、文本提取、代码处理、LLM 调用、结果输出每个环节都变成一个可以单独调试的模块。某个环节出问题你只要看日志定位到具体节点不用整个流程从头跑。对我这种习惯反复改的人来说这个特性太重要了。在 Dify 里搭建这条流水线用到的核心能力有四块文件变量、文档提取器、代码节点、LLM 节点。文件变量用来接收上传的 PDF文档提取器负责把 PDF 内容转成纯文本代码节点做文本清洗、分块、结果组装这些不适合 LLM 干的活LLM 节点才是真正执行多语言翻译的地方。模型管理则允许你随时切换底层模型中英互译我常用 Claude 和 GPT 系预算敏感时也能切到国产模型。3.2 节点编排与数据流向这条流水线我最终的节点编排是这样的开始节点 → 文档提取器 → 文本预处理代码节点 → LLM 翻译节点 → 结果组装代码节点 → 结束节点开始节点定义了一个“文件”类型的变量用来承接上传的 PDF。文档提取器节点读取这个文件输出包含全文的字符串。文本预处理代码节点拿到字符串后做几件事清理页眉页脚、压缩多余空行、识别标题和表格标记、按段落切分成长度合适的文本块。LLM 翻译节点按分块顺序逐块翻译输出译文。结果组装代码节点把所有译文块按阅读顺序拼接成一份完整 Markdown最后在结束节点里作为文件或文本输出。这里要特别说明一点Dify 的文档提取器对数字化 PDF 识别效果尚可但它只提取文本不做版面分析。如果你处理的是双栏论文提取器也会把文本顺序搞乱所以我在它后面一定接一个代码节点做预处理。扫描件则不能在文档提取器这里硬扛需要提前 OCR 成带文本层的 PDF再进入流水线。数据流向方面文件变量只在开始节点产出不会自动传给后面所有节点你得在后续节点里显式引用它。字符串变量则可以通过“变量赋值”或代码节点的返回值不断更新。这块刚上手容易搞混我的建议是先在草稿纸上把每个节点的输入输出画一遍再回 Dify 里拖节点。3.3 分块策略和术语一致性LLM 翻译不是把整篇文档一次性丢进去就行。超长文档超出上下文窗口必得分块分得太碎上下文一断前后术语就翻得不一致了。分块策略我踩过好几轮。我现在采用按语义段落分块单块控制在 1000 到 2000 字符左右最长不超过 3000。为什么不按固定字符数切因为固定字符数很容易把一个完整段落从中间截断LLM 拿到半段话很难判断整体语义。按段落切分虽然块长度参差不齐但每块都是完整语义单元翻译质量稳定得多。如果相邻两块内容联系紧密比如一个长列表被拆开了我会在分块时保留 100 到 200 字符的重叠把上一块结尾带进下一块开头给模型多一点上下文。术语一致性是另一个容易被忽略的点。技术文档里“Transformer”该翻成“变压器”还是“Transformer”产品名该不该保留原文这些规则如果不告诉模型模型就会按自己的理解随意发挥。我在 Dify 里的做法是给系统 Prompt 塞一份术语表同时把公司常用的术语表构件成知识库作为可选输入。不是每个项目都需要挂知识库但一份几十行的术语表对专业文档的帮助立竿见影。3.4 需要准备的模型和外部依赖跑这套流水线之前有几个基础设施要提前确认。第一是模型翻译场景对指令遵循能力要求不低建议至少选一个当前主流的商用模型或者能力达标的开源模型我自己的默认配置是用中长上下文模型并开启 Dify 里的模型 API 配置。第二是格式转换工具如果你要最终导出 PDFDify 沙箱里不一定有 LibreOffice 或 Pandoc我通常是把组装好的 Markdown 或 DOCX 通过 API 回传给自己的一台服务器或本地脚本再调用 Pandoc 转 PDF。第三是 OCR 引擎只处理数字化 PDF 可以不准备一旦出现扫描件就得提前把 OCR 服务接入到文档提取之前。这些依赖不用一天全部配齐可以先跑通核心链路再按需追加。我最初做的时候系统 Prompt 写得很随意文档提取后也没做清洗第一版结果惨不忍睹后来一步步把预处理和术语表加进去效果才稳定下来。4. 手把手实现多语言 PDF 翻译流水线4.1 创建应用与配置模型打开 Dify 控制台新建一个应用类型选“工作流”而不是“聊天助手”。聊天助手偏对话式交互适合问答场景我们要实现的是确定性的批处理流程工作流才能精确控制每一步。创建后先进入“编排”页面右侧找到模型配置区域选择你已经接入的模型供应商。我的建议是设置两个模型一个做翻译主模型配置低一点温度控制创造性另一个备用模型或小模型做文本清洗、关键词校验这类轻量任务。模型没接的话Dify 里各模型的配置差异不大按官方文档把 API Key 填进去即可。接下来拖出开始节点添加一个“文件”类型的变量命名为 pdf_file用来接收后续上传的 PDF 文件。再拖一个结束节点在输出里预留一个字符串变量和一个文件变量位置分别用来放译文 Markdown 和最终生成的文档。这个时候工作流还是空的先把变量打通后面填节点就不容易乱。4.2 文档解析与文本预处理在 Dify 的节点列表里找到“文档提取器”输入端引用开始节点的 pdf_file。这个节点直接输出一个字符串就是提取出来的全部文本。你可以在预览面板快速验证提取质量看看是不是乱序、有没有把页眉页脚混进来。提取出来的文本通常脏得很需要代码节点清洗。我习惯在文档提取器后面放一个 Python 代码节点做以下几件固定动作删除重复的页眉页脚行比如每页都出现的公司名称、页码、章节名。把多个连续空行压缩成一个。识别明显的标题行如果原来是“1.2.3 xxx”这种编号给它加上 Markdown 标题标记。按段落切分成长度合适的文本块存入数组变量。一个简化版的分块代码大概长这样import re def main(text: str): # 去掉页码等纯数字行 lines [line.strip() for line in text.splitlines() if line.strip() and not re.fullmatch(r\d{1,4}, line.strip())] # 合并成段落 paragraphs [] current [] for line in lines: if line.endswith((., 。, :, , ;, )): current.append(line) paragraphs.append( .join(current)) current [] else: current.append(line) if current: paragraphs.append( .join(current)) # 按段落切分块大小控制在1500字符左右 chunks [] block [] size 0 for p in paragraphs: block.append(p) size len(p) if size 1500: chunks.append(\n.join(block)) block [] size 0 if block: chunks.append(\n.join(block)) return {chunks: chunks}这个代码不复杂但能解决大部分“提取结果没法直接用”的问题。注意 Dify 代码节点有自己的输入输出约定函数名必须是 main返回必须是可 JSON 序列化的结构我在上面示例里已经把格式写成了适配 Dify 的形式。4.3 翻译 Prompt 的设计LLM 节点是整条流水线的核心Prompt 设计直接决定翻译质量。我踩过最大的坑是给模型的指令太笼统只有一句“翻译成中文”结果标题层级丢了、表格标记没了、术语五花八门。现在我的翻译 Prompt 长这样分享出来可直接抄你是一位资深技术文档翻译专家擅长多语言互译。请把用户给出的原文片段翻译成指定的目标语言。 要求 1. 完整保留原文的结构标记包括 Markdown 标题符号 #、列表符号 - 和数字序号、表格分隔符 | 和 ---。 2. 严格使用用户提供的术语表不得随意替换术语术语表中未出现的专有名词第一次出现时可以保留英文原文并在括号里给中文译名之后统一使用中文译名。 3. 表格只翻译单元格内容不改变行列数。 4. 不要添加原文没有的解释或说明不要输出翻译以外的任何内容。 5. 目标语言{{language}} 术语表 {{glossary}} 原文片段 {{text}}三个占位变量要分别接到上游数据text 接分块后的原文glossary 接一个包含术语对的文本language 接目标语言。它们分别在前面节点定义好或作为工作流输入参数传入。模型温度我设置为 0.2 到 0.3不设太低否则翻译容易变得机械、失去流畅度但也必须控制创造性和随意发挥。每次改完 Prompt我在预览里用同一段测试原文跑两次观察输出是否稳定稳定了再推进下一个节点。4.4 结果组装与输出下载LLM 节点翻译完输出的是一个一个独立译文块。结果组装代码节点把数组里的译文块按原顺序拼接并做最后清理比如把块与块之间多余的换行合并、统一每个段落前是否有缩进。拼接后的完整译文是一个 Markdown 字符串。这个字符串直接放在结束节点的文本输出里用户在运行工作流后就能在结果页复制。但光有文本不够很多人要的是能直接分发或打印的 PDF 文件所以还要多一步格式导出。Dify 沙箱环境对第三方库限制比较严我不建议在 Dify 代码节点里直接尝试“生成 PDF”这种操作大概率会碰到库缺失或权限不足。更稳的做法是分两步走第一步在工作流结束时用 API 把译文 Markdown 回传到你自己的服务器或本地目录第二步在服务器上执行一条 Pandoc 命令完成转换。这是我本地标准化的导出命令pandoc output.md \ -o output.pdf \ --pdf-enginexelatex \ -V CJKmainfontNoto Sans CJK SC \ -V geometry:margin2.5cm如果你更想要 DOCX 而不是 PDF就把输出格式改成 docx命令更简单Word 会负责自动重排对表格和长文档更友好pandoc output.md -o output.docx这条链路绕了一层但胜在灵活Dify 负责翻译质量外部脚本负责版式成品各干各的强项。我实际项目里就是用一个监听 Dify Webhook 的小脚本收到译文后自动执行 Pandoc然后上传到指定目录整条链路全自动。4.5 完整测试与调优流程新搭建的流水线第一次跑通常不会完美我有自己的一套调优顺序从快到慢能快速定位问题。先用一页内容做小样测试。上传一个只有两三页的简单文档跑通全流程重点看输出有没有明显乱序。然后增加一个带多栏或表格的页面看预处理代码节点是否能把结构保留下来。接着检查术语找几个技术名词看译文是否按术语表执行。最后才整篇文档跑一次完整流程随机抽三页人工对照原文和译文。这个顺序每次都能帮我节省很多时间因为如果你在小样本阶段就发现问题处理成本往往只有几分钟等整篇跑完才发现分块错乱返工成本就高了。调优过程中我会持续盯着 Dify 每个节点的运行日志LLM 节点可以看完整输入输出代码节点则看返回的 JSON 结构出问题一目了然。5. 常见问题速查与踩坑实录5.1 提取出来的文本顺序全乱了这是大多数人在 PDF 翻译上碰到的第一个拦路虎。原因前面说过双栏或表格布局下内部文本对象顺序不等于阅读顺序。乱序的具体表现是第一栏读一半突然跳到第二栏再跳回来表格里数字和其它单元格内容被拆得七零八落。我对付这个问题有两招。第一招是换提取工具Dify 内置提取器输出乱序时用 pdfplumber 或 PyMuPDF 这类能拿坐标的工具做一次重排。第二招是直接放弃“从 PDF 提取顺序”这件事改用转 DOCX 的方式Word 引擎会自动根据版面分析重建阅读顺序。我现在处理双栏论文时默认先转 DOCX 再进 Dify效果比纯文本提取稳定得多。5.2 扫描件 PDF 一个字符都提不出来文档提取器输出空字符串基本可以断定这份 PDF 是扫描件没有文本层。很多人在这一步以为自己的 Dify 配置坏了其实不是是文件本身的问题。解决方案是 OCR。我常用的流程是先用 PaddleOCR 对每页做文字识别生成带文本层的新 PDF再把新 PDF 丢进原有的 Dify 流水线。注意 OCR 识别的准确率直接影响后面翻译质量尤其是术语和缩写。扫描件比较多的场景我会在文档提取器前增加一个 OCR 分流节点自动判断文件是否包含文本层没有就进入 OCR 分支有就走常规提取分支。5.3 译文段落变短表格溢出这个属于“格式重建”问题不是翻译质量的问题。原文五行的段落译成中文可能只有三行如果直接按原文坐标排版页面下半部分会出现大片空白表格里的单元格装不下中文时内容就会溢出。我试过很多办法最有效的是放弃“按原页面坐标逐行对应”的思路改用流式文档重建。把译文写进 Markdown再通过 Pandoc 导出让排版引擎自己处理换行和分页。这样虽然不会跟原文件像素级一致但逻辑结构和阅读体验都保留住了交付给业务方完全够用。如果你面对的是合同、标书这种对版式特别敏感的文档就改用 Markdown 转 DOCX 再手工微调效率也比从零排版高得多。5.4 模型翻译时把 Markdown 标记弄丢了这种情况最气人内容翻译对了但标题的 # 号没了表格的 | 分隔符不见了列表编号变成纯文本。原因通常是 Prompt 里对结构标记的约束不够强或者模型为了“让译文更通顺”主动把标记当作噪音清理掉了。针对性解决办法有几个。第一在 Prompt 里明确强调“保留所有结构标记”并给一两个示例few-shot 比单纯指令可靠。第二把温度降到 0.2 以下减少模型自由发挥空间。第三在后置代码节点里做一次结构校验比如检查原文的 # 数量是否和译文一致不一致时再重投一次翻译。我的流程里已经把第二和第三个办法都加进去了在实践中校验成本远低于人工返工成本。5.5 Dify 输出文件下载与格式转换的那些坑工作流跑完后译文在结束节点输出很多朋友希望在页面里直接下载一个 PDF 文件。这里有个现实问题Dify 沙箱环境内直接生成 DOCX 或 PDF 文件并挂载到输出节点在社区版里支持有限。最开始的版本里我试着在代码节点里用 Python 生成一个 DOCX 文件返回结果运行报错或者文件变量无法正常输出调试到怀疑人生。最终稳定方案是Dify 只负责输出 Markdown 文本通过 Webhook 或 API 把文本发送到自己的中转服务再在那边调用 Pandoc 转文件。中转服务可以是几十行代码的小 Flask 应用也可以是 n8n 这类自动化平台。整个过程不算难但确实需要一点点前后端配合的思维这也是整套方案里对我而言唯一需要“再写一个小系统”的地方。6. 一些扩展和我的实际体会6.1 接入术语库让专业翻译更可控基础流水线跑通后我第一个做的扩展是接术语库。Dify 的知识库天然适合存术语表我把每个客户或每个产品的术语做成一个独立知识库在工作流里作为“知识检索”节点挂进去让 LLM 在翻译前先检索相关术语。这里有一个非常实在的效果不同业务线的文档翻译术语风格完全一致了。公司的产品经理和技术人员都很在意“超融合”“数据面”“控制面”这些词在不同文档里的译法是否统一知识库接入后这个问题基本没有再出现过。如果你只是个人使用不搭知识库也行直接在 Prompt 里写死后缀也够用。6.2 批量处理与定时任务单篇文档跑通之后自然想批量处理。Dify 的界面操作适合小批量和调试几十份文档逐一点运行太痛苦。我后来写了一个 Python 脚本通过 Dify 服务端 API 批量提交文件回调接收翻译结果再自动执行 Pandoc 导出。这个脚本大概三百行跑完整个流程完全不依赖人工干预白天丢一批文件进去下班前就能收到全部译文文档。批量处理有个注意点要控制并发和限额避免瞬间打爆 Dify 服务或模型 API 配额。我在脚本里做了简单的时间间隔控制和失败重试机制每提交一份休眠几秒失败任务先记日志全部跑完后统一人工处理重试项。6.3 我越来越觉得“格式”才是文档翻译的门槛整套方案从想法到稳定运行前后折腾了大概两周期间反复在“提取乱序”“格式重建”“术语不统一”几个问题上打转。到后面我越来越确定一件事语言转换本身已经不是门槛了多语言模型的翻译质量远超绝大多数人的预期真正的门槛在“格式”二字。谁能把 PDF 解析得干净、谁能让译文漂亮地重排回一份可用文档谁才真正解决业务问题。我现在的这套 Dify 流水线本质上是在“内容翻译质量”和“格式还原成本”之间找到了平衡点——不追求像素级还原但保证结构清晰、排版自然、术语一致交付速度还足够快。遇到特别敏感的版式要求就落到 DOCX 分支再做精修。这也是我在这件事上最重要的体会先跑通流程再逐步加约束别指望第一次就把文档翻译做成工业级产品做成这样已经能省掉团队大量手工时间了。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →