{"record":{"id":"e1d74fef3b5bb738","repo":"jamiepine/voicebox","slug":"each-example-must-be-a-user-assistant-pair","errorCode":null,"errorMessage":"Each example must be a [user, assistant] pair","messagePattern":"Each example must be a \\[user, assistant\\] pair","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"backend/routes/llm.py","lineNumber":60,"sourceCode":"                task_manager.error_download(progress_model_name, str(e))\n\n        task_manager.start_download(progress_model_name)\n        create_background_task(download_llm_background())\n\n        return JSONResponse(\n            status_code=202,\n            content={\n                \"message\": f\"Qwen3 {model_size} is being downloaded. Please wait and try again.\",\n                \"model_name\": progress_model_name,\n                \"downloading\": True,\n            },\n        )\n\n    examples: list[tuple[str, str]] | None = None\n    if request.examples:\n        for pair in request.examples:\n            if len(pair) != 2:\n                raise HTTPException(\n                    status_code=400,\n                    detail=\"Each example must be a [user, assistant] pair\",\n                )\n        examples = [(pair[0], pair[1]) for pair in request.examples]\n\n    try:\n        text = await backend.generate(\n            prompt=request.prompt,\n            system=request.system,\n            max_tokens=request.max_tokens,\n            temperature=request.temperature,\n            model_size=model_size,\n            examples=examples,\n        )\n        return models.LLMGenerateResponse(text=text, model_size=model_size)\n    except Exception as e:\n        # The backend exception text can include filesystem paths and stack\n        # frames — log it server-side and hand the client a generic message.","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/llm.py#L42-L78","documentation":"Returned by POST /llm/generate when request.examples contains an inner list whose length is not exactly 2. examples is typed Optional[List[List[str]]] with max_length=8; the schema guarantees a list-of-lists-of-strings but cannot enforce the inner arity, so this route-level check enforces the [user, assistant] pair contract before the pairs are zipped into chat turns.","triggerScenarios":"POST /llm/generate with examples like [[\"u1\",\"a1\",\"extra\"]] (3 elements), [[\"only-user\"]] (1 element), or [[]] (0 elements). A single malformed pair aborts the whole request before generation.","commonSituations":"Refinement service assembled a triple by mistake (user, assistant, rationale); client serialized a single string instead of a pair; prompt template builder emitted an empty placeholder pair; JSON edit left a dangling comma producing a 3-element array.","solutions":["Ensure every entry in examples is exactly [userText, assistantText] — two non-empty strings.","Validate the shape on the client before sending: pairs.every(p => Array.isArray(p) && p.length === 2).","If you need richer few-shot metadata (labels, tags), keep it out of examples — those pairs map directly onto chat messages.","Reduce examples to <=8 pairs to also satisfy the max_length constraint on the field."],"exampleFix":"// before\nbody: {prompt, examples: [['q','a','why']]}\n// after\nbody: {prompt, examples: [['q','a']]}","handlingStrategy":"validation","validationCode":"function validateExamples(ex?: string[][]) {\n  if (!ex) return undefined;\n  if (ex.length > 8) throw new Error('At most 8 example pairs');\n  for (const p of ex) {\n    if (!Array.isArray(p) || p.length !== 2 || typeof p[0] !== 'string' || typeof p[1] !== 'string') {\n      throw new Error('Each example must be a [user, assistant] pair');\n    }\n  }\n  return ex as [string, string][];\n}","typeGuard":"type ChatPair = [string, string];\nfunction isChatPair(p: unknown): p is ChatPair {\n  return Array.isArray(p) && p.length === 2 && p.every(v => typeof v === 'string');\n}\nconst isExamples = (x: unknown): x is ChatPair[] =>\n  Array.isArray(x) && x.length <= 8 && x.every(isChatPair);","tryCatchPattern":"if (payload.examples && !isExamples(payload.examples)) {\n  // surface inline error in the UI; do not send\n} else {\n  await fetch('/llm/generate', {method:'POST', body: JSON.stringify(payload)});\n}","preventionTips":["Construct examples from typed [user, assistant] tuples in the client, never from free-form arrays.","Cap at 8 pairs client-side to satisfy both the max_length field constraint and the pair-shape rule.","Unit-test the few-shot builder to guarantee arity before serializing."],"tags":["llm","validation","few-shot","fastapi","http-400"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}