{"record":{"id":"2850ea1750c569d5","repo":"linshenkx/prompt-optimizer","slug":"variables-index-is-missing-a-valid-position","errorCode":null,"errorMessage":"variables[${index}] is missing a valid \"position\" object.","messagePattern":"variables\\[(.+?)\\] is missing a valid \"position\" object\\.","errorType":"validation","errorClass":"VariableExtractionParseError","httpStatus":null,"severity":"error","filePath":"packages/core/src/services/variable-extraction/service.ts","lineNumber":223,"sourceCode":"    }\n\n    // 标准化每个变量\n    const variables: ExtractedVariable[] = data.variables.map((variable: any, index: number) => {\n      // 验证必需字段\n      if (!variable || typeof variable !== 'object') {\n        throw new VariableExtractionParseError(`variables[${index}] is not a valid object.`);\n      }\n\n      if (typeof variable.name !== 'string' || !variable.name.trim()) {\n        throw new VariableExtractionParseError(`variables[${index}] is missing a valid \"name\" field.`);\n      }\n\n      if (typeof variable.value !== 'string') {\n        throw new VariableExtractionParseError(`variables[${index}] is missing a valid \"value\" field.`);\n      }\n\n      if (!variable.position || typeof variable.position !== 'object') {\n        throw new VariableExtractionParseError(`variables[${index}] is missing a valid \"position\" object.`);\n      }\n\n      if (typeof variable.position.originalText !== 'string') {\n        throw new VariableExtractionParseError(\n          `variables[${index}].position is missing a valid \"originalText\" field.`\n        );\n      }\n\n      if (typeof variable.position.occurrence !== 'number') {\n        throw new VariableExtractionParseError(\n          `variables[${index}].position is missing a valid \"occurrence\" number.`\n        );\n      }\n\n      if (typeof variable.reason !== 'string') {\n        throw new VariableExtractionParseError(`variables[${index}] is missing a valid \"reason\" field.`);\n      }\n","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/linshenkx/prompt-optimizer/blob/3e677b1d9f7e0493c142c175560531e7ae786dce/packages/core/src/services/variable-extraction/service.ts#L205-L241","documentation":"Thrown by normalizeExtractionResponse in the variable-extraction service when a variable in the LLM's extraction response is missing its position object or position is not an object. The service strictly validates the LLM's JSON output shape before returning it, because position data (originalText + occurrence) is required to locate the variable inside the prompt. Any deviation from the expected schema raises VariableExtractionParseError.","triggerScenarios":"Calling parseExtractionResult with an LLM response where variables[i] has no 'position' key, position is null/undefined, or position is a string/number instead of an object (e.g. the model returned \"position\": \"paragraph 2\").","commonSituations":"Weaker LLM models omitting nested objects; prompt template drift after a version upgrade changing the output schema; the model wrapping position differently (e.g. position: { location: {...} }); truncated JSON from max_tokens limits.","solutions":["Inspect the raw LLM response logged before normalization to see what shape 'position' actually has","Tighten the extraction prompt/template to explicitly require {\"position\": {\"originalText\": \"...\", \"occurrence\": N}} for every variable","Use a stronger model or lower temperature so the JSON schema is followed more reliably","Catch VariableExtractionParseError and retry generation once before failing","If you control the response, pre-validate/repair the payload before passing it to parseExtractionResult"],"exampleFix":"// before\nconst result = await extractionService.parseExtractionResult(rawLlmText);\n\n// after\nconst parsed = JSON.parse(rawLlmText);\nfor (const v of parsed.variables ?? []) {\n  if (!v.position || typeof v.position !== 'object') {\n    throw new Error(`missing position for variable ${v.name}`); // or repair: v.position = { originalText: v.value, occurrence: 0 }\n  }\n}\nconst result = await extractionService.parseExtractionResult(rawLlmText);","handlingStrategy":"type-guard","validationCode":"const ok = (parsed.variables ?? []).every(v => v.position && typeof v.position === 'object' && !Array.isArray(v.position));","typeGuard":"function hasValidPosition(v: any): v is { name: string; position: Record<string, unknown> } {\n  return !!v && typeof v === 'object' && !!v.position && typeof v.position === 'object' && !Array.isArray(v.position);\n}","tryCatchPattern":"try { const r = service.parseExtractionResult(text); } catch (e) { if (e instanceof VariableExtractionParseError && /position/.test(e.message)) { /* re-prompt or repair */ } else throw e; }","preventionTips":["Include a complete one-shot example of the variables array (with position.originalText and position.occurrence) in the extraction prompt","Log raw LLM output when parse errors spike to catch schema drift early"],"tags":["llm-output","schema-validation","variable-extraction","json"],"backgroundTag":"llm-response-schema-invalid","analyzedSha":"3e677b1d9f7e0493c142c175560531e7ae786dce","analyzedAt":"2026-08-27T21:29:16.709Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}