| name | generate-pdf |
| description | Generate a PDF from ingested source documents using the agnostic Document tree |
| model | deepseek/deepseek-v4-pro |
| tier | pro |
| inputs | [{"name":"source_document_ids","type":"list[str]","required":true,"description":"Prisma Document.id values to ground generation in"},{"name":"brief","type":"str","required":true,"description":"What the user wants generated"},{"name":"style","type":"str","required":false,"description":"Tone/style hint (e.g. \"clinical\", \"casual\", \"technical\")"},{"name":"audience","type":"str","required":false,"description":"Target reader description"},{"name":"target_length","type":"str","required":false,"description":"short | medium | long (default: medium)"}] |
| outputs | [{"name":"file_path","type":"str"},{"name":"file_url","type":"str"},{"name":"title","type":"str"},{"name":"node_count","type":"int"},{"name":"model","type":"str"},{"name":"source_document_ids","type":"list[str]"}] |
generate-pdf
Generates a PDF document from one or more ingested source documents stored in
the OfficePlane database.
What it does
- Loads the requested source documents from Postgres via Prisma (chapters,
sections, summaries).
- Prompts DeepSeek v4-flash to produce an agnostic Document JSON tree
aligned to the CommonMark / Pandoc AST vocabulary — NOT the legacy
modules → lessons → blocks schema.
- Parses the JSON into a
Document dataclass tree via parse_document.
- Renders the tree to
.docx bytes via render_docx.
- Converts the
.docx to .pdf using libreoffice --headless (already
installed in the api Docker image — no extra pip deps required).
- Writes the result to
/data/workspaces/<job_id>/output.pdf.
PDF conversion path
The PDF backend uses libreoffice's headless conversion mode:
Document tree → render_docx() → doc.docx → libreoffice --convert-to pdf → doc.pdf
This reuses the libreoffice binary already present in the api image for DOCX→PDF
ingestion. No new pip dependencies are introduced.
Expected JSON shape
The LLM must return a strict JSON object conforming to the agnostic Document
schema:
{
"type": "document",
"schema_version": "1.0",
"meta": {"title": "My Document", "language": "en"},
"children": [
{
"type": "section",
"id": "s1",
"level": 1,
"heading": "Introduction",
"children": [
{"type": "paragraph", "text": "Opening prose here."},
{
"type": "list",
"ordered": false,
"items": [
{"type": "paragraph", "text": "First bullet"},
{"type": "paragraph", "text": "Second bullet"}
]
}
]
}
],
"attributions": [
{
"node_id": "s1",
"document_id": "<source-doc-uuid>",
"section_id": "<source-section-id>"
}
]
}
Key rules:
- Sections nest recursively. Use
level 1..6 to indicate depth.
- Block types:
heading, paragraph, list, table, figure, code,
callout, quote, divider.
- Never emit
"modules" or "lessons" — use nested sections instead.
- Every non-trivial node should have an
id (short string).
- Provide at least one attribution per major section pointing back to a
source
document_id.
Minimal output example
{
"type": "document",
"meta": {"title": "BP Primer"},
"children": [
{
"type": "section",
"id": "intro",
"level": 1,
"heading": "Why Measure Blood Pressure",
"children": [
{"type": "paragraph", "text": "Early detection of hypertension saves lives."}
]
}
],
"attributions": [
{"node_id": "intro", "document_id": "doc-abc123"}
]
}