What is Extract Versioned Template?
Extract Versioned Template is an AI refactoring for LLM systems, in the Composing prompts group. Gather scattered prompt fragments into one template with a name, version, and variables.
Extrair Template Versionado
Extract Versioned Template is an AI refactoring for LLM systems, in the Composing prompts group. Gather scattered prompt fragments into one template with a name, version, and variables.
The prompt is scattered in pieces across the code, unversioned, or tuned to one model in a way nobody can change safely. These are the Scattered Prompt Strings, Unversioned Prompt, and Model-Pinned Prompt smells.
Picture the company’s SDR. The first message to a lead is born in four files: the persona in persona.py, the tone in tone.py, the quarter’s campaign in campaigns/q3.py, and an if in writer.py that hides the price when the lead is enterprise. Nobody on the team can paste the full prompt into a chat and say “this is what the model sees”.
One Monday the reply rate drops. You open the trace and see only the model call. No name, no version, no rendered text. Which of the four files changed? Is the Q3 campaign still live in October? Did the price if fire for the wrong lead? Without an artifact, you are guessing.
The destination pattern for this refactoring is the Prompt Template Registry.
Gather the pieces into a single template, with a name, an immutable version, and declared variables. Code loads the template by name and version, fills in the variables, and records the version on every run.
-# persona.py, tone.py, campaigns/q3.py, writer.py
-prompt = BASE + tone(lead.segment) + CAMPAIGN + f"Lead: {lead.name}. " \
- + ("Do not mention price. " if lead.is_enterprise else "")
+# prompts/sdr_first_message/v14.yaml
+# name: sdr_first_message
+# version: 14
+# model: claude-sonnet-4-5
+# variables: [lead_name, company, tone, pricing_policy, campaign]
+# template: |
+# You are Julia, a consultant at the Company.
+# Tone: {{ tone }}.
+# Active campaign: {{ campaign }}.
+# Pricing policy for this lead: {{ pricing_policy }}.
+# Lead: {{ lead_name }}, {{ company }}.
+
+tpl = prompts.get("sdr_first_message", version=14)
+prompt = tpl.render(
+ lead_name=lead.name, company=lead.company, tone=tone_for(lead.segment),
+ pricing_policy=pricing_policy_for(lead), campaign=active_campaign())
+trace.set("prompt.name", tpl.name)
+trace.set("prompt.version", tpl.version)
Before: the prompt only exists at run time.
flowchart LR
A["persona.py"]:::tool --> J["1. Concatenation"]:::tool
B["tone.py"]:::tool --> J
C["campaign.py"]:::tool --> J
J --> M(["2. Prompt with no name or version"]):::llm
After: a versioned template, filled with declared variables.
flowchart LR
R[("1. Registry: sdr_first_message v14")]:::store --> T["2. Render with variables"]:::tool
V(["Lead data"]):::io --> T
T --> M(["3. Model"]):::llm
M --> L[("4. Trace with name and version")]:::store
Take a realistic lead: Marina, at Indústria Norte (a Brazilian manufacturer), segment manufacturing, enterprise contract. In the before, writer.py concatenates four strings and if lead.is_enterprise appends “Do not mention price”. The final text exists only in that instant.
In the after, tone_for("manufacturing") returns "formal", pricing_policy_for(lead) returns "Do not mention price or a range.", and active_campaign() returns the live campaign text. Template v14 receives five values and produces a reproducible prompt. The span looks like this:
prompt.name=sdr_first_message prompt.version=14
lead_name=Marina company=Indústria Norte tone=formal
A week later the reply rate drops again. You filter the trace by prompt.version=14, grab the rendered prompt, and compare it with a case that used to work. If the text is identical, the problem is not the prompt: it is the lead, the model, or the channel. If it differs, you know exactly what changed.
A versioned template turns the prompt into an artifact: you can read it whole, review it in a pull request, compare two versions, and roll back. Declared variables make explicit what changes from one request to the next; everything else is fixed and testable.
The version on the trace closes the loop. A bad answer points to the exact text that produced it, and the eval can compare a new version with the previous one before the switch. Migrating models becomes creating a new template version for the new model and comparing the two, instead of rewriting scattered if model == ... checks.
There is a cache win too. Providers such as Anthropic and OpenAI cache the stable prefix of the prompt. If the persona, the base tone, and the rules sit at the start of the template, and only lead_name, campaign, and the date change at the end, the cache hits. Scattered concatenation mixes what changes with what does not, and the cache misses the prefix.
A common mistake: moving the Python if into the YAML. {{ "formal" if segment == "manufacturing" else "friendly" }} recreates Scattered Prompt Strings, now hidden inside the template. Compute tone in code and pass the result.
pricing_policy, tone), not logic in the template.Immutable version means: the file v14.yaml is never edited after it is published. A one-word tweak becomes v15.yaml. Production points at a number, not at latest.
When the model changes, you do not reuse v14 “and hope”. You create v15 with the text adjusted to the new model, run the eval on both, and only then promote. That is the treatment for Model-Pinned Prompt: the text and the model travel together, but out in the open.
campaign can receive a whole PDF, the template becomes a bottomless bag. Declare type and max size in the registry, and reject values that miss.Which parts of these snippets become template variables, and which stay fixed?
prompt = "You answer HR questions for the Company. "
prompt += f"The employee's name is {emp.name} and they work in {emp.state}. "
if emp.state == "SP":
prompt += "Account for the January 25 city holiday. "
prompt += "Be brief. Today is " + date.today().isoformat() + "."
Fixed: the role, “be brief”. Variables: employee_name, local_holidays (a list computed in code from the state), and today. The date should go at the end of the prompt, or into the user message, so it does not break the prompt cache.
The if emp.state == "SP" does not go into the YAML. In code, something like holidays_for(emp.state) returns "January 25 (São Paulo)" or an empty list. São Paulo (the Brazilian state, abbreviated SP) has a city holiday that other states do not. The template only interpolates.
name: hr_assistant
version: 3
variables: [employee_name, local_holidays, today]
template: |
You answer HR questions for the Company. Be brief.
Employee: {{ employee_name }}.
Local holidays to consider: {{ local_holidays }}.
Today's date: {{ today }}.Extract Versioned Template is an AI refactoring for LLM systems, in the Composing prompts group. Gather scattered prompt fragments into one template with a name, version, and variables.
It treats Model-Pinned Prompt, Scattered Prompt Strings, and Unversioned Prompt.
The Prompt Template Registry design pattern.