await page.pdf() 之后,Agent 还欠你一次文件回读

0 阅读5分钟

一个导出任务返回路径,Agent 就回复“PDF 已生成,可以发送”。这个接口没撒谎,但它回答的只是有没有文件,不是文件里有没有缺字、分页有没有截断表格。

如果你在为小团队搭建方案或报告助手,我建议把 exported 和 reviewed 拆开。本文讨论受控 HTML 转 PDF 的链路,场景与代码均为教学示例,不是某次客户项目的实测报告。

不要让一个路径承担所有状态

把输出设计成下面这样,会迫使下游面对尚未完成的检查:

type PdfArtifact = {
  id: string;
  fileRef: string;
  sha256: string;
  state: "exported" | "review_pending" | "accepted";
  checks: {
    content: "pending" | "pass" | "fail";
    layout: "pending" | "pass" | "fail";
    links: "pending" | "pass" | "fail";
  };
};

哈希只负责确认检查的是哪份文件,不证明内容正确。accepted 的条件要由工作流明确:谁检查了哪些项目,是否仍有未解决问题。不要由导出工具顺手把它设为通过。

对外发送是另外的动作。失败时保留文件与诊断,不自动发送一个“差不多能看”的版本,也别让检查用的账号或虚构数据混进真实交付。

浏览器预览与打印布局不是同一个基线

Playwright 官方说明,page.pdf 默认使用 print 媒体样式。若你对比的是 screen 截图,两者本来就可能不同。官方 PDF 接口文档

因此先固定媒体模式、纸张、边距、字体资源和浏览器版本,再谈差异。不要让前端用一种纸张 CSS,导出服务再用另一组参数,最后靠试错把比例调小。

最小流程可以写成:

准备受控输入
  → 固定 print 模式与页面内容
  → 检查资源就绪
  → 导出 PDF
  → 从 PDF 生成检查视图
  → 检查内容、布局和链接
  → 绑定文件版本确认交付

这里的“检查视图”来自导出的 PDF,不是原 HTML 截图。否则你只是在证明输入页面好看,输出仍然没被看过。

fonts.ready 是同步点,不是质量证明

这是一个特别容易出现假绿灯的地方:

await page.evaluate(async () => {
  await document.fonts.ready;
});

规范说明 ready 会等待相关字体加载和布局结束,但其 Promise 不会因加载失败而 reject;之后新增内容也可能带来新的字体需求。CSS Font Loading 规范

所以应先冻结要导出的内容,再等待资源,然后检查最终字形。常用中文能显示,不等于标题里的少见字、符号或粗体也正确。字体来自可控资源,并确认字符覆盖,比依赖宿主机字体列表更容易维护。

字形缺失、表格边界与链接是不同的故障面

图片同样需要区分:img 解码成功、背景图加载、canvas 完成绘制,是不同条件。一个笼统的“等三秒”既不能证明资源齐全,也可能给快任务增加无意义等待。让导出模板暴露可检查的就绪状态,同时设置整个任务的截止时间;超时就报告缺哪项。

别把 break-inside: avoid 当作无条件承诺

有时一个表格跨页不好看,开发者给整个 table 加上 avoid,接着发现大块留白或内容仍然分裂。因为分页约束必须面对真实页面高度。

对短的提示块可以尝试:

@media print {
  .callout { break-inside: avoid; }
  .section-title { break-after: avoid; }
}

但超长表格需要拆分结构,而不是无限加“不分页”。W3C 的分页规范定义这些属性的约束语义;它不保证任意内容都能保持完整占据一页。CSS Fragmentation 规范

检查器也别只数页数。页数没变,表格最后一列可能已被遮住;页数增加,可能只是正确保留了字号。把“允许多少页”与“哪些内容不能缺”分别定义。

两种检查结果,不能互相冒充

从 PDF 提取文字,适合检查必要章节、金额和联系方式是否存在;把 PDF 渲染为页面图,适合发现裁切、重叠、缺字和异常空白。两者都通过,也不等于业务事实已经核验。

链接再单独检查目标与可点击区域。验证时尽量只处理明确安全的地址,避免把链接检查变成未经授权的外部操作。临时签名地址可能暂时可用,但若业务要求长期使用,就不符合交付约定。

检查最终文件的多页结果,而不是重复检查原稿

一份诊断记录不必很大:

{
  "artifactId": "demo-report-r2",
  "check": "layout",
  "page": 3,
  "result": "fail",
  "reason": "表格右侧内容超出可见区域",
  "evidenceRef": "review/page-003.png",
  "nextAction": "调整表格结构后重新导出"
}

这是自拟格式,没有实际执行此示例。关键是 nextAction 指向需要修复的层:资源失败就补资源,表格太宽就改结构,不要每次都让模型全文重写。重新导出后新文件重新验收,旧报告不能直接沿用。

文件交接才是最后一段流程

这也是我们做 Tipkay 时的一种取舍。面向小微企业、一人公司和小团队的按需 AI 员工,不只生成正文,还应让排版、文件处理和发布准备接得上。本文的状态结构与检查方法是通用工程设计,不代表 Tipkay 已提供同名内置 PDF 验证能力。

不必第一天就搭自动评分大屏。先给导出工具加一个清楚的状态:文件已生成,尚未回读。再让使用者能打开对应版本,看到检查范围和未解决项。

对 Agent 来说,最重要的进步可能不是更快返回 fileRef,而是知道什么时候还不能说“可以发给客户了”。