Skip to content

LMS AI Authoring

Ed Mozley edited this page Jul 12, 2026 · 2 revisions

LMS AI Authoring

The mechanics of the three AI helpers in the course editor β€” how FreeITSM turns a knowledge article into a lesson, drafts a course outline from a topic, and writes quiz questions from a lesson you've written. Companion to LMS Authoring; the shared provider plumbing is on AI Providers.

All three live in one endpoint, api/lms/ai_author.php, gated by lms.manage. They share everything except the prompt: the same provider config, the same JSON contract, the same error handling. Each returns a draft β€” and that word carries the whole safety model.


The one rule: the AI never publishes

Every helper returns a draft that lands in the editor for the author to read and change. Nothing is written to the database by the AI. When the author presses Save, the draft goes through the same validated endpoints a hand-typed lesson or question uses β€” so the AI cannot create a course nobody looked at, a lesson full of markup it shouldn't emit, or (critically) a question with no correct answer. The model is a drafting assistant behind the normal front door, not a privileged writer.

This is why, for example, AI-generated quiz questions are still subject to the "at least one correct answer, and a single-choice question can't have two" checks in api/lms/questions.php (see LMS Authoring β†’ validation). A malformed generation is rejected exactly as a malformed hand edit would be.


Shared plumbing

$cfg = aiSettingsLoad($conn, 'lms_ai');          // provider, model, decrypted key
if (($cfg['api_key'] ?? '') === '') throw new Exception('LMS AI is not configured…');

$resp  = aiProviderChat($cfg, ['system' => $system, 'user' => $user, 'max_tokens' => …]);
$draft = parseClaudeJson(trim((string)($resp['content'] ?? '')));
  • lms_ai is the config namespace (provider + model + encrypted key), set on LMS β†’ Settings. See AI Providers for how namespaces, encryption and the aiProviderChat() client work. Unconfigured β†’ the helper throws a friendly error and the editor still works by hand.
  • parseClaudeJson() (api/cmdb/_ai_helpers.php) is the shared, tolerant JSON extractor β€” it copes with a model that wraps its JSON in prose or fences. Every mode asks the model to "Respond ONLY as JSON" with an explicit shape, then parses it, then validates the shape before trusting it.
  • A shared house prompt sets the voice for all three: "an experienced instructional designer writing internal IT training… plainly and concretely, in British English… Never invent product features, policies or facts that were not given to you." That last clause is the spine of the whole feature.

Mode 1 β€” Outline (topic β†’ lesson skeleton)

Input: a topic and a lesson count (2–12). Output: {title, description, lessons: [{title, summary}]}.

The model is asked for exactly N lessons that build on each other in a sensible teaching order. Back in the editor (lms-editor.js), each returned lesson is created as a real but empty lesson whose body is just the AI's one-line summary wrapped in a <p> β€” a skeleton to write into, explicitly not finished content. The author then fills each lesson (by hand, or with Mode 2).

The outline is the one helper that does create rows β€” but only empty lesson shells, each saved through the normal lesson endpoint. It's scaffolding, not content.

Mode 2 β€” Lesson (article or title β†’ lesson body)

Input: a knowledge article_id, and/or a lesson title. Output: {title, body} where body is simple HTML.

This mode has two genuinely different prompts, because the two jobs have different risk profiles:

Source Prompt stance Why
A knowledge article (grounded) "Rewrite the source material below as a lesson that TEACHES it… Keep every fact from the source; add none of your own." The article is known-good content you wrote. Rewriting it into teaching material is safe and grounded.
Just a title (ungrounded) "Because you have no source material, stay at the level of general good practice and do NOT invent company-specific policies, names, systems or figures." With nothing to ground on, the model is explicitly fenced away from inventing specifics it can't know.

When grounded, the article's HTML is reduced to plain text server-side (strip_tags β†’ decode entities β†’ collapse whitespace, capped at ~12k chars) before it's handed to the model β€” the same plain-text reduction the Knowledge "Ask AI" uses. The output body is constrained to a small HTML allowlist (<p>, <h3>, lists, <strong>/<em>, <code> β€” no <script>, no styles, no images) so it drops cleanly into TinyMCE. The draft appears in the editor; nothing is saved until the author presses Save.

Turning your knowledge base into training is the headline use. Your documented fixes become your onboarding, and the AI works from something true rather than hallucinating a course about nothing β€” which is the whole reason the grounded path exists.

Mode 3 β€” Quiz (lesson β†’ questions)

Input: a saved lesson_id and a question count (1–10). Output: {questions: [{question_text, question_type, explanation, answers: [{answer_text, is_correct}]}]}.

The lesson's own text is reduced to plain text and put in the prompt, and the model is told every question must be answerable from the lesson text alone β€” "test whether someone has UNDERSTOOD the lesson, not whether they can remember a phrase from it", with plausible wrong options rather than obvious filler. Grounding in the lesson is the trick: a question the lesson doesn't answer is worse than no question.

Because a model can still return something malformed, the endpoint cleans and filters before handing the draft back:

  • drop any question with fewer than two answers, or with no correct answer;
  • if a question is marked single but has several correct answers, relabel it multiple (trust the key, fix the label);
  • keep only single / multiple / truefalse as the type.

Anything that survives is offered to the author, who saves it through questions.php β€” where the same validation runs again, server-side, as a belt-and-braces final gate. A generated answer key that's still wrong never reaches a learner.


Why this shape

  • One endpoint, three prompts β€” the modes differ only in the instruction and the JSON shape, so sharing the config/parse/error path keeps them consistent and cheap to maintain.
  • Draft-not-write β€” the AI's output is always reviewable and always re-validated on save, so the blast radius of a bad generation is "the author sees a poor draft", never "a learner is graded against a broken quiz".
  • Grounding over generation β€” the helpers are strongest when pointed at something true (an article, a written lesson) and are explicitly fenced when they aren't, which is what keeps the training trustworthy.

Related pages

  • LMS Authoring β€” the editor, the content model, and the save-side validation these helpers rely on
  • AI Providers β€” namespaces, encrypted keys, and the aiProviderChat() client
  • Knowledge β€” the article source for grounded lessons
  • LMS β€” the module hub

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally