go-patterns
esc

Prompt Template

Build prompts from versioned, parameterized templates so prompt logic stays separate from agent orchestration code and can be tested, diffed, and swapped independently.

មិនទាន់អាន ~3 min · 3 ឯកសារ · go1.24
ក្រុម Agentic ស្មុគស្មាញ ទាប ប្រើក្នុងការពិត ខ្ពស់ ឧទាហរណ៍ src/code/agentic/prompt-template

The Problem

Agent prompt strings concatenated inline — "You are a " + role + " assistant. The user asked: " + query — are impossible to review, test independently, or version. A one-character change to a prompt buried in a 500-line agent file can silently break behavior. As prompts grow to hundreds of tokens with multiple sections, inline construction becomes unmanageable.

The Solution

Store each prompt section as a named PromptTemplate with a version string and a text/template body. A Registry compiles templates at registration time — syntax errors surface immediately rather than at runtime. Registry.Render(name, vars) performs substitution with missingkey=error, so a forgotten variable is a compile-time-equivalent panic, not a silent blank. A PromptBuilder assembles multi-section prompts by rendering and concatenating named templates in order.

Structure

Prompt Template Pattern
Step 1 of 3

Registration and Validation

Templates are registered at package init or server startup via MustRegister(). text/template.Parse() runs immediately — a malformed template panics early rather than failing during a live LLM call.

Implementation

package main

// PromptTemplate holds a named, versioned text/template for prompt construction.
type PromptTemplate struct {
	Name    string
	Version string
	Raw     string
}

// RenderedPrompt is the final prompt string after variable substitution.
type RenderedPrompt struct {
	TemplateName string
	Content      string
}

// TemplateVars is a free-form map of variable substitutions.
type TemplateVars map[string]any

Real-World Analogy

Legal document templates: a law firm maintains versioned contract templates with placeholder clauses. The paralegal fills in the blanks (client name, date, jurisdiction) for each engagement. The underlying template is reviewed, approved, and versioned independently from the data that populates it. Changing the indemnification clause means updating one template file, not hunting through every engagement letter.

Pros and Cons

| Pros | Cons | |------|------| | Prompt text is reviewable, diffable, and versionable as plain files | text/template syntax is unfamiliar to non-Go contributors | | missingkey=error surfaces missing vars at render time, not silently | Template logic (conditionals, ranges) can grow complex and hard to test | | Registry validates syntax at startup, not during a live LLM call | Each new section type requires a new template registration | | PromptBuilder decouples assembly order from template content | Templates are strings — IDE support for embedded syntax is limited |

Best Practices

  • Version every template (Version: "1.0.0") and log the version alongside every LLM call — prompt regressions are easier to diagnose when you know which version was active.
  • Keep templates as logic-free as possible — use Go code for conditionals, not {{if}} blocks inside templates.
  • Write unit tests that call Registry.Render() with known vars and assert the output string — prompts are testable code.
  • Store template raw strings as .tmpl files in an embed.FS so they can be reviewed in pull requests without touching Go source.
  • Use TemplateVars (a map[string]any alias) consistently — resist the temptation to pass typed structs, which break the uniform Render signature.

When to Use

  • Any agent with more than one prompt section or more than two variable substitutions.
  • Teams where non-engineers need to review or edit prompt content.
  • Systems that run A/B experiments on prompt wording.

When NOT to Use

  • Trivial single-turn agents with a one-sentence system prompt — a string constant is fine.
  • Prompts with no variable substitution at all — just use a const.