linshenkx/prompt-optimizer · error · CompareValidationError

Optimized text must be a string

Error message

Optimized text must be a string

What it means

Thrown by normalizeReferencePromptPreview after the model's text is parsed and constrained: JSON.stringify of the constrained object produced nothing or the literal 'null'. This means the parsed 'object' was not a serializable record (e.g. null, undefined, or JSON.stringify returned undefined for a non-object) even though earlier parsing appeared to succeed.

Source

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

        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;
    let processedOptimized = optimized;

    if (options.ignoreWhitespace) {

View on GitHub (pinned to 3e677b1d9f)

Solutions

  1. Strengthen the extraction prompt to demand a JSON object with specific keys
  2. Add an explicit isRecord check right after JSON.parse so failures surface earlier with clearer intent
  3. Retry the request; add 'Return only a JSON object, never null' to the system prompt
  4. Validate model output against a zod/schema before normalizing

Example fix

// before
const formattedPrompt = JSON.stringify(constrainedPromptObject, null, 2)
if (!formattedPrompt || formattedPrompt === 'null') {
  throw new Error('Model response is not a valid JSON prompt object')
}

// after (reject non-records before serialization)
if (!isRecord(constrainedPromptObject)) {
  throw new Error('Model response is not a valid JSON prompt object')
}
const formattedPrompt = JSON.stringify(constrainedPromptObject, null, 2)
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check the raw model text parses to a record before normalizing
function parsesToRecord(text: string): boolean {
  try {
    return isRecord(JSON.parse(text))
  } catch {
    return false
  }
}

Type guard

function isRecord(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null && !Array.isArray(v)
}

Try / catch

try {
  const preview = await extractor.resolveReferencePromptPreview(/* ... */)
} catch (error) {
  if (error instanceof Error && error.message.includes('not a valid JSON prompt object')) {
    return retryOnceOrElse(defaultPreview)
  }
  throw error
}

Prevention

When it happens

Trigger: Calling resolveReferencePromptPreview where the model returns JSON like 'null', a bare string/number that parsed but wasn't a record, or an object whose serialization fails; constrainPromptVariables then yields null.

Common situations: Model outputs 'null' despite responseMimeType json; model wraps JSON in prose so extraction grabs the wrong fragment; upstream parse produced a non-record value; prompt changes causing the model to answer with a scalar.

Related errors


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