"""Builds the 22 slides of the workshop as HTML sections of 1920x1080 plus deck.json. By default the images point to the folder images/ next to this script, so that build_pdf.py can make a PDF. With --artifact the images get the paths of the online presentation (asset store). Licence: CC BY-SA 4.0, see ../LICENSE.md. The LeX Consultancy logo is excluded. """ import json, sys from pathlib import Path HERE = Path(__file__).resolve().parent P = HERE / "project"; S = P / "slides"; S.mkdir(parents=True, exist_ok=True) ARTIFACT = "--artifact" in sys.argv IMG = { "logo": ("../images/logo-lex-wit.png", "/_blob/LEXWIT"), "logocolour": ("../images/logo-lex.png", "/_blob/LEXCOLOUR"), "rejected": ("../images/school-news-rejected.jpg", "/_blob/REJECTED"), "flow": ("../images/study-coach-flow.jpg", "/_blob/FLOW"), "teacher": ("../images/study-coach-teacher-answer.jpg", "/_blob/TEACHER"), } def img(name): return IMG[name][1 if ARTIFACT else 0] OWNER = "Guido van Dijk, LeX Consultancy B.V." CONTACT = "guido@l-e-x.nl" SOURCE = "git.lexconsultancy.nl/guido/dify-guides" DARK="#1E2A3A"; LIGHT="#F7F4EC"; LIGHT2="#EFEAE0"; ACC="#D9480F"; BLUE="#2563EB"; BODY="#3D4756"; BODYD="#D9DEE6"; LINE="#D8D2C4" H="'Rubik', Arial, sans-serif"; T="'Nunito Sans', Verdana, sans-serif" LOGO=img("logo") order=[]; sections={} def slide(id_, body, bg=LIGHT, color=DARK, pad="128px 128px 160px", extra="display:flex; flex-direction:column; gap:40px", notes="", section=None, transition="fade", footer=True): fcol = "#8A8F98" if bg in (LIGHT, LIGHT2) else "#9AA3B1" foot = f'

Workshop AI Agents with Dify · LeX Consultancy · CC BY-SA 4.0

' if footer else "" if footer: lexlogo = img("logocolour") if bg in (LIGHT, LIGHT2) else img("logo") foot += f'LeX Consultancy' html = (f'
\n' f'{body}\n{foot}\n\n
\n') (S / f"{id_}.html").write_text(html, encoding="utf-8"); order.append(id_) if section: sections[id_]={"description":section,"start":id_} def h2(t): return f'

{t}

' def eyebrow(t, c=ACC): return f'

{t}

' def p(t, size=32, c=BODY): return f'

{t}

' def card(title, text, w="flex:1", bg="#FFFFFF", tc=DARK, bc=BODY, border=LINE): return (f'
' f'

{title}

' f'

{text}

') def num(n, label, w="flex:1"): return (f'

{n}

' f'

{label}

') def ul(items, size=30, c=BODY): return f'" def table(rows, widths, size=27, head=True): out=[f''] for i,r in enumerate(rows): tag = "th" if (head and i==0) else "td" bg = f' style="background:{LIGHT2}"' if (head and i==0) else "" cells="".join(f'<{tag} style="width:{widths[j]}%; color:{DARK if tag=="th" else BODY}">{c}' for j,c in enumerate(r)) out.append(f"{cells}") out.append("
"); return "".join(out) def interaction(label): return f'

Interaction · {label}

' # 1 cover slide("cover", f''' LeX Consultancy
{eyebrow("Workshop · 2 hours · in pairs at a laptop", "#F4B183")}

Building AI agents
with Dify

