DeepSeek+Harness+LibreOffice:打通文档Agent最后一公里
最近我在折腾一个把 DeepSeek 接进 Harness 的项目做着做着发现一个特别现实的瓶颈模型能写出很漂亮的内容但生成不了“文件”。你说一个 Agent 分析完数据最后回给你一坨 Markdown 文本这在命令行里看看还行真要交付给业务同事人家要的是带页码、带目录、排版正常的 docx 或 PDF。所以我把 LibreOffice 整个塞进了 Harness 的工具箱让文档 Agent 终于能补齐这最后一公里——直接吐出可交付的文档。这篇文章就聊聊我这个组合的架构思路、接入细节以及实测中踩过的各种坑。1. 这波折腾的背景文档 Agent 的输出经常卡在“最后一公里”1.1 真实工作流不认 Markdown我最早做的几个 Agent 原型输出全靠文本流。跑 RAG 问答还行用户问一句模型引着知识库答一段终端里贴出来也像那么回事。但真正用到办公场景里就完了你让 Agent 生成一份季度总结、整理一份合同初稿、把一堆数据变成报表它输出给谁看总不能让人手动复制进 Word 再调一天格式吧。这个“最后一公里”我理解有两层意思。第一层是格式落地内容要能落到真正的文档格式里比如 docx、odt、PDF第二层是流程落地文档要能被后续环节直接使用比如归档、打印、合并、盖章。之前很多 Agent 项目就死在第一层因为大家默认“能生成文本就等于能生成文档”忽略了下游消费方其实只认文件。1.2 为什么偏偏是 LibreOffice你要是只想要一份“像样”的文档其实有很多方案比如直接调在线文档服务或者用 python-docx 拼 docx。但我的场景有几个硬约束要离线局域网可用要覆盖文字、表格、演示三类文档要能被 Agent 当成一个工具频繁调用还不能按次收费。市面上能同时满足这几点的LibreOffice 几乎是唯一选择。它本身就是开源办公套件支持 docx、xlsx、pptx 这些常见格式的读写带完整的无头模式headless可以在没有桌面环境的情况下作为一个排版服务来跑。更关键的是它自带 UNO API可以程序化控制文档对象。这意味着我不光能“把文本灌进模板”还能设置样式、插入表格、导出 PDF整套操作都能封装成 Harness 里的工具函数。2. 三层架构怎么搭DeepSeek 出内容Harness 做编排LibreOffice 管排版2.1 三个角色各干各的这个项目里三个组件各司其职缺一个都会变味。DeepSeek 是大脑负责理解用户的自然语言指令拆解成可执行的步骤并生成文档需要的核心内容。Harness 是手和骨架负责管理 Agent 的执行流程、工具调用、上下文传递以及失败回退。LibreOffice 是车间负责把内容变成真正的文档处理排版、样式、分页、导出等工作。你可能会问让 DeepSeek 直接生成 docx 的 XML 结构不行吗理论可行但实操非常痛苦。docx 本质是 zip 包里的 XML 文件模型生成这种结构很容易漏 namespace、图片关系、样式定义一旦缺了就整个文件打不开。LibreOffice 的价值就在于它把“内容和排版之间”的脏活累活全包了模型只需要告诉它“我要什么内容”剩下的交给模板和样式。2.2 一次调用链路的“慢动作”我习惯把整个流程拆成一条链来看这样出了问题好定位。用户发来指令比如“把这份销售数据整理成季度报告带趋势分析和表格”。Harness 先把这条指令和可用工具列表一起交给 DeepSeek模型返回一个执行计划比如“读取数据文件分析趋势调用文档模板工具生成 docx再转换 PDF”。Harness 按计划逐个调度工具其中文档生成工具会启动 LibreOffice 服务往模板里填内容导出文件最后把输出路径回传给模型。模型拿到路径之后可以判定任务是否完成如果需要还能继续追加修改。这条链路里有一个容易忽略的设计点工具返回给模型的信息一定要精简。不要把整个文档内容回传模型记不住那么长的上下文传一个文件路径加文件大小的摘要就足够了。如果后续要修改模型只需要基于用户最新指令再次调用工具不需要重新知道全文。3. LibreOffice 无头模式的接入细节UNO 连接、模板填充、PDF 导出3.1 无头模式安装与启动我用的系统是 Ubuntu 类的 Linux 服务器安装的时候不要整个套件都装用 --no-install-recommends 可以省掉很多没用的东西。sudo apt-get update sudo apt-get install -y libreoffice-writer libreoffice-calc --no-install-recommends注意 LibreOffice 有不少命令最关键的是 soffice。启动无头服务时我用的参数是soffice --headless --invisible --nodefault --norestore \ --acceptsocket,host127.0.0.1,port2002;urp; 这里的核心是--accept参数它打开了一个 UNO 监听端口。也就是说LibreOffice 不再只是一个“每次转换完就退出”的一次性进程而是常驻的服务其他程序可以通过 socket 连上来控制它。--norestore很重要如果不加异常退出后它可能试图恢复上次会话导致进程卡住。装完之后建议先手动测试一下服务是否正常别急着上代码。用命令转一个小文件是最快的探活方式soffice --headless --convert-to pdf --outdir /tmp test.docx如果这一步能正常输出说明库依赖没问题如果把 document 转换成 pdf 时报字体错误那多半是系统缺字体后面容器化那节我会专门讲。3.2 用 UNO 做模板填充我的方案不是让模型直接生成完整文档而是先准备一套模板再通过占位符填充。这么做的好处是格式稳定且用户可控性更强。模板里有{{title}}、{{summary}}、{{table_content}}这样的占位符Python 脚本连上 UNO 后打开模板替换占位符另存为新文件。import uno from com.sun.star.beans import PropertyValue def connect(): ctx uno.getComponentContext() resolver ctx.ServiceManager.createInstanceWithContext( com.sun.star.bridge.UnoUrlResolver, ctx) url uno:socket,host127.0.0.1,port2002;urp;StarOffice.ComponentContext component_ctx resolver.resolve(url) smgr component_ctx.ServiceManager desktop smgr.createInstanceWithContext(com.sun.star.frame.Desktop, component_ctx) return desktop def fill_template(template_path, output_path, replacements): desktop connect() props [] prop PropertyValue() prop.Name Hidden prop.Value True props.append(prop) doc desktop.loadComponentFromURL( ffile://{template_path}, _blank, 0, tuple(props)) # 遍历全文并替换占位符 for key, value in replacements.items(): search doc.createSearchDescriptor() search.SearchString key found doc.findFirst(search) while found: found.setString(value) found doc.findNext(found.End, search) doc.storeToURL(ffile://{output_path}, ()) doc.close(True)这里有个细节我一开始差点忽略loadComponentFromURL的 URL 必须是file://开头的绝对路径如果传成相对路径或普通路径UNO 会直接报错。另外替换文本后如果占位符本身带着表格的换行结构需要额外处理否则插入的内容会丢失格式。我实测下来最简单的稳妥做法是每个替换值里只放纯文本需要表格的地方单独用 API 创建不要让模型把表格的可见文本硬塞进去。3.3 命令行转换这条“安全通道”UNO 适合精细控制但它也是坑最多的路径。比如进程连接超时、端口被占用、连接无法释放都会导致整个 Agent 卡住。所以我实际上准备了两套工具一套是 UNO 模板填充精细操作另一套是纯命令行转换快速稳妥。soffice --headless --convert-to pdf --outdir /output report.docx命令行转换的好处是简单、隔离性强、不容易污染常驻服务。缺点是控制粒度粗只能做格式转换不能改内容。我在 Harness 里把这两个工具分开了需要精细排版走 fill_template只需要产出 PDF 版本走 convert_pdf。实践中你会发现绝大多数“最后一公里”问题用命令行转换就够了UNO 是锦上添花。4. 完整复现一次从一句自然语言到一份可交付的 docx4.1 先在 Harness 里注册两个文档工具接下来是集成环节。我用的 Harness 支持注册自定义工具说白了就是给每个函数写清楚名字、描述、参数告诉模型“你有一个工具叫这个名字输入这些参数调用后返回这些信息”。我的工具注册表长这样{ tools: [ { name: libreoffice_fill_template, description: 使用LibreOffice打开模板文件替换占位符生成docx文档, parameters: { template_path: string, output_path: string, replacements: object } }, { name: libreoffice_convert_pdf, description: 将docx或odt文件转换为PDF用于最终交付, parameters: { input_path: string, output_dir: string } } ] }这段注册信息会拼进系统提示词里DeepSeek 看到之后才知道“哦我可以用这两个工具”。我必须强调工具的 description 要写清楚适用场景。最开始我写得太简单模型经常在不需要转换 PDF 的时候也调用转换工具白白浪费几次调用。后来把 description 改成“仅在用户要求PDF格式或最终交付需要时使用”误调用率立刻降了下来。4.2 DeepSeek 的规划与工具调用我实际跑通的场景是让 Agent 基于一份销售数据生成季度报告要求“包含三个章节最后附一个对比表格导出为 docx 和 PDF 两个版本”。Harness 把指令交给 DeepSeek 后模型给出的规划大致是分析数据文件提取各季度销售数字。撰写报告文字包括概述、趋势分析、下季度建议。调用libreoffice_fill_template把内容填入模板。调用libreoffice_convert_pdf生成 PDF 版本。这里值得注意的是 DeepSeek 能根据模板结构自动决定替换哪些字段。模板里的{{chapter1_title}}、{{chapter1_body}}、{{sales_table}}这些字段模型会照着章节内容去填。不过sales_table这个字段不能直接填 Markdown 表格因为 LibreOffice 的文本替换不会自动把竖线文本变成表格。我的做法是让工具函数内部识别“表格占位符”然后单独用 UNO API 插入真正的表格对象。4.3 我踩过的几个小坑第一个坑是 LibreOffice 进程的并发安全。当我同时跑多个文档任务时多个 UNO 客户端连同一个服务会互相干扰。最典型的报错是 “connect failed” 和 “no such element in collection”。二次看日志发现是并发调用同一个 soffice 进程导致的。我的解决办法是把 UNO 连接封装进一个带锁的队列保证同一时刻只有一个填充任务在执行。第二个坑是中文字体缺失。默认容器里没有中文字体生成 PDF 的时候所有汉字变成方块整体排版直接崩了。这个问题排查起来最烦因为 docx 在 LibreOffice 里打开看着是对的转 PDF 才出错。后来我在系统里装好了 Noto CJK 字体并且在模板里把字体显式设置为“Noto Sans CJK SC”问题才彻底解决。字体问题对中文文档 Agent 来说几乎绕不开后面容器化章节我会再展开。第三个坑是 Harness 的上下文窗口被工具返回信息撑爆。最初我把整个模板填充后的校验结果都回传给模型文本很长。后来学乖了工具只返回“生成成功文件路径xxx大小xxKB”模型只需要这个结论就够了。你真需要让模型知道文档内容时它可以再开一个读取工具去读文件而不是在工具返回里塞全文。5. 从“能用”到“抗造”容器化、并发控制、失败回退里的实战心得5.1 批量生成时别让 LibreOffice 进程打架我把这套东西跑出单次流程之后第一个想做的就是批量生成几十份周报一次性跑完。结果并发一开问题立刻暴露。LibreOffice 的无头模式其实有两个用法。一种是我前面说的常驻服务模式快但单个进程扛不住并发。另一种是每次调用临时启动一个soffice --convert-to命令用完就退出慢但隔离性强。批量任务里我建议混合用转换 PDF 这种“无状态”操作走临时进程多个并行没关系前提是你控制了机器资源模板填充这种“有状态”操作走常驻服务但必须排队。我实测下来的经验是四核机器上同时跑六个--convert-to进程是没问题的再往上内存就开始吃紧。模板填充服务我最多给它留一个并发名额宁等勿乱。排队的实现直接用了 Python 的线程锁简单粗暴但后面跑了几千次文档任务一次没因为资源冲突崩溃过。5.2 容器镜像里的字体与 locale 问题容器化是“抗造”的关键一步。我的 Docker 镜像基于 Debian就安装文字处理相关组件和字体加上中文字体包同时把 locale 也设置好避免生成日期或数字格式时不按中文习惯显示。这一套下来镜像看起来有点大但换来的是环境完全可控再也不用担心换一台机器生成出来的文档样式不一致。这里还有一个容易忽略的点时区。默认容器的时区是 UTC文档里如果带日期时间戳很可能比你的本地时间早八个小时。我一开始没设时区连续两次看到文档里时间不对还以为是数据源的问题最后才查出来是容器的锅。5.3 代码回退与失败重试的设计最后一个让我真正觉得“可以交付”的功能是回退机制。文档生成过程里最让人头大的是“生成到一半失败”。比如模板里有模型没见过的占位符或者 LibreOffice 进程突然挂了如果 Harness 不处理用户只能拿到一个半成品文件路径体验会很差。我的处理方案分两层。第一层是 Harness 级别的重试失败之后Harness 从报错信息里提取关键词自动决定是重新调用工具还是调整一次参数再试。比如转换 PDF 失败大概率是输入文件的问题那就原样重试一次如果还失败就果断放弃不要再让模型空转。第二层是模板级别的回退每个模板文件都保留一个备份填充前先备份填充失败就用备份恢复确保原始模板不会被污染。这些设计做完之后我最大的感觉是整个 Agent 从“偶尔能出文档”变成了“稳定产出文档”。至少在离线局域网的环境下DeepSeek 负责理解指令Harness 负责任务编排LibreOffice 负责排版输出一套既不需要联网又不需要付费的文档 Agent 工作流就闭环了。如果你也想搭类似的东西我的建议是不要一上来就追求复杂功能。先把“自然语言到纯文本”跑通再单独接一个 LibreOffice 转换工具最后再加模板填充和批量并发。每一步都验证完再往前走踩坑的频率会小很多。

相关新闻

赛诺菲把新品定义交给上海:大客户销售如何重画决策链地图

赛诺菲把新品定义交给上海:大客户销售如何重画决策链地图

“中国接单、外国决策”成过去式——赛诺菲把下一款新产品交给上海定义。对大客户销售而言,这条新闻的分量在于:客户决策链到了必须重画的时刻。 决策权搬到哪里,关系力就要跟到哪里 客户组织里谁说了算,从来不写在组织架构图上。…

2026/10/10 13:15:13 阅读更多 →
6568★ 只用了 7 天,但增速已经转缓:fast-jev-compaction 的 GitHub 曲线说明什么?

6568★ 只用了 7 天,但增速已经转缓:fast-jev-compaction 的 GitHub 曲线说明什么?

6568★ 只用了 7 天,但增速已经转缓:fast-jev-compaction 的 GitHub 曲线说明什么? 【免费下载链接】fast-jev-compaction Claude Code plugin that replaces the compaction summary with Jev decisions: every tool call and result is sco…

2026/10/10 13:15:13 阅读更多 →
显卡驱动更新的三大精准方法:硬件ID锁定、DDU清理与Windows Update深度利用

显卡驱动更新的三大精准方法:硬件ID锁定、DDU清理与Windows Update深度利用

1. 为什么“更新显卡驱动”这件事,90%的人做得既慢又错?“亲测有效!3个实用的电脑更新显卡驱动小妙招”——这个标题一出来,我就知道它戳中了太多人的日常痛点。不是没人想更新驱动,而是大多数人卡在第一步&#xff1a…

2026/10/10 13:15:13 阅读更多 →

最新新闻

软件测试面试题全解析:从基础理论到AI与物联网实战

软件测试面试题全解析:从基础理论到AI与物联网实战

软件测试面试题这个话题,每年都能收到一堆私信。有人刷了一周八股文还是挂在一面,有人只准备了两天却拿到了不错的offer。核心区别不在于背了多少题,而在于有没有把题目背后的考察点摸透。我整理了这份软件测试面试常见问题清单,附…

2026/10/10 14:08:50 阅读更多 →
C++ unordered_map与unordered_set详解:哈希表原理、接口用法与性能优化

C++ unordered_map与unordered_set详解:哈希表原理、接口用法与性能优化

用过 C 的都知道,当你还在用map、set做查找和去重的时候,数据量一旦上来,心里多少会有点不踏实。这时候就该unordered_map和unordered_set登场了。这两个容器在 C11 里正式进入标准库,核心卖点就一句话:基于哈希表实现…

2026/10/10 14:08:50 阅读更多 →
Linux新手第一周:环境搭建、常用命令与学习路线全记录

Linux新手第一周:环境搭建、常用命令与学习路线全记录

几个月前,社团面试的场景还在眼前,转眼第一周周报已经躺在群文件里。说实话,接手【西邮 Linux 兴趣小组】的第一周,我最大的感受不是“教了多少东西”,而是“被一群刚接触 Linux 的新人追着问问题,自己回头…

2026/10/10 14:08:50 阅读更多 →
踩坑实录:接进RAG后召回率反而崩了?all-MiniLM-L6-v2的5个隐藏陷阱

踩坑实录:接进RAG后召回率反而崩了?all-MiniLM-L6-v2的5个隐藏陷阱

踩坑实录:接进RAG后召回率反而崩了?all-MiniLM-L6-v2的5个隐藏陷阱 【免费下载链接】all-MiniLM-L6-v2 项目地址: https://ai.gitcode.com/hf_mirrors/sentence-transformers/all-MiniLM-L6-v2 把 all-MiniLM-L6-v2 接进 RAG 管线,几…

2026/10/10 14:08:50 阅读更多 →
2026年AI Agent发展趋势与挑战:从理论到实践的跨越,TaoToken统一Key打通OpenClaw落地链路

2026年AI Agent发展趋势与挑战:从理论到实践的跨越,TaoToken统一Key打通OpenClaw落地链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 14:08:50 阅读更多 →
Harness 工程安全基线:为 AI Agent 编写 SECURITY.md 安全策略文件

Harness 工程安全基线:为 AI Agent 编写 SECURITY.md 安全策略文件

【免费下载链接】learn-harness-engineering Harness engineering beginner tutorial, from 0 to 1 项目地址: https://gitcode.com/gh_mirrors/le/learn-harness-engineering 点击查看 免费下载 SECURITY.md 是面向 Agent 的仓库(agent-first reposito…

2026/10/10 14:07:49 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →