Skip to main content

Typed, declarative Pydantic v2 model for slide decks with first-class JSON schema export for LLM structured output.

Project description

deck-spec

Typed, declarative Pydantic v2 model for slide decks. JSON schema included — LLMs target it directly.

Built at Trollfabriken AITrix AB for the AIMOS Insight and Granskning pipelines, where every product needed to emit narrated video, PDF handouts, and editable PowerPoint from a single LLM-authored source of truth. The package publishes the JSON schema; LLMs target it; validators catch errors before any renderer touches the data.


What it solves

Previous problem Solution
Every LLM-to-slides project invents its own ad-hoc JSON shape One published schema; one Python model; one validation path
python-pptx requires you to write layout code per slide (60+ lines/slide) Declarative Slide(layout="content", title=..., body=...)
LLM output drifts when prompts evolve JSON schema constrains structured output across model versions
No way to verify a deck's shape before rendering validate_json(...) raises with field path; render only if valid
Markdown-fenced JSON, trailing commas, prose preamble from LLMs parse_response() handles all three quirks
Decks tied to one output format One Deck -> PPTX, PDF, HTML, narrated MP4 (via siblings)
Swedish text breaks on naive serializers UTF-8 round-trips; language="sv-SE" documented

Installation

pip install deck-spec
pip install "deck-spec[dev]"  # for testing

Runtime requirement: pydantic >= 2.5. Nothing else.


Quick start

from deck_spec import Deck, validate_json

# Validate JSON produced by an LLM
deck = validate_json('''
{
    "title": "LVU for foraldrar",
    "language": "sv-SE",
    "slides": [
        {"layout": "title",
         "title": "LVU for foraldrar",
         "subtitle": "Vad det ar och hur det fungerar"},
        {"layout": "bullet",
         "title": "Vad ar LVU?",
         "bullets": [
             "Lag med sarskilda bestammelser om vard av unga",
             "Tvangsatgard nar frivilliga insatser inte racker",
             "Beslut tas av forvaltningsratten"
         ]},
        {"layout": "stat",
         "title": "Omfattning",
         "stat_value": "21 000",
         "stat_label": "barn i samhallsvard",
         "stat_supporting": "Sverige, senaste aret"}
    ]
}
''')

# Construct programmatically
from deck_spec import Slide, Theme
deck = Deck(
    title="Q1 Review",
    theme=Theme(name="default"),
    slides=[
        Slide(layout="title", title="Q1 Review", subtitle="2026"),
        Slide(layout="stat", title="Revenue",
              stat_value="$12.4M", stat_label="Q1", stat_supporting="+18% YoY"),
    ],
)

# Export the schema for LLM use
import json
from deck_spec import deck_schema
json.dump(deck_schema(), open("schema.json", "w"))

The schema

Why we publish the schema as a file

Three reasons:

  1. LLM structured output — OpenAI's response_format, Anthropic's tool definitions, and JSON-mode all accept a JSON Schema. Pass the schema once; the model produces valid decks.
  2. Cross-language validation — Node, browser, and Go can consume the schema without a Python runtime.
  3. Versioningdeck.schema.json ships in the wheel, tagged with the package version. Pinning deck-spec==0.1.0 locks the schema shape.

The schema is generated from the Pydantic models and checked into source control. CI verifies they stay in sync (scripts/regen_and_check_schema.py).

Emit it with the CLI:

deck-spec schema > deck.schema.json
deck-spec schema --pretty

Or load it in Python:

from deck_spec import deck_schema
schema = deck_schema()   # dict; pass directly to your LLM client

Configuration

Deck — top-level fields

Field Type Default Notes
title str required Deck title
subtitle str | None None Optional subtitle
author str | None None Author name
language str "en" BCP-47; "sv-SE" supported
theme Theme Theme() Visual theme; see below
slides list[Slide] required At least one slide expected
metadata dict[str, str] {} Arbitrary string-keyed metadata
voice_default VoiceConfig | None None Default TTS config; used by talk-cast

Slide — key fields

Field Type Default Notes
layout Literal[...] "content" Controls which content fields renderers read
title str | None None Slide heading
body str | None None Markdown text; "content" layout
bullets list[str] | None None "bullet" layout
columns list[Column] | None None "two_column" / "comparison"
image ImageRef | None None "image" / "image_caption"
quote str | None None "quote" layout
stat_value str | None None "stat" layout — e.g. "$12.4M"
notes str | None None Speaker notes; used as narration by talk-cast
narration str | None None Explicit narration override
transition Literal[...] "fade" "fade", "cut", "slide-left", "slide-right", "zoom"
duration_hint float | None None Minimum seconds on screen (talk-cast)

