TM Taeyang Moon
Demo · Architecture write-up

BookForge — AI book (PDF) publishing platform

Enter a topic and an 8-stage quality pipeline produces a publication-grade book PDF

Azure OpenAI gpt-5.4 (keyless)8-stage quality pipelineEvidence grounding (no hallucination)WeasyPrint typesettingContainer AppsManaged Identity
🔗
Live sitebookforge-api.greensea-643eec7e.koreacentral.azurecontainerapps.io/
Open site →

This document treats BookForge — a platform that turns a customer's brief into a publication-quality book as a PDF as a single Azure architecture case study, explaining which services it uses, how they fit together, and why it was designed this way, from the perspective of an engineer who wants to rebuild it. (As of 2026-06-17; regions: Container Apps = Korea Central, Azure OpenAI = Korea Central)

⚠️ Disclaimer: Books produced by this demo are AI-generated. Facts and figures are for reference only; verify against original sources before any important decision.


At a glance

BookForge turns "topic / format / content / references / length → an 8-stage quality pipeline → a typeset book PDF with cover, table of contents, body, and bibliography." This demo has exactly one success metric: the quality of the book's content. So it is designed to refine in stages rather than "write it all at once."


🎨 Storybook Mode (illustrated, per-page art)

Unlike adult "books," a storybook lives or dies on art quality + age-fit + character consistency, so it uses a dedicated pipeline.


Architecture

  Internet ──HTTPS──▶  Azure Container Apps (FastAPI + static front, Korea Central)
                        bookforge-api  (api/server.py, ingress 8000, single replica)
                        - GET  /                     input form + live progress polling UI (web/index.html)
                        - POST /api/books            (multipart) start job → {job_id}, pipeline on a daemon thread
                        - GET  /api/books/{id}       poll status (stage / progress / per-chapter / logs)
                        - GET  /api/books/{id}/pdf   download finished PDF
                        - System-assigned identity (keyless)
                              │
            ┌─────────────────┼──────────────────────────────────┐
            ▼                 ▼                                    ▼
   app/grounding.py    app/pipeline.py (8-stage orchestration)  app/render.py
    files/URL/web        plan→ground→outline→write→critique       Markdown→HTML→PDF
    → corpus + per-       →consistency→copyedit→render             WeasyPrint (cover/TOC/
    chapter facts                │                                 body/bibliography, Nanum)
                                ▼
                          app/llm.py — gpt-5.4 (keyless AAD)
                          DefaultAzureCredential → AzureOpenAI
                          foundry-uzrz5ojtsjvae · gpt-5.4
                          (reasoning model: max_completion_tokens)
                              │
                              ▼
                          app/store.py — job state & artifacts
                          container-local (demo) / Blob+PE (production)

Services and roles

Service Role Key point
Azure Container Apps (FastAPI) Web backend + static front bookforge-api Runs the 8-stage pipeline on a daemon thread, status polling. Single replica (consistent create/poll/download). System-assigned identity (keyless)
Azure OpenAI / Foundry — gpt-5.4 Reasoning for every stage (plan/write/critique/consistency) Keyless (AAD token). Reasoning model → uses max_completion_tokens
Azure OpenAI — gpt-image-1 (storybook) Per-page illustration generation Keyless (AAD). No image models in koreacentral, so deployed separately in eastus2 (foundry-bookimg-mty). Cover 1024×1536 (high) · pages 1024×1024 (medium)
Grounding (grounding.py) Single source of truth for evidence uploads/URL/web → corpus → per-chapter "verified facts". Numbers/quotes confined to evidence (hallucination prevention)
WeasyPrint (render.py) Book typesetting (HTML→PDF) CSS Paged Media: cover, copyright, auto TOC (page numbers), serif body, bibliography. Korean fonts (Nanum/Noto) installed in the container
Storage (store.py) Job state & artifacts Demo uses container-local (simple, governance-independent). Production scales to Blob + Private Endpoint
Azure Container Registry (Basic) Image storage az acr build builds an image with WeasyPrint runtime + Korean fonts

Why it is designed this way (key decisions)

  1. Quality is the goal → multi-stage refinement, not a single shot. Asking an LLM to "write a good book in one go" yields average, flat prose. So we fix the direction (thesis) in planning, design a non-redundant, escalating outline, write each chapter, then have a separate editor persona critique and rewrite it. Splitting "writer" and "editor" roles — even on the same model — visibly raises density.

  2. Grounding to prevent hallucination. A book's credibility collapses on one "specific but wrong number." So we build a corpus from references (files/URLs/web search) and extract only the facts each chapter needs, injecting them. The writing/revision prompts insist "concrete numbers/quotes only from this evidence." With no references, the model writes from its own knowledge but is told to beware of numeric hallucination.

  3. Continuous writing via rolling summaries. Writing chapters independently creates duplication and contradictions. After each chapter we summarize it in three sentences and feed it as context to the next chapter's prompt, so the book reads as one continuous work.

  4. Keyless (zero secrets). The subscription blocks Azure OpenAI key auth (disableLocalAuth=true). So we obtain an AAD token via DefaultAzureCredential and, in the deployed environment, grant only a Managed Identity the Cognitive Services OpenAI User role. There is no key to leak.

  5. A storage choice that doesn't fight governance (demo). We first tried Blob (managed identity), but central governance immediately forced storage publicNetworkAccess to Disabled, so writes from ACA (Consumption, no VNet) failed with AuthorizationFailure. For the demo we simplified to container-local storage + single replica, working reliably regardless of governance. (For persistent, multi-replica production, see below.)

  6. Investing in book-like typesetting. Great content fails to land if it looks like "notepad text." WeasyPrint produces a cover, an auto TOC (real page numbers via CSS target-counter), a serif body, and a bibliography, so it reads as a book from first glance. Body is serif (readability), headings sans-serif (hierarchy).


Pitfalls hit and fixed (so you don't repeat them)


Build it yourself (summary)

# Local
cd demos/bookforge
python3 -m venv .venv && source .venv/bin/activate
pip install -r api/requirements.txt        # WeasyPrint needs system pango (macOS: brew install pango)
az login
./run-local.sh                              # → http://localhost:8000

# Deploy to Azure (Container Apps, keyless)
az login && az account set --subscription <SUB_ID>
./deploy.sh                                 # ACR build → ACA create → grant AOAI role to the managed identity
# Cleanup: az group delete -n rg-bookforge --yes --no-wait

Production scaling: for persistence and multiple replicas, move artifact storage to Blob + Private Endpoint. MCAPS governance periodically locks public storage access, so you need the VNet-injected Container Apps + Private Endpoint pattern (see this portal's "governance-immune storage" demo).


Customer value (business view)

← All demosPortal home