Welcome letters برای چهل مشتری جدید، یک گواهی برای هر شرکت‌کننده، یک فاکتور برای هر سفارش: همان سند، اما هر بار رکورد متفاوت. با اجرای GroupDocs.Assembly.Mcp به صورت محلی (dnx GroupDocs.Assembly.Mcp --yes)، Claude Desktop، Cursor یا Copilot این کار را با یک فراخوانی ابزار، assemble_per_record، انجام می‌دهند؛ ابزاری که قالب Word شما را برای هر عنصر از یک آرایه JSON یک‌بار پر می‌کند، هر نتیجه را به‌عنوان یک PDF جداگانه با نامی که از یک فیلد انتخاب می‌کنید ذخیره می‌کند و رکوردهای ناموفق را گزارش می‌دهد به‌جای اینکه در اولین خطا متوقف شود:

یک PDF نامه خوش‌آمدگویی برای هر مشتری در customers.json با استفاده از welcome-letter.docx بسازید و هر فایل را با نام مشتری ذخیره کنید.

نسخه گام‌به‌گام همراه با پیکربندی و عیب‌یابی در مستندات موجود است: How to generate one document per record (mail merge) with an AI agent .

چرا اجازه ندهیم عامل (agent) حلقه بزند؟

می‌تواند این کار را انجام دهد و برای پنج رکورد مشکلی نیست. اما یک حلقه با چهل فراخوانی ابزار یعنی چهل فرصت برای این که عامل شمارش را از دست بدهد، وقتی زمینه طولانی می‌شود یک رکورد را نادیده بگیرد، یا «به‌طور مفید» یک نامه را بازنویسی کند. یک ابزار per‑record حلقه را به سرور منتقل می‌کند: یک فراخوانی، یک قالب، یک عبور بر روی آرایه، و گزارشی که دقیقاً می‌گوید چند فایل نوشته شده و کدام رکوردها موفق نشده‌اند. کار عامل این است که قالب، فایل داده و فیلد نام‌گذاری را انتخاب کند و سپس گزارش را برای شما بخواند.

اولین کاری که عامل بررسی می‌کند چیست؟

دو چیز: ظاهر داده‌ها و آنچه قالب می‌خواند. در آزمایش ما، پنج مشتری در 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. ...

رکورد ۴، Wingtip Travel، تاریخ اولین فاکتور را در داده‌ها نداشت. چهار نامه دیگر نوشته شد، هر کدام با نام مشتری، و خطا همراه با شماره رکورد و فیلدی که موجود نبود برگردانده شد. یک عامل این را به‌صورت «چهار نامه آماده شد؛ Wingtip Travel تاریخ اولین فاکتور ندارد، آیا می‌خواهید خط را خالی بگذارم یا منتظر تاریخ بمانم؟» گزارش می‌کند. این همان گفتگویی است که می‌خواهید، نه چهل فایل بدون دانستن اینکه کدام یک اشتباه است.

نحوه کار 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 محدودکننده است.

چه چیزی یک دسته (batch) runaway را متوقف می‌کند؟

maxDocuments که به‌صورت پیش‌فرض ۱۰۰ است و می‌تواند تا ۱۰۰۰ افزایش یابد. قبل از نوشتن هر چیزی، سرور تعداد رکوردها را می‌شمارد؛ اگر بیش از حد مجاز باشد، درخواست را رد می‌کند. ما آن را برای پنج مشتری‌مان به ۳ تنظیم کردیم:

-> 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

تولید per‑record فقط JSON می‌خواند: یک آرایه از اشیاء، یا یک شیء که آرایه‌ای درون خود دارد. وقتی لیست در یک CSV یا یک شیت‌اکسل قرار دارد، عامل ابتدا ردیف‌ها را به JSON تبدیل می‌کند (عامل‌ها این کار را به‌خوبی انجام می‌دهند) و فایل را در پوشه ذخیره‌سازی می‌نویسد. اگر می‌خواهید یک سند داشته باشید که تمام ردیف‌ها را فهرست کند، مثل یک لیست قیمت یا رجیستر، از assemble_document با <<foreach>> در قالب استفاده کنید؛ این روش CSV و صفحات گسترده را به‌صورت مستقیم می‌خواند.

محدودیت‌های صادقانه

  • تولید per‑record فقط 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 بدون بارگذاری لیست مشتریان ادغام ایمیلی (mail merge) انجام دهم؟
بله. سرور به‌صورت محلی اجرا می‌شود و customers.json را از پوشهٔ ذخیره‌سازی شما می‌خواند؛ PDFهای تولید شده نیز در همانجا ذخیره می‌شوند.

نام فایل‌های خروجی چگونه تعیین می‌شود؟
با فیلدی که به‌عنوان fileNameField می‌گذارید، در این مثال نام مشتری؛ بنابراین Contoso Pharma.pdf. بدون این فیلد، شماره‌گذاری متوالی خواهد بود.

آیا می‌توان هر سند را به‌جای PDF به‌صورت ایمیل ارسال کرد؟
بله: از قالب EML یا MSG استفاده کنید، یا format: "eml" را بگذارید. در حالت ارزیابی بدنهٔ HTML ایمیل با خط ارزیابی جایگزین می‌شود، بنابراین برای تست خروجی ایمیل باید لایسنس داشته باشید.

عمیق‌تر بروید