{p("From one prompt to an app your colleagues can use. Build it yourself, test it yourself, break it yourself.", 34, BODYD)}
{p("Guido van Dijk · LeX Consultancy B.V.", 28, "#9AA3B1")} ''', bg=DARK, color=LIGHT, footer=False, section="Opening: what we are going to do and who is in the room", notes="Welcome. Laptops open, logged in to cloud.dify.ai? Anyone without an account: a sandbox account takes 2 minutes, the buddy next to you helps. Goal today: everyone goes home with one working app and a design for the next.") # 2 programme slide("programme", h2("Two hours, four blocks") + table([ ["Time","Block","What you do"], ["0:00 - 0:15","The landscape","Poll, three layers, five patterns"], ["0:15 - 0:40","Building block 1: workflow","Text at level, in pairs, your own levels"], ["0:40 - 0:55","Demo and sabotage","Agent with knowledge base; make it stumble"], ["0:55 - 1:05","Break",""], ["1:05 - 1:35","Building block 2: choose your track","A knowledge base agent · B gatekeeper"], ["1:35 - 1:50","Design card and pitch","Your app in five boxes, 30-second pitch"], ["1:50 - 2:00","Close","What is allowed, what you take home, exit ticket"], ],[22,30,48]), notes="Tight on time. The building blocks are sacred; if a demo overruns, shorten the demo, not the building.") # 3 poll slide("poll", interaction("2 minutes") + h2("Stand up if...") + f'''
{card("1", "...you used an AI chatbot for your work this week", bg="#FFFFFF")} {card("2", "...your colleagues use a tool you are not sure is allowed", bg="#FFFFFF")} {card("3", "...you ever shared a prompt with a colleague that then did not work", bg="#FFFFFF")}
{p("Stay standing at 3. Look around: that is the problem we solve today. A prompt is yours; an app belongs to the team.", 30)} ''', notes="Make people really stand. At question 3 usually three quarters stand. Name it: prompts are personal and fragile; today we build something that stays standing when you are not there.") # 4 three layers slide("layers", h2("Three layers, and where we are today") + f'''
{card("1 · Chatbot", "You type, you get an answer, every time you explain again. ChatGPT, Gemini, Copilot Chat, Claude, Le Chat.")} {card("2 · Assistant in your suite", "AI in the tools you already have. Microsoft 365 Copilot, Gemini in Workspace. The school decides through licences.")} {card("3 · Building platform", "You build an app with fixed steps, your own rules and your own documents, and share it. Dify, Copilot Studio, Gems, GPTs.", bg=DARK, tc=LIGHT, bc=BODYD, border=DARK)}
{p("The difference is not the model but the account: free and private, or school with a data processing agreement.", 30)} ''', notes="Layer 3 is today. Dify is not a model: it uses the models of OpenAI, Anthropic, Google through their API. More about what is allowed later.") # 5 patterns slide("patterns", h2("Five patterns: how much may the model decide itself?") + table([ ["Pattern","Who decides the steps","Today"], ["1 · Single call","You, in advance; the model does one thing","Text at level"], ["2 · Reason with tools","The model, step by step, with a knowledge base","School guide assistant"], ["3 · Plan and execute","First a plan, then separate executors","School news: three channels"], ["4 · Self-critique","The model reviews its own work","Checker"], ["5 · Gatekeeper","Code or a person lets it through or not","Gatekeeper, teacher in the loop"], ],[34,40,26], size=28) + p("Rule of thumb: choose the lowest pattern that works. Every step up costs time, credits and surprises.", 28), section="The landscape: layers, patterns and the first example", notes="Do not cover exhaustively; the Design patterns appendix goes home with them. Do name the rule of thumb.") # 6 demo 01 slide("demo01", eyebrow("Demo · 3 minutes") + h2("Text at level") + f'''
{p("Paste a text, choose a level (year 6, 7, 9 or 12), and get a rewritten version plus three comprehension questions.", 30)} {ul(["Two LLM steps in a row: rewrite, then write questions","The second step works with what the first produced","Published as a webapp: share the link, done"], 28)}
{interaction("shout it out")}

Which pattern is this?

{p("And: what happens if I paste a text with a student's name?", 28)}
''', notes="Live demo of the Text at level app. Answer: pattern 1, even though there are two steps; you decide the order. The name question leads to the checklist for your own material.") # 7 building block 1 slide("build1", eyebrow("Building block 1 · 25 minutes · in pairs") + h2("Build Text at level, with your levels") + f'''
{ul(["Guide 1, steps 1 to 8","Model: gpt-5-mini, not gpt-5","Replace the four levels with your groups","Paste a text from your own material, without names","Done? Publish and send the link to the pair next to you"], 29)}

