☰
DeepSeek 联手 LibreOffice,打通文档 Agent 的最后一公里交付
2026/10/10 4:14:19 网站建设 项目流程

DeepSeek 把 LibreOffice 塞进 Harness,文档 Agent 开始补齐最后一公里

最近我在搭一个"文档 Agent"模拟项目,越搭越觉得有意思:模型聊天、规划、拆解任务这些能力已经够用了,真正让人头疼的是"交付物"——用户不会满足于屏幕上的一段 Markdown,他们要的是能下载、能打印、能发给客户的 docx、PDF、ODT。换句话说,Agent 的前 99 公里是推理和对话,最后一公里是真实文件。

我最后选用的组合是 DeepSeek 负责语言理解和工具调用规划,LibreOffice 以无头模式充当文档渲染引擎,外面再套一层 Harness 来控制工具注册、权限、超时和错误恢复。这套东西跑通之后,Agent 才算是真正"能交差"了。这篇就把我踩过的坑和最终的实现思路完整拆出来,给同样在折腾文档 Agent 的人当个参考。

1. 文档Agent的"最后一公里"究竟卡在哪

1.1 聊天很顺,交付物却一塌糊涂

我最早搭的 Agent 版本,本质上就是个"加强版聊天机器人"。用户说"帮我写一份月度汇报",它能输出结构完整的文字,甚至能给出好看的排版建议。但只要用户追问一句"导出成 PDF 发我一下",整个链路就露馅了:要么模型只会生成一段 Markdown,用户还得自己复制到编辑器里去转格式;要么我硬写一段 Python 脚本直接把字符串塞进 docx,结果没有样式、没有目录、没有分页,交付物像一份未经排版的长文。

这不是模型能力问题,而是工程架构问题。模型擅长的是生成连续令牌,它并不擅长也不需要直接操作二进制文件。文档 Agent 的"最后一公里",本质上指的是从文本生成到文件交付这中间的所有工程环节:格式转换、模板填充、嵌入图表、生成页码目录、导出 PDF、兼容老旧 Office 格式等。没有这一公里,Agent 充其量是个聪明的文本生成器,算不上文档生产力工具。

1.2 最后一公里不是生成文本,而是生成文件

我习惯把文档 Agent 的能力拆成三层看:

  • 语言层:理解用户意图、生成内容、拆解多步任务,这是模型的事;
  • 规划层:把"写一份带数据图表的调研报告"拆成若干子任务,决定先后顺序,这也是模型配合 Harness 完成的;
  • 执行层:真实打开文档、写入内容、套用模板、转换格式、导出文件,这是工具链的事。

大多数项目死在执行层。原因也很现实:文本生成有现成的模型能力兜底,文件操作却没有一个通用的"万能模型"能直接输出 PDF 二进制流。模型最多输出一个工具调用意图,剩下的必须交给外部程序去干。LibreOffice 在这条链路里就是最顺手的执行层引擎。

1.3 DeepSeek在这条链路里的位置

选 DeepSeek 做语言层,我自己是这么权衡的:上下文窗口足够长,适合让它读完整份模板和素材再规划;工具调用的指令遵循能力不错,能稳定输出结构化的 JSON 工具请求;部署成本可控,既可以用 API,也可以本地跑量化版本,这对需要处理敏感文档的场景很重要。

但这里要强调一个边界:DeepSeek 再聪明,也不会自己打开 LibreOffice 去转 PDF。它要做的是把用户需求理解成"工具动作序列",剩下的事交给 Harness 去执行。模型管想,LibreOffice 管做,Harness 管调度。三者各管一段,链路才干净。

2. 为什么偏偏选中 LibreOffice:它是Agent少有的"全能文件引擎"

2.1 LibreOffice在Agent里的真实角色

很多人在朋友圈里听到"把 LibreOffice 塞进 Harness",第一反应是"又一个套壳工具"。实际用下来,LibreOffice 在 Agent 体系里的角色更像一个"文件渲染内核"。

它可以无头运行,不需要图形界面,适合放在服务器上;它原生支持 ODT、DOCX、XLSX、PPTX、PDF 等几十种格式,能把老旧的 .doc、.xls 也救回来;它还提供 UNO 编程接口,允许你从 Python 里启动它、打开文档、修改内容、另存为其他格式。对我来说最重要的一点是:它把"文档格式"这个复杂问题收敛成了一个"本地服务",Agent 只需要和这个服务打交道。

2.2 直接写文件 vs 交给LibreOffice渲染

我之前也试过用 Python 库直接生成 docx,比如 python-docx。简单文档没问题,但一旦涉及模板、跨格式转换、老版本兼容就非常吃力。我整理过一张对账表,当时选型就看到很直观:

