AI Integration and Prompting
` framing from the content if present 1.
Prompt Construction and Structure
Section titled “Prompt Construction and Structure”Prompts are constructed in src/ppt_craft/prompts.py using a modular approach to ensure byte-stability for prefix caching 2. The system prompt is built by concatenating four sections: §A (Design Reference), §B (JSON Schema), §C (Few-Shot Examples), and §D (Standing Instructions).
§A provides OOXML and PowerPoint design rules, such as font sizes, bullet point limits, and layout choices. §B defines the strict JSON schemas for Storyline (Phase A), Single Slide (Phase B), and Critique outputs. §C includes byte-stable few-shot examples for storyline and single-slide generation. §D enforces stylistic rules, including British spelling, tone, and constraints on consultantese.
User prompts are generated dynamically based on the phase. build_storyline_user_prompt accepts intent, theme, audience, and optional slide count to guide Phase A. build_slide_user_prompt incorporates prior titles to avoid repetition and provides the specific storyline entry for Phase B. Repair and critique prompts are built using build_repair_user_prompt and build_critique_user_prompt, which inject validation errors or existing JSON into the user message.
JSON Output Extraction and Parsing
Section titled “JSON Output Extraction and Parsing”The system requires AI outputs to be enclosed in a ```json fenced block. The extract_json function in prompts.py uses a regular expression (_FENCE_RE) to locate the first JSON fence in the model’s response.
If a match is found, the function returns the content inside the fence, stripped of whitespace. If no fence is detected, it returns the raw text stripped of leading/trailing whitespace. This extracted string is then passed to standard JSON parsers for validation and consumption by the renderer.
"""Local-Qwen httpx client. Self-contained, no external dependencies.
`chat_template_kwargs={'enable_thinking': bool}`, `</think>` stripping,
model auto-probe via `/v1/models`. NO MCP - direct httpx to localhost:8010.
"""
from __future__ import annotations
import dataclasses
import time
from typing import Any
import httpx
DEFAULT_URL = "http://localhost:8010"
DEFAULT_TIMEOUT = 300
class QwenError(RuntimeError):
"""Local Qwen call failure with a stable shape."""
@dataclasses.dataclass
class QwenResponse:
content: str
latency_ms: float
truncated: bool
raw: dict[str, Any]
def resolve_model_id(url: str = DEFAULT_URL, timeout: int = 10) -> str:
"""Probe /v1/models to discover the served model id."""
try:
resp = httpx.get(f"{url.rstrip('/')}/v1/models", timeout=timeout)
resp.raise_for_status()
data = resp.json().get("data") or []
if not data:
raise QwenError(f"local Qwen has no models loaded at {url}")
return data[0]["id"]
except httpx.HTTPError as e:
"""System prompts for the storyline + slide-by-slide build phases.
prose-hygiene: allow (these prompts name the AI-speak words the model must
avoid, so the prose-hygiene gate must skip this file.)
Layout (mirrors `sddc.blueprint.prompts` for prefix-cache stability):
§A OOXML/PowerPoint design quick reference (~1k tokens, byte-stable)
§B Deck/Slide JSON schema (byte-stable)
§C Hand-curated few-shot examples (byte-stable)
§D Standing instructions (byte-stable)
user prompt = intent + theme + facts (varies per call)
Output extraction is JSON fence: ` ```json … ``` `.
"""
from __future__ import annotations
import json
import re
# ── §A: OOXML / PowerPoint design quick reference ─────────────────────
SECTION_A = """\
You are an expert presentations editor. Your output drives a renderer that
materialises slides into native PowerPoint OOXML (python-pptx + lxml on
ppt/slides/slideN.xml, ppt/charts/chartN.xml, ppt/theme/themeN.xml).
Design rules:
- Every slide should carry a clear single message. Title states it; body
proves it; visual reinforces it.
- Title 36-44pt, body 14-18pt. Don't suggest sizes outside this range.
- Bullets should be ≤ 8 words; prefer parallel grammatical structure.
- Use a chart whenever the slide compares quantities, shows a trend, or
partitions a whole. Prefer "bar" for comparisons, "line" for trends,
"pie/doughnut" for parts of a whole, "area"/"line" for time series.
- Use a table for ≤ 6 rows of categorical data with multiple attributes.
- Layouts: title (cover) | title_content | two_column | chart |
image_text | table | divider | closing.
- Keep the deck tight - ≤ 12 slides for most decks; cover ≥ 4 distinct