Skip to content

Commit f2ae1e7

Browse files
committed
test(go/ai/prompt): add regression tests for template variable substitution
Add tests to ensure template variables are properly substituted at execution time, not load time. This is a regression test for #3924 Test coverage includes: - single role: basic template variable substitution - multi role: internal marker syntax (<<<dotprompt:role:...>>>) - multi role with handlebars syntax: {{role "..."}} syntax
1 parent 192cd7c commit f2ae1e7

File tree

1 file changed

+272
-0
lines changed

1 file changed

+272
-0
lines changed

go/ai/prompt_test.go

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2920,3 +2920,275 @@ func TestDefineDataPromptPanics(t *testing.T) {
29202920
}, "name is required")
29212921
})
29222922
}
2923+
2924+
// TestLoadPromptTemplateVariableSubstitution tests that template variables are
2925+
// properly substituted with actual input values at execution time.
2926+
// This is a regression test for https://github.com/firebase/genkit/issues/3924
2927+
func TestLoadPromptTemplateVariableSubstitution(t *testing.T) {
2928+
t.Run("single role", func(t *testing.T) {
2929+
tempDir := t.TempDir()
2930+
2931+
mockPromptFile := filepath.Join(tempDir, "greeting.prompt")
2932+
mockPromptContent := `---
2933+
model: test/chat
2934+
description: A greeting prompt with variables
2935+
---
2936+
Hello {{name}}, welcome to {{place}}!
2937+
`
2938+
2939+
if err := os.WriteFile(mockPromptFile, []byte(mockPromptContent), 0644); err != nil {
2940+
t.Fatalf("Failed to create mock prompt file: %v", err)
2941+
}
2942+
2943+
prompt := LoadPrompt(registry.New(), tempDir, "greeting.prompt", "template-var-test")
2944+
2945+
// Test with first set of input values
2946+
actionOpts1, err := prompt.Render(context.Background(), map[string]any{
2947+
"name": "Alice",
2948+
"place": "Wonderland",
2949+
})
2950+
if err != nil {
2951+
t.Fatalf("Failed to render prompt with first input: %v", err)
2952+
}
2953+
2954+
if len(actionOpts1.Messages) != 1 {
2955+
t.Fatalf("Expected 1 message, got %d", len(actionOpts1.Messages))
2956+
}
2957+
2958+
text1 := actionOpts1.Messages[0].Content[0].Text
2959+
if !strings.Contains(text1, "Alice") {
2960+
t.Errorf("Expected message to contain 'Alice', got: %s", text1)
2961+
}
2962+
if !strings.Contains(text1, "Wonderland") {
2963+
t.Errorf("Expected message to contain 'Wonderland', got: %s", text1)
2964+
}
2965+
2966+
// Test with second set of input values (different from first)
2967+
actionOpts2, err := prompt.Render(context.Background(), map[string]any{
2968+
"name": "Bob",
2969+
"place": "Paradise",
2970+
})
2971+
if err != nil {
2972+
t.Fatalf("Failed to render prompt with second input: %v", err)
2973+
}
2974+
2975+
if len(actionOpts2.Messages) != 1 {
2976+
t.Fatalf("Expected 1 message, got %d", len(actionOpts2.Messages))
2977+
}
2978+
2979+
text2 := actionOpts2.Messages[0].Content[0].Text
2980+
if !strings.Contains(text2, "Bob") {
2981+
t.Errorf("Expected message to contain 'Bob', got: %s", text2)
2982+
}
2983+
if !strings.Contains(text2, "Paradise") {
2984+
t.Errorf("Expected message to contain 'Paradise', got: %s", text2)
2985+
}
2986+
2987+
// Critical: Ensure the second render did NOT use the first input values
2988+
if strings.Contains(text2, "Alice") {
2989+
t.Errorf("BUG: Second render contains 'Alice' from first input! Got: %s", text2)
2990+
}
2991+
if strings.Contains(text2, "Wonderland") {
2992+
t.Errorf("BUG: Second render contains 'Wonderland' from first input! Got: %s", text2)
2993+
}
2994+
})
2995+
2996+
t.Run("multi role", func(t *testing.T) {
2997+
tempDir := t.TempDir()
2998+
2999+
mockPromptFile := filepath.Join(tempDir, "multi_role.prompt")
3000+
mockPromptContent := `---
3001+
model: test/chat
3002+
description: A multi-role prompt with variables
3003+
---
3004+
<<<dotprompt:role:system>>>
3005+
You are a {{personality}} assistant.
3006+
3007+
<<<dotprompt:role:user>>>
3008+
Hello {{name}}, please help me with {{task}}.
3009+
`
3010+
3011+
if err := os.WriteFile(mockPromptFile, []byte(mockPromptContent), 0644); err != nil {
3012+
t.Fatalf("Failed to create mock prompt file: %v", err)
3013+
}
3014+
3015+
prompt := LoadPrompt(registry.New(), tempDir, "multi_role.prompt", "multi-role-var-test")
3016+
3017+
// Test with first set of input values
3018+
actionOpts1, err := prompt.Render(context.Background(), map[string]any{
3019+
"personality": "helpful",
3020+
"name": "Alice",
3021+
"task": "coding",
3022+
})
3023+
if err != nil {
3024+
t.Fatalf("Failed to render prompt with first input: %v", err)
3025+
}
3026+
3027+
if len(actionOpts1.Messages) != 2 {
3028+
t.Fatalf("Expected 2 messages, got %d", len(actionOpts1.Messages))
3029+
}
3030+
3031+
// Check system message
3032+
systemMsg := actionOpts1.Messages[0]
3033+
if systemMsg.Role != RoleSystem {
3034+
t.Errorf("Expected first message role to be 'system', got '%s'", systemMsg.Role)
3035+
}
3036+
systemText := systemMsg.Content[0].Text
3037+
if !strings.Contains(systemText, "helpful") {
3038+
t.Errorf("Expected system message to contain 'helpful', got: %s", systemText)
3039+
}
3040+
3041+
// Check user message
3042+
userMsg := actionOpts1.Messages[1]
3043+
if userMsg.Role != RoleUser {
3044+
t.Errorf("Expected second message role to be 'user', got '%s'", userMsg.Role)
3045+
}
3046+
userText := userMsg.Content[0].Text
3047+
if !strings.Contains(userText, "Alice") {
3048+
t.Errorf("Expected user message to contain 'Alice', got: %s", userText)
3049+
}
3050+
if !strings.Contains(userText, "coding") {
3051+
t.Errorf("Expected user message to contain 'coding', got: %s", userText)
3052+
}
3053+
3054+
// Test with second set of input values (different from first)
3055+
actionOpts2, err := prompt.Render(context.Background(), map[string]any{
3056+
"personality": "professional",
3057+
"name": "Bob",
3058+
"task": "writing",
3059+
})
3060+
if err != nil {
3061+
t.Fatalf("Failed to render prompt with second input: %v", err)
3062+
}
3063+
3064+
if len(actionOpts2.Messages) != 2 {
3065+
t.Fatalf("Expected 2 messages, got %d", len(actionOpts2.Messages))
3066+
}
3067+
3068+
// Check system message with new values
3069+
systemMsg2 := actionOpts2.Messages[0]
3070+
systemText2 := systemMsg2.Content[0].Text
3071+
if !strings.Contains(systemText2, "professional") {
3072+
t.Errorf("Expected system message to contain 'professional', got: %s", systemText2)
3073+
}
3074+
if strings.Contains(systemText2, "helpful") {
3075+
t.Errorf("BUG: Second render system message contains 'helpful' from first input! Got: %s", systemText2)
3076+
}
3077+
3078+
// Check user message with new values
3079+
userMsg2 := actionOpts2.Messages[1]
3080+
userText2 := userMsg2.Content[0].Text
3081+
if !strings.Contains(userText2, "Bob") {
3082+
t.Errorf("Expected user message to contain 'Bob', got: %s", userText2)
3083+
}
3084+
if !strings.Contains(userText2, "writing") {
3085+
t.Errorf("Expected user message to contain 'writing', got: %s", userText2)
3086+
}
3087+
if strings.Contains(userText2, "Alice") {
3088+
t.Errorf("BUG: Second render user message contains 'Alice' from first input! Got: %s", userText2)
3089+
}
3090+
if strings.Contains(userText2, "coding") {
3091+
t.Errorf("BUG: Second render user message contains 'coding' from first input! Got: %s", userText2)
3092+
}
3093+
})
3094+
3095+
t.Run("multi role with handlebars syntax", func(t *testing.T) {
3096+
tempDir := t.TempDir()
3097+
3098+
mockPromptFile := filepath.Join(tempDir, "handlebars_role.prompt")
3099+
// Use Handlebars {{role "..."}} syntax instead of internal markers
3100+
mockPromptContent := `---
3101+
model: test/chat
3102+
description: A multi-role prompt using Handlebars role syntax
3103+
---
3104+
{{role "system"}}
3105+
You are a {{personality}} assistant.
3106+
3107+
{{role "user"}}
3108+
Hello {{name}}, please help me with {{task}}.
3109+
`
3110+
3111+
if err := os.WriteFile(mockPromptFile, []byte(mockPromptContent), 0644); err != nil {
3112+
t.Fatalf("Failed to create mock prompt file: %v", err)
3113+
}
3114+
3115+
prompt := LoadPrompt(registry.New(), tempDir, "handlebars_role.prompt", "handlebars-role-test")
3116+
3117+
// Test with first set of input values
3118+
actionOpts1, err := prompt.Render(context.Background(), map[string]any{
3119+
"personality": "helpful",
3120+
"name": "Alice",
3121+
"task": "coding",
3122+
})
3123+
if err != nil {
3124+
t.Fatalf("Failed to render prompt with first input: %v", err)
3125+
}
3126+
3127+
if len(actionOpts1.Messages) != 2 {
3128+
t.Fatalf("Expected 2 messages, got %d", len(actionOpts1.Messages))
3129+
}
3130+
3131+
// Check system message
3132+
systemMsg := actionOpts1.Messages[0]
3133+
if systemMsg.Role != RoleSystem {
3134+
t.Errorf("Expected first message role to be 'system', got '%s'", systemMsg.Role)
3135+
}
3136+
systemText := systemMsg.Content[0].Text
3137+
if !strings.Contains(systemText, "helpful") {
3138+
t.Errorf("Expected system message to contain 'helpful', got: %s", systemText)
3139+
}
3140+
3141+
// Check user message
3142+
userMsg := actionOpts1.Messages[1]
3143+
if userMsg.Role != RoleUser {
3144+
t.Errorf("Expected second message role to be 'user', got '%s'", userMsg.Role)
3145+
}
3146+
userText := userMsg.Content[0].Text
3147+
if !strings.Contains(userText, "Alice") {
3148+
t.Errorf("Expected user message to contain 'Alice', got: %s", userText)
3149+
}
3150+
if !strings.Contains(userText, "coding") {
3151+
t.Errorf("Expected user message to contain 'coding', got: %s", userText)
3152+
}
3153+
3154+
// Test with second set of input values (different from first)
3155+
actionOpts2, err := prompt.Render(context.Background(), map[string]any{
3156+
"personality": "professional",
3157+
"name": "Bob",
3158+
"task": "writing",
3159+
})
3160+
if err != nil {
3161+
t.Fatalf("Failed to render prompt with second input: %v", err)
3162+
}
3163+
3164+
if len(actionOpts2.Messages) != 2 {
3165+
t.Fatalf("Expected 2 messages, got %d", len(actionOpts2.Messages))
3166+
}
3167+
3168+
// Check system message with new values
3169+
systemMsg2 := actionOpts2.Messages[0]
3170+
systemText2 := systemMsg2.Content[0].Text
3171+
if !strings.Contains(systemText2, "professional") {
3172+
t.Errorf("Expected system message to contain 'professional', got: %s", systemText2)
3173+
}
3174+
if strings.Contains(systemText2, "helpful") {
3175+
t.Errorf("BUG: Second render system message contains 'helpful' from first input! Got: %s", systemText2)
3176+
}
3177+
3178+
// Check user message with new values
3179+
userMsg2 := actionOpts2.Messages[1]
3180+
userText2 := userMsg2.Content[0].Text
3181+
if !strings.Contains(userText2, "Bob") {
3182+
t.Errorf("Expected user message to contain 'Bob', got: %s", userText2)
3183+
}
3184+
if !strings.Contains(userText2, "writing") {
3185+
t.Errorf("Expected user message to contain 'writing', got: %s", userText2)
3186+
}
3187+
if strings.Contains(userText2, "Alice") {
3188+
t.Errorf("BUG: Second render user message contains 'Alice' from first input! Got: %s", userText2)
3189+
}
3190+
if strings.Contains(userText2, "coding") {
3191+
t.Errorf("BUG: Second render user message contains 'coding' from first input! Got: %s", userText2)
3192+
}
3193+
})
3194+
}

0 commit comments

Comments
 (0)