场景Python直写LibreOffice无头服务
新建简单docx方便略繁琐
套用复杂模板要保留样式麻烦,样式容易丢通过ODT模板填充后导出,保留度高
doc转docx/pdf基本不可行一条命令
xlsx公式重算需要额外库模拟计算启动计算链后自动重算
带批注/修订的文档处理很难UNO接口可以操作
老格式甚至 .wps不支持转换兜底能力强

这些对比说明一个核心问题:文档不是纯文本,它里面有节、段、样式、页眉页脚、批注、修订、公式、嵌入对象。用"读写 XML 的库"去模拟这些能力,等于重新造半个 Office。LibreOffice 已经把这些功能实现了二十多年,直接把它当引擎调用,是最省力的解法。

2.3 Harness与LibreOffice的关系

接下来解释一下 Harness。在 Agent 工程里,harness 指的是包在模型外层的"调度总成":它负责维护多轮上下文、管理可用工具列表、执行工具调用、限制权限、处理超时和错误恢复。你可以把它理解成给模型配的操作系统——模型只负责发指令,具体进程管理、参数校验、文件读写都由 harness 接住。

LibreOffice 本身不认识 DeepSeek,它只认 UNO 调用和命令行参数。所以二者的连接器就是 harness:模型说"我要把这个 docx 转成 PDF,放在输出目录",harness 负责把这句话规范化成一条合法的 LibreOffice 命令并执行,再把结果回传给模型,让它能接着规划下一步。没有这层管理,模型直接去拼 shell 命令,既危险又不可控。

3. 无头模式三件套:安装、常驻服务与Profile隔离

3.1 最小安装与第一次转换

在 Linux 服务器上安装 LibreOffice,不必装完整版全家桶。我的最小集是:

sudo apt update sudo apt install -y libreoffice-core libreoffice-writer libreoffice-calc fonts-noto-cjk

libreoffice-core 提供基础引擎,libreoffice-writer 处理文字文档,calc 处理表格,字体包先装上中文,避免后面转 PDF 时中文全部变成方框。装完先跑一次最小验证:

libreoffice --headless --convert-to pdf --outdir /tmp/test /tmp/test.docx

这条命令的意思是:无头模式启动,把 test.docx 转成 PDF,输出到 /tmp/test 目录。第一次跑会慢一些,因为要初始化用户配置目录,但成功后你能立刻在 /tmp/test 下看到 PDF。这一步通了,后面就可以考虑常驻服务方案了。

3.2 UNO常驻服务:让Agent告别冷启动

每次调 libreoffice 命令都会启动完整进程,文档稍大一点,光冷启动就要三五秒,任务一多就非常难受。更好的方式是启动一个常驻的 UNO 服务,让 Agent 通过 Python 连接:

soffice --headless --invisible --norestore --nofirststart \ --accept="socket,host=127.0.0.1,port=2002;urp;" &

之后 Python 侧用 uno 模块连接过来,就能直接打开文档、调用接口:

import uno from com.sun.star.beans import PropertyValue local_context = uno.getComponentContext() resolver = local_context.ServiceManager.createInstance( "com.sun.star.bridge.UnoUrlResolver") ctx = resolver.resolve( "uno:socket,host=127.0.0.1,port=2002;urp;StarOffice.ComponentContext") smgr = ctx.ServiceManager desktop = smgr.createInstance("com.sun.star.frame.Desktop") input_url = "file:///data/reports/weekly_report.docx" doc = desktop.loadComponentFromURL(input_url, "_blank", 0, ()) # ... 对文档做修改、重算、导出等操作 doc.close(False)

连接常驻服务后,转换和编辑的速度会快很多,因为省去了进程启动和组件加载时间。这才是 Agent 真正可用的形态:工具调用延迟稳定,不再随文档大小大幅波动。

3.3 多实例隔离:每个任务一个干净Profile

常驻服务的坑也随之而来:LibreOffice 默认会锁用户配置目录,多个进程或多次并发调用抢同一个 profile 时,会出现"already running"或者直接拒绝连接。我踩过几次之后,方案改成——每个任务都指定独立的 profile 目录:

soffice --headless \ -env:UserInstallation=file:///tmp/lo_profile_task_123 \ --accept="socket,host=127.0.0.1,port=2002;urp;"

这样每个任务都有干净的字体缓存、扩展配置、锁文件。任务结束之后把临时 profile 目录一并清理,不会互相污染。这个习惯养成了,你的文档 Agent 才能扛住并发请求。

3.4 中文字体与依赖:提前装好,别等出方框再哭

很多人第一步转换就发现 PDF 里的中文全是方框。这不是 LibreOffice 的问题,而是系统缺中文字体。除了前面提到的 fonts-noto-cjk,还可以装 fonts-wqy-zenhei 作为后备字体。装好后在终端里执行 fc-list | grep -i "noto|wqy" 检查字体是否被识别。字体问题属于"不遇到不知道,遇到一次就浪费半天"的类型,环境准备阶段多花两分钟,后面能省一整晚。