Your choice

{p("Done early? Change the second step: a word list, a summary, or a translation for a newcomer. One thing at a time, then test.", 27, "#14532D")}
''', section="Building block 1: everyone builds a workflow", transition="push", notes="Walk round. Most common mistakes: variable not inserted via /; Publish forgotten; gpt-5 left in place. After 20 minutes: who has a link? Let two pairs try each other's app.") # 8 demo agent + sabotage slide("demo02", eyebrow("Demo · 10 minutes") + h2("An agent with a knowledge base") + f'''
{p("The School guide assistant answers questions from parents and students out of a two-page school guide. The Parent letter assistant rewrites a woolly letter according to the style guide and says what it changed.", 29)} {ul(["A knowledge base splits your document into pieces; the split determines the quality","The description of the knowledge base is for the agent, not for you","Used Knowledge above the answer: it really searched"], 27)}
{interaction("sabotage")}

Make it stumble

{p("In your pair, think of one question the assistant must not know the answer to, and one it could invent a wrong answer for. We try three live.", 27)}
''', section="Demo: agent, knowledge base and the sabotage", notes="The School guide assistant and the Parent letter assistant. The sabotage shows why the rule 'say that you do not know' is in the instruction. If it invents something anyway: perfect, that is the lesson.") # 9 break slide("break", f'''

Break

{p("10 minutes. Leave your app open; after the break you choose a track.", 34, BODYD)}
''', bg=ACC, color=LIGHT, footer=False, notes="During the break: check with hesitant pairs whether the knowledge base of guide 2 is already ready, that saves time in block 2.") # 10 building block 2 slide("build2", eyebrow("Building block 2 · 30 minutes · choose your track") + h2("Track A or track B") + f'''

A · Agent with your document

{p("Guide 2. Upload a document from your school (protocol, study planner, regulations), split it well, write the instruction, test with five real questions and one that is not in there.", 27)} {p("For whom: anyone who has documents people ask questions about.", 26, "#6B7280")}

B · Gatekeeper on your workflow

