linshenkx/prompt-optimizer · error · CompareValidationError

Original text must be a string

Error message

Original text must be a string

What it means

Thrown by buildReferencePromptPrompts when the configured reference image template's messages contain no message with role 'user'. The extractor builds its model call from a template, and a user prompt is mandatory for the request. A template with only system/assistant messages, or a mismatched role string, triggers this.

Source

Thrown at packages/core/src/services/compare/service.ts:66

      };
    } catch (error) {
      if (error instanceof CompareValidationError) {
        throw error;
      }
      
      const errorMessage = error instanceof Error ? error.message : String(error);
      throw new CompareCalculationError(
        `Text comparison calculation failed: ${errorMessage}`
      );
    }
  }

  /**
   * 验证输入参数
   */
  private validateInput(original: string, optimized: string): void {
    if (typeof original !== 'string') {
      throw new CompareValidationError('Original text must be a string');
    }
    if (typeof optimized !== 'string') {
      throw new CompareValidationError('Optimized text must be a string');
    }
  }

  /**
   * 执行文本对比 - 使用 jsdiff
   */
  private performTextComparison(
    original: string,
    optimized: string,
    options: CompareOptions
  ): TextFragment[] {
    let diffResult: Change[];

    // 根据配置处理文本预处理
    let processedOriginal = original;

View on GitHub (pinned to 3e677b1d9f)

Solutions

  1. Ensure the template's messages include at least one entry with role exactly 'user' containing the prompt text
  2. Validate templates at save/import time with a schema check requiring a user-role message
  3. Check for role casing/whitespace issues — normalize roles before collectPromptByRole

Example fix

// before
const template = { messages: [{ role: 'system', content: 'You extract styles.' }] }

// after
const template = {
  messages: [
    { role: 'system', content: 'You extract styles.' },
    { role: 'user', content: 'Extract the visual style from this reference image: {{image}}' },
  ],
}
Defensive patterns

Strategy: validation

Validate before calling

function templateHasUserPrompt(template: { messages: Array<{ role: string; content?: string }> }): boolean {
  return template.messages.some(
    (m) => m.role?.toLowerCase() === 'user' && typeof m.content === 'string' && m.content.length > 0,
  )
}
if (!templateHasUserPrompt(template)) {
  throw new Error('Template must contain a user prompt before running extraction')
}

Type guard

type ChatMessage = { role: string; content?: string }
function isUserMessage(m: ChatMessage): m is ChatMessage & { role: 'user'; content: string } {
  return m.role === 'user' && typeof m.content === 'string' && m.content.length > 0
}

Try / catch

try {
  const prompts = buildReferencePromptPrompts(template)
} catch (error) {
  if (error instanceof Error && error.message === 'Reference image template is missing a user prompt') {
    return fixTemplateByAddingDefaultUserMessage(template)
  }
  throw error
}

Prevention

When it happens

Trigger: Calling prompts / buildReferencePromptPrompts with a reference image template whose messages array has no role==='user' entry (missing, typo'd role like 'User', or empty messages).

Common situations: Custom user-supplied templates; template schema migration where 'user' role was renamed; template loaded from storage/JSON where the user message was dropped; localization tools altering role fields.


AI-assisted analysis of linshenkx/prompt-optimizer@3e677b1d9f (2026-08-27). Data as JSON: /api/errors/b6514bbc3e62babf. Report an issue: GitHub.