4. 在Harness里把文档工具管起来:注册、调用与容错

4.1 先想清楚:Agent到底需要哪些文档能力

在写代码之前,我先规划了工具清单。文档 Agent 最常见的执行需求其实就这么几个:

  • create_doc_from_template:基于模板 + 变量生成新文档;
  • convert_format:把一种格式转成另一种,比如 docx 转 pdf,或老 doc 转 docx;
  • export_pdf:带封面、目录、页码设置导出 PDF;
  • read_doc_text:从 docx/pdf 里抽取文本,供模型阅读;
  • fill_table:往指定的表格区域填结构化数据;
  • docx_to_text:把上传的文档先转成文本,给模型"看"。

这六个工具基本覆盖了日常文档操作的 80%。工具数量不建议一开始搞太多,每多一个工具,模型误调用的概率就高一分。先把核心链路跑通,再逐步加。

4.2 工具注册与模型输出的映射

在 Harness 里,每个工具都要注册清晰的 Schema。我拿 export_pdf 举例,它接收的参数要保持很"窄":

{ "tool": "export_pdf", "params": { "source": "/data/reports/weekly_report.docx", "outdir": "/data/output", "cover_page": true, "toc": true, "page_numbers": true } }

DeepSeek 在收到用户一句"把这周的报告整理成带封面和目录的 PDF"之后,会把它映射成上述 JSON 请求。Harness 拿到参数后,先做路径校验,再调用 LibreOffice 服务,等命令结束,把产物路径和日志摘要返回给模型。

整个过程里,模型不需要知道 LibreOffice 装在哪个目录、用什么端口、有哪些启动参数。它只负责把自然语言翻译成工具请求,工具的复杂度全部封装在 harness 侧。这也是 Agent 工程里比较健康的边界。

4.3 参数校验与路径沙箱

模型的输出不能盲信。我会在 harness 里强制做这几项校验:

  • 路径必须是白名单前缀,比如 /data/input 和 /data/output,其他路径直接拒绝;
  • 文件名必须做合法化处理,禁止包含 .. 或 shell 特殊字符;
  • 输出目录不存在时先创建,但只在允许的范围内创建;
  • 文件类型后缀必须和转换目标匹配,不能出现"把 PDF 当模板填数据"这种操作。

这些规则看起来是常识,但实际跑起来你会发现,模型在生成参数时出错的概率比想象中高。路径多了个引号、少了个反斜杠、把目录传到了文件字段里,这些都会让工具调用失败。靠模型自觉不如靠 harness 硬校验,这一步省不得。

4.4 超时与失败回退:不能因为一个文件卡死整个任务

文档转换有时会长时间无响应,原因可能是文件损坏、临时文件过大、或者 LibreOffice 进程僵死。我会给每个工具调用设置超时,比如默认 30 秒,超过就强制终止并清理进程:

timeout 30 libreoffice --headless --convert-to pdf --outdir /data/output /data/input/bad_file.docx

如果超时,harness 返回给模型的错误信息必须是"可行动的":文件路径、失败原因、剩余空间、进程日志片段。模型可以基于这些信息决定下一步——可能是重试一次,可能是让用户换文件,或者降级成"先抽取文字供阅读"。重要的是,单个文件的失败不能拖垮整个 Agent 任务,该放弃就放弃,该重试就重试。

5. 实测踩坑记录:字体方框、并发锁与格式漂移

5.1 中文字体变成方框,问题不在LibreOffice而在系统字体

第一次在服务器上跑转换,所有中文字符全部渲染成方框,往 PDF 里嵌字体也溅不起水花。排查到最后,发现系统里只有英文字体,LibreOffice 找不到中文字体就全部拿"默认空字形"代替。装上 fonts-noto-cjk 之后症状消失。经验就是:无头服务器的字体环境和普通桌面不同,桌面一般自带一堆字体,服务器真就是"裸奔"。环境准备阶段把常用中英文字体都装一遍,能杜绝后面一大半格式问题。

5.2 并发调用时的Profile锁与僵尸进程

后面我试着同时跑三个转换任务,结果一个成功、两个报错,错误日志是用户配置目录被占用。原因就是我前面说的 profile 锁。LibreOffice 只允许一个进程操作同一 profile,多任务并发必须隔离。用 -env:UserInstallation 指定独立目录之后,问题解决。另外要注意,headless 模式在某些异常情况下会留下僵尸 soffice 进程,定期用 pkill -f soffice 清理,或者让每次转换都在独立 profile 里跑,结束后自动清理,能避免进程堆积。

5.3 大文档转换超时与任务回滚