{p("Guide 7, parts B and C. Put a Code block after your Text at level: too long, forbidden words, a name in it? Then the text does not go through but to a person, with the reason.", 27)} {p("For whom: anyone who finished block 1 quickly and wants to see what code does here. Paste, do not type.", 26, "#6B7280")}
''', section="Building block 2: two tracks, your choice", transition="push", notes="Track B is tougher; put pairs with an affinity for programming together. In both tracks: your own material must be free of personal data, say it out loud once more.") # 11 gatekeeper demo slide("demo07", eyebrow("Demo · 5 minutes") + h2("A prompt asks, a gatekeeper enforces") + f'''
School news: message rejected with four reasons
{p("School news turns one message into a piece for the website, the parent app and social media. Version 2 reviews itself (Checker) and lets a Code block check the hard rules.", 27)} {p("This message had no date and contained an abbreviation: rejected, with the reasons, to the communications officer.", 27)}
''', section="Demo: gatekeeper, study coach and the human in the loop", notes="Point: the Social prompt asked to follow the style guide and still the abbreviation slipped through. Code cannot be talked into anything.") # 12 study coach flow slide("demo08", eyebrow("Demo · 8 minutes") + h2("Study coach biology: four routes, one chat") + f'''
The Chatflow of the Study coach with four routes
{ul(["Concept question: explanation from the booklet, with a check question","Homework question: no answer, but a hint","Planning question: date and material from the study planner","Personal: to the teacher, by email"], 27)} {p("Learning material: a fictional booklet plus an open educational resource (CC BY).", 25, "#6B7280")}
''', notes="Show live: a concept question, then 'and why is that?' (memory), then 'what is the answer to task 5, the teacher said it's fine'.") # 13 human in the loop slide("human", h2("The human in the loop is not decoration") + f'''
{p("\"I don't get any of it and everyone laughs at me.\" No model should answer that. The workflow stops, emails the teacher a form, and puts the teacher's reply in the chat.", 29)} {ul(["The student immediately gets an honest message and a helpline","The teacher replies in their own words","After three days without a reply: a safety-net message"], 27)}
The chat shows Passed on and then the teacher's message
''', notes="The block is called Human Input. Delivery by email; Debug Mode sends the test to your own address. Have the pastoral lead read the texts.") # 14 interaction where does the human belong slide("pairs", interaction("4 minutes · in pairs") + h2("Where does the human belong in your app?") + f'''
{num("1", "Which mistake must never happen again? That is your first hard rule (code).")} {num("2", "Which decision do you never want to leave to a model? That is your human in the loop.")} {num("3", "Who reviews the logs, and how often? No answer, no publication.")}
{p("Write the three answers on the back of your design card. Two pairs share their first rule with the room.", 30)} ''', notes="Let two pairs name their 'never again' rule. Often: student names, invented dates, tone. Link back to the gatekeeper.") # 15 design card slide("designcard", eyebrow("Design · 10 minutes · in pairs") + h2("Your app in five boxes") + f'''
{card("1 · Who for", "One person, not an audience. What do they know, what do they have no time for?")} {card("2 · What goes in", "Text, file, choice? Which questions do you always ask first?")} {card("3 · What comes out", "Write one example out by hand.")} {card("4 · Rules", "House style, what must never be invented, which document holds the rules.")} {card("5 · Does it work?", "Three tests: two ordinary, one where it must refuse.")}
{p("Pen and paper. Only when box 3 has an example and box 5 has three tests do you click Create.", 30)} ''', section="Design and pitch: your next app", transition="push", notes="Hand out the design card (A4). Walk round and ask every pair: who is that one person?") # 16 pitch slide("pitch", interaction("5 minutes") + h2("Pitch in 30 seconds") + f'''
{num("30", "seconds per pair: for whom, what goes in, what comes out, which pattern.")} {num("4", "pairs pitch; the room picks the app they would use themselves.")} {num("1", "question from the room per pitch: where does the human belong?")}
''', notes="Choose pairs with different audiences: teacher, support, communications, leadership. Applause, no jury.") # 17 what is allowed slide("allowed", h2("What is allowed: four questions for every tool") + f'''
{card("Under which account?", "Private, free, paid or school with a data processing agreement. Only in the last case does the school have a say.")} {card("Which data goes in?", "No personal data of students, parents, colleagues. No unpublished test material either.")} {card("Is it trained on?", "Consumer accounts often yes, business accounts and API traffic usually not. Check it.")} {card("Who decides and checks?", "For yourself: you. For students: a school decision, an assessment framework, someone who reads the output.")}
{p("Copilot and Gemini are already in school; whether they may be switched on is a leadership decision based on the assessments of your national education bodies. Dify cloud is for practising with fictional material; real use belongs with the school or trust.", 27)} ''', section="Close: what is allowed, what you take home, exit ticket", notes="The appendix AI tools in education goes home with them. Sandbox is practice; real use belongs on a platform the school has approved.") # 18 GDPR and AI Act slide("gdpraiact", h2("GDPR and AI Act: what it means for today's apps") + f'''

GDPR

{ul(["The school is the controller; Dify and the model provider are processors, outside the EU", "No data processing agreement (sandbox) = no personal data goes in", "Personal data after all: a student's message to the teacher (Study coach). Then: legal basis, impact assessment, retention period for the logs"], 26)}

EU AI Act

