Part 6 of 15 • 🟡 Intermediate • ⏱️ 90 minutes

Streaming Dynamic Files & PDF Generation in watsonx Orchestrate: Delivering On-Demand Reports

Generating custom binary PDF documents on the fly with ReportLab and returning download URLs in chat.

#watsonxOrchestrate#PDFGeneration#FileStreaming#ReportLab#AIDocuments#Python
Streaming Dynamic Files & PDF Generation in watsonx Orchestrate: Delivering On-Demand Reports

Why This Pattern Matters

Executives often require formal PDF briefing packets with corporate headers and timestamps. Rather than sending static templates, our agent synthesizes live conversational data and renders a downloadable binary PDF on demand.


Step 1: Write Tool Logic (pdf_generator.py)

from ibm_watsonx_orchestrate import tool
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
import tempfile
import os
from datetime import datetime

@tool
def generate_report_pdf(report_title: str, report_body: str) -> str:
    """
    Generate a formatted binary PDF document and return a download URI.
    """
    temp_dir = tempfile.gettempdir()
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    filename = f"WXO_Report_{timestamp}.pdf"
    filepath = os.path.join(temp_dir, filename)

    c = canvas.Canvas(filepath, pagesize=letter)
    width, height = letter

    # Header branding
    c.setFont("Helvetica-Bold", 18)
    c.setFillColorRGB(0.06, 0.38, 0.99) # IBM Blue
    c.drawString(50, height - 50, "IBM watsonx Orchestrate")

    c.setFont("Helvetica-Bold", 14)
    c.setFillColorRGB(0.1, 0.1, 0.1)
    c.drawString(50, height - 85, report_title)

    c.setFont("Helvetica", 9)
    c.setFillColorRGB(0.4, 0.4, 0.4)
    c.drawString(50, height - 105, f"Generated: {datetime.now().strftime('%B %d, %Y at %H:%M:%S UTC')}")

    # Body
    c.setFont("Helvetica", 11)
    c.setFillColorRGB(0.1, 0.1, 0.1)
    text_object = c.beginText(50, height - 140)
    for line in report_body.split("\n"):
        text_object.textLine(line)
    c.drawText(text_object)

    c.save()
    filesize = os.path.getsize(filepath)

    return f"""
📄 PDF Document Generated Successfully
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Title:    {report_title}
File:     {filename} ({filesize:,} bytes)
Location: {filepath}
Status:   Ready for download
"""

Step 2: Dependencies (requirements.txt)

ibm-watsonx-orchestrate>=2.9.0
reportlab>=4.0.0

Step 3: Agent Manifest (agent.yaml)

spec_version: v1
kind: native
name: report_publisher_agent
title: "Report Publisher"
description: "Generates formatted PDF executive briefings on demand."
model: ibm/granite-3-8b-instruct
instructions: |
  You are an executive publishing assistant. Format summaries into structured
  briefings and call `generate_report_pdf` to create the document.
tools:
  - generate_report_pdf

Step 4: Live Execution

orchestrate chat ask -n report_publisher_agent "Generate a PDF report on Q3 AI Adoption"
╭─ 🤖 report_publisher_agent ───────────────────────────────────────────╮
│                                                                        │
│  📄 PDF Document Generated Successfully                                │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━                                  │
│  Title:    Q3 AI Adoption Briefing                                     │
│  File:     WXO_Report_20260924_201500.pdf (4,812 bytes)                │
│  Status:   Ready for download                                          │
╰────────────────────────────────────────────────────────────────────────╯

💡 Pro Tips & Architecture Insights

💡 Cloud Ephemeral Storage

On local dev or container sandboxes, `/tmp` works directly. For production cloud deployments, stream the generated bytes to IBM Cloud Object Storage (COS) or AWS S3 and return a presigned HTTPS URL.

💡 ReportLab Fonts

Stick to built-in PostScript fonts (`Helvetica`, `Times-Roman`, `Courier`) to avoid bundle size bloat and missing TTF font errors.


📋 Open IBM Community Ready HTML