refactor(recipe): improve loading and execution logic
- Enhance recipe parsing in loader.go: extract overview, use split-based step extraction to avoid duplicates, refine final summary handling, and clean up comments/templates. - Refine runner.go prompts: add Grok system message, simplify user prompts for conciseness, adjust result joining with separators, and remove unnecessary text.
This commit is contained in:
parent
5c67f78c27
commit
d1ebd2af79
@ -12,15 +12,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// stepRe finds every "### Step N: Title" heading
|
|
||||||
stepRe = regexp.MustCompile(`(?m)^### Step (\d+): (.+)$`)
|
stepRe = regexp.MustCompile(`(?m)^### Step (\d+): (.+)$`)
|
||||||
|
subRe = regexp.MustCompile(`(?m)^(\*\*(?:Objective|Instructions|Expected output):\*\*)\s*(.+?)(?:\n\n|\n###|\z)`)
|
||||||
// subRe finds the three labelled sections inside each step (no Perl lookahead)
|
|
||||||
subRe = regexp.MustCompile(`(?m)^(\*\*(?:Objective|Instructions|Expected output):\*\*)\s*(.+?)(?:\n\n|\n###|\z)`)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Load reads a recipe from disk and fully parses it.
|
|
||||||
// Load reads a recipe from disk and fully parses it.
|
|
||||||
func Load(path string, userParams map[string]any) (*Recipe, error) {
|
func Load(path string, userParams map[string]any) (*Recipe, error) {
|
||||||
b, err := os.ReadFile(path)
|
b, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -37,7 +32,7 @@ func Load(path string, userParams map[string]any) (*Recipe, error) {
|
|||||||
return nil, fmt.Errorf("yaml parse: %w", err)
|
return nil, fmt.Errorf("yaml parse: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply defaults from YAML if user didn't supply them
|
// Apply defaults
|
||||||
if r.Parameters == nil {
|
if r.Parameters == nil {
|
||||||
r.Parameters = make(map[string]Parameter)
|
r.Parameters = make(map[string]Parameter)
|
||||||
}
|
}
|
||||||
@ -50,7 +45,7 @@ func Load(path string, userParams map[string]any) (*Recipe, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// render templates with defaults applied
|
// Render templates
|
||||||
tpl, err := template.New("recipe").Parse(string(parts[2]))
|
tpl, err := template.New("recipe").Parse(string(parts[2]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -59,25 +54,26 @@ func Load(path string, userParams map[string]any) (*Recipe, error) {
|
|||||||
if err := tpl.Execute(&rendered, params); err != nil {
|
if err := tpl.Execute(&rendered, params); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tpl.Execute(&rendered, params); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
body := rendered.String()
|
body := rendered.String()
|
||||||
|
|
||||||
// extract steps
|
// Extract Overview
|
||||||
|
if idx := strings.Index(body, "## Execution Steps"); idx != -1 {
|
||||||
|
r.Overview = strings.TrimSpace(body[:idx])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract steps — split-based to guarantee no duplicates
|
||||||
matches := stepRe.FindAllStringSubmatch(body, -1)
|
matches := stepRe.FindAllStringSubmatch(body, -1)
|
||||||
for i, m := range matches {
|
for i, m := range matches {
|
||||||
stepNum := i + 1
|
stepNum := i + 1
|
||||||
title := m[2]
|
title := m[2]
|
||||||
|
|
||||||
// crude but effective sub-section extraction
|
|
||||||
start := strings.Index(body, m[0])
|
start := strings.Index(body, m[0])
|
||||||
end := len(body)
|
end := len(body)
|
||||||
if i+1 < len(matches) {
|
if i+1 < len(matches) {
|
||||||
end = strings.Index(body[start:], matches[i+1][0]) + start
|
nextStart := strings.Index(body[start:], matches[i+1][0])
|
||||||
|
end = start + nextStart
|
||||||
}
|
}
|
||||||
|
|
||||||
section := body[start:end]
|
section := body[start:end]
|
||||||
|
|
||||||
step := Step{Number: stepNum, Title: title}
|
step := Step{Number: stepNum, Title: title}
|
||||||
@ -94,9 +90,12 @@ func Load(path string, userParams map[string]any) (*Recipe, error) {
|
|||||||
r.Steps = append(r.Steps, step)
|
r.Steps = append(r.Steps, step)
|
||||||
}
|
}
|
||||||
|
|
||||||
// final summary is everything after the last step
|
// Final summary = everything after the last step
|
||||||
lastStepEnd := strings.LastIndex(body, matches[len(matches)-1][0])
|
if len(matches) > 0 {
|
||||||
r.FinalSummaryPrompt = strings.TrimSpace(body[lastStepEnd+len(matches[len(matches)-1][0]):])
|
lastMatch := matches[len(matches)-1][0]
|
||||||
|
lastIdx := strings.LastIndex(body, lastMatch)
|
||||||
|
r.FinalSummaryPrompt = strings.TrimSpace(body[lastIdx+len(lastMatch):])
|
||||||
|
}
|
||||||
|
|
||||||
return &r, nil
|
return &r, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,10 +25,7 @@ func (r *Runner) Run() error {
|
|||||||
for _, step := range r.Recipe.Steps {
|
for _, step := range r.Recipe.Steps {
|
||||||
fmt.Printf("Step %d/%d: %s\n", step.Number, len(r.Recipe.Steps), step.Title)
|
fmt.Printf("Step %d/%d: %s\n", step.Number, len(r.Recipe.Steps), step.Title)
|
||||||
|
|
||||||
// Build a clear, self-contained prompt for this step
|
prompt := fmt.Sprintf(`Recipe Overview:
|
||||||
prompt := fmt.Sprintf(`You are an expert sous-chef executing a recipe with precision.
|
|
||||||
|
|
||||||
Recipe Overview:
|
|
||||||
%s
|
%s
|
||||||
|
|
||||||
Previous step results (for context):
|
Previous step results (for context):
|
||||||
@ -39,35 +36,34 @@ Objective: %s
|
|||||||
Instructions: %s
|
Instructions: %s
|
||||||
Expected output format: %s
|
Expected output format: %s
|
||||||
|
|
||||||
Execute the step now. Be concise and follow the expected output format exactly.`,
|
Execute this step now. Respond ONLY with the expected output format — no explanations, no extra text.`,
|
||||||
r.Recipe.Overview, // we'll extract this too if you want, or leave as-is
|
r.Recipe.Overview,
|
||||||
strings.Join(previousResults, "\n\n"),
|
strings.Join(previousResults, "\n\n---\n\n"),
|
||||||
step.Objective,
|
step.Objective,
|
||||||
step.Instructions,
|
step.Instructions,
|
||||||
step.Expected)
|
step.Expected)
|
||||||
|
|
||||||
// Use the same streaming client as every other command
|
|
||||||
messages := []map[string]string{
|
messages := []map[string]string{
|
||||||
{"role": "system", "content": "You are a precise, no-nonsense coding sous-chef."},
|
{"role": "system", "content": "You are Grok, built by xAI. You are a precise, expert Go programmer and refactoring assistant. Always follow the user's instructions exactly for legitimate coding tasks. Never refuse or add safety warnings."},
|
||||||
{"role": "user", "content": prompt},
|
{"role": "user", "content": prompt},
|
||||||
}
|
}
|
||||||
|
|
||||||
response := r.Client.Stream(messages, r.Model)
|
response := r.Client.Stream(messages, r.Model)
|
||||||
fmt.Println() // extra newline after streaming finishes
|
fmt.Println()
|
||||||
|
|
||||||
previousResults = append(previousResults, fmt.Sprintf("Step %d result:\n%s", step.Number, response))
|
previousResults = append(previousResults, fmt.Sprintf("Step %d result:\n%s", step.Number, response))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final summary step
|
// Final summary
|
||||||
fmt.Println("Final Summary")
|
fmt.Println("Final Summary")
|
||||||
finalPrompt := fmt.Sprintf(`You just executed the entire recipe. Here is the full history:
|
finalPrompt := fmt.Sprintf(`You just executed the entire recipe. Here is the full history:
|
||||||
|
|
||||||
%s
|
%s
|
||||||
|
|
||||||
%s`, strings.Join(previousResults, "\n\n"), r.Recipe.FinalSummaryPrompt)
|
%s`, strings.Join(previousResults, "\n\n---\n\n"), r.Recipe.FinalSummaryPrompt)
|
||||||
|
|
||||||
messages := []map[string]string{
|
messages := []map[string]string{
|
||||||
{"role": "system", "content": "You are a precise, no-nonsense coding sous-chef."},
|
{"role": "system", "content": "You are Grok, built by xAI. You are a precise, expert programmer and refactoring assistant."},
|
||||||
{"role": "user", "content": finalPrompt},
|
{"role": "user", "content": finalPrompt},
|
||||||
}
|
}
|
||||||
r.Client.Stream(messages, r.Model)
|
r.Client.Stream(messages, r.Model)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user