一份 200 页带大量高清图片的文档,转换耗时可能超过一分钟。如果 Agent 层面不给超时,模型会一直等在那里,占住窗口不释放。前后端任务都卡死,体验极差。我的处理是在 harness 里加了动态超时:普通文档默认 30 秒,超过 10MB 的文档放宽到 120 秒,并且转换过程轮询文件产物是否出现,而不是死等进程退出。一旦超时,明确告诉用户"文档过大或格式异常,建议拆分成小文件重试"。

5.4 模板填充后格式漂移:从"在XML里硬改"到"先转ODT再导出"

原本想在 docx 里直接做模板变量替换,用 Python 改 document.xml,结果变量替换成功但整个段落样式全乱了——因为 docx 的 XML 结构里,样式信息分散在多个关系文件里,手动改一个节点很容易破坏其它引用。

后来换成更稳妥的套路:先把模板转成 ODT,在 ODT 里用占位符标记,比如 {{client_name}},再用 UNO 接口搜索替换,最后无头导出为 docx 或 PDF。ODT 的结构比 docx 的松散 XML 更规整,LibreOffice 对自家格式的读写保真度最高,替换结束后样式、分页、字体很少出现漂移。这也是我强烈建议"模板文件先用 LibreOffice 生成或净化一遍"的原因。

5.5 文件名带空格/中文时:参数解析失效

还有一个小坑:文件名或路径里带空格和中文时,直接用字符串拼接 shell 命令会出各种怪问题。最稳妥的办法是不走 shell,直接用 Python 的 subprocess 传入参数列表,让系统帮你处理转义。或者用 UNO 打开文件时把路径统一转成 file:// URL 格式,中文和空格都会被正确编码。千万不要为省事把文件名拼接进命令字符串,这一点在无头环境里尤其致命。

6. 跑通之后:从"能生成"到"能交差"的几个扩展方向

6.1 实测一档:一条指令生成带封面和目录的PDF

整个链路跑通之后,我做了个端到端测试:给 Agent 说一句"把季度财务说明生成 PDF,带封面、目录和页码,封面标题用大号粗体"。DeepSeek 将它拆成三个子任务——填充数据到模板、生成目录、导出 PDF;Harness 依次调度,LibreOffice 完成所有排版;最终产物是一个 12 页、带封面和可点击目录的 PDF。整个过程从用户提问到文件返回大概三四十秒,对付大多数办公场景已经足够。

要说哪里还值得优化,就是目录页码最后重新加载一次文档才能正确刷新。这个小问题不算大,但如果用户经常要改内容再重新导出,就会多出一次空转。所以我也在工具里加了"导出前是否刷新字段"的开关,让模型根据具体情况决定。

6.2 扩展方向:表格重算、PDF反向抽取、多格式分发

跑通基础链路后,后续可以继续扩展的方向也很多:

  • 表格重算:让 Agent 修改 xlsx 里的公式参数,再调用 LibreOffice 重算,输出带最新结果的表格;
  • PDF 反向抽取:把 PDF 转回文本或结构化数据,喂给模型做摘要和问答;
  • 多格式分发:一份内容同时导出 PDF、docx、txt,方便不同终端用户使用;
  • 批量水印与页眉:通过 UNO 接口批量操作,这一点对合同、标书的场景非常实用。

这些方向的核心仍然是同一个模式:模型负责理解和规划,LibreOffice 负责所有真正麻烦的文件级操作,harness 在外围做控制和兜底。模式一旦跑通,换场景只是多注册几个工具而已。

6.3 我最后留下的稳定组合

如果你也想照着搭,我最后留一个当前比较稳的配置参考:

组件选型建议
语言模型DeepSeek,API 或本地部署均可
调度层自研 Harness 工程,轻量即可,重点做好工具注册与超时
文档引擎LibreOffice 7.x 及以上,headless + UNO 常驻服务
字体fonts-noto-cjk + fonts-wqy-zenhei
并发隔离每个任务独立 UserInstallation profile
超时策略普通文档 30 秒,超大文档 120 秒,超时即回收
路径规则白名单目录 + 参数合法化校验

组合并不复杂,但每一环都有存在的理由。模型再聪明,最终也要落到"文件真的能打开、格式真的不变"这个朴素的标准上。文档 Agent 的竞争力不在聊天,而在交付。


最后说点我自己的体会:做文档 Agent,最容易低估的就是"文件操作"这部分的工程量。模型把话说得漂亮很容易,把文件做得漂亮很难。LibreOffice 的价值不是某个花哨功能,而是它能稳定接住那些脏活累活——不管输入是老格式、带模板、还是带公式,它都能给你一个可预测的产物。

如果你正在搭类似的文档 Agent,我的建议是先把"一条最简单的转换链路"跑穿,再谈复杂功能。第一次看到 Agent 自主生成一份排版完整的 PDF 时,那种"终于不是纸老虎"的感觉,值得你折腾到今天这一步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询