Rebuild the pipeline on LangChain
Assemble research, the zod outline, deck writing and the repair loop into one LangChain pipeline that takes a model as a parameter, replacing the hand-written generate.js.
You have a model factory, a structured outline and web research. This lesson connects them with the deck writing and repair loop from module 7 into one function: generateDeck(topic, options).
With AI (Gemini)
Topic
“cobots in small factories”
Research
Google Search, optional
Outline
structured JSON
SlideML
<CardsSlide>…
Without AI (your engine)
Parse
text → tree
Components + theme
short tags → layout
Layout + text fitting
exact boxes
.pptx file
pptxgenjs
The steps
- Research, only if
options.researchis on. - Outline with
generateOutline, passing the research notes. - Write the deck with
writeDeck(written for you): system prompt plus outline, extract the<deck>. - Compile and repair: the loop from lesson 7.3, now passing the model to
repairDeck.
The model is a parameter
Every step receives model instead of creating one. generateDeck uses createModel() when you don't pass one. In the fallback models lesson that lets the pipeline switch to a backup model, and in module 11 it lets tests pass a that needs no network at all.
Your project so far
18 files · 1 new or changed in this lesson
src/pipeline.js
// The whole generator, rebuilt on LangChain chat models:
//
// (optional) research → outline → write the deck → compile → repair what's broken
//
// It replaces generate.js. The steps are the same; what changed is that each
// step receives a `model`, so we can swap in a different model, or a fake one in tests.
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
import { contentText, createModel } from "./llm.js";
import { generateOutline } from "./outline.js";
import { researchTopic } from "./research.js";
import { buildSystemPrompt, extractTag } from "./prompt.js";
import { LIBRARY } from "./library.js";
import { compileDeck } from "./compile.js";
const MAX_REPAIR_ROUNDS = 2;
/** Outline → SlideML, following the component library. */
export async function writeDeck(model, outline) {
const reply = await model.invoke([
new SystemMessage(buildSystemPrompt(LIBRARY)),
new HumanMessage(`Write the deck for this outline:\n${JSON.stringify(outline, null, 2)}`),
]);
const xml = extractTag(contentText(reply), "deck");
if (!xml) throw new Error("The model didn't reply with a <deck> element.");
return xml;
}
/** Send the deck back with the compiler's problems and ask for a corrected deck. */
export async function repairDeck(model, xml, problems) {
const list = problems.map((problem) => `- Slide ${problem.slide}: ${problem.message}`).join("\n");
const reply = await model.invoke([
new SystemMessage(buildSystemPrompt(LIBRARY)),
new HumanMessage(`This deck has problems:\n${list}\n\nFix them and reply with the whole corrected <deck>.\n\n${xml}`),
]);
return extractTag(contentText(reply), "deck");
}
/**
* options:
* model a LangChain chat model (default: createModel())
* research true to search the web first
* slideCount how many slides to plan (default 6)
* theme "light" or "dark"
* compile the compiler to use (tests pass a fake one)
*/
export async function generateDeck(topic, options = {}) {
const { model = createModel(), research = false, slideCount = 6, theme, compile = compileDeck } = options;
const findings = research ? await researchTopic(model, topic) : { notes: "", sources: [] };
const outline = await generateOutline(model, topic, { slideCount, research: findings.notes });
let xml = await writeDeck(model, outline);
let result = compile(xml, theme);
let repairs = 0;
while (result.problems.length > 0 && repairs < MAX_REPAIR_ROUNDS) {
repairs += 1;
const fixed = await repairDeck(model, xml, result.problems);
if (!fixed) break;
const next = compile(fixed, theme);
if (next.problems.length >= result.problems.length) break;
xml = fixed;
result = next;
}
return { outline, xml, slides: result.slides, problems: result.problems, repairs, sources: findings.sources };
}Key takeaways
- pipeline.js runs research, outline, deck and repair, with the model passed in.
- Passing the model as a parameter makes it easy to swap models, or use a fake in tests.
- The steps are the same as generate.js: the architecture didn't change, only the model API did.
Sign in to run the exercise
Reading is free. Writing code here needs an account so we have somewhere to keep your Gemini key and the +40 XP you are about to earn.