欢迎信给四十位新客户,每位与会者的证书,每个订单的发票:相同的文档,每次使用不同的记录。使用本地运行的 GroupDocs.Assembly.Mcp(dnx GroupDocs.Assembly.Mcp --yes),Claude Desktop、Cursor 或 Copilot 只需一次工具调用 assemble_per_record,即可对 JSON 数组的每个元素填充 Word 模板,将每个结果保存为以您选择的字段命名的 PDF,并报告失败的记录,而不是在第一个错误时停止:

为 customers.json 中的每位客户使用 welcome-letter.docx 生成欢迎信 PDF,并以客户名称命名每个文件。

带有配置和故障排除的逐步版本请参见文档:如何使用 AI 代理为每条记录生成一个文档(邮件合并).

为什么不让代理循环?

它可以做到,对于五条记录来说这没问题。但四十次工具调用的循环相当于四十次机会,让代理计数出错、在上下文变长时跳过记录,或“好心”地改写某封信。每记录工具将循环移到服务器端:一次调用、一个模板、一次遍历数组,并生成一份报告,准确说明写入了多少文件以及哪些记录未成功。代理的工作是选择模板、数据文件和命名字段,然后把报告读给你听。

代理首先检查什么?

两件事:数据的结构以及模板读取的内容。在我们的测试中,customers.json 中有五位客户,以及一页的欢迎信:

-> get_document_info {"file":{"filePath":"customers.json"}}
<- { "dataKind": "json",
     "data": { "root": "array", "length": 5, "keys": null } }

-> get_template_fields {"template":{"filePath":"welcome-letter.docx"}}
<- { "dataSourceNames": [ "customer" ],
     "fields": [ "customer.Contact", "customer.Plan", "customer.Name",
                 "customer.Manager", "customer.FirstInvoice" ] }

模板将单条记录称为 customer;服务器会自行解析,因此代理无需传递 dataSourceName。

一次调用,五条记录,一个失败

-> assemble_per_record {"template":{"filePath":"welcome-letter.docx"},
     "data":{"filePath":"customers.json"},"fileNameField":"Name",
     "format":"pdf"}
<- Assembled 4 of 5 document(s) from 'welcome-letter.docx' with
   'customers.json', one per record (each record as 'customer'), as PDF.
   Saved to:
   .../Contoso Pharma.pdf
   .../Fabrikam Logistics.pdf
   .../Tailspin Toys.pdf
   .../Litware Labs.pdf
   1 record(s) failed:
   record 4: InvalidOperationException: ... Can not get the value of
   member 'FirstInvoice' on type 'System.Data.DataRow'. The template
   reads 'FirstInvoice', which the data does not have. ...

第 4 条记录,Wingtip Travel,在数据中没有 first-invoice 日期。其余四封信已生成,文件名均为对应客户名称,失败信息返回了记录编号和缺失的字段。代理会报告为“已完成四封信;Wingtip Travel 没有 first-invoice 日期,您是希望我将该行留空还是等待日期?”这才是您想要的对话,而不是四十个文件却不知道哪个出错。

fan-out 工作原理

customers.json  [ {Name:"Contoso Pharma",...}, {Name:"Fabrikam ..."},
                  ... 5 records ]
        |
assemble_per_record {"template":{"filePath":"welcome-letter.docx"},
                     "data":{"filePath":"customers.json"},
                     "fileNameField":"Name","format":"pdf"}
        |
        +--> Contoso Pharma.pdf
        +--> Fabrikam Logistics.pdf     ... 2 more
        '--> "4 of 5 ... 1 record(s) failed: record 4 ..."

三个参数完成此工作。fileNameField 使用记录字段(如 customer.Name 或发票号)为每个输出命名;如果不提供,则文件使用递增编号。recordsPath 在 JSON 为包含数组的对象时指向该数组,例如 "customers" 或 "batch.items"。maxDocuments 则是限制阀。

什么会阻止批处理失控?

maxDocuments,默认值为 100,最高可达 1000。在写入任何文件之前,服务器会先统计记录数;如果超过限制,则拒绝。我们将其设为 3 来对抗我们的五位客户:

-> assemble_per_record {..., "maxDocuments":3}
<- Per-record assembly failed for 'welcome-letter.docx': there are 5
   records, more than maxDocuments=3. Raise 'maxDocuments' (up to 1000)
   or pass fewer records.

没有写入任何文件。对于实际发送给真实用户的邮件,这是一种合适的默认设置:如果误将整个 CRM 导出作为输入,系统会返回一个提示,而不是生成一万份 PDF。

我的数据是电子表格,而不是 JSON

每记录生成仅支持读取 JSON:对象数组或包含数组的对象。当列表位于 CSV 或工作表中时,代理会先将行转换为 JSON,代理对此处理得很好,并将文件写入存储文件夹。如果您想要的是一个列出所有行的文档,例如价目表或登记册,则使用 assemble_document 并在模板中加入 <<foreach>>,它可以直接读取 CSV 和电子表格。

真实的限制

  • 每记录组装仅读取 JSON;CSV 和电子表格数据需先转换。
  • 评估模式:每个生成的文件都会以 “Evaluation Only. Created with GroupDocs.Assembly 26.7…” 开头,电子邮件模板仅保留纯文本正文。请在实际邮件发送前为服务器授权。
  • 缺少字段会导致该记录失败。设置 allowMissingMembers: true 可将其写为空行;自行决定使用哪种方式。
  • 日期:请以 2026-12-01T00:00:00 的形式写入,并在模板中进行格式化,就像我们的信件使用 <<[customer.FirstInvoice]:"d MMMM yyyy">> 那样。在我们的测试中,UTC+2 机器上仅日期字符串会偏移一天。
  • 服务器只写入文件;不会发送。交付是另一步骤。

常见问题

我可以在不上传客户列表的情况下使用 Claude 进行邮件合并吗?
可以。服务器本地运行,并从您的存储文件夹读取 customers.json;生成的 PDF 也会写入同一位置。

输出文件如何命名?
使用您传入的 fileNameField 对应的记录字段命名,这里是客户名称,例如 Contoso Pharma.pdf。如果未提供,则使用递增编号。

每个文档可以是电子邮件而不是 PDF 吗?
可以:使用 EML 或 MSG 模板,或传入 format: "eml"。在评估模式下,电子邮件的 HTML 正文会被评估行替换,因此请使用授权后测试电子邮件输出。

深入了解