Skip to content

Instantly share code, notes, and snippets.

@pauleveritt
Created May 3, 2026 21:46
Show Gist options
  • Select an option

  • Save pauleveritt/905fdf3ba1f42ce241a78ce32bfd953b to your computer and use it in GitHub Desktop.

Select an option

Save pauleveritt/905fdf3ba1f42ce241a78ce32bfd953b to your computer and use it in GitHub Desktop.

t-strings (PEP 750) for prompt templating — research findings

(From web research subagent, 2026-05-03 night spike. Triggered by observation that tainie has two prompt-templating brittleness problems: server-side jinja in Qwen3.6 GGUF, and client-side "\n".join([...]) in prompts.py.)

What PEP 750 actually changes

t"..." returns a Template object (iterable of alternating str segments and Interpolation slots), **not a str **. Each Interpolation carries value, expr (source text like "name"), conv, format_spec. Nothing renders until a template function walks the parts and decides how to combine them.

The relevant property for prompts: structural separation between author-controlled literal text and runtime-controlled values. F-strings render eagerly and lose this distinction. A template function can apply per-slot policy — escape, fence, validate against a Pydantic model, refuse to render — before bytes reach the model.

Existing ecosystem

awesome-t-strings lists ~12 libraries: SQL, HTML (tdom), regex, dedent, structured data. Zero are LLM-prompt libraries.

  • Pydantic AI's TemplateStr uses Jinja-style {{name}}, not PEP 750.
  • LangChain / LlamaIndex / Instructor / Mirascope — all f-string or Jinja.
  • Anthropic / OpenAI SDKs — no t-string surface.
  • HuggingFace transformers chat templates — Jinja.

This is greenfield. If tainie ports prompts to t-strings, it's pioneering, not following.

What t-strings CAN address in tainie

A. System-prompt assembly (src/tainie/agent/prompts.py)

Today: "\n".join(rule_strings). Each rule is a raw constant. The string {{name}} in NO_TEMPLATE_PLACEHOLDER_RULE is a footgun — it could collide with downstream Jinja consumers.

A prompt() template function (~30 LOC) could:

  • Accept Template | str parts
  • Apply auto-escape on Interpolation.value (e.g. fence dynamic content with code blocks)
  • Reject newlines or malformed values per-slot via format_spec
  • Round-trip byte-identical to current output for a known input set

B. Tool descriptions / few-shot example injection

Splicing user-controlled paths, symbol names, or fixture content into examples is currently raw string concatenation. A t"path: {p:safe}" slot could enforce shape via the format_spec.

C. Structured prompt objects (a tdom-style DSL)

Sketch: t"<system>...<example input={x}>...</example></system>" parses to a typed PromptNode tree. Diffable, snapshot-testable, single render path emits OpenAI-chat / Anthropic-blocks / raw text. No such library exists yet. Informal name candidates: tprompt, tdom-prompt.

What t-strings will NOT address

  • The Qwen3.5/3.6 jinja "No user query found" bug is server-side — inside the GGUF's bundled chat template. PEP 750 is client-side. The known fixes are: update LM Studio, switch to lmstudio-community GGUF, override prompt template in LM Studio settings.
  • vLLM / TGI / oMLX / LM Studio accept Jinja chat templates only. No inference server has a t-string contract.
  • Pydantic AI's TemplateStr is Jinja-flavored. Replacing it means parallel adapter, not drop-in.
  • Type safety is modest. ty/mypy see Template, not the slot types. PEP 750 didn't ship language hints; per-slot validation is still runtime.

Recommendation for tainie

Invest cautiously, scoped to client-side assembly, only after one concrete experiment succeeds. The first experiment:

  1. Port src/tainie/agent/prompts.py to a small in-package prompt() template function (~30 LOC).
  2. Accepts Template | str, applies auto-escape on Interpolation.value.
  3. Round-trips byte-identical with current output.
  4. Add snapshot tests proving equality with current string.
  5. Then add one new affordance — slot-level escaping for a user-controlled value (project path injected into a tool description). Demonstrate it rejecting a malformed input the current code would silently splice.

Evidence threshold to expand wider:

  • (a) Preserves current model behavior on live regression suite
  • (b) Catches at least one realistic prompt-injection or placeholder-collision case the f-string version misses
  • (c) Reads more clearly to a reviewer

If yes: expand to tool docstrings and few-shots. If no: the win is cosmetic, not worth imposing the Python 3.14 floor on contributors.

The workspace neighbors (tdom, tdom-svcs) already commit to t-strings, so tainie is well-positioned to pioneer — but pioneering is the right word.

Sources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment