ยินดีต้อนรับจดหมายสำหรับลูกค้าใหม่สี่สิบคน, ใบรับรองสำหรับผู้เข้าร่วมทุกคน, ใบแจ้งหนี้ต่อคำสั่งซื้อหนึ่งรายการ: เอกสารเดียวกัน, แต่บันทึกที่แตกต่างกันในแต่ละครั้ง. ด้วย 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.

ทำไมไม่ให้เอเจนต์ทำลูป?

มันทำได้, และสำหรับห้าบันทึกก็พอใช้ได้. แต่ลูปของการเรียกเครื่องมือสี่สิบครั้งคือสี่สิบโอกาสที่เอเจนต์จะพลาดนับ, ข้ามบันทึกเมื่อบริบทยาว, หรือ “ช่วยเหลือ” โดยการเปลี่ยนคำจดหมายหนึ่งฉบับ. เครื่องมือต่อบันทึกจะย้ายลูปไปที่เซิร์ฟเวอร์: การเรียกหนึ่งครั้ง, เทมเพลตหนึ่งไฟล์, การวนผ่านอาร์เรย์หนึ่งครั้ง, และรายงานที่บอกจำนวนไฟล์ที่เขียนได้และบันทึกใดบ้างที่ไม่สำเร็จ. งานของเอเจนต์คือเลือกเทมเพลต, ไฟล์ข้อมูลและฟิลด์ตั้งชื่อ, แล้วอ่านรายงานกลับมาให้คุณ.

เอเจนต์ตรวจสอบอะไรเป็นอันดับแรก?

สองอย่าง: รูปแบบของข้อมูลและสิ่งที่เทมเพลตอ่านได้. ในการทดสอบของเรา, มีลูกค้าห้าคนใน 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, ไม่มีวันที่ใบแจ้งหนี้แรกในข้อมูล. จดหมายสี่ฉบับที่เหลือถูกเขียน, แต่ละฉบับตั้งชื่อตามลูกค้า, และความล้มเหลวถูกส่งกลับพร้อมหมายเลขบันทึกและฟิลด์ที่หายไป. เอเจนต์จะรายงานว่า “จดหมายสี่ฉบับเสร็จ; 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 ที่วิ่งเกินขอบเขต?

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 ก่อน, ซึ่งเอเจนต์ทำได้ดี, แล้วเขียนไฟล์ลงในโฟลเดอร์ storage. หากคุณต้องการเอกสารหนึ่งฉบับที่แสดงทุกแถว, เช่น รายการราคา หรือทะเบียน, ให้ใช้ assemble_document พร้อม <<foreach>> ในเทมเพลต, ซึ่งจะอ่าน CSV และสเปรดชีตโดยตรง.

ข้อจำกัดที่ต้องระวัง

  • การประกอบต่อบันทึกอ่านเฉพาะ JSON; ข้อมูล CSV และสเปรดชีตต้องแปลงเป็น JSON ก่อน.
  • โหมดประเมินผล: ทุกไฟล์ที่สร้างจะเริ่มด้วย “Evaluation Only. Created with GroupDocs.Assembly 26.7…”, และเทมเพลตอีเมลจะเก็บเฉพาะข้อความตัวอักษรธรรมดา. ให้ทำการเปิดใช้งานไลเซนส์บนเซิร์ฟเวอร์ก่อนการส่งจดหมายจริง.
  • ฟิลด์ที่หายไปจะทำให้บันทึกนั้นล้มเหลว. allowMissingMembers: true จะเขียนบันทึกโดยปล่อยบรรทัดว่างแทน; เลือกตามที่คุณต้องการ.
  • วันที่: เขียนเป็น 2026-12-01T00:00:00 แล้วจัดรูปแบบในเทมเพลต, เช่นจดหมายของเราที่ใช้ <<[customer.FirstInvoice]:"d MMMM yyyy">>. เราพบว่าข้อความวันที่ที่มีเฉพาะวันถูกเลื่อนหนึ่งวันบนเครื่องที่ตั้งค่า UTC+2 ในการทดสอบ.
  • เซิร์ฟเวอร์เขียนไฟล์; มันไม่ส่งไฟล์. การจัดส่งเป็นขั้นตอนแยกต่างหาก.

คำถามที่พบบ่อย

ฉันสามารถทำ mail merge ด้วย Claude โดยไม่ต้องอัปโหลดรายการลูกค้าได้หรือไม่?
ได้. เซิร์ฟเวอร์ทำงานแบบโลคัลและอ่าน customers.json จากโฟลเดอร์ storage ของคุณ; PDF ที่สร้างจะถูกเขียนลงที่นั่นเช่นกัน.

ไฟล์ผลลัพธ์ตั้งชื่ออย่างไร?
โดยฟิลด์บันทึกที่คุณส่งเป็น fileNameField, ในกรณีนี้คือชื่อลูกค้า, ดังนั้น Contoso Pharma.pdf. หากไม่ได้ตั้งค่า, จะใช้หมายเลขลำดับ.

แต่ละเอกสารสามารถเป็นอีเมลแทน PDF ได้หรือไม่?
ได้: ใช้เทมเพลต EML หรือ MSG, หรือส่ง format: "eml". ในโหมดประเมินผลเนื้อหา HTML ของอีเมลจะถูกแทนที่ด้วยบรรทัดประเมินผล, ดังนั้นให้ทดสอบการส่งอีเมลด้วยไลเซนส์.

ไปให้ลึกขึ้น