micro/go-micro · error

flow: LLMOptimizer requires a model

Error message

flow: LLMOptimizer requires a model

What it means

The PromptOptimizer was constructed without a backing AI model, so OptimizePrompt cannot generate a revised prompt. The check also covers calling the method on a nil receiver. The library refuses to proceed rather than panicking on a nil model.

Source

Thrown at flow/analyze.go:159

	runs, graded, gradeFailures, errors, retries int
	feedback, runIDs                             []string
	latencies                                    []time.Duration
}

// PromptOptimizer proposes prompt improvements for a candidate without mutating
// the source flow. Applying the returned prompt stays explicitly gated by the caller.
type PromptOptimizer struct{ model ai.Model }

// LLMOptimizer returns an optimizer that asks model to revise prompts for
// Analyze candidates. The model is injected so tests and callers can use mocks.
func LLMOptimizer(model ai.Model) *PromptOptimizer { return &PromptOptimizer{model: model} }

// 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 == "" {

View on GitHub (pinned to 24529f1404)

Solutions

  1. Create the optimizer with a valid model: NewLLMOptimizer(ai.New("openai", ai.WithAPIKey(key))).
  2. Set Provider and APIKey (and optionally BaseURL) in flow options so the model is initialized.
  3. Check the ai.New return value for nil before wiring it into the optimizer.
  4. Skip optimization paths when no model is configured instead of calling OptimizePrompt.

Example fix

// before
opt := &flow.PromptOptimizer{}
newPrompt, err := opt.OptimizePrompt(ctx, cand, prompt)
// after
opt := flow.NewLLMOptimizer(ai.New("openai", ai.WithAPIKey(os.Getenv("OPENAI_API_KEY"))))
newPrompt, err := opt.OptimizePrompt(ctx, cand, prompt)
Defensive patterns

Strategy: validation

Validate before calling

if optimizer == nil || reflect.ValueOf(optimizer).IsZero() {
	return errors.New("prompt optimizer not configured with a model")
}

Type guard

func (o *PromptOptimizer) Ready() bool { return o != nil && o.model != nil }

Try / catch

prompt, err := opt.OptimizePrompt(ctx, cand, current)
if err != nil && strings.Contains(err.Error(), "requires a model") {
	return current, nil // fall back to the unchanged prompt
}

Prevention

When it happens

Trigger: Calling OptimizePrompt on a PromptOptimizer built without NewLLMOptimizer(model) — e.g. constructed with a nil model, or when model initialization was skipped because ai.New returned nil for an unknown provider.

Common situations: Configuring a flow without Provider/APIKey so no model is created; passing a nil model deliberately for testing; using a pointer to PromptOptimizer that was never initialized.

Related errors


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