Expand PromptDefinition renderer coverage and tighten missing/nil input handling
This commit is contained in:
@@ -43,7 +43,7 @@ func (r *goRenderer) Render(ctx context.Context, definition *domain.PromptDefini
|
|||||||
funcs := template.FuncMap{
|
funcs := template.FuncMap{
|
||||||
"input": func(name string) (string, error) {
|
"input": func(name string) (string, error) {
|
||||||
art, ok := inputs[name]
|
art, ok := inputs[name]
|
||||||
if !ok {
|
if !ok || art == nil {
|
||||||
return "", fmt.Errorf("%w: %s", ErrUnknownInput, name)
|
return "", fmt.Errorf("%w: %s", ErrUnknownInput, name)
|
||||||
}
|
}
|
||||||
return string(art.Body), nil
|
return string(art.Body), nil
|
||||||
|
|||||||
@@ -12,8 +12,53 @@ func TestGoRenderer_Render(t *testing.T) {
|
|||||||
renderer := NewGoRenderer()
|
renderer := NewGoRenderer()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
profile := &domain.PromptDefinition{
|
inputs := map[string]*domain.Artifact{
|
||||||
ID: "test-profile",
|
"transcript": {Body: []byte("The quick brown fox.")},
|
||||||
|
}
|
||||||
|
vars := map[string]string{
|
||||||
|
"role": "helpful assistant",
|
||||||
|
"tone": "concise",
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("rendering inline message content", func(t *testing.T) {
|
||||||
|
def := &domain.PromptDefinition{
|
||||||
|
Inputs: []domain.PromptInput{{Name: "transcript", Required: true}},
|
||||||
|
Templates: []domain.PromptMessageTemplate{
|
||||||
|
{Role: "user", Content: "Analyze this: {{input \"transcript\"}}"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := renderer.Render(ctx, def, inputs, vars)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if len(res.Messages) != 1 {
|
||||||
|
t.Fatalf("expected 1 message, got %d", len(res.Messages))
|
||||||
|
}
|
||||||
|
if res.Messages[0].Content != "Analyze this: The quick brown fox." {
|
||||||
|
t.Fatalf("unexpected rendered content: %q", res.Messages[0].Content)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("rendering file-backed message content loaded into prompt definition", func(t *testing.T) {
|
||||||
|
def := &domain.PromptDefinition{
|
||||||
|
Inputs: []domain.PromptInput{{Name: "transcript", Required: true}},
|
||||||
|
Templates: []domain.PromptMessageTemplate{
|
||||||
|
{Role: "user", Content: "From file: {{input \"transcript\"}}", ContentFile: "/tmp/user.tmpl"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := renderer.Render(ctx, def, inputs, vars)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if got := res.Messages[0].Content; got != "From file: The quick brown fox." {
|
||||||
|
t.Fatalf("unexpected file-backed render result: %q", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("rendering system and user messages", func(t *testing.T) {
|
||||||
|
def := &domain.PromptDefinition{
|
||||||
Inputs: []domain.PromptInput{{Name: "transcript", Required: true}},
|
Inputs: []domain.PromptInput{{Name: "transcript", Required: true}},
|
||||||
Templates: []domain.PromptMessageTemplate{
|
Templates: []domain.PromptMessageTemplate{
|
||||||
{Role: "system", Content: "You are a {{.role}}."},
|
{Role: "system", Content: "You are a {{.role}}."},
|
||||||
@@ -21,90 +66,147 @@ func TestGoRenderer_Render(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
inputs := map[string]*domain.Artifact{
|
res, err := renderer.Render(ctx, def, inputs, vars)
|
||||||
"transcript": {Body: []byte("The quick brown fox.")},
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := map[string]string{
|
|
||||||
"role": "helpful assistant",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("successful render", func(t *testing.T) {
|
|
||||||
res, err := renderer.Render(ctx, profile, inputs, vars)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if len(res.Messages) != 2 {
|
if len(res.Messages) != 2 {
|
||||||
t.Errorf("expected 2 messages, got %d", len(res.Messages))
|
t.Fatalf("expected 2 messages, got %d", len(res.Messages))
|
||||||
}
|
}
|
||||||
if res.Messages[0].Content != "You are a helpful assistant." {
|
if res.Messages[0].Role != "system" || res.Messages[1].Role != "user" {
|
||||||
t.Errorf("unexpected system message: %s", res.Messages[0].Content)
|
t.Fatalf("unexpected roles: %#v", res.Messages)
|
||||||
}
|
|
||||||
if res.Messages[1].Content != "Analyze this: The quick brown fox." {
|
|
||||||
t.Errorf("unexpected user message: %s", res.Messages[1].Content)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("missing required input", func(t *testing.T) {
|
t.Run("accessing vars", func(t *testing.T) {
|
||||||
emptyInputs := map[string]*domain.Artifact{}
|
def := &domain.PromptDefinition{
|
||||||
_, err := renderer.Render(ctx, profile, emptyInputs, vars)
|
|
||||||
if !errors.Is(err, ErrMissingRequiredInput) {
|
|
||||||
t.Errorf("expected ErrMissingRequiredInput, got %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("unknown input in template", func(t *testing.T) {
|
|
||||||
profileUnknown := &domain.PromptDefinition{
|
|
||||||
Inputs: []domain.PromptInput{{Name: "transcript", Required: true}},
|
Inputs: []domain.PromptInput{{Name: "transcript", Required: true}},
|
||||||
Templates: []domain.PromptMessageTemplate{
|
Templates: []domain.PromptMessageTemplate{
|
||||||
{Role: "user", Content: "Hello {{input \"ghost\"}}"},
|
{Role: "system", Content: "Speak in a {{.tone}} tone."},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_, err := renderer.Render(ctx, profileUnknown, inputs, vars)
|
|
||||||
|
res, err := renderer.Render(ctx, def, inputs, vars)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if res.Messages[0].Content != "Speak in a concise tone." {
|
||||||
|
t.Fatalf("unexpected vars rendering: %q", res.Messages[0].Content)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("inserting required input artifact", func(t *testing.T) {
|
||||||
|
def := &domain.PromptDefinition{
|
||||||
|
Inputs: []domain.PromptInput{{Name: "transcript", Required: true}},
|
||||||
|
Templates: []domain.PromptMessageTemplate{
|
||||||
|
{Role: "user", Content: "{{input \"transcript\"}}"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := renderer.Render(ctx, def, inputs, vars)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if res.Messages[0].Content != "The quick brown fox." {
|
||||||
|
t.Fatalf("unexpected required input rendering: %q", res.Messages[0].Content)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("optional input absent and not referenced", func(t *testing.T) {
|
||||||
|
def := &domain.PromptDefinition{
|
||||||
|
Inputs: []domain.PromptInput{
|
||||||
|
{Name: "transcript", Required: true},
|
||||||
|
{Name: "glossary", Required: false},
|
||||||
|
},
|
||||||
|
Templates: []domain.PromptMessageTemplate{
|
||||||
|
{Role: "user", Content: "Transcript: {{input \"transcript\"}}"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := renderer.Render(ctx, def, inputs, vars)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if len(res.Messages) != 1 {
|
||||||
|
t.Fatalf("expected one rendered message, got %d", len(res.Messages))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("optional input absent but referenced, expecting failure", func(t *testing.T) {
|
||||||
|
def := &domain.PromptDefinition{
|
||||||
|
Inputs: []domain.PromptInput{
|
||||||
|
{Name: "transcript", Required: true},
|
||||||
|
{Name: "glossary", Required: false},
|
||||||
|
},
|
||||||
|
Templates: []domain.PromptMessageTemplate{
|
||||||
|
{Role: "user", Content: "Glossary: {{input \"glossary\"}}"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := renderer.Render(ctx, def, inputs, vars)
|
||||||
if !errors.Is(err, ErrRenderFailure) {
|
if !errors.Is(err, ErrRenderFailure) {
|
||||||
t.Errorf("expected ErrRenderFailure, got %v", err)
|
t.Fatalf("expected ErrRenderFailure, got %v", err)
|
||||||
}
|
}
|
||||||
if !errors.Is(err, ErrUnknownInput) {
|
if !errors.Is(err, ErrUnknownInput) {
|
||||||
t.Errorf("expected ErrUnknownInput, got %v", err)
|
t.Fatalf("expected ErrUnknownInput, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("required input missing, expecting failure", func(t *testing.T) {
|
||||||
|
def := &domain.PromptDefinition{
|
||||||
|
Inputs: []domain.PromptInput{{Name: "transcript", Required: true}},
|
||||||
|
Templates: []domain.PromptMessageTemplate{
|
||||||
|
{Role: "user", Content: "Analyze this: {{input \"transcript\"}}"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := renderer.Render(ctx, def, map[string]*domain.Artifact{}, vars)
|
||||||
|
if !errors.Is(err, ErrMissingRequiredInput) {
|
||||||
|
t.Fatalf("expected ErrMissingRequiredInput, got %v", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid template syntax", func(t *testing.T) {
|
t.Run("invalid template syntax", func(t *testing.T) {
|
||||||
profileInvalid := &domain.PromptDefinition{
|
def := &domain.PromptDefinition{
|
||||||
Inputs: []domain.PromptInput{{Name: "transcript", Required: true}},
|
Inputs: []domain.PromptInput{{Name: "transcript", Required: true}},
|
||||||
Templates: []domain.PromptMessageTemplate{
|
Templates: []domain.PromptMessageTemplate{
|
||||||
{Role: "user", Content: "Hello {{.unclosed"},
|
{Role: "user", Content: "Hello {{.unclosed"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_, err := renderer.Render(ctx, profileInvalid, inputs, vars)
|
|
||||||
|
_, err := renderer.Render(ctx, def, inputs, vars)
|
||||||
if !errors.Is(err, ErrInvalidTemplate) {
|
if !errors.Is(err, ErrInvalidTemplate) {
|
||||||
t.Errorf("expected ErrInvalidTemplate, got %v", err)
|
t.Fatalf("expected ErrInvalidTemplate, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unknown input reference", func(t *testing.T) {
|
||||||
|
def := &domain.PromptDefinition{
|
||||||
|
Inputs: []domain.PromptInput{{Name: "transcript", Required: true}},
|
||||||
|
Templates: []domain.PromptMessageTemplate{
|
||||||
|
{Role: "user", Content: "Hello {{input \"ghost\"}}"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := renderer.Render(ctx, def, inputs, vars)
|
||||||
|
if !errors.Is(err, ErrRenderFailure) {
|
||||||
|
t.Fatalf("expected ErrRenderFailure, got %v", err)
|
||||||
|
}
|
||||||
|
if !errors.Is(err, ErrUnknownInput) {
|
||||||
|
t.Fatalf("expected ErrUnknownInput, got %v", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("empty message role", func(t *testing.T) {
|
t.Run("empty message role", func(t *testing.T) {
|
||||||
profileNoRole := &domain.PromptDefinition{
|
def := &domain.PromptDefinition{
|
||||||
Inputs: []domain.PromptInput{{Name: "transcript", Required: true}},
|
Inputs: []domain.PromptInput{{Name: "transcript", Required: true}},
|
||||||
Templates: []domain.PromptMessageTemplate{
|
Templates: []domain.PromptMessageTemplate{
|
||||||
{Role: "", Content: "Hello"},
|
{Role: "", Content: "Hello"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_, err := renderer.Render(ctx, profileNoRole, inputs, vars)
|
_, err := renderer.Render(ctx, def, inputs, vars)
|
||||||
if !errors.Is(err, ErrInvalidMessageRole) {
|
if !errors.Is(err, ErrInvalidMessageRole) {
|
||||||
t.Errorf("expected ErrInvalidMessageRole, got %v", err)
|
t.Fatalf("expected ErrInvalidMessageRole, got %v", err)
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("missing variable in template", func(t *testing.T) {
|
|
||||||
profileMissingVar := &domain.PromptDefinition{
|
|
||||||
Inputs: []domain.PromptInput{{Name: "transcript", Required: true}},
|
|
||||||
Templates: []domain.PromptMessageTemplate{
|
|
||||||
{Role: "system", Content: "You are {{.missing}}"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_, err := renderer.Render(ctx, profileMissingVar, inputs, vars)
|
|
||||||
if !errors.Is(err, ErrRenderFailure) {
|
|
||||||
t.Errorf("expected ErrRenderFailure for missing variable, got %v", err)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user