prompt_cache_structurer.py

script

← Back to skill

Content hash: 46a529e925d92c146cd93d040093862c693866d791b80b5eae0cee488decdb21
#!/usr/bin/env python3
"""Structure prompts for maximum prefix-cache hits across providers.

Demonstrates the core rule: static content first, dynamic content last,
with provider-specific cache breakpoints (Anthropic) vs auto-caching (OpenAI).
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any


# ── Canonical prompt structure (static-first) ──────────────────────────────

@dataclass
class Prompt:
    system: str
    tools: list[dict] = field(default_factory=list)
    examples: list[dict[str, str]] = field(default_factory=list)
    user_input: str = ""

    def static_block(self) -> str:
        """Everything that rarely changes, ordered first for cache reuse."""
        parts = [self.system]
        if self.tools:
            parts.append("\n## Tools\n" + "\n".join(
                f"- {t['name']}: {t['description']}" for t in self.tools
            ))
        if self.examples:
            parts.append("\n## Examples")
            for ex in self.examples:
                parts.append(f"Q: {ex['q']}\nA: {ex['a']}")
        return "\n".join(parts)

    def dynamic_block(self) -> str:
        """Per-request content, always appended LAST."""
        return self.user_input


# ── Anthropic: explicit cache_control breakpoints ─────────────────────────

def anthropic_messages(prompt: Prompt) -> list[dict[str, Any]]:
    """Build an Anthropic messages payload with cache_control breakpoints."""
    messages: list[dict[str, Any]] = []

    # Static system prompt gets a cache breakpoint
    messages.append({
        "role": "system",
        "content": [{
            "type": "text",
            "text": prompt.static_block(),
            "cache_control": {"type": "ephemeral"},  # mark stable block as cacheable
        }],
    })

    # Dynamic user content: NO breakpoint (it changes every request)
    messages.append({
        "role": "user",
        "content": [{"type": "text", "text": prompt.dynamic_block()}],
    })
    return messages


# ── OpenAI: automatic caching (just structure correctly) ──────────────────

def openai_messages(prompt: Prompt) -> list[dict[str, Any]]:
    """OpenAI caches automatically; keep stable prefix identical across calls."""
    return [
        {"role": "system", "content": prompt.static_block()},
        {"role": "user", "content": prompt.dynamic_block()},
    ]


# ── Cache-hit simulation: detect prefix divergence ────────────────────────

def estimate_prefix_overlap(prompts: list[Prompt]) -> dict[str, int]:
    """Count how many tokens a stable prefix is shared (rough proxy)."""
    statics = [p.static_block() for p in prompts]
    if not statics:
        return {}
    base = statics[0]
    shared_chars = 0
    for s in statics[1:]:
        n = 0
        for a, b in zip(base, s):
            if a == b:
                n += 1
            else:
                break
        shared_chars += n
    return {
        "base_static_chars": len(base),
        "total_shared_across_requests": shared_chars,
        "note": "A timestamp/random id ANYWHERE in the prefix destroys overlap",
    }


# ── Demo ───────────────────────────────────────────────────────────────────

def main() -> None:
    shared_tools = [{"name": "get_weather", "description": "Fetch weather by city"}]
    base_system = (
        "You are a helpful assistant. Respond concisely. "
        "If unsure, say UNKNOWN. Do not invent data."
    )

    prompts = [
        Prompt(system=base_system, tools=shared_tools, user_input="Weather in Paris?"),
        Prompt(system=base_system, tools=shared_tools, user_input="Weather in Tokyo?"),
        # BAD: injecting a timestamp into the static block breaks the cache
        Prompt(
            system=base_system + "\nRequest time: 2025-08-15T10:00:00Z",
            tools=shared_tools,
            user_input="Weather in Berlin?",
        ),
    ]

    print("=== Anthropic-style (with breakpoint) ===")
    print(anthropic_messages(prompts[0])[0]["content"][0]["cache_control"])

    print("\n=== Prefix overlap report ===")
    report = estimate_prefix_overlap(prompts)
    for k, v in report.items():
        print(f"  {k}: {v}")

    print("\nRules:")
    print("  1. Static blocks (system, tools, examples) go FIRST")
    print("  2. Dynamic content (user query) goes LAST")
    print("  3. Anthropic: add cache_control breakpoints to stable blocks only")
    print("  4. OpenAI: automatic - just keep the prefix byte-identical")


if __name__ == "__main__":
    main()