{"record":{"id":"c7bc25a1b7eee86d","repo":"instructure/canvas-lms","slug":"flashcard-item-malformed","errorCode":null,"errorMessage":"Flashcard item malformed","messagePattern":"Flashcard item malformed","errorType":"exception","errorClass":"CedarUnavailable","httpStatus":null,"severity":"error","filePath":"app/services/study_assist.rb","lineNumber":311,"sourceCode":"        question = item[:question] || item[\"question\"]\n        options = item[:options] || item[\"options\"]\n        result = item[:result] || item[\"result\"]\n        raise CedarUnavailable, \"Quiz item malformed\" if question.blank? || options.blank? || result.nil?\n\n        { question:, answers: options, correctAnswerIndex: result.to_i }\n      end\n\n      { quizItems: quiz_items }\n    end\n\n    def build_flashcards_response(raw)\n      cards = parse_json_array!(raw)\n      raise CedarUnavailable, \"Flashcards response missing items\" if cards.blank? || !cards.is_a?(Array)\n\n      flash_cards = cards.first(10).map do |card|\n        question = card[:question] || card[\"question\"]\n        answer = card[:answer] || card[\"answer\"]\n        raise CedarUnavailable, \"Flashcard item malformed\" if question.blank? || answer.blank?\n\n        { question:, answer: }\n      end\n\n      { flashCards: flash_cards }\n    end\n\n    def parse_json_array!(raw)\n      parsed = InstLLMHelper.extract_json_array(raw.to_s)\n      raise CedarUnavailable, \"Invalid JSON response from Cedar\" if parsed.nil?\n\n      parsed\n    end\n  end\nend\n","sourceCodeStart":293,"sourceCodeEnd":327,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/study_assist.rb#L293-L327","documentation":"Guard in StudyAssist#build_flashcards_response: a flashcard item in the parsed LLM response is missing question, options, or a result value, so CedarUnavailable 'Flashcard item malformed' is raised — the AI output didn't match the expected shape.","triggerScenarios":"build_response -> build_flashcards_response when any element of the parsed cards array lacks :question/:\"question\" or :answer/:\"answer\", or has a blank value for either.","commonSituations":"The LLM returns cards with only a question, or wraps answer text in a different key (e.g. 'solution'); model upgrades changing the emitted schema; truncation cutting off the last card's answer mid-JSON.","solutions":["Retry generation — a single malformed card usually disappears on a fresh call.","Log the malformed card to identify which key the model actually used, then normalize that key in the mapping.","Strengthen the prompt to specify exact JSON keys question and answer per card.","If truncation is the cause, reduce requested card count or increase output token limits."],"exampleFix":"// before\ncards.map { |c| { question: c[:question], answer: c[:answer] } }\n# blank answers -> Flashcard item malformed\n\n// after\ncards.map { |c| { question: c[:question] || c['question'], answer: c[:answer] || c['answer'] || c['solution'] } }","handlingStrategy":"validation","validationCode":"CARD_SCHEMA = ->(c) {\n  q = c[:question] || c['question']\n  a = c[:answer] || c['answer']\n  q.present? && a.present?\n}\npre_ok = parsed_cards.all?(&CARD_SCHEMA)","typeGuard":"def valid_card?(card)\n  q = card[:question] || card['question']\n  a = card[:answer] || card['answer']\n  q.is_a?(String) && q.present? && a.is_a?(String) && a.present?\nend","tryCatchPattern":"begin\n  cards = service.call(tool: :flashcards)\nrescue StudyAssist::CedarUnavailable\n  Rails.logger.warn('flashcard item malformed; regenerating')\n  cards = service.call(tool: :flashcards)\nend","preventionTips":["Validate question/answer presence per card before rendering","Normalize alternate answer keys (e.g. 'solution') if the model uses them","Reduce requested card count if outputs get truncated"],"tags":["llm","flashcards","schema","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}