micro/go-micro · warning

flow: LLMOptimizer returned an empty prompt

Error message

flow: LLMOptimizer returned an empty prompt

What it means

The model responded successfully to the prompt-revision request, but its Answer (and fallback Reply) contained only whitespace or was empty. The library refuses to return an empty proposal since replacing a prompt with empty text would break the workflow step.

Source

Thrown at flow/analyze.go:171

// OptimizePrompt asks the model for a revised prompt for candidate using the
// current prompt and trace feedback. It returns only the proposal; it never
// modifies a Flow, Step, or Checkpoint.
func (o *PromptOptimizer) OptimizePrompt(ctx context.Context, candidate Candidate, currentPrompt string) (string, error) {
	if o == nil || o.model == nil {
		return "", fmt.Errorf("flow: LLMOptimizer requires a model")
	}
	prompt := fmt.Sprintf("Revise this workflow step prompt to improve the failing step.\nStep: %s\nMetric: %s\nScore: %.2f\nFeedback:\n- %s\n\nCurrent prompt:\n%s\n\nReturn only the revised prompt.", candidate.Step, candidate.Metric, candidate.Score, strings.Join(candidate.SampleFeedback, "\n- "), currentPrompt)
	resp, err := o.model.Generate(ctx, &ai.Request{Prompt: prompt})
	if err != nil {
		return "", err
	}
	proposal := strings.TrimSpace(resp.Answer)
	if proposal == "" {
		proposal = strings.TrimSpace(resp.Reply)
	}
	if proposal == "" {
		return "", fmt.Errorf("flow: LLMOptimizer returned an empty prompt")
	}
	return proposal, nil
}

func verificationFields(result string) (bool, string, bool) {
	if result == "" {
		return false, "", false
	}
	var obj map[string]any
	if err := json.Unmarshal([]byte(result), &obj); err != nil {
		return false, "", false
	}
	v, ok := obj["verification_passed"].(bool)
	if !ok {
		return false, "", false
	}
	fb, _ := obj["verification_feedback"].(string)
	return v, fb, true

View on GitHub (pinned to 24529f1404)

Solutions

  1. Check the model response fields your provider actually populates (Answer vs Reply) and confirm the adapter maps them.
  2. Increase max tokens / reduce prompt size so the model can produce output.
  3. Switch to a stronger model or retry the Generate call; transient empty completions are common.
  4. Log resp.Answer and resp.Reply to confirm what the model returned before failing.

Example fix

// before
resp, err := o.model.Generate(ctx, &ai.Request{Prompt: prompt}) // tiny model, max_tokens:1
// after
resp, err := o.model.Generate(ctx, &ai.Request{Prompt: prompt, Options: map[string]any{"max_tokens": 512}})
Defensive patterns

Strategy: retry

Validate before calling

if resp != nil && strings.TrimSpace(resp.Answer) == "" && strings.TrimSpace(resp.Reply) == "" {
	return errors.New("model returned empty answer; retrying")
}

Type guard

func hasProposal(resp *ai.Response) bool {
	return resp != nil && (strings.TrimSpace(resp.Answer) != "" || strings.TrimSpace(resp.Reply) != "")
}

Try / catch

prompt, err := opt.OptimizePrompt(ctx, cand, current)
if err != nil && strings.Contains(err.Error(), "empty prompt") {
	prompt, err = opt.OptimizePrompt(ctx, cand, current) // one retry on transient empty completion
	if err != nil { prompt = current }
}

Prevention

When it happens

Trigger: Calling OptimizePrompt when the model returns an empty string — e.g. the model hit a content filter, the response was truncated to nothing, or a misconfigured provider returned a 200 with no body content.

Common situations: Using a provider that populates a different response field than Answer/Reply; very small models that emit blank output; token limits causing empty completions; proxy/gateway stripping the response body.

Related errors


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/015f7e8e8ca3387a. Report an issue: GitHub.