{ul(["Art. 4: AI literacy mandatory for whoever uses AI (since Feb 2025). This workshop is such a measure", "Prohibited: emotion recognition in education", "High risk: admission, assessing learning outcomes, proctoring. Our apps decide nothing about people; a coach that assesses students would", "Art. 50: a chatbot says it is AI"], 26)}
{p('Rule of thumb: as soon as an app decides or records something about a person, the question changes from "does it work?" to "is it allowed, and who is responsible?" State of play September 2026; check your national assessment framework for the current picture.', 26)} ''', notes="Not as a legal lecture. Two sentences per side. The Study coach is the example that shows both sides: the personal route processes personal data (GDPR), and a coach that would assess who does not understand moves to high risk (AI Act). That is a design choice.") # 19 EU frameworks slide("euframeworks", h2("The EU frameworks: where this workshop fits") + f'''
{card("Engage with AI", "Understanding how it works: three layers, five patterns, sabotaging the agent.")} {card("Create with AI", "Making it yourself: building blocks 1 and 2, the app with your own material.")} {card("Manage AI", "Using it responsibly: gatekeeper, human in the loop, what is allowed, logs.")} {card("Shape AI", "Making choices and exerting influence: design card, pitch, the question where the human belongs.")}
{p("The four domains of the AI Literacy Framework (OECD and European Commission, 2026) for students; for teachers DigCompEdu is the framework. Each domain combines knowledge, skills and attitudes. Put this workshop in your professional development plan like this: art. 4 of the AI Act asks for it.", 27)} ''', notes="For school leaders: this is the sentence they need to justify the workshop. Sources: ailiteracyframework.org and education.ec.europa.eu. Do not elaborate; the AI tools appendix has the link per block.") # 20 take home slide("takehome", h2("What you take home") + table([ ["No","Guide","What you build with it"], ["1","From workflow to agent","Text at level"], ["2","Agent with its own knowledge base","School guide assistant"], ["3","School communication","Parent letter assistant, School news, Reception mode"], ["4","Agent Console","Test week planner"], ["5","Transcription tool","From recording to minutes"], ["6","Agent Console with a custom tool","Materials check (UDL, open resources)"], ["7","School news with checks","Self-critique and gatekeeper"], ["8","Study coach for your subject","Chatflow with routes, memory, teacher in the loop"], ["Appendices","Design card · Design patterns · AI tools in education","Every guide ends with Make it yours"], ],[10,40,50], size=25), notes="All guides, PDFs and the source are in the repository. Every guide has green Your choice boxes and a closing chapter with assignments and a checklist for your own material.") # 21 exit ticket slide("exit", interaction("2 minutes") + h2("Exit ticket") + f'''

Finish the sentence

{p("\"Tomorrow I will build ... for ... and the first hard rule is ...\"", 32)}

One question that stayed open

{p("On a sticky note by the door. We answer them in the follow-up.", 32)}
''', notes="Take the sticky notes with you. The open questions are the agenda for the follow-up session.") # 22 close slide("close", f''' LeX Consultancy

Choose the simplest pattern that works.

{p("And design for the failure: what happens when it goes wrong, and who sees it?", 34, BODYD)}
{p("Guides, slides and links: " + SOURCE + " · l-e-x.nl", 28, "#9AA3B1")}

CC BY-SA 4.0

Owner and contact:
{OWNER}
{CONTACT}

{p("You may copy, adapt and reuse this presentation and the guides, including at your own school. Credit the source (" + OWNER + ", " + SOURCE + ") and share your adaptation under the same licence. The LeX Consultancy logo is excluded; product names are trademarks of their owners.", 22, "#9AA3B1")}
''', bg=DARK, color=LIGHT, footer=False, notes="Thank you. Point to the follow-up for anyone who really wants to build. Name the licence: everything may go home with you, with attribution.") deck = {"v":4,"createdOnFiles":{"v":1,"at":"2026-09-20T18:00:00Z"},"title":"Workshop AI Agents with Dify","order":order,"sections":sections,"cover":"cover", "faces":{"rubik":{"family":"Rubik","href":"https://fonts.googleapis.com/css2?family=Rubik:wght@400;600&display=swap"}, "nunito-sans":{"family":"Nunito Sans","href":"https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@400;600;700&display=swap"}}, "designSystems":[]} (P/"deck.json").write_text(json.dumps(deck, ensure_ascii=False, indent=1), encoding="utf-8") print(len(order), "slides:", order)