Available layouts

"title"         — large centered title + subtitle
"section"       — section divider on accent background
"content"       — title + body text (most common)
"two_column"    — title + two content columns
"bullet"        — title + bulleted list
"image"         — title + single large image
"image_caption" — image with caption beside it
"quote"         — large pull quote with attribution
"comparison"    — two side-by-side blocks
"stat"          — one big number, label, and supporting line
"blank"         — custom; place elements explicitly

Theme

Field Type Default
name str "default"
width_px int 1920
height_px int 1080
margin_px int 80
accent_style Literal[...] "solid"
colors ColorPalette see below
fonts FontConfig see below

ColorPalette defaults: background="#FFFFFF", foreground="#1A1A1A", accent1="#0B5FFF", accent2="#FF6B35", accent3="#00A878".

FontConfig defaults: heading="Inter", body="Inter", mono="JetBrains Mono", base_size_pt=18.


CLI

deck-spec schema                    # print JSON schema to stdout
deck-spec schema --pretty           # pretty-printed
deck-spec validate deck.json        # validate file; exit 0 if valid
deck-spec inspect deck.json         # slide count, layouts, missing narration
deck-spec example > example.json    # write a reference example
deck-spec example --layout bullet   # example focused on bullet layout

validate prints the Pydantic field path on failure and exits 1. Use it in pre-render pipelines to gate bad LLM output before it reaches a renderer.

inspect does not validate — it reports structure. Useful for debugging a deck that passes schema validation but looks wrong in output.


Package structure

deck-spec/
├── src/
│   └── deck_spec/
│       ├── __init__.py              <- version, public exports
│       ├── models.py                <- Deck, Slide, Theme, Element, ...
│       ├── colors.py                <- ColorPalette + named-palette presets
│       ├── layouts.py               <- layout enum + field-relevance metadata
│       ├── validate.py              <- validate_json, validate_dict, decks_equivalent
│       ├── cli.py                   <- argparse CLI (deck-spec entrypoint)
│       ├── schemas/
│       │   ├── __init__.py
│       │   └── deck.schema.json     <- generated; checked in; ships in wheel
│       ├── llm/
│       │   ├── __init__.py
│       │   ├── prompts.py           <- build_prompt, parse_response
│       │   └── examples.py          <- example Deck instances
│       └── examples/
│           ├── education.json       <- civic education example deck
│           ├── audit.json           <- municipal audit report deck
│           └── civic.json           <- LVU/socialtjanst explainer deck
├── tests/
│   ├── test_model_validation.py
│   ├── test_schema_generation.py
│   ├── test_schema_round_trip.py
│   ├── test_validate_helpers.py
│   ├── test_llm_helpers.py
│   ├── test_cli_schema.py
│   ├── test_cli_validate.py
│   └── test_examples_validate.py
├── scripts/
│   └── regen_and_check_schema.py   <- update deck.schema.json; CI checks sync
├── pyproject.toml
├── MANIFEST.in
└── LICENSE

(C) Trollfabriken AITrix AB -- MIT licensed

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

deck_spec-0.1.0.tar.gz (18.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

deck_spec-0.1.0-py3-none-any.whl (26.0 kB view details)

Uploaded Python 3

File details

Details for the file deck_spec-0.1.0.tar.gz.

File metadata

  • Download URL: deck_spec-0.1.0.tar.gz
  • Upload date:
  • Size: 18.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for deck_spec-0.1.0.tar.gz
Algorithm Hash digest
SHA256 54bbca9565f40e8996320364ea8234b74c322875cc0984d601df7dec207eac23
MD5 490138f75229c72ad35a28357f5d9bf3
BLAKE2b-256 92eebf7ad0dd3737268e8e2b3db0f9e71cdaadd6fc28acea327dde5bd5c26cb6

See more details on using hashes here.

Provenance

The following attestation bundles were made for deck_spec-0.1.0.tar.gz:

Publisher: release.yml on tomastimelock/deck-spec

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file deck_spec-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: deck_spec-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 26.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for deck_spec-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b71572967d297e0f2ecdf45b031a366c8f78ee870d341ba68229222bd2dcceed
MD5 0427f9ba24abb54180e964f1002da30c
BLAKE2b-256 bae06da406ae840289bb0efc05d313c2e9cfb19d71cfaeef80f0d8f9a5d9bbc6

See more details on using hashes here.

Provenance

The following attestation bundles were made for deck_spec-0.1.0-py3-none-any.whl:

Publisher: release.yml on tomastimelock/deck-spec

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page