Skip to content

在 AI 超级智能体项目中,你开发的 PDF 生成工具是如何实现的? ​

  • 标签:后端、编程导航

🏛 业务架构师 ​

PDF 生成工具的实现路径:模型产出结构化内容(JSON)→ 程序把数据灌入 PDF 模板渲染 → 文件上传对象存储 → 返回下载链接给用户。技术栈上我用模板引擎(如 iText/Flying Saucer/HTML→PDF)预置多套版式,业务代码只负责数据组装。

关键设计决策——为什么内容走结构化而不是让模型直接吐 PDF:

  • 模型只负责内容生产(结构化输出:标题、章节、要点、表格数据),版式交给确定性的模板代码——职责分离后,PDF 的排版永远专业稳定(模型直接生成排版是灾难:字体错乱、分页破碎);
  • 模板是设计资产:多版式(报告风、简历风、行程卡片)让同一份内容多形态输出——交付物的颜值就是产品价值感。

业务价值的三层:

  1. 对话→交付物:用户分析的竞品报告、规划的行程,导出 PDF 才能"拿走"(发给领导、分享社群)——AI 的价值从"聊过"沉淀为"用上";
  2. 付费转化抓手:免费用户看网页版、导出 PDF 是会员权益——交付物是天然的付费墙,AI 产品的商业化常见落点;
  3. 分享即增长:带产品水印的 PDF 被转发,每次分享都是获客渠道。

工程纪律:生成是异步任务(大文档渲染耗时,走任务队列+完成通知);模板变量防注入(内容里的特殊字符转义);配额限制(每用户每日 N 份)——工具的能力要用配额管理,防止滥用刷爆资源。

一句话:PDF 工具把 AI 从"聊天窗口"升级为"生产力出口"——用户最终为拿走的东西付费。

🏗 系统架构师 ​

PDF 生成工具的核心思路:让模型负责"内容与结构",程序负责"渲染"——LLM 生成 Markdown/HTML 结构化内容,Java 侧模板化渲染成 PDF。

实现链路:

1. 工具定义:

java
@Tool(description = "将给定Markdown内容生成PDF文档并返回下载链接。适用于用户要求导出报告、总结为文档的场景")
public String generatePdf(@ToolParam(description = "完整的Markdown格式文档内容") String markdown) {
    String html = markdownRenderer.render(markdown);   // markdown → HTML(含样式)
    byte[] pdf = openHtmlToPdf.convert(html);          // HTML → PDF
    String url = oss.upload(pdf);                      // 存储返回链接
    return "PDF已生成:" + url;
}

2. 渲染层选型:

  • OpenHTMLtoPDF / Flying Saucer:HTML+CSS → PDF,样式控制精细(中文字体要内嵌,宋体/思源),我的选择——模板与内容分离,产品化程度高;
  • iText:API 级精细控制,工作量大;
  • 前端方案(浏览器 print)不可靠,弃。

3. 关键工程细节:

  • 中文字体必须显式注册(否则中文口字方块);
  • 长文档分页:CSS @page 控制页眉页脚页码;
  • 结构化生成:报告类场景用结构化输出让模型返回 JSON(标题/章节/表格),程序套 Thymeleaf 模板渲染——比整篇 Markdown 更可控;
  • 大 PDF 异步生成(@Async + 任务表),工具先返回"生成中",完成推送链接。

安全:内容 sanitize(防 XSS 进 HTML);下载链接带签名与有效期。

架构观点:这个工具展示了 Tool Calling 的通用模式——LLM 做内容理解与生成,确定性程序做格式转换与副作用,各司其职,比"端到端让模型输出 PDF"可靠一个量级。

💻 全栈开发专家 ​

方案选型:模型输出 markdown/HTML → OpenHTMLtoPDF(iText 底座) 渲染成 PDF → 上传 OSS → 返回下载链接。比直接 iText 排版省事(模型擅长产 HTML,不擅长产 PDF 布局代码)。

java
public class PdfGenerateTool {
    @Tool(description = "将指定的 Markdown 内容生成 PDF 文件并返回下载链接。"
        + "适合用户要求导出报告、总结、文档时调用")
    public String generatePdf(
            @ToolParam(description = "Markdown 格式的文档内容") String content,
            @ToolParam(description = "文档标题") String title) {
        try {
            String html = markdownToHtml(content);       // flexmark 渲染
            html = wrapTemplate(title, html);            // 带样式的 HTML 模板(字体/边距)
            Path pdf = Files.createTempFile("doc", ".pdf");
            PdfRendererBuilder builder = new PdfRendererBuilder();
            builder.withHtmlContent(html, null);
            builder.useFont(pdfFont("NotoSansSC"), "NotoSansSC");  // 中文字体!
            builder.toStream(Files.newOutputStream(pdf)).run();
            String url = ossClient.upload(pdf);          // 对象存储
            return "PDF 已生成,下载链接: " + url;
        } catch (Exception e) {
            return "生成失败: " + e.getMessage();
        }
    }
}

踩过的坑(都是真实教训):

  1. 中文字体:默认字体不含中文,PDF 全是方块——必须内嵌字体(NotoSansSC/思源黑体),并把 CSS font-family 指到它;
  2. 长文档内存:50 页 PDF 生成耗时 10s+,异步化(工具先返回"生成中"+ 任务链接,完成后通知)而不是让会话干等;
  3. HTML 质量:模型偶尔输出非法 HTML 导致渲染异常,先过一遍 jsoup 清洗;
  4. 链接时效:OSS 链接设过期时间并返回文件名,避免链接被